YARP & SystemWebAdapter 我之前都是升級 web api
定時排程
entity framework
還有些 底層類別
不過也都一一克服逐步上線 , 可以看這篇筆記 現在也研究看看要如何將整個 asp.net mvc 網站升級上去到 .net core
找了些資源後發現有 .NET Upgrade Assistant 這個 extension 開來玩看看 , 礙於專案實在太大又亂馬上陣亡
不過倒是發現兩個關鍵 YARP 跟 systemweb-adapters YARP
是用 c# 寫的 Reverse Proxy 類似 nginx
envoy
的作用 , 不過他可以很好的在 .net core 專案裡面直接用程式碼的方式來設定代理systemweb-adapters
這個東西也滿猛的 , 他可以讓 asp.net mvc 的 HttpContext Session Identity 等讓 .net core 吃到 這兩者搭起來如果有實作了 asp.net mvc 的路由的話 , 會被轉到 .net core 的 controller , 反之會沿用 asp.net mvc 原本的 controller 不過他的文件還是滿難啃的一個閃神就設定失敗 XD 可以看看以下這幾個關鍵連結
https://learn.microsoft.com/zh-tw/aspnet/core/migration/inc/overview?view=aspnetcore-8.0 https://www.youtube.com/watch?v=zHgYDZK3MrA&list=PLdo4fOcmZ0oWiK8r9OkJM3MUUL7_bOT9z https://github.com/mjrousos/UpgradeSample https://github.com/dotnet/systemweb-adapters/tree/main/samples
記錄下我測試 Identity 的過程 先開個 asp.net mvc 的站台 , 然後啟用身分認證 , 接著安裝 Microsoft.AspNetCore.SystemWebAdapters
Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices
關鍵要在 web.config
上面設定這段 , 如果要開 Session 好像還要設定別的模組 , 正常這段他會幫你自動加 可是我公司專案好像是使用新版的設定 , 所以不會自動加上去 , 整個暴雷
1 2 3 4 5 6 <system.webServer> <modules> <remove name="SystemWebAdapterModule" /> <add name="SystemWebAdapterModule" type="Microsoft.AspNetCore.SystemWebAdapters.SystemWebAdapterModule, Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices" preCondition="managedHandler" /> </modules> </system.webServer>
然後在 Global.asax.cs
加上這串 , 他這裡還可以設定 Session 等其他進階用法 , 暫時沒玩到 ApiKey 需要是 guid , 稍後在 .net core 專案也要用一樣的 key 這樣才會正常連線
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MvcApplication : System.Web.HttpApplication { protected void Application_Start ( ) { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); SystemWebAdapterConfiguration.AddSystemWebAdapters(this ) .AddProxySupport(options => options.UseForwardedHeaders = true ) .AddRemoteAppServer(options => options.ApiKey = "25E65A2D-AFD2-4024-8C24-A81F8E9C5465" ) .AddAuthenticationServer(); } }
接著開個 .net core mvc 新專案 , 安裝 YARP
Microsoft.AspNetCore.SystemWebAdapters
Microsoft.AspNetCore.SystemWebAdapters.CoreServices
這三個套件
這裡有一點要注意 , 如果你的 asp.net mvc 網站是放在 subsite
底下的話 網址應該會長這樣 http://localhost:58588/ladisai
這時候要設定 RemoteAppUrl
為 http://localhost:58588/ladisai
才能吃到 HttpContext , 如果你設定 http://localhost:58588
這樣的話會直接 GG 他會得到 null 被這個雷搞超久
Program 設定如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 var builder = WebApplication.CreateBuilder(args);builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy" )); builder.Services.AddControllersWithViews(); builder.Services .AddSystemWebAdapters() .AddRemoteAppClient(options => { options.RemoteAppUrl = new ("http://localhost:58588/ladisai" ); options.ApiKey = builder.Configuration["RemoteAppApiKey" ]; }) .AddAuthenticationClient(true ); var app = builder.Build();if (!app.Environment.IsDevelopment()){ app.UseExceptionHandler("/Home/Error" ); } app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseSystemWebAdapters(); app.MapControllerRoute( name: "default" , pattern: "{controller=Home}/{action=Index}/{id?}" ); app.MapReverseProxy(); app.Run();
RemoteAppApiKey
稍早我們在 asp.net mvc 上面設定的 key
appsetting.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 { "RemoteAppApiKey" : "25E65A2D-AFD2-4024-8C24-A81F8E9C5465" , "Logging" : { "LogLevel" : { "Default" : "Information" , "Microsoft.AspNetCore" : "Warning" } }, "AllowedHosts" : "*" , "ReverseProxy" : { "Routes" : { "fallbackRoute" : { "ClusterId" : "fallbackCluster" , "Order" : "1" , "Match" : { "Path" : "{**catch-all}" } } }, "Clusters" : { "fallbackCluster" : { "Destinations" : { "fallbackApp" : { "Address" : "" } } } } } }
這個裡面的 ReverseProxy__Clusters__fallbackCluster__Destinations__fallbackApp__Address
表示舊專案 asp.net mvc 的網址
launchSettings.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 { "$schema" : "http://json.schemastore.org/launchsettings.json" , "iisSettings" : { "windowsAuthentication" : false , "anonymousAuthentication" : true , "iisExpress" : { "applicationUrl" : "http://localhost:2762" , "sslPort" : 0 } }, "profiles" : { "http" : { "commandName" : "Project" , "dotnetRunMessages" : true , "launchBrowser" : true , "applicationUrl" : "http://localhost:5180" , "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" , "ReverseProxy__Clusters__fallbackCluster__Destinations__fallbackApp__Address" : "https://localhost:44350" } }, "IIS Express" : { "commandName" : "IISExpress" , "launchBrowser" : true , "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" , "ReverseProxy__Clusters__fallbackCluster__Destinations__fallbackApp__Address" : "https://localhost:44350" } } } }
最後隨便蓋一個 controller 然後先登入到 asp.net mvc 的網站 , 接著呼叫這隻 .net core 的 controller 看看是否正常拿到值即可
1 2 3 4 5 6 7 8 9 public class Test : Controller { [Route("/Test")] public IActionResult Test() { var name = this.User.Identity.Name; return Ok(); } }
今天實驗一個之前很困惑的問題 , 如果登入走 OIDC/OAuth2 的話最後不還是會 redirect 回去本來的舊站台嗎 , 經過實驗測試只要在 identityserver 把 redirect 的地方換成 yarp 的路徑就成功了 @@!
WebOptimizer 如果以前專案使用如 jquery angularjs vue1 vue2 等等 , 很有可能前後端會混合使用 , 這時候就需要 WebOptimizer 可以參考這影片 這裡可以把本來的 Content
Scripts
Images
等靜態資源搬進專案 注意不是放在 wwwroot
而是跟原本一樣階層原封不動搬過來
1 2 3 4 5 6 7 8 9 10 11 12 13 builder.Services.AddWebOptimizer(pipeline => { pipeline.AddJavaScriptBundle("/js/bundle", "Scripts/" + "jquery-3.7.0.min.js", "Scripts/" + "jquery-ui-1.12.1.js" ).UseContentRoot(); pipeline.AddCssBundle("/css/bundle", "Content/" + "jquery-ui-1.12.1/jquery-ui.min.css", "Content/" + "jquery-ui-1.12.1/theme.css" ).UseContentRoot(); });
注意到 UseWebOptimizer
要在 UseStaticFiles
之前
1 2 3 //UseWebOptimizer 要在 StaticFiles 之前 app.UseWebOptimizer(); app.UseStaticFiles();
另外因為 legacy 難免有些髒髒的 code , 為了防止 bundle 以外的資源被引用的話 , 可以設定這樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider( Path.Combine(Directory.GetCurrentDirectory(), "Content")), RequestPath = "/Content" }); app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider( Path.Combine(Directory.GetCurrentDirectory(), "Scripts")), RequestPath = "/Scripts" }); app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider( Path.Combine(Directory.GetCurrentDirectory(), "Images")), RequestPath = "/Images" });
接著引用的部分舊版有 Scripts.Render
Styles.Render
但是 .net core 已經沒了
1 2 @Scripts.Render("~/js/xxx") @Styles.Render("~/css/xxx")
.net core 返璞歸真
1 2 <script src="~/js/xxx"></script> <link rel="stylesheet" href="~/css/xxx" />
subsite 問題 如果你的 asp.net mvc 5 站台跑在 subsite 的話應該還需要以下設定 , 參考這裡
1 2 3 4 5 6 7 8 builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")) .AddTransforms(builderContext => { //這裡與 basepath 好像都要設定 //這裡應該要設定才不會多一層 YOURSUBSITENAME 變為 YOURSUBSITENAME/YOURSUBSITENAME //等價於設定 appsettings.json 裡面的 PathRemovePrefix builderContext.AddPathRemovePrefix("/YOURSUBSITENAME"); });
這個設定還需要搭配 UsePathBase
1 app.UsePathBase("/YOURSUBSITENAME");
如果要在 appsettings.json
設定也可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 "ReverseProxy": { "Routes": { "fallbackRoute": { "ClusterId": "fallbackCluster", "Order": "1", "Match": { "Path": "{**catch-all}" } }, "MvcWebNetFramework": { "ClusterId": "MvcWebNetFramework", "Match": { "Path": "{**catch-all}" }, "Transforms": [ { "PathRemovePrefix": "/assemblyline" } ] } }, "Clusters": { "MvcWebNetFramework": { "Destinations": { "MvcWebNetFramework/destination1": { "Address": "http://localhost:5987/YOURSUBSITENAME" } } }, "fallbackCluster": { "Destinations": { "fallbackApp": { "Address": "" } } } } }
後來發現只要在 environmentVariables
底下設定 ASPNETCORE_URLS
就可以不用額外設定 UsePathBase
可以參考這篇
Json.Encode 解法 舊版
1 var currentLang = @Html.Raw(Json.Encode((string)ViewData["CurrentLang"]));
.net core
1 var currentLang = @Html.Raw(JsonConvert.SerializeObject((string)ViewData["CurrentLang"]));
多語系切換 可以參考這裡 這個新舊寫法好像不太一樣 CookieRequestCultureProvider.DefaultCookieName
這個值為 .AspNetCore.Culture
然後要用 CookieRequestCultureProvider.MakeCookieValue
來建立才會正常
1 2 3 4 5 6 7 8 9 10 11 12 13 [AllowAnonymous] [Route("switch-lang-core/{lang?}")] [HttpGet] public IActionResult SwitchLang(string lang = null) { Response.Cookies.Append("MyLang", lang); Response.Cookies.Append( CookieRequestCultureProvider.DefaultCookieName, CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(lang)) ); string referrerUrl = Request.Headers["Referer"].ToString(); return Redirect(referrerUrl); }
ControllerBase 沒辦法接收到 Model 參數的問題 無意中發現 , 如果只用 ControllerBase 好像沒辦法收到 view 傳進來的參數 但加上 [ApiController] 這個 Attribute 就能動了
1 2 3 4 [ApiController] public QQController : ControllerBase { public void Save(QQ model){} }
.net framework AddSystemWebAdapters 沒辦法取得 identity 這個問題滿雷的 , 花了不少時間才發現 使用 SystemWebAdapters 要特別注意到套件是否有 conflicts 這裡如果發生 conflicts 會造成 null 完全讀不出來 identity 的狀況
1 2 3 4 5 6 7 8 9 Warning Found conflicts between different versions of the same dependent assembly. In Visual Studio, double-click this warning (or select it and press Enter) to fix the conflicts; otherwise, add the following binding redirects to the "runtime" node in the application configuration file: <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Microsoft.Extensions.Logging" culture="neutral" publicKeyToken="adb9793829ddae60" /> <bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" /> </dependentAssembly> </assemblyBinding>
wwwroot & UsePathBase 資源讀不到的問題 今天測的時候發現如果把本來的 Scripts
Content
移動到 wwwroot
裡面的話搭配 UsePathBase
會出問題 好像要調整 UseStaticFiles
如下才會正常
1 2 3 4 5 6 7 8 9 10 11 #if DEBUG //這裡要設定 RequestPath 不然有設定 UsePathBase 會吃不到 //https://learn.microsoft.com/en-us/answers/questions/800998/why-js-css-lib-didnt-load-from-wwwroot app.UseStaticFiles(new StaticFileOptions { RequestPath = "/lasai" }); app.UsePathBase("/lasai"); #else app.UseStaticFiles(); #endif