Skip to content

Instantly share code, notes, and snippets.

@maliming
Created April 18, 2021 02:16
Show Gist options
  • Save maliming/b252b1cf23db538769e87f7434945057 to your computer and use it in GitHub Desktop.
Save maliming/b252b1cf23db538769e87f7434945057 to your computer and use it in GitHub Desktop.
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