ASP.NET MVC: представления, использующие тип модели, загруженный MEF, не могут быть найдены механизмом представления - PullRequest
12 голосов
/ 01 декабря 2010

Я пытаюсь создать среду, позволяющую динамически импортировать контроллеры и представления в приложение MVC.Вот как это работает:

  • Я использую .NET 4, ASP.NET MVC 3 RC и Razor ViewEngine
  • Контроллеры экспортируются и импортируются с использованием MEF для проекта -Я называю набор контроллеров и представлений из данного проекта «Модулем»
  • Сборки, обнаруженные с использованием MEF, динамически ссылаются на BuildManager с использованием метода запуска перед приложением и BuildManager.AddReferencedAssembly.
  • Двоичные файлы (из экспорта проекта) и представления копируются в структуру папок целевого проекта с использованием события построения
  • Контроллеры выбираются с использованием настраиваемой фабрики контроллеров, которая наследуется от DefaultControllerFactory и переопределяет GetControllerType ()
  • Viewsвыбираются с помощью пользовательского механизма представления, который наследует от RazorViewEngine и переопределяет GetView () и GetPartialView (), чтобы он мог искать представления в директориях представления для конкретного модуля

Пока все работает , кроме для представлений, использующих строго типизированную модель.Представления, использующие динамическую модель, работают нормально, но когда я указываю тип модели с помощью @model, я получаю YSOD, который говорит: «Представление« Индекс »или его мастер не найден».

При отладке моегоВ реализации ViewEngine я вижу, что: this.VirtualPathProvider.FileExists(String.Format(this.ViewLocationFormats[2], viewName, controllerContext.RouteData.GetRequiredString("controller"))) возвращает значение true, в то время как

this.FileExists(controllerContext, String.Format(this.ViewLocationFormats[2], viewName, controllerContext.RouteData.GetRequiredString("controller"))) возвращает значение false.

Если смотреть в Reflector, реализация RazorViewEngine для FileExists() в конечном итоге завершается следующим образом:

return (BuildManager.GetObjectFactory(virtualPath, false) != null);

Однако я не могу просмотреть BuildManager.GetObjectFactory() из Reflector, потому что он каким-то образом скрыт.

Я подозреваю, что это как-то связано с тем, что тип моделитип, который загружается из MEF, но поскольку я уже ссылаюсь на сборки, обнаруженные MEF из BuildManager, у меня нет потенциальных клиентов.Может ли кто-нибудь предоставить немного больше информации о том, что может происходить?


Обновление: Оказывается, я использовал устаревшую версию Reflector по сравнению с .NET 4. Я вижу,GetObjectFactory () сейчас, но я не могу найти ничего полезного.Я попытался добавить это в мою перегрузку FindView ():

try {var path = String.Format (this.ViewLocationFormats [2], viewName, controllerContext.RouteData.GetRequiredString ("controller"));var objFactory = System.Web.Compilation.BuildManager.GetObjectFactory (virtualPath: path, throwIfNotFound: true);} catch {}

К сожалению, objFactory обнуляется, и исключение не выдается.Все биты, которые имеют дело с ошибками компиляции, являются частными методами или типами, поэтому я не могу отладить ничего из этого, но даже кажется, что они в конечном итоге выдают исключение, которое, похоже, не происходит.Похоже, я снова в тупике.Справка!


Обновление 2

Я обнаружил, что в момент вызова FindView (), если я вызываю AppDomain.CurrentDomain.GetAssemblies(), сборкатип модели в включен .Однако я не могу загрузить тип, используя Type.GetType().


Обновление 3

Вот что я вижу: not found



Обновление 4

Вот реализация ViewEngine:

using System;
using System.Linq;
using System.Web.Mvc;
using System.Web.Hosting;
using System.Web.Compilation;

namespace Site.Admin.Portal
{
    public class ModuleViewEngine : RazorViewEngine
    {
        private static readonly String[] viewLocationFormats = new String[]
        {
            "~/Views/{0}/{{1}}/{{0}}.aspx",
            "~/Views/{0}/{{1}}/{{0}}.ascx",
            "~/Views/{0}/{{1}}/{{0}}.cshtml",
            "~/Views/{0}/Shared/{{0}}.aspx",
            "~/Views/{0}/Shared/{{0}}.ascx",
            "~/Views/{0}/Shared/{{0}}.cshtml"
        };

        public ModuleViewEngine(IModule module)
        {
            this.Module = module;
            var formats = viewLocationFormats.Select(f => String.Format(f, module.Name)).ToArray();

            this.ViewLocationFormats = formats;
            this.PartialViewLocationFormats = formats;
            this.AreaViewLocationFormats = formats;
            this.AreaPartialViewLocationFormats = formats;
            this.AreaMasterLocationFormats = formats;
        }

        public IModule Module { get; private set; }

        public override ViewEngineResult FindPartialView(ControllerContext controllerContext, String partialViewName, Boolean useCache)
        {
            var moduleName = controllerContext.RouteData.GetRequiredString("module");
            if (moduleName.Equals(this.Module.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                return base.FindPartialView(controllerContext, partialViewName, useCache);
            }
            else return new ViewEngineResult(new String[0]);
        }

        public override ViewEngineResult FindView(ControllerContext controllerContext, String viewName, String masterName, Boolean useCache)
        {
            var moduleName = controllerContext.RouteData.GetRequiredString("module");
            if (moduleName.Equals(this.Module.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                var baseResult = base.FindView(controllerContext, viewName, masterName, useCache);
                return baseResult;
            }
            else return new ViewEngineResult(new String[0]);
        }        
    }
}

Ответы [ 2 ]

10 голосов
/ 01 декабря 2010

Основываясь на обновлении 2, я предполагаю, что у вас есть явно загруженная копия вашей сборки (то есть она была загружена с помощью другого метода, отличного от Load, например LoadFrom). Явно загруженные сборки откладываются в специальном месте, потому что они не могут удовлетворить требования неявного типа. Правила для Fusion (загрузчик сборок) могут быть довольно загадочными и трудными для понимания.

Я согласен с оценкой Мэтью, что для того, чтобы это сработало, ваша DLL должна быть в / bin, иначе она никогда не сможет удовлетворить неявное требование типа.

5 голосов
/ 01 декабря 2010

Импортированные библиотеки не находятся в каталоге /bin, поэтому они не проверяются при попытке разрешить ссылки.Я обнаружил работу, которую я опубликовал в моей статье MVC + MEF (часть 2) .По сути, вам нужно добавить свои каталоги, в которых ваши расширения находятся, на путь зондирования домена приложений.

По существу, где я создаю свой контейнер:

/// <summary>
/// Creates the composition container.
/// </summary>
/// <returns></returns>
protected virtual CompositionContainer CreateCompositionContainer()
{
    var catalog = new AggregateCatalog();
    catalog.Catalogs.Add(new DirectoryCatalog(MapPath("~/bin")));

    var config = CompositionConfigurationSection.GetInstance();
    if (config != null && config.Catalogs != null) {
        config.Catalogs
            .Cast<CatalogConfigurationElement>()
            .ForEach(c =>
            {
                if (!string.IsNullOrEmpty(c.Path)) {
                    string path = c.Path;
                    if (path.StartsWith("~"))
                        path = MapPath(path);

                    foreach (var directoryCatalog in GetDirectoryCatalogs(path)) {

                        // Register our path for probing.
                        RegisterPath(directoryCatalog.FullPath);

                        // Add the catalog.
                        catalog.Catalogs.Add(directoryCatalog);
                    }
                }
            });
    }

    var provider = new DynamicInstantiationExportProvider();
    var container = new CompositionContainer(catalog, provider);
    provider.SourceProvider = container;

    return container;
}

Я регистрирую все каталоги каталогов втекущий домен:

/// <summary>
/// Registers the specified path for probing.
/// </summary>
/// <param name="path">The probable path.</param>
private void RegisterPath(string path)
{
    AppDomain.CurrentDomain.AppendPrivatePath(path);
}

Я считаю, что то же самое должно работать для MVC3.

ОБНОВЛЕНИЕ : исправьте меня, если я ошибаюсь, но я не верюViewEngines создаются один раз за запрос, вы создаете один экземпляр, который вы регистрируете в MVC.Из-за этого в вашем ViewEngine используется только один экземпляр IModule, поэтому, если путь не совпадает с первым IModule.Name, он не будет найден?Имеет ли это смысл?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...