소스 검색

增加一些扩展功能

懒得勤快 3 년 전
부모
커밋
3970b0e957

+ 1 - 1
Masuit.Tools.Abstractions/Extensions/BaseType/StringExtensions.cs

@@ -187,7 +187,7 @@ namespace Masuit.Tools
         /// <returns></returns>
         public static bool IsNullOrEmpty(this string s)
         {
-            return string.IsNullOrEmpty(s) || s.Equals("null", StringComparison.CurrentCultureIgnoreCase);
+            return string.IsNullOrWhiteSpace(s) || s.Equals("null", StringComparison.CurrentCultureIgnoreCase);
         }
 
         /// <summary>

+ 55 - 1
Masuit.Tools.Abstractions/Extensions/BaseType/ValueTypeConvertExtensions.cs

@@ -109,5 +109,59 @@ namespace Masuit.Tools
         {
             return new decimal(num);
         }
+
+        /// <summary>
+        /// 保留小数
+        /// </summary>
+        /// <param name="num"></param>
+        /// <param name="decimals"></param>
+        /// <returns></returns>
+        public static decimal Round(this ref decimal num, int decimals)
+        {
+            num = Math.Round(num, decimals);
+            return num;
+        }
+
+        /// <summary>
+        /// 保留小数
+        /// </summary>
+        /// <param name="num"></param>
+        /// <param name="decimals"></param>
+        /// <returns></returns>
+        public static double Round(this ref double num, int decimals)
+        {
+            num = Math.Round(num, decimals);
+            return num;
+        }
+
+        /// <summary>
+        /// 保留小数
+        /// </summary>
+        /// <param name="num"></param>
+        /// <param name="decimals"></param>
+        /// <returns></returns>
+        public static decimal? Round(this ref decimal? num, int decimals)
+        {
+            if (num.HasValue)
+            {
+                num = Math.Round(num.Value, decimals);
+            }
+            return num;
+        }
+
+        /// <summary>
+        /// 保留小数
+        /// </summary>
+        /// <param name="num"></param>
+        /// <param name="decimals"></param>
+        /// <returns></returns>
+        public static double? Round(this ref double? num, int decimals)
+        {
+            if (num.HasValue)
+            {
+                num = Math.Round(num.Value, decimals);
+            }
+            return num;
+        }
     }
-}
+}

+ 16 - 0
Masuit.Tools.Abstractions/Validator/EnumOfAttribute.cs

@@ -29,3 +29,19 @@ public class EnumOfAttribute : ValidationAttribute
         return Enum.IsDefined(Type, value);
     }
 }
+
+/// <summary>
+/// 非空值校验
+/// </summary>
+public class NotNullOrEmptyAttribute : ValidationAttribute
+{
+    public override bool IsValid(object value)
+    {
+        if (value is null)
+        {
+            return false;
+        }
+
+        return !value.IsNullOrEmpty();
+    }
+}

+ 21 - 0
Masuit.Tools.AspNetCore/Extensions/DisableFormValueModelBindingAttribute.cs

@@ -0,0 +1,21 @@
+using System;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+
+namespace Masuit.Tools.AspNetCore.Extensions;
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
+{
+    public void OnResourceExecuting(ResourceExecutingContext context)
+    {
+        var factories = context.ValueProviderFactories;
+        factories.RemoveType<FormValueProviderFactory>();
+        factories.RemoveType<FormFileValueProviderFactory>();
+        factories.RemoveType<JQueryFormValueProviderFactory>();
+    }
+
+    public void OnResourceExecuted(ResourceExecutedContext context)
+    {
+    }
+}

+ 12 - 0
Masuit.Tools.AspNetCore/Extensions/IMultipartRequestService.cs

@@ -0,0 +1,12 @@
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Primitives;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Masuit.Tools.AspNetCore.Extensions;
+
+public interface IMultipartRequestService
+{
+    Task<(Dictionary<string, StringValues>, byte[])> GetDataFromMultiPart(MultipartReader reader, CancellationToken cancellationToken);
+}

+ 82 - 0
Masuit.Tools.AspNetCore/Extensions/MultipartRequestService.cs

@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Masuit.Tools.Core.AspNetCore;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
+
+namespace Masuit.Tools.AspNetCore.Extensions;
+
+[ServiceInject(ServiceLifetime.Scoped)]
+public class MultipartRequestService : IMultipartRequestService
+{
+    public async Task<(Dictionary<string, StringValues>, byte[])> GetDataFromMultiPart(MultipartReader reader, CancellationToken cancellationToken)
+    {
+        var formAccumulator = new KeyValueAccumulator();
+        var file = Array.Empty<byte>();
+
+        MultipartSection section;
+        while ((section = await reader.ReadNextSectionAsync(cancellationToken)) != null)
+        {
+            if (!ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition))
+            {
+                continue;
+            }
+
+            if (contentDisposition.IsFormDisposition())
+            {
+                formAccumulator = await AccumulateForm(formAccumulator, section, contentDisposition);
+            }
+            else if (contentDisposition.IsFileDisposition())
+            {
+                // you will want to replace all of this because it copies the file into a memory stream. We don't want that.
+                await using var memoryStream = new MemoryStream();
+                await section.Body.CopyToAsync(memoryStream, cancellationToken);
+
+                file = memoryStream.ToArray();
+            }
+        }
+
+        return (formAccumulator.GetResults(), file);
+    }
+
+    private Encoding GetEncoding(MultipartSection section)
+    {
+        var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out var mediaType);
+
+        // UTF-7 is insecure and shouldn't be honored. UTF-8 succeeds in
+        // most cases.
+        if (!hasMediaTypeHeader || Encoding.UTF7.Equals(mediaType.Encoding))
+        {
+            return Encoding.UTF8;
+        }
+
+        return mediaType.Encoding;
+    }
+
+    private async Task<KeyValueAccumulator> AccumulateForm(KeyValueAccumulator formAccumulator, MultipartSection section, ContentDispositionHeaderValue contentDisposition)
+    {
+        var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name).Value;
+        using var streamReader = new StreamReader(section.Body, GetEncoding(section), true, 1024, true);
+        {
+            var value = await streamReader.ReadToEndAsync();
+            if (string.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
+            {
+                value = string.Empty;
+            }
+            formAccumulator.Append(key, value);
+
+            if (formAccumulator.ValueCount > FormReader.DefaultValueCountLimit)
+            {
+                throw new InvalidDataException($"Form key count limit {FormReader.DefaultValueCountLimit} exceeded.");
+            }
+        }
+
+        return formAccumulator;
+    }
+}

+ 13 - 0
Masuit.Tools.AspNetCore/Extensions/ServiceCollectionExt.cs

@@ -0,0 +1,13 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Masuit.Tools.AspNetCore.Extensions;
+
+public static class ServiceCollectionExt
+{
+    public static IServiceCollection AddMultipartRequestService(this IServiceCollection services)
+    {
+        services.TryAddScoped<IMultipartRequestService, MultipartRequestService>();
+        return services;
+    }
+}

+ 22 - 0
Masuit.Tools.AspNetCore/Extensions/ViewDataDictionaryExt.cs

@@ -0,0 +1,22 @@
+using System;
+using Microsoft.AspNetCore.Mvc.ViewFeatures;
+
+namespace Masuit.Tools.AspNetCore.Extensions;
+
+public static class ViewDataDictionaryExt
+{
+    public static T GetValue<T>(this ViewDataDictionary dic, string s) where T : class
+    {
+        return dic[s] as T;
+    }
+
+    public static T GetValueOrDefault<T>(this ViewDataDictionary dic, string s, T defaultValue)
+    {
+        return (T)(dic[s] ?? defaultValue);
+    }
+
+    public static T GetValueOrDefault<T>(this ViewDataDictionary dic, string s, Func<T> defaultValue)
+    {
+        return (T)(dic[s] ?? defaultValue());
+    }
+}

+ 16 - 1
Masuit.Tools.Core/AspNetCore/DbSetExtensions.cs

@@ -80,6 +80,7 @@ namespace Masuit.Tools.Core.AspNetCore
                     string idName = dataType.Name + "Id";
                     keyIgnoreFields = dataType.GetProperties().Where(p => p.Name.Equals("Id", StringComparison.OrdinalIgnoreCase) || p.Name.Equals(idName, StringComparison.OrdinalIgnoreCase)).ToList();
                 }
+
                 // 更新所有非主键属性
                 foreach (var p in typeof(T).GetProperties().Where(p => p.GetSetMethod() != null && p.GetGetMethod() != null))
                 {
@@ -116,5 +117,19 @@ namespace Masuit.Tools.Core.AspNetCore
                 _ => throw new NotSupportedException("不支持的表达式类型:" + oldExpression.NodeType)
             };
         }
+
+#if NET6_0_OR_GREATER
+
+        /// <summary>
+        /// 随机排序
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        public static IOrderedQueryable<T> OrderByRandom<T>(this IQueryable<T> query)
+        {
+            return query.OrderBy(_ => EF.Functions.Random());
+        }
+#endif
     }
-}
+}

+ 119 - 42
Masuit.Tools.Core/AspNetCore/ServiceCollectionExtensions.cs

@@ -7,58 +7,135 @@ using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc.Infrastructure;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Hosting;
+using System.Collections.Generic;
+using System.Reflection;
+using System;
+using System.Linq;
 
-namespace Masuit.Tools.Core.AspNetCore
+namespace Masuit.Tools.Core.AspNetCore;
+
+/// <summary>
+/// 依赖注入ServiceCollection容器扩展方法
+/// </summary>
+public static class ServiceCollectionExtensions
 {
     /// <summary>
-    /// 依赖注入ServiceCollection容器扩展方法
+    /// 注入断点续传服务
     /// </summary>
-    public static class ServiceCollectionExtensions
+    /// <param name="services"></param>
+    /// <returns></returns>
+    public static IServiceCollection AddResumeFileResult(this IServiceCollection services)
     {
-        /// <summary>
-        /// 注入断点续传服务
-        /// </summary>
-        /// <param name="services"></param>
-        /// <returns></returns>
-        public static IServiceCollection AddResumeFileResult(this IServiceCollection services)
-        {
-            services.TryAddSingleton<IActionResultExecutor<ResumePhysicalFileResult>, ResumePhysicalFileResultExecutor>();
-            services.TryAddSingleton<IActionResultExecutor<ResumeVirtualFileResult>, ResumeVirtualFileResultExecutor>();
-            services.TryAddSingleton<IActionResultExecutor<ResumeFileStreamResult>, ResumeFileStreamResultExecutor>();
-            services.TryAddSingleton<IActionResultExecutor<ResumeFileContentResult>, ResumeFileContentResultExecutor>();
-            return services;
-        }
+        services.TryAddSingleton<IActionResultExecutor<ResumePhysicalFileResult>, ResumePhysicalFileResultExecutor>();
+        services.TryAddSingleton<IActionResultExecutor<ResumeVirtualFileResult>, ResumeVirtualFileResultExecutor>();
+        services.TryAddSingleton<IActionResultExecutor<ResumeFileStreamResult>, ResumeFileStreamResultExecutor>();
+        services.TryAddSingleton<IActionResultExecutor<ResumeFileContentResult>, ResumeFileContentResultExecutor>();
+        return services;
+    }
 
-        /// <summary>
-        /// 注入7z压缩
-        /// </summary>
-        /// <param name="services"></param>
-        /// <returns></returns>
-        public static IServiceCollection AddSevenZipCompressor(this IServiceCollection services)
-        {
-            services.AddHttpClient<ISevenZipCompressor, SevenZipCompressor>();
-            return services;
-        }
+    /// <summary>
+    /// 注入7z压缩
+    /// </summary>
+    /// <param name="services"></param>
+    /// <returns></returns>
+    public static IServiceCollection AddSevenZipCompressor(this IServiceCollection services)
+    {
+        services.AddHttpClient<ISevenZipCompressor, SevenZipCompressor>();
+        return services;
+    }
 
-        /// <summary>
-        /// 注入HttpContext静态对象,方便在任意地方获取HttpContext,services.AddHttpContextAccessor();
-        /// </summary>
-        /// <param name="services"></param>
-        public static void AddStaticHttpContext(this IServiceCollection services)
+    /// <summary>
+    /// 注入HttpContext静态对象,方便在任意地方获取HttpContext,services.AddHttpContextAccessor();
+    /// </summary>
+    /// <param name="services"></param>
+    public static void AddStaticHttpContext(this IServiceCollection services)
+    {
+        services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
+    }
+
+    /// <summary>
+    /// 注入HttpContext静态对象,方便在任意地方获取HttpContext,app.UseStaticHttpContext();
+    /// </summary>
+    /// <param name="app"></param>
+    /// <returns></returns>
+    public static IApplicationBuilder UseStaticHttpContext(this IApplicationBuilder app)
+    {
+        var httpContextAccessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
+        HttpContext2.Configure(httpContextAccessor);
+        return app;
+    }
+
+    /// <summary>
+    /// 自动扫描需要注册的服务,被ServiceInject标记的class可自动注入
+    /// </summary>
+    /// <param name="services"></param>
+    public static void AutoRegisterServices(this IServiceCollection services)
+    {
+        var assemblies = AppDomain.CurrentDomain.GetAssemblies();
+        services.RegisterServiceByAttribute(assemblies);
+        services.RegisterBackgroundService(assemblies);
+    }
+
+    /// <summary>
+    /// 通过 ServiceAttribute 批量注册服务
+    /// </summary>
+    /// <param name="services"></param>
+    private static void RegisterServiceByAttribute(this IServiceCollection services, IEnumerable<Assembly> assemblies)
+    {
+        var types = assemblies.SelectMany(t => t.GetTypes()).Where(t => t.GetCustomAttributes(typeof(ServiceInjectAttribute), false).Length > 0 && t.IsClass && !t.IsAbstract).ToList();
+
+        foreach (var type in types)
         {
-            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
+            var typeInterface = type.GetInterfaces().FirstOrDefault();
+            if (typeInterface == null)
+            {
+                //服务非继承自接口的直接注入
+                switch (type.GetCustomAttribute<ServiceInjectAttribute>().Lifetime)
+                {
+                    case ServiceLifetime.Singleton: services.AddSingleton(type); break;
+                    case ServiceLifetime.Scoped: services.AddScoped(type); break;
+                    case ServiceLifetime.Transient: services.AddTransient(type); break;
+                }
+            }
+            else
+            {
+                //服务继承自接口的和接口一起注入
+                switch (type.GetCustomAttribute<ServiceInjectAttribute>().Lifetime)
+                {
+                    case ServiceLifetime.Singleton: services.AddSingleton(typeInterface, type); break;
+                    case ServiceLifetime.Scoped: services.AddScoped(typeInterface, type); break;
+                    case ServiceLifetime.Transient: services.AddTransient(typeInterface, type); break;
+                }
+            }
         }
+    }
 
-        /// <summary>
-        /// 注入HttpContext静态对象,方便在任意地方获取HttpContext,app.UseStaticHttpContext();
-        /// </summary>
-        /// <param name="app"></param>
-        /// <returns></returns>
-        public static IApplicationBuilder UseStaticHttpContext(this IApplicationBuilder app)
+    /// <summary>
+    /// 注册后台服务
+    /// </summary>
+    /// <param name="services"></param>
+    private static void RegisterBackgroundService(this IServiceCollection services, IEnumerable<Assembly> assemblies)
+    {
+        var types = assemblies.SelectMany(t => t.GetTypes()).Where(t => typeof(BackgroundService).IsAssignableFrom(t) && !t.IsAbstract);
+        foreach (var type in types)
         {
-            var httpContextAccessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
-            HttpContext2.Configure(httpContextAccessor);
-            return app;
+            services.AddSingleton(typeof(IHostedService), type);
         }
     }
-}
+}
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
+public class ServiceInjectAttribute : Attribute
+{
+    public ServiceInjectAttribute()
+    {
+    }
+
+    public ServiceInjectAttribute(ServiceLifetime lifetime)
+    {
+        Lifetime = lifetime;
+    }
+
+    public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Transient;
+}

+ 1 - 1
Masuit.Tools.Core/AspNetCore/UpdateIgnoreAttribute.cs

@@ -9,4 +9,4 @@ namespace Masuit.Tools.Core.AspNetCore
     public sealed class UpdateIgnoreAttribute : Attribute
     {
     }
-}
+}

+ 2 - 2
Masuit.Tools.Core/Masuit.Tools.Core.csproj

@@ -32,7 +32,7 @@ github:https://github.com/ldqk/Masuit.Tools
 
     <ItemGroup>
         <PackageReference Include="DnsClient" Version="1.6.1" />
-        <PackageReference Include="HtmlSanitizer" Version="7.1.512" />
+        <PackageReference Include="HtmlSanitizer" Version="7.1.542" />
         <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
         <PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
         <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta14" />
@@ -66,7 +66,7 @@ github:https://github.com/ldqk/Masuit.Tools
     </ItemGroup>
     <ItemGroup Condition=" '$(TargetFramework)' == 'net6'">
         <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
-        <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.6" />
+        <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.7" />
         <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0" />
         <PackageReference Include="Microsoft.Extensions.Http" Version="6.0" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="6.0.1" />

+ 1 - 1
Masuit.Tools.Excel/Masuit.Tools.Excel.csproj

@@ -29,7 +29,7 @@
         <DocumentationFile>.\Masuit.Tools.Excel.xml</DocumentationFile>
     </PropertyGroup>
     <ItemGroup>
-        <PackageReference Include="EPPlus" Version="6.0.5" />
+        <PackageReference Include="EPPlus" Version="6.0.6" />
         <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
         <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
     </ItemGroup>

+ 1 - 1
Masuit.Tools.NoSQL.MongoDBClient/Masuit.Tools.NoSQL.MongoDBClient.csproj

@@ -38,7 +38,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="MongoDB.Driver" Version="2.16.1" />
+    <PackageReference Include="MongoDB.Driver" Version="2.17.0" />
   </ItemGroup>
 
 </Project>

+ 1 - 1
Test/Masuit.Tools.AspNetCore.ResumeFileResults.WebTest/Masuit.Tools.AspNetCore.ResumeFileResults.WebTest.csproj

@@ -23,7 +23,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
+    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
   </ItemGroup>
 
   <ItemGroup>

+ 1 - 1
Test/Masuit.Tools.Core.Test/Masuit.Tools.Core.Test.csproj

@@ -9,7 +9,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="6.0.6" />
+    <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="6.0.7" />
     <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
     <PackageReference Include="xunit" Version="2.4.1" />