using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.IO.Enumeration;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using AngleSharp.Dom;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;

namespace Dpz.Core.Infrastructure;

public static class DpzAppBuilderExtensions
{
    public static IApplicationBuilder Init(this IApplicationBuilder app)
    {
        BsonSerializer.RegisterSerializer(typeof(Date), new DateBsonSerializer());

        app.GetHostingEnvironment();
        var appBuilder = app.HandlerOtherStatusCode();
        return appBuilder;
    }

    /// <summary>
    /// 处理非200状态码
    /// </summary>
    /// <param name="app"></param>
    /// <returns></returns>
    public static IApplicationBuilder HandlerOtherStatusCode(this IApplicationBuilder app)
    {
        var appBuilder = app.UseStatusCodePages(async x =>
        {
            if (x.HttpContext.Response.StatusCode == 404)
            {
                var envService = app.ApplicationServices.GetService<IWebHostEnvironment>();
                x.HttpContext.Response.ContentType = "text/html";
                await x.HttpContext.Response.SendFileAsync(
                    Path.Combine(envService?.WebRootPath ?? "", "NotFound.html")
                );
            }
            else
            {
                x.HttpContext.Response.ContentType = "text/plain";

                await x.HttpContext.Response.WriteAsync(
                    "Status code page, status code: " + x.HttpContext.Response.StatusCode
                );
            }
        });
        //app.UseExceptionHandler(new ExceptionHandlerOptions
        //{
        //    ExceptionHandler = async x =>
        //    {
        //        await x.Response.WriteAsync("1");
        //    }
        //});
        return appBuilder;
    }

    //public static IServiceCollection AddPasswordAndCookiesGuides(this IServiceCollection services)
    //{
    //    services.Configure<IdentityOptions>(options =>
    //    {
    //        // 密码规则
    //        options.Password.RequireDigit = true;
    //        options.Password.RequireLowercase = true;
    //        options.Password.RequireNonAlphanumeric = true;
    //        options.Password.RequireUppercase = true;
    //        options.Password.RequiredLength = 6;
    //        options.Password.RequiredUniqueChars = 1;

    //        // 锁定设置
    //        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
    //        options.Lockout.MaxFailedAccessAttempts = 5;
    //        options.Lockout.AllowedForNewUsers = true;

    //        // 允许出现的字符
    //        options.User.AllowedUserNameCharacters =
    //            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
    //        options.User.RequireUniqueEmail = false;
    //    });

    //    services.ConfigureApplicationCookie(options =>
    //    {
    //        // Cookie 设置
    //        options.Cookie.HttpOnly = true;
    //        options.ExpireTimeSpan = TimeSpan.FromMinutes(5);

    //        options.LoginPath = "/Identity/Account/Login";
    //        options.AccessDeniedPath = "/Identity/Account/AccessDenied";
    //        options.SlidingExpiration = true;
    //    });
    //}

    /// <summary>
    /// 获取有关运行应用程序的Web托管环境的信息。
    /// </summary>
    /// <param name="app"></param>
    /// <returns></returns>
    public static void GetHostingEnvironment(this IApplicationBuilder app)
    {
        if (
            app.ApplicationServices.GetService(typeof(IWebHostEnvironment))
            is IWebHostEnvironment env
        )
        {
            EnvironmentInfo.ContentRootPath = env.ContentRootPath;
            EnvironmentInfo.EnvironmentName = env.EnvironmentName;
            EnvironmentInfo.WebRootPath = env.WebRootPath;
        }
    }

    /// <summary>
    /// 生成MD5
    /// </summary>
    /// <param name="str"></param>
    /// <returns></returns>
    public static string GenerateMd5(this string str)
    {
        var bytes = MD5.HashData(Encoding.Default.GetBytes(str));
        return BitConverter.ToString(bytes).Replace("-", "");
    }

    /// <summary>
    /// 生成MD5 小写
    /// </summary>
    /// <param name="str"></param>
    /// <returns></returns>
    public static string GenerateHashMd5(this string str)
    {
        var result = string.Join(
            "",
            from x in MD5.HashData(Encoding.Default.GetBytes(str))
            select x.ToString("x2")
        );
        return result;
    }

    /// <summary>
    /// 将当前时间转换为时间戳
    /// </summary>
    /// <param name="dateTime"></param>
    /// <returns></returns>
    public static long ToTimeStamp(this DateTime dateTime)
    {
        var timeSpan =
            dateTime.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
        var timeStamp = (long)timeSpan.TotalSeconds;
        return timeStamp;
    }

    /// <summary>
    /// 遍历集合
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source"></param>
    /// <param name="action"></param>
    public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
    {
        foreach (var item in source)
        {
            action(item);
        }
    }

    /// <summary>
    /// 获取集合的下标
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source"></param>
    /// <param name="item"></param>
    /// <returns></returns>
    public static int IndexOf<T>(this IEnumerable<T> source, T item)
    {
        var array = source.ToArray();
        return Array.IndexOf(array, item);
    }

    /// <summary>
    /// 将时间戳转换为DateTime
    /// </summary>
    /// <param name="timeStamp"></param>
    /// <returns></returns>
    public static DateTime ToDateTime(this long timeStamp)
    {
        var utcTime = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero).AddSeconds(timeStamp);
        return utcTime.LocalDateTime;
    }

    /// <summary>
    /// 从Html中获取所有图片
    /// </summary>
    /// <param name="images"></param>
    /// <returns></returns>
    public static List<string> ToImages(this IHtmlCollection<IElement> images)
    {
        return images
            .Select(x => x.Attributes["src"])
            .Where(x => x != null)
            .Select(x => x!.Value.Split('/'))
            .Where(x => x.Length > 0)
            .Select(x =>
            {
                ObjectId.TryParse(x.Last(), out var id);
                return id;
            })
            .Where(x => x != ObjectId.Empty)
            .Select(x => x.ToString())
            .ToList();
    }

    /// <summary>
    /// 从Html中获取所有图片
    /// </summary>
    /// <param name="images"></param>
    /// <returns></returns>
    public static List<ObjectId> ToObjectIdImages(this IHtmlCollection<IElement> images)
    {
        return images
            .Select(x => x.Attributes["src"])
            .Where(x => x != null)
            .Select(x => x!.Value.Split('/'))
            .Where(x => x.Length > 0)
            .Select(x =>
            {
                ObjectId.TryParse(x.Last(), out var id);
                return id;
            })
            .Where(x => x != ObjectId.Empty)
            .ToList();
    }

    /// <summary>
    /// 只替换第一次匹配的指定的字符串,
    /// </summary>
    /// <param name="str"></param>
    /// <param name="oldStr">需要替换的字符串</param>
    /// <param name="newStr">新字符</param>
    /// <returns></returns>
    public static string ReplaceOne(this string str, string oldStr, string newStr)
    {
        var index = str.IndexOf(oldStr, StringComparison.Ordinal);
        if (index >= 0)
            return string.Concat(str.AsSpan(0, index), newStr, str.AsSpan(index + oldStr.Length));
        return str;
    }

    public static bool WildCardMatch(this string wildCard, string value)
    {
        return wildCard.Equals(value, StringComparison.OrdinalIgnoreCase)
            || FileSystemName.MatchesSimpleExpression(wildCard, value);
        // var reg = "^" + Regex.Escape(wildCard).Replace("\\?", ".").Replace("\\*", ".*") + "$";
        // return Regex.IsMatch(value, reg);
    }

    public static string ToQueryString(this NameValueCollection? collection)
    {
        if (collection == null || collection.Count == 0)
        {
            return "";
        }

        var sb = new StringBuilder();
        var keys = collection.AllKeys;
        for (var i = 0; i < collection.Count; i++)
        {
            var key = keys[i];
            var values = collection.GetValues(key);
            if (values == null)
                continue;
            foreach (var value in values)
            {
                if (!string.IsNullOrEmpty(key))
                {
                    sb.Append(key).Append('=');
                }
                sb.Append(HttpUtility.UrlEncode(value)).Append('&');
            }
        }
        return sb.ToString(0, sb.Length - 1);
    }

    public static byte[] Base64StringToBytes(string base64String)
    {
        var buffer = new byte[
            (base64String.Length * 3 + 3) / 4
                - (
                    base64String.Length > 0 && base64String[^1] == '='
                        ? base64String.Length > 1 && base64String[^2] == '='
                            ? 2
                            : 1
                        : 0
                )
        ];
        return Convert.TryFromBase64String(base64String, buffer, out _) ? buffer : [];
    }
    
    public static string ToSha256Hash(string input)
    {
        var hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(input));
        return BitConverter.ToString(hashBytes).Replace("-", "");
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

下面对你贴出的 C# 源代码逐段做说明与要点提示,便于理解每个扩展方法的作用、行为以及需要注意的地方。

总体说明

  • 该文件定义了一组静态扩展方法(针对 IApplicationBuilder、string、IEnumerable、IHtmlCollection、NameValueCollection 等),位于 Dpz.Core.Infrastructure 命名空间,旨在为 Web 应用(ASP.NET Core)提供常用工具/初始化逻辑和若干通用帮助函数。
  • 有与 MongoDB Bson 序列化、HTTP 状态码处理、环境信息捕获、字符串/哈希/时间戳工具、HTML 图片解析、通配符匹配、查询字符串组装、Base64/哈希转换等相关的功能集合。

按方法逐项解释

Init(this IApplicationBuilder app)

  • 程序初始化入口方法(扩展 IApplicationBuilder)。
  • 注册一个自定义 Bson 序列化器:BsonSerializer.RegisterSerializer(typeof(Date), new DateBsonSerializer());(假设项目中定义了 Date 类型和 DateBsonSerializer)。
  • 读取并缓存宿主环境信息(调用 GetHostingEnvironment)。
  • 注册状态码处理中间件(调用 HandlerOtherStatusCode),并返回配置后的 IApplicationBuilder。

HandlerOtherStatusCode(this IApplicationBuilder app)

  • 使用 UseStatusCodePages 为非 200 的响应生成自定义页面。
  • 当状态码为 404 时,读取 IWebHostEnvironment.WebRootPath 下的 NotFound.html 并返回(通过 SendFileAsync)。
  • 其他状态码则返回 text/plain 文本,内容为 "Status code page, status code: {status}"。
  • 注释中保留了异常处理中间件(UseExceptionHandler)的示例,但被注释掉了。

GetHostingEnvironment(this IApplicationBuilder app)

  • 从 app.ApplicationServices 中获取 IWebHostEnvironment 并将某些属性(ContentRootPath, EnvironmentName, WebRootPath)保存到 EnvironmentInfo(代码中未展示 EnvironmentInfo 的定义,推测是应用的静态环境信息容器)。

GenerateMd5(this string str)

  • 计算传入字符串的 MD5 值并返回大写十六进制(BitConverter.ToString 默认返回带连字符的十六进制字符串,随后 Replace("-", "") 去掉连字符)。
  • 使用 Encoding.Default 将字符串编码为字节(注意:Encoding.Default 与平台/系统区域设置有关,参见“注意事项”)。

GenerateHashMd5(this string str)

  • 计算 MD5 并以小写的十六进制字符串返回(每个字节格式化为 "x2" 并拼接)。

ToTimeStamp(this DateTime dateTime)

  • 将 DateTime 转换成 UTC 后,计算自 Unix 纪元 (1970-01-01 00:00:00 UTC) 起的总秒数,返回 long(时间戳,单位为秒)。

ToDateTime(this long timeStamp)

  • 将从 Unix 纪元起的秒数转换为 DateTime,本实现先从 DateTimeOffset(1970...) 加上秒数,最终返回 LocalDateTime(本地时区时间)。

ForEach(this IEnumerable source, Action action)

  • 对 IEnumerable 的每个元素执行 action。
  • 与 List.ForEach 类似,但作用对象是任何实现 IEnumerable 的集合。

IndexOf(this IEnumerable source, T item)

  • 将 IEnumerable 转为数组后调用 Array.IndexOf,返回元素索引(若不存在返回 -1)。
  • 注意:会一次性把整个集合复制到数组中(尽量小心对大集合使用)。

ToImages(this IHtmlCollection images)

  • 使用 AngleSharp 的 IHtmlCollection(假设为 集合)提取每个元素的 src 属性,按 '/' 分割,尝试把最后一段解析为 MongoDB 的 ObjectId,返回成功解析的对象 Id 的字符串列表。
  • 适用于系统中以文件/资源 URL 最后段为 MongoDB ObjectId 的场景。

ToObjectIdImages(this IHtmlCollection images)

  • 与 ToImages 类似,但直接返回 ObjectId 列表(而非字符串)。

ReplaceOne(this string str, string oldStr, string newStr)

  • 仅替换第一个匹配的 oldStr 为 newStr。
  • 用 IndexOf 找到第一个位置,使用 ReadOnlySpan/AsSpan 拼接以减少不必要的中间字符串分配(更高效)。

WildCardMatch(this string wildCard, string value)

  • 支持简单通配符匹配:
    • 先做不区分大小写的完全相等比较。
    • 否则利用 FileSystemName.MatchesSimpleExpression 来匹配(支持 ? 和 *)。
  • 注释中还给出基于正则的可替代实现(被注释)。

ToQueryString(this NameValueCollection? collection)

  • 将 System.Collections.Specialized.NameValueCollection 转换成 URL 查询字符串:
    • 对每个 key 的所有值都进行 HttpUtility.UrlEncode 后以 key=value& 的形式追加。
    • 对于空 key 会直接把值写出(不写 key=),最后去掉多余的尾部 '&'。
  • 返回空集合则返回空字符串。

Base64StringToBytes(string base64String)

  • 目标是把 base64 字符串转换为 byte[]:
    • 先根据 base64 长度计算解码后字节数组的预估长度并分配 buffer。
    • 调用 Convert.TryFromBase64String 填充 buffer,若成功则返回 buffer,否则返回空数组。
  • 注意:实现中使用了基于长度的计算逻辑来预分配数组,且在失败时返回空数组。

ToSha256Hash(string input)

  • 计算输入字符串的 SHA-256 摘要,返回十六进制字符串(使用 BitConverter.ToString,然后 Replace("-", ""),因此返回的是大写十六进制形式)。

其他细节与注意事项(建议/潜在问题)

  • 编码问题:
    • GenerateMd5/GenerateHashMd5 使用 Encoding.Default 将字符串转为字节,这会依赖运行环境的默认编码,不利于可预测性与跨平台一致性。建议使用 Encoding.UTF8。
  • 时间处理:
    • ToTimeStamp/ToDateTime 在时区和 UTC/本地时间转换上需谨慎。建议明确使用 DateTimeKind(UTC 或 Local)或统一使用 DateTimeOffset 来避免歧义。
  • Base64 解码:
    • Base64StringToBytes 自行计算容量并使用 Convert.TryFromBase64String 是可以的,但实现中用到的返回空数组处(代码里写的 [])在 C# 中并非有效字面量;更典型的返回空数组写法为 Array.Empty() 或 new byte[0]。此外,直接使用 Convert.FromBase64String 会更简单,但会抛异常而不是 Try 返回 false。
  • 哈希字符串大小写:
    • GenerateMd5 返回的是大写十六进制(因为 BitConverter 默认大写),GenerateHashMd5 返回小写,两者行为不一致,可能导致使用时混淆。ToSha256Hash 返回大写十六进制。建议统一格式(全部大写或全部小写)。
  • 性能/副作用:
    • IndexOf 将整个 IEnumerable 复制为数组,可能对大集合不合适。
    • ForEach 对于惰性集合(yield return)会触发枚举;调用时要注意副作用。
  • ToImages/ToObjectIdImages:
    • 假设图片 URL 的最后一段是 ObjectId;若 URL 有查询字符串或其它后缀(如 .png?x=1),TryParse 会失败。可以先对最后一段做清洗(移除查询字符串/片段)再尝试解析。
  • HandlerOtherStatusCode:
    • 当 envService?.WebRootPath 为空时,代码 Path.Combine("", "NotFound.html") 会产生 "NotFound.html"(相对路径),在某些部署下可能找不到资源。可以考虑回退到 ContentRootPath 或更鲁棒的错误处理方案。
  • 可维护性:
    • 部分公共方法(例如通配符匹配、Base64、哈希等)若在多处使用可考虑抽象成独立的工具类以便测试和复用。

总结

  • 这是一个集合了应用启动初始化(Bson 注册、环境读取、状态页中间件)和若干常用工具函数的扩展类,便于在 ASP.NET Core 应用中通过扩展方法快速调用这些功能。
  • 代码总体清晰、功能明确,但存在编码默认、时间处理、哈希大小写不一致、Base64 返回空数组等需要注意或改进的小点。

如果你希望,我可以:

  • 指出并修复潜在的 bug(例如 Base64 返回空数组写法或 Encoding.Default 改为 UTF8);
  • 给出更严格/更健壮的 ToImages 实现(清洗 URL 查询字符串后再解析 ObjectId);
  • 或者把这些工具函数写成可单元测试的独立类并补充单元测试示例。哪一项你更想先做?
评论加载中...