Prerender Application Level Middleware - ASP.NET Core Middleware
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 Core Middleware as a application level middleware to implement prerender.
Application Level Middleware Architecture
At first, let's review what's the appliaction level middleware solution architecture.

ASP.NET Core Middleware - PrerenderMiddleware
In ASP.NET Core, we can create a Middleware, which has the similar functionality as HttpModule in ASP.NET, but in ASP.NET Core, there is no interface or base class we can use to declare a Middleware.
- Create PrerenderMiddleware class
The default convention is that, we need to:
- The Middleware class needs to have a constructure which has RequestDelegate parameter as for next delegate.
- The Middleware class needs to have an async Invoke method with parameter HttpContext
So, the class is as below. I have added PrerenderConfiguration for getting configuration.
#region Ctor
public PrerenderMiddleware(RequestDelegate next, PrerenderConfiguration configuration)
{
_next = next;
Configuration = configuration;
}
#endregion
#region Properties
public PrerenderConfiguration Configuration { get; private set; }
#endregion
#region Invoke
public async Task Invoke(HttpContext httpContext)
{
await Prerender(httpContext);
}
#endregion
Then, we need to implement Prerender(httpContext) logic
If you know my implementation for PrerenderHttpModule in ASP.NET, I used HttpWebRequest & HttpWebResponse.
But for PrerenderMiddleware here, I use HttpClient, as with the HttpWebRequest in ASP.NET Core (at 2/11/2017), there is no way to setup AllowAutoRedirect and other http headers.
private async Task Prerender(HttpContext httpContext)
{
var request = httpContext.Request;
var response = httpContext.Response;
var requestFeature = httpContext.Features.Get<IHttpRequestFeature>();
if (IsValidForPrerenderPage(request, requestFeature))
{
// generate URL
var requestUrl = request.GetDisplayUrl();
// if traffic is forwarded from https://, we convert http:// to https://.
if (string.Equals(request.Headers[Constants.HttpHeader_XForwardedProto], Constants.HttpsProtocol, StringComparison.OrdinalIgnoreCase)
&& requestUrl.StartsWith(Constants.HttpProtocol, StringComparison.OrdinalIgnoreCase))
{
requestUrl = Constants.HttpsProtocol + requestUrl.Substring(Constants.HttpProtocol.Length);
}
var prerenderUrl = $"{Configuration.ServiceUrl.Trim('/')}/{requestUrl}";
// use HttpClient instead of HttpWebRequest, as HttpClient has AllowAutoRedirect option.
var httpClientHandler = new HttpClientHandler() { AllowAutoRedirect = true };
// Proxy Information
if (!string.IsNullOrEmpty(Configuration.ProxyUrl) && Configuration.ProxyPort > 0)
httpClientHandler.Proxy = new WebProxy(Configuration.ProxyUrl, Configuration.ProxyPort);
using (var httpClient = new HttpClient(httpClientHandler))
{
httpClient.Timeout = TimeSpan.FromSeconds(60);
httpClient.DefaultRequestHeaders.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue() { NoCache = true };
httpClient.DefaultRequestHeaders.TryAddWithoutValidation(Constants.HttpHeader_ContentType, "text/html");
httpClient.DefaultRequestHeaders.TryAddWithoutValidation(Constants.HttpHeader_UserAgent, request.Headers[Constants.HttpHeader_UserAgent].ToString());
if (!string.IsNullOrEmpty(Configuration.Token))
httpClient.DefaultRequestHeaders.TryAddWithoutValidation(Constants.HttpHeader_XPrerenderToken, Configuration.Token);
using (var webMessage = await httpClient.GetAsync(prerenderUrl))
{
var text = default(string);
try
{
response.StatusCode = (int)webMessage.StatusCode;
foreach (var keyValue in webMessage.Headers)
{
response.Headers[keyValue.Key] = new StringValues(keyValue.Value.ToArray());
}
using (var stream = await webMessage.Content.ReadAsStreamAsync())
using (var reader = new StreamReader(stream))
{
webMessage.EnsureSuccessStatusCode();
text = reader.ReadToEnd();
}
}
catch (Exception e)
{
text = e.Message;
}
await response.WriteAsync(text);
}
}
}
else
{
await _next.Invoke(httpContext);
}
}
- At last, let's take a look at IsValidForPrerenderPage(HttpRequest request, IHttpRequestFeature requestFeature), This method is the same as PrerenderHttpModule class in ASP.NET.
private bool IsValidForPrerenderPage(HttpRequest request, IHttpRequestFeature requestFeature)
{
var userAgent = request.Headers[Constants.HttpHeader_UserAgent];
var rawUrl = requestFeature.RawTarget;
var relativeUrl = request.Path.ToString();
// check if follows google search engine suggestion
if (request.Query.Keys.Any(a => a.Equals(Constants.EscapedFragment, StringComparison.OrdinalIgnoreCase)))
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;
}
Use PrerenderMiddleware in ASP.NET Core Project
In order to use PrerenderMiddleware in ASP.NET Core project easily, I have created some extension method, so that we can easily setup it in Startup.cs
- AddPrerenderConfig()
AddPrerenderConfig is used to add PrerenderConfiguration.json to IApplicationBuilder.
/// <summary>
/// Add PrerenderConfiguration.json to configuration.
/// Or you can put the configuration in appsettings.json file either.
/// </summary>
/// <param name="builder"></param>
/// <param name="jsonFileName"></param>
/// <returns></returns>
public static IConfigurationBuilder AddPrerenderConfig(this IConfigurationBuilder builder, string jsonFileName = "PrerenderConfiguration.json")
=> builder.AddJsonFile(jsonFileName, false, true);
- ConfigureSection()
ConfigureSection is used to configure options into servicecollection, so that we can easily get it from servicecollection in the future.
/// <summary>
/// Configure Section into Service Collections
/// </summary>
/// <typeparam name="TOptions"></typeparam>
/// <param name="serviceCollection"></param>
/// <param name="configuration"></param>
/// <param name="singletonOptions"></param>
public static void ConfigureSection<TOptions>(this IServiceCollection serviceCollection, IConfiguration configuration, bool singletonOptions = true)
where TOptions : class, new()
{
serviceCollection.Configure<TOptions>(configuration.GetSection(typeof(TOptions).Name));
if (singletonOptions)
{
serviceCollection.AddSingleton<TOptions>(a => a.GetService<IOptions<TOptions>>().Value);
}
}
- UsePrerender()
UsePrerender is used to register PrerenderMiddleware
#region UsePrerender
/// <summary>
/// Use Prerender Middleware to prerender JavaScript logic before turn back.
/// </summary>
/// <param name="app"></param>
/// <param name="configuration">Prerender Configuration, if this parameter is NULL, will get the PrerenderConfiguration from ServiceCollection</param>
/// <returns></returns>
public static IApplicationBuilder UsePrerender(this IApplicationBuilder app, PrerenderConfiguration configuration = null)
=> app.UseMiddleware<PrerenderMiddleware>(configuration ?? app.ApplicationServices.GetService<IOptions<PrerenderConfiguration>>().Value);
// => app.Use(next => new PrerenderMiddleware(next, configuration).Invoke);
// => app.Use(next => context => new PrerenderMiddleware(next, configuration).Invoke(context)); // either way.
#endregion
- With above extension methods, we can easily setup PrerenderMiddleware in Startup.cs
- Step 1
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
// Prerender Step 1: Add Prerender configuration Json file.
.AddPrerenderConfig()
.AddEnvironmentVariables();
Configuration = builder.Build();
}
- Step 2
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
// Prerender Step 2: Add Options.
services.AddOptions();
services.ConfigureSection<PrerenderConfiguration>(Configuration);
}
- Step 3
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
// Prerender Step 3: UsePrerender, before others.
app.UsePrerender();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
.............
PrerenderConfiguration.json
I have added PrerenderConfiguration.json file into ASP.NET Core project, then I can configure for prerender service.
The format of this json file is:
{
"PrerenderConfiguration": {
"ServiceUrl": "http://service.prerender.io",
"Token": null,
"CrawlerUserAgentPattern": null,
"WhiteListPattern": null,
"BlackListPattern": "lib|css|js",
"AdditionalExtensionPattern": null,
"ProxyUrl": null,
"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 DotNetCoreOpen.PrerenderMiddleware
If you want to take a look more detail about this package, you can go https://www.nuget.org/packages/DotNetCoreOpen.PrerenderMiddleware/
Use PrerenderMiddleware and configure PrerenderConfiguration.json 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 Core Middleware, you can go tohttps://github.com/dingyuliang/prerender-dotnet/tree/master/src/DotNetCorePrerender
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 Core Middleware的更多相关文章
- Prerender Application Level Middleware - ASP.NET HttpModule
In the previous post Use Prerender to improve AngularJS SEO, I have explained different solutions at ...
- ASP.NET Core Middleware 抽丝剥茧
一. 宏观概念 ASP.NET Core Middleware是在应用程序处理管道pipeline中用于处理请求和操作响应的组件. 每个组件是pipeline 中的一环. 自行决定是否将请求传递给下一 ...
- ASP.NET Core Middleware (转载)
What is Middleware? Put simply, you use middleware components to compose the functionality of your A ...
- [ASP.NET Core] Middleware
前言 本篇文章介绍ASP.NET Core里,用来处理HTTP封包的Middleware,为自己留个纪录也希望能帮助到有需要的开发人员. ASP.NET Core官网 结构 在ASP.NET Core ...
- ASP.NET Core Middleware管道介绍
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.Use(async (context, ne ...
- [转]Session and application state in ASP.NET Core
本文转自:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state By Rick Anderson and Steve ...
- Patterns for application development with ASP.NET Core
此文章翻译自 NDC { London } 16-20 January 2017 上, Damian Edwards和David Fowler的演讲,如果翻译不周,请大家指出错误. Logging 生 ...
- 使用Azure Application Insignhts监控ASP.NET Core应用程序
Application Insignhts是微软开发的一套监控程序.他可以对线上的应用程序进行全方位的监控,比如监控每秒的请求数,失败的请求,追踪异常,对每个请求进行监控,从http的耗时,到SQL查 ...
- 翻译 - ASP.NET Core 基本知识 - 中间件(Middleware)
翻译自 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-5.0 中间件是集成 ...
随机推荐
- Java实现文件MD5加密
代码实现: import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.s ...
- java程序计算数独游戏
兴趣来了,写了个简单的数独游戏计算程序,未做算法优化. 通过文件来输入一个二维数组,9行,每行9个数组,数独游戏中需要填空的地方用0来表示.结果也是打印二维数组. import java.io.Fil ...
- PHP Variable Scope
原文: https://phppot.com/php/variable-scope-in-php/ Last modified on March 24th, 2017 by Vincy. ------ ...
- Nginx + FastCGI 程序(C/C++)搭建高性能web service的demo
http://blog.csdn.net/chdhust/article/details/42645313 Nginx + FastCGI 程序(C/C++)搭建高性能web service的Demo ...
- 前端性能优化:使用Array.prototype.join代替字符串连接
来源:GBin1.com 有一种非常简单的客户端优化方式,就是用Array.prototype.join代替原有的基本的字符连接的写法.在这个系列的第一篇中,我在代码中使用了基本字符连接: htmlS ...
- 初始化linux环境
初始化linux环境 1. 新建用户组 addgroup admin //假定为admin用户组 2. 新建用户 useradd -d /home/work -s /bin/bash -m work ...
- vue - webpack.dev.conf.js for merge
webpack-merge提供了一个merge连接数组并合并创建新对象的对象的函数.如果遇到函数,它将执行它们,通过算法运行结果,然后再次将返回的值包装在函数中. 这种行为在配置webpack时特别有 ...
- 《Java程序猿面试笔试宝典》之 什么是AOP
AOP(Aspect-Oriented Programming.面向切面编程)是对面向对象开发的一种补充,它同意开发者在不改变原来模型的基础上动态地改动模型从而满足新的需求.比如.在不改变原来业务逻辑 ...
- Android源码-SignApk.java
/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Versi ...
- Java 线程池的原理与实现 (转)
最近在学习线程池.内存控制等关于提高程序运行性能方面的编程技术,在网上看到有一哥们写得不错,故和大家一起分享. [分享]Java 线程池的原理与实现 这几天主要是狂看源程序,在弥补了一些以前知 ...