寫在前面
ASP.NET Core是微軟新推出的支持跨平台、高性能、開源的開發框架,它的優勢不必多說,因為已經說得太多了。當然,現在依然有着數量龐大的系統運行於.NET Framework上,由於有大量的Break Changes,很多項目項目團隊也不敢貿然升級,其中的考量也不全部是技術原因,更多的可能還是業務推進因素。
小編自年前開始考慮升級一套電商系統,原先是基於.NET Framework 4.5的,打算直接升級到.NET Core 3.1,由於系統規模比較龐大,所以一旦開工就是一個漫長的工程,我的博客也在很長時間沒有再更新,有點對不起讀者了。
年前第一次重構時,由於低估這套系統的復雜性再加上有些冒進,步子邁得有點大,出現了很多問題,不得不重新開始。這一次重構先易后難,步步為營,難題統一在后面解決,到現在已經完成了全部工程的百分之八十,后面的也沒有太困難了,所以特地抽出時間小結一下。
詳細內容
類庫部分
類庫部分的遷移應該是最簡單的了,我是創建了一個新的類庫,然后把代碼copy過去,很少有地方需要修改,當然了有一些引用的第三方類庫需要特殊對待,如Automapper、Autofac、FluentValidation等,這些也很簡單,看看文檔就行。
.NET Framework中,會有一些常用的封裝庫,如Session、Cookie和HttpRuntime等,這些變化比較大,所以自己在Startup中啟用。
- Session:
- Startup.Configure:
1: app.UseSession(new SessionOptions
2: {
3: Cookie = new CookieBuilder
4: {
5:
6: },
7: IdleTimeout = TimeSpan.FromSeconds(1),
8: IOTimeout = Timeout.InfiniteTimeSpan
9: });
-
-
- Startup.ConfigureServices:
1: services.AddSession();
- Startup.ConfigureServices:
-
- 使用Session,可以通過HttpContext調用:
1: HttpContext.Session.SetString("sessionId", sessionValue);
2: HttpContext.Session.GetString("sessionId");
3: context.Session.Remove("sessionId");
- Cookie:
-
1: Response.Cookies.Append("User", "1", new CookieOptions()
2: {
3: Expires = DateTime.Now.AddMinutes(10)
4: });
5: Response.Cookies.Delete("User");
-
HttpRuntime的使用,可以通過IMemoryCache替換,具體的使用方法可參考MSDN
- System.Drawing已經不存在了,我使用的是ZKWeb.System.Drawing,基本上類名、枚舉名沒變化,只是命名空間Drawing變成了DrawingCore
- 依賴注入部分全部遷移到Startup.ConfigureServices
Controller部分
順便說一下,靜態資源部分,如JS、CSS、Image、Font這些復制到wwwroot目錄上,另外app.UseStaticFiles();會在模板中出現。
1、獲取Controller及Action信息,可以通過RouteData.Values["controller"].ToString(),RouteData.Values["action"].ToString()
2、很多的信息都放到了Request.Header[“”]中,如果之前可以用過Request直接點出來的,但是現在點不出來了,可以嘗試使用這種方式,說不准會有意外驚喜。另外有一個相關的常量在這里出示一下,使用方式即Request.Header[HeaderNames.Authority],當然Request.HttpMethod 改為了 Request.Method。
1: public static class HeaderNames
2: {
3: public static readonly string Accept;
4: public static readonly string AcceptCharset;
5: public static readonly string AcceptEncoding;
6: public static readonly string AcceptLanguage;
7: public static readonly string AcceptRanges;
8: public static readonly string AccessControlAllowCredentials;
9: public static readonly string AccessControlAllowHeaders;
10: public static readonly string AccessControlAllowMethods;
11: public static readonly string AccessControlAllowOrigin;
12: public static readonly string AccessControlExposeHeaders;
13: public static readonly string AccessControlMaxAge;
14: public static readonly string AccessControlRequestHeaders;
15: public static readonly string AccessControlRequestMethod;
16: public static readonly string Age;
17: public static readonly string Allow;
18: public static readonly string Authority;
19: public static readonly string Authorization;
20: public static readonly string CacheControl;
21: public static readonly string Connection;
22: public static readonly string ContentDisposition;
23: public static readonly string ContentEncoding;
24: public static readonly string ContentLanguage;
25: public static readonly string ContentLength;
26: public static readonly string ContentLocation;
27: public static readonly string ContentMD5;
28: public static readonly string ContentRange;
29: public static readonly string ContentSecurityPolicy;
30: public static readonly string ContentSecurityPolicyReportOnly;
31: public static readonly string ContentType;
32: public static readonly string Cookie;
33: public static readonly string CorrelationContext;
34: public static readonly string Date;
35: public static readonly string DNT;
36: public static readonly string ETag;
37: public static readonly string Expect;
38: public static readonly string Expires;
39: public static readonly string From;
40: public static readonly string Host;
41: public static readonly string IfMatch;
42: public static readonly string IfModifiedSince;
43: public static readonly string IfNoneMatch;
44: public static readonly string IfRange;
45: public static readonly string IfUnmodifiedSince;
46: public static readonly string KeepAlive;
47: public static readonly string LastModified;
48: public static readonly string Location;
49: public static readonly string MaxForwards;
50: public static readonly string Method;
51: public static readonly string Origin;
52: public static readonly string Path;
53: public static readonly string Pragma;
54: public static readonly string ProxyAuthenticate;
55: public static readonly string ProxyAuthorization;
56: public static readonly string Range;
57: public static readonly string Referer;
58: public static readonly string RequestId;
59: public static readonly string RetryAfter;
60: public static readonly string Scheme;
61: public static readonly string SecWebSocketAccept;
62: public static readonly string SecWebSocketKey;
63: public static readonly string SecWebSocketProtocol;
64: public static readonly string SecWebSocketVersion;
65: public static readonly string Server;
66: public static readonly string SetCookie;
67: public static readonly string Status;
68: public static readonly string StrictTransportSecurity;
69: public static readonly string TE;
70: public static readonly string TraceParent;
71: public static readonly string TraceState;
72: public static readonly string Trailer;
73: public static readonly string TransferEncoding;
74: public static readonly string Translate;
75: public static readonly string Upgrade;
76: public static readonly string UpgradeInsecureRequests;
77: public static readonly string UserAgent;
78: public static readonly string Vary;
79: public static readonly string Via;
80: public static readonly string Warning;
81: public static readonly string WebSocketSubProtocols;
82: public static readonly string WWWAuthenticate;
83: public static readonly string XFrameOptions;
84: }
3、Request.IsAjaxRequest
這個已經不存在了,可以自行實現。
1: public static bool IsAjaxRequest(this HttpRequest request)
2: {
3: if (request == null)
4: throw new ArgumentNullException("request");
5:
6: if (request.Headers != null)
7: return request.Headers["X-Requested-With"] == "XMLHttpRequest";
8: return false;
9: }
4、Area注冊
之前的AreaRegistration已經不存在,如果需要設置Area,可以在每個Controller上設置[Area(“Admin”)],路由處的注冊可以考慮如下方式
1: app.UseEndpoints(endpoints =>
2: {
3: endpoints.MapControllerRoute(
4: name: "default",
5: pattern: "{controller=Home}/{action=Index}/{id?}");
6:
7: endpoints.MapControllerRoute(
8: name: "areas",
9: pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
10: );
11: });
5、AbsoluteUri也已經不存在了,但是可以通過如下方式取代:
1: /// <summary>
2: /// Returns the combined components of the request URL in a fully un-escaped form (except for the QueryString)
3: /// suitable only for display. This format should not be used in HTTP headers or other HTTP operations.
4: /// </summary>
5: /// <param name="request">The request to assemble the uri pieces from.</param>
6: /// <returns>The combined components of the request URL in a fully un-escaped form (except for the QueryString)
7: /// suitable only for display.</returns>
8: public static string GetDisplayUrl(this HttpRequest request);
9:
10: /// <summary>Returns the relative URI.</summary>
11: /// <param name="request">The request to assemble the uri pieces from.</param>
12: /// <returns>The path and query off of <paramref name="request" />.</returns>
13: public static string GetEncodedPathAndQuery(this HttpRequest request);
14:
15: /// <summary>
16: /// Returns the combined components of the request URL in a fully escaped form suitable for use in HTTP headers
17: /// and other HTTP operations.
18: /// </summary>
19: /// <param name="request">The request to assemble the uri pieces from.</param>
20: /// <returns>The encoded string version of the URL from <paramref name="request" />.</returns>
21: public static string GetEncodedUrl(this HttpRequest request);
6、過濾器
之前繼承ActionFilterAttribute,現在實現IActionFilter,注冊方式為services.AddMvc(o=>o.Filters.Add(new XX())),當然之前的很多過濾器或者Controller基類方法已經不存在了,如Controller OnAuthentication。
IResultFilter中的OnResultExecuting(ResultExecutingContext filterContext)需要通過filterContext.Controller as Controller來獲取默認的Controller。
最后有一個比較重要的類ActionDescriptor,ControllerDescriptor繼承自ActionDescriptor,這里可以通過類型轉換獲取相關信息。
之前有很多的FilterAttribute也可以通過中間件來取代。
7、Action上被去掉的Attribute,如[ValidateInput(false)],[ChildActionOnly]
View部分
1、頁面基類型及擴展
之前我們創建頁面基類型,是通過繼承System.Web.Mvc.WebViewPage<TModel>來實現,現在我們可以通過RazorPage<TModel>來取代。
擴展HtmlHelper也換成了IHtmlHelper接口。HtmlString也替換了MvcHtmlString,更上層也以接口方式來取代IHtmlContent。
1: public static IHtmlContent XXX(this IHtmlHelper _html, string xxxxx)
2: {
3: //your code
4: return new HtmlString(html.ToString());
5: }
2、Ajax.BeginForm換成了<form asp-controller="DistributorGrade" asp-action="Save" id="addform" data-ajax="true" data-ajax-method="post" data-ajax-begin="begin" data-ajax-success="success">。當前.NET Core 依然支持Html.BeginForm,不過我建議大家有時間的時候都替換一下,具體請參考下一條。
3、第2條出現的asp-action等是通過Razor Tag Helpers來實現的,很多的自定義需要加入到_ViewImports.cshtml,當然一些引用也可以統一放到這里,如@using Microsoft.AspNetCore.Routing,這樣就可以在當前的Area中作為全局引用了。
Razor Tag Help是一個十分重要的功能,它使得.NET Core MVC的開發更像是在寫Html語言,更加的清晰,更加具有生產力。
如@Html.TextBoxFor()可以用通過<input asp-for=””/>替換,以下圖片摘自MSDN:
Framework MVC的寫法
Core MVC的寫法
一些Tag Help集錦:
Built-in ASP.NET Core Tag Helpers
4、@Html.Action和@Html.RenderAction可以通過ViewComponents來取代
1: public class XXXXViewComponent : ViewComponent
2: {
3: public IViewComponentResult Invoke()
4: {
5: return this.View("");
6: }
7: }
調用方式是await Component.InvokeAsync(“XXXXViewComponent“),詳情請點擊鏈接
5、@MvcHtmlString.Create()可以使用new Microsoft.AspNetCore.Html.HtmlString()取代
6、IP地址可以通過HttpRequest.HttpContext.Connection.RemoteIpAddress獲取
7、之前通過@helper 定義頁面的函數,這個已經被去掉了,現在可以通過@functions來取代
小結
限於篇幅,先總結這么多,系統尚未完全結束,不過升級到.NET Core是一個非常棒的過程,可以更好地體驗.NET Core的強大。如果小伙伴在升級過程中也遇到了很多問題,希望這篇文章可以給大家一些幫助,另外我沒有寫到的,大家可以留個言,我統一收集一下。