Могу ли я указать настраиваемое расположение для «поиска представлений» в ASP.NET MVC?

105

У меня есть следующий макет моего проекта mvc:

  • / Контроллеры
    • / Демо
    • / Demo / DemoArea1Controller
    • / Demo / DemoArea2Controller
    • и т.д...
  • /Просмотры
    • / Демо
    • /Demo/DemoArea1/Index.aspx
    • /Demo/DemoArea2/Index.aspx

Однако, когда у меня есть это для DemoArea1Controller:

public class DemoArea1Controller : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

Я получаю ошибку «Индекс представления или его мастер не найден» при обычном поиске.

Как я могу указать эти контроллеры в поиске пространства имен «Demo» во вложенной папке представления «Demo»?

Даниэль Шаффер
источник
Вот еще один пример простого ViewEngine из приложения MVC Commerce Роба Коннери: Просмотр кода движка и кода Global.asax.cs для установки ViewEngine: Global.asax.cs Надеюсь, это поможет.
Роберт Дин,

Ответы:

121

Вы можете легко расширить WebFormViewEngine, указав все местоположения, в которых вы хотите искать:

public class CustomViewEngine : WebFormViewEngine
{
    public CustomViewEngine()
    {
        var viewLocations =  new[] {  
            "~/Views/{1}/{0}.aspx",  
            "~/Views/{1}/{0}.ascx",  
            "~/Views/Shared/{0}.aspx",  
            "~/Views/Shared/{0}.ascx",  
            "~/AnotherPath/Views/{0}.ascx"
            // etc
        };

        this.PartialViewLocationFormats = viewLocations;
        this.ViewLocationFormats = viewLocations;
    }
}

Убедитесь, что вы не забыли зарегистрировать механизм просмотра, изменив метод Application_Start в файле Global.asax.cs.

protected void Application_Start()
{
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new CustomViewEngine());
}
Сэм Вессель
источник
Как вы можете получить доступ к пути мастер-страницы из вложенной мастер-страницы? Как при настройке вложенного макета главной страницы для поиска по путям CustomViewEngine
Drahcir
6
Не лучше ли пропустить очистку уже зарегистрированных движков и просто добавить новый, а в viewLocations будут только новые?
Прасанна
3
Реализует без ViewEngines.Engines.Clear (); Все нормально работает. Если вы хотите использовать * .cshtml, вы должны унаследовать его от RazorViewEngine
KregHEk
Есть ли способ связать опции «добавить представление» и «перейти к просмотру» из контроллеров с новыми местоположениями просмотра? Я использую визуальную студию 2012
Невилл Назеран
Как упоминалось @Prasanna, нет необходимости очищать существующие движки, чтобы добавлять новые местоположения, см. Этот ответ для получения дополнительной информации.
Hooman Bahreini
45

Теперь в MVC 6 вы можете реализовать IViewLocationExpanderинтерфейс, не возясь с механизмами просмотра:

public class MyViewLocationExpander : IViewLocationExpander
{
    public void PopulateValues(ViewLocationExpanderContext context) {}

    public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
    {
        return new[]
        {
            "/AnotherPath/Views/{1}/{0}.cshtml",
            "/AnotherPath/Views/Shared/{0}.cshtml"
        }; // add `.Union(viewLocations)` to add default locations
    }
}

где {0}- имя целевого представления, {1}- имя контроллера и {2}- имя области.

Вы можете вернуть свой собственный список местоположений, объединить его со списком по умолчанию viewLocations( .Union(viewLocations)) или просто изменить их ( viewLocations.Select(path => "/AnotherPath" + path)).

Чтобы зарегистрировать свой пользовательский расширитель расположения представлений в MVC, добавьте следующие строки в ConfigureServicesметод в Startup.csфайле:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.ViewLocationExpanders.Add(new MyViewLocationExpander());
    });
}
Whyleee
источник
3
Я хотел бы проголосовать за это на 10 голосов. Это именно то, что нужно в Asp.net 5 / MVC 6. Красиво. Очень полезно в моем случае (и других), когда вы хотите сгруппировать области в супер-области для более крупных сайтов или логических групп.
drewid
Часть Startup.cs должна быть: services.Configure <RazorViewEngineOptions> Он входит в этот метод: public void ConfigureServices (IServiceCollection services)
OrangeKing89
42

На самом деле существует гораздо более простой метод, чем жесткое кодирование путей в вашем конструкторе. Ниже приведен пример расширения движка Razor для добавления новых путей. Я не совсем уверен в одном: будут ли кэшироваться пути, которые вы добавляете сюда:

public class ExtendedRazorViewEngine : RazorViewEngine
{
    public void AddViewLocationFormat(string paths)
    {
        List<string> existingPaths = new List<string>(ViewLocationFormats);
        existingPaths.Add(paths);

        ViewLocationFormats = existingPaths.ToArray();
    }

    public void AddPartialViewLocationFormat(string paths)
    {
        List<string> existingPaths = new List<string>(PartialViewLocationFormats);
        existingPaths.Add(paths);

        PartialViewLocationFormats = existingPaths.ToArray();
    }
}

И ваш Global.asax.cs

protected void Application_Start()
{
    ViewEngines.Engines.Clear();

    ExtendedRazorViewEngine engine = new ExtendedRazorViewEngine();
    engine.AddViewLocationFormat("~/MyThemes/{1}/{0}.cshtml");
    engine.AddViewLocationFormat("~/MyThemes/{1}/{0}.vbhtml");

    // Add a shared location too, as the lines above are controller specific
    engine.AddPartialViewLocationFormat("~/MyThemes/{0}.cshtml");
    engine.AddPartialViewLocationFormat("~/MyThemes/{0}.vbhtml");

    ViewEngines.Engines.Add(engine);

    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
}

Одно замечание: для вашего настраиваемого местоположения потребуется файл ViewStart.cshtml в его корне.

Крис С
источник
23

Если вы хотите просто добавить новые пути, вы можете добавить их к механизмам просмотра по умолчанию и сэкономить несколько строк кода:

ViewEngines.Engines.Clear();
var razorEngine = new RazorViewEngine();
razorEngine.MasterLocationFormats = razorEngine.MasterLocationFormats
      .Concat(new[] { 
          "~/custom/path/{0}.cshtml" 
      }).ToArray();

razorEngine.PartialViewLocationFormats = razorEngine.PartialViewLocationFormats
      .Concat(new[] { 
          "~/custom/path/{1}/{0}.cshtml",   // {1} = controller name
          "~/custom/path/Shared/{0}.cshtml" 
      }).ToArray();

ViewEngines.Engines.Add(razorEngine);

То же самое касается WebFormEngine

Марсело Де Зен
источник
2
Для представлений: используйте razorEngine.ViewLocationFormats.
Aldentev
13

Вместо того, чтобы создавать подклассы RazorViewEngine или полностью заменять их, вы можете просто изменить существующее свойство PartialViewLocationFormats RazorViewEngine. Этот код находится в Application_Start:

System.Web.Mvc.RazorViewEngine rve = (RazorViewEngine)ViewEngines.Engines
  .Where(e=>e.GetType()==typeof(RazorViewEngine))
  .FirstOrDefault();

string[] additionalPartialViewLocations = new[] { 
  "~/Views/[YourCustomPathHere]"
};

if(rve!=null)
{
  rve.PartialViewLocationFormats = rve.PartialViewLocationFormats
    .Union( additionalPartialViewLocations )
    .ToArray();
}
Саймон Джайлз
источник
2
Это сработало для меня, за исключением того, что тип движка бритвы был «FixedRazorViewEngine» вместо «RazorViewEngine». Также я генерирую исключение, если движок не был найден, поскольку это мешает успешной инициализации моего приложения.
Роб
3

Последнее, что я проверил, требует от вас создания собственного ViewEngine. Я не знаю, облегчили ли они это в RC1.

Базовый подход, который я использовал до первого RC, заключался в моем собственном ViewEngine в разделении пространства имен контроллера и поиске папок, соответствующих его частям.

РЕДАКТИРОВАТЬ:

Вернулся и нашел код. Вот общая идея.

public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName)
{
    string ns = controllerContext.Controller.GetType().Namespace;
    string controller = controllerContext.Controller.GetType().Name.Replace("Controller", "");

    //try to find the view
    string rel = "~/Views/" +
        (
            ns == baseControllerNamespace ? "" :
            ns.Substring(baseControllerNamespace.Length + 1).Replace(".", "/") + "/"
        )
        + controller;
    string[] pathsToSearch = new string[]{
        rel+"/"+viewName+".aspx",
        rel+"/"+viewName+".ascx"
    };

    string viewPath = null;
    foreach (var path in pathsToSearch)
    {
        if (this.VirtualPathProvider.FileExists(path))
        {
            viewPath = path;
            break;
        }
    }

    if (viewPath != null)
    {
        string masterPath = null;

        //try find the master
        if (!string.IsNullOrEmpty(masterName))
        {

            string[] masterPathsToSearch = new string[]{
                rel+"/"+masterName+".master",
                "~/Views/"+ controller +"/"+ masterName+".master",
                "~/Views/Shared/"+ masterName+".master"
            };


            foreach (var path in masterPathsToSearch)
            {
                if (this.VirtualPathProvider.FileExists(path))
                {
                    masterPath = path;
                    break;
                }
            }
        }

        if (string.IsNullOrEmpty(masterName) || masterPath != null)
        {
            return new ViewEngineResult(
                this.CreateView(controllerContext, viewPath, masterPath), this);
        }
    }

    //try default implementation
    var result = base.FindView(controllerContext, viewName, masterName);
    if (result.View == null)
    {
        //add the location searched
        return new ViewEngineResult(pathsToSearch);
    }
    return result;
}
Джоэл
источник
1
На самом деле это намного проще. Подкласс WebFormsViewEngine, а затем просто добавьте к массиву путей, которые он уже ищет в вашем конструкторе.
Craig Stuntz,
Хорошо знать. В прошлый раз, когда мне нужно было изменить эту коллекцию, это было невозможно.
Joel
Стоит упомянуть, что вам нужно установить переменную baseControllerNamespace в пространство имен вашего базового контроллера (например, «Project.Controllers»), но в остальном я сделал именно то, что мне нужно, через 7 лет после публикации.
prototype14
3

Попробуйте что-то вроде этого:

private static void RegisterViewEngines(ICollection<IViewEngine> engines)
{
    engines.Add(new WebFormViewEngine
    {
        MasterLocationFormats = new[] {"~/App/Views/Admin/{0}.master"},
        PartialViewLocationFormats = new[] {"~/App/Views/Admin//{1}/{0}.ascx"},
        ViewLocationFormats = new[] {"~/App/Views/Admin//{1}/{0}.aspx"}
    });
}

protected void Application_Start()
{
    RegisterViewEngines(ViewEngines.Engines);
}
Виталий Улантиков
источник
3

Примечание: для ASP.NET MVC 2 у них есть дополнительные пути к расположению, которые вам нужно будет установить для представлений в «Областях».

 AreaViewLocationFormats
 AreaPartialViewLocationFormats
 AreaMasterLocationFormats

Создание движка просмотра для Area описано в блоге Фила .

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

Simon_Weaver
источник
1

Большинство ответов здесь: очистите существующие местоположения , позвонив, ViewEngines.Engines.Clear()а затем снова добавьте их ... в этом нет необходимости.

Мы можем просто добавить новые места к существующим, как показано ниже:

// note that the base class is RazorViewEngine, NOT WebFormViewEngine
public class ExpandedViewEngine : RazorViewEngine
{
    public ExpandedViewEngine()
    {
        var customViewSubfolders = new[] 
        {
            // {1} is conroller name, {0} is action name
            "~/Areas/AreaName/Views/Subfolder1/{1}/{0}.cshtml",
            "~/Areas/AreaName/Views/Subfolder1/Shared/{0}.cshtml"
        };

        var customPartialViewSubfolders = new[] 
        {
            "~/Areas/MyAreaName/Views/Subfolder1/{1}/Partials/{0}.cshtml",
            "~/Areas/MyAreaName/Views/Subfolder1/Shared/Partials/{0}.cshtml"
        };

        ViewLocationFormats = ViewLocationFormats.Union(customViewSubfolders).ToArray();
        PartialViewLocationFormats = PartialViewLocationFormats.Union(customPartialViewSubfolders).ToArray();

        // use the following if you want to extend the master locations
        // MasterLocationFormats = MasterLocationFormats.Union(new[] { "new master location" }).ToArray();   
    }
}

Теперь вы можете настроить свой проект для использования вышеуказанного RazorViewEngineв Global.asax:

protected void Application_Start()
{
    ViewEngines.Engines.Add(new ExpandedViewEngine());
    // more configurations
}

См. Этот учебник для получения дополнительной информации.

Hooman Bahreini
источник