A technique for localized or enhanced views using ASP.NET MVC 4

by Pete 28. December 2012 08:47

Microsoft introduced Display Modes in ASP.NET MVC 4. Display Modes lets an application determine which views to render to the user.

Scott Hanselman has a great post on how to make a switchable Desktop and Mobile sitethat illustrates how to use Display Modes to customize the views for particular mobile browsers by using the User Agent string the browser passes during the request. One of the points that Scott makes in his post is to customize the view that is passed back to the user by changing the ContextCondition evaluated on each request.

This got me thinking about how to use this same technique to render localized content to users. As it turns out it is quite simple, by adding a ContextCondition that checks the default UserLanguage in the Request you can render any localized content. The example that follows checks to see if the user’s default language is Spanish and if so render a view with *.es.cshtml .

DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("es")
{
    ContextCondition = (ctx => ctx.Request.UserLanguages != null && 
        ctx.Request.UserLanguages[0].IndexOf("es", StringComparison.OrdinalIgnoreCase) >= 0)
});

You can extrapolate this technique to display different content for different types of users (standard, premium, etc). The example below will render a *.premium.cshtml view to a user that is determined to be a “premium” user by the IsPremiumUser extension method.

DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("premium")
{
    ContextCondition = (ctx => ctx.IsPremiumUser)
});

Overall I feel like this is a pretty clean way of dealing with these scenarios. Thoughts?

Tags:

ASP.NET MVC

MORE Making a switchable Desktop and Mobile site with ASP.NET MVC 3

by Pete 8. November 2012 20:34

Recently I was catching up on some reading and discovered Scott Hanselman’s Making a switchable Desktop and Mobile site with ASP.NET MVC 4 and jQuery Mobile post. His post reminded me that this functionality is also available for ASP.NET MVC 3 by using the MobileViewEngines NuGet package that Scott and I have worked on over the past several years.

In this example, we are going to create a very simple ASP.NET MVC 3 Web Application that allows Mobile Device users to switch from a Mobile view to a Desktop view. The source for this MobileViewEngine NuGet project is available on GitHub.

The first step is to launch Visual Studio and create a new ASP.NET MVC 3 Web Application by going to File | New | Project | ASP.NET MVC3 Application. Next, using NuGet, install the MobileViewEngines package. You can do this by either right clicking on References and selecting Manage NuGet Packages or by using the Package Manager Console and typing:

NuGet1

Next, we need to add mobile-friendly Index and _Layout views.

Solution1_thumb

First, we need to create a new view in the Shared directory for our mobile-friendly layout. Below is the code from my sample _Layout.Mobile.cshtml.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>@ViewBag.Title</title>
        <meta name="viewport" content="width=device-width" />
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    </head>
    <body>
        <div data-role="page" data-theme="b">
            <div data-role="header">
                @if (IsSectionDefined("Header")) {
                    @RenderSection("Header")
                } else {
                    <h1>@ViewBag.Title</h1>
                    @Html.Partial("_LogOnPartial")
                }
            </div>
            <div data-role="content">
                @RenderBody()
            </div>
            <div data-role="footer">
                @Html.Partial("ViewSwitcher")
            </div>
        </div>

        @RenderSection("scripts", required: false)
    </body>
</html>

Next, we need to create a new view in the Home directory for our mobile-friendly Index view. Below is the code from my sample Index.Mobile.cshtml.

@{
    ViewBag.Title = "Home Page";
    Layout = "~/Views/Shared/_Layout.Mobile.cshtml";
}

<h2>@ViewBag.Message</h2>
<p>
    To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
</p>

<ul data-role="listview" data-inset="true">
    <li data-role="list-divider">Navigation</li>
    <li>@Html.ActionLink("Home", "Index", "Home")</li>
    <li>@Html.ActionLink("About", "About", "Home")</li>
</ul>

Now that you have the basic pieces in place, you can run the project in your favorite Mobile Web Browser, I like Electric Mobile Studio from Electric Plum(thanks Shaun!), and you will see the Mobile version of the views.

Mobile1_thumb1

If you select the ‘Desktop View’ link on the page, it switches your views to the Desktop friendly views.

Mobile2_thumb

At this point, the basics of Mobile View Switching are setup and you are free to continue by adding jQuery Mobile or your mobile framework of choice.

Refactoring Nerd Dinner - Adding Knockout JS

by Pete 24. July 2012 08:39

Ever wonder what Knockout JS is? Since it's going to be a part of the default ASP.NET template in VS2012 I figured that I should take a look. AND what better project to try something new in than Nerd Dinner.

The Popular Dinners list in the Home/Index view is already using jQuery to get JSON data and dynamically building the list, this seems like the perfect candidate to refactor to use knockout.

dinners

The first step in adding Knockout to NuGet (assuming that you already have the latest Nerd Dinner source) is to get the package from NuGet

knockout

First, I need to add a script reference to knockout in the _Layout.cshtml

<script src="@Url.Content("~/Scripts/knockout-2.1.0.js")" type="text/javascript"></script>


Next, the code in the Index.cshtml view needs to be changed from the empty container that gets dynamically populated to the use a knockout template. So I changed this line:

<div class="entry-content" id="dinnerList"></div>


to:

<ul class="entry-content" id="dinnerList" data-bind="foreach: dinners">
    <li class="dinnerItem">
        <a data-bind="attr: { href: Url }, text: Title"></a>
        <br />
        <strong data-bind="dateString: EventDate, datePattern: 'mmm d'"></strong><span data-bind="rsvpMessage: RSVPCount"></span>
    </li>
</ul>


Similarly, in the NerdDinner.js file _renderDinners needs to be changed to use knockout model binding. After some code cleanup _renderDinners now looks like this:

NerdDinner._renderDinners = function (dinners) {
    var viewModel = {
        dinners: ko.observableArray(dinners)
    };
    ko.applyBindings(viewModel);

    NerdDinner.ClearMap();

    $.each(dinners, function (i, dinner) {
        var LL = new VELatLong(dinner.Latitude, dinner.Longitude, 0, null);

        // Add Pin to Map
        NerdDinner.LoadPin(LL, _getDinnerLinkHTML(dinner), _getDinnerDescriptionHTML(dinner), false);

        // Display the event's pin-bubble on hover.
        var dinnerPin = _getDinnerPin(dinner.DinnerID);
        if (dinnerPin != null) {
            $(dinner).hover(
                function () { NerdDinner._map.ShowInfoBox(dinnerPin); },
                function () { NerdDinner._map.HideInfoBox(dinnerPin); }
            );
        }
    });

    // Adjust zoom to display all the pins we just added.
    if (NerdDinner._points.length > 1) {
        NerdDinner._map.SetMapView(NerdDinner._points);
    }
};


Next, there were some custom binders that I needed to add. One for displaying the EventDate properly and another for showing the RSVP Message. They look like this at the end of the NerdDinner.js file:

ko.bindingHandlers.dateString = {
    update: function (element, valueAccessor, allBindingsAccessor) {
        var value = valueAccessor();
        var allBindings = allBindingsAccessor();
        var valueUnwrapped = ko.utils.unwrapObservable(value);
        var pattern = allBindings.datePattern || 'MM/dd/yyyy';
        var v1 = eval('new' + valueUnwrapped.replace(/\//g, ' '));
        var v2 = v1.format(pattern);
        $(element).text(v2);
    }
};
ko.bindingHandlers.rsvpMessage = {
    update: function (element, valueAccessor) {
        var value = valueAccessor();
        var rsvpMessage = " with " + value + " RSVP";
        if (value > 1)
            rsvpMessage += "s";
        $(element).text(rsvpMessage);
    }
};


Finally, This sample has just barely scratched the surface of the interesting things that knockout allows you to do. If you’re interested in learning more go the Knockout JS site. Also, the Nerd Dinner source is on Codeplex. I’d encourage you to get it, fork it, and make it better!

Hanselminutes: Making your first PhoneGap Application with Peter Mourfield

by Pete 2. February 2012 07:03

I was interviewed recently by Scott Hanselman about the technology that I used in building the Trail To Eagle app. The podcast is available at http://www.hanselminutes.com/304/making-your-first-phonegap-application-with-peter-mourfield .

Tags:

Community

ASP.NET MVC Mobile ViewEngines Available on Nuget

by Pete 4. September 2011 10:36

Lately, I’ve been spending my spare time helping with Nerd Dinner open source project. The other day Scott Hanselman announced the latest release of Nerd Dinner, one of the fixes he mention was as fixing a release-mode caching bug that was introduced a few years back. Also, Phil Haack published the ASP.NET MVC 4 Roadmap,

Hey Microsoft, please extend ASP.NET MVC 4 View Redirection!

by Pete 21. July 2011 08:54

There is a lot of discussion going on in the web development world around how to best support mobile devices.

Scott Hanselman’s ( @shanselman ) post ‘A Better ASP.NET MVC Mobile Device Capabilities ViewEngine’ is an interesting look on how to do view redirection using a combination of a custom view engine with extension methods in ASP.NET MVC.

Kevin Montrose’s ( @kevinmontrose ) post ‘Mobile Views in ASP.NET MVC3’ describes how the StackExchange platform uses a view redirection technique to render mobile-friendly views.

And finally, Phil Haack ( @haacked ) posted a sneak peak into the future of ASP.NET MVC with ‘ASP.NET MVC 4 Roadmap’ which describes the tooling around a view redirection technique very similar to what Kevin Montrose describes.

There are all great, but my thought is why not continue down this path of view redirection and make the functionality more generic. This would allow me (the developer) to use the same technique to do things like deliver specific localized views for my Spanish speaking friends.

Conceptually, here’s what I mean, if I want to redirect the mobile views to SomeView.Mobile.cshtml then I can add some code that looks like this:

ViewEngines.Engines.Add(new RazorViewEngine(c => c.IsMobileDevice(), "Mobile"));

Where IsMobileDevice is some extension method that I wrote that does mobile detection. Alternatively, by doing something like this it allows me to similarly do this:

ViewEngines.Engines.Add(new RazorViewEngine(c => c.IsSpanish(), "es"));

Again, IsSpanish is an extension method that I wrote that allows me to do language detection and the views would redirect to SomeView.es.cshtml.

The whole idea here is that while the framework might provide a basic implementation of mobile views, it is open enough to allow the developer to customize it to whatever scenario they can dream up.

Tags:

ASP.NET MVC

Using HTML5 attributes with ASP.NET MVC 3 Helpers

by Pete 23. May 2011 08:55

ASP.NET MVC 3 introduces a new way of declaring HTML5 attributes with Html Helpers. The trick is to use underscores in place of the dashes. For Example:

@Html.ActionLink("About", "About", "Home", new { data_some_attribute="testing" })

That is all. k. thx. bye.

Tags:

ASP.NET MVC

Extending the ASP.NET MVC ViewEngine to support localization

by Pete 10. May 2011 08:56

I’ve been using with Scott Hanselman’s CustomMobileViewEngine from his post A Better ASP.NET MVC Mobile Device Capabilities ViewEngine along with jQuery Mobile (for mobile templates) and 51degrees.mobi (for accurate mobile browser detection) to build ASP.NET MVC sites that output nice mobile-friendly templates. The techniques that Scott talks about in his post have been working really well.

So recently when I had to solve a similar problem when trying to render out localized templates I took a deeper look into Scott’s approach to see if I could ‘tweak’ it to do what I wanted, which, in Visual Studio looks like:

This allows my site to serve the same URLs for multiple languages. For example the url /Home/Index uses the same controllers, models, etc, but will call different views for English and Spanish users based on the current uiCulture.

Here’s how I did it. I took Scott’s classes and refactored them just a bit so that as I extended this functionality there weren’t bits of code getting duplicated (See DRY – Don’t repeat yourself ).

First, I took the existing CustomMobileViewEngine class and renamed it CustomViewEngine as this engine will no longer be Mobile only. Other than that no changes were necessary.

public class CustomViewEngine : IViewEngine
{
public IViewEngine BaseViewEngine { get; private set; }
public Func IsTheRightDevice { get; private set; }
public string PathToSearch { get; private set; }

public CustomViewEngine(Func isTheRightDevice, string pathToSearch, IViewEngine baseViewEngine)
{
BaseViewEngine = baseViewEngine;
IsTheRightDevice = isTheRightDevice;
PathToSearch = pathToSearch;
}

public ViewEngineResult FindPartialView(ControllerContext context, string viewName, bool useCache)
{
if (IsTheRightDevice(context))
{
return BaseViewEngine.FindPartialView(context, PathToSearch + "/" + viewName, useCache);
}
return new ViewEngineResult(new string[] { }); //we found nothing and we pretend we looked nowhere
}

public ViewEngineResult FindView(ControllerContext context, string viewName, string masterName, bool useCache)
{
if (IsTheRightDevice(context))
{
return BaseViewEngine.FindView(context, PathToSearch + "/" + viewName, masterName, useCache);
}
return new ViewEngineResult(new string[] { }); //we found nothing and we pretend we looked nowhere
}

public void ReleaseView(ControllerContext controllerContext, IView view)
{
throw new NotImplementedException();
}
}

Next, I took the most generic AddMobile extension method, renamed it AddCustomView and put it in it’s own ViewHelper class.
public static class ViewHelper
{
public static void AddCustomView(this ViewEngineCollection ves, Func isTheRightDevice, string pathToSearch)
where T : IViewEngine, new()
{
ves.Add(new CustomViewEngine(isTheRightDevice, pathToSearch, new T()));
}
}

Additionally, I refactored the existing MobileHelpers class to call the newly refactored AddCustomView to prevent further duplication of code.
public static class MobileHelpers
{
public static bool UserAgentContains(this ControllerContext c, string agentToFind)
{
return (c.HttpContext.Request.UserAgent.IndexOf(agentToFind, StringComparison.OrdinalIgnoreCase) >= 0);
}

public static bool IsMobileDevice(this ControllerContext c)
{
return c.HttpContext.Request.Browser.IsMobileDevice;
}

public static void AddMobile(this ViewEngineCollection ves, string userAgentSubstring, string pathToSearch)
where T : IViewEngine, new()
{
ves.AddCustomView(c => c.UserAgentContains(userAgentSubstring), pathToSearch);
}

public static void AddIPhone(this ViewEngineCollection ves) //specific example helper
where T : IViewEngine, new()
{
ves.AddCustomView(c => c.UserAgentContains("iPhone"), "Mobile/iPhone");
}

public static void AddGenericMobile(this ViewEngineCollection ves)
where T : IViewEngine, new()
{
ves.AddCustomView(c => c.IsMobileDevice(), "Mobile");
}
}

Finally, I created some AddLanguage extension methods in their own LocalizationHelpers class along with the UICulture detection routing.
public static class LocalizationHelpers
{
public static bool UICultureEquals(this ControllerContext c, string stringToFind)
{
var culture = CultureInfo.CurrentUICulture;
var cultureName = culture != null ? culture.Name : string.Empty;

return (cultureName.IndexOf(stringToFind, StringComparison.OrdinalIgnoreCase) >= 0);
}

public static void AddLanguage(this ViewEngineCollection ves, string cultureName, string pathToSearch)
where T : IViewEngine, new()
{
ves.AddCustomView(c => c.UICultureEquals(cultureName), pathToSearch);
}

public static void AddLanguage(this ViewEngineCollection ves, string cultureName)
where T : IViewEngine, new()
{
ves.AddCustomView(c => c.UICultureEquals(cultureName), cultureName);
}
}

Using the Localized views in your project is as simple as registering the view in the Application_Start() method.
ViewEngines.Engines.Clear();
ViewEngines.Engines.AddLanguage("es-ES");
ViewEngines.Engines.AddGenericMobile();
ViewEngines.Engines.AddCustomView(c => c.IsMobileDevice() && c.UICultureEquals("es-ES"), "Mobile/es-ES");
ViewEngines.Engines.Add(new WebFormViewEngine());

Lastly, my sample project has these classes in they’re own class library because it’s my hope to be able to provide this functionality as a NuGet package soon.

Tags:

ASP.NET MVC

About Pete

Peter Mourfield

Peter Mourfield is a software developer, speaker, agile development coach, husband, father, and TaxSlayer employee.

More...

Widget Twitter not found.

Root element is missing.X

Month List

Disclaimer

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