Extending Views

Views are the most common type of result returned from actions. A view is generally some kind of template with code inside to customize the output based on the input (the model). ASP.NET MVC ships with two view engines installed by default: the Web Forms view engine (which has been in MVC since version 1.0) and the Razor view engine (which was introduced in MVC 3). Several third-party view engines are also available for MVC applications, including Spark, NHaml, and NVelocity.

Customizing View Engines

An entire book could be written on the subject of writing a custom view engine, and in truth, perhaps a dozen people would buy it. Writing a view engine from scratch is just not a task very many people need to do, and there is enough existing source code for functional view engines that those few users have good starting places from which to work. Instead, this section is devoted to the customization of the two existing view engines that ship with MVC.

The two view engine classes — WebFormViewEngine and RazorViewEngine — both derive from BuildManagerViewEngine, which itself derives from VirtualPathProviderViewEngine. Both the build manager and virtual path providers are features inside of the core ASP.NET run time. The build manager is the component that locates view files on disk (like .aspx or .cshtml files) and converts them into source code and compiles them. The virtual path provider helps to locate files of any type; by default, the system will look for files on disk, but a developer could also replace the virtual path provider with one that loads the view content from other locations (like from a database or from an embedded resource). These two base classes allow a developer to replace the build manager and/or the virtual path provider, if needed.

A more common scenario for overriding is changing the locations on disk where the view engines look for files. By convention, it finds them in the following locations:

∼/Areas/AreaName/Views/ControllerName
∼/Areas/AreaName/Views/Shared
∼/Views/ControllerName
∼/Views/Shared

These locations are set into collection properties of the view engine during its constructor, so developers could create a new view engine that derives from their view engine of choice and override these locations. The following code shows the relevant code from one of the constructors of WebFormViewEngine:

AreaMasterLocationFormats = new string[] {
    "∼/Areas/{2}/Views/{1}/{0}.master",
    "∼/Areas/{2}/Views/Shared/{0}.master"
};
AreaViewLocationFormats = new string[] {
    "∼/Areas/{2}/Views/{1}/{0}.aspx",
    "∼/Areas/{2}/Views/{1}/{0}.ascx",
    "∼/Areas/{2}/Views/Shared/{0}.aspx",
    "∼/Areas/{2}/Views/Shared/{0}.ascx"
};
AreaPartialViewLocationFormats = AreaViewLocationFormats;

MasterLocationFormats = new string[] {
    "∼/Views/{1}/{0}.master",
    "∼/Views/Shared/{0}.master"
};
ViewLocationFormats = new string[] {
    "∼/Views/{1}/{0}.aspx",
    "∼/Views/{1}/{0}.ascx",
    "∼/Views/Shared/{0}.aspx",
    "∼/Views/Shared/{0}.ascx"
};
PartialViewLocationFormats = ViewLocationFormats;

These strings are sent through String.Format, and the parameters that are passed to them are:

{0} = View Name
{1} = Controller Name
{2} = Area Name

Changing these strings allows the developer to change the conventions for view location. For example, say you only wanted to serve .aspx files for full views and .ascx files for partial views. This would allow you to have two views with the same name but different extensions, and which one got rendered would depend on whether you requested a full or partial view.

The code inside the Razor view engine's constructor looks similar:

AreaMasterLocationFormats = new string[] {
    "∼/Areas/{2}/Views/{1}/{0}.cshtml",
    "∼/Areas/{2}/Views/{1}/{0}.vbhtml",
    "∼/Areas/{2}/Views/Shared/{0}.cshtml",
    "∼/Areas/{2}/Views/Shared/{0}.vbhtml"
};
AreaViewLocationFormats = AreaMasterLocationFormats;
AreaPartialViewLocationFormats = AreaMasterLocationFormats;

MasterLocationFormats = new string[] {
    "∼/Views/{1}/{0}.cshtml",
    "∼/Views/{1}/{0}.vbhtml",
    "∼/Views/Shared/{0}.cshtml",
    "∼/Views/Shared/{0}.vbhtml"
};
ViewLocationFormats = MasterLocationFormats;
PartialViewLocationFormats = MasterLocationFormats;

The small differences in this code account for the fact that Razor uses the file extension to differentiate the programming language (C# versus VB), but does not have separate file types for master views, views, and partial views; it also does not have separate file types for pages versus controls because those constructs don't exist in Razor.

Once you have the customized view engine, you'll need to let MVC know to use it. In addition, you'll need to remove the existing view engine that you're planning to replace. You should configure MVC from within your Global.asax file (or by using one of the Config classes in the App_Start folder of the default MVC 4 templates).

For example, if you are replacing the Razor view engine with your own custom view engine, the code might look something like this:

var razorEngine = ViewEngines.Engines
                      .SingleOrDefault(ve => ve is RazorViewEngine);

if (razorEngine != null)
    ViewEngines.Engines.Remove(razorEngine);

ViewEngines.Engines.Add(new MyRazorViewEngine());

This code uses a little bit of LINQ magic to determine whether a Razor view engine is already installed (removing it if so), and then adds an instance of your new Razor view engine instead. Remember that view engines are run in order, so if you want your new Razor view engine to take precedence over whatever other view engines are registered, you should use .Insert instead of .Add (with an index of 0 to make sure it goes first).

Writing HTML Helpers

HTML helpers are those methods that help you generate HTML inside your views. They are primarily written as extension methods to the HtmlHelper, AjaxHelper, or UrlHelper classes (depending on whether you're generating plain HTML, Ajax-enabled HTML, or URLs). HTML and Ajax helpers have access to the ViewContext (because they can only be called from views), and URL helpers have access to the ControllerContext (because they can be called from both controllers and views).

Extension methods are static methods in a static class that use the this keyword on their first parameter to tell the compiler which type they are providing the extension for. For example, if you wanted an extension method for HtmlHelper that took no parameters, you might write:

public static class MyExtensions {
    public static string MyExtensionMethod(this HtmlHelper html) {
        return "Hello, world!";
    }
}

You can still call this method the traditional way (by calling MyExtensions.MyExtensionMethod(Html)), but it's more convenient to call it via the extension syntax (by calling Html.MyExtensionMethod()). Any additional parameters you provide to the static method will become parameters in the extension method as well; only the extension parameter marked with the this keyword “disappears.”

Extension methods in MVC 1.0 all tended to return values of the String type, and that value would be directly placed into the output stream with a call much like this one (Web Forms view syntax):

<%= Html.MyExtensionMethod() %>

Unfortunately, there was a problem with the old Web Forms syntax: it was too easy to let unintended HTML escape into the wild. The Web world of the late 1990s through the early 2000s, in which ASP.NET started its life, is quite different from today, where your web apps must be very careful of things like cross-site scripting (XSS) attacks and cross-site request forgeries (CSRF). To make the world slightly safer, ASP.NET 4 introduced a new syntax for Web Forms that automatically encodes HTML values:

<%: Html.MyExtensionMethod() %>

Notice how the colon has replaced the equals sign. This is great for data safety, but what happens when you actually need to return HTML, as many HTML helpers will? ASP.NET 4 also introduced a new interface (IHtmlString) that any type can implement. When you pass such a string through the <%: %> syntax, the system recognizes that the type is already promising to be safe HTML and outputs it without encoding. In ASP.NET MVC 2, the team made the decision to mildly break backward compatibility, and make all HTML helpers return instances of MvcHtmlString.

When you write HTML helpers that are generating HTML, it's almost always going to be the case that you want to return IHtmlString instead of String, because you don't want the system to encode your HTML. This is even more important in the face of the Razor view engine, which only has a single output statement, and it always encodes:

@Html.MyExtensionMethod()

Writing Razor Helpers

In addition to the HTML helper syntax that's been available since MVC 1.0, developers can also write Razor helpers in the Razor syntax. This is a feature that shipped as part of the Web Pages 1.0 framework, which is included in MVC applications. These helpers don't have access to the MVC helper objects (like HtmlHelper, AjaxHelper, or UrlHelper) or to the MVC context objects (like ControllerContext or ViewContext). They can get access to the core ASP.NET run time intrinsic context objects through the traditional static ASP.NET API HttpContext.Current.

Developers might choose to write a Razor helper for simple reuse with a view, or if they wanted to reuse the same helper code from within both an MVC application and a Web Pages application (or if the application they are building is a combination of the two technologies). For the pure MVC developer, the traditional HTML Helper route offers more flexibility and customizability, albeit with a slightly more verbose syntax.

Note
For more information on writing Razor helpers, see Jon Galloway's blog post “Comparing MVC 3 Helpers: Using Extension Methods and Declarative Razor @helper Syntax” (http://weblogs.asp.net/jgalloway/7730805.aspx). Although Jon's blog post is about MVC 3, the topics he covers are still applicable for developers writing Razor helpers in MVC 4.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset