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. 实现淡入淡出效果的组件,继承自JComponent

    由于仅贴出代码,供有缘人参考. import java.awt.AlphaComposite; import java.awt.Graphics; import java.awt.Graphics2D ...

  2. Cocos2d-x中如何增加图片和文本菜单

    菜单都以MenuItem开头 MenuItemLabel - 文本菜单项 MenuItemImage - 图片菜单项 // on "init" you need to initia ...

  3. 解决Eclipse下不自动拷贝apk到模拟器问题( The connection to adb is down, and a severe error has occured)

    如题 解决方案如下: 1.先把eclipse关闭.2.在管理器转到你的android SDK 的platform-tools下3.键入adb kill-server ,如果adb关闭了会提示 serv ...

  4. js实现选择及复制粘贴

    最近项目有个点击复制到粘贴板的需求,在这里做一个简单的例子分享给大家,没考虑兼容性,需要兼容的大家去查找下文档 //html<p id="element">测试测试&l ...

  5. Vue Router 获取url路径参数 query

    https://router.vuejs.org/zh/api/#路由对象属性 $route.query 类型: Object 一个 key/value 对象,表示 URL 查询参数.例如,对于路径  ...

  6. Win7系统与它的Virtualbox中安装的Ubuntu14.04共享信息的几种方法

    虚拟机是每一个程序猿必备的工具.本文依据最新版VirtualBox用户手冊的提示,通过自己的亲自实践,给出了Win7系统与执行在当中的VirtualBox 5.0.2中的Ubuntu 14.04共享信 ...

  7. 【PHP】(原创)之表单FORM的formhash校验,以TP3.2示例

    1.目的:每次表单POST提交(ajax的POST也适用)过来数据,都必须校验formhash参数是否和服务器端的一致,不一致说明重复提交或者 跨站攻击提交csrf 2.原理:参照了 KPPW 的fo ...

  8. C# 取字符串中间文本 取字符串左边 取字符串右边

    好像是第二种效率高一点,取str字符串中123左边的所有字符:第一种Between(str,"","123"),而第二种是Between(str,null,&q ...

  9. 一个来自红帽的Java垃圾回收算法,试图把停顿时间降到10毫秒以下 原创 2017-01-10 薛命灯

    转自 微信聊聊架构 GC... 早在三年前,Red Hat就启动了Shenandoah项目.Shenandoah是一种新的Java虚拟机GC算法,目标是利用现代多核CPU的优势,减少大堆内存在GC方面 ...

  10. Groovy小结:java调用Groovy方法并传递参数

    Groovy小结:java调用Groovy方法并传递参数 @(JAVA总结) 1. 场景描述 在网上查了资料发现,java有三种方式调用groovy脚本.但是真正在实际的服务器环境中,嵌入groovy ...