From 29b04fd16983a198c7fb0d24059b8b16b4a651db Mon Sep 17 00:00:00 2001 From: Nice <2017875139@qq.com> Date: Tue, 22 Feb 2022 15:26:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Easy.sln | 37 +++++ src/Easy.Api/ApiService.cs | 7 + src/Easy.Api/Easy.Api.csproj | 17 ++ .../RouteHandlerBuilderExtensions.cs | 43 +++++ .../ServiceCollectionServiceExtensions.cs | 42 +++++ src/Easy.Api/FirstTemplateAttribute.cs | 24 +++ src/Easy.Api/IgnoreAttribute.cs | 16 ++ .../Middlewares/AnnotationsOperationFilter.cs | 140 ++++++++++++++++ src/Easy.Api/Realization/ApiMapAuto.cs | 55 +++++++ .../Realization/AppServiceActivator.cs | 25 +++ .../AppServiceControllerFeatureProvider.cs | 16 ++ .../Realization/ApplicationConvention.cs | 149 ++++++++++++++++++ src/Easy.Api/_Imports.cs | 2 + src/Easy.DI/Easy.DI.csproj | 13 ++ .../ServiceCollectionServiceExtensions.cs | 28 ++++ src/Easy.DI/ILazyServiceProvider.cs | 20 +++ src/Easy.DI/IScopedDependency.cs | 8 + src/Easy.DI/ISingletonDependency.cs | 8 + src/Easy.DI/ITransientDependency.cs | 8 + .../Realization/DependencyInterface.cs | 52 ++++++ .../Realization/LazyServiceProvider.cs | 73 +++++++++ src/Easy.DI/_Imports.cs | 2 + src/Easy.Result/ApiResult.cs | 27 ++++ src/Easy.Result/ApiResultConsts.cs | 13 ++ src/Easy.Result/ApiResultPaged.cs | 18 +++ src/Easy.Result/ApiResultValue.cs | 11 ++ src/Easy.Result/ApiResultValues.cs | 11 ++ src/Easy.Result/Common/AutoMapReadonly.cs | 24 +++ src/Easy.Result/Easy.Result.csproj | 16 ++ src/Easy.Result/Exceptions/SvcException.cs | 18 +++ .../Extensions/ApiResultExtensions.cs | 77 +++++++++ .../ApplicationBuilderExtensions.cs | 22 +++ .../FluentValidationRegister.cs | 36 +++++ src/Easy.Result/IEntities.cs | 65 ++++++++ src/Easy.Result/IEntity.cs | 10 ++ src/Easy.Result/IModel.cs | 9 ++ .../Middlewares/ErrApiResMiddleware.cs | 51 ++++++ src/Easy.Result/PagingRequest.cs | 38 +++++ src/Easy.Result/When.cs | 20 +++ 39 files changed, 1251 insertions(+) create mode 100644 Easy.sln create mode 100644 src/Easy.Api/ApiService.cs create mode 100644 src/Easy.Api/Easy.Api.csproj create mode 100644 src/Easy.Api/Extensions/RouteHandlerBuilderExtensions.cs create mode 100644 src/Easy.Api/Extensions/ServiceCollectionServiceExtensions.cs create mode 100644 src/Easy.Api/FirstTemplateAttribute.cs create mode 100644 src/Easy.Api/IgnoreAttribute.cs create mode 100644 src/Easy.Api/Middlewares/AnnotationsOperationFilter.cs create mode 100644 src/Easy.Api/Realization/ApiMapAuto.cs create mode 100644 src/Easy.Api/Realization/AppServiceActivator.cs create mode 100644 src/Easy.Api/Realization/AppServiceControllerFeatureProvider.cs create mode 100644 src/Easy.Api/Realization/ApplicationConvention.cs create mode 100644 src/Easy.Api/_Imports.cs create mode 100644 src/Easy.DI/Easy.DI.csproj create mode 100644 src/Easy.DI/Extensions/ServiceCollectionServiceExtensions.cs create mode 100644 src/Easy.DI/ILazyServiceProvider.cs create mode 100644 src/Easy.DI/IScopedDependency.cs create mode 100644 src/Easy.DI/ISingletonDependency.cs create mode 100644 src/Easy.DI/ITransientDependency.cs create mode 100644 src/Easy.DI/Realization/DependencyInterface.cs create mode 100644 src/Easy.DI/Realization/LazyServiceProvider.cs create mode 100644 src/Easy.DI/_Imports.cs create mode 100644 src/Easy.Result/ApiResult.cs create mode 100644 src/Easy.Result/ApiResultConsts.cs create mode 100644 src/Easy.Result/ApiResultPaged.cs create mode 100644 src/Easy.Result/ApiResultValue.cs create mode 100644 src/Easy.Result/ApiResultValues.cs create mode 100644 src/Easy.Result/Common/AutoMapReadonly.cs create mode 100644 src/Easy.Result/Easy.Result.csproj create mode 100644 src/Easy.Result/Exceptions/SvcException.cs create mode 100644 src/Easy.Result/Extensions/ApiResultExtensions.cs create mode 100644 src/Easy.Result/Extensions/ApplicationBuilderExtensions.cs create mode 100644 src/Easy.Result/FluentValidation/FluentValidationRegister.cs create mode 100644 src/Easy.Result/IEntities.cs create mode 100644 src/Easy.Result/IEntity.cs create mode 100644 src/Easy.Result/IModel.cs create mode 100644 src/Easy.Result/Middlewares/ErrApiResMiddleware.cs create mode 100644 src/Easy.Result/PagingRequest.cs create mode 100644 src/Easy.Result/When.cs diff --git a/Easy.sln b/Easy.sln new file mode 100644 index 0000000..422b084 --- /dev/null +++ b/Easy.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32210.238 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Easy.Api", "src\Easy.Api\Easy.Api.csproj", "{689367DF-1216-40A5-91CE-412EE8FE6C8F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Easy.DI", "src\Easy.DI\Easy.DI.csproj", "{A269FF3D-00BC-499D-A496-46D97D51FE92}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Easy.Result", "src\Easy.Result\Easy.Result.csproj", "{82677F0B-E9CF-4491-8B04-9BF9B04B1534}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {689367DF-1216-40A5-91CE-412EE8FE6C8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {689367DF-1216-40A5-91CE-412EE8FE6C8F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {689367DF-1216-40A5-91CE-412EE8FE6C8F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {689367DF-1216-40A5-91CE-412EE8FE6C8F}.Release|Any CPU.Build.0 = Release|Any CPU + {A269FF3D-00BC-499D-A496-46D97D51FE92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A269FF3D-00BC-499D-A496-46D97D51FE92}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A269FF3D-00BC-499D-A496-46D97D51FE92}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A269FF3D-00BC-499D-A496-46D97D51FE92}.Release|Any CPU.Build.0 = Release|Any CPU + {82677F0B-E9CF-4491-8B04-9BF9B04B1534}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {82677F0B-E9CF-4491-8B04-9BF9B04B1534}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82677F0B-E9CF-4491-8B04-9BF9B04B1534}.Release|Any CPU.ActiveCfg = Release|Any CPU + {82677F0B-E9CF-4491-8B04-9BF9B04B1534}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7260112B-0232-4725-AB84-EF4DCCABFD77} + EndGlobalSection +EndGlobal diff --git a/src/Easy.Api/ApiService.cs b/src/Easy.Api/ApiService.cs new file mode 100644 index 0000000..653ac63 --- /dev/null +++ b/src/Easy.Api/ApiService.cs @@ -0,0 +1,7 @@ +using Microsoft.AspNetCore.Routing; + +namespace Easy.Api; +public abstract class ApiService +{ + +} \ No newline at end of file diff --git a/src/Easy.Api/Easy.Api.csproj b/src/Easy.Api/Easy.Api.csproj new file mode 100644 index 0000000..f30fb54 --- /dev/null +++ b/src/Easy.Api/Easy.Api.csproj @@ -0,0 +1,17 @@ + + + net6.0 + enable + True + + + + + + + + + + + + diff --git a/src/Easy.Api/Extensions/RouteHandlerBuilderExtensions.cs b/src/Easy.Api/Extensions/RouteHandlerBuilderExtensions.cs new file mode 100644 index 0000000..0836fc0 --- /dev/null +++ b/src/Easy.Api/Extensions/RouteHandlerBuilderExtensions.cs @@ -0,0 +1,43 @@ +using Easy.Api.Realization; +using Microsoft.AspNetCore.Routing; +using Swashbuckle.AspNetCore.Annotations; + +namespace Easy.Api.Extensions; + +/// +/// A class containing extension methods for the class. This class cannot be inherited. +/// +public static class RouteHandlerBuilderExtensions +{ + /// + /// Adds the to the metadata for all builders produced by builder. + /// + /// The . + /// The operation summary. + /// The optional operation description. + /// + /// A that can be used to further customize the endpoint. + /// + public static RouteHandlerBuilder WithDescription( + this RouteHandlerBuilder builder, + string summary, + string description = null) + { + return builder.WithMetadata(new SwaggerOperationAttribute(summary, description)); + } + /// + /// 路由模板 App名称带头 + /// + //public static ApiMapAuto AutoApi(this IEndpointRouteBuilder endpoint, string template) + //{ + // if (string.IsNullOrEmpty(template)) + // { + // template = "app"; + // } + // if (!template.StartsWith("api")) + // { + // template = "api/" + template; + // } + // return new ApiMapAuto(template, endpoint); + //} +} diff --git a/src/Easy.Api/Extensions/ServiceCollectionServiceExtensions.cs b/src/Easy.Api/Extensions/ServiceCollectionServiceExtensions.cs new file mode 100644 index 0000000..5ae58db --- /dev/null +++ b/src/Easy.Api/Extensions/ServiceCollectionServiceExtensions.cs @@ -0,0 +1,42 @@ +using Easy.Api; +using Easy.Api.Realization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Easy.Api.Extensions; +public static class ServiceCollectionServiceExtensions +{ + /// + /// 动态代理Api + /// + public static IMvcBuilder AddDynamicWebApi(this IServiceCollection services) + { + + var build = services.AddControllers(); + + build.ConfigureApplicationPartManager(applicationPartManager => + { + applicationPartManager.FeatureProviders.Add(new AppServiceControllerFeatureProvider()); + }); + + ControllerFeature feature = new(); + + build.PartManager.PopulateFeature(feature); + + foreach (var controller in feature.Controllers.Select(c => c.AsType()).Where(o => o != typeof(ApiService)))// 禁用父类 + { + services.TryAddTransient(controller, controller); + } + + services.Replace(ServiceDescriptor.Transient()); + + services.Configure(options => + { + options.Conventions.Add(new ApplicationConvention()); + }); + + return build; + } + +} diff --git a/src/Easy.Api/FirstTemplateAttribute.cs b/src/Easy.Api/FirstTemplateAttribute.cs new file mode 100644 index 0000000..add213c --- /dev/null +++ b/src/Easy.Api/FirstTemplateAttribute.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Easy.Api; + +/// +/// api定义? +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] +public class FirstTemplateAttribute : Attribute +{ + public string FirstTemplate { get; } + public FirstTemplateAttribute() + { + FirstTemplate = ""; + } + public FirstTemplateAttribute(string firstTemplate) + { + FirstTemplate = firstTemplate; + } +} diff --git a/src/Easy.Api/IgnoreAttribute.cs b/src/Easy.Api/IgnoreAttribute.cs new file mode 100644 index 0000000..9a0c97f --- /dev/null +++ b/src/Easy.Api/IgnoreAttribute.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Easy.Api; + +/// +/// 忽略 +/// +[AttributeUsage(AttributeTargets.Parameter)] +public class ParamIgnoreAttribute : Attribute +{ + +} diff --git a/src/Easy.Api/Middlewares/AnnotationsOperationFilter.cs b/src/Easy.Api/Middlewares/AnnotationsOperationFilter.cs new file mode 100644 index 0000000..0f86fc1 --- /dev/null +++ b/src/Easy.Api/Middlewares/AnnotationsOperationFilter.cs @@ -0,0 +1,140 @@ +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.SwaggerGen; +using System.Globalization; + +namespace Easy.Api.Middlewares; + +public class AnnotationsOperationFilter : IOperationFilter +{ + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + IEnumerable controllerAttributes = Array.Empty(); + IEnumerable actionAttributes = Array.Empty(); + IEnumerable metadataAttributes = Array.Empty(); + + if (context.MethodInfo is not null) + { + controllerAttributes = context.MethodInfo.DeclaringType!.GetCustomAttributes(true); + actionAttributes = context.MethodInfo.GetCustomAttributes(true); + } + + if (context.ApiDescription.ActionDescriptor.EndpointMetadata is not null) + { + metadataAttributes = context.ApiDescription.ActionDescriptor.EndpointMetadata; + } + + // NOTE: When controller and action attributes are applicable, action attributes should take precendence. + // Hence why they're at the end of the list (i.e. last one wins). + // Distinct() is applied due to an ASP.NET Core issue: https://github.com/dotnet/aspnetcore/issues/34199. + var allAttributes = controllerAttributes + .Union(actionAttributes) + .Union(metadataAttributes) + .Distinct(); + + var actionAndEndpointAttribtues = actionAttributes + .Union(metadataAttributes) + .Distinct(); + + ApplySwaggerOperationAttribute(operation, actionAndEndpointAttribtues); + ApplySwaggerOperationFilterAttributes(operation, context, allAttributes); + ApplySwaggerResponseAttributes(operation, context, allAttributes); + } + + + + private static void ApplySwaggerOperationAttribute( + OpenApiOperation operation, + IEnumerable actionAttributes) + { + var swaggerOperationAttribute = actionAttributes + .OfType() + .FirstOrDefault(); + + if (swaggerOperationAttribute == null) + { + return; + } + + if (swaggerOperationAttribute.Summary != null) + { + operation.Summary = swaggerOperationAttribute.Summary; + } + + if (swaggerOperationAttribute.Description != null) + { + operation.Description = swaggerOperationAttribute.Description; + } + + if (swaggerOperationAttribute.OperationId != null) + { + operation.OperationId = swaggerOperationAttribute.OperationId; + } + + if (swaggerOperationAttribute.Tags != null) + { + operation.Tags = swaggerOperationAttribute.Tags + .Select(tagName => new OpenApiTag { Name = tagName }) + .ToList(); + } + } + + private static void ApplySwaggerOperationFilterAttributes( + OpenApiOperation operation, + OperationFilterContext context, + IEnumerable controllerAndActionAttributes) + { + var swaggerOperationFilterAttributes = controllerAndActionAttributes + .OfType(); + + foreach (var swaggerOperationFilterAttribute in swaggerOperationFilterAttributes) + { + var filter = (IOperationFilter)Activator.CreateInstance(swaggerOperationFilterAttribute.FilterType)!; + filter.Apply(operation, context); + } + } + + private static void ApplySwaggerResponseAttributes( + OpenApiOperation operation, + OperationFilterContext context, + IEnumerable controllerAndActionAttributes) + { + var swaggerResponseAttributes = controllerAndActionAttributes.OfType(); + + foreach (var swaggerResponseAttribute in swaggerResponseAttributes) + { + string statusCode = swaggerResponseAttribute.StatusCode.ToString(CultureInfo.InvariantCulture); + + if (operation.Responses == null) + { + operation.Responses = new OpenApiResponses(); + } + + if (!operation.Responses.TryGetValue(statusCode, out OpenApiResponse response)) + { + response = new OpenApiResponse(); + } + + if (swaggerResponseAttribute.Description != null) + { + response.Description = swaggerResponseAttribute.Description; + } + + operation.Responses[statusCode] = response; + + if (swaggerResponseAttribute.ContentTypes != null) + { + response.Content.Clear(); + + foreach (string contentType in swaggerResponseAttribute.ContentTypes) + { + var schema = (swaggerResponseAttribute.Type != null && swaggerResponseAttribute.Type != typeof(void)) + ? context.SchemaGenerator.GenerateSchema(swaggerResponseAttribute.Type, context.SchemaRepository) + : null; + + response.Content.Add(contentType, new OpenApiMediaType { Schema = schema }); + } + } + } + } +} diff --git a/src/Easy.Api/Realization/ApiMapAuto.cs b/src/Easy.Api/Realization/ApiMapAuto.cs new file mode 100644 index 0000000..e69dc07 --- /dev/null +++ b/src/Easy.Api/Realization/ApiMapAuto.cs @@ -0,0 +1,55 @@ +namespace Easy.Api.Realization; + +public class ApiMapAuto +{ + public readonly static string[] CommonPostfixes = { "AppService", "Service" }; + public static string FiterControllerName(string controllerName) // 过滤控制器 + { + foreach (var commonPostfixe in CommonPostfixes) + { + if (controllerName.EndsWith(commonPostfixe)) + { + controllerName = controllerName.Remove(controllerName.IndexOf(commonPostfixe)); + break; + } + } + + return controllerName; + } + + public readonly static List ActionPostfixes = new() + { + "GetAll", "GetList", "Get", + "Post", "Create", "Add", "Insert", + "Put", "Update", + "Delete", "Remove", + "Patch" + }; + private const string AsyncStr = "Async"; + internal static string FiterActionName(string actionName) + { + string actionPostfixe = ActionPostfixes.FirstOrDefault(actionPostfixe => actionName.StartsWith(actionPostfixe)) ?? ""; + if (actionName.StartsWith(actionPostfixe)) + actionName = actionName.Remove(actionName.IndexOf(actionPostfixe), actionPostfixe.Length); + if (actionName.EndsWith(AsyncStr)) + actionName = actionName.Remove(actionName.IndexOf(AsyncStr)); + return actionName; + } + + public static string GetHttpMethod(string actionName) + { + if (actionName.StartsWith("Get")) + return "GET"; + + if (actionName.StartsWith("Put") || actionName.StartsWith("Update")) + return "PUT"; + + if (actionName.StartsWith("Delete") || actionName.StartsWith("Remove")) + return "DELETE"; + + if (actionName.StartsWith("Patch")) + return "PATCH"; + + return "POST"; + } +} diff --git a/src/Easy.Api/Realization/AppServiceActivator.cs b/src/Easy.Api/Realization/AppServiceActivator.cs new file mode 100644 index 0000000..eccb719 --- /dev/null +++ b/src/Easy.Api/Realization/AppServiceActivator.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; + +namespace Easy.Api.Realization; + +internal class AppServiceActivator : IControllerActivator +{ + public object Create(ControllerContext actionContext) + { + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + var controllerType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType(); + + var controller = actionContext.HttpContext.RequestServices.GetRequiredService(controllerType); + + return controller; + } + + public virtual void Release(ControllerContext context, object controller) + { + + } +} \ No newline at end of file diff --git a/src/Easy.Api/Realization/AppServiceControllerFeatureProvider.cs b/src/Easy.Api/Realization/AppServiceControllerFeatureProvider.cs new file mode 100644 index 0000000..73eab65 --- /dev/null +++ b/src/Easy.Api/Realization/AppServiceControllerFeatureProvider.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc.Controllers; +using System.Reflection; + +namespace Easy.Api.Realization; + +internal class AppServiceControllerFeatureProvider : ControllerFeatureProvider +{ + protected override bool IsController(TypeInfo typeInfo) + { + var result = + typeof(ApiService).IsAssignableFrom(typeInfo) && typeof(ApiService) != typeInfo && + typeInfo.IsClass && typeInfo.IsPublic && !typeInfo.IsAbstract; + //if (result){} + return result; + } +} diff --git a/src/Easy.Api/Realization/ApplicationConvention.cs b/src/Easy.Api/Realization/ApplicationConvention.cs new file mode 100644 index 0000000..20509d8 --- /dev/null +++ b/src/Easy.Api/Realization/ApplicationConvention.cs @@ -0,0 +1,149 @@ + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Easy.Api.Realization; + +internal class ApplicationConvention : IApplicationModelConvention +{ + public void Apply(ApplicationModel application) + { + application.Controllers.ToList().ForEach(controller => + { + controller.ControllerName = ApiMapAuto.FiterControllerName(controller.ControllerName); + ConfigureSelector(controller); + ConfigureParameters(controller); + ConfigureApiExplorer(controller); + }); + } + + + + //配置记录应用: 这可用于使用 Swagger 等工具为 Web API 生成帮助页。 + private static void ConfigureApiExplorer(ControllerModel controller) + { + if (!controller.ApiExplorer.IsVisible.HasValue) + { + controller.ApiExplorer.IsVisible = true; + } + + foreach (var action in controller.Actions) + { + if (!action.ApiExplorer.IsVisible.HasValue) + { + action.ApiExplorer.IsVisible = true; + } + } + } + //配置选择器:方法路由 HttpMethod + private static void ConfigureSelector(ControllerModel controller) + { + foreach (var action in controller.Actions) + { + action.Selectors[0].AttributeRouteModel = new(new RouteAttribute(RouteTemplate(action))); + action.Selectors[0].ActionConstraints.Add(new HttpMethodActionConstraint(new[] { ApiMapAuto.GetHttpMethod(action.ActionName) })); + } + } + //配置方法参数 + private static void ConfigureParameters(ControllerModel controller) + { + foreach (var action in controller.Actions) + { + foreach (var parameter in action.Parameters) + { + if (parameter.BindingInfo != null) + continue; + + if (parameter.ParameterType.IsClass && + parameter.ParameterType != typeof(string) && + parameter.ParameterType != typeof(IFormFile)) + { + + var httpMethods = action.Selectors.SelectMany(temp => temp.ActionConstraints).OfType().SelectMany(temp => temp.HttpMethods); + + if (new[] { "GET", "DELETE", "TRACE", "HEAD" }.Any(value => httpMethods.Contains(value))) + continue; + + parameter.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromBodyAttribute() }); + } + } + } + } + + + //获得一个自定义的路由 + private static string RouteTemplate(ActionModel action) + { + + var routeAttribute = action.Controller.Attributes.FirstOrDefault(o => o?.GetType() == typeof(FirstTemplateAttribute)); + string url = ""; + if (routeAttribute != null) + { + url = ((FirstTemplateAttribute)routeAttribute).FirstTemplate; + } + else + { + url += "api"; + } + + + + //控制器部分 + url += $"/{ApiMapAuto.FiterControllerName(action.Controller.ControllerType.Name)}"; + // id 部分 + var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id"); + if (idParameterModel != null) + { + if (action.Parameters.Any(temp => temp.ParameterName == "id")) + url += "/{id}"; + else + { + var properties = idParameterModel.ParameterType.GetProperties(BindingFlags.Instance | BindingFlags.Public); + + foreach (var property in properties) + { + url += "/{" + property.Name + "}"; + } + } + } + + //方法部分 + string actionName = ApiMapAuto.FiterActionName(action.ActionMethod.Name); + if (!string.IsNullOrEmpty(actionName)) + { + url += $"/{actionName}"; + + // id 部分 + var secondaryIds = action.Parameters.Where(p => + p.Attributes.Any(o => o.GetType() != typeof(ParamIgnoreAttribute)) && + p.ParameterName.EndsWith("Id", StringComparison.Ordinal)).ToList(); + if (secondaryIds.Count == 1) + { + url += $"/{{{secondaryIds[0].Name}}}"; + } + + } + + return url; + } + + + //清空Selectors + //private static void RemoveSelectors(IList selectors) + //{ + // for (var i = selectors.Count - 1; i >= 0; i--) + // { + // selectors.Remove(selectors[i]); + // } + //} + +} diff --git a/src/Easy.Api/_Imports.cs b/src/Easy.Api/_Imports.cs new file mode 100644 index 0000000..7964c36 --- /dev/null +++ b/src/Easy.Api/_Imports.cs @@ -0,0 +1,2 @@ +global using Microsoft.AspNetCore.Builder; +global using Microsoft.Extensions.DependencyInjection; diff --git a/src/Easy.DI/Easy.DI.csproj b/src/Easy.DI/Easy.DI.csproj new file mode 100644 index 0000000..d03fc4d --- /dev/null +++ b/src/Easy.DI/Easy.DI.csproj @@ -0,0 +1,13 @@ + + + net6.0 + enable + + + + + + + + + diff --git a/src/Easy.DI/Extensions/ServiceCollectionServiceExtensions.cs b/src/Easy.DI/Extensions/ServiceCollectionServiceExtensions.cs new file mode 100644 index 0000000..0d3097c --- /dev/null +++ b/src/Easy.DI/Extensions/ServiceCollectionServiceExtensions.cs @@ -0,0 +1,28 @@ +using Easy.DI.Realization; + +namespace Easy.DI.Extensions; +public static class ServiceCollectionServiceExtensions +{ + + public static IServiceCollection AddDependencyAbstracts(this IServiceCollection s, params Action[] abstractConfigs) + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + var types = assembly.GetTypes().Where(o => !o.IsInterface && !o.IsAbstract && o.IsClass).ToArray(); + foreach (var config in abstractConfigs) + { + config(s, types); + } + } + return s; + } + public static IServiceCollection AddDependency(this IServiceCollection s) + { + s.AddDependencyAbstracts( + DependencyInterface.Singleton, + DependencyInterface.Transient, + DependencyInterface.Scoped + ); + return s; + } +} diff --git a/src/Easy.DI/ILazyServiceProvider.cs b/src/Easy.DI/ILazyServiceProvider.cs new file mode 100644 index 0000000..0ede11f --- /dev/null +++ b/src/Easy.DI/ILazyServiceProvider.cs @@ -0,0 +1,20 @@ +namespace Easy.DI; + +public interface ILazyServiceProvider +{ + T LazyGetRequiredService(); + + object LazyGetRequiredService(Type serviceType); + + T LazyGetService(); + + object LazyGetService(Type serviceType); + + T LazyGetService(T defaultValue); + + object LazyGetService(Type serviceType, object defaultValue); + + object LazyGetService(Type serviceType, Func factory); + + T LazyGetService(Func factory); +} diff --git a/src/Easy.DI/IScopedDependency.cs b/src/Easy.DI/IScopedDependency.cs new file mode 100644 index 0000000..db73255 --- /dev/null +++ b/src/Easy.DI/IScopedDependency.cs @@ -0,0 +1,8 @@ +namespace Easy.DI; + +/// +/// 只 new 一次 +/// +public interface IScopedDependency +{ +} diff --git a/src/Easy.DI/ISingletonDependency.cs b/src/Easy.DI/ISingletonDependency.cs new file mode 100644 index 0000000..a8a6ab5 --- /dev/null +++ b/src/Easy.DI/ISingletonDependency.cs @@ -0,0 +1,8 @@ +namespace Easy.DI; + +/// +/// 全局只NEW 一次对象 +/// +public interface ISingletonDependency +{ +} diff --git a/src/Easy.DI/ITransientDependency.cs b/src/Easy.DI/ITransientDependency.cs new file mode 100644 index 0000000..002492b --- /dev/null +++ b/src/Easy.DI/ITransientDependency.cs @@ -0,0 +1,8 @@ +namespace Easy.DI; + +/// +/// 依赖注入瞬态生命周期 +/// +public interface ITransientDependency +{ +} diff --git a/src/Easy.DI/Realization/DependencyInterface.cs b/src/Easy.DI/Realization/DependencyInterface.cs new file mode 100644 index 0000000..4fb28fb --- /dev/null +++ b/src/Easy.DI/Realization/DependencyInterface.cs @@ -0,0 +1,52 @@ +using Easy.DI; + +namespace Easy.DI.Realization; + +internal static class DependencyInterface +{ + public static void Scoped(this IServiceCollection s, Type[] types) + { + foreach (var type in types.Where(o => IsServiceLifetime(o))) + { + Type interfaceType = GetInterface(type); + + if (interfaceType != null) + s.TryAddScoped(interfaceType, type); + else + s.TryAddScoped(type, type); + } + } + public static void Singleton(this IServiceCollection s, Type[] types) + { + foreach (var type in types.Where(o => IsServiceLifetime(o))) + { + Type interfaceType = GetInterface(type); + + if (interfaceType != null) + s.TryAddSingleton(interfaceType, type); + else + s.TryAddSingleton(type, type); + + } + } + public static void Transient(this IServiceCollection s, Type[] types) + { + foreach (var type in types.Where(o => IsServiceLifetime(o))) + { + Type interfaceType = GetInterface(type); + + if (interfaceType != null) + s.TryAddTransient(interfaceType, type); + else + s.TryAddTransient(type, type); + + } + } + + private static bool IsServiceLifetime(Type o) => typeof(ServiceLifetime).IsAssignableFrom(o); + + private static Type GetInterface(Type type) + { + return type.GetInterfaces().FirstOrDefault(interfaceType => interfaceType.Name.EndsWith(type.Name)); + } +} diff --git a/src/Easy.DI/Realization/LazyServiceProvider.cs b/src/Easy.DI/Realization/LazyServiceProvider.cs new file mode 100644 index 0000000..8f3acee --- /dev/null +++ b/src/Easy.DI/Realization/LazyServiceProvider.cs @@ -0,0 +1,73 @@ +namespace Easy.DI.Realization; + +public class LazyServiceProvider : ILazyServiceProvider, ITransientDependency +{ + protected IDictionary CachedServices { get; set; } + + protected IServiceProvider ServiceProvider { get; set; } + + public LazyServiceProvider(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + CachedServices = new Dictionary(); + } + + public virtual T LazyGetRequiredService() + { + return (T)LazyGetRequiredService(typeof(T)); + } + + public virtual object LazyGetRequiredService(Type serviceType) + { + if (CachedServices.TryGetValue(serviceType, out object obj)) + { + return obj; + } + + return CachedServices[serviceType] = ServiceProvider?.GetService(serviceType); + } + + public virtual T LazyGetService() + { + return (T)LazyGetService(typeof(T)); + } + + public virtual object LazyGetService(Type serviceType) + { + if (CachedServices.TryGetValue(serviceType, out object obj)) + { + return obj; + } + + return CachedServices[serviceType] = ServiceProvider?.GetService(serviceType); + } + + public virtual T LazyGetService(T defaultValue) + { + return (T)LazyGetService(typeof(T), defaultValue); + } + + public virtual object LazyGetService(Type serviceType, object defaultValue) + { + return LazyGetService(serviceType) ?? defaultValue; + } + + public virtual T LazyGetService(Func factory) + { + return (T)LazyGetService(typeof(T), factory); + } + + public virtual object LazyGetService(Type serviceType, Func factory) + { + + if (CachedServices.TryGetValue(serviceType, out object obj)) + { + return obj; + } + + return CachedServices[serviceType] = factory(ServiceProvider); + } + + +} + diff --git a/src/Easy.DI/_Imports.cs b/src/Easy.DI/_Imports.cs new file mode 100644 index 0000000..85923a6 --- /dev/null +++ b/src/Easy.DI/_Imports.cs @@ -0,0 +1,2 @@ +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; diff --git a/src/Easy.Result/ApiResult.cs b/src/Easy.Result/ApiResult.cs new file mode 100644 index 0000000..088b564 --- /dev/null +++ b/src/Easy.Result/ApiResult.cs @@ -0,0 +1,27 @@ +namespace Easy.Result; + +/// +/// ApiResult +/// +public class ApiResult : IModel +{ + /// + /// 消息 + /// + public string Message { get; set; } + /// + /// 状态 + /// + public string Status { get; set; } + + + public static ApiResultValue Value(TValue value) + { + return new ApiResultValue(value); + } + public static ApiResultValues Value(List value) + { + return new ApiResultValues(value); + } +} + diff --git a/src/Easy.Result/ApiResultConsts.cs b/src/Easy.Result/ApiResultConsts.cs new file mode 100644 index 0000000..49a7111 --- /dev/null +++ b/src/Easy.Result/ApiResultConsts.cs @@ -0,0 +1,13 @@ +namespace Easy.Result; + +public class ApiResultConsts +{ + /// + /// 正确返回状态 + /// + public const string SUCCESS = "SUCCESS"; + /// + /// 需要用户重试 基本是报错了 或者业务问题 + /// + public const string RETRY = "RETRY"; +} diff --git a/src/Easy.Result/ApiResultPaged.cs b/src/Easy.Result/ApiResultPaged.cs new file mode 100644 index 0000000..06d8498 --- /dev/null +++ b/src/Easy.Result/ApiResultPaged.cs @@ -0,0 +1,18 @@ +namespace Easy.Result; + +public class ApiResultPaged : ApiResult +{ + public ApiResultPaged() { } + /// + /// 页面数量 + /// + public long PageCount { get; set; } + /// + /// 页面大小 + /// + public int PageSize { get; set; } + /// + /// 返回结果 + /// + public List Data { get; set; } +} \ No newline at end of file diff --git a/src/Easy.Result/ApiResultValue.cs b/src/Easy.Result/ApiResultValue.cs new file mode 100644 index 0000000..6e5880b --- /dev/null +++ b/src/Easy.Result/ApiResultValue.cs @@ -0,0 +1,11 @@ +namespace Easy.Result; + +public class ApiResultValue : ApiResult +{ + public ApiResultValue() { } + public ApiResultValue(TValue value) + { + Result = value; + } + public TValue Result { get; set; } +} diff --git a/src/Easy.Result/ApiResultValues.cs b/src/Easy.Result/ApiResultValues.cs new file mode 100644 index 0000000..d45d49c --- /dev/null +++ b/src/Easy.Result/ApiResultValues.cs @@ -0,0 +1,11 @@ +namespace Easy.Result; + +public class ApiResultValues : ApiResult +{ + public ApiResultValues() { } + public ApiResultValues(List value) + { + Result = value; + } + public List Result { get; set; } +} diff --git a/src/Easy.Result/Common/AutoMapReadonly.cs b/src/Easy.Result/Common/AutoMapReadonly.cs new file mode 100644 index 0000000..89b0f07 --- /dev/null +++ b/src/Easy.Result/Common/AutoMapReadonly.cs @@ -0,0 +1,24 @@ +using AutoMapper; +using Microsoft.Extensions.DependencyModel; +using System.Reflection; +using System.Runtime.Loader; + +namespace Easy.Result.Common; + +public class AutoMapReadonly +{ + private static IEnumerable Assemblies = DependencyContext.Default.CompileLibraries. + Where(lib => !lib.Serviceable && lib.Type != "package" && lib.Type != "referenceassembly"). + Select(lib => AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(lib.Name))); + + public readonly static IConfigurationProvider ConfigurationProvider = new MapperConfiguration(cfg => + { + foreach (var assembly in Assemblies) + { + foreach (var profileType in assembly.GetTypes().Where(o => typeof(Profile).IsAssignableFrom(o))) + { + cfg.AddProfile(profileType); + } + } + }); +} diff --git a/src/Easy.Result/Easy.Result.csproj b/src/Easy.Result/Easy.Result.csproj new file mode 100644 index 0000000..6d57724 --- /dev/null +++ b/src/Easy.Result/Easy.Result.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + True + + + + + + + + + + diff --git a/src/Easy.Result/Exceptions/SvcException.cs b/src/Easy.Result/Exceptions/SvcException.cs new file mode 100644 index 0000000..5850d5b --- /dev/null +++ b/src/Easy.Result/Exceptions/SvcException.cs @@ -0,0 +1,18 @@ +namespace Easy.Result.Exceptions; + +/// +/// 服务异常 +/// +internal class SvcException : Exception +{ + /// + /// 服务异常 + /// + /// 消息 + public SvcException(string message) + : base(message) + { + + } + +} \ No newline at end of file diff --git a/src/Easy.Result/Extensions/ApiResultExtensions.cs b/src/Easy.Result/Extensions/ApiResultExtensions.cs new file mode 100644 index 0000000..5877674 --- /dev/null +++ b/src/Easy.Result/Extensions/ApiResultExtensions.cs @@ -0,0 +1,77 @@ +using AutoMapper; +using Easy.Result.Common; + +namespace Easy.Result.Extensions; + +public static class ApiResultExtensions +{ + /// + /// 是成功 + /// + static public bool IsSuccess(this TSource source) + where TSource : ApiResult + { + return source.Status.Equals(ApiResultConsts.SUCCESS); + } + /// + /// 是重试 + /// + static public bool IsRETRY(this TSource source) + where TSource : ApiResult + { + return source.Status.Equals(ApiResultConsts.RETRY); + } + /// + /// 成功 + /// + static public TSource SUCCESS(this TSource source, string message = null) + where TSource : ApiResult + { + return source.CustomStatusMessage("SUCCESS", message ?? "操作成功"); + } + /// + /// 重试 + /// + static public TSource RETRY(this TSource source, string message = null) + where TSource : ApiResult + { + return source.CustomStatusMessage("RETRY", message ?? "出错了,请稍后再试。"); + } + /// + /// 自定义状态消息 + /// + static public TSource CustomStatusMessage(this TSource source, string status, string message) + where TSource : ApiResult + { + source.Message = message; + source.Status = status; + return source; + } + + static public TEntity ToEntity(this IModel model) + where TEntity : IEntity, new() + { + return new Mapper(AutoMapReadonly.ConfigurationProvider).Map(model); + } + + static public ApiResultValue ToModel(this IEntity entity) + where TModel : IModel, new() + { + return ApiResult.Value(new Mapper(AutoMapReadonly.ConfigurationProvider).Map(entity) ?? new()); + } + static public ApiResultValues ToModels(this IEntities entities) + { + return ApiResult.Value(new Mapper(AutoMapReadonly.ConfigurationProvider).Map>(entities.Data) ?? new()); + } + + static public ApiResultPaged ToPagedModel(this IPaged paged) + { + return new ApiResultPaged() + { + Data = new Mapper(AutoMapReadonly.ConfigurationProvider).Map>(paged.Data) ?? new(), + PageSize = paged.PageSize, + PageCount = paged.PageCount + }; + } + +} \ No newline at end of file diff --git a/src/Easy.Result/Extensions/ApplicationBuilderExtensions.cs b/src/Easy.Result/Extensions/ApplicationBuilderExtensions.cs new file mode 100644 index 0000000..479db05 --- /dev/null +++ b/src/Easy.Result/Extensions/ApplicationBuilderExtensions.cs @@ -0,0 +1,22 @@ +using Easy.Result.FluentValidation; +using Easy.Result.Middlewares; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace Easy.Result.Extensions; + +public static class ApplicationBuilderExtensions +{ + public static IApplicationBuilder UseErrApiResult(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + public static IMvcBuilder AddEasyFluentValidation(this IMvcBuilder builder) + { + return builder.AddMvcOptions(options => + { + options.Filters.Add(); + }); + } +} + diff --git a/src/Easy.Result/FluentValidation/FluentValidationRegister.cs b/src/Easy.Result/FluentValidation/FluentValidationRegister.cs new file mode 100644 index 0000000..3252163 --- /dev/null +++ b/src/Easy.Result/FluentValidation/FluentValidationRegister.cs @@ -0,0 +1,36 @@ +using FluentValidation; +using FluentValidation.Internal; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Easy.Result.FluentValidation; + +public class FluentValidationFilter : IAsyncActionFilter +{ + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + foreach (var arg in context.ActionArguments) + { + var requestType = arg.Value?.GetType(); + if (requestType != null) + { + var abstractValidatorType = typeof(AbstractValidator<>).MakeGenericType(requestType); + + //var name = abstractValidatorType.Name + requestType.Name; + var classValidatorType = requestType.Assembly.GetTypes().First(o => abstractValidatorType.IsAssignableFrom(o)); + + if (classValidatorType != null) + { + IValidator validator = (IValidator)Activator.CreateInstance(classValidatorType); + + //new ValidationContext(instance, new PropertyChain(), ValidatorOptions.Global.ValidatorSelectors.DefaultValidatorSelectorFactory()) + var validationContextType = typeof(ValidationContext<>).MakeGenericType(requestType); + IValidationContext ValidationContext = (IValidationContext)Activator.CreateInstance(validationContextType, arg.Value, new PropertyChain(), ValidatorOptions.Global.ValidatorSelectors.DefaultValidatorSelectorFactory()); + + var validationResult = await validator.ValidateAsync(ValidationContext); + + When.Is(!validationResult.IsValid, validationResult.Errors?.First()?.ErrorMessage); + } + } + } + } +} diff --git a/src/Easy.Result/IEntities.cs b/src/Easy.Result/IEntities.cs new file mode 100644 index 0000000..39bd746 --- /dev/null +++ b/src/Easy.Result/IEntities.cs @@ -0,0 +1,65 @@ +namespace Easy.Result; + +public interface IEntities +{ + /// + /// 返回结果 + /// + public object Data { get; set; } + + + public static IEntities Create(List Data) + { + return new Entites() + { + Data = Data ?? new() + }; + } +} + +public sealed class Entites : IEntities +{ + /// + /// 返回结果 + /// + public object Data { get; set; } +} + +public interface IPaged : IEntities +{ + /// + /// 页面数量 + /// + public long PageCount { get; set; } + /// + /// 页面大小 + /// + public int PageSize { get; set; } + + public static IPaged Create(long PageCount, int PageSize, List Data) + { + return new Paged() + { + PageCount = PageCount, + Data = Data ?? new(), + PageSize = PageSize, + }; + } + +} + +public sealed class Paged : IPaged +{ + /// + /// 页面数量 + /// + public long PageCount { get; set; } + /// + /// 页面大小 + /// + public int PageSize { get; set; } + /// + /// 返回结果 + /// + public object Data { get; set; } +} \ No newline at end of file diff --git a/src/Easy.Result/IEntity.cs b/src/Easy.Result/IEntity.cs new file mode 100644 index 0000000..497da50 --- /dev/null +++ b/src/Easy.Result/IEntity.cs @@ -0,0 +1,10 @@ +using SqlSugar; + +namespace Easy.Result; + +public interface IEntity +{ + [SugarColumn(IsPrimaryKey = true)] + public long Id { get; set; } + +} diff --git a/src/Easy.Result/IModel.cs b/src/Easy.Result/IModel.cs new file mode 100644 index 0000000..66204ea --- /dev/null +++ b/src/Easy.Result/IModel.cs @@ -0,0 +1,9 @@ +using AutoMapper; +using Easy.Result.Common; +namespace Easy.Result; + +public interface IModel +{ + public TEntity ToEntity() where TEntity : IEntity, new() => new Mapper(AutoMapReadonly.ConfigurationProvider).Map(this); + +} diff --git a/src/Easy.Result/Middlewares/ErrApiResMiddleware.cs b/src/Easy.Result/Middlewares/ErrApiResMiddleware.cs new file mode 100644 index 0000000..2a194c7 --- /dev/null +++ b/src/Easy.Result/Middlewares/ErrApiResMiddleware.cs @@ -0,0 +1,51 @@ +using Easy.Result.Exceptions; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Easy.Result.Middlewares; + +public class ErrApiResultMiddleware +{ + private readonly RequestDelegate _next; + + public ErrApiResultMiddleware(RequestDelegate next) + { + _next = next; + } + public async Task InvokeAsync(HttpContext context, ILoggerFactory loggerFactory) + { + if (context.Request.Path.Value.StartsWith("/api")) + { + var logger = loggerFactory.CreateLogger(nameof(ErrApiResultMiddleware)); + try + { + await _next(context); + } + catch (SvcException ex) + { + await context.Response.WriteAsJsonAsync(new ApiResult() + { + Message = ex.Message, + Status = "RETRY", + }); + } + catch (Exception ex) + { + string message = $"ErrMessage = {ex.Message}"; +#pragma warning disable CA2254 // 模板应为静态表达式 + logger.LogError(new EventId(ex.HResult), ex, message); +#pragma warning restore CA2254 // 模板应为静态表达式 + await context.Response.WriteAsJsonAsync(new ApiResult() + { + Message = "出错了,请稍后再试。", + Status = "RETRY", + }); + } + } + else + { + await _next(context); + } + + } +} diff --git a/src/Easy.Result/PagingRequest.cs b/src/Easy.Result/PagingRequest.cs new file mode 100644 index 0000000..598b3d0 --- /dev/null +++ b/src/Easy.Result/PagingRequest.cs @@ -0,0 +1,38 @@ +namespace Easy.Result; + +public record PagingRequest : IModel +{ + private int _pageIndex; + private int _pageSize; + + /// + /// 当前第几页 + /// + public int PageIndex + { + get => _pageIndex; + set + { + if (value <= 0) value = 1; + _pageIndex = value; + } + } + + /// + /// 页面数量 + /// + public int PageSize + { + get => _pageSize; + set + { + if (value <= 9) value = 10; + _pageSize = value; + } + } + + /// + /// 默认是降序 + /// + public bool OrderByDescending { get; set; } = true; +} diff --git a/src/Easy.Result/When.cs b/src/Easy.Result/When.cs new file mode 100644 index 0000000..0f08639 --- /dev/null +++ b/src/Easy.Result/When.cs @@ -0,0 +1,20 @@ +using Easy.Result.Exceptions; + +namespace Easy.Result; + +/// +/// when 语句 +/// +public class When +{ + /// + /// 是 + /// + /// 是否 + /// 消息 + /// 服务异常会被中间件拦截 + public static void Is(bool isOk, string message) + { + if (isOk) throw new SvcException(message); + } +}