ASP.NET Core 內置了對 CORS 的支持,使用很簡單,只需先在 Startup 的 ConfigureServices() 中添加 CORS 策略:git
public void ConfigureServices(IServiceCollection services) { services.AddCors(options => options.AddPolicy( "AllowSameDomain", builder => builder.WithOrigins( "http://www.cnblogs.com", "https://q.cnblogs.com", "https://zzk.cnblogs.com", "https://i.cnblogs.com", "https://news.cnblogs.com", "https://job.cnblogs.com"))); }
而後在想啓用 CORS 的控制器 Action 上應用這個策略:github
[EnableCors("AllowSameDomain")] public IActionResult Markdown() { return View(); }
可是,當看到上面一堆網址時,當想到每增長一個二級域名都須要修改上面的代碼時,一種不舒服的感受油然而生,一種想偷懶的衝動涌上心頭。ide
難道沒有一勞永逸的方法嗎?DNS解析中支持在域名中使用通配符(*.cnblogs.com),CA證書中也支持,若是這裏的 CORS 策略也支持使用通配符,不就能夠一勞永逸了嗎?配置 CORS 策略的代碼就能夠簡化爲下面的樣子:ui
public void ConfigureServices(IServiceCollection services) { services.AddCors(options => options.AddPolicy( "AllowSameDomain", builder => builder.WithOrigins("*.cnblogs.com"))); }
不只一勞永逸,並且代碼更加簡潔漂亮。lua
但是負責 ASP.NET CORS 的開發者沒這麼貼心,只能本身動手了。spa
從 github 簽出 ASP.NET CORS 的源代碼,找到其中根據域名進行判斷的實現代碼:code
public class CorsService : ICorsService { public virtual void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result) { var origin = context.Request.Headers[CorsConstants.Origin]; if (StringValues.IsNullOrEmpty(origin) || !policy.AllowAnyOrigin && !policy.Origins.Contains(origin)) { return; } AddOriginToResult(origin, policy, result); result.SupportsCredentials = policy.SupportsCredentials; AddHeaderValues(result.AllowedExposedHeaders, policy.ExposedHeaders); } public virtual void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result) { var origin = context.Request.Headers[CorsConstants.Origin]; if (StringValues.IsNullOrEmpty(origin) || !policy.AllowAnyOrigin && !policy.Origins.Contains(origin)) { return; } //... } }
(這裏居然有重複代碼,又增添了一份不舒服的感受,暫且無論)blog
原來是經過 !policy.Origins.Contains(origin) 判斷的,只要修改這個地方的判斷代碼,就能實現一勞永逸的偷懶目的。可是,這部分代碼不是隨意能夠修改的,要走代碼貢獻流程,並且不必定被接受,目前仍是先想辦法擴展它吧。繼承
英明的 ASP.NET CORS 開發者早就考慮了這個地方的擴展性,將 EvaluateRequest() 與 EvaluatePreflightRequest 定義爲虛擬方法,咱們只需定義一個子類繼承自 CorsService ,覆蓋這兩個方法便可。ip
接下來就是解決如何覆蓋的問題。把父類中的實現代碼複製過來修改不可取,之後 ASP.NET CORS 升級了,這部分代碼改了,就會帶來問題。咱們須要想辦法在不改變現有處理邏輯的前提下,影響處理結果。
咱們繼續看 !policy.Origins.Contains(origin) ,policy.Origins 的類型是 IList<string> ,它存儲的就是咱們在定義 CORS 策略時添加的網址,因此,若是咱們想要影響這裏的判斷結果,惟有改變 policy.Origins 的值。
根據當前的狀況,咱們能夠把問題簡化爲下面的代碼:
public static void Main(string[] args) { IList<string> origins = new List<string>() { "*.cnblogs.com" }; var origin = "http://www.cnblogs.com"; //在origins中添加"http://www.cnblogs.com" Assert.True(origins.Contains(origin)); }
接下來只需作一件事——集中精力把上面的註釋變成代碼。
。。。
咱們將註釋變成了下面的代碼:
private void EvaluateOriginForWildcard(IList<string> origins, string origin) { //只在沒有匹配的origin的狀況下進行操做 if (!origins.Contains(origin)) { //查詢全部以星號開頭的origin var wildcardDomains = origins.Where(o => o.StartsWith("*")); if (wildcardDomains.Any()) { //遍歷以星號開頭的origin foreach (var wildcardDomain in wildcardDomains) { //若是以.cnblogs.com結尾 if (origin.EndsWith(wildcardDomain.Substring(1)) //或者以//cnblogs.com結尾,針對http://cnblogs.com || origin.EndsWith("//" + wildcardDomain.Substring(2))) { //將http://www.cnblogs.com添加至origins origins.Add(origin); break; } } } } }
而後基於上面的代碼實現 CorsService 的子類 WildcardCorsService :
public class WildcardCorsService : CorsService { public WildcardCorsService(IOptions<CorsOptions> options) : base(options) { } public override void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result) { var origin = context.Request.Headers[CorsConstants.Origin]; EvaluateOriginForWildcard(policy.Origins, origin); base.EvaluateRequest(context, policy, result); } public override void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result) { var origin = context.Request.Headers[CorsConstants.Origin]; EvaluateOriginForWildcard(policy.Origins, origin); base.EvaluatePreflightRequest(context, policy, result); } private void EvaluateOriginForWildcard(IList<string> origins, string origin) { //... } }
而後將其注入:
services.Add(ServiceDescriptor.Transient<ICorsService, WildcardCorsService>());
(注:這裏要用 Add ,不要用 TryAdd ,由於在 service.AddMvc 中已經把 CorsService 注入了,用 Add 才能覆蓋 CorsService 的注入。)
最終 ConfigureServices() 中的代碼變成了這樣:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.Add(ServiceDescriptor.Transient<ICorsService, WildcardCorsService>()); services.Configure<CorsOptions>(options => options.AddPolicy( "AllowSameDomain", builder => builder.WithOrigins("*.cnblogs.com"))); }
一勞永逸的目標就此達成,不舒服的感受煙消雲散。