Created
April 18, 2021 02:16
-
-
Save maliming/b252b1cf23db538769e87f7434945057 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using Abp.Application.Services; | |
using Abp.AspNetCore.Configuration; | |
using Abp.Extensions; | |
using Castle.Windsor.MsDependencyInjection; | |
using Microsoft.AspNetCore.Mvc; | |
using Microsoft.AspNetCore.Mvc.ApplicationModels; | |
using Microsoft.Extensions.DependencyInjection; | |
using System.Linq; | |
using System.Reflection; | |
using System.Threading.Tasks; | |
using Abp; | |
using Abp.Collections.Extensions; | |
using Abp.Web.Api.ProxyScripting.Generators; | |
using JetBrains.Annotations; | |
using Microsoft.AspNetCore.Mvc.ModelBinding; | |
using Microsoft.AspNetCore.Mvc.ActionConstraints; | |
namespace MyCompanyName.AbpZeroTemplate.Web | |
{ | |
public class MyAbpAppServiceConvention : IApplicationModelConvention | |
{ | |
private readonly Lazy<AbpAspNetCoreConfiguration> _configuration; | |
public MyAbpAppServiceConvention(IServiceCollection services) | |
{ | |
_configuration = new Lazy<AbpAspNetCoreConfiguration>(() => | |
{ | |
return services | |
.GetSingletonService<AbpBootstrapper>() | |
.IocManager | |
.Resolve<AbpAspNetCoreConfiguration>(); | |
}, true); | |
} | |
public void Apply(ApplicationModel application) | |
{ | |
foreach (var controller in application.Controllers) | |
{ | |
var type = controller.ControllerType.AsType(); | |
var configuration = GetControllerSettingOrNull(type); | |
if (typeof(IApplicationService).GetTypeInfo().IsAssignableFrom(type)) | |
{ | |
controller.ControllerName = controller.ControllerName.RemovePostFix(ApplicationService.CommonPostfixes); | |
configuration?.ControllerModelConfigurer(controller); | |
ConfigureCacheControl(controller, _configuration.Value.DefaultResponseCacheAttributeForAppServices); | |
ConfigureArea(controller, configuration); | |
ConfigureRemoteService(controller, configuration); | |
} | |
else | |
{ | |
var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(type.GetTypeInfo()); | |
if (remoteServiceAtt != null && remoteServiceAtt.IsEnabledFor(type)) | |
{ | |
ConfigureCacheControl(controller, _configuration.Value.DefaultResponseCacheAttributeForControllers); | |
ConfigureRemoteService(controller, configuration); | |
} | |
} | |
} | |
} | |
private void ConfigureCacheControl(ControllerModel controller, ResponseCacheAttribute responseCacheAttribute) | |
{ | |
if (responseCacheAttribute == null) | |
{ | |
return; | |
} | |
if (controller.Filters.Any(filter => typeof(ResponseCacheAttribute).IsAssignableFrom(filter.GetType()))) | |
{ | |
return; | |
} | |
controller.Filters.Add(responseCacheAttribute); | |
} | |
private void ConfigureArea(ControllerModel controller, [CanBeNull] AbpControllerAssemblySetting configuration) | |
{ | |
if (configuration == null) | |
{ | |
return; | |
} | |
if (controller.RouteValues.ContainsKey("area")) | |
{ | |
return; | |
} | |
controller.RouteValues["area"] = configuration.ModuleName; | |
} | |
private void ConfigureRemoteService(ControllerModel controller, [CanBeNull] AbpControllerAssemblySetting configuration) | |
{ | |
ConfigureApiExplorer(controller); | |
ConfigureSelector(controller, configuration); | |
ConfigureParameters(controller); | |
} | |
private void ConfigureParameters(ControllerModel controller) | |
{ | |
foreach (var action in controller.Actions) | |
{ | |
foreach (var prm in action.Parameters) | |
{ | |
if (prm.BindingInfo != null) | |
{ | |
continue; | |
} | |
if (!TypeHelper.IsPrimitiveExtendedIncludingNullable(prm.ParameterInfo.ParameterType)) | |
{ | |
if (CanUseFormBodyBinding(action, prm)) | |
{ | |
prm.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromBodyAttribute() }); | |
} | |
} | |
} | |
} | |
} | |
private bool CanUseFormBodyBinding(ActionModel action, ParameterModel parameter) | |
{ | |
if (_configuration.Value.FormBodyBindingIgnoredTypes.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType))) | |
{ | |
return false; | |
} | |
foreach (var selector in action.Selectors) | |
{ | |
if (selector.ActionConstraints == null) | |
{ | |
continue; | |
} | |
foreach (var actionConstraint in selector.ActionConstraints) | |
{ | |
var httpMethodActionConstraint = actionConstraint as HttpMethodActionConstraint; | |
if (httpMethodActionConstraint == null) | |
{ | |
continue; | |
} | |
if (httpMethodActionConstraint.HttpMethods.All(hm => hm.IsIn("GET", "DELETE", "TRACE", "HEAD"))) | |
{ | |
return false; | |
} | |
} | |
} | |
return true; | |
} | |
private void ConfigureApiExplorer(ControllerModel controller) | |
{ | |
if (controller.ApiExplorer.GroupName.IsNullOrEmpty()) | |
{ | |
controller.ApiExplorer.GroupName = controller.ControllerName; | |
} | |
if (controller.ApiExplorer.IsVisible == null) | |
{ | |
var controllerType = controller.ControllerType.AsType(); | |
var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(controllerType.GetTypeInfo()); | |
if (remoteServiceAtt != null) | |
{ | |
controller.ApiExplorer.IsVisible = | |
remoteServiceAtt.IsEnabledFor(controllerType) && | |
remoteServiceAtt.IsMetadataEnabledFor(controllerType); | |
} | |
else | |
{ | |
controller.ApiExplorer.IsVisible = true; | |
} | |
} | |
foreach (var action in controller.Actions) | |
{ | |
ConfigureApiExplorer(action); | |
} | |
} | |
private void ConfigureApiExplorer(ActionModel action) | |
{ | |
if (action.ApiExplorer.IsVisible == null) | |
{ | |
var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(action.ActionMethod); | |
if (remoteServiceAtt != null) | |
{ | |
action.ApiExplorer.IsVisible = | |
remoteServiceAtt.IsEnabledFor(action.ActionMethod) && | |
remoteServiceAtt.IsMetadataEnabledFor(action.ActionMethod); | |
} | |
} | |
} | |
private void ConfigureSelector(ControllerModel controller, [CanBeNull] AbpControllerAssemblySetting configuration) | |
{ | |
RemoveEmptySelectors(controller.Selectors); | |
if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null)) | |
{ | |
return; | |
} | |
var moduleName = GetModuleNameOrDefault(controller.ControllerType.AsType()); | |
foreach (var action in controller.Actions) | |
{ | |
ConfigureSelector(moduleName, controller.ControllerName, action, configuration); | |
} | |
} | |
private void ConfigureSelector(string moduleName, string controllerName, ActionModel action, [CanBeNull] AbpControllerAssemblySetting configuration) | |
{ | |
RemoveEmptySelectors(action.Selectors); | |
var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(action.ActionMethod); | |
if (remoteServiceAtt != null && !remoteServiceAtt.IsEnabledFor(action.ActionMethod)) | |
{ | |
return; | |
} | |
if (!action.Selectors.Any()) | |
{ | |
AddAbpServiceSelector(moduleName, controllerName, action, configuration); | |
} | |
else | |
{ | |
NormalizeSelectorRoutes(moduleName, controllerName, action); | |
} | |
} | |
private void AddAbpServiceSelector(string moduleName, string controllerName, ActionModel action, [CanBeNull] AbpControllerAssemblySetting configuration) | |
{ | |
var abpServiceSelectorModel = new SelectorModel | |
{ | |
AttributeRouteModel = CreateAbpServiceAttributeRouteModel(moduleName, controllerName, action) | |
}; | |
var verb = configuration?.UseConventionalHttpVerbs == true | |
? ProxyScriptingHelper.GetConventionalVerbForMethodName(action.ActionName) | |
: ProxyScriptingHelper.DefaultHttpVerb; | |
abpServiceSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { verb })); | |
action.Selectors.Add(abpServiceSelectorModel); | |
} | |
private static void NormalizeSelectorRoutes(string moduleName, string controllerName, ActionModel action) | |
{ | |
foreach (var selector in action.Selectors) | |
{ | |
if (selector.AttributeRouteModel == null) | |
{ | |
selector.AttributeRouteModel = CreateAbpServiceAttributeRouteModel( | |
moduleName, | |
controllerName, | |
action | |
); | |
} | |
} | |
} | |
private string GetModuleNameOrDefault(Type controllerType) | |
{ | |
return GetControllerSettingOrNull(controllerType)?.ModuleName ?? | |
AbpControllerAssemblySetting.DefaultServiceModuleName; | |
} | |
[CanBeNull] | |
private AbpControllerAssemblySetting GetControllerSettingOrNull(Type controllerType) | |
{ | |
var settings = _configuration.Value.ControllerAssemblySettings.GetSettings(controllerType); | |
return settings.FirstOrDefault(setting => setting.TypePredicate(controllerType)); | |
} | |
private static AttributeRouteModel CreateAbpServiceAttributeRouteModel(string moduleName, string controllerName, ActionModel action) | |
{ | |
return new AttributeRouteModel( | |
new RouteAttribute( | |
$"api/services/{moduleName}/{controllerName}/{action.ActionName}" | |
) | |
); | |
} | |
private static void RemoveEmptySelectors(IList<SelectorModel> selectors) | |
{ | |
selectors | |
.Where(IsEmptySelector) | |
.ToList() | |
.ForEach(s => selectors.Remove(s)); | |
} | |
private static bool IsEmptySelector(SelectorModel selector) | |
{ | |
return selector.AttributeRouteModel == null && | |
selector.ActionConstraints.IsNullOrEmpty() && | |
selector.EndpointMetadata.IsNullOrEmpty(); | |
} | |
} | |
internal static class ReflectionHelper | |
{ | |
/// <summary> | |
/// Checks whether <paramref name="givenType"/> implements/inherits <paramref name="genericType"/>. | |
/// </summary> | |
/// <param name="givenType">Type to check</param> | |
/// <param name="genericType">Generic type</param> | |
public static bool IsAssignableToGenericType(Type givenType, Type genericType) | |
{ | |
var givenTypeInfo = givenType.GetTypeInfo(); | |
if (givenTypeInfo.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) | |
{ | |
return true; | |
} | |
foreach (var interfaceType in givenType.GetInterfaces()) | |
{ | |
if (interfaceType.GetTypeInfo().IsGenericType && interfaceType.GetGenericTypeDefinition() == genericType) | |
{ | |
return true; | |
} | |
} | |
if (givenTypeInfo.BaseType == null) | |
{ | |
return false; | |
} | |
return IsAssignableToGenericType(givenTypeInfo.BaseType, genericType); | |
} | |
/// <summary> | |
/// Gets a list of attributes defined for a class member and it's declaring type including inherited attributes. | |
/// </summary> | |
/// <param name="inherit">Inherit attribute from base classes</param> | |
/// <param name="memberInfo">MemberInfo</param> | |
public static List<object> GetAttributesOfMemberAndDeclaringType(MemberInfo memberInfo, bool inherit = true) | |
{ | |
var attributeList = new List<object>(); | |
attributeList.AddRange(memberInfo.GetCustomAttributes(inherit)); | |
if (memberInfo.DeclaringType != null) | |
{ | |
attributeList.AddRange(memberInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(inherit)); | |
} | |
return attributeList; | |
} | |
/// <summary> | |
/// Gets a list of attributes defined for a class member and type including inherited attributes. | |
/// </summary> | |
/// <param name="memberInfo">MemberInfo</param> | |
/// <param name="type">Type</param> | |
/// <param name="inherit">Inherit attribute from base classes</param> | |
public static List<object> GetAttributesOfMemberAndType(MemberInfo memberInfo, Type type, bool inherit = true) | |
{ | |
var attributeList = new List<object>(); | |
attributeList.AddRange(memberInfo.GetCustomAttributes(inherit)); | |
attributeList.AddRange(type.GetTypeInfo().GetCustomAttributes(inherit)); | |
return attributeList; | |
} | |
/// <summary> | |
/// Gets a list of attributes defined for a class member and it's declaring type including inherited attributes. | |
/// </summary> | |
/// <typeparam name="TAttribute">Type of the attribute</typeparam> | |
/// <param name="memberInfo">MemberInfo</param> | |
/// <param name="inherit">Inherit attribute from base classes</param> | |
public static List<TAttribute> GetAttributesOfMemberAndDeclaringType<TAttribute>(MemberInfo memberInfo, bool inherit = true) | |
where TAttribute : Attribute | |
{ | |
var attributeList = new List<TAttribute>(); | |
if (memberInfo.IsDefined(typeof(TAttribute), inherit)) | |
{ | |
attributeList.AddRange(memberInfo.GetCustomAttributes(typeof(TAttribute), inherit).Cast<TAttribute>()); | |
} | |
if (memberInfo.DeclaringType != null && memberInfo.DeclaringType.GetTypeInfo().IsDefined(typeof(TAttribute), inherit)) | |
{ | |
attributeList.AddRange(memberInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(typeof(TAttribute), inherit).Cast<TAttribute>()); | |
} | |
return attributeList; | |
} | |
/// <summary> | |
/// Gets a list of attributes defined for a class member and type including inherited attributes. | |
/// </summary> | |
/// <typeparam name="TAttribute">Type of the attribute</typeparam> | |
/// <param name="memberInfo">MemberInfo</param> | |
/// <param name="type">Type</param> | |
/// <param name="inherit">Inherit attribute from base classes</param> | |
public static List<TAttribute> GetAttributesOfMemberAndType<TAttribute>(MemberInfo memberInfo, Type type, bool inherit = true) | |
where TAttribute : Attribute | |
{ | |
var attributeList = new List<TAttribute>(); | |
if (memberInfo.IsDefined(typeof(TAttribute), inherit)) | |
{ | |
attributeList.AddRange(memberInfo.GetCustomAttributes(typeof(TAttribute), inherit).Cast<TAttribute>()); | |
} | |
if (type.GetTypeInfo().IsDefined(typeof(TAttribute), inherit)) | |
{ | |
attributeList.AddRange(type.GetTypeInfo().GetCustomAttributes(typeof(TAttribute), inherit).Cast<TAttribute>()); | |
} | |
return attributeList; | |
} | |
/// <summary> | |
/// Tries to gets an of attribute defined for a class member and it's declaring type including inherited attributes. | |
/// Returns default value if it's not declared at all. | |
/// </summary> | |
/// <typeparam name="TAttribute">Type of the attribute</typeparam> | |
/// <param name="memberInfo">MemberInfo</param> | |
/// <param name="defaultValue">Default value (null as default)</param> | |
/// <param name="inherit">Inherit attribute from base classes</param> | |
public static TAttribute GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<TAttribute>(MemberInfo memberInfo, TAttribute defaultValue = default(TAttribute), bool inherit = true) | |
where TAttribute : class | |
{ | |
return memberInfo.GetCustomAttributes(true).OfType<TAttribute>().FirstOrDefault() | |
?? memberInfo.ReflectedType?.GetTypeInfo().GetCustomAttributes(true).OfType<TAttribute>().FirstOrDefault() | |
?? defaultValue; | |
} | |
/// <summary> | |
/// Tries to gets an of attribute defined for a class member and it's declaring type including inherited attributes. | |
/// Returns default value if it's not declared at all. | |
/// </summary> | |
/// <typeparam name="TAttribute">Type of the attribute</typeparam> | |
/// <param name="memberInfo">MemberInfo</param> | |
/// <param name="defaultValue">Default value (null as default)</param> | |
/// <param name="inherit">Inherit attribute from base classes</param> | |
public static TAttribute GetSingleAttributeOrDefault<TAttribute>(MemberInfo memberInfo, TAttribute defaultValue = default(TAttribute), bool inherit = true) | |
where TAttribute : Attribute | |
{ | |
//Get attribute on the member | |
if (memberInfo.IsDefined(typeof(TAttribute), inherit)) | |
{ | |
return memberInfo.GetCustomAttributes(typeof(TAttribute), inherit).Cast<TAttribute>().First(); | |
} | |
return defaultValue; | |
} | |
/// <summary> | |
/// Gets a property by it's full path from given object | |
/// </summary> | |
/// <param name="obj">Object to get value from</param> | |
/// <param name="objectType">Type of given object</param> | |
/// <param name="propertyPath">Full path of property</param> | |
/// <returns></returns> | |
internal static object GetPropertyByPath(object obj, Type objectType, string propertyPath) | |
{ | |
var property = obj; | |
var currentType = objectType; | |
var objectPath = currentType.FullName; | |
var absolutePropertyPath = propertyPath; | |
if (absolutePropertyPath.StartsWith(objectPath)) | |
{ | |
absolutePropertyPath = absolutePropertyPath.Replace(objectPath + ".", ""); | |
} | |
foreach (var propertyName in absolutePropertyPath.Split('.')) | |
{ | |
property = currentType.GetProperty(propertyName); | |
currentType = ((PropertyInfo) property).PropertyType; | |
} | |
return property; | |
} | |
/// <summary> | |
/// Gets value of a property by it's full path from given object | |
/// </summary> | |
/// <param name="obj">Object to get value from</param> | |
/// <param name="objectType">Type of given object</param> | |
/// <param name="propertyPath">Full path of property</param> | |
/// <returns></returns> | |
internal static object GetValueByPath(object obj, Type objectType, string propertyPath) | |
{ | |
var value = obj; | |
var currentType = objectType; | |
var objectPath = currentType.FullName; | |
var absolutePropertyPath = propertyPath; | |
if (absolutePropertyPath.StartsWith(objectPath)) | |
{ | |
absolutePropertyPath = absolutePropertyPath.Replace(objectPath + ".", ""); | |
} | |
foreach (var propertyName in absolutePropertyPath.Split('.')) | |
{ | |
var property = currentType.GetProperty(propertyName); | |
value = property.GetValue(value, null); | |
currentType = property.PropertyType; | |
} | |
return value; | |
} | |
/// <summary> | |
/// Sets value of a property by it's full path on given object | |
/// </summary> | |
/// <param name="obj"></param> | |
/// <param name="objectType"></param> | |
/// <param name="propertyPath"></param> | |
/// <param name="value"></param> | |
internal static void SetValueByPath(object obj, Type objectType, string propertyPath, object value) | |
{ | |
var currentType = objectType; | |
PropertyInfo property; | |
var objectPath = currentType.FullName; | |
var absolutePropertyPath = propertyPath; | |
if (absolutePropertyPath.StartsWith(objectPath)) | |
{ | |
absolutePropertyPath = absolutePropertyPath.Replace(objectPath + ".", ""); | |
} | |
var properties = absolutePropertyPath.Split('.'); | |
if (properties.Length == 1) | |
{ | |
property = objectType.GetProperty(properties.First()); | |
property.SetValue(obj, value); | |
return; | |
} | |
for (int i = 0; i < properties.Length - 1; i++) | |
{ | |
property = currentType.GetProperty(properties[i]); | |
obj = property.GetValue(obj, null); | |
currentType = property.PropertyType; | |
} | |
property = currentType.GetProperty(properties.Last()); | |
property.SetValue(obj, value); | |
} | |
internal static bool IsPropertyGetterSetterMethod(MethodInfo method, Type type) | |
{ | |
if (!method.IsSpecialName) | |
{ | |
return false; | |
} | |
if (method.Name.Length < 5) | |
{ | |
return false; | |
} | |
return type.GetProperty(method.Name.Substring(4), BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic) != null; | |
} | |
internal static async Task<object> InvokeAsync(MethodInfo method, object obj, params object[] parameters) | |
{ | |
var task = (Task)method.Invoke(obj, parameters); | |
await task; | |
var resultProperty = task.GetType().GetProperty("Result"); | |
return resultProperty?.GetValue(task); | |
} | |
} | |
internal static class TypeHelper | |
{ | |
public static bool IsFunc(object obj) | |
{ | |
if (obj == null) | |
{ | |
return false; | |
} | |
var type = obj.GetType(); | |
if (!type.GetTypeInfo().IsGenericType) | |
{ | |
return false; | |
} | |
return type.GetGenericTypeDefinition() == typeof(Func<>); | |
} | |
public static bool IsFunc<TReturn>(object obj) | |
{ | |
return obj != null && obj.GetType() == typeof(Func<TReturn>); | |
} | |
public static bool IsPrimitiveExtendedIncludingNullable(Type type, bool includeEnums = false) | |
{ | |
if (IsPrimitiveExtended(type, includeEnums)) | |
{ | |
return true; | |
} | |
if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) | |
{ | |
return IsPrimitiveExtended(type.GenericTypeArguments[0], includeEnums); | |
} | |
return false; | |
} | |
private static bool IsPrimitiveExtended(Type type, bool includeEnums) | |
{ | |
if (type.GetTypeInfo().IsPrimitive) | |
{ | |
return true; | |
} | |
if (includeEnums && type.GetTypeInfo().IsEnum) | |
{ | |
return true; | |
} | |
return type == typeof (string) || | |
type == typeof (decimal) || | |
type == typeof (DateTime) || | |
type == typeof (DateTimeOffset) || | |
type == typeof (TimeSpan) || | |
type == typeof (Guid); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment