views:

695

answers:

7

I have the following layout for my mvc project:

  • /Controllers
    • /Demo
    • /Demo/DemoArea1Controller
    • /Demo/DemoArea2Controller
    • etc...
  • /Views
    • /Demo
    • /Demo/DemoArea1/Index.aspx
    • /Demo/DemoArea2/Index.aspx

However, when I have this for DemoArea1Controller:

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

I get the "The view 'index' or its master could not be found" error, with the usual search locations.

How can I specify that controllers in the "Demo" namespace search in the "Demo" view subfolder?

A: 

Last I checked, this requires you to build your own ViewEngine. I don't know if they made it easier in RC1 though.

The basic approach I used before the first RC was, in my own ViewEngine, to split the namespace of the controller and look for folders which matched the parts.

EDIT:

Went back and found the code. Here's the general idea.

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;
}
Joel Potter
It's actually much easier. Subclass WebFormsViewEngine and then just add to the array of paths it already searches in your constructor.
Craig Stuntz
Yeah, what Craig said!
Haacked
Good to know. The last time I needed to modify that collection, it wasn't possible in that manner.
Joel Potter
+1  A: 

Here is another sample of a simple ViewEngine from Rob Connery's MVC Commerce app:

View Engine Code

And the Global.asax.cs code to set the ViewEngine:

Global.asax.cs

Hope this helps.

robDean
A: 

May be Phil Haacks article about areas may help.

Jakub Šturc
+6  A: 

You can easily extend the WebFormViewEngine to specify all the locations you want to look in:

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;
    }
}

Make sure you remember to register the view engine by modifying the Application_Start method in you Global.asax.cs

    protected void Application_Start()
    {
        ViewEngines.Engines.Clear();
        ViewEngines.Engines.Add(new CustomViewEngine());
    }
Sam Wessel
+1  A: 

Try something like this:

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);
}
Veton
A: 

Note: for ASP.NET MVC 2 they have additional location paths you will need to set for views in 'Areas'.

 AreaViewLocationFormats
 AreaPartialViewLocationFormats
 AreaMasterLocationFormats

Creating a view engine for an Area is described on Phil's blog.

Note: This is for preview release 1 so is subject to change.

Simon_Weaver
A: 

Has anyone built a view engine around themes and areas - and nested master pages? I've found several examples on doing theming and they all seem to work except with the nested master pages.

Basically, I have a Themes folder at the root and I used a CustomViewEngine to find the correct "Theme" master page. This all seems to work.

However, my Area (Forum) has a master page for the (Forum) area which is nested and points to the default Site.master. When I go to the Forum Area views I expected the Forum.master to now point to my "Themed" master page. But what actually happens is the views in the area now completely bypass the Forum.master and are embedded in the Themed master page.

Make sense? So, I essentially have to check in the custom theme engine if the view is part of an area (through the namespace) and if it is then I don't change to the themed master. This way the views in the area still point to the Forum.master, but the end result is that the area does not get themed.

I found a way to change the area views "MasterName" and got everything working but it feels more like a hack. Anybody else run into this and found another way to deal with it?

darren