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查 ...
随机推荐
- mysql多实例介绍及配置
mysql多实例介绍及配置 1.mysql多实例介绍 1.1 什么是mysql多实例 mysql多实例就是在一台机器上开启多个不同的服务端口(如:3306,3307),运行多个MySQL服务进程,通过 ...
- Android打印机--小票打印格式及模板设置
小票打印就是向打印设备发送控制打印格式的指令集,而这些打印格式须要去查询相应打印机的API文档,这里我把经常使用的api给封装了一下 文字对齐方式 打印字体大小 字体是否加粗 打印二维码 打印条形码 ...
- 在单进程单线程或单进程多线程下实现log4cplus写日志并按大小切割
基于脚本配置来过滤log信息 除了通过程序实现对log环境的配置之外.log4cplus通过PropertyConfigurator类实现了基于脚本配置的功能.通过 脚本能够完毕对logger.app ...
- iOS Programming - Views(视图 - 基本绘制,变换,平移,旋转,反转,倾斜)
1. Views A view (an object whose class is UIView or a subclass of UIView) knows how to draw itself i ...
- java检索文件时加入线程
package xianChengSaomiao; import java.io.File; import java.util.ArrayList; import java.util.List; pu ...
- JQUERY插件学习之jQuery UI
jQuery UI:http://jqueryui.com/ jQuery UI介绍: jQuery UI 是以 jQuery 为基础的开源 JavaScript 网页用户界面代码库.包含底层用户交互 ...
- 服务器如何开启php的fsockopen函数? 使用发邮箱类
参考:http://www.daixiaorui.com/read/16.html#viewpl 服务器如何开启php的fsockopen函数?如果你要使用一些邮件的类,那么很多要求支持php的fso ...
- 【微信小程序】微信小程序wx.previewImage预览图片
一.小知识 二.例子,配合轮播图使用效果更佳!(如图1) 1.wxml <scroll-view scroll-y="true"> <swiper catchta ...
- SSH面试题目
一简答 spring工作原理 为什么要用spring 3.请你谈谈SSH整合 4.介绍一下Spring的事务管理 5.Struct2基本流程 6.在Hibernate应用中Java对象的状态有哪些? ...
- [转]PHP运行出现Notice : Use of undefined constant 的完美解决方案
Notice: Use of undefined constant title - assumed 'title' in F:\wamp\www\load_myweb.php on line 22No ...