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:

  1. The Middleware class needs to have a constructure which has RequestDelegate parameter as for next delegate.
  2. 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.

  1. Prerender Middleware for ASP.NET Core

  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 Core Middleware, you can go tohttps://github.com/dingyuliang/prerender-dotnet/tree/master/src/DotNetCorePrerender

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 Core Middleware的更多相关文章

  1. Prerender Application Level Middleware - ASP.NET HttpModule

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

  2. ASP.NET Core Middleware 抽丝剥茧

    一. 宏观概念 ASP.NET Core Middleware是在应用程序处理管道pipeline中用于处理请求和操作响应的组件. 每个组件是pipeline 中的一环. 自行决定是否将请求传递给下一 ...

  3. ASP.NET Core Middleware (转载)

    What is Middleware? Put simply, you use middleware components to compose the functionality of your A ...

  4. [ASP.NET Core] Middleware

    前言 本篇文章介绍ASP.NET Core里,用来处理HTTP封包的Middleware,为自己留个纪录也希望能帮助到有需要的开发人员. ASP.NET Core官网 结构 在ASP.NET Core ...

  5. ASP.NET Core Middleware管道介绍

    public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.Use(async (context, ne ...

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

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

  7. Patterns for application development with ASP.NET Core

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

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

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

  9. 翻译 - ASP.NET Core 基本知识 - 中间件(Middleware)

    翻译自 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-5.0 中间件是集成 ...

随机推荐

  1. 突破防盗链机制:使用referrer-killer

    在开发it博客汇的过程中遇到一个难题:很多图片链接设置了防盗链机制,从我的网站请求图片会返回403错误,但直接在浏览器中打开图片的url时却又正常. 使用fiddler抓包发现,从我的网站请求图片会带 ...

  2. 这里先发布一个,自己写得unityUI的适配的方案(插播)

    这个适配是依据坐标系的象限的思想来进项适配的.參考了部分的NGUI的适配方案. 在程序的事实上,来測量UI距离相机边界的像素然后依据比例来进行适配,个人认为还不错. 放码! . 有个前提哦就是你要先定 ...

  3. java 过滤器(理解二)

    request.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf ...

  4. dubbo-monitor安装监控中心,管理控制台安装网页一直访问不到,解决bug的方式记录

    问题再现 第一步,重启机器 第二步,按照dubbo-monitor需要开启的服务,顺序逐一进行启动,之前的推翻全部重新再走一遍就通了,千万不能死磕...很浪费时间.... 通过打印日志追踪问题的所在. ...

  5. C#注释——爱你不是两三天

    说到注释这个东东,我不得不说:爱你不是两三天,每天却想你很多遍...原来梁静茹同学这首歌不全然是情歌啊~   一句注释也没有的一大片的代码有木有 看着那些无名者写的神秘代码,有没有骂一句,你妹的... ...

  6. java中的super限定

    super的用法: (1)如果需要在子类中调用父类中被覆盖的实例方法,可以用super限定来调用父类中被覆盖的方法.当然,也可以调用从父类继承的实例变量. public void callOverri ...

  7. JMeter 十:录制脚本--使用bodboy

    1. 下载bodboy 下载地址:http://www.badboy.com.au/download 这里填写完基本信息,点击下方的Continue即可跳转到下载页面. 任选一个version,点击后 ...

  8. Win10系统Host文件修改不了权限不足怎么办

    Win10系统Host文件修改不了权限不足怎么办 Win10系统Host文件修改不了不能保存 工具原料:电脑+win10 Win10系统Host文件修改不了权限不足方法步骤如下: .打开"运 ...

  9. python求pi的方法

    来自 #_*_ coding=utf-8 *_* ## {{{ http://code.activestate.com/recipes/578130/ (r5) def pi(places=10): ...

  10. 一学就会之ado.net(一)

    ado.net十一组用于和数据源进行交互的面向对象类库.数据源能够是数据库也能够是文本文件.excel表格或者XML文件. 简单来说.ado.net就是与不同的数据源进行交互(增删改查)的. ado. ...