---
# System prepended metadata

title: dotnet framework 4 升級到 6 要改的東西

---

# 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打包壓縮功能
