分类 软件开发 下的文章

@match 的匹配规则是由 Chrome 浏览器 规定的,而不是油猴(Tampermonkey)自己定义的。油猴作为浏览器扩展,遵循了 Chrome 的 URL 匹配模式。这种匹配规则最初来源于 Chrome 扩展的 manifest.json 中的 matches 字段。

因此,@match 的语法和行为与 Chrome 扩展中的 URL 匹配规则完全一致。

https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns

public static async Task<string> HttpGetAsync(string baseUrl, string entityName, string entityNameSuffixQueryParamer)
{
    var requestUrl = $@"{baseUrl}{entityName}{entityNameSuffixQueryParamer}";
    var headerRetryAfter = 0;// header中包含的RetryAfter值

    // 配置重试机制
    var retryPolicy = Policy
        .Handle<Exception>()
        .WaitAndRetry(
            // 重试次数
            retryCount: 3,
            // 确定每次重试尝试需要等待的时长
            sleepDurationProvider: (retryAttempt) =>
            {
                if (headerRetryAfter == 0)
                {
                    // 重试间隔时间(秒) => 3^次数
                    return TimeSpan.FromSeconds(Math.Pow(3, retryAttempt));
                }
                else
                {
                    var retryAfter = TimeSpan.FromSeconds(headerRetryAfter);
                    headerRetryAfter = 0;
                    return retryAfter;
                }
            },
            // 每次重试时要执行的操作或函数
            onRetry: (exception, timeSpan, retryCount, context) =>
            {
                Console.WriteLine($"请求D365失败,第{retryCount}次重试,延迟{timeSpan.TotalSeconds}秒");
            }
        );

    return await retryPolicy.Execute(async () =>
    {
        // 获取授权信息
        var token = await GetToken();

        // 设置授权信息
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);

        // 发起Get请求
        var httpResponseMessage = await httpClient.GetAsync(requestUrl);

        // 读取响应正文
        var responseBody = await httpResponseMessage.Content.ReadAsStringAsync();

        // 200 - 299
        if (!httpResponseMessage.IsSuccessStatusCode)
        {
            // 看是否存在Retry-After的header(429 请求过多)
            // See https://learn.microsoft.com/power-apps/developer/data-platform/api-limits#retry-operations
            if (httpResponseMessage.Headers.Contains("Retry-After"))
            {
                headerRetryAfter = int.Parse(httpResponseMessage.Headers.GetValues("Retry-After").FirstOrDefault());
            }

            var exceptionObj = new
            {
                Msg = "请求D365失败",
                ChinaTime = DateTime.Now.FormatChinaTime(),
                RequestUrl = requestUrl,
                RequestToken = token,
                HttpResponseStatusCode = (int)httpResponseMessage.StatusCode,
                HttpResponseStatusCodeName = httpResponseMessage.StatusCode.ToString(),
                HttpResponseHeader = JsonConvert.SerializeObject(httpResponseMessage.Headers),
                HttpResponseBody = responseBody,
            };
            var exceptionString = JsonConvert.SerializeObject(exceptionObj, Formatting.Indented);
            throw new Exception(exceptionString);
        }

        // 记录日志
        var reqId = httpResponseMessage.Headers.GetValues("REQ_ID").FirstOrDefault();
        var logString = $@"EntityName: {entityName}, _REQ_ID: {reqId}";
        _logger.LogInformation(logString);

        // 返回响应正文
        return responseBody;
    });
}

.net sdk style 转换工具

try-convert
官网:https://github.com/dotnet/try-convert

在此将其作为全局工具安装:

dotnet tool install -g try-convert

如果您已经安装,请确保更新:

dotnet tool update -g try-convert

如果您再次使用该工具,请确保您已获得最新版本:https://github.com/dotnet/try-convert/releases

如果只想转换特定的子文件夹、解决方案或项目,请输入:

try-convert -w path-to-folder-or-solution-or-project

使用参数:

选项:
  -?, -h, --help                                 显示帮助和使用信息
  -p, --project <project>                        要转换的项目的路径 [默认: ]
  -w, --workspace <workspace>                    要操作的解决方案或项目文件。如果未指定项目,则命令将在当前目录中搜索一个。 [默认: ]
  -m, --msbuild-path <msbuild-path>              MSBuild.exe 的路径,如果您更喜欢使用该工具 [默认: ]
  -tfm, --target-framework <target-framework>    您要升级到的框架的名称。如果未指定,默认应用程序的 TFM 将为您的机器上找到的最高可用版本,库的默认 TFM 将为 .NET Standard 2.0。 [默认: ]
  --force-web-conversion                         尝试转换 MVC 和 WebAPI 项目,即使在迁移此类项目后需要进行大量手动工作。 [默认: False]
  --preview                                      使用预览 SDK 作为转换的一部分 [默认: False]
  --diff-only                                    生成要转换的项目的差异;不执行任何转换操作 [默认: False]
  --no-backup                                    转换项目,不创建原始文件的备份并移除 packages.config 文件。 [默认: False]
  --keep-current-tfms                            转换项目文件但不更改任何 TFMs。如果未指定,TFMs 可能会更改。 [默认: False]
  --maui-conversion                              尝试将 Xamarin.Forms 项目转换为 .NET MAUI 项目。在迁移此类项目后可能需要进行额外的手动工作。 [默认: False]
  -u, --update                                   更新 try-convert 工具到最新可用版本 [默认: False]

如果转换web项目失败,可以添加--force-web-conversion参数进行强制转换

示例

try-convert -w "ASP.NET Web App 单页应用程序" --force-web-conversion

public static Mock<ILogger> GetILogger()
{
    // 创建一个模拟的 ILogger 实例
    var loggerMock = new Mock<Microsoft.Extensions.Logging.ILogger>();
    // 配置模拟对象,在调用 Log 方法时打印日志消息到控制台
    loggerMock.Setup(x => x.Log(
        It.IsAny<LogLevel>(),
        It.IsAny<EventId>(),
        It.IsAny<object>(),
        It.IsAny<Exception>(),
        It.IsAny<Func<object, Exception, string>>()
    )).Callback<LogLevel, EventId, object, Exception, Func<object, Exception, string>>((logLevel, eventId, state, exception, formatter) =>
    {
        Console.WriteLine($"[{logLevel}] - {formatter(state, exception)}");
    });

    return loggerMock;
}

/// <summary>
/// 将DateTime转换为UTC时间字符串
/// </summary>
/// <param name="dateTime"></param>
/// <param name="ignoreTimeZone">
///     默认忽略时区影响,仅将DateTime中的"年月日时分秒"拼接为对应的Utc字符串;<br/>
///     如果考虑时区,会将DateTime转换为Utc时间后再转换为对应Utc字符串
/// </param>
/// <returns></returns>
public static string ToUtcTimeString(this DateTime dateTime, bool ignoreTimeZone = true)
{
    if (!ignoreTimeZone)
    {
        return dateTime.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ");
    }

    return dateTime.ToString("yyyy-MM-ddTHH:mm:ssZ");
}

本文中的时区通过TimeZoneInfo.FindSystemTimeZoneById获取的,其中的参数Id是windows下特有的,因此其Id在不同的操作系统下不一定存在,为标准起见可采用IANA时区库,可以使用三方库NodaTime来处理时间,本文暂不对NodaTime讲解。


  • DateTime 不储存时区信息
  • DateTimeOffset 可以储存时区信息

代码:

/// <summary>
/// 转换字符串为DateTime
/// </summary>
/// <param name="sourceTimeString">
///     源时间字符串
/// </param>
/// <param name="sourceFormat">
///     源时间的字符串格式<br/>
///     1. 例如: "yyyyMMddHHmmss","yyyy-MM-dd HH:mm:ss","yyyy-MM-ddTHH:mm:ssZ"<br/>
///     2. 本参数仅用于解析源时间时指定的字符串格式,不会对字符串中是否存在时区信息进行处理和转换<br/>
/// </param>
/// <param name="sourceIsUtc">
///     源时间是否UTC时间
/// </param>
/// <param name="sourceTimeZoneInfoId">
///     时区信息ID<br/>
///     1. 如果sourceIsUtc为true,该参数可以为空<br/>
///     2. 可通过<seealso cref="GetAllTimeZoneId"/>获取所有时区查看需要的时区ID<br/>
///     3. 中国时区ID为: "China Standard Time"
/// </param>
/// <param name="isToUtc">
///     是否转换为UTC时间<br/>
///     1. 如果为false,则转换为本地时间(本地时间取决于操作系统时区)<br/>
///     2. 如果需要转换为其他时区的时间,请使"isToUtc"为"true"后得到UTC时间再自行进行转换
/// </param>
/// <returns></returns>
public static DateTime? FormatDate(
    this string sourceTimeString,
    string sourceFormat,
    bool sourceIsUtc,
    string sourceTimeZoneInfoId,
    bool isToUtc
    )
{
    // 将指定时间转换为无时区的时间
    var sourceProvider = CultureInfo.InvariantCulture;// 不指定区域文化
    var srouceStyle = DateTimeStyles.None;// 不指定时区 System.DateTimeKind.Unspecified

    // 这个时间是默认建立在操作系统所在地区的时区上,因此不能直接使用,需要进一步转换为带时区和偏移的时间
    var sourceLocalTime = DateTime.ParseExact(sourceTimeString, sourceFormat, sourceProvider, srouceStyle);

    // 获取源时间对应的国家时区信息,以便找到时间偏移量
    var sourceTimeZoneInfo = GetTimeZoneOffsetHours(sourceIsUtc, sourceTimeZoneInfoId);
    var sourceOffsetHours = sourceTimeZoneInfo.BaseUtcOffset.Hours;

    // 将无时区时间转换为带时区的时间
    var dateTimeOffset = new DateTimeOffset(sourceLocalTime, TimeSpan.FromHours(sourceOffsetHours));

    if (isToUtc)
    {
        return dateTimeOffset.UtcDateTime;
    }
    else
    {
        return dateTimeOffset.LocalDateTime;
    }
}

/// <summary>
/// 根据时区ID获取时区信息
/// </summary>
/// <param name="isUtc"></param>
/// <param name="timeZoneInfoId"></param>
/// <returns></returns>
private static TimeZoneInfo GetTimeZoneOffsetHours(bool isUtc, string timeZoneInfoId = "China Standard Time")
{
    if (isUtc)
    {
        return TimeZoneInfo.Utc;
    }
    else
    {
        return TimeZoneInfo.FindSystemTimeZoneById(timeZoneInfoId);
    }
}

/// <summary>
/// 获取系统中所有可用的时区信息
/// </summary>
/// <returns></returns>
public static List<Tuple<int, string>> GetAllTimeZoneId()
{
    // 获取系统中所有可用的时区信息
    var timeZones = TimeZoneInfo.GetSystemTimeZones();
    return timeZones
        .Select(t => new Tuple<int, string>(t.BaseUtcOffset.Hours, t.Id))
        .OrderBy(t => t.Item1)
        .ThenBy(t => t.Item2)
        .ToList();
}

单元测试:

{
    // 获取当前系统所在时区的时间
    var dateTime = DateTime.Now;
    // 转换为日本时间
    TimeZoneInfo tokyoTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
    DateTime tokyoTime = TimeZoneInfo.ConvertTime(dateTime, tokyoTimeZone);
}

{
    var timeZoneIds = StringExtension.GetAllTimeZoneId();

    // 假设日本时间为 2024-08-16 21:00:00,对应utc时间为 12:00,对应北京时间(本机)为 20:00
    var inputTimeString = "20240816210000";
    string sourceFormat = "yyyyMMddHHmmss";
    bool sourceIsUtc = false;
    string sourceTimeZoneInfoId = "Tokyo Standard Time";
    bool isToUtc = true;
    var outDateTime = inputTimeString.FormatDate(sourceFormat, sourceIsUtc, sourceTimeZoneInfoId, isToUtc);

    var beijingTime = DateTime.Parse("2024-08-16 20:00:00");
    Assert.AreEqual(
        outDateTime,
        beijingTime.ToUniversalTime()
    );
}

命名空间和类名一样,如何指定使用的类

给对应的程序集设置别名,或者通过右键属性指定别名

<Project Sdk="Microsoft.NET.Sdk">
    <ItemGroup>
        <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.0">
            <Aliases>MsLog</Aliases>
        </PackageReference>
    </ItemGroup>
</Project>

使用

extern alias MsLog; // 指定要使用的别名
using ILogger = MsLog::Microsoft.Extensions.Logging.ILogger; // 引入别名中的class
using LoggerExtensions = MsLog::Microsoft.Extensions.Logging.LoggerExtensions;

namespace xxxxx.xxxxxx.Tests
{
    [TestClass()]
    public class CreateTests
    {
        [TestMethod()]
        public async Task RunTestAsync()
        {
            // 使用对应的class
            LoggerExtensions.LogInformation("")
            Assert.Fail();
        }
    }
}

AutoMapper 依赖注入 Profile

安装 AutoMapper.Extensions.Microsoft.DependencyInjection 包

依赖注入

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
public class Startup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        // AutoMapper
        builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly(), typeof([其他需要反射的程序集的某一个class引用]).Assembly);
    }
}
using AutoMapper;
public class XXXXXXXServiceBus{
    private readonly IMapper mapper;
    public XXXXXXXServiceBus(IMapper _mapper)
    {
        mapper = _mapper;
    }
}

配置Profile

using AutoMapper;
public class AccountMappingProfile : Profile
{
    public AccountMappingProfile()
    {
        CreateMap<AccountTable, MqAccountModel>();
    }
}

使用

var mqAccountModel = mapper.Map<MqAccountModel>(accountTable);

参考:

关于Azure Function找不到dll程序集的问题

大致现象

静态编译没问题,运行报错内容如下:

Could not load file or assembly 'Microsoft.xxxx.xxxx.xxxx, Version=6.15.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. The system cannot find the file specified.

或:

无法加载文件或程序集 'Microsoft.xxxx.xxxx.xxxx, Version=6.15.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'。系统找不到指定的文件。

- 阅读剩余部分 -

错误信息

System.ServiceModel.Security.MessageSecurityException: 从另一方收到未进行安全处理或安全处理不正确的错误。有关错误代码和详细信息,请参见内部 FaultException。 
---> System.ServiceModel.FaultException: An error occurred when verifying security for the message. 验证报文安全性时发生错误。

Invalid Login Information : 
从另一方收到未进行安全处理或安全处理不正确的错误。有关错误代码和详细信息,请参见内部 FaultException。 
=> An error occurred when verifying security for the message.
Unable to Login to Dynamics CRM
Unable to Login to Dynamics CRM************ NullReferenceException - WhoAmI : Execute (WhoAmI) request to CRM from IOrganizationService |
=> 未将对象引用设置到对象的实例。
未将对象引用设置到对象的实例。
[TerminalFailure] Failed to Execute Command - WhoAmI : 
RequestID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx : Execute (WhoAmI) request to CRM from IOrganizationService duration=00:00:00.0032364 ExceptionMessage = 未将对象引用设置到对象的实例。
未将对象引用设置到对象的实例。

Error    : OrganizationWebProxyClient is null

- 阅读剩余部分 -