我可以在ASP.NET MVC中指定“搜索视图”的自定义位置吗?

时间:2009-03-11 01:09:46

标签: asp.net-mvc views

我的mvc项目有以下布局:

  • /控制器
    • /演示
    • /演示/ DemoArea1Controller
    • /演示/ DemoArea2Controller
    • 等...
  • /浏览次数
    • /演示
    • /Demo/DemoArea1/Index.aspx
    • /Demo/DemoArea2/Index.aspx

但是,当我为DemoArea1Controller提供此内容时

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

我得到了“视图'索引'或其主人找不到”错误,通常的搜索位置。

如何在“Demo”视图子文件夹中指定“Demo”命名空间中的控制器?

10 个答案:

答案 0 :(得分:118)

您可以轻松扩展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;
    }
}

确保您记得通过修改Global.asax.cs中的Application_Start方法来注册视图引擎

protected void Application_Start()
{
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new CustomViewEngine());
}

答案 1 :(得分:41)

现在,在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());
    });
}

答案 2 :(得分:40)

实际上,比将路径硬编码到构造函数中要容易得多。下面是扩展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文件。

答案 3 :(得分:22)

如果您只想添加新路径,可以添加到默认视图引擎并备用一些代码行:

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

答案 4 :(得分: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();
}

答案 5 :(得分: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;
}

答案 6 :(得分: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);
}

答案 7 :(得分:3)

注意:对于ASP.NET MVC 2,他们需要为“区域”中的视图设置其他位置路径。

 AreaViewLocationFormats
 AreaPartialViewLocationFormats
 AreaMasterLocationFormats

为区域创建视图引擎为described on Phil's blog

注意:这是针对预览版1的,因此可能会发生变化。

答案 8 :(得分:2)

这里的大多数答案都是通过致电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();   
    }
}

现在,您可以在Global.asax中将项目配置为使用上面的RazorViewEngine

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

有关更多信息,请参见this tutoral

答案 9 :(得分:0)

我在MVC 5中是这样做的。我不想清除默认位置。

助手类:

namespace ConKit.Helpers
{
    public static class AppStartHelper
    {
        public static void AddConKitViewLocations()
        {
            // get engine
            RazorViewEngine engine = ViewEngines.Engines.OfType<RazorViewEngine>().FirstOrDefault();
            if (engine == null)
            {
                return;
            }

            // extend view locations
            engine.ViewLocationFormats =
                engine.ViewLocationFormats.Concat(new string[] {
                    "~/Views/ConKit/{1}/{0}.cshtml",
                    "~/Views/ConKit/{0}.cshtml"
                }).ToArray();

            // extend partial view locations
            engine.PartialViewLocationFormats =
                engine.PartialViewLocationFormats.Concat(new string[] {
                    "~/Views/ConKit/{0}.cshtml"
                }).ToArray();
        }
    }
}

然后在Application_Start中:

// Add ConKit View locations
ConKit.Helpers.AppStartHelper.AddConKitViewLocations();