Asp.NET MVC 3 не будет разрешать маршруты для области MVC, загруженной вне каталога bin - PullRequest
1 голос
/ 19 сентября 2011

У меня есть области MVC во внешних библиотеках, которые имеют свой собственный регистрационный код области, как и в обычной области MVC. Эта регистрация области вызывается для каждого dll (модуля), и я убедился, что RouteTable содержит все маршруты из загруженных модулей после завершения загрузки.

Когда я ссылаюсь на эти внешние области на главном сайте, они попадают в каталог bin и прекрасно загружаются. То есть, когда делается запрос на маршрут, который существует во внешней библиотеке, правильный тип передается на мою фабрику пользовательских контроллеров (Ninject) и может быть создан экземпляр контроллера.

Однако, как только я переместил эти dll за пределы каталога bin (скажем, в папку Modules), возникает проблема с маршрутизацией. Я проверил, что RouteTable имеет все требуемые маршруты, но к тому времени, когда запрос попадает на фабрику контроллеров Ninject, запрашиваемый тип становится пустым. Из прочтения здесь ссылки SO здесь кажется, что это происходит, когда ASP.NET MVC не может найти контроллер, соответствующий запрошенному маршруту, или не знает, как понять маршрут.

При внешней загрузке модулей я убедился, что загружаемые модули загружаются в домен приложения с помощью вызова Assemby.LoadFrom(modulePath);

Я провел некоторое исследование, и кажется, что при попытке загрузить библиотеку вне bin вам нужно указать частное исследование в app.config, как указано ; . У меня установлено 'bin \ Modules', куда также перемещаются модули области mvc.

У кого-нибудь есть идеи, почему простое перемещение проекта области mvc за пределы папки bin может привести к тому, что запрошенный тип, переданный на фабрику контроллеров, станет нулевым, что приведет к созданию экземпляра контроллера?

Edit:

  • Все маршруты, зарегистрированные во внешних областях, имеют пространство имен контроллера, указанное в маршруте

Ниже приведен фрагмент кода, который создает новое ядро ​​Ninject, читает список имен модулей из включаемого файла и затем выполняет поиск включенных модулей в каталоге bin / Modules. Модуль загружается через загрузчик сборок, регистрирует свои области и затем загружается в ядро ​​ninject.

        // comma separated list of modules to enable
        string moduleCsv = ConfigurationManager.AppSettings["Modules.Enabled"];
        if (!string.IsNullOrEmpty(moduleCsv)) {
            string[] enabledModuleList = moduleCsv.Replace(" ", "").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            _Modules = enabledModuleList ?? new string[0];

            // load enabled modules from bin/Modules.
            var moduleList = Directory.GetFiles(Server.MapPath("~" + Path.DirectorySeparatorChar + "bin" + Path.DirectorySeparatorChar + "Modules"), "*.dll");

            foreach (string enabledModule in enabledModuleList) {
                string modulePath = moduleList.Single(m => m.Contains(enabledModule));
                // using code adapted from from AssemblyLoader
                var asm = AssemblyLoader.LoadAssembly(modulePath);
                // register routes for module
                AreaRegistrationUtil.RegisterAreasForAssemblies(asm);

                // load into Ninject kernel
                kernel.Load(asm);
            }
        }

Это суть фабрики контроллеров Ninject, которая получает вышеупомянутое ядро ​​Ninject и обрабатывает запросы на создание контроллеров. Для контроллеров, которые существуют в сборке в bin / Modules, GetControllerType (...) возвращает нулевое значение для запрошенного имени контроллера.

public class NinjectControllerFactory : DefaultControllerFactory
{
    #region Instance Variables
    private IKernel _Kernel;
    #endregion

    #region Constructors
    public NinjectControllerFactory(IKernel kernel)
    {
        _Kernel = kernel;
    }

    protected override Type GetControllerType(System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        // Is null for controller names requested outside of bin directory.
        var type = base.GetControllerType(requestContext, controllerName);
        return type;
    }

    protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
    {
        IController controller = null;
        if (controllerType != null)
            controller = _Kernel.Get(controllerType) as IController;
        return controller;
    }
}

Обновление Ninject Nuget Install

По какой-то причине мне не удалось установить Ninject.MVC3 через NuGet. Visual Studio выдавала ошибку schemaVersion при нажатии кнопки установки (я установил другие пакеты Nuget, например, ELMAH).

Я обнаружил кое-что еще интересное, и это то, что если я передам дополнительные сборки модулей в NinjectControllerFactory, у меня есть и выполнит поиск, когда тип не может быть разрешен, он найдет правильный тип и сможет построить контроллер. Это приводит к еще одной странной проблеме.

Первый маршрут, запрашиваемый у внешнего модуля, - это / Account / LogOn в модуле авторизации и регистрации. Поставщик виртуального пути выдает здесь ошибку после того, как он обнаружил представление, и пытается выдать его с жалобой на отсутствующее пространство имен. Это приводит к сбою маршрута ошибки, который обрабатывается модулем ErrorHandling. Как ни странно, это загружается и рендерит нормально!

Так что я все еще застрял с двумя проблемами; 1) Необходимость немного хитроумного взлома и передачи дополнительных сборок модулей в NinjectControllerFactory, чтобы иметь возможность разрешать типы для контроллеров во внешних модулях. 2) Ошибка с одним конкретным модулем, когда он жалуется на то, что пространство имен не найдено

Эти две проблемы, очевидно, связаны, потому что загрузка сборки просто не загружается и делает все доступное, что должно быть. Если все эти области MVC загружены из каталога bin, все работает нормально. Так что это явно проблема с пространством имен / загрузкой сборки.

1 Ответ

1 голос
/ 19 сентября 2011

LoadFrom загрузить сборку в контекст загрузки.Эти типы недоступны для других классов в контексте загрузки по умолчанию.Вероятно, это причина того, что контроллер не найден.

Если вы знаете, какие сборки должны быть загружены, вы всегда должны использовать Assembly.Load ().Если вы не знаете, какие сборки развернуты в каталоге, то либо угадывайте по именам файлов имена сборок, либо используйте Assembly.ReflectionOnlyLoadFrom () (предпочтительно с использованием временного домена приложений) для получения имен сборок.Затем загрузите сборки, используя Assembly.Load () с именем сборки.

Если ваши сборки содержат NinjectModules, вы также можете использовать kernel.Load (), который делает то, что я описал выше.Но он загружает только сборки, содержащие хотя бы один модуль.

Прочитайте http://msdn.microsoft.com/en-us/library/dd153782.aspx о различных контекстах сборки.

Вот небольшой фрагмент из кодовой базы Ninject.Я удалил ненужные вещи, но не пытался скомпилировать или запустить, так что, вероятно, есть небольшие проблемы с этим.

public class AssemblyLoader
{
    public void LoadAssemblies(IEnumerable<string> filenames)
    {
        GetAssemblyNames(filenames).Select(name => Assembly.Load(name));
    }

    private static IEnumerable<AssemblyName> GetAssemblyNames(IEnumerable<string> filenames)
    {
        var temporaryDomain = CreateTemporaryAppDomain();
        try
        {
            var assemblyNameRetriever = (AssemblyNameRetriever)temporaryDomain.CreateInstanceAndUnwrap(typeof(AssemblyNameRetriever).Assembly.FullName, typeof(AssemblyNameRetriever).FullName);

            return assemblyNameRetriever.GetAssemblyNames(filenames.ToArray());
        }
        finally
        {
            AppDomain.Unload(temporaryDomain);
        }
    }

    private static AppDomain CreateTemporaryAppDomain()
    {
        return AppDomain.CreateDomain(
            "AssemblyNameEvaluation",
            AppDomain.CurrentDomain.Evidence,
            AppDomain.CurrentDomain.SetupInformation);
    }

    private class AssemblyNameRetriever : MarshalByRefObject
    {
        public IEnumerable<AssemblyName> GetAssemblyNames(IEnumerable<string> filenames)
        {
            var result = new List<AssemblyName>();
            foreach(var filename in filenames)
            {
                Assembly assembly;
                try
                {
                    assembly = Assembly.LoadFrom(filename);
                }
                catch (BadImageFormatException)
                {
                    // Ignore native assemblies
                    continue;
                }

                result.Add(assembly.GetName(false));
            }

            return result;
        }
    }
}
...