Reactive

Using Rx in ASP.NET

I recently started an ASP.NET MVC 5 project, and having used Reactive Extensions (Rx) for five years in other projects I was really keen on using Rx in my ASP.NET code also. The question was how? Well, after some tinkering I figured it all out.

Asynchronous action methods

The first thing we’ll take advantage of is the fact that action methods in controllers can be asynchronous. Instead of declaring your action method like this:

public ActionResult Index()

You type:

public async Task<ActionResult> Index()

That alone frees up the web server by asynchronously waiting for the response to ‘come back’. This means it can serve other requests in the meantime. The next step is of course converting an observable Rx chain like this:

var allOrders = ordersService
                    .GetAllOrders()
                    .Timeout(TimeSpan.FromSeconds(30));

into an ActionResult . Well, to do that we need an extension method. Basically, we want to convert an IObservable<T> to a Task<T> . The reason is of course to match the action method’s return type.

Error handling

Also, we need to incorporate error handling, so that any exceptions are handled and the appropriate return value is used. In general I always prefer to have generic (“catch-all”) error handling in place, and then, if needed, we can customize it for specific cases. That way no errors slip through.

How to use it

I decided to use optional parameters for the error handling. Here’s what the default use of my extension method looks like:

public async Task<ActionResult> Index()
{
    return await orderService
                     .GetAllOrders()
                     .Select(orders => new OrdersViewModel(orders))
                     .ToActionResult(View);
}

You could also write:

public async Task<ActionResult> Index()
{
    return await orderService
                     .GetAllOrders()
                     .Select(orders => new OrdersViewModel(orders))
                     .ToActionResult(viewModel => RedirectToAction("AddNewOrder", viewModel));
}

And if you want to do specific error handling, you could do:

public async Task<ActionResult> Index()
{
    return await orderService
                     .GetAllOrders()
                     .Select(orders => new OrdersViewModel(orders))
                     .ToActionResult(viewModel => RedirectToAction("AddNewOrder", viewModel), new HttpNotFoundResult("Get all orders failed"));
}

The extension method

public static Task<ActionResult> ToActionResult<T>(
    this IObservable<T> source, 
    IScheduler scheduler, 
    Func<T, ActionResult> successAction,
    Func<ActionResult> failAction, TimeSpan? timeout)
        {
            timeout = timeout ?? Constants.DefaultTimeout;

            return source
                .Take(1)
                .Select(successAction)
                .Timeout(timeout.Value, scheduler)
                .Catch<ActionResult, Exception>(e => Observable.Return(failAction != null ? failAction() : new HttpNotFoundResult(e.Message)))
                .ToTask();
        }

As you can see from the code above you can specify an optional scheduler, as well as a custom timeout period.

Source code

The full source code is available on Github.


Posted

in

, ,

by

Tags:

Comments

Leave a Reply