Prerender Application Level Middleware - ASP.NET HttpModule
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:
- 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)
- 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 - Setting -> AdditionalExtensionPattern
- Setting -> BlackListPattern
- Setting -> WhiteListPattern
- At last, return false.
Register PrerenderHttpModule
Generally, we have two different ways to register a HttpModule in ASP.NET,
- Use HttpModule configuration in Web.config
- 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
- Version 1.01-1.0.2, they are for .NET Framework 4.6.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.
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
- Use Prerender to improve AngularJS SEO
- Setup Prerender Service for JavaScript SEO
- Prerender Implementation Best Practice
- Prerender Application Level Middleware - ASP.NET HttpModule
- Prerender Application Level Middleware - ASP.NET Core Middleware
------------------------------------------------------------------------------------------------
Prerender Application Level Middleware - ASP.NET HttpModule的更多相关文章
- Prerender Application Level Middleware - ASP.NET Core Middleware
In the previous post Use Prerender to improve AngularJS SEO, I have explained different solutions at ...
- 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 ...
- ASP.NET (HttpModule,HttpHandler)
asp.net 事件模型机制 ----------------------- 一 客户的请求页面由aspnet_isapi.dll这个动态连接库来处理,把请求的aspx文件发送给CLR进行编译执行,然 ...
- ASP.NET HttpModule URL 重写 (一) 【Z】
大家好,又来和大家见面了,此次给大家带来的URL重写,关于URL重写是什么,有什么好处,如何重写,今天我和大家一起分享一下我的经验 一.URL重写 URL重写就是首先获得一个进入的URL请求然后把它重 ...
- [转]Session and application state in ASP.NET Core
本文转自:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state By Rick Anderson and Steve ...
- asp.net httpmodule 访问页面控件 备忘
用到的时候发现还得找代码,存一个例子方便自己和他人修改: using System; using System.Data; using System.Configuration; using Syst ...
- Patterns for application development with ASP.NET Core
此文章翻译自 NDC { London } 16-20 January 2017 上, Damian Edwards和David Fowler的演讲,如果翻译不周,请大家指出错误. Logging 生 ...
- asp.net HttpModule和HttpHandler
ASP.Net处理Http Request时,使用Pipeline(管道)方式,由各个HttpModule对请求进行处理,然后到达 HttpHandler,HttpHandler处理完之后,仍经过Pi ...
- 使用Azure Application Insignhts监控ASP.NET Core应用程序
Application Insignhts是微软开发的一套监控程序.他可以对线上的应用程序进行全方位的监控,比如监控每秒的请求数,失败的请求,追踪异常,对每个请求进行监控,从http的耗时,到SQL查 ...
随机推荐
- Spark Streaming ReceiverTracker架构设计
本节的主要内容: 一.ReceiverTracker的架构设计 二.消息循环系统 三.ReceiverTracker具体实现 Spark Streaming作为Spark Core基础 架构之上的一个 ...
- UVA270-Lining Up
斜率斜率斜率......... #include<iostream> #include<cstdio> #include<algorithm> #include&l ...
- Spring集成ActiveMQ配置 --转
转自:http://suhuanzheng7784877.iteye.com/blog/969865 集成环境 Spring采用2.5.6版本,ActiveMQ使用的是5.4.2,从apache站点可 ...
- DOM BOM document window 区别
DOM 是为了操作文档出现的 API,document 是其的一个对象: BOM 是为了操作浏览器出现的 API,window 是其的一个对象. 使用下图讲解: 归DOM管的: E区:即doc ...
- 云、Iaas、Paas、Saas
云服务现在已经快成了一个家喻户晓的词了.如果你不知道PaaS, IaaS 和SaaS的区别,那么也没啥,因为很多人确实不知道. 云其实是互联网的一个隐喻,云计算其实就是使用互联网来接入存储或者运行在远 ...
- Github 入门基本操作
翻译自:https://guides.github.com/activities/hello-world/ 文章概述: 什么是GitHub? 创建一个存储库 创建一个分支 做出承诺 打开拉请求 合并拉 ...
- Popup.js
test.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><HTML> ...
- 网络请求框架---Volley
去年的Google I/O大会为android开发者带来了一个网络请求框架,它的名字叫做Volley.Volley诞生的使命就是让Android的网络请求更快,更健壮,而且它的网络通信的实现是基于Ht ...
- epoll反应堆模型
================================ 下面代码实现的思想:epoll反应堆模型:( libevent 网络编程开源库 核心思想) 1. 普通多路IO转接服务器: 红黑树 ― ...
- Linux软件包安装和卸载
安装软件包的三种方法 1.rpm.yum.源码包 2.yum会把依赖的包都安装上 rpm包介绍 3.rmp报名组成结构:yum-3.4.3-132.el7.centos.0.1.noarch.rpm ...