Reverse Proxy in ASP.NET Web API – Part 2

At first I only needed the reverse proxy for a JSON rest API. Soon, however, it was expanded to also cover HTML content. Thus the below update to make sure any URLs in the HTML was replaced to correctly match the reverse proxy server and not the internal server:

public class ProxyHandler : DelegatingHandler
{
    private readonly string redirectUrl; 

    public ProxyHandler(string redirectUrl)
    {
        this.redirectUrl = redirectUrl;
    } 

    private async Task<HttpResponseMessage> RedirectRequest(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var redirectLocation = redirectUrl;
        var localPath = request.RequestUri.LocalPath.Replace("ExternalVirtualPath", "InternalVirtualPath"); 

        var client = new HttpClient(new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }); 

        var clonedRequest = await HttpRequestMessageExtensions.CloneHttpRequestMessageAsync(request); 

        clonedRequest.RequestUri = new Uri(redirectLocation + localPath); 

        var httpResponseMessage = await client.SendAsync(clonedRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
        httpResponseMessage.Headers.Add("X-ReverseProxy", "true"); 

        if (httpResponseMessage.Content?.Headers?.ContentType != null)
        {
            if (httpResponseMessage.Content.Headers.ContentType.MediaType == "text/html")
            {
                var content = await httpResponseMessage.Content.ReadAsByteArrayAsync();
                var stringContent = Encoding.UTF8.GetString(content); 

                var newContent = stringContent.Replace("InternalVirtualPath", "ExternalVirtualPath");
                httpResponseMessage.Content = new StringContent(newContent, Encoding.UTF8, "text/html");
            }
        } 

        return httpResponseMessage;
    } 

    protected override
        Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        return RedirectRequest(request, cancellationToken);
    }
}

Reverse Proxy in ASP.NET Web API

Updated: Be sure to check my follow up post

Due to infrastructure limitations my current team was in need of a reverse proxy that could point to our ASP.NET Web API endpoint. After failing to get IT to setup a reverse proxy in the load balancer I ended up experimenting with a reverse proxy based on a simple implementation using Web API.

The first requirement is to intercept all requests made to the reverse proxy endpoint. Fortunately the Web API pipeline allows this via the DelegatingHandler:

public class ProxyHandler : DelegatingHandler{} 

public class WebApiConfig
{
    public static void Configure(HttpConfiguration config)
    {
        config.MessageHandlers.Add(new ProxyHandler());
        config.Routes.MapHttpRoute("abe", "{*path}");
    }
}

The configuration above adds the ProxyHandler to the general pipeline thus allowing it to intercept all requests which are processed by the Web API pipeline. Then a single catch-all route is added to make sure all requests are processed by the pipeline.

In the proxy delegating handler all requests must now be forwarded to the desired location:

public class ProxyHandler : DelegatingHandler
{
    private async Task<HttpResponseMessage> RedirectRequest(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var redirectLocation = "http://localhost:61948";
        var localPath = request.RequestUri.LocalPath; 

        var client = new HttpClient(); 

        var clonedRequest = await HttpRequestMessageExtensions.CloneHttpRequestMessageAsync(request); 

        clonedRequest.RequestUri = new Uri(redirectLocation + localPath); 

        return await client.SendAsync(clonedRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
    } 

    protected override
        Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        return RedirectRequest(request, cancellationToken);
    }
}

I experienced some problems forwarding GET requests which is why the above code clone the entire HttpRequestMessage via the below snippet found on stack overflow:

public static class HttpRequestMessageExtensions
{
    public static async Task<HttpRequestMessage> CloneHttpRequestMessageAsync(HttpRequestMessage req)
    {
        var clone = new HttpRequestMessage(req.Method, req.RequestUri); 

        var ms = new MemoryStream();
        if (req.Content != null)
        {
            await req.Content.CopyToAsync(ms).ConfigureAwait(false);
            ms.Position = 0; 

            if ((ms.Length > 0 || req.Content.Headers.Any()) && clone.Method != HttpMethod.Get)
            {
                clone.Content = new StreamContent(ms); 

                if (req.Content.Headers != null)
                    foreach (var h in req.Content.Headers)
                        clone.Content.Headers.Add(h.Key, h.Value);
            }
        } 

        clone.Version = req.Version; 

        foreach (var prop in req.Properties)
            clone.Properties.Add(prop); 

        foreach (var header in req.Headers)
            clone.Headers.TryAddWithoutValidation(header.Key, header.Value); 

        return clone;
    }
}

I’m very impressed by the elegance of both Web API but more so the way the HttpRequestMessage/HttpResponseMessage is reused between Web API and HttpClient.

I tested the reverse proxy solutions against our current API and all our GET/POST requests went through. Furthermore all exception message was passed through the proxy as well.

Using GridView/ListView’s Built-in Animations

The GridView and ListView controls in Windows 8 both come with built-in animation for various operations. When the controls are bound to an ObservableCollection the will do a fancy animation whenever an item is

  • Added to the collection (even if it’s added in the middle of the collection, in which case the other elements will slide away to give room for the new element)
  • Removed from the collection

I mention the two animations/operations above specifically because I rarely see developers taking advantage of this built-in functionality. Most of the time, when applications fetch new data, they simply clear the previous data, and insert the new data, even though there might be elements that were both present in the new and the old data set. This is commonly seen in news/feed application that retrieve new posts — an obvious use for the built animations.

To see an example of these animations in action see my Open Nearby app (You might have to add a location in Denmark, if you are not from here). There is an option to filter the shops in the app bar. When this filter is applied the add/remove operations are used to filter away shops that no longer fit in the result set, and likewise it is filled up with new shops that does.

It can be a hassle to synchronize the old dataset for the GridView/ListView with freshly fetched data. Below is a few extension methods that will make it easier to get going:

public static void SyncCollection&lt;T&gt;(this ObservableCollection&lt;T&gt; observableCollection, IEnumerable&lt;T&gt; dataToSync, Func&lt;T, T, bool&gt; insertBefore, Func&lt;T, T, bool&gt; equality)
{
&nbsp;&nbsp;&nbsp;&nbsp;var recentlyToRemove = observableCollection.Where(s =&gt; dataToSync.All(ss =&gt; !equality(ss,s))).ToList();
&nbsp;&nbsp;&nbsp;&nbsp;var recentlyToAdd = dataToSync.Where(s =&gt; observableCollection.All(ss =&gt; !equality(ss, s)));
&nbsp;&nbsp;&nbsp;&nbsp;foreach (var item in recentlyToRemove)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;observableCollection.Remove(item);
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;foreach (var item in recentlyToAdd)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;InsertInOrder(item, observableCollection, insertBefore);
&nbsp;&nbsp;&nbsp;&nbsp;}
}
private static void InsertInOrder&lt;T&gt;(T item, ObservableCollection&lt;T&gt; observableCollection, Func&lt;T, T, bool&gt; insertBefore)
{
&nbsp;&nbsp;&nbsp;&nbsp;for (int i = 0; i &lt; observableCollection.Count; i++)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (insertBefore(item, observableCollection[i]))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;observableCollection.Insert(i, item);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;// if empty or last
&nbsp;&nbsp;&nbsp;&nbsp;observableCollection.Add(item);
}

 

Here’s an example of usage from Open Nearby:

var newShops = ...// newly fetched shops from data service;

ObservableCollection<Shop> previousShops = ..// the old data current being shown in the view;

Qua.WS.ObservableCollectionExtensions.SyncCollection(previousShops, newShops,
(newShop, oldShop) => newShop.Distance.DistanceInKilometers < oldShop.Distance.DistanceInKilometers,
(shop1, shop2) => shop1.Id == shop2.Id);

Checking for Connectivity the Bulletproof Way

A huge amount of the currently released apps in the Store does a check upon app start up to see if connectivity is available. If this is not the case, then the user is redirected to an offline page, and there is no way to continue use of the app.

This is a major concern, when several of the apps does not correctly check for connectivity. One such case, is when the user connects to a VPN. In that case the main connection will be changed to ‘Limited’ even though the user still has an active connection to the internet.

The below snippet will check all the connection profiles for internet access:

public static bool IsConnected
{
get
{
var profiles = NetworkInformation.GetConnectionProfiles();
var internetProfile = NetworkInformation.GetInternetConnectionProfile();
return profiles.Any(s => s.GetNetworkConnectivityLevel() == NetworkConnectivityLevel.InternetAccess)
|| (internetProfile != null
&& internetProfile.GetNetworkConnectivityLevel() == NetworkConnectivityLevel.InternetAccess);
}
}

The naive way to check for internet access would be: NetworkInformation.GetInternetConnectionProfile().GetNetworkConnectivityLevel()== NetworkConnectivityLevel.InternetAccess , but this approach fails in the scenario described above.

Remember ScrollViewer Position

Often in Windows 8 apps the user will be presented with long horizontal groups of content. After having navigated to these items, and then returning to the original page there is no built in functionality to remember where in the list that the user scrolled to.

Creating this feature is relatively quick:

  1. Whenever the user leaves the page, remember the current scroll position.
  2. Whenever the user navigates back to the page, retrieve the previous scroll position
  3. When all your content is loaded, scroll to the previous position
// Step 1
private double? horizontalOffsetState;

protected override void SaveState(Dictionary<string, object> pageState)
{
base.SaveState(pageState);

var myScrollViewer = … // your scroll viewer
var offset = myScrollViewer.HorizontalOffset;

pageState["horizontalOffset"] = offset;
}

// Step 2
protected override void LoadState(object navigationParameter,Dictionary<string, object> pageState)
{
base.LoadState(navigationParameter, pageState);

if (pageState != null)
{
horizontalOffsetState = (double) pageState["horizontalOffset"];
}
}

// step 3 - put in constructor or loadstate/navigatedTo
this.Loaded += (sender, args) =>
{
var myScrollViewer = … // your scroll viewer
if (horizontalOffsetState.HasValue)
{
myScrollViewer.ScrollToHorizontalOffset(horizontalOffsetState.Value);
}

};