# dotnet framework 4 升級到 6 要改的東西 ## 為什麼要做專案升級? 其實執行升級前已經先有心理準備會有一大堆問題要手動處理,但身為資訊人員一輩子都得學,躲得了一時躲不了一輩子。 雖然也可以選擇在新專案直接開一個新專案來做,不過我想自己拿一個小專案試一下,並把所有問題與解決方式逐一寫起來,這樣的好處有 1. 了解新舊差異,並清楚優劣 2. 可以評估舊專案升級所要花費的成本 (人力 時間) 3. 自我挑戰,雖然大家都說打掉重練最快,但打掉重練也是容易遇到不可預期的問題 以下前面一部分會依照我的執行順序逐一說明,後面篇幅則是各種零星問題的集合整理。 ## 程式碼備份 可以自己事先備份,也可以等執行 .NET 升級小幫手 時自動備份,會在原專案內產生一個 ProjectName.backup 的資料夾,檔案會備份在這裡 ## 選用升級方式 這邊有兩個選項 1. 使用 .NET 升級小幫手(2022/5/9 時還是預覽版 preview) 將 ASP.NET MVC 應用程式升級至 .NET 6 https://docs.microsoft.com/zh-tw/dotnet/core/porting/upgrade-assistant-aspnetmvc 2. 從 ASP.NET MVC 移轉至 ASP.NET Core MVC https://docs.microsoft.com/zh-tw/aspnet/core/migration/mvc?view=aspnetcore-6.0 第1個是指令列模式的工具,第2個則是用開專案的方式慢慢搬過去,我選用的是第1個 跟著步驟在指令列一直按 enter 下一步就好,截圖記錄一下執行過程 ![](https://i.imgur.com/IbFMbgL.png) ![](https://i.imgur.com/eyVWKqF.png) --- ## 刪除 整個 App_Start 資料夾 和 Global.asax.cs 檔案。 在 dotnet core 中 已經改用 Startup.cs ## Content、Scripts、css、js 等靜態檔案移動到 wwwroot 新的權限架構下,預設能夠被存取的檔案都移動到 wwwroot 前端僅能碰觸到 wwwroot內的檔案 ,但後端可讀取實體磁碟的路徑故不限 wwwroot ## ASP.NET Core 5 vs ASP.NET Core 6 啟動的檔案有點不一樣,使用升級工具升上去,出現的是 5 的架構 dotnet 5 program.cs ```C#= public class Startup { public void Configure(IApplicationBuilder app) { app.UseStaticFiles(); } } ``` 另外還有個 Startup.cs dotnet 6 program.cs (專案生成預設沒有Startup.cs) ```C#= var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.UseStaticFiles(); app.Run(); ``` https://docs.microsoft.com/zh-tw/aspnet/core/migration/50-to-60-samples?view=aspnetcore-6.0 ## 剩下的逐一手動搬移 畢竟不可能一次全部弄到好,所以接下來的都是逐條手動操作的步驟,以下的順序並非操作順序,可以任意調整順序,如果建置有過,基本上就能發行到 IIS 或 雲端平台 跑看看了 --- ## 不能使用 @Styles 和 @Scripts @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/bootstrap") 無解,因為 dotnet core 比較推薦用前端領域專屬的工具(webpack 或 gulp)壓縮前端程式碼 或是就是逐行scripts style 一個個引入使用靜態 html 的傳統做法 ## 不能使用 System.Web 這邊每個的對應方式都不一樣,請逐條檢視 ## Server.MapPath() > IHostEnvironment.ContentRootPath 改用 Microsoft.Extensions.Hosting.IHostEnvironment 這邊是用 DI 相依性注入的方式來做, 首先在原本有 Server.MapPath 的 Controller 加上這段,把 IHostEnvironment 註冊進去 ``` private readonly IHostEnvironment _hostingEnvironment; public HomeController(IHostEnvironment hostingEnvironment) { _hostingEnvironment = hostingEnvironment; } ``` 接著在 Action 內就能用 ```_hostingEnvironment.ContentRootPath``` 取得原本 Server.MapPath("~/") 的路徑了 以下為完整範例 ```C#= using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Hosting; public class HomeController : Controller { private readonly IHostEnvironment _hostingEnvironment; public HomeController(IHostEnvironment hostingEnvironment) { _hostingEnvironment = hostingEnvironment; } public string Index() { return _hostingEnvironment.ContentRootPath; } public string MapPath => _hostingEnvironment.ContentRootPath; } ``` 參考來源 * IHostEnvironment 介面 https://docs.microsoft.com/zh-TW/dotnet/api/microsoft.extensions.hosting.ihostenvironment?view=dotnet-plat-ext-6.0 * ASP.NET Core 教學 - 取得網站根目錄 https://blog.johnwu.cc/article/asp-net-core-getting-web-root-path.html ## HttpUtility.HtmlDecode(code) > HtmlEncoder.Default.Encode(code) .net4 using System.Web; ``` HttpUtility.HtmlDecode(code) HttpUtility.HtmlEncode(code) ``` .net core using System.Text.Encodings.Web; ``` HtmlEncoder.Default.Encode(code) HtmlEncoder.Default.Decode(code) ``` 參考來源 * ASP.NET Core(.NET Core)中使用HtmlDecode和HtmlEncode方法 https://www.cjavapy.com/article/78/ ## Request.InputStream > Request.Body 參考來源 * Request.InputStream in ASP.NET Core https://stackoverflow.com/questions/48995809/request-inputstream-in-asp-net-core * HttpRequest.Body 屬性 https://docs.microsoft.com/zh-tw/dotnet/api/microsoft.aspnetcore.http.httprequest.body?view=aspnetcore-6.0 ## HttpRequestBase > HttpRequest 引用這個namespace ```using Microsoft.AspNetCore.Http;``` 然後把 HttpRequestBase 直接改 HttpRequest 就好 ## Request.RawUrl > Request.HttpContext .net4 ```=C# HttpRequest Request Request.RawUrl; ``` .net core ```=C# var httpContext = Request.HttpContext; $"{ httpContext.Request.Host}{ httpContext.Request.Path}{ httpContext.Request.QueryString}"; ``` 參考來源 * ASP.NET Core 教學 - 取得網站根目錄 https://blog.johnwu.cc/article/asp-net-core-getting-web-root-path.html ## NotFound() 改 StatusCode(404) .net4 ```=C# return Ok(); // Http status code 200 return Created(); // Http status code 201 return NoContent(); // Http status code 204 return BadRequest(); // Http status code 400 return Unauthorized(); // Http status code 401 return Forbid(); // Http status code 403 return NotFound(); // Http status code 404 ``` .net core ```=C# return StatusCode(404); ``` 參考來源 * Quick Tip – Return HTTP Status Code from ASP.NET Core Methods https://www.talkingdotnet.com/return-http-status-code-from-asp-net-core-methods/ ## Request.QueryString.AllKeys > linq where .net4 ``` Request.QueryString.AllKeys ``` core ```=C# string[] AllKeys = Request.Query.Where(m => string.IsNullOrEmpty(m.Value)).Select(m => m.Key).ToArray(); ``` 參考來源 * How to read keyless querystring in .net core https://stackoverflow.com/questions/51288760/how-to-read-keyless-querystring-in-net-core ## FormCollection > Microsoft.AspNetCore.Http.IFormCollection .net 4 ``` System.Web.Mvc.FormCollection ``` .net core ``` Microsoft.AspNetCore.Http.IFormCollection ``` 參考來源 * C# ASP.NET Core Cannot bind to a model of type 'Microsoft.AspNetCore.Http.FormCollection' https://stackoverflow.com/questions/46070586/c-sharp-asp-net-core-cannot-bind-to-a-model-of-type-microsoft-aspnetcore-http-f ## razor helper 沒了 以前我會開一個 App_Code/MyHelper.cshtml,在裡面寫類似這樣的東西 ```C#= @helper NowYear(){ <p>Datetime.Now.Year</p> } ``` 但在 dotnet 6 已經不支援這種寫法了 看了各種討論之後,最簡單的做法是改用 partial view 首先在 view 裡面開一個 Default.cshtml (路徑、檔名可自訂) ```C#= @model string <h3>Priority Items</h3> <ul> <li>@Model + todo.Name</li> </ul> ``` 接著在真正要使用的 view 上面使用的時候,利用 await Html.PartialAsync() 來執行 ```C#= @{ var mm = "abc"; } @await Html.PartialAsync("~/Views/Shared/Components/Default.cshtml",mymodel) ``` 但是另一個問題,想要快速跳到 partial 裡面沒有辦法按 F12 快捷鍵跳過去 這邊選用的方法是去設定快捷鍵, 1. 在Visual Studio 上方選單 > 工具 > 選項 > 環境 > 鍵盤 2. 搜尋 前往所有,找到 編輯.前往所有 3. 設定快捷鍵 (我使用的是 Ctrl+T) ![](https://i.imgur.com/YEM334G.png) 4. 接著在程式碼中,點選 Default 字串的時候,按下ctrl+t,右上角會跳出搜尋視窗 ![](https://i.imgur.com/msPM3Ec.png) 5. 直接點選 enter 就會進去了 參考來源 * 台灣 .NET 技術愛好者俱樂部 https://www.facebook.com/groups/DotNetUserGroupTaiwan/posts/2465760107050426/ * Is there a Visual Studio "Go to Partial" shortcut https://stackoverflow.com/questions/51655377/is-there-a-visual-studio-go-to-partial-shortcut ## System.IO.IOException: 'Failed to bind to address https://localhost:21223.' SocketException: 嘗試存取通訊端被拒絕,因為存取權限不足。 網站偵錯使用的預設 port 可能跟某個服務相衝了,可以開 cmd 輸入 ```netstat -nat | findstr 5000``` 檢查看看是跟哪個相衝,嘗試關閉對應服務。 或是到專案的 Properties\launchSettings.json 檔案,更改這一段的 port ```"applicationUrl": "https://localhost:21223;http://localhost:21224"``` 如果使用 visual studio 2022,在上方選單>偵錯>偵錯屬性>最下面的應用程式URL也可以修改 ![](https://i.imgur.com/E1HOqxc.png) 參考來源 * [NETCore] ASP.NET Core 啟動失敗 - 嘗試存取通訊端被拒絕,因為存取權限不足 https://marcus116.blogspot.com/2019/07/netcore-aspnet-core-kestrel-fail-to-bind-address.html ## App_Data 資料夾內的東西發行的時候沒有發出去 在 專案.csproj 檔案或發行設定檔增加下面這一段 如果是增加在專案.csproj,則每一個發行設定都會使用這個設定 如果有分成 FTP 發布、本地發布等等要分開設定,就要加在發行設定擋 ``` <ItemGroup> <DotnetPublishFiles Include="App_Data/**/*"> <DestinationRelativePath>App_Data/%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath> </DotnetPublishFiles> </ItemGroup> ``` 參考來源 * Visual Studio 發佈設定檔 (. .pubxml) 以進行 ASP.NET Core 應用程式部署 https://docs.microsoft.com/zh-tw/aspnet/core/host-and-deploy/visual-studio-publish-profiles?view=aspnetcore-6.0#include-files ## 不能使用的 nuget 元件 * WebGrease :ASP.NET MVC的CSS/JS打包壓縮功能