Modifying the static file response with a Server value

25 Jul 2016

Single page applications are dynamic and completely constructed on the client side , i.e., with various config values, they can act differently.

For e.g., A single code logic can receive the language/locale through URL param & shows the corresponding language content dynamically.

This works fine for the users, but the bots deals with this in a different way.

They simply see an empty page or the information that are statically send by the application on initial request.

Take for instance - Facebook crawler.

The Facebook share works by crawling the shared webpage and builds the Open Graph object based on that data. So it expects the shared page to contain the required information. In multi-locale or multi-culture instance,  each page would have different share images based on the culture, that should be picked up by the Facebook bot.

But Facebook crawler has a limitation, that it accept only the complete absolute URL rather than relative ones. So this requires us to construct the absolute URL from the server side when the page is initially served.

ASP.Net has lot of events in the application / request life cycle, that makes it easier to modify the required information dynamically.

Following HTML shows how we can have a domain - replacement variable in our static files, that we would want to change based upon different culture.

<meta property="og:image" content="{{DOMAIN_URL}}/img/shareimage.jpg "/>
<meta property="og:image:url" content="{{DOMAIN_URL}}/img/shareimage.jpg" />

An elegant way to modify any response would be through Response Filters, where you will get all required page output markups etc., Even ASP .Net offers many filters that can be simply applied to a response stream.

In our case we would write a module as shown below and subscribe to the "PostReleaseRequestState" - event by the time, all output markups would have been constructed and response filters are applied. Necessary filters can be applied to a response by simply assigning to the Response.Filters property.

using System;
using System.IO;
using System.Text;
using System.Web;

namespace ContentParseModule
{
    public class ContentParser : IHttpModule
    {
        public void Dispose()
        {
            throw new NotImplementedException();
        }

        public void Init(HttpApplication context)
        {
            context.PostReleaseRequestState += new EventHandler(Application_PostReleaseRequestState);
        }

        private void Application_PostReleaseRequestState(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;
            string filePath = context.Request.FilePath;
            string fileExtension =
                VirtualPathUtility.GetExtension(filePath);
            if (fileExtension.Equals(".html", StringComparison.InvariantCultureIgnoreCase))
            {
                string path = application.Request.Path;

                if (path.Contains("/meta/"))
                {
                    int idx = path.LastIndexOf('/');
                    string domainUrl = $"https://{application.Request.Url.Host}{application.Request.Path.Substring(0, idx)}";
                    application.Response.Filter = new IlxProcessingStream(application.Response.Filter, domainUrl);
                }
            }
        }
    }

    public class IlxProcessingStream : MemoryStream
    {
        private Stream _outputStream;
        private string _domainUrl;

        public IlxProcessingStream(Stream outputStream, string domainUrl)
        {
            _outputStream = outputStream;
            _domainUrl = domainUrl;
        }
        public override void Write(byte[] buffer, int offset, int count)
        {
            string output = UTF8Encoding.UTF8.GetString(buffer);
            output = output.Replace("{{DOMAIN_URL}}", _domainUrl);
            _outputStream.Write(UTF8Encoding.UTF8.GetBytes(output), offset, UTF8Encoding.UTF8.GetByteCount(output));
        }
    }
}