Categories
asp.net c# webforms

Can I specify a custom location to “search for views” in ASP.NET MVC?

106

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?

1

  • 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.

    Mar 11, 2009 at 2:51

122

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 your Global.asax.cs

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

5

  • How can you access the path of a Master Page from a Nested Master page? As in setting the nested master page layout to search within the paths of the CustomViewEngine

    – Drahcir

    Dec 18, 2012 at 16:27

  • 6

    Is it not better if we skip Clearing the already registered engines and just add the new one and viewLocations shall have only the new ones?

    – Prasanna

    Sep 1, 2014 at 11:43

  • 3

    Implements without ViewEngines.Engines.Clear(); All work fine. If you want to use *.cshtml you must inherits from RazorViewEngine

    – KregHEk

    Feb 19, 2015 at 11:40

  • 1

    is there any way we can link the “add view” and “go to view” options from the controllers to the new view locations? i am using visual studio 2012

    Nov 19, 2016 at 0:39

  • As mentioned by @Prasanna, there is no need to clear the existing engines in order to add new locations, see this answer for more details.

    Jan 26, 2020 at 4:28

50

Now in MVC 6 you can implement IViewLocationExpander interface without messing around with view engines:

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

where {0} is target view name, {1} – controller name and {2} – area name.

You can return your own list of locations, merge it with default viewLocations (.Union(viewLocations)) or just change them (viewLocations.Select(path => "/AnotherPath" + path)).

To register your custom view location expander in MVC, add next lines to ConfigureServices method in Startup.cs file:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.ViewLocationExpanders.Add(new MyViewLocationExpander());
    });
}

3

  • 3

    I wish I could vote this up 10 votes. Is exactly what is needed in Asp.net 5 / MVC 6. Beautiful. Very useful in my case (and others) when you want to group areas into super areas for either larger sites or logical groupings.

    – drewid

    Nov 19, 2015 at 7:08

  • The Startup.cs portion should be: services.Configure<RazorViewEngineOptions> It goes in this method: public void ConfigureServices(IServiceCollection services)

    Oct 20, 2016 at 13:08


  • Agree with drewid in that I wish I could upvote this 10 times. Worked perfectly in .Net 5 and is much easier to implement and simpler to understand than any of the other solutions involving view engines.

    – dislexic

    Jun 22, 2021 at 2:26

42

There’s actually a lot easier method than hardcoding the paths into your constructor. Below is an example of extending the Razor engine to add new paths. One thing I’m not entirely sure about is whether the paths you add here will be cached:

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

And your 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);
}

One thing to note: your custom location will need the ViewStart.cshtml file in its root.