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 is new to 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.

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 into 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()

Why Use MvcHtmlString instead of Htmlstring?

ASP.NET 4 introduced the HtmlString class in addition to the IHtmlString interface to provide users with a convenient way to make HTML strings without needing to implement the interface themselves. So why did ASP.NET MVC 2 create the MvcHtmlString class?

ASP.NET MVC 2 is capable of targeting both .NET 3.5 SP1 and .NET 4. To do this, it had to be compiled against .NET 3.5 SP1, which means that the HtmlString (and IHtmlString) types are not actually available to the MVC framework. If you look inside the source code for MVC 2, you'll see that the MvcHtmlString doesn't actually directly implement IHtmlString. So how does .NET 4 know that the thing is actually an HTML string?

The answer lies in runtime code generation. The MVC framework uses a technique at run time where it detects what version of the .NET Framework is currently being used. When it detects .NET 4, it dynamically creates a new class that derives from MvcHtmlString and also implements IHtmlString. This is why creating MvcHtmlString instances is done by calling MvcHtmlString.Create() instead of the MvcHtmlString constructor, so that the MVC framework can very sneakily return a new custom type when appropriate.

So long as you're targeting .NET 4, you can use the built-in HtmlString type; if you also need to target .NET 3.5 SP1 (because your HTML helper needs to support MVC 2), then returning instances of MvcHtmlString is the right answer.

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 3 applications that use the Razor view engine. These helpers don't have access to the MVC helper objects (like HtmlHelper, AjaxHelper, or UrlHelper) nor 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.

For more information on writing Razor helpers, please see Jon Galloway's blog post “Comparing MVC 3 Helpers: Using Extension Methods and Declarative Razor @helper Syntax” online at: http://weblogs.asp.net/jgalloway/7730805.aspx.

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

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