分类 C Sharp 下的文章

C# HttpClient PostAsync 卡死/假死/死锁 详解

注意:为了避免阅读本文产生误解,本文在撰写过程中,部分使用了PostAsync,部分使用了GetAsync,均视为一个表述

例子

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Method().GetAwaiter().GetResult();// 这里为同步等待
            button1.Text = "End";
        }

        private async Task<string> Method()
        {
            HttpClient client = new HttpClient();

            // 发送 GET 请求
            HttpResponseMessage response = await client.PostAsync("https://baidu.com/");

            // 读取响应内容
            string responseBody = await response.Content.ReadAsStringAsync();

            return responseBody;
        }
    }

产生的场景

在Winform或Asp.net这类使用主线程的应用程序中,并且在主线程使用了同步等待(除了await Method()这类方式以外都是同步等待)的情况下,子方法调用HttpClientawait PostAsync方法时会导致死锁。(控制台程序没这个问题,控制台是单线程)

- 阅读剩余部分 -

方式一

// 使用计算器来设置每次同时运行的任务数量
var semaphore = new SemaphoreSlim(TaskNumber);
var tasks = new Task[projects.Count];
for (int i = 0; i < projects.Count; i++)
{
    var project = projects[i];

    // 在每个任务之前,先等待信号量授予
    await semaphore.WaitAsync();

    tasks[i] = new Task(async () =>
    {
        try
        {
            _logger.LogInformation($"开始执行:第{i + 1}个,总共:{projects.Count}个,当前ProjectId为:{project.Id}");
            await ReSaveAllProjectTask(project, i, projects.Count);
        }
        catch (Exception e)
        {
            errorProjectIds.Add(project.Id);
        }
        finally
        {
            // 无论任务是否成功完成,都要释放信号量
            semaphore.Release();
        }
    });
    tasks[i].Start();
}
// 等待所有任务完成
Task.WaitAll(tasks);

方式二

ParallelOptions parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = TaskNumber };
await Parallel.ForEachAsync(projects, parallelOptions, async (project, cancel) =>
{
    try
    {
        var i = projects.IndexOf(project);
        _logger.LogInformation($"开始执行:第{i + 1}个,总共:{projects.Count}个,当前ProjectId为:{project.Id}");
        await ReSaveAllProjectTask(project, i, projects.Count);
    }
    catch (Exception)
    {
        errorProjectIds.Add(project.Id);
    }
});

后端代码:

using System.Diagnostics.CodeAnalysis;
using TB.ComponentModel;// 需要安装依赖 UniversalTypeConverter

[HttpPost]
public async Task<List<DFileInfoDto>> BatchUploadFileAsync(IFormCollection Form)
{
    var tokenDTO = await TokenHelper.GetCurrentTokenAsync(HttpContext);

    if (tokenDTO == null)
    {
        throw new CustomException(ErrCodes.DocTokenGetDataFail, "从Token中获取用户信息失败");
    }

    // Request.Form or Form
    var files = Form.Files;
    var list2 = IFormCollectionToGeneric<DFileInfoDto>(Form);

    var res = new List<DFileInfoDto>();
    for (int i = 0; i < files.Count; i++)
    {
        list2[i].FromFile = files[i];

        var fileInfoDto = await _document.UploadFileAsync(list2[i], tokenDTO);
        res.Add(fileInfoDto);
    }

    return res;
}


public static List<T> IFormCollectionToGeneric<T>(IFormCollection formCollection) where T : class, new()
{
    var properties = typeof(T).GetProperties();
    var propertiesNames = properties.Select(p => p.Name).ToList();
    var ignoreCaseComparer = new IgnoreCaseComparer();
    var keys = formCollection.Keys.Where(key => propertiesNames.Contains(key, ignoreCaseComparer)).ToList();
    var formCount = formCollection[keys.First()].Count;

    var list = new List<T>();
    for (int i = 0; i < formCount; i++)
    {
        var instance = Activator.CreateInstance<T>();
        foreach (var property in properties)
        {
            if (formCollection.TryGetValue(property.Name, out var stringValues))
            {
                var value = property.PropertyType.IsValueType ? Activator.CreateInstance(property.PropertyType) : null;
                if (stringValues[i].IsConvertibleTo(property.PropertyType))
                {
                    value = stringValues[i].To(property.PropertyType);
                }
                property.SetValue(instance, value);
            }
        }
        list.Add(instance);
    }
    return list;
}

public class IgnoreCaseComparer : IEqualityComparer<string>
{
    public bool Equals(string? x, string? y)
    {
        return x.Equals(y, StringComparison.OrdinalIgnoreCase);
    }

    public int GetHashCode([DisallowNull] string obj)
    {
        return obj.ToUpper().GetHashCode();
    }
}

前端代码:
前端代码不完整,仅供参考,其原理为:将需要请求的对象数组,全部转换为FormData对象,因FormData对象没有对象数组的概念,所以FormData对象中的key是可重复的,在后端同个key可以接收到多个value

  const form = new FormData();
  dto.data!.forEach((t) => {
    const keys = Object.keys(t);
    keys.forEach((k) => {
      form.append(k, t[k]);
    });
  });
  dto.data = form;
  
  
  
  function PostApi<T = any>(url, data, headers?: any): Promise<ApiResultDTO<T>> {
  url = FormatUrl(url);
  let config: ConfigType = {
    headers: {},
    method: "POST",
    body: data != null ? JSON.stringify(data) : null,
  };
  if (data) {
    if (data instanceof FormData) {
      config.body = data;
    } else if (headers && headers["Content-Type"] === "multipart/form-data") {
      config.body = data;
    } else {
      config.body = JSON.stringify(data);
      config.headers["Content-Type"] = "application/json";
    }
  } else {
    config.body = null;
  }

  if (headers) {
    Object.assign(config.headers, headers);
  }
  return new Promise((resolve, reject) => {
    setLoading(true);
    fetch(url, config).then((res) => handleJsonResult(resolve, reject, res));
  });
}




Swagger UI 5.0.0-rc5 does not send authorization header to solve
Swagger UI 5.0.0-rc5 未发送授权标头 解决

方式一:

services.AddSwaggerGen(opt =>
{
    opt.SwaggerDoc("v1", new OpenApiInfo { Title = "My Api", Version = "v1" });
    opt.AddSecurityDefinition("bearer", new OpenApiSecurityScheme
    {
        Type = SecuritySchemeType.Http,
        BearerFormat = "JWT",
        In = ParameterLocation.Header,
        Scheme = "bearer"
    });
    opt.OperationFilter<AuthenticationRequirementsOperationFilter>();
});

public class AuthenticationRequirementsOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        if (operation.Security == null)
            operation.Security = new List<OpenApiSecurityRequirement>();


        var scheme = new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "bearer" } };
        operation.Security.Add(new OpenApiSecurityRequirement
        {
            [scheme] = new List<string>()
        });
    }
}

方式二:

 c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
            {
                Description = "JWT Authorization header using the Bearer scheme.",
                Name = "Authorization",
                In = ParameterLocation.Header,
                Type = SecuritySchemeType.ApiKey,
                Scheme = "bearer"
            });

            c.AddSecurityRequirement(new OpenApiSecurityRequirement
            {
                {
                    new OpenApiSecurityScheme
                    {
                        Reference = new OpenApiReference
                        {
                            Type = ReferenceType.SecurityScheme,
                            Id = "Bearer"
                        }
                    }, new List<string>()
                }
            });

方式三:

              options.AddSecurityDefinition(definition, new OpenApiSecurityScheme
               {
                   Description = $"desc",
                   Name = "Authorization",
                   In = ParameterLocation.Header,
                   Type = SecuritySchemeType.ApiKey,
                   Scheme = "bearer"
               });

               options.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer"
                            }
                        }, new List<string>()
                    }
                });

参考

https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1425#issuecomment-571926368
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1425#issuecomment-606508464
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1425#issuecomment-1014403184

现象

项目引用了,using也写了,也没写错,都是直接复制粘贴的,偏偏报错说“命名空间xxx中不存在类型或命名空间名xxx”,百思不得其解.......

原因

本来打算放弃了,后面找到原因了:因为整个解决方案总有多个项目,有两个名称类似的项目,仔细看了名称没问题,问题在两个项目的程序集名称是一样的,所以使用using时会导致“命名空间xxx中不存在类型或命名空间名xxx”的错误出现。

解决

  • 查看“程序集名称”是否正确
  • 通常“程序集名称”和“默认命名空间”是一致的

步骤(二选一)

  • 可通过右键项目,选择“属性”进行修改
  • 可通过右键项目,选择“编辑项目文件”,在PropertyGroup节点中,修改AssemblyName的值为RootNamespace的值一致即可

后续处理步骤(必选)

  1. 清理解决方案
  2. 先单独生成修改后的那个项目
  3. 在主项目中删除对该项目的引用
  4. 在主项目中添加对该项目的引用

至此问题就解决了。

linq 从字符串转换日期和/或时间时,转换失败

一、测试

在执行一句简单的 efcore linq 查询时,如下:

    var q = from a in _db.TeamPlans
            where a.FromDate >= DateTime.Parse("2021-6-9 11:10:42")
            select a;

    var list = q.ToList();

Microsoft.Data.SqlClient.SqlException:“从字符串转换日期和/或时间时,转换失败。”(一脸黑头问号)

这不是最常见的写法吗?为什么会报错呢?
于是我换成了如下方式

    var q = from a in _db.TeamPlans
            select a;
    q = q.Where(a => a.FromDate >= DateTime.Parse("2021-6-9 11:10:42"));
    var list = q.ToList();

此时却没有问题,可以正常运行,这是为什么呢?

二、分析

仔细看上面的异常为:Microsoft.Data.SqlClient.SqlException,其中SqlClient格外显眼,说明这个异常时数据库抛出来的,于是咱们进行下面的分析

查看通过linq to sql生成的sql语句如下:

SELECT // 省略字段
FROM [TeamPlans] AS [t]
WHERE [t].[FromDate] >= '2021-06-09T11:10:42.0000000'

然后再查看上面第二种查询方式生成的sql如下:

SELECT // 省略字段
FROM [TeamPlans] AS [t]
WHERE [t].[FromDate] >= '2021-06-09T11:10:42.000'

仔细看最后的时间,好家伙,第一种方式生成sql语句精度远远大于sqlserver能识别的范围

三、解决

那么知道问题的原因出在“CLR和SQLServer数据类型精度不一致”的问题上,那么我们得找一个可以映射CLR和SQLServer数据类型的方法对吧
于是想到了ColumnAttribute特性,该特性中恰好有个TypeName属性,那么我们试试看。

在需要处理的DateTime类型的字段上添加一个Column特性即可解决该问题,
添加Column特性后,生成的sql中会自动降低该DateTime类型的精度

    [Column(TypeName = "datetime")]
    public DateTime FromDate { get; set; }

这些常见的特性都在System.ComponentModel.DataAnnotations.Schema命名空间下

参考
EF Core 查看linq to sql生成的sql
Linq to SQL 中datetime 和string转换

.Net 反射 方法 用于调用泛型方法

核心代码:

  1. 通过Type下的GetMethod获取对应的方法信息
  2. 在通过MethodInfo下的MakeGenericMethod设置泛型参数
  3. 最后通过Invoke进行触发

注意:被反射的方法需要为public访问权限

        MethodInfo SetMethod = typeof(DbContext).GetMethod(nameof(DbContext.Set));
        MethodInfo makeGenericMethod = SetMethod.MakeGenericMethod(entityType);
        return (IQueryable)makeGenericMethod.Invoke(context, null);

例一:

反射DbContext类中的DbContext.Set方法

public static partial class CustomExtensions
{
    /// <summary>
    /// 通过指定实体类型名反射获取 IQueryable
    /// </summary>
    /// <param name="context"></param>
    /// <param name="entityName"></param>
    /// <returns></returns>
    public static IQueryable GetDbSet(this DbContext context, string entityName)
    {
        IEntityType findEntityType = context.Model.FindEntityType(entityName);
        return context.GetDbSet(findEntityType.ClrType);
    }
    /// <summary>
    /// 通过指定实体类型反射获取 IQueryable
    /// </summary>
    /// <param name="context"></param>
    /// <param name="entityType"></param>
    /// <returns></returns>
    public static IQueryable GetDbSet(this DbContext context, Type entityType)
    {
        MethodInfo SetMethod = typeof(DbContext).GetMethod(nameof(DbContext.Set));
        MethodInfo makeGenericMethod = SetMethod.MakeGenericMethod(entityType);
        return (IQueryable)makeGenericMethod.Invoke(context, null);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="Type"></typeparam>
    /// <param name="context"></param>
    /// <param name="entityType"></param>
    /// <returns></returns>
    public static IQueryable<T> GetDbSet<T>(this DbContext context, Type entityType)
    {
        MethodInfo SetMethod = typeof(DbContext).GetMethod(nameof(DbContext.Set));
        MethodInfo makeGenericMethod = SetMethod.MakeGenericMethod(entityType);
        return (IQueryable<T>)makeGenericMethod.Invoke(context, null);
    }
}

例二:

反射DbHelper类中的CheckDataBuild方法

    /// <summary>
    /// 校验标识了CheckDataAttribute的字段,所对应的外键数据是否存在
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="item"></param>
    /// <param name="errorMsgList"></param>
    /// <param name="db"></param>
    /// <returns></returns>
    public static bool CheckData<T>(T item, List<string> errorMsgList, DatabaseContext db) where T : class
    {
        var t = typeof(T);
        var props = t.GetProperties();
        var success = true;
        var methodInfo = typeof(DbHelper).GetMethod(nameof(CheckDataBuild));

        foreach (var prop in props)
        {
            var attr = prop.GetCustomAttributes().OfType<CheckDataAttribute>().FirstOrDefault();
            if (attr != null)
            {
                var val = prop.GetValue(item);

                MethodInfo mi = methodInfo.MakeGenericMethod(attr.EntityType);
                success = (bool)mi.Invoke(null, new object[] { errorMsgList, db, success, prop, attr, val });
            }
        }
        return success;
    }
    /// <summary>
    /// 数据校验
    /// </summary>
    /// <typeparam name="T2"></typeparam>
    /// <param name="errorMsgList">错误信息</param>
    /// <param name="db">db对象</param>
    /// <param name="success">校验结果</param>
    /// <param name="prop">属性信息</param>
    /// <param name="attr">校验的特性</param>
    /// <param name="val">值</param>
    /// <returns></returns>
    public static bool CheckDataBuild<T2>(List<string> errorMsgList, DatabaseContext db, bool success, PropertyInfo prop, CheckDataAttribute attr, object val) where T2 : class
    {
        var table = db.GetDbSet<T2>(attr.EntityType);
        var existedItem = table.QueryByField(attr.FieldName, val, attr.EntityType);

        if (existedItem == null)
        {
            success = false;
            errorMsgList.Add($"Value {val} for filed {prop.Name} not available");
        }
        return success;
    }

参考

C#里Type 如何转换成泛型T? - 知乎
C# Type传参转换成泛型T_雷小虎的专栏-CSDN博客

C#实体对象序列化成Json,并让字段的首字母小写
C# api 字段 小写

引言

最近在工作中遇到与某些API对接的post的数据需要将对象的字段首字母小写。
解决办法有两种

第一种

使用对象的字段属性设置JsonProperty来实现(不推荐,因为需要手动的修改每个字段的属性)

public class UserInfo
{
    [JsonProperty("id")]
    public int Id{ set; get; } 
    
    [JsonProperty("userName")] 
    public string UserName{ set; get; } 
}

第二种(推荐)

使用newtonsoft.json来设置格式化的方式(推荐使用)

var user = new { Name = "john", Age = 19 }; 
var serializerSettings = new JsonSerializerSettings 
{ // 设置为驼峰命名 
    ContractResolver = new CamelCasePropertyNamesContractResolver() 
}; 
var userStr = JsonConvert.SerializeObject(user, Formatting.None, serializerSettings);

参考:
https://blog.csdn.net/weixin_33694172/article/details/85998910