初始刷
This commit is contained in:
225
ZR.Common/DynamicApiSimple/ApiConvention.cs
Normal file
225
ZR.Common/DynamicApiSimple/ApiConvention.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
using Infrastructure.Helper;
|
||||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace ZR.Common.DynamicApiSimple;
|
||||
|
||||
class ApiConvention : IApplicationModelConvention
|
||||
{
|
||||
public void Apply(ApplicationModel application)
|
||||
{
|
||||
foreach (var controller in application.Controllers)
|
||||
{
|
||||
var type = controller.ControllerType;
|
||||
if (typeof(IDynamicApi).IsAssignableFrom(type) || type.IsDefined(typeof(DynamicApiAttribute), true))
|
||||
{
|
||||
ClearAction(controller);
|
||||
ConfigureApiExplorer(controller);
|
||||
ConfigureSelector(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearAction(ControllerModel controller)
|
||||
{
|
||||
Type genericBaseType = AssemblyUtils.GetGenericTypeByName("BaseService`1");
|
||||
var needRemoveAction = controller.Actions
|
||||
.Where(action => !action.ActionMethod.DeclaringType.IsDerivedFromGenericBaseRepository(genericBaseType))
|
||||
.ToList();
|
||||
|
||||
foreach (var actionModel in needRemoveAction)
|
||||
{
|
||||
controller.Actions.Remove(actionModel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureSelector(ControllerModel controller)
|
||||
{
|
||||
RemoveEmptySelectors(controller.Selectors);
|
||||
|
||||
if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null))
|
||||
return;
|
||||
|
||||
foreach (var action in controller.Actions)
|
||||
{
|
||||
ConfigureSelector(action);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveEmptySelectors(IList<SelectorModel> selectors)
|
||||
{
|
||||
for (var i = selectors.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var selector = selectors[i];
|
||||
if (selector.AttributeRouteModel == null &&
|
||||
(selector.ActionConstraints == null || selector.ActionConstraints.Count <= 0) &&
|
||||
(selector.EndpointMetadata == null || selector.EndpointMetadata.Count <= 0))
|
||||
{
|
||||
selectors.Remove(selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureSelector(ActionModel action)
|
||||
{
|
||||
RemoveEmptySelectors(action.Selectors);
|
||||
|
||||
if (action.Selectors.Count <= 0)
|
||||
AddServiceSelector(action);
|
||||
else
|
||||
NormalizeSelectorRoutes(action);
|
||||
}
|
||||
|
||||
private void AddServiceSelector(ActionModel action)
|
||||
{
|
||||
var template = new Microsoft.AspNetCore.Mvc.RouteAttribute(GetRouteTemplate(action));
|
||||
var selector = new SelectorModel
|
||||
{
|
||||
AttributeRouteModel = new AttributeRouteModel(template)
|
||||
};
|
||||
selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { GetHttpMethod(action) }));
|
||||
action.Selectors.Add(selector);
|
||||
}
|
||||
|
||||
private void NormalizeSelectorRoutes(ActionModel action)
|
||||
{
|
||||
foreach (var selector in action.Selectors)
|
||||
{
|
||||
var template = new Microsoft.AspNetCore.Mvc.RouteAttribute(GetRouteTemplate(action,selector));
|
||||
selector.AttributeRouteModel = new AttributeRouteModel(template);
|
||||
if (selector.ActionConstraints.OfType<HttpMethodActionConstraint>().FirstOrDefault()?.HttpMethods?.FirstOrDefault() == null)
|
||||
selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { GetHttpMethod(action) }));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private string GetRouteTemplate(ActionModel action,SelectorModel selectorModel=null)
|
||||
{
|
||||
var routeTemplate = new StringBuilder();
|
||||
var names = action.Controller.ControllerType.Namespace.Split('.');
|
||||
if (names.Length > 2)
|
||||
{
|
||||
routeTemplate.Append(names[^2]);
|
||||
}
|
||||
|
||||
// Controller
|
||||
var controllerName = action.Controller.ControllerName;
|
||||
if (controllerName.EndsWith("Service"))
|
||||
controllerName = controllerName[0..^7];
|
||||
|
||||
if (selectorModel is { AttributeRouteModel: not null })
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(selectorModel.AttributeRouteModel?.Template))
|
||||
{
|
||||
if (selectorModel.AttributeRouteModel.Template.StartsWith("/"))
|
||||
{
|
||||
routeTemplate.Append(selectorModel.AttributeRouteModel.Template);
|
||||
}
|
||||
else
|
||||
{
|
||||
routeTemplate.Append($"{BaseRoute}/{controllerName}/{selectorModel.AttributeRouteModel.Template}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
routeTemplate.Append($"{BaseRoute}/{controllerName}");
|
||||
|
||||
// Action
|
||||
var actionName = action.ActionName;
|
||||
if (actionName.EndsWith("Async") || actionName.EndsWith("async"))
|
||||
actionName = actionName[..^"Async".Length];
|
||||
|
||||
if (!string.IsNullOrEmpty(actionName))
|
||||
{
|
||||
routeTemplate.Append($"/{RemoveHttpMethodPrefix(actionName)}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return routeTemplate.ToString();
|
||||
}
|
||||
|
||||
private static string GetHttpMethod(ActionModel action)
|
||||
{
|
||||
var actionName = action.ActionName.ToLower();
|
||||
string Method = string.Empty;
|
||||
if (!string.IsNullOrEmpty(actionName))
|
||||
{
|
||||
Method = GetName(actionName);
|
||||
}
|
||||
return Method;
|
||||
}
|
||||
|
||||
private static string GetName(string actionName)
|
||||
{
|
||||
string result = "POST";
|
||||
foreach (string key in Methods.Keys)
|
||||
{
|
||||
if (actionName.Contains(key))
|
||||
{
|
||||
result = Methods[key];
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
internal static Dictionary<string, string> Methods { get; private set; }
|
||||
internal static string BaseRoute { get; private set; } = "api";
|
||||
static ApiConvention()
|
||||
{
|
||||
Methods = new Dictionary<string, string>()
|
||||
{
|
||||
|
||||
["get"] = "GET",
|
||||
["find"] = "GET",
|
||||
["fetch"] = "GET",
|
||||
["query"] = "GET",
|
||||
["post"] = "POST",
|
||||
["add"] = "POST",
|
||||
["create"] = "POST",
|
||||
["insert"] = "POST",
|
||||
["submit"] = "POST",
|
||||
["put"] = "POST",
|
||||
["update"] = "POST",
|
||||
["delete"] = "DELETE",
|
||||
["remove"] = "DELETE",
|
||||
["clear"] = "DELETE",
|
||||
["patch"] = "PATCH"
|
||||
};
|
||||
|
||||
}
|
||||
private static string RemoveHttpMethodPrefix(string actionName)
|
||||
{
|
||||
foreach (var method in Methods.Keys)
|
||||
{
|
||||
if (actionName.StartsWith(method, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 移除前缀并返回结果
|
||||
return actionName.Substring(method.Length);
|
||||
}
|
||||
}
|
||||
|
||||
return actionName; // 如果没有找到前缀,返回原始名称
|
||||
}
|
||||
}
|
||||
28
ZR.Common/DynamicApiSimple/ApiFeatureProvider.cs
Normal file
28
ZR.Common/DynamicApiSimple/ApiFeatureProvider.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
|
||||
namespace ZR.Common.DynamicApiSimple;
|
||||
|
||||
class ApiFeatureProvider : ControllerFeatureProvider
|
||||
{
|
||||
protected override bool IsController(TypeInfo typeInfo)
|
||||
{
|
||||
Type type = typeInfo.AsType();
|
||||
// 不能是非公开的、值类型、抽象类、泛型类或基元类型
|
||||
if (!type.IsPublic || type.IsValueType || type.IsAbstract || type.IsGenericType || type.IsPrimitive || string.IsNullOrWhiteSpace(type.Namespace)) return false;
|
||||
|
||||
// 原生层或者实现IDynamicApiController(类),[DynamicApi](接口)
|
||||
if ((!typeof(Controller).IsAssignableFrom(type) && typeof(ControllerBase).IsAssignableFrom(type)) || type.IsDefined(typeof(DynamicApiAttribute), true) || typeof(IDynamicApi).IsAssignableFrom(type))
|
||||
{
|
||||
// 如果是忽略的则跳过自定义的接口在前面会报错,所以必须在后面
|
||||
if (type.IsDefined(typeof(ApiExplorerSettingsAttribute), true) && type.GetCustomAttribute<ApiExplorerSettingsAttribute>(true).IgnoreApi)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
24
ZR.Common/DynamicApiSimple/DynamicApiAttribute.cs
Normal file
24
ZR.Common/DynamicApiSimple/DynamicApiAttribute.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace ZR.Common.DynamicApiSimple
|
||||
{
|
||||
/// <summary>
|
||||
/// 动态api属性
|
||||
/// </summary>
|
||||
public class DynamicApiAttribute : Attribute
|
||||
{
|
||||
public string Name;
|
||||
public string Order;
|
||||
public string Description;
|
||||
public DynamicApiAttribute()
|
||||
{
|
||||
|
||||
}
|
||||
public DynamicApiAttribute(string _name, string _order, string _description)
|
||||
{
|
||||
Name = _name;
|
||||
Order = _order;
|
||||
Description = _description;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
ZR.Common/DynamicApiSimple/Extens/DynamicApiExtens.cs
Normal file
52
ZR.Common/DynamicApiSimple/Extens/DynamicApiExtens.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Infrastructure.Helper;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ZR.Common.DynamicApiSimple.Extens
|
||||
{
|
||||
public static class DynamicApiExtens
|
||||
{
|
||||
public static string TIME_FORMAT_FULL = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/// <summary>
|
||||
/// 注入动态api
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddDynamicApi(this IServiceCollection services)
|
||||
{
|
||||
services.AddMvc()
|
||||
.ConfigureApplicationPartManager(m =>
|
||||
{
|
||||
foreach (Assembly assembly in AssemblyUtils.GetAssemblies())
|
||||
{
|
||||
|
||||
if (m.ApplicationParts.Any(it => it.Name.Equals(assembly.FullName.Split(',')[0]))) continue;
|
||||
|
||||
m.ApplicationParts.Add(new AssemblyPart(assembly));
|
||||
}
|
||||
m.FeatureProviders.Add(new ApiFeatureProvider());
|
||||
}).AddNewtonsoftJson(options =>
|
||||
{
|
||||
options.SerializerSettings.DateFormatString = TIME_FORMAT_FULL;
|
||||
options.SerializerSettings.Converters.Add(new IsoDateTimeConverter
|
||||
{
|
||||
DateTimeFormat = TIME_FORMAT_FULL,
|
||||
});
|
||||
// 设置为驼峰命名
|
||||
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
|
||||
});
|
||||
|
||||
services.Configure<MvcOptions>(o =>
|
||||
{
|
||||
o.Conventions.Add(new ApiConvention());
|
||||
});
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
ZR.Common/DynamicApiSimple/IDynamicApi.cs
Normal file
6
ZR.Common/DynamicApiSimple/IDynamicApi.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace ZR.Common.DynamicApiSimple
|
||||
{
|
||||
public interface IDynamicApi
|
||||
{
|
||||
}
|
||||
}
|
||||
49
ZR.Common/DynamicApiSimple/JsonModelBinder.cs
Normal file
49
ZR.Common/DynamicApiSimple/JsonModelBinder.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ZR.Common.DynamicApiSimple;
|
||||
|
||||
public class JsonModelBinder : IModelBinder
|
||||
{
|
||||
private readonly IModelBinder _fallbackBinder;
|
||||
|
||||
public JsonModelBinder(IModelBinder fallbackBinder)
|
||||
{
|
||||
_fallbackBinder = fallbackBinder;
|
||||
}
|
||||
|
||||
public async Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
var request = bindingContext.HttpContext.Request;
|
||||
if ((request.Method == "POST" || request.Method == "PUT") && request.ContentType != null && request.ContentType.Contains("application/json"))
|
||||
{
|
||||
using (var reader = new StreamReader(request.Body))
|
||||
{
|
||||
var body = await reader.ReadToEndAsync();
|
||||
if (!string.IsNullOrEmpty(body))
|
||||
{
|
||||
var result = JsonConvert.DeserializeObject(body, bindingContext.ModelType);
|
||||
bindingContext.Result = ModelBindingResult.Success(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_fallbackBinder != null)
|
||||
{
|
||||
await _fallbackBinder.BindModelAsync(bindingContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user