In the previous post Use Prerender to improve AngularJS SEO, I have explained different solutions at 3 different levels to implement Prerender. In this post, I will explain how to implement a ASP.NET HttpModule as a application level middleware to implement prerender. Since we call it ASP.NET, so it is applicable for both ASP.NET WebForm and MVC.

Application Level Middleware Architecture

At first, let's review what's the appliaction level middleware solution architecture.

ASP.NET HttpModule - PrerenderHttpModule

From above diagram, the easest way to implement it in ASP.NET is to create a HttpModule. Here I named it as PrerenderHttpModule.

At first, let's create PrerenderHttpModule and register BeginRequest event.

#region Implement IHttpModule
/// <summary>
/// init
/// </summary>
/// <param name="context"></param>
public void Init(HttpApplication context)
{
context.BeginRequest += context_BeginRequest;
} /// <summary>
/// dispose
/// </summary>
public void Dispose()
{
}
#endregion #region Begin Request
protected void context_BeginRequest(object sender, EventArgs e)
{
try
{
Prerender(sender as HttpApplication);
}
catch (Exception exception)
{
Debug.Write(exception.ToString());
}
}
#endregion

In PrerenderHttpModule, the major method is Prerender(HttpApplication)

private void Prerender(HttpApplication application)
{
var httpContext = application.Context;
var request = httpContext.Request;
var response = httpContext.Response;
if (IsValidForPrerenderPage(request))
{
// generate URL
var requestUrl = request.Url.AbsoluteUri;
// if traffic is forwarded from https://, we convert http:// to https://.
if (string.Equals(request.Headers[Constants.HttpHeader_XForwardedProto], Constants.HttpsProtocol, StringComparison.InvariantCultureIgnoreCase)
&& requestUrl.StartsWith(Constants.HttpProtocol, StringComparison.InvariantCultureIgnoreCase))
{
requestUrl = Constants.HttpsProtocol + requestUrl.Substring(Constants.HttpProtocol.Length);
}
var prerenderUrl = $"{Configuration.ServiceUrl.Trim('/')}/{requestUrl}"; // create request
var webRequest = (HttpWebRequest)WebRequest.Create(prerenderUrl);
webRequest.Method = "GET";
webRequest.UserAgent = request.UserAgent;
webRequest.AllowAutoRedirect = false;
webRequest.Headers.Add("Cache-Control", "no-cache");
webRequest.ContentType = "text/html"; // Proxy Information
if (!string.IsNullOrEmpty(Configuration.ProxyUrl) && Configuration.ProxyPort > 0)
webRequest.Proxy = new WebProxy(Configuration.ProxyUrl, Configuration.ProxyPort); // Add token
if (!string.IsNullOrEmpty(Configuration.Token))
webRequest.Headers.Add(Constants.HttpHeader_XPrerenderToken, Configuration.Token); var webResponse = default(HttpWebResponse);
try
{
// Get the web response and read content etc. if successful
webResponse = (HttpWebResponse)webRequest.GetResponse();
}
catch (WebException e)
{
// Handle response WebExceptions for invalid renders (404s, 504s etc.) - but we still want the content
webResponse = e.Response as HttpWebResponse;
} // write response
response.StatusCode = (int)webResponse.StatusCode;
foreach (string key in webResponse.Headers.Keys)
{
response.Headers[key] = webResponse.Headers[key];
}
using (var reader = new StreamReader(webResponse.GetResponseStream(), DefaultEncoding))
{
response.Write(reader.ReadToEnd());
} response.Flush();
application.CompleteRequest();
}
}

Also, in order to make the logic flexible and easy to configure, I have add a configuration class and IsValidForPrerenderPage() method

private bool IsValidForPrerenderPage(HttpRequest request)
{
var userAgent = request.UserAgent;
var url = request.Url;
var rawUrl = request.RawUrl;
var relativeUrl = request.AppRelativeCurrentExecutionFilePath; // check if follows google search engine suggestion
if (request.QueryString.AllKeys.Any(a => a.Equals(Constants.EscapedFragment, StringComparison.InvariantCultureIgnoreCase)))
return true; // check if has user agent
if (string.IsNullOrEmpty(userAgent))
return false; // check if it's crawler user agent.
var crawlerUserAgentPattern = Configuration.CrawlerUserAgentPattern ?? Constants.CrawlerUserAgentPattern;
if (string.IsNullOrEmpty(crawlerUserAgentPattern)
|| !Regex.IsMatch(userAgent, crawlerUserAgentPattern, RegexOptions.IgnorePatternWhitespace))
return false; // check if the extenion matchs default extension
if (Regex.IsMatch(relativeUrl, DefaultIgnoredExtensions, RegexOptions.IgnorePatternWhitespace))
return false; if (!string.IsNullOrEmpty(Configuration.AdditionalExtensionPattern) && Regex.IsMatch(relativeUrl, Configuration.AdditionalExtensionPattern, RegexOptions.IgnorePatternWhitespace))
return false; if (!string.IsNullOrEmpty(Configuration.BlackListPattern)
&& Regex.IsMatch(rawUrl, Configuration.BlackListPattern, RegexOptions.IgnorePatternWhitespace))
return false; if (!string.IsNullOrEmpty(Configuration.WhiteListPattern)
&& Regex.IsMatch(rawUrl, Configuration.WhiteListPattern, RegexOptions.IgnorePatternWhitespace))
return true; return false; }

The priority of checking whether is valid for prerender page:

  1. Constant -> EscapedFragment:  _escaped_fragment_

User Agent: Setting -> CrawlerUserAgentPattern

(google)|(bing)|(Slurp)|(DuckDuckBot)|(YandexBot)|(baiduspider)|(Sogou)|(Exabot)|(ia_archiver)|(facebot)|(facebook)|(twitterbot)|(rogerbot)|(linkedinbot)|(embedly)|(quora)|(pinterest)|(slackbot)|(redditbot)|(Applebot)|(WhatsApp)|(flipboard)|(tumblr)|(bitlybot)|(Discordbot)
  1. Constant -> DefaultIgnoredExtensions

    \\.vxml|js|css|less|png|jpg|jpeg|gif|pdf|doc|txt|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent
  2. Setting -> AdditionalExtensionPattern
  3. Setting -> BlackListPattern
  4. Setting -> WhiteListPattern
  5. At last, return false.

Register PrerenderHttpModule

Generally, we have two different ways to register a HttpModule in ASP.NET,

  1. Use HttpModule configuration in Web.config
  2. Use DynamicModuleUtility (Microsoft.Web.Infrastructure.dll)

Here, I have added the logic to support both, and I have added a app setting to control which one we want to use. By default, it will use DynamicModuleUtility, as we don't need to configure HttpModule in web.config, it's automatical.

Note, in order to use this HttpModule, please configure your IIS Application Pool to integrated mode.

<!--If it's false, please configure http module for UsePrestartForPrenderModule-->
<add key="UsePrestartForPrenderModule" value="true"/>

1. Use DynamicModuleUtility (Microsoft.Web.Infrastructure.dll)

This is the default configuration.

  • Configre UsePrestartForPrerenderModule  to true or remove this setting
<!--If it's false, please configure http module for UsePrestartForPrenderModule-->
<add key="UsePrestartForPrenderModule" value="true"/>
  • Add reference for Microsoft.Web.Infrastructure.dll
  • Add a static class PrerenderPreApplicationStart for assembly prestart.  We need to use static method in static class.
public static class PrerenderPreApplicationStart
{
public const string StartMethodName = "Prestart";
static bool UsePrestart = !bool.FalseString.Equals(ConfigurationManager.AppSettings[Constants.AppSetting_UsePrestartForPrenderModule], StringComparison.InvariantCultureIgnoreCase); /// <summary>
/// used to configure for PreApplicationStart.
/// i.e. [assembly: PreApplicationStartMethod(typeof(PrerenderPreApplicationStart), "Start")]
/// </summary>
public static void Prestart()
{
if (UsePrestart)
{
DynamicModuleUtility.RegisterModule(typeof(PrerenderHttpModule));
}
}
}
  • Register PreApplicationStartMethodAttribute in AssemblyInfo.cs
[assembly: PreApplicationStartMethodAttribute(typeof(PrerenderPreApplicationStart), PrerenderPreApplicationStart.StartMethodName)]

Once the ASP.NET application loads this assembly, it will trigger PrerenderPreApplicationStart.Prestart() method, then registers the PrerenderHttpModule.

2. Use HttpModule configuration in Web.config

This is a very common and easy way, what we need to do is to:

  • Configure UsePrestartForPrerenderModule to false
<!--If it's false, please configure http module for UsePrestartForPrenderModule-->
<add key="UsePrestartForPrenderModule" value="false"/>
  • Configure PrerenderHttpModule
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules runAllManagedModulesForAllRequests="false">
<!--Configure PrerenderHttpModule when UsePrestartForPrenderModule is false; -->
<add name="prerender" type="DotNetOpen.PrerenderModule.PrerenderHttpModule, DotNetOpen.PrerenderModule" />
<remove name="FormsAuthentication"/>
</modules>
</system.webServer>

Configuration Section in Web.config

I have added a configuration section PrerenderConfigurationSection for prerender options

  • Declare the configuration section in web.config section group.
  <configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="prerender" type="DotNetOpen.PrerenderModule.Configuration.PrerenderConfigurationSection, DotNetOpen.PrerenderModule"/>
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/>
</configSections>
  • Configure options.
  <!--prerender settings-->
<!--CrawlerUserAgentPattern: "(google)|(bing)|(Slurp)|(DuckDuckBot)|(YandexBot)|(baiduspider)|(Sogou)|(Exabot)|(ia_archiver)|(facebot)|(facebook)|(twitterbot)|(rogerbot)|(linkedinbot)|(embedly)|(quora)|(pinterest)|(slackbot)|(redditbot)|(Applebot)|(WhatsApp)|(flipboard)|(tumblr)|(bitlybot)|(Discordbot)"-->
<!--WhiteListPattern, BlackListPattern: will check raw URL, which includes query string-->
<!--AdditionalExtensionPattern: will only check extension-->
<prerender ServiceUrl="http://localhost:3000"
Token=""
WhiteListPattern=""
BlackListPattern=""
AdditionalExtensionPattern=""
ProxyUrl=""
ProxyPort="80"/>

You can go to my github wiki page to get more details about each option: Configuration & Check Priority

Nuget Package

I have created a nuget package, which is very convenient if you don't want to dive deep into the source code.

  • Install Nuget Package in your project.

Visual Studio -> Tools -> Nuget Package Manager -> Package Manager Console.

Install-Package DotNetOpen.PrerenderModule

If you want to take a look more detail about this package, you can go https://www.nuget.org/packages/DotNetOpen.PrerenderModule/

There are several versions

  1. Version 1.01-1.0.2, they are for .NET Framework 4.6.2
  2. From Version 1.0.2, I have changed the .NET Framework version from 4.6.2 to 4.0, so that more people can use it. Which means the latest Nuget Package is using .NET Framework 4.0
  • Register PrerenderHttpModule and configure options for prerender service.

I have fully documented how to do this in my github wiki page, you can go there take a look.

  1. Prerender Middleware for ASP.NET
  2. Configuration & Check Priority

  • Done, try it out.

Github Project

I also have created a github project to host all source code includes sample code for testing: https://github.com/dingyuliang/prerender-dotnet, in this project, it includes ASP.NET HttpModule, ASP.NET Core Middleware, IIS Configuration 3 different solution.

For ASP.NET HttpModule, you can go to https://github.com/dingyuliang/prerender-dotnet/tree/master/src/DotNetPrerender

Prerender Related

  1. Use Prerender to improve AngularJS SEO
  2. Setup Prerender Service for JavaScript SEO
  3. Prerender Implementation Best Practice
  4. Prerender Application Level Middleware - ASP.NET HttpModule
  5. Prerender Application Level Middleware - ASP.NET Core Middleware

------------------------------------------------------------------------------------------------

Prerender Application Level Middleware - ASP.NET HttpModule的更多相关文章

  1. Prerender Application Level Middleware - ASP.NET Core Middleware

    In the previous post Use Prerender to improve AngularJS SEO, I have explained different solutions at ...

  2. Examining Application Startup in ASP.NET 5

    By Steve Smith  June 23, 2015 ASP.NET 5 differs from previous versions of ASP.NET in many ways. Gone ...

  3. ASP.NET (HttpModule,HttpHandler)

    asp.net 事件模型机制 ----------------------- 一 客户的请求页面由aspnet_isapi.dll这个动态连接库来处理,把请求的aspx文件发送给CLR进行编译执行,然 ...

  4. ASP.NET HttpModule URL 重写 (一) 【Z】

    大家好,又来和大家见面了,此次给大家带来的URL重写,关于URL重写是什么,有什么好处,如何重写,今天我和大家一起分享一下我的经验 一.URL重写 URL重写就是首先获得一个进入的URL请求然后把它重 ...

  5. [转]Session and application state in ASP.NET Core

    本文转自:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state By Rick Anderson and Steve ...

  6. asp.net httpmodule 访问页面控件 备忘

    用到的时候发现还得找代码,存一个例子方便自己和他人修改: using System; using System.Data; using System.Configuration; using Syst ...

  7. Patterns for application development with ASP.NET Core

    此文章翻译自 NDC { London } 16-20 January 2017 上, Damian Edwards和David Fowler的演讲,如果翻译不周,请大家指出错误. Logging 生 ...

  8. asp.net HttpModule和HttpHandler

    ASP.Net处理Http Request时,使用Pipeline(管道)方式,由各个HttpModule对请求进行处理,然后到达 HttpHandler,HttpHandler处理完之后,仍经过Pi ...

  9. 使用Azure Application Insignhts监控ASP.NET Core应用程序

    Application Insignhts是微软开发的一套监控程序.他可以对线上的应用程序进行全方位的监控,比如监控每秒的请求数,失败的请求,追踪异常,对每个请求进行监控,从http的耗时,到SQL查 ...

随机推荐

  1. c#跟objective-c语言特性的对比

    拿c#语言跟objective-c做个对比,记录下自己认为是差不多的东西. 学过objc的人相信对category这个东西肯定不陌生,它可以让我们在没有源码的基础上对原先的类添加额外的一些方法,写到这 ...

  2. 记一次vue2路由参数传递this指针问题

    需要船体一个data()内的对象到另一个页面. <player-card v-for="(note, key) in sortedtNodes" :imgurl=" ...

  3. 常用命令(过滤、管道、重定向、ping 命令、netstat 命令、ps命令)

    常用命令 过滤 过滤出 /etc/passwd 文件中包含 root 的记录 grep 'root' /etc/passwd 递归地过滤出 /var/log/ 目录中包含 linux 的记录 grep ...

  4. VS2013编译boost1.55库

    1. 官网下载最新的Boost库,我的是1.55 2. 在使用vs2013编译boost-1.55.0之前,先要给boost做下修改: boost_1_55_0\boost\intrusive\det ...

  5. [读后感]spring Mvc 教程框架实例以及系统演示下载

    [读后感]spring Mvc 教程框架实例以及系统演示下载 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致&qu ...

  6. POJ 1163&amp;&amp; 3176 The Triangle(DP)

    The Triangle Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 41169   Accepted: 24882 De ...

  7. sql jion

    A Visual Explanation of SQL Joins I thought Ligaya Turmelle's post on SQL joins was a great primer f ...

  8. Linux 文件系统类型 文件系统结构 与Windows文件系统的比较

    摘自:http://blog.csdn.net/gelivable007/article/details/7249365 Linux 文件系统类型 磁盘文件系统.包括硬盘.CD-ROM.DVD.USB ...

  9. 基于shiro+jwt的真正rest url权限管理,前后端分离

    代码地址如下:http://www.demodashi.com/demo/13277.html bootshiro & usthe bootshiro是基于springboot+shiro+j ...

  10. 十五套专为开发人员打造的PHP资源库

    转载自:http://developer.51cto.com/art/201508/488143.htm 1)Mink Mink是一套PHP 5.3库,用于在测试当中模拟Web应用程序与浏览器之间的交 ...