Monday, February 16, 2015

Microsoft 70-486: Compose the UI layout of an application

Exam Objectives


Implement partials for reuse in different areas of the application, design and implement pages by using Razor templates (Razor view engine), design layouts to provide visual structure, implement master/application pages

Quick Overview of Training Materials


Text templating using Razor the easy way
How to create custom HTML Helpers for ASP.NET MVC 3
C# Razor Syntax Quick Reference


Partial Views


I covered partial views briefly in my post on objective 2.2 (UI Behavior), with regard to returning AJAX responses. The difference between partial views and regular views really has more to do with how they are used than the actual view files themselves.  In this code, I'm using the same view as both a full view and a partial:

public ActionResult About()
{
    ViewBag.Message = "Your application description page.";
 
    return View("AjaxAction");
}
 
public PartialViewResult AjaxAction()
{
    return PartialView();
}


There is one caveat here. In the above example, the AjaxAction.cshtml file didn't explicitly define a Layout.  If I modified it to include a Layout, then the layout would be included regardless of whether I returned it as a partial or a full view:

@{
    ViewBag.Title = "View1";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
This is the partial view returned!


Conceptually, a partial view is just that: a small piece of the overall UI.  Using partials allows you to make Ajax calls, and it can lend itself to partial page caching (donut hole caching).  It can also be handy for reusing code.  For example, if you have most of the same form elements for an "Add New" form and an "Update" form, you could put the common elements in a partial view that is consumed by both views.  Partial views also also little bits of the main UI to be easily changed, which you can see with the "login" partial, which displays differently depending on if you are logged in or not, while leaving the _Layout page otherwise agnostic as to whether you are logged in or not.

Partial views can be included declaratively in another view by using the @Html.Partial() and @Html.RenderPartial helper function.  It seems the main difference between the two is that Html.Partial will return the Html encoded string of the partial view, which can then be stored in a variable, whereas RenderPartial writes directly to the context and returns void.

Razor Templates


The Exam Ref here focuses on EditorFor as an example of a "Razor Template".  Every other example I managed to find treated Razor helpers and templates as basically synonymous.  I covered the built in helpers pretty thoroughly in my post "Apply the UI design", so I think looking at building a custom helper will be illuminating.

One way to use helpers is to create extension methods, much like "EditorFor", "LabelFor", etc. This is pretty straightforward, just create the method, and then reference the namespace, either in the view with the @using statement, or in the view web.config file in <namespaces>.  Here is a quick example that just spits out some static text (not super useful, I know, but you get the idea):

namespace MVCMusicStoreHelpers
{
    public static class HtmlHelperExtensions
    {
        public static MvcHtmlString TestHelper(this HtmlHelper helper) 
        {
            return new MvcHtmlString("The TestHelper method generated some text!");
        }
    }
}

This is the code in the view:
@using MVCMusicStoreHelpers
@Html.TestHelper()

In lieu of the @using statement, it's possible to include the namespace in the web.config, though this way doesn't seem to give you Intellisense (at least it didn't for me...).  But if you needed to use the helper everywhere, this would be pretty helpful.
  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization"/>
        <add namespace="System.Web.Routing" />
        <add namespace="MVCMusicStore" />
        <add namespace="MVCMusicStoreHelpers"/>
      </namespaces>
    </pages>
  </system.web.webPages.razor>

As a slightly more useful example, we could make a helper that takes an IEnumerable<string> and converts it to an unordered list.  Not super useful, but it's more in the general vicinity of usefulness lol.

public static MvcHtmlString Listify(this HtmlHelper helperIEnumerable<string> things)
{
    string listOfThings = "<ul>";
    foreach (var item in things)
    {
        listOfThings += "<li>" + item.ToString() + "</li>";
    }
    listOfThings += "</ul>";
 
    return new MvcHtmlString(listOfThings);
}

We call it the same way (here I've commented out the "using" statement to demonstrate the web.config namespace call in action):
@*@using MVCMusicStoreHelpers*@
@Html.TestHelper()
@Html.Listify(new List<string>() {"Banana""Orange""Apple""Grape" })

And this is the result:

Of course, there is no requirement that helpers be extension methods of the HtmlHelper class.  Really any method that returns string data can be used with the Razor syntax to programmatically create page content.  This static method can be called like any other static method:

public static class GenericHelper
{
    public static MvcHtmlString StandAloneHelper()
    {
        return new MvcHtmlString("<p>This helper</p><p>is not an extension method</p>");
    }
}

Calling the method in the view:
@using MVCMusicStore.Helpers
@GenericHelper.StandAloneHelper()

This would output two paragraphs with the static text, as expected.  

Designing Layouts


Here the Exam Ref pretty much punts it back to a refresher on basic Html.  My blog on applying UI design pretty much references everything relevant to writting HTML, so there is little point in repeating that.

ASP.NET uses convention to determine which views to use when they aren't called explicitly.  When you call View() in a method in the HomeController, the framework will look for a view with the same name as the method in the Views/Home/ folder.  If the view isn't found in the folder corresponding to the controller, the framework will look in the Views/Shared/ folder.  This is true of both full views and partial views, so calling PartialView() works the same way.


Above, the Index view is being passed a collection of "albums".  The Index view is strongly typed to take a list of album objects as it's model:

@model List<MVCMusicStore.Models.Album>
@{
    ViewBag.Title = "Home Page";
}
 
<div id="promotion">
 
</div>
 
<h3><em>Fresh</em> off the grill</h3>
 
<ul id="album-list">
    @foreach (var album in Model)
    {
        <li>
            <a href="@Url.Action("Details"new{id=album.AlbumId})">
                <img alt="@album.Title" src="@album.AlbumArtUrl" />
                <span>@album.Title</span>
 
            </a>
        </li>
    }
 
</ul>

In this case, Razor will iterate through the collection and create a list item with a linked image for each album in the collection passed to the view.

Implement master pages


The _Layout view is included with the default template, and is located in the Views/Shared/ folder.  If a page doesn't specify a specific "Layout" page, then this one will be used.  A Layout page will include the markup that is shared across the views that make use of it.  In the default MVC template, _Layout includes the navigation links, Login partial view, <head> tag, site wide script and style includes.  This minimizes duplication, which is what we are always after in our code.  The "default" layout for the site is set in the _ViewStart.cshtml file in the /Views/ folder.

One command that is vital to a layout page working correctly is @RenderBody().  When a view is rendered, this function is where the content of the view is inserted.  It would seem that returning a PartialViewResult avoids execution of the _ViewStart code.
.

3 comments: