FW 迁移到 Core 常见的坑或问题

_

该记录对FW项目Core迁移的一些坑、调整等变动和遇到的问题。

一为了迁移学习记录

二是方便后续迁移工作,尽量避免多次遇到同个文档导致项目迁移工作卡顿和测试、线上问题

迁移常见问题:


1.Core3.1 Json序列化使用Newtonsoft.Json

原因可参考 [ Net Core 3.1 json序列号时间和emoji格式相关问题](https://blog.kagamikun.com/archives/netcore31json%E5%BA%8F%E5%88%97%E5%8F%B7%E6%97%B6%E9%97%B4%E5%92%8Cemoji%E6%A0%BC%E5%BC%8F%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98)

使用的包为Microsoft.AspNetCore.Mvc.NewtonsoftJson

注意项目版本若为3.1,则使用 3.1.18 版本


2.中文返回编码问题,时间格式问题:

这两行设置时间输入和输出格式及中文被编码

1.Json序列化使用Newtonsoft.Json解决特殊字符编码编码问题

2.因Newtonsoft时间转IsoDateTimeConverter设置DefaultDateTimeFormat值得情况下,是直接调用DateTime.ParseExact,如果前端同时会2020-01-012020-01-01 12:00:00两种格式的话,前者会无法反序列化

所以要加一个解析类

services.AddControllers(op =>
{
    //过滤器
    op.Filters.Add<Attribute>();
    op.Filters.Add<Attribute>();
}).AddNewtonsoftJson(options =>
{
    options.SerializerSettings.ContractResolver = new DefaultContractResolver();//移除默认驼峰格式
    options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
    options.SerializerSettings.Converters.Add(new DatetimeJsonConverter());
});

 /// <summary>
    /// Newtonsoft.Json自定义时间格式转换
    /// </summary>
    public class DatetimeJsonConverter : DateTimeConverterBase
    {
        //读取对象,DateTimeFormat为空时就不按DateTimeFormat的格式强制序列化,"2021-08-29"这种只有日期的字符串也能序列化成功
        private static IsoDateTimeConverter isoDateTimeConverterRaad = new IsoDateTimeConverter() { };
        //写对象,输出时间格式为"yyyy-MM-dd HH:mm:ss"
        private static IsoDateTimeConverter isoDateTimeConverterWrite = new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" };
        /// <summary>
        /// 重写输入时的时间序列化格式
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="objectType"></param>
        /// <param name="existingValue"></param>
        /// <param name="serializer"></param>
        /// <returns></returns>
        public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
        {
            object result = null;
            try
            {
                result = isoDateTimeConverterRaad.ReadJson(reader, objectType, existingValue, serializer);
            }
            catch (Exception)
            {
            }
            return result;
        }
        /// <summary>
        /// 重写输出时的时间格式
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="value"></param>
        /// <param name="serializer"></param>
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            isoDateTimeConverterWrite.WriteJson(writer, value, serializer);
        }
    }


3.Linux - Docker环境中的时区问题

在部分系统中,应用内部DateTime.Now会获取到UTC时间,在部署到Docker时,需要设置手动显式设置时区

这里可以自己拷一下locltime然run的时候做映射

-v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro


4.解决URL中的双斜杠问题

Core 3.1不支持多余的斜杠

这边的项目很多其他项目调用,有不确定的多斜杠问题,所以目前解决方式是加管道,移除请求进来时多余的斜杠

需要注意管道添加的位置,最好在进入时处理(放到Configure第一行)

/// <summary>
/// 重写url规则
/// </summary>
public class RewriteRouteRule
{
    /// <summary>
    /// 处理url中的多斜杠("//")问题
    /// </summary>
    /// <param name="context"></param>
    public static void ReWriteRequests(RewriteContext context)
    {
        var request = context.HttpContext.Request;
        if (request.Path.Value.Contains("//"))
        {
            string[] splitlist = request.Path.Value.Split("/", StringSplitOptions.RemoveEmptyEntries);
            var newpath = "/" + string.Join("/", splitlist);
            request.Path = newpath;
        }
    }
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRewriter(new RewriteOptions().Add(RewriteRouteRule.ReWriteRequests));
}

5.System.Web.Security哈希

Core没有System.Web.Security,自己实现差不多得功能就行

Encoding.UTF8.GetBytes(System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sKey, "SHA1").Substring(0, 8));

换成

Encoding.UTF8.GetBytes(SHA(sKey).Substring(0, 8));

<details>

<summary>EncryptHelper.cs</summary>

```C#

/// <summary>

/// 摘要数据

/// </summary>

/// <param name="str">摘要数据</param>

/// <param name="type"></param>

/// <returns></returns>

public static string EncryptHash(string str, string type)

{

byte[] hashBytes;

switch (type)

{

case "SHA1":

using (var sha = SHA1.Create())

hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(str)); break;

case "MD5":

using (var md5 = MD5.Create())

hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(str)); break;

default: return "";

}

var hashStr = BitConverter.ToString(hashBytes);

return hashStr.Replace("-", "").ToUpper();

}

/// <summary>

/// 加密数据

/// </summary>

/// <param name="encryptStr">加密字符</param>

/// <returns></returns>

public static string Encrypt(string encryptStr)

{

return Encrypt(encryptStr, "EncryptKey");

}

/// <summary>

/// 加密数据

/// </summary>

/// <param name="EncryptStr">加密数据</param>

/// <param name="sKey">自定义密匙</param>

/// <returns></returns>

public static string Encrypt(string encryptStr, string encryptKey)

{

try

{

DESCryptoServiceProvider des = new DESCryptoServiceProvider();

byte[] inputByteArray;

inputByteArray = Encoding.Default.GetBytes(encryptStr);

des.Key = Encoding.UTF8.GetBytes(EncryptHash(encryptKey, "SHA1").Substring(0, 8));

des.IV = Encoding.UTF8.GetBytes(EncryptHash(encryptKey, "SHA1").Substring(0, 8));

System.IO.MemoryStream ms = new System.IO.MemoryStream();

CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);

cs.Write(inputByteArray, 0, inputByteArray.Length);

cs.FlushFinalBlock();

StringBuilder ret = new StringBuilder();

foreach (byte b in ms.ToArray())

ret.AppendFormat("{0:X2}", b);

return ret.ToString();

}

catch (Exception)

{

return "加密数据出错,请检查加密数据";

}

}

public static string Decrypt(string decryptStr)

{

return Decrypt(decryptStr, "EncryptKey");

}

public static string Decrypt(string decryptStr, string encryptKey)

{

try

{

DESCryptoServiceProvider des = new DESCryptoServiceProvider();

int len;

len = decryptStr.Length / 2;

byte[] inputByteArray = new byte[len];

int x, i;

for (x = 0; x < len; x++)

{

i = Convert.ToInt32(decryptStr.Substring(x * 2, 2), 16);

inputByteArray[x] = (byte)i;

}

des.Key = Encoding.UTF8.GetBytes(EncryptHash(encryptKey, "SHA1").Substring(0, 8));

des.IV = Encoding.UTF8.GetBytes(EncryptHash(encryptKey, "SHA1").Substring(0, 8));

System.IO.MemoryStream ms = new System.IO.MemoryStream();

CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write);

cs.Write(inputByteArray, 0, inputByteArray.Length);

cs.FlushFinalBlock();

return Encoding.Default.GetString(ms.ToArray());

}

catch (Exception)

{

return "解密数据出错,请检查密匙.";

}

}

```

</details>


6.设置统一的Controller

Core对控制器的使用跟FW有些不同,需要对控制器添加 [ApiController] 标签,不然打开后,Swagger会出现下面异常

Action require a unique method/path ...

所以推荐用一BaseControllerClass去做兜底,而其他控制器全部继承它

一来统一了控制器的父类,好做后续子的调整,二来避免改漏

```C#

[ApiController]

[Route("api/[controller]/[action]")]

public class BaseController : ControllerBase{}

```


7.ModelState和部分项目接口返回400的问题

对于使用了 ModelState 需要注意在Core中自带就有一套验证,但我们已经有自己的一套验证,所以需要禁用了MVC自带的验证返回

```C#

services.Configure<ApiBehaviorOptions>((o) =>

{

o.SuppressModelStateInvalidFilter = true;

});

```


8.使用环境变量控制 非开发环境 不开启Swagger

原因如题,统一ConfigureUseSwaggerConfigureServicesAddSwaggerGen加上以下统一的开关

if (Environment.GetEnvironmentVariable("Key") != "online")

docker run -e Key="online"

这里使用了环境变量Environment.GetEnvironmentVariable("Key"))

这样使用的话,可以在docker执行run的时候传入Key参数来控制运行的环境


9.Linux环境DateTime.ToString()格式不对

对于Core的时间格式,是获取系统的设置进行格式化的

在一些环境下,时间格式跟win不一样,比如Liunx执DataTime.Now.ToString()出来的就可能5/20/2022 15:00

有些时候我们不能使用这种格式,那么就可以调整这个默认的格式

这里需要注意下,ToString的格式ShortDatePattern+LongTimePattern不要调整错参数了,不然就没效果了

Main加上以下配置即可

该配置是整个程序全局配置,不一定需要放到Main,放到Main只是为了第一时间生效

```C#

CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("zh-CN", true)

{

DateTimeFormat = {

ShortTimePattern = "H:mm:ss",

ShortDatePattern = "yyyy/M/d",

FullDateTimePattern = "yyyy/M/d HH:mm:ss",

LongDatePattern = "yyyy/M/d",

LongTimePattern = "HH:mm:ss"

}

};

```


C# List分页 2022-04-22
实现较简单的OrderBy使用方式,适配使用起来表达式比较复杂的ORM 2022-08-11