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查 ...
随机推荐
- zabbix乱码问题
Zabbix页面遇到历史记录的乱码需要修改数据库: 解决办法: 1.将 zabbix 数据库中的表备份: 2.手动删除 zabbix 数据库: 3.重新创建 zabbix 库时手动指定字符集为 utf ...
- windows Server2012 之 IIS8.0配置安装完整教程
IIS8.0是windows Server2012自带的服务器管理系统,和以往不同,IIS8.0安装和操作都比较简单,界面很简洁,安装也很迅速.今天我们重点完整的演示下Internet Informa ...
- vue - config(index.js)
描述:我想,这是调用最多的一个文件了吧(无论是dev,还是prod) 'use strict' // Template version: 1.3.1 // see http://vuejs-templ ...
- vue - config(dev.env.js和prov.env.js)
描述:配置产品模式.打包模式:开发还是打包,以最佳运行(不配置则有一个大大的Warning!!!) 官网:https://www.webpackjs.com/concepts/mode/
- Unity3D优化之合并网格
原文地址点击这里
- java 内部类、匿名内部类、嵌套类的使用
我们都知道java的类能够由public.default(缺省.不写)来修饰,分别表示的含义是同意公开訪问以及仅仅同意包内其他类訪问,而同一包内的类一般是为完毕同一个功能而协作. 除此之外, ...
- Random.org -- 真正的随机数生成器
接触过程序设计的人一定对随机数不陌生.随机数的用途非常广,比方在測试.分布和统计程序.游戏中. 大多数编程语言也提供了随机数库.能够方便的使用. 只是从严格意义上来讲,这些程序生成的随机数并非真正的随 ...
- shell脚本中执行mysql 语句,去除warning using a password on the command line interface can be insecure信息
方法二:使用mysql参数的方法 mysql -u$user -p$pass -D $db -e "select host from user;"当然,可以通过将传参的方式来传递 ...
- ajax 参数data问题 data中的 参数名 参数值为string 提交到后台后,会自动转换参数名相同的 类型 和 js字符串拼接
latlng"14.6005238,100.43635419999998"Cusid"accb5c1b-6aef-4f3b-a4eb-d60ea1ca5f54" ...
- DOS命令 bat-call的用法
call 从批处理程序调用另一个批处理程序 call有几种用法 第一种用法,也就是最常用的一种,调用另一个批处理,在被调用的批处理执行完后在执行call下面的命令.如: @echo off call ...