Skip to content

Mvc articles

Comma-separated parameter values in WebAPI  

The model binding mechanism in ASP.NET is pretty slick – it’s clever and highly extensible and built on TypeDescriptor system for all sorts of re-use that lets you get out of having to write boilerplate code to map between CLR objects and their web representations.

One surprising thing however is that out of the box neither WebAPI or MVC support comma-separated parameter values when bound to an array, e.g.

public class MyController : Controller {
    public string Page([FromUri]int[] ids) {
        return String.Join(" ; ", ids);
    }
}

Will only return 1 ; 2 ; 3 when supplied with /my/page?ids=1&ids=2&ids=3 and if you instead give it /my/page?ids=1,2,3 it will fail.

The reason for this was likely because there isn’t a standard for this at all and that the former – supported – scenario maps to what forms do when they post multiple value selections such as that in a select list box. The latter however is much more readable and is expected by some client frameworks and supported by some other web frameworks such as the Java Spring MVC framework.

Of course that extensible system lets us easily extend this behavior so that we can support both transparently – and interestingly enough – even mix-and-match on the same URL. So for example;

/my/page?ids=1,2&ids=3 will now return 1 ; 2 ; 3 in our example.

Although this supports both types if you are currently passing invalid or blank values to your app they are treated by the default model binder as 0. e.g. ?ids=&ids=1&ids=a would have been received as 0, 1, 0 but now empty values are skipped and invalid values would throw an error – as they should!

CommaSeparatedArrayModelBinder class

The source is available in the DamienGKit project but also here.

Out of the box it supports integer types and Guid’s although you could extend it to floats and decimals – again just be careful with that formatting!

public class CommaSeparatedArrayModelBinder : IModelBinder {
  private static readonly Type[] supportedElementTypes = {
    typeof(int), typeof(long), typeof(short), typeof(byte),
    typeof(uint), typeof(ulong), typeof(ushort), typeof(Guid)
  };

  public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) {
    if (!IsSupportedModelType(bindingContext.ModelType)) return false;
    var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
    var stringArray = valueProviderResult?.AttemptedValue?
                      .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
    if (stringArray == null) return false;
    var elementType = bindingContext.ModelType.GetElementType();
    if (elementType == null) return false;

    bindingContext.Model = CopyAndConvertArray(stringArray, elementType);
    return true;
  }

  private static Array CopyAndConvertArray(IReadOnlyList sourceArray, Type elementType) {
    var targetArray = Array.CreateInstance(elementType, sourceArray.Count);
    if (sourceArray.Count > 0) {
      var converter = TypeDescriptor.GetConverter(elementType);
      for (var i = 0; i < sourceArray.Count; i++)
        targetArray.SetValue(converter.ConvertFromString(sourceArray[i]), i);
    }
    return targetArray;
  }

  internal static bool IsSupportedModelType(Type modelType) {
    return modelType.IsArray && modelType.GetArrayRank() == 1
      && modelType.HasElementType
      && supportedElementTypes.Contains(modelType.GetElementType());
  }
}

public class CommaSeparatedArrayModelBinderProvider : ModelBinderProvider {
  public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType) {
    return CommaSeparatedArrayModelBinder.IsSupportedModelType(modelType)
      ? new CommaSeparatedArrayModelBinder() : null;
  }
}

To register

It’s necessary to register ModelBinderProviders with your ASP.NET application at start-up, usually in the WebApiConfig.cs file.

public static class WebApiConfig {
  public static void Register(HttpConfiguration config) {
    // All your usual configuration up here
    config.Services.Insert(typeof(ModelBinderProvider), 0, new CommaSeparatedArrayModelBinderProvider());
  }
}

[)amien

Differences between Azure Functions v1 and v2 in C#  

I’ve been messing around in the .NET ecosystem again and have jumped back in with Azure Functions (similar to AWS Lambda) to get my blog onto 99% static hosting. I immediately ran into the API changes between v1 and v2 (currently in beta).

These changes are because v1 was based around .NET 4.6 using WebAPI 2 while the v2 is based on ASP.NET Core which uses MVC 6. There are some guides around to converting but none in the pure context of Azure Functions.

I’ll illustrate with a PageViewCount sample that uses Table Storage to retrieve and update a simple page count.

v1 (.NET 4.61 / WebAPI 2)

[FunctionName("PageView")]
public static async Task<HttpResponseMessage> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get")]HttpRequestMessage req, TraceWriter log)
{
    var page = req.MessageUri.ParseQueryString()["page"];
    if (String.IsNullOrEmpty(page))
        return req.CreateErrorResponse(HttpStatusCode.BadRequest, "'page' parameter missing.");

    var table = Helpers.GetTableReference("PageViewCounts");
    var pageView = await table.RetrieveAsync<PageViewCount>("damieng.com", page) ?? new PageViewCount(page) { ViewCount = 0 };
    var operation = pageView.ViewCount == 0
        ? TableOperation.Insert(pageView)
        : TableOperation.Replace(pageView);

    pageView.ViewCount++;
    await table.ExecuteAsync(operation);

    return req.CreateResponse(HttpStatusCode.OK, new { viewCount = pageView.ViewCount });
}

v2 (ASP.NET Core / MVC 6)

[FunctionName("PageView")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get")]HttpRequest req, TraceWriter log)
{
    var page = req.Query["page"];
    if (String.IsNullOrEmpty(page))
       return new BadRequestObjectResult("'page' parameter missing.");

    var table = Helpers.GetTableReference("PageViewCounts");
    var pageView = await table.RetrieveAsync<PageViewCount>("damieng.com", page) ?? new PageViewCount(page) { ViewCount = 0 };
    var operation = pageView.ViewCount == 0
        ? TableOperation.Insert(pageView)
        : TableOperation.Replace(pageView);

    pageView.ViewCount++;
    await table.ExecuteAsync(operation);

    return new OkObjectResult(new { viewCount = pageView.ViewCount });
}

Differences

The main differences are that:

  1. The return types are now the simpler IActionResult/ObjectResult objects rather than extension methods off of HttpRequestMessage (so, easier to mock/create custom ones)
  2. The input is the HttpRequest object rather than HttpResponseMessage (easier to get query parameters)

If you get an error about ‘Can not create abstract class’ when executing your function then you are trying to use the wrong one in the wrong environment!

Helpers

Both classes above use a small helper class to take care of Table Storage which doesn’t have the nicest to use API. A wrapper much like a data context that ensures the right types go to the right table names might be an even better options.

static class Helpers
{
    public static CloudStorageAccount GetCloudStorageAccount()
    {
        var connection = ConfigurationManager.AppSettings["DamienGTableStorage"];
        return connection == null 
            ? CloudStorageAccount.DevelopmentStorageAccount
            : CloudStorageAccount.Parse(connection);
    }

    public static CloudTable GetTableReference(string name)
    {
        return GetCloudStorageAccount().CreateCloudTableClient().GetTableReference(name);
    }

    public static async Task<T> RetrieveAsync<T>(this CloudTable cloudTable, string partitionKey, string rowKey) 
       where T:TableEntity
    {
        var tableResult = await cloudTable.ExecuteAsync(TableOperation.Retrieve(partitionKey, rowKey));
        return (T)tableResult.Result;
    }
}

To compile

If you want to compile this or maybe you were just looking for code to do a simple page counter here’s the missing TableEntity class;

public class PageViewCount : TableEntity
{
    public PageViewCount(string pageName)
    {
        PartitionKey = "damieng.com";
        RowKey = pageName;
    }

    public PageViewCount() { }
    public int ViewCount { get; set; }
}

[)amien

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