Try   HackMD

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 下一步就好,截圖記錄一下執行過程


刪除 整個 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

public class Startup { public void Configure(IApplicationBuilder app) { app.UseStaticFiles(); } }

另外還有個 Startup.cs

dotnet 6 program.cs (專案生成預設沒有Startup.cs)

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("~/") 的路徑了

以下為完整範例

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; }

參考來源

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)

參考來源

Request.InputStream > Request.Body

參考來源

HttpRequestBase > HttpRequest

引用這個namespace
using Microsoft.AspNetCore.Http;
然後把 HttpRequestBase 直接改 HttpRequest 就好

Request.RawUrl > Request.HttpContext

.net4

HttpRequest Request 
Request.RawUrl;

.net core

var httpContext = Request.HttpContext;
$"{ httpContext.Request.Host}{ httpContext.Request.Path}{ httpContext.Request.QueryString}";

參考來源

NotFound() 改 StatusCode(404)

.net4

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

return StatusCode(404);

參考來源

Request.QueryString.AllKeys > linq where

.net4

Request.QueryString.AllKeys

core

string[] AllKeys = Request.Query.Where(m => string.IsNullOrEmpty(m.Value)).Select(m => m.Key).ToArray();

參考來源

FormCollection > Microsoft.AspNetCore.Http.IFormCollection

.net 4

System.Web.Mvc.FormCollection

.net core

Microsoft.AspNetCore.Http.IFormCollection

參考來源

razor helper 沒了

以前我會開一個 App_Code/MyHelper.cshtml,在裡面寫類似這樣的東西

@helper NowYear(){ <p>Datetime.Now.Year</p> }

但在 dotnet 6 已經不支援這種寫法了

看了各種討論之後,最簡單的做法是改用 partial view

首先在 view 裡面開一個 Default.cshtml (路徑、檔名可自訂)

@model string <h3>Priority Items</h3> <ul> <li>@Model + todo.Name</li> </ul>

接著在真正要使用的 view 上面使用的時候,利用 await Html.PartialAsync() 來執行

@{ var mm = "abc"; } @await Html.PartialAsync("~/Views/Shared/Components/Default.cshtml",mymodel)

但是另一個問題,想要快速跳到 partial 裡面沒有辦法按 F12 快捷鍵跳過去
這邊選用的方法是去設定快捷鍵,

  1. 在Visual Studio 上方選單 > 工具 > 選項 > 環境 > 鍵盤

  2. 搜尋 前往所有,找到 編輯.前往所有

  3. 設定快捷鍵 (我使用的是 Ctrl+T)

  4. 接著在程式碼中,點選 Default 字串的時候,按下ctrl+t,右上角會跳出搜尋視窗

  5. 直接點選 enter 就會進去了

參考來源

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也可以修改

參考來源

App_Data 資料夾內的東西發行的時候沒有發出去

在 專案.csproj 檔案或發行設定檔增加下面這一段
如果是增加在專案.csproj,則每一個發行設定都會使用這個設定
如果有分成 FTP 發布、本地發布等等要分開設定,就要加在發行設定擋

<ItemGroup>
  <DotnetPublishFiles Include="App_Data/**/*">
      <DestinationRelativePath>App_Data/%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
  </DotnetPublishFiles>
</ItemGroup>

參考來源

不能使用的 nuget 元件

  • WebGrease :ASP.NET MVC的CSS/JS打包壓縮功能