Archive for ASP.NET tag

Behind the scenes at xbox.com – RSS enabling web marketplace

A number of people were requesting additional RSS feeds for the xbox.com web marketplace. (We had just one that included all new arrivals)

Looking across our site as the various lists of products we display today the significant views are:

  • Browse games by department
  • Search results
  • Promotions (e.g. Deal of the week)
  • Game detail (shows downloads available beneath it)
  • Avatar item browse

These views also have sorting options and a set of filters available for things like product type, game genre, content rating etc.

So we had a couple of options:

  1. Write controller actions that expose the results of specific queries as RSS
  2. Introduce a mechanism whereby any of our product result pages can render as RSS including any user-defined filtering

Our web marketplace is written in ASP.NET MVC (like most of xbox.com) so while option 1 sounds simpler MVC really helps us make option 2 more attractive by way of a useful feature called ActionFilters that let us jump in and reshape the way existing actions behave.

ActionFilters

ActionFilters can be applied to either to an individual action method on a controller or to the controller class itself which applies it to all the actions on that controller. They provide hooks into the processing pipeline where you can jump in and perform additional processing.

The most interesting events are:

  • OnActionExecuting
  • OnActionExecuted
  • OnResultExecuting
  • OnResultExecuted
We’re going to hook in to the OnActionExecuted step – this is because we always want to run after the code in the controller action has executed but before the ActionResult has done it’s work – i.e. before page or RSS rendering.

Writing our ActionFilter

The first thing we want to do is identify that a request wants the RSS version. One way is to read the accepts header and switch when it requests mime/type but this can be a little trickier to test,  another is to append a query parameter on the url which is very easy to test.

Once we’ve identified the incoming request should be for RSS we need to identify the data we want to turn into RSS and repurpose it.

All the views we identified at the start of this post share a common rendering mechanism and each view model subclasses from one of our base models. For simplicity though we’ll imagine an interface that just exposes an IEnumerable<Product> property.

public class RssEnabledAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext) {
        var viewModel = filterContext.Controller.ViewData.Model as IProductResultViewModel;
        if (viewModel == null)
            return;

        var rssFeedTitle = FeedHelper.MakeTitle(viewModel.Results);
        filterContext.Controller.ViewData.Add("RssFeedTitle", rssFeedTitle);

        var format = filterContext.RequestContext.HttpContext.Request.QueryString["format"];
        if (format == "rss" && rssFeedTitle != null) {
            var urlHelper = new UrlHelper(filterContext.RequestContext);
            var url = QueryStringUtility.RemoveQueryStringParameter(filterContext.RequestContext.HttpContext.Request.Url.ToString(), "format");
            var feedItems = FeedHelper.GetSyndicationItems(viewModel.Results, urlHelper);
            filterContext.Result = FeedHelper.CreateProductFeed(rssFeedTitle, viewModel.Description, new Uri(url), feedItems);
        }

        base.OnActionExecuted(filterContext);
    }
}

This class relies on our FeedHelper class to achieve three things it needs:

  1. MakeTitle takes the request details – i.e. which page, type of products, filtering and sorting is selected and makes a title by re-using our breadcrumbs
  2. GetSyndicationItems takes the IEnumerable<Product> and turns it into IEnumerable<SyndicationItem> by way of a foreach projecting Product into SyndicationItem with some basic HTML formatting, combining the product image and setting the correct category (with a yield thrown in for good measure)
  3. CreateProductFeed then creates a Syndication feed with the appropriate Copyright and Language set and chooses the formatter – in our case RSS 2.0 but could easily be Atom 1.0, e.g.
public static SyndicationFeedResult CreateProductFeed(string title, string description, Uri link, IEnumerable<SyndicationItem> syndicationItems)
{
    var feed = new SyndicationFeed(title, description, link, syndicationItems) {
        Copyright = new TextSyndicationContent(String.Format(Resources.FeedCopyrightFormat, DateTime.Now.Year)),
        Language = CultureInfo.CurrentUICulture.Name
    };

    return new FeedResult(new Rss20FeedFormatter(feed, false));
}

The FeedResult class is a simple one that takes the built-in .NET SyndicationFeed class and wires it up to MVC by implementing an ActionResult that writes the XML of the SyndicationFeedFormatter into the response as well as setting the application/rss+xml content type and encoding.

Advertising the feed in the head

Now that we have the ability to serve up RSS we need to let browsers know it exists.

The ActionFilter we wrote above needs to know the title of the RSS feed regardless of whether it is rendering the RSS (which needs a title) or rendering the page (which will need to advertise the RSS title) so it always calculates it and then puts it into the ViewData dictionary with the key RssFeedTitle.

Now finally our site’s master page can check for the existence of that key/value pair and advertise it out with a simple link tag:

var rssFeedTitle = ViewData["RssFeedTitle"] as string;
if (!String.IsNullOrEmpty(rssFeedTitle)) { %>
<link rel="alternate" type="application/rss+xml" title="<%:rssFeedTitle%>" href="<%:Url.ForThisAsRssFeed%>" />
<% }

This code requires just one more thing – a very small UrlHelper which will append “format=rss” to the query string (taking into account whether there existing query parameters or not).

The result of this is we can now just add [RssEnabled] in front of any controller or action to turn on RSS feeds for that portion of our marketplace! :)

[)amien

Creating RSS feeds in ASP.NET MVC

ASP.NET MVC is the technology that brought me to Microsoft and the west-coast and it’s been fun getting to grips with it these last few weeks.

Last week I needed to expose RSS feeds and checked out some examples online but was very disappointed.

If you find yourself contemplating writing code to solve technical problems rather than the specific business domain you work in you owe it to your employer and fellow developers to see what exists before churning out code to solve it.

The primary excuse (and I admit to using it myself) is “X is too bloated, I only need a subset. I can write that quicker than learn their solution.” but a quick reality check:

  • Time – code always takes longer than you think
  • Bloat – indicates the problem is more complex than you realize
  • Growth – todays requirements will grow tomorrow
  • Maintenance – fixing code outside your business domain
  • Isolation – nobody coming in will know your home-grown solution

The RSS examples I found had their own ‘feed’ and ‘items’ classes and implemented flaky XML rendering by themselves or as MVC view pages.

If these people had spent a little time doing some research they would have discovered .NET’s built in SyndicatedFeed and SyndicatedItem class for content and two classes (Rss20FeedFormatter and Atom10FeedFormatter )  to handle XML generation with correct encoding, formatting and optional fields.

All that is actually required is a small class to wire up these built-in classes to MVC.

using System;
using System.ServiceModel.Syndication;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Xml;

namespace MyApplication.Something
{
    public class FeedResult : ActionResult
    {
        public Encoding ContentEncoding { get; set; }
        public string ContentType { get; set; }

        private readonly SyndicationFeedFormatter feed;
        public SyndicationFeedFormatter Feed{
            get { return feed; }
        }

        public FeedResult(SyndicationFeedFormatter feed) {
            this.feed = feed;
        }

        public override void ExecuteResult(ControllerContext context) {
            if (context == null)
                throw new ArgumentNullException("context");

            HttpResponseBase response = context.HttpContext.Response;
            response.ContentType = !string.IsNullOrEmpty(ContentType) ? ContentType : "application/rss+xml";

            if (ContentEncoding != null)
                response.ContentEncoding = ContentEncoding;

            if (feed != null)
                using (var xmlWriter = new XmlTextWriter(response.Output)) {
                    xmlWriter.Formatting = Formatting.Indented;
                    feed.WriteTo(xmlWriter);
                }
        }
    }
}

In a controller that supplies RSS feed simply project your data onto SyndicationItems and create a SyndicationFeed then return a FeedResult with the FeedFormatter of your choice.

public ActionResult NewPosts() {
    var blog = data.Blogs.SingleOrDefault();
    var postItems = data.Posts.Where(p => p.Blog = blog).OrderBy(p => p.PublishedDate).Take(25)
        .Select(p => new SyndicationItem(p.Title, p.Content, new Uri(p.Url)));

    var feed = new SyndicationFeed(blog.Title, blog.Description, new Uri(blog.Url) , postItems) {
        Copyright = blog.Copyright,
        Language = "en-US"
    };

    return new FeedResult(new Rss20FeedFormatter(feed));
}

This also has a few additional advantages:

  1. Unit tests can ensure the ActionResult is a FeedResult
  2. Unit tests can examine the Feed property to examine results without parsing XML
  3. Switching to Atom format involved just changing the new Rss20FeedFormatter to Atom10FeedFormatter

[)amien

Localizing MVC for ASP.NET views and master pages

Microsoft’s MVC for ASP.NET is still under serious development but at the moment support for localization is a little weak. Here’s one approach that works with the 04/16 source-drop.

LocalizingWebFormViewLocator class

This class helps by trying to identify language-specific versions of views, user controls and master-pages where they exist, falling back to the generic one where necessary.

public class LocalizingWebFormViewLocator : ViewLocator
{
    public LocalizingWebFormViewLocator() : base() {
        ViewLocationFormats = new[] { "~/Views/{1}/{0}.{2}aspx", "~/Views/{1}/{0}.{2}ascx", "~/Views/Shared/{0}.{2}aspx", "~/Views/Shared/{0}.{2}ascx" };
        MasterLocationFormats = new[] { "~/Views/{1}/{0}.{2}master", "~/Views/Shared/{0}.{2}master" };
    }

    protected override string GetPath(RequestContext requestContext, string[] locationFormats, string name) {
        string foundView = FindViewLocation(locationFormats, requestContext, name, CultureInfo.CurrentUICulture.Name + ".");
        if (String.IsNullOrEmpty(foundView))
            foundView = FindViewLocation(locationFormats, requestContext, name, "");
        return foundView;
    }

    protected string FindViewLocation(string[] locationFormats, RequestContext requestContext, string name, string cultureSuffix) {
        string controllerName = requestContext.RouteData.GetRequiredString("controller");
        foreach (string locationFormat in locationFormats) {
            string viewFile = string.Format(CultureInfo.InvariantCulture, locationFormat, name, controllerName, cultureSuffix);
            if (HostingEnvironment.VirtualPathProvider.FileExists(viewFile))
                return viewFile;
            }
        return null;
    }
}

Using the class

To use the class you must set the ViewLocator on the WebFormViewEngine to a new instance of LocalizingWebFormViewLocator (either in the constructor or in your common controller subclass) and ensure that any master pages are specified on the RenderView calls to ensure the localized version is detected.

public class HomeController : Controller
{
    public HomeController() {
        ((WebFormViewEngine)ViewEngine).ViewLocator = new LocalizingWebFormViewLocator();
    }

    public ActionResult Index() {
        return RenderView("Index", "Site");
    }

    public ActionResult About() {
        return RenderView("About", "Site");
    }
}

You must also ensure the thread’s current UI culture is set. The easiest way to do this is to specify the following in your web.config file’s system.web section which will pick it up automatically from the user’s browser settings via the HTTP language-accept header.

<globalization responseEncoding="UTF-8" requestEncoding="UTF-8" culture="auto" uiCulture ="auto" />

MVC for ASP.NET default page in pseudo-Japanese via the Babelfish
Then all you need to do is create views and master pages that have the culture name appended between the name and .aspx, e.g:

/Views/Home/Index.aspx (common fall-back for this view)
/Views/Home/Index.ja.aspx (Japanese view)
/Views/Home/Index.en-GB.aspx (British English view)

/Views/Shared/Site.Master (common fall-back for this masterpage)
/Views/Shared/Site.ja.Master (Japanese masterpage)

Caveats

There are some limitations to this solution:

Only primary language is attempted

Only the user’s primary language specified in their browser is attempted despite browsers having a complete list in order of preference. Ideally we would scan down this entire list before giving up but that would need more code and there is the issue of whether scanning for several languages across several folders could be too much of a performance hit.

Specifying the masterpage on RenderView

It would be nice if you didn’t have to specify the masterpage on renderview but if you do not then the ViewLocator never gets called to resolve the actual masterpage address. This may be for backward compatibility within MVC.

Creating files in Visual Studio

Visual Studio 2008 seems to get a little confused if you create a Index.ja.aspx or Site.ja.aspx – whilst the files are created okay the names are not and you will need to adjust the class names to ensure they don’t conflict and make sure the opening declaration on the .aspx file points to the right code-behind page and inherits from the correct name.

Of course the beauty of this approach is you can mix-and-match using dedicated views where required and localising labels in the fall-back view when it isn’t.

[)amien

5 signs your ASP.NET application may be vulnerable to HTML injection

If you don’t encode data when using any of the following methods to output to HTML your application could be compromised by unexpected HTML turning up in the page and modifying everything from formatting though to capturing and interfering with form data via remote scripts (XSS). Such vulnerabilities are incredibly dangerous.

Using MonoRail or Microsoft’s MVC does not make you automatically immune – use {! } in MonoRail’s Brail engine and the HtmlHelpers in Microsoft’s MVC to ensure correct encoding.

Just imagine post.Author contains “><script src=”http://abadsite.com”></script> after an unscrupulous user entered that into a field your application uses and it got into the database. The following typical ASP.NET techniques would leave you open.

1. You use <%= %> or <%# %> tags to output data

Example showing outputting literals with <%= %> :

// Vulnerable
<p>Posted by <%= post.Author %></p>
// Secure
<p>Posted by <%= HttpUtility.HtmlEncode(post.Author) %></p>

2. You use Response.Write

Example showing writing out attributes with Response.Write and String.Format, again post.Author could contain <script>:

// Vulnerable
Response.Write(String.Format("<input type=\"text\" value=\"{0}\" />", post.Author);
// Secure
Response.Write(String.Format("<input type=\"text\" value=\"{0}\" />", HttpUtility.HtmlAttributeEncode(post.Author));

3. You set HRef or Src on HtmlAnchor, HtmlImage or HtmlnputImage controls

In general the HtmlControls namespace are very well behaved with encoding but there is a bug in the code that attempts to adjust the relative url’s for href and src attributes which causes those properties to bypass encoding (I’ve reported this to Microsoft).

Example showing anchor HRef attribute abuse:

// Vulnerable
outputDiv.Controls.Add(new HtmlAnchor() { Text = "Test", HRef = post.Author } );
// Secure
outputDiv.Controls.Add(new HtmlAnchor() { Text = "Test", HRef = HttpUtility.HtmlAttributeEncode(post.Author) } );

4. You set the Text property of WebControls/WebForms

You would imagine the high-level WebForms controls would take care of encoding and you’d be wrong.

Example showing the Label control being so easily taken advantage of:

// Vulnerable
outputDiv.Controls.Add(new Label() { Text = post.Author } );
// Secure
outputDiv.Controls.Add(new Label() { Text = HttpUtility.HtmlEncode(post.Author) } );

The one exception to this is the Text property of input controls – as they put the value into an attribute and therefore call HttpUtility.HtmlAttributeEncode for you.

5. You use the LiteralControl

LiteralControl is a useful control for adding text to the output stream that doesn’t require it’s own tag. It also helpfully, and uncharacteristically, provides a useful constructor. Unfortunately it fails encode the output.

Example showing poor LiteralControl wide open:

// Vulnerable
outputDiv.Controls.Add(new LiteralControl(post.Author));
// Secure
outputDiv.Controls.Add(new LiteralControl(HttpUtility.HtmlEncode(post.Author)));
Do not:
  1. Encode data in the database – your contaminated data will be difficult to use elsewhere and will end up double-encoded
  2. Look for script on submit – you won’t catch every combination and it might prevent valid data
  3. Trap entry with client-side code – it is trivially bypassed

Just encode the output :)

[)amien
(The samples use .NET 3.5 object initializer syntax for brevity as many affected controls do not have useful constructors)