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. 【实践】js封装 jq siblings 方法

    思路: 1.获取调用元素的父元素下的所有子元素(即它的所有同辈元素和调用元素本身) 2.遍历调用元素父元素下的所有子元素 除调用元素外的所有元素保存在一个数组里面 代码如下: <!DOCTYPE ...

  2. Codeforces 112A-Petya and Strings(实现)

    A. Petya and Strings time limit per test 2 seconds memory limit per test 256 megabytes input standar ...

  3. [Tools] Add a Dynamic Tweet Button to a Webpage

    To let people easily share the patio11bot, we'll add a "Tweet" button to the page. You can ...

  4. 无埋点数据收集和adb monkey测试屏蔽通知栏

    简单记录百度移动统计android无埋点sdk使用和monkey测试屏蔽通知栏的问题 1.无埋点sdk使用 很简单,下载完sdk后导入到项目中 , 参考sdk文档进行就可以了,个人觉得比友盟还简单,几 ...

  5. python版本管理--pyenv

    python版本环境管理 下载依赖 yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readli ...

  6. EAS常用工具类

    package com.kingdee.eas.custom; import java.io.File; import java.io.FileNotFoundException; import ja ...

  7. spring cloud jackson 枚举json互转 枚举json序列化/反序列化

    先定义一个枚举基类 import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @JsonDeserialize(using = ...

  8. Eclipse项目上红叉

    整个项目可以编译通过并且运行都没问题,但是项目上有个红叉,这个问题一般是有两个原因: 1.查看项目是不是有的引用包报错 解决办法:在项目的build path 中删除不可用引用或者修正 2.项目的编译 ...

  9. 0基础的小白怎么学习Java?

    自身零基础,那么我们应该先学好Java,首先我们来了解下Java的特性: Java语言是简单的 Java语言的语法与C语言和C++语言很接近,使得大多数程序员很容易学习和使用Java.另一方面,Jav ...

  10. SQL&EF优化第一篇 各种情况下的性能测试之count函数篇

    测试环境  mssql 08  +win7    数据 30W条 二〇一六年十月二十九日 09:04:43 结论:1>主键> *>可空列    推测未论证: 根据情况优先选择 顺便提 ...