Marcin On ASP.NET

Keeping my eye on the dot

Posts
  • Marcin On ASP.NET

    Razor, Nested Layouts and Redefined Sections

    • 10 Comments

    In a recent post I introduced a technique for dealing with optional Razor sections and default content. In this post I will expand upon that technique and describe a way of working with sections across nested layout hierarchies. If you are not familiar with sections, layout pages, or my technique then go ahead and read that post to catch up.

    One aspect of the relationship between layout pages and sections in Razor that a fair number of people might find surprising is that a section defined in a content page can only be used in its immediate layout. There is implicit scoping going on that prevents certain use cases. Take the following example:

    <!DOCTYPE html>
    <html>
    <body>
    @RenderSection("SubSection")
    @RenderBody()
    </body>
    </html>
    @{
        Layout = "~/Views/_MasterLayout.cshtml";
    }
    <div>
    @section SubSection {
    <h1>Title</h1>
    }
    @RenderBody()
    @RenderSection("ContentSection")
    </div>
    @{
        Layout = "~/Views/_SubLayout.cshtml";
    }
    <div>
    <p>Main content</p>
    @section ContentSection {
    <div>Footer</div>
    }
    </div>

    In the above example you can certainly call RenderSection("SubSection") in _MasterLayout.cshtml, as well as call RenderSection("ContentSection") in _SubLayout.cshtml. However, it is impossible to call RenderSection("ContentSection") from _MasterLayout.cshtml because the file rendering the section and the file defining the section are not directly related. Essentially sections are limited to the Content-Layout scope and are not accessible to other layout pages outside of that scope.

    Redefining sections

    You can work around this by essentially redefining the section in the intermediate layout. 

    @{
        Layout = "~/Views/_MasterLayout.cshtml";
    }
    <div>
    @section SubSection {
    <h1>Title</h1>
    }
    @RenderBody()
    @section ContentSection {
      @RenderSection("ContentSection", required: false)
    }
    </div>

    Now you are able to reference “ContentSection” from _MasterLayout.cshtml. However you should be aware that you are not overriding “ContentSection" in the same sense as overriding methods in a child class. You are actually defining a new section named “ContentSection” in the SubLayout-MasterLayout scope that renders the section named “ContentSection” from the Content-SubLayout scope. The fact that the names match is incidental. The names certainly do not have to match.

    Things get even more complicated when you want to use the IsSectionDefined method to conditionally provide default content for optional sections. Because it’s necessary to propagate “ContentSection” by redefining the section in _SubLayout.cshtml you can no longer depend on IsSectionDefined returning the expected result.

    Conditionally redefining sections via RedefineSection

    Fortunately not everything is lost. What you want to do is to conditionally redefine a section. Building on the RenderSection helper method from my previous post I created the RedefineSection more helper methods to aid in this scenario:

    public static class SectionExtensions {
        private static readonly object _o = new object();
        public static HelperResult RenderSection(this WebPageBase page,
                                string sectionName,
                                Func<object, HelperResult> defaultContent) {
            if (page.IsSectionDefined(sectionName)) {
                return page.RenderSection(sectionName);
            }
            else {
                return defaultContent(_o);
            }
        }
    
        public static HelperResult RedefineSection(this WebPageBase page,
                                string sectionName) {
            return RedefineSection(page, sectionName, defaultContent: null);
        }
    
        public static HelperResult RedefineSection(this WebPageBase page,
                                string sectionName,
                                Func<object, HelperResult> defaultContent) {
            if (page.IsSectionDefined(sectionName)) {
                page.DefineSection(sectionName,
                                   () => page.Write(page.RenderSection(sectionName)));
            }
            else if (defaultContent != null) {
                page.DefineSection(sectionName,
                                   () => page.Write(defaultContent(_o)));
            }
            return new HelperResult(_ => { });
        }
    }

    The RedefineSection method conditionally redefines a section (that is it redefines in only if a section was already defined in a content page). The second overload also allows you to provide a default content template that will be used if the content page did not define the section. Using this code you can write the following pages:

    <!DOCTYPE html>
    <html>
    <body>
    @RenderSection("TitleSection", required: false)
    @RenderBody()
    </body>
    </html>
    @{
        Layout = "~/Views/_MasterLayout.cshtml";
    }
    <div>
    @this.RedefineSection("TitleSection",
                          @<h1>Default SubLayout title</h1>)
    @RenderBody()
    </div>
    @{
        Layout = "~/Views/_SubLayout.cshtml";
    }
    @section TitleContent {
    <h1>Title</h1>
    }
    <p>Main content</p>

    In the above example Content.cshtml defines the “TitleContent” section. _SubLayout.cshtml redefines that section but also provides some default markup in case the content page does not have the “TitleContent” section defined. Finally, _MasterLayout.cshtml consumes the section indicating that it is optional – this means that the entire site will still work even if the content page does not define the section and the intermediate layout does not provide a default value.

    Hope you find the above technique useful in your complex layout pages. Please let me know if you encounter any issues and whether these methods are valuable enough that they should be added to the framework for the next version.

  • Marcin On ASP.NET

    Optional Razor Sections with Default Content

    • 8 Comments

    Solution quick links:

    1. IsSectionDefined method
    2. Razor inline templates

    RenderSection

    The new ASP.NET page framework built on Razor (which is available in MVC 3) provides a facility for content pages to contribute named fragments of markup to their layout pages which the layout page can then render in an arbitrary location using the RenderSection method.

    For example, the following content page declares an "ExtraContent" section:

    @{
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
    
    @section ExtraContent {
        <div>Some extra content</div>
    }
    
    <div>The main content</div>

    And the following layout page renders it:

    <!DOCTYPE html>
    <html>
    <head></head>
    <body>
    @RenderBody()
    
    @RenderSection("ExtraContent")
    @RenderSection("OptionalContent", required: false)
    </body>
    </html>

    You can even declare that a section is not required like the “OptionalContent” section in the example above.

    But what if you want to have some default content for your optional sections?

    Option 1: Use the IsSectionDefined method

    The IsSectionDefined method returns true if a child content page defined a section. You can use that to decide whether to render a section or some other content:

    <!DOCTYPE html>
    <html>
    <body>
    @RenderBody()
    
    @if (IsSectionDefined("OptionalContent")) { 
        @RenderSection("OptionalContent")
    }
    else { 
        <div>Default content</div>
    }
    </body>
    </html>

    Just remember that you need to use the @RenderSection() syntax (you need the @ character) so that the contents of the section is actually printed to the output. Without that character you will get an exception with the following message:

    The following sections have been defined but have not been rendered for the layout page "~/Views/Shared/_Layout.cshtml": "OptionalContent"

    Option 2: Use Razor inline templates

    Razor provides a way to pass inline markup templates into function calls. This is a powerful mechanism that allows you to combine custom markup fragments with general purpose display logic. In order to make the following example work you will need to drop the following code into your project:

    using System.Web.WebPages;
    using System;
    
    public static class SectionExtensions {
        private static readonly object _o = new object();
        public static HelperResult RenderSection(this WebPageBase page,
                            string sectionName,
                            Func<object, HelperResult> defaultContent) {
            if (page.IsSectionDefined(sectionName)) {
                return page.RenderSection(sectionName);
            }
            else {
                return defaultContent(_o);
            }
        }
    }

    This code essentially wraps the previous example in a reusable extension method. All you need now is to provide the default content markup. With it you can write views like the following:

    <!DOCTYPE html>
    <html>
    <body>
    @RenderBody()
    
    @this.RenderSection("OptionalContent",
                        @<div>Default content</div>)
    </body>
    </html>

    For the second parameter you need to use the @ character to trigger the Razor parser to go into markup mode. Once in markup mode you can do anything that you do in the main body of the page.

    Just remember that since this is an extension method you need to call it off the this object. Otherwise the C# compiler will not be able to locate it and you will get a compilation error.

    If you like this technique read the follow up: Razor, Nested Layouts and Redefined Sections

  • Marcin On ASP.NET

    Fixing area view lookups when using multiple view engines

    • 1 Comments

    11/17 Update: I've fixed a bug in the view engine registration code that prevented this method from working. Please note the addition of the dummyFormats property in the solution below.

    Oskar from the ASP.NET forums asked a question about the order of view lookups when using Areas in MVC 3. The problem is that now that MVC ships with two view engines registered by default (WebFormViewEngine and RazorViewEngine), the order of lookups might not work as expected when you are using Areas in conjunction with a mixed environment of Aspx and Razor views.

    Take the following application layout as an example. This application has an “Admin” area with a Home controller and a Home Index view written in Razor. There’s also a default Home Index view written using the Aspx view engine.

    AreaLookup

    The Problem

    When requesting the Index action of the Admin area’s HomeController you would expect Index.cshtml to be served. However, in MVC 3 you will actually be served Index.aspx. This is because the process for looking up a View involves querying the collection of registered view engine until the first successful match. Once a successful match is found the remaining view engines are ignored. This gets a bit counter-intuitive when you mix Razor and Aspx views because this is the effective search order:

    ~/Areas/Admin/Views/Home/Index.aspx
    ~/Areas/Admin/Views/Home/Index.ascx
    ~/Areas/Admin/Views/Shared/Index.aspx
    ~/Areas/Admin/Views/Shared/Index.ascx
    ~/Views/Home/Index.aspx
    ~/Views/Home/Index.ascx
    ~/Views/Shared/Index.aspx
    ~/Views/Shared/Index.ascx
    ~/Areas/Admin/Views/Home/Index.cshtml
    ~/Areas/Admin/Views/Home/Index.vbhtml
    ~/Areas/Admin/Views/Shared/Index.cshtml
    ~/Areas/Admin/Views/Shared/Index.vbhtml
    ~/Views/Home/Index.cshtml
    ~/Views/Home/Index.vbhtml
    ~/Views/Shared/Index.cshtml
    ~/Views/Shared/Index.vbhtml

    Notice that because the WebFormsViewEngine is registered first by default, the path Views/Home/Index.aspx file gets looked at before the Areas/Admin/Views/Home/Index.cshtml path. That’s because the MVC Areas support is not hardcoded as a general pattern. Instead, it’s the job of each view engine to implement the lookup ordering and fallback logic.

    To make things work intuitively, you would need a multi-pass system that first scans all view engines for area-specific matches and then does another pass asking for general matches. But that is not how things are implemented in MVC (mainly because Areas did not exist in MVC 1 and it was not possible to change the existing view lookup contracts).

    The Solution

    However, it’s actually quite easy to reconfigure MVC to emulate this multi-pass behavior. Just use multiple instances of view engines. In your Global.asax.cs file add the following code (don’t forget to call this method from Application_Start):

    public static void SetUpViewEngines(ViewEngineCollection viewEngines) {
        viewEngines.Clear();
    
        string[] dummyFormats = new[] { "~/ThisFileShouldNotExist.aspx" };
    
        // area-specific pass
        viewEngines.Add(new WebFormViewEngine {
            ViewLocationFormats = dummyFormats,
            PartialViewLocationFormats = dummyFormats,
            MasterLocationFormats = dummyFormats
        });
        viewEngines.Add(new RazorViewEngine {
            ViewLocationFormats = dummyFormats,
            PartialViewLocationFormats = dummyFormats,
            MasterLocationFormats = dummyFormats
        });
    
        // general pass
        viewEngines.Add(new WebFormViewEngine {
            AreaViewLocationFormats = null,
            AreaPartialViewLocationFormats = null,
            AreaMasterLocationFormats = null
        });
        viewEngines.Add(new RazorViewEngine {
            AreaViewLocationFormats = null,
            AreaPartialViewLocationFormats = null,
            AreaMasterLocationFormats = null
        });
    }

    This solution works by first registering a pair of WebForms and Razor view engines that have dummy general location formats pointing to a file that does not exist (meaning that they are effectively only aware of the area-specific locations formats) and then a pair that only knows about the general location formats. This way both Aspx and Razor views will be searched for in area locations and if neither succeed the general views will be searched.

    This technique should be helpful for those who want to transition their application from Aspx to Razor (or maybe between some other pair of view engines). However, once you are done with the transition you should probably stick to just having one view engine registered to avoid unnecessary searches in the other view engines.

  • Marcin On ASP.NET

    Granular Request Validation in ASP.NET MVC 3

    • 7 Comments

    12/10 Update: In MVC 3 RC 2 SkipRequestValidationAttribute got renamed to AllowHtmlAttribute. I have updated the examples below.

    A little while ago I wrote a blog post describing granular request validation that shipped in MVC 3 Beta. However, since then we have changed the API for this feature and that post is no longer valid. In this post I will present the new API which is usable in the recently-shipped MVC 3 Release Candidate.

    But first: a quick refresh on request validation and why it’s great to make it granular. Request validation is a feature of ASP.NET that analyzes the data that a browser sends to the server when a user interacts with your site (such as form or query string data) and rejects requests that contain suspicious input that looks like html code (basically anything with a ‘<’). This protects you from HTML injection attacks such as cross-site scripting (XSS). It is enabled by default, however in previous versions it was an all-on-or-off feature, meaning that if you want to be able to accept HTML-formatted input from your users in just one field you had to completely turn this protection off. This in turn meant that you now had to validate every bit of data that came from the client.

    AllowHtmlAttribute SkipRequestValidationAttribute

    In MVC 3 we are introducing a new attribute called AllowHtmlAttribute. You can use this attribute to annotate your model properties to indicate that values corresponding to them should not be validated. Let’s take this User model and UserController as an example:

    public class User {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        [AllowHtml]
        public string Description { get; set; }
        [AllowHtml]
        public string Bio { get; set; }
    }
    
    public class UserController {
        [HttpPost]
        public ActionResult Update(User user) {
            // update user database
        }
    }

    I have annotated the Description and Bio properties to indicate they should not be request-validated. Now when the Update action method gets invoked these two properties on the User object will not be validated and any HTML they might contain will be passed straight through to the action method. However, everything else will still go through request validation and requests that contain suspicious content in the Name or Email fields will get rejected.

    ValidateInputAttribute

    AllowHtmlAttribute can only be applied to properties of a model class. For other request validation scenarios the existing ValidateInputAttribute is still helpful. For example, you can use it to disable request validation for action methods that bind to a loose collection of parameters:

    [ValidateInput(false)]
    public ActionResult Update(int userId, string description) {
    }

    Now when the parameters of the Update method get bound request validation will not be performed. You can apply ValidateInput to action methods as shown above or to the entire controller to affect all of its action methods.

    ValidateInput is also more usable in MVC 3. In MVC 2 running on .NET 4 you had to set requestValidationMode="2.0" in order to turn request validation off. In MVC 3 this is no longer necessary.

  • Marcin On ASP.NET

    Granular Request Validation in ASP.NET MVC 3 Beta

    • 0 Comments

    11/9 Update: This article only applies to the MVC 3 Beta release. I have a new post about AllowHtmlAttribute that describes the new APIs and behaviors available in MVC 3 RC and later.

    If you don’t know already, we have just released the ASP.NET MVC 3 Beta and it brings a slew of new features. A small but useful feature is what we call granular request validation. It allows you to opt out of ASP.NET’s request validation on a field-by-field basis, meaning that it will be a lot easier now to write forms that accept HTML user input (think smart text boxes like TinyMCE, etc.) for some of the fields while at the same time maintaining the built-in security for the rest.

    Request Validation is a core ASP.NET feature that helps in preventing cross-site scripting (XSS) and other flavors of HTML injection attacks. It works by examining incoming requests and blocking when any client-supplied data (such as through form fields or the query string) contains potentially dangerous input (for example, the less-than character ‘<’). It is turned on by default and the you have to explicitly disable it if you want to accept HTML-formatted user input. However, until now this was an all-or-nothing setting and turning it off meant that you had to take extra steps to manually validate all of the inputs for malicious content.

    In ASP.NET MVC you can control whether an action should have request validation performed using ValidateInputAttriubte. For example the following usage will disable request validation:

    [ValidateInput(false)]
    public ActionResult MyAction() { ... }

    Note for ASP.NET 4: because of the hardening of request validation in .NET 4 for the above usage to actually work in MVC 2 you had to revert to 2.0 behavior by changing a setting in web.config:

    <httpRuntime requestValidationMode="2.0" />

    MVC 3 Beta introduces a new property on ValidateInputAttribute called Exclude. Using this property you can opt out of request validation for only some of your fields. For example, take the following User model:

    public class User {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public string Description { get; set; }
        public string Bio { get; set; }
    }

    If you want your users to be able to edit their Bio or Description and insert arbitrary markup into those two fields while at the same time ensuring no markup makes its way into the Name or Email fields you can annotate your action in the following manner:

    [ValidateInput(true, Exclude="Bio, Description")]
    public ActionResult Edit(User user) { ... }

    The Exclude property takes in a string with a comma-separated list of property/field names that should not be validated. All other fields in the incoming request will be validated and blocked if dangerous input is detected.

    With this Beta release it is also no longer necessary to set your application to 2.0 request validation mode. You benefit from .NET 4’s more secure behavior while at the same time being able to allow unvalidated HTML input only on the fields of your choosing.

Page 2 of 5 (25 items) 12345