1. 程式人生 > 程式設計 >.Net Core 2.2升級3.1的避坑指南(小結)

.Net Core 2.2升級3.1的避坑指南(小結)

寫在前面

  微軟在更新.Net Core版本的時候,動作往往很大,使得每次更新版本的時候都得小心翼翼,坑實在是太多。往往是悄咪咪的移除了某項功能或者元件,或者不在支援XX方法,這就很花時間去找回需要的東西了,下面是個人在遷移.Net Core WebApi專案過程中遇到的問題彙總:

開始遷移

1. 修改*.csproj專案檔案

<TargetFramework>netcoreapp2.2</TargetFramework>
修改為
<TargetFramework>netcoreapp3.1</TargetFramework>

2 修改Program

public static void Main(string[] args)
    {
      CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
          .UseStartup<Startup>().ConfigureAppConfiguration((hostingContext,config) =>
          {
            config.AddJsonFile($"你的json檔案.json",optional: true,reloadOnChange: true);
          }
          );

修改為

public static void Main(string[] args)
    {
      CreateHostBuilder(args).Build().Run();
    }
 
    public static IHostBuilder CreateHostBuilder(string[] args) =>
      Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
          webBuilder.UseStartup<Startup>()
                .ConfigureAppConfiguration((hostingContext,config)=>
                {
                  config.AddJsonFile($"你的json檔案.json",reloadOnChange: true);
                });
        });

3.1 修改Startup.ConfigureServices

services.AddMvc();
修改為
services.AddControllers();

3.2 修改Startup.Configure

public void Configure(IApplicationBuilder app,IHostingEnvironment env)

修改為
using Microsoft.Extensions.Hosting;
public void Configure(IApplicationBuilder app,IWebHostEnvironment env)

IHostingEnvironment在3.0之後已被標記棄用。

路由配置:

app.UseMvc(routes =>
        {
          routes.MapRoute(
            name: "areas",template: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
          );

          routes.MapRoute(
            name: "default",template: "{controller=Home}/{action=Index}/{id?}"
          );
        });

修改為

      app.UseRouting();
      app.UseEndpoints(endpoints =>
      {
        endpoints.MapControllers();
        endpoints.MapControllerRoute(
            name: "areas",pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
        endpoints.MapControllerRoute(
            name: "default",pattern: "{controller=Home}/{action=Index}/{id?}");
      });

你以為結束了?還沒。

  這時候你以為結束了,興高采烈的去伺服器裝好runningTime和hosting相應的版本,執行……

.Net Core 2.2升級3.1的避坑指南(小結)

HTTP Error 500.30 – ANCM In-Process Start Failure

直接cmd,進入到釋出目錄,執行:

E:\你的路徑>dotnet xxx.dll

顯示詳細錯誤

.Net Core 2.2升級3.1的避坑指南(小結)

而我的相應250程式碼行是:

services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

搜尋最新的AutoMapper根本沒更新或改變,所以不是這個元件的問題。

嘗試下載補丁Windows6.1-KB974405-x64.msu,無果……

解除安裝sdk重置,無果……

修改web.config,無果……

修改應用池32位,無果……

最後,檢視釋出:勾選上【刪除現有檔案】,解決……

.Net Core 2.2升級3.1的避坑指南(小結)

Endpoint contains CORS metadata,but a middleware was not found that supports CORS.

  順利可以啟動專案之後,發現有些介面:

2020-06-29 10:02:23,357 [14] ERROR System.String - 全域性異常捕捉:異常:Endpoint contains CORS metadata,but a middleware was not found that supports CORS.
Configure your application startup by adding app.UseCors() inside the call to Configure(..) in the application startup code. The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...).

提示很明顯,在.net core 2.2 的時候

app.UseCors();

不是需要強制在指定位置的,在3.0之後需要設定在app.UseRouting和app.UseEndpoints 之間

app.UseRouting();//跨域
app.UseCors(one);
app.UseCors(two);
……
app.UseEndpoints(endpoints => ……

The JSON value could not be converted to System.Int32. Path……

  執行之後,有些介面沒有資料返回,而有些直接報錯了。原因又是爸爸把Newtonsoft.Json移除,使用內建的System.Text.Json,所以依賴於Newtonsoft.Json的元件將不可用,那麼,只能手動新增。

Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson -Version 3.1.5

然後新增引用

public void ConfigureServices(IServiceCollection services)
{
  services.AddControllers().AddNewtonsoftJson();
}

目前還不太建議你使用內建的序列化,因為實在太多功能或方法不支援,詳細對比請參考https://docs.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to

授權相關

  基於策略授權,我想在座的加班狗都是大同小異,在2.2以前:

public class PolicyHandler : AuthorizationHandler<PolicyRequirement>
  {
    /// <summary>
    /// 授權方式(cookie,bearer,oauth,openid)
    /// </summary>
    public IAuthenticationSchemeProvider Schemes { get; set; }

    private IConfiguration _configuration;

    /// <summary>
    /// ctor
    /// </summary>
    /// <param name="configuration"></param>
    /// <param name="schemes"></param>
    /// <param name="jwtApp"></param>
    public PolicyHandler(IConfiguration configuration,IAuthenticationSchemeProvider schemes)
    {
      Schemes = schemes;
      _jwtApp = jwtApp;
      _configuration = configuration;
    }

    /// <summary>
    /// 授權處理
    /// </summary>
    /// <param name="context"></param>
    /// <param name="requirement"></param>
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,PolicyRequirement requirement)
    {
      var httpContext = (context.Resource as AuthorizationFilterContext).HttpContext;

      //獲取授權方式
      var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
      if (defaultAuthenticate != null)
      {
        //驗證簽發的使用者資訊
        var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);
        if (result.Succeeded)
        {
          httpContext.User = result.Principal;
         
          //判斷是否過期
          var expirationTime = DateTime.Parse(httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration).Value);
          if (expirationTime >= DateTime.UtcNow)
          {
             //你的校驗方式
             //todo
            context.Succeed(requirement);
          }
          else
          {
            HandleBlocked(context,requirement);
          }
          return;
        }
      }
      HandleBlocked(context,requirement);
    }
     
    /// <summary>
    /// 驗證失敗返回
    /// </summary>
    private void HandleBlocked(AuthorizationHandlerContext context,PolicyRequirement requirement)
    {
      var authorizationFilterContext = context.Resource as AuthorizationFilterContext;
      authorizationFilterContext.Result = new Microsoft.AspNetCore.Mvc.JsonResult(new UnAuthorizativeResponse()) { StatusCode = 202 };
      //不要呼叫 context.Fail(),設定為403會顯示不了自定義資訊,改為Accepted202,由客戶端處理,;
      context.Succeed(requirement);
    }
  }

然後發現升級到3.0之後,

var httpContext = (context.Resource as AuthorizationFilterContext).HttpContext;

3.0不再支援返回AuthorizationFilterContext,而是返回的是RouteEndpoint,這句程式碼就會報錯,所以修改的方式就是注入IHttpContextAccessor,從裡面獲取HttpContext,這裡就不用演示了吧。

並修改PolicyHandler校驗失敗時候呼叫的方法:

/// <summary>
    /// 驗證失敗返回
    /// </summary>
    private void HandleBlocked(AuthorizationHandlerContext context,PolicyRequirement requirement)
    {
      context.Fail();
    }

並在Startup.ConfigureServices修改

 services.AddHttpContextAccessor();

在AddJwtBearer中

.AddJwtBearer(s =>
      {
        //3、新增 Jwt bearer 
        s.TokenValidationParameters = new TokenValidationParameters
        {
          ValidIssuer = issuer,ValidAudience = audience,IssuerSigningKey = key,//允許的伺服器時間偏差的偏移量
          ClockSkew = TimeSpan.FromSeconds(5),ValidateLifetime = true
        };
        s.Events = new JwtBearerEvents
        {
          OnAuthenticationFailed = context =>
          {
            //Token 過期 
            if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
            {
              context.Response.Headers.Add("Token-Expired","true");
            } 
            return Task.CompletedTask;
          },OnChallenge = context =>
          {
            context.HandleResponse(); 
            context.Response.StatusCode = StatusCodes.Status200OK;
            context.Response.ContentType = "application/json";
            //無授權返回自定義資訊
            context.Response.WriteAsync(JsonConvert.SerializeObject(new UnAuthorizativeResponse()));
            return Task.CompletedTask;
          }
        };
      });

UnAuthorizativeResponse 是自定義返回的內容。

Startup.Configure中啟用Authentication,注意順序

app.UseRouting();
//跨域
app.UseCors(one);
app.UseCors(two);
……
//啟用 Authentication 
app.UseAuthorization();
app.UseAuthentication();
app.UseEndpoints(endpoints => ……

也必須在app.UseRouting和app.UseEndpoints之間。

檔案下載

  單獨封裝的HttpContext下載方法:

public static void DownLoadFile(this HttpContext context,string fileName,byte[] fileByte,string contentType = "application/octet-stream")
    {
      int bufferSize = 1024;
      
      context.Response.ContentType = contentType;
      context.Response.Headers.Append("Content-Disposition","attachment;filename=" + HttpUtility.UrlEncode(fileName));
      context.Response.Headers.Append("Charset","utf-8");
      context.Response.Headers.Append("Access-Control-Expose-Headers","Content-Disposition");
     
      //context.Response.Headers.Append("Access-Control-Allow-Origin","*");
      //使用FileStream開始迴圈讀取要下載檔案的內容
      using (Stream fs = new MemoryStream(fileByte))
      {
        using (context.Response.Body)
        {
          long contentLength = fs.Length;
          context.Response.ContentLength = contentLength;

          byte[] buffer;
          long hasRead = 0;
          while (hasRead < contentLength)
          {
            if (context.RequestAborted.IsCancellationRequested)
            {
              break;
            }
            
            buffer = new byte[bufferSize];
            //從下載檔案中讀取bufferSize(1024位元組)大小的內容到伺服器記憶體中
            int currentRead = fs.Read(buffer,bufferSize);
            context.Response.Body.Write(buffer,currentRead);
            context.Response.Body.Flush();
            hasRead += currentRead;
          }
        }
      }
    }

下載的時候發現以下錯誤:Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.

2020-06-29 14:18:38,898 [109] ERROR System.String - System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
  at Microsoft.AspNetCore.Server.IIS.Core.HttpResponseStream.Write(Byte[] buffer,Int32 offset,Int32 count)
  at Microsoft.AspNetCore.Server.IIS.Core.WrappingStream.Write(Byte[] buffer,Int32 count)
  at DigitalCertificateSystem.Common.Extensions.HttpContextExtension.DownLoadFile(HttpContext context,String fileName,Byte[] fileByte,String contentType) in ……

意思不運行同步操作,修改為

context.Response.Body.WriteAsync(buffer,currentRead);

這才順利完成了更新。真的太坑了,不過也感覺微軟的抽象化做得很好,按需引入,減少專案的冗餘。

更多升級指南請參考“孫子兵法”:https://docs.microsoft.com/zh-cn/aspnet/core/migration/22-to-30?view=aspnetcore-2.1&tabs=visual-studio

作者:EminemJK(山治先生)
出處:https://www.cnblogs.com/EminemJK/

到此這篇關於.Net Core 2.2升級3.1的避坑指南(小結)的文章就介紹到這了,更多相關.Net Core 2.2升級3.1內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!