24

I'm looking at implementing a custom RazorViewEngine. Basically I have two sites with effectively the same code base. The differences being that they look different. I want to override the standard view engine to make MVC look in two separate locations for it's views, layouts, etc. one for company A and another for Company B. Company A will contain the master views and company B's view will override these masters. So I want the View Engine to look in location B for a view, layout, master or partial if it finds it then return it, if it doesn't find it I want it to default to company A's views as the default. Obviously company A will only look in it's own folder.

Ok to the crux of the question: I've found this site: http://www.aspnetwiki.com/mvc-3-razor:extending-the-view-engine

First question, is this the best way to achieve this?

Second do I need to override the CreatePartial, CreateView, FindPartial and FindView methods?

Update

Ok I've figured out the second question myself, the Methods I want to override are CreateView and CreatePartialView as at this point it's built the view string and I can fiddle with it.

Liam
  • 27,717
  • 28
  • 128
  • 190

3 Answers3

18

Ok in the end I opted for an approach detailed here: http://weblogs.asp.net/imranbaloch/archive/2011/06/27/view-engine-with-dynamic-view-location.aspx

thanks to @Adriano for the answers and pointers but in the end I think this approach fits my needs better. The approach below allows me to keep the standard functionality but to create a new higher priority view location to be searched.

public class Travel2ViewEngine : RazorViewEngine
{
    protected BrandNameEnum BrandName;
    private string[] _newAreaViewLocations = new string[] {
        "~/Areas/{2}/%1Views/{1}/{0}.cshtml",
        "~/Areas/{2}/%1Views/{1}/{0}.vbhtml",
        "~/Areas/{2}/%1Views//Shared/{0}.cshtml",
        "~/Areas/{2}/%1Views//Shared/{0}.vbhtml"
    };

    private string[] _newAreaMasterLocations = new string[] {
        "~/Areas/{2}/%1Views/{1}/{0}.cshtml",
        "~/Areas/{2}/%1Views/{1}/{0}.vbhtml",
        "~/Areas/{2}/%1Views/Shared/{0}.cshtml",
        "~/Areas/{2}/%1Views/Shared/{0}.vbhtml"
    };

    private string[] _newAreaPartialViewLocations = new string[] {
        "~/Areas/{2}/%1Views/{1}/{0}.cshtml",
        "~/Areas/{2}/%1Views/{1}/{0}.vbhtml",
        "~/Areas/{2}/%1Views/Shared/{0}.cshtml",
        "~/Areas/{2}/%1Views/Shared/{0}.vbhtml"
    };

    private string[] _newViewLocations = new string[] {
        "~/%1Views/{1}/{0}.cshtml",
        "~/%1Views/{1}/{0}.vbhtml",
        "~/%1Views/Shared/{0}.cshtml",
        "~/%1Views/Shared/{0}.vbhtml"
    };

    private string[] _newMasterLocations = new string[] {
        "~/%1Views/{1}/{0}.cshtml",
        "~/%1Views/{1}/{0}.vbhtml",
        "~/%1Views/Shared/{0}.cshtml",
        "~/%1Views/Shared/{0}.vbhtml"
    };

    private string[] _newPartialViewLocations = new string[] {
        "~/%1Views/{1}/{0}.cshtml",
        "~/%1Views/{1}/{0}.vbhtml",
        "~/%1Views/Shared/{0}.cshtml",
        "~/%1Views/Shared/{0}.vbhtml"
    };

    public Travel2ViewEngine()
        : base()
    {
        Enum.TryParse<BrandNameEnum>(Travel2.WebUI.Properties.Settings.Default.BrandName, out BrandName);

        AreaViewLocationFormats = AppendLocationFormats(_newAreaViewLocations, AreaViewLocationFormats);

        AreaMasterLocationFormats = AppendLocationFormats(_newAreaMasterLocations, AreaMasterLocationFormats);

        AreaPartialViewLocationFormats = AppendLocationFormats(_newAreaPartialViewLocations, AreaPartialViewLocationFormats);

        ViewLocationFormats = AppendLocationFormats(_newViewLocations, ViewLocationFormats);

        MasterLocationFormats = AppendLocationFormats(_newMasterLocations, MasterLocationFormats);

        PartialViewLocationFormats = AppendLocationFormats(_newPartialViewLocations, PartialViewLocationFormats);
    }

    private string[] AppendLocationFormats(string[] newLocations, string[] defaultLocations)
    {
        List<string> viewLocations = new List<string>();
        viewLocations.AddRange(newLocations);
        viewLocations.AddRange(defaultLocations);
        return viewLocations.ToArray();
    }

    protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
    {
        return base.CreateView(controllerContext, viewPath.Replace("%1", BrandName.ToString()), masterPath);
    }

    protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
    {
        return base.CreatePartialView(controllerContext, partialPath.Replace("%1", BrandName.ToString()));
    }

    protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
    {
        return base.FileExists(controllerContext, virtualPath.Replace("%1", BrandName.ToString()));
    }
}

then register in Gloabal.asax

protected void Application_Start(object sender, EventArgs e)
{
    RegisterRoutes(RouteTable.Routes);


    //Register our customer view engine to control T2 and TBag views and over ridding
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new Travel2ViewEngine());
}
Liam
  • 27,717
  • 28
  • 128
  • 190
7

Yes that's the way but you do not need to override that methods. RazorViewEngine inherits from VirtualPathProviderViewEngine so you can use its properties to set the location of your views.

For an example read Creating your first MVC ViewEngine and How to set a Default Route (To an Area) in MVC.

Community
  • 1
  • 1
Adriano Repetti
  • 65,416
  • 20
  • 137
  • 208
  • This seems interesting but I'm not convinced it does exactly what I want it to do, documentation is a bit sparse, do you have an example you can post? – Liam Mar 23 '12 at 13:02
  • 1
    It's just a small example but: http://coderjournal.com/2009/05/creating-your-first-mvc-viewengine/ and this very nice link here on SO: http://stackoverflow.com/questions/2140208/how-to-set-a-default-route-to-an-area-in-mvc – Adriano Repetti Mar 23 '12 at 13:10
4

Here's my answer: Add this to your global.ascx

        ViewEngines.Engines.Clear();
        var customEngine = new RazorViewEngine();
        customEngine.PartialViewLocationFormats = new string[]
        {
            "~/Views/{1}/{0}.cshtml",
            "~/Views/Shared/{0}.cshtml",
            "~/Views/Partial/{0}.cshtml",
            "~/Views/Partial/{1}/{0}.cshtml"
        };



        customEngine.ViewLocationFormats = new string[]
        {
            "~/Views/{1}/{0}.cshtml",
            "~/Views/Shared/{0}.cshtml",
            "~/Views/Controller/{1}/{0}.cshtml"
        };

        customEngine.MasterLocationFormats = new string[]
        {
            "~/Views/Shared/{0}.cshtml",
            "~/Views/Layout/{0}.cshtml"
        };

        ViewEngines.Engines.Add(customEngine);

those are the folders where razor checks your views.

Let me know if this works.

CyberNinja
  • 872
  • 9
  • 25
  • I have implemented the same thing but somehow viewstart.cshtml is not rendering properly – Parashuram Aug 10 '18 at 09:33
  • what do you mean by not rendering properly? what errors are you getting? – CyberNinja Aug 10 '18 at 12:00
  • the layout was not set properly. I had identified the issue. I had kept my views outside of the views folder and by default, viewstart cshtml would be searched from current view path and hierarchically towards the root until its found. So in my case, I have kept the viewstart in root directory and its working now. – Parashuram Aug 12 '18 at 05:45