Logo

Maarten Balliauw {blog}

ASP.NET, ASP.NET MVC, Azure, PHP, OpenXML, VSTS, ...

About the author

Maarten Balliauw is an MVP ASP.NET and is currently employed as .NET Software Engineer at RealDolmen. His interests are mainly web applications developed in ASP.NET (C#) or PHP.
More about me More about me
Send mail E-mail me


Microsoft Most Valuable Professional - MVP - ASP.NET

Subscribe to my RSS feed Follow me on Twitter! View Maarten Balliauw's profile on LinkedIn RealDolmen - Rock-solid passion for ICT
I'm a speaker at TechDays Belgium and TechDays Finland

Search

Latest Twitter

    Follow me on Twitter...

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

    © Copyright Maarten Balliauw 2010

    Creating a custom ViewEngine for the ASP.NET MVC framework

    Have you ever seen a presentation of ScottGu about the ASP.NET MVC framework? There is one particular slide that keeps coming back, stating that every step in the ASP.NET MVC life cycle is pluggable. Let's find out if replacing one of these components is actually easy by creating a custom ViewEngine and corresponding view.

     ASP.NET MVC life cycle

    Some background

    After a route has been determined by the route handler, a Controller is fired up. This Controller sets ViewData, which is afterwards passed into the ViewEngine. In short, the ViewEngine processes the view and provides the view with ViewData from the Controller. Here's the base class:

    public abstract class ViewEngineBase {
         public abstract void RenderView(ViewContext viewContext);
    }

    By default, the ASP.NET MVC framework has a ViewEngine named WebFormsViewEngine. As the name implies, this WebFormsViewEngine is used to render a view which is created using ASP.NET web forms.

    The MvcContrib project contains some other ViewEngine implementations like NVelocity, Brail, NHaml, XSLT, ...

    What we are going to build...

    Rendered viewIn this blog post, we'll build a custom ViewEngine which will render a page like you see on the right from a view with the following syntax:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns=""http://www.w3.org/1999/xhtml"">http://www.w3.org/1999/xhtml" >
    <head>
        <title>Custom ViewEngine Demo</title>
    </head>
    <body>
        <h1>{$ViewData.Title}</h1>
        <p>{$ViewData.Message}</p>
        <p>The following fruit is part of a string array: {$ViewData.FruitStrings[1]}</p>
        <p>The following fruit is part of an object array: {$ViewData.FruitObjects[1].Name}</p>
        <p>Here's an undefined variable: {$UNDEFINED}</p>
    </body>
    </html>

    What do we need?

    First of all, download the current ASP.NET MVC framework from CodePlex. After creating a new ASP.NET MVC web site, tweak some stuff:

    • Remove /Views/*.*
    • Remove /Content/*.* (unless you want to keep the default CSS files)
    • Add a folder /Code

    In order to create a ViewEngine, we will have to do the following:

    • Create a default IControllerFactory which sets the ViewEngine we will create on each Controller
    • Edit Global.asax.cs and register the default controller factory
    • Create a ViewLocator (this one will map a controller + action to a specific file name that contains the view to be rendered)
    • Create a ViewEngine (the actual purpose of this blog post)

    Let's do some coding!

    1. Creating and registering the IControllerFactory implementation

    Of course, ASP.NET MVC has a default factory which creates a Controller instance for each incoming request. This factory takes care of dependency injection, including Controller initialization and the assignment of a ViewEngine. Since this is a good point of entry to plug our own ViewEngine in, we'll create an inherited version of the DefaultControllerFactory:

    public class SimpleControllerFactory : DefaultControllerFactory
    {
        protected override IController CreateController(RequestContext requestContext, string controllerName)
        {
            Controller controller = (Controller)base.CreateController(requestContext, controllerName);
            controller.ViewEngine = new SimpleViewEngine(); // <-- will be implemented later in this post
            return controller;
        }
    }

    In order to make this SimpleControllerFactory the default factory, edit the Global.asax.cs file and add the following line of code in the Application_Start event:

    ControllerBuilder.Current.SetControllerFactory(typeof(SimpleControllerFactory));

    2. Create a ViewLocator

    In order for the ViewEngine we'll build to find the correct view for each controller + action combination, we'll have to implement a ViewLocator too:

    public class SimpleViewEngine : IViewEngine
    {
        // ...
        // Private member: IViewLocator _viewLocator = null;
        // Public property: IViewLocator ViewLocator
        // ...

        #region IViewEngine Members

        public void RenderView(ViewContext viewContext)
        {
            string viewLocation = ViewLocator.GetViewLocation(viewContext, viewContext.ViewName);
            if (string.IsNullOrEmpty(viewLocation))
            {
                throw new InvalidOperationException(string.Format("View {0} could not be found.", viewContext.ViewName));
            }

            string viewPath = viewContext.HttpContext.Request.MapPath(viewLocation);
            string viewTemplate = File.ReadAllText(viewPath);

            IRenderer renderer = new PrintRenderer();
            viewTemplate = renderer.Render(viewTemplate, viewContext);

            viewContext.HttpContext.Response.Write(viewTemplate);
        }

        #endregion
    }

    Note that we first locate the view using the ViewLocator, map it to a real path on the server and then render contents directly to the HTTP response. The PrintRenderer class maps {$....} strings in the view to a real variable from ViewData. If you want to see the implementation, please check the download of this example.

    Conclusion

    Conclusions Replacing the default ViewEngine with a custom made version is actually quite easy! The most difficult part in creating your own ViewEngine implementation will probably be the parsing of your view. Fortunately, there are some examples around which may be a good source of inspiration (see MvcContrib).

    If someone wants to use the code snippets I posted to create their own PHP Smarty, please let me know! Smarty is actually quite handy, and might also be useful in ASP.NET MVC.

    And yes, it has been a lot of reading, but I did not forget. Download the example code from this blog post: CustomViewEngine.zip (213.17 kb)

    kick it on DotNetKicks.com


    Comments

    alvinashcraft.com | Reply

    Wednesday, May 21, 2008 3:10 PM

    pingback

    Pingback from alvinashcraft.com

    Dew Drop - May 21, 2008 | Alvin Ashcraft's Morning Dew

    blog.cwa.me.uk | Reply

    Thursday, May 22, 2008 9:19 AM

    pingback

    Pingback from blog.cwa.me.uk

    Reflective Perspective - Chris Alcock  » The Morning Brew #99

    weblogs.asp.net | Reply

    Sunday, May 25, 2008 4:05 AM

    pingback

    Pingback from weblogs.asp.net

    Interesting Finds: 2008.05.25 - gOODiDEA.NET

    Roger United States | Reply

    Friday, May 30, 2008 9:58 PM

    Roger

    I'm curious, would it be possible to store views in a database or somewhere other than the Views folder? I know that routes always look to the Views folder, but if we can change the View Engine, etc, could we not make the framework look to a database for the view?

    maartenba Belgium | Reply

    Monday, June 02, 2008 7:47 AM

    maartenba

    That is perfectly possible. Make sure you create your own viewlocator on top of the WebFormsViewEngine, for example, and you can store your ASP.NET webforms-based views in a database for example.

    Andrey | Reply

    Monday, June 30, 2008 8:49 AM

    Andrey

    It's not posiible to store views in database,because ViewLocator simple return file name in file system,but you can take a look at VirtualPathProvider.That's very weak extensibility point.

    Scott Hanselman United States | Reply

    Sunday, July 06, 2008 10:53 PM

    Scott Hanselman

    Audrey, actually there's nothing that INSISTS that you use ViewLocator. It's just there as a helper. You can ignore it and pull the View page from whereever. That said, you are correct that VirtualPathProvider was actually intended for this purpose. Here's a DbVirtualPathProvider: www.dotnetbips.com/.../...7-8ac5-bd56a2902979.aspx

    Q.Lee.lulu | Reply

    Monday, July 14, 2008 4:08 PM

    trackback

    Trackback from Q.Lee.lulu

    ASP.NET MVC : 实现我们自己的视图引擎

    dotnetwitter.wordpress.com | Reply

    Friday, July 25, 2008 12:37 PM

    pingback

    Pingback from dotnetwitter.wordpress.com

    links for 2008-07-25 « Praveen’s Blog

    nathanstults.com | Reply

    Tuesday, July 29, 2008 8:46 PM

    pingback

    Pingback from nathanstults.com

    To (ASP.NET)MVC or not to MVC (or, ASP.NET MVC Hyperlink Acupuncture) | nathanstults.com

    hsidev.wordpress.com | Reply

    Tuesday, July 29, 2008 8:47 PM

    pingback

    Pingback from hsidev.wordpress.com

    To (ASP.NET)MVC or not to MVC (or, ASP.NET MVC Hyperlink Acupuncture) « HSI Developer Blog

    DotNetKicks.com | Reply

    Sunday, August 17, 2008 7:05 AM

    trackback

    Trackback from DotNetKicks.com

    Creating a custom ViewEngine for the ASP.NET MVC Framework

    Chris Pietschmann | Reply

    Sunday, August 17, 2008 8:27 AM

    trackback

    Trackback from Chris Pietschmann

    How To Setup Custom Theme Support In ASP.NET MVC using a Custom ViewEngine

    umbyersw Canada | Reply

    Thursday, September 25, 2008 2:16 AM

    umbyersw

    Hi, I like your post and tried to follow it, however the property:

    controller.ViewEngine

    was not available when I tried to implement the SimpleControllerFactory. I'm using ASP.NET MVC preview 5. Did I miss something?

    Thanks,
    Wes

    maartenba Belgium | Reply

    Thursday, September 25, 2008 7:47 AM

    maartenba

    There have been some changes in preview 5, that's correct.

    All ViewEngine implementations for the ASP.NET MVC framework implement the IViewEngine interface:
    public interface IViewEngine  
    {  
        ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName);  
        ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName);  
    }

    The only responsibility an IViewEngine implementation has, is finding a view or partial view in the application. When a view has not been found, the implementation should return a list of searched locations. If a view has been found, a ViewEngineResult is returned.
    When a view is required to render, each registered IViewEngine is consulted (in order of registration) until the ASP.NET it finds one that returns a view that can be rendered. If no view can be found, an exception will be thrown.

    A ViewEngine could look like this:

    using System.Web.Mvc;

    namespace CustomViewEngine.Core
    {
        public class SimpleViewEngine : VirtualPathProviderViewEngine
        {
            #region Constructor

            public SimpleViewEngine() : base()
            {
                base.MasterLocationFormats = new string[] { "" };

                base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.htm",
                                                          "~/Views/{1}/{0}.html",
                                                          "~/Views/Shared/{0}.htm",
                                                          "~/Views/Shared/{0}.html"
                };

                base.PartialViewLocationFormats = ViewLocationFormats;
            }

            #endregion

            #region VirtualPathProviderViewEngine Members

            protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
            {
                return new SimpleView(partialPath);  
            }

            protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
            {
                return new SimpleView(viewPath);  
            }

            #endregion
        }
    }

    The View itself should implement IView which has one method on which you can render:

    void Render(ViewContext viewContext, TextWriter writer)

    mazellee United States | Reply

    Thursday, October 09, 2008 6:42 PM

    mazellee

    so if controller.ViewEngine isn't available in MVC preview 5. Any other way to find out what ViewEngine a particular type of controller is using?

    Thanks!
    mazel

    maartenba Belgium | Reply

    Thursday, October 09, 2008 7:05 PM

    maartenba

    Actually, this is no longer possible. MVC is all about separation of concerns: the controller does not have to know which ViewEngine is used. It just knows a name and that specific data (the model) is required.

    Anon United States | Reply

    Monday, February 16, 2009 9:35 AM

    Anon

    For those interested, here's a great example of implementing a custom Asp.Net MVC view engine using the popular StringTemplate engine.

    websitelogic.net/.../

    Add comment




      Country flag

    biuquote
    • Comment
    • Preview
    Loading