AbpAspNetCoreMvcModule
using Volo.Abp.UI;
using Volo.Abp.UI.Navigation;
using Volo.Abp.Validation.Localization;
namespace Volo.Abp.AspNetCore.Mvc;
[DependsOn(
typeof(AbpAspNetCoreModule),
typeof(AbpLocalizationModule),
typeof(AbpApiVersioningAbstractionsModule),
typeof(AbpAspNetCoreMvcContractsModule),
typeof(AbpUiNavigationModule),
typeof(AbpGlobalFeaturesModule),
typeof(AbpDddApplicationModule),
typeof(AbpJsonSystemTextJsonModule)
)]
public class AbpAspNetCoreMvcModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
//排除拦截器要排除的类型 ControllerBase 不加拦截器,包括约定注入的
DynamicProxyIgnoreTypes.Add<ControllerBase>();
DynamicProxyIgnoreTypes.Add<PageModel>();
DynamicProxyIgnoreTypes.Add<ViewComponent>();
context.Services.AddConventionalRegistrar(
new AbpAspNetCoreMvcConventionalRegistrar());
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpApiDescriptionModelOptions>(options =>
{
options.IgnoredInterfaces.AddIfNotContains(typeof(IAsyncActionFilter));
options.IgnoredInterfaces.AddIfNotContains(typeof(IFilterMetadata));
options.IgnoredInterfaces.AddIfNotContains(typeof(IActionFilter));
});
Configure<AbpRemoteServiceApiDescriptionProviderOptions>(options =>
{
var statusCodes = new List<int>
{
(int) HttpStatusCode.Forbidden,
(int) HttpStatusCode.Unauthorized,
(int) HttpStatusCode.BadRequest,
(int) HttpStatusCode.NotFound,
(int) HttpStatusCode.NotImplemented,
(int) HttpStatusCode.InternalServerError
};
options.SupportedResponseTypes.AddIfNotContains(
statusCodes.Select(statusCode => new ApiResponseType
{
Type = typeof(RemoteServiceErrorResponse),
StatusCode = statusCode
}));
});
context.Services.PostConfigure<AbpAspNetCoreMvcOptions>(options =>
{
if (options.MinifyGeneratedScript == null)
{
options.MinifyGeneratedScript = context.Services.
GetHostingEnvironment().IsProduction();
}
});
var mvcCoreBuilder = context.Services.AddMvcCore(options =>
{
options.Filters.Add(new AbpAutoValidateAntiforgeryTokenAttribute());
});
context.Services.ExecutePreConfiguredActions(mvcCoreBuilder);
var abpMvcDataAnnotationsLocalizationOptions = context.Services
.ExecutePreConfiguredActions(
new AbpMvcDataAnnotationsLocalizationOptions()
);
context.Services
.AddSingleton<IOptions<AbpMvcDataAnnotationsLocalizationOptions>>(
new OptionsWrapper<AbpMvcDataAnnotationsLocalizationOptions>(
abpMvcDataAnnotationsLocalizationOptions
)
);
var mvcBuilder = context.Services.AddMvc()
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) =>
{
var resourceType = abpMvcDataAnnotationsLocalizationOptions
.AssemblyResources
.GetOrDefault(type.Assembly);
if (resourceType != null)
{
return factory.Create(resourceType);
}
return factory.CreateDefaultOrNull() ??
factory.Create(type);
};
})
.AddViewLocalization(); //TODO: How to configure from
//the application? Also, consider to move to a UI module
//since APIs does not care about it.
if (context.Services.GetHostingEnvironment().IsDevelopment() &&
context.Services.ExecutePreConfiguredActions<AbpAspNetCoreMvcOptions>().
EnableRazorRuntimeCompilationOnDevelopment)
{
mvcCoreBuilder.AddAbpRazorRuntimeCompilation();
}
mvcCoreBuilder.AddAbpJson();
context.Services.ExecutePreConfiguredActions(mvcBuilder);
//TODO: AddViewLocalization by default..?
context.Services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
//Use DI to create controllers
mvcBuilder.AddControllersAsServices();
//Use DI to create view components
mvcBuilder.AddViewComponentsAsServices();
//Use DI to create razor page
context.Services.Replace(ServiceDescriptor.Singleton<
IPageModelActivatorProvider, ServiceBasedPageModelActivatorProvider>());
//Add feature providers
var partManager = context.Services.GetSingletonInstance<ApplicationPartManager>();
var application = context.Services.GetSingletonInstance<IAbpApplication>();
partManager.FeatureProviders.
Add(new AbpConventionalControllerFeatureProvider(application));
partManager.ApplicationParts.
AddIfNotContains(typeof(AbpAspNetCoreMvcModule).Assembly);
context.Services.Replace(ServiceDescriptor.Singleton<IValidationAttributeAdapterProvider,
AbpValidationAttributeAdapterProvider>());
context.Services.AddSingleton<ValidationAttributeAdapterProvider>();
context.Services.AddOptions<MvcOptions>()
.Configure<IServiceProvider>((mvcOptions, serviceProvider) =>
{
mvcOptions.AddAbp(context.Services);
// serviceProvider is root service provider.
var stringLocalizer = serviceProvider.
GetRequiredService<IStringLocalizer<AbpValidationResource>>();
mvcOptions.ModelBindingMessageProvider.
SetValueIsInvalidAccessor(_ => stringLocalizer["The value '{0}' is invalid."]);
mvcOptions.ModelBindingMessageProvider.
SetNonPropertyValueMustBeANumberAccessor(()
=> stringLocalizer["The field must be a number."]);
mvcOptions.ModelBindingMessageProvider.
SetValueMustBeANumberAccessor(value =>
stringLocalizer["The field {0} must be a number.", value]);
});
Configure<AbpEndpointRouterOptions>(options =>
{
options.EndpointConfigureActions.Add(endpointContext =>
{
endpointContext.Endpoints.MapControllerRoute(
"defaultWithArea", "{area}/{controller=Home}/{action=Index}/{id?}");
endpointContext.Endpoints.MapControllerRoute(
"default", "{controller=Home}/{action=Index}/{id?}");
endpointContext.Endpoints.MapRazorPages();
});
});
Configure<DynamicJavaScriptProxyOptions>(options =>
{
options.DisableModule("abp");
});
context.Services.Replace(ServiceDescriptor.Singleton<
IHttpResponseStreamWriterFactory, AbpMemoryPoolHttpResponseStreamWriterFactory>());
}
public override void PostConfigureServices(ServiceConfigurationContext context)
{
ApplicationPartSorter.Sort(
context.Services.GetSingletonInstance<ApplicationPartManager>(),
context.Services.GetSingletonInstance<IModuleContainer>()
);
}
public override void OnApplicationInitialization(
ApplicationInitializationContext context)
{
AddApplicationParts(context);
}
private static void AddApplicationParts(ApplicationInitializationContext context)
{
var partManager = context.ServiceProvider.GetService<ApplicationPartManager>();
if (partManager == null)
{
return;
}
//Plugin modules
var moduleAssemblies = context
.ServiceProvider
.GetRequiredService<IModuleContainer>()
.Modules
.Where(m => m.IsLoadedAsPlugIn)
.Select(m => m.Type.Assembly)
.Distinct();
AddToApplicationParts(partManager, moduleAssemblies);
//Controllers for application services
var controllerAssemblies = context
.ServiceProvider
.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>()
.Value
.ConventionalControllers
.ConventionalControllerSettings
.Select(s => s.Assembly)
.Distinct();
AddToApplicationParts(partManager, controllerAssemblies);
}
private static void AddToApplicationParts(
ApplicationPartManager partManager, IEnumerable<Assembly> moduleAssemblies)
{
foreach (var moduleAssembly in moduleAssemblies)
{
partManager.ApplicationParts.AddIfNotContains(moduleAssembly);
}
}
}
abp 注册的过滤器
internal static class AbpMvcOptionsExtensions{
private static void AddActionFilters(MvcOptions options)
{
//abp 通过服务的方式加入全局的过滤器,这些过滤器必须要要注入容器中,才能使用
options.Filters.AddService(typeof(GlobalFeatureActionFilter));
options.Filters.AddService(typeof(AbpAuditActionFilter));
options.Filters.AddService(typeof(AbpNoContentActionFilter));
options.Filters.AddService(typeof(AbpFeatureActionFilter));
options.Filters.AddService(typeof(AbpValidationActionFilter));
options.Filters.AddService(typeof(AbpUowActionFilter));
options.Filters.AddService(typeof(AbpExceptionFilter));
}
}
注入 DI 的异常过滤器 ,此过滤器会把异常包装为 RemoteServiceErrorInfo 返回
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
public class AbpExceptionFilter : IAsyncExceptionFilter, ITransientDependency
{
public virtual async Task OnExceptionAsync(ExceptionContext context)
{
if (!ShouldHandleException(context))
{
LogException(context, out _);
return;
}
await HandleAndWrapException(context);
}
//只对 controller 返回 json 的 ajax 请求进行过滤
protected virtual bool ShouldHandleException(ExceptionContext context)
{
//TODO: Create DontWrap attribute to control wrapping..?
if (context.ActionDescriptor.IsControllerAction() &&
context.ActionDescriptor.HasObjectResult())
{
return true;
}
if (context.HttpContext.Request.CanAccept(MimeTypes.Application.Json))
{
return true;
}
if (context.HttpContext.Request.IsAjax())
{
return true;
}
return false;
}
protected virtual async Task HandleAndWrapException(ExceptionContext context)
{
//TODO: Trigger an AbpExceptionHandled event or something like that.
LogException(context, out var remoteServiceErrorInfo);
await context.GetRequiredService<IExceptionNotifier>().NotifyAsync(
new ExceptionNotificationContext(context.Exception));
if (context.Exception is AbpAuthorizationException)
{
await context.HttpContext.RequestServices.
GetRequiredService<IAbpAuthorizationExceptionHandler>()
.HandleAsync(context.Exception.
As<AbpAuthorizationException>(), context.HttpContext);
}
else
{
//生成状态码
context.HttpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true");
context.HttpContext.Response.StatusCode = (int)context
.GetRequiredService<IHttpExceptionStatusCodeFinder>()
.GetStatusCode(context.HttpContext, context.Exception);
//包装响应内容
context.Result = new ObjectResult(
new RemoteServiceErrorResponse(remoteServiceErrorInfo));
}
context.Exception = null!; //Handled!
}
protected virtual void LogException(
ExceptionContext context, out RemoteServiceErrorInfo remoteServiceErrorInfo)
{
var exceptionHandlingOptions =
context.GetRequiredService<IOptions<AbpExceptionHandlingOptions>>().Value;
var exceptionToErrorInfoConverter =
context.GetRequiredService<IExceptionToErrorInfoConverter>();
remoteServiceErrorInfo =
exceptionToErrorInfoConverter.Convert(context.Exception, options =>
{
options.SendExceptionsDetailsToClients =
exceptionHandlingOptions.SendExceptionsDetailsToClients;
options.SendStackTraceToClients =
exceptionHandlingOptions.SendStackTraceToClients;
});
var remoteServiceErrorInfoBuilder = new StringBuilder();
remoteServiceErrorInfoBuilder.
AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------");
remoteServiceErrorInfoBuilder.
AppendLine(context.GetRequiredService<IJsonSerializer>().
Serialize(remoteServiceErrorInfo, indented: true));
var logger = context.
GetService<ILogger<AbpExceptionFilter>>(NullLogger<AbpExceptionFilter>.Instance)!;
var logLevel = context.Exception.GetLogLevel();
logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString());
logger.LogException(context.Exception, logLevel);
}
}
应用服务转化为 controller
asp.net core 有俩种生成 controller 的方式:
- ServiceBasedControllerActivator : IControllerActivator
- DefaultControllerActivator : IControllerActivator
abp 选择了 controller 作为 ServiceBasedControllerActivator
context.Services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
//Use DI to create controllers
mvcBuilder.AddControllersAsServices();
asp.net core
public static IMvcBuilder AddControllersAsServices(this IMvcBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
var feature = new ControllerFeature();
//在部件中获取所有 ControllerFeature
builder.PartManager.PopulateFeature(feature);
foreach (var controller in feature.Controllers.Select(c => c.AsType()))
{
//把 controller 加入 di
builder.Services.TryAddTransient(controller, controller);
}
//使用 ServiceBasedControllerActivator 生成 controller 在 di 里找到 controller
builder.Services.Replace(ServiceDescriptor.
Transient<IControllerActivator, ServiceBasedControllerActivator>());
return builder;
}
应用服务会转换为 controller 注入,暴露的服务和实现都是应用服务 TestAppService,之后,注入 后的 controller 就是 TestAppService, TestAppService 被代理成 Castle.Proxies.TestAppServiceProxy,请求 TestAppService 服务 获取到的实现是 Castle.Proxies.TestAppServiceProxy
应用服务继承什么接口不重要,都是按照类型注入及拦截
public class TestAppService : ApplicationService,ITestAppService {
private readonly IRepository<A,Guid> _repository;
private readonly IHttpContextAccessor _httpContextAccessor;
public TestAppService(IRepository<A,Guid> repository,
IHttpContextAccessor httpContextAccessor ) {
_repository = repository;
_httpContextAccessor = httpContextAccessor;
}
[UnitOfWork(IsDisabled = true)]
public async Task<string> CreateFAsync() {
//Castle.Proxies.TestAppServiceProxy
var i = this;
//true
var IsProxy = ProxyUtil.IsProxy(i);
//获取到所有的接口
var interfaces= i.GetType().GetInterfaces();
//TestAppService
var baseType = i.GetType().BaseType;
//(Castle.DynamicProxy.IInterceptor[],
//Volo.Abp.Domain.Repositories.IRepository`2[WebApplicationWithAbp.A,System.Guid],
//Microsoft.AspNetCore.Http.IHttpContextAccessor)
var con = i.GetType().GetConstructors().FirstOrDefault();
// TestAppService
var targetObj = ProxyUtil.GetUnproxiedType(i);
return await Task.FromResult("");
}
}
AbpAspNetCoreMvcOptions
Configure<AbpAspNetCoreMvcOptions>(options => {
options.ConventionalControllers.Create(typeof(MyModule).Assembly);
});
public class ConventionalControllerSettingList : List<ConventionalControllerSetting>
{
[CanBeNull]
public ConventionalControllerSetting GetSettingOrNull(Type controllerType)
{
return this.FirstOrDefault(controllerSetting =>
controllerSetting.ControllerTypes.Contains(controllerType));
}
}
public class AbpConventionalControllerOptions
{
public ConventionalControllerSettingList
ConventionalControllerSettings { get; }
public List<Type> FormBodyBindingIgnoredTypes { get; }
public bool UseV3UrlStyle { get; set; }
public AbpConventionalControllerOptions()
{
ConventionalControllerSettings = new ConventionalControllerSettingList();
FormBodyBindingIgnoredTypes = new List<Type>
{
typeof(IFormFile),
typeof(IRemoteStreamContent)
};
}
public AbpConventionalControllerOptions Create(
Assembly assembly,
[CanBeNull] Action<ConventionalControllerSetting> optionsAction = null)
{
var setting = new ConventionalControllerSetting(
assembly,
ModuleApiDescriptionModel.DefaultRootPath,
ModuleApiDescriptionModel.DefaultRemoteServiceName
);
//对 setting 进行配置
optionsAction?.Invoke(setting);
//生成可以转换为 controller 的 类型
setting.Initialize();
//把自动发现 controller 的程序集及相关的类型加入 ConventionalControllerSettings
ConventionalControllerSettings.Add(setting);
return this;
}
}
public class ConventionalControllerSetting
{
//服务类所在的程序集
[NotNull]
public Assembly Assembly { get; }
[NotNull]
//可转换为 controller 的服务类
internal HashSet<Type> ControllerTypes { get; }
/// <summary>
/// Set true to use the old style URL path style.
/// Default: null (uses the value of the <
///see cref="AbpConventionalControllerOptions.UseV3UrlStyle"/>).
/// </summary>
public bool? UseV3UrlStyle { get; set; }
[NotNull]
public string RootPath {
get => _rootPath;
set {
Check.NotNull(value, nameof(value));
_rootPath = value;
}
}
private string _rootPath;
[NotNull]
public string RemoteServiceName {
get => _remoteServiceName;
set {
Check.NotNull(value, nameof(value));
_remoteServiceName = value;
}
}
private string _remoteServiceName;
[CanBeNull]
public Func<Type, bool> TypePredicate { get; set; }
/// <summary>
/// Default value: All.
/// </summary>
public ApplicationServiceTypes ApplicationServiceTypes { get; set; } =
ApplicationServiceTypes.All;
[CanBeNull]
public Action<ControllerModel> ControllerModelConfigurer { get; set; }
[CanBeNull]
public Func<UrlControllerNameNormalizerContext, string>
UrlControllerNameNormalizer { get; set; }
[CanBeNull]
public Func<UrlActionNameNormalizerContext, string>
UrlActionNameNormalizer { get; set; }
public List<ApiVersion> ApiVersions { get; }
public Action<ApiVersioningOptions> ApiVersionConfigurer { get; set; }
public ConventionalControllerSetting(
[NotNull] Assembly assembly,
[NotNull] string rootPath,
[NotNull] string remoteServiceName)
{
Assembly = Check.NotNull(assembly, nameof(assembly));
RootPath = Check.NotNull(rootPath, nameof(rootPath));
RemoteServiceName = Check.NotNull(remoteServiceName, nameof(remoteServiceName));
ControllerTypes = new HashSet<Type>();
ApiVersions = new List<ApiVersion>();
}
//加入远程服务作为控制类, todo IntegrationServiceAttribute
public void Initialize()
{
var types = Assembly.GetTypes()
//继承接口的或没有禁用的
.Where(IsRemoteService)
.Where(IsPreferredApplicationServiceType)
.WhereIf(TypePredicate != null, TypePredicate);
foreach (var type in types)
{
ControllerTypes.Add(type);
}
}
public IReadOnlyList<Type> GetControllerTypes()
{
return ControllerTypes.ToImmutableList();
}
private static bool IsRemoteService(Type type)
{
if (!type.IsPublic || type.IsAbstract || type.IsGenericType)
{
return false;
}
var remoteServiceAttr = ReflectionHelper.
GetSingleAttributeOrDefault<RemoteServiceAttribute>(type);
if (remoteServiceAttr != null && !remoteServiceAttr.IsEnabledFor(type))
{
return false;
}
if (typeof(IRemoteService).IsAssignableFrom(type))
{
return true;
}
return false;
}
private bool IsPreferredApplicationServiceType(Type type)
{
if (ApplicationServiceTypes == ApplicationServiceTypes.ApplicationServices)
{
return !IntegrationServiceAttribute.IsDefinedOrInherited(type);
}
if (ApplicationServiceTypes == ApplicationServiceTypes.IntegrationServices)
{
return IntegrationServiceAttribute.IsDefinedOrInherited(type);
}
return true;
}
}
AddApplicationParts
[DependsOn(
typeof(AbpAspNetCoreModule),
typeof(AbpLocalizationModule),
typeof(AbpApiVersioningAbstractionsModule),
typeof(AbpAspNetCoreMvcContractsModule),
typeof(AbpUiNavigationModule),
typeof(AbpGlobalFeaturesModule),
typeof(AbpDddApplicationModule),
typeof(AbpJsonSystemTextJsonModule)
)]
public class AbpAspNetCoreMvcModule : AbpModule{
public override void OnApplicationInitialization(
ApplicationInitializationContext context)
{
//把应用程序服务类所在的程序集作为部件赋给 ApplicationPartManager
AddApplicationParts(context);
}
private static void AddApplicationParts(ApplicationInitializationContext context)
{
var partManager = context.ServiceProvider.GetService<ApplicationPartManager>();
if (partManager == null)
{
return;
}
//Plugin modules
var moduleAssemblies = context
.ServiceProvider
.GetRequiredService<IModuleContainer>()
.Modules
.Where(m => m.IsLoadedAsPlugIn)
.Select(m => m.Type.Assembly)
.Distinct();
AddToApplicationParts(partManager, moduleAssemblies);
//Controllers for application services
var controllerAssemblies = context
.ServiceProvider
.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>()
.Value
.ConventionalControllers
.ConventionalControllerSettings
.Select(s => s.Assembly)
.Distinct();
AddToApplicationParts(partManager, controllerAssemblies);
}
private static void AddToApplicationParts(ApplicationPartManager partManager,
IEnumerable<Assembly> moduleAssemblies)
{
foreach (var moduleAssembly in moduleAssemblies)
{
/*
加入程序集部件
public class AssemblyPart : ApplicationPart, IApplicationPartTypeProvider{
}
*/
partManager.ApplicationParts.AddIfNotContains(moduleAssembly);
}
}
}
ControllerFeatureProvider 在应用程序部件发现 controller
// controller view 属于 ApplicationFeature
public interface IApplicationFeatureProvider
{
}
public interface IApplicationFeatureProvider<TFeature> : IApplicationFeatureProvider
{
void PopulateFeature(IEnumerable<ApplicationPart> parts, TFeature feature);
}
public class ControllerFeature
{
/// <summary>
/// Gets the list of controller types in an MVC application.
/// </summary>
public IList<TypeInfo> Controllers { get; } = (IList<TypeInfo>) new List<TypeInfo>();
}
public class ControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
private const string ControllerTypeNameSuffix = "Controller";
//把程序集部件里所有的 controller 加入 ControllerFeature
public void PopulateFeature(
IEnumerable<ApplicationPart> parts,
ControllerFeature feature)
{
foreach (var part in parts.OfType<IApplicationPartTypeProvider>())
{
foreach (var type in part.Types)
{
if (IsController(type) && !feature.Controllers.Contains(type))
{
feature.Controllers.Add(type);
}
}
}
}
//找出符合 controller 条件的
protected virtual bool IsController(TypeInfo typeInfo)
{
if (!typeInfo.IsClass)
{
return false;
}
if (typeInfo.IsAbstract)
{
return false;
}
// We only consider public top-level classes as controllers.
//IsPublic returns false for nested
// classes, regardless of visibility modifiers
if (!typeInfo.IsPublic)
{
return false;
}
if (typeInfo.ContainsGenericParameters)
{
return false;
}
if (typeInfo.IsDefined(typeof(NonControllerAttribute)))
{
return false;
}
if (!typeInfo.Name.EndsWith(ControllerTypeNameSuffix,
StringComparison.OrdinalIgnoreCase) &&
!typeInfo.IsDefined(typeof(ControllerAttribute)))
{
return false;
}
return true;
}
}
AbpConventionalControllerFeatureProvider 添加 abp 自己的部件里发现的 controller 规则
/*
AbpAspNetCoreMvcModule 里 把 abp 自己配置的 AbpConventionalControllerFeatureProvider
赋给 partManager
asp.net 就会把远程服务转换为 controller
var partManager = context.Services.GetSingletonInstance<ApplicationPartManager>();
var application = context.Services.GetSingletonInstance<IAbpApplication>();
partManager.FeatureProviders.Add(new AbpConventionalControllerFeatureProvider(application));
*/
namespace Volo.Abp.AspNetCore.Mvc.Conventions;
public class AbpConventionalControllerFeatureProvider : ControllerFeatureProvider
{
private readonly IAbpApplication _application;
public AbpConventionalControllerFeatureProvider(IAbpApplication application)
{
_application = application;
}
protected override bool IsController(TypeInfo typeInfo)
{
//TODO: Move this to a lazy loaded field for efficiency.
if (_application.ServiceProvider == null)
{
return false;
}
//abp 里没有禁用的远程服务所在类就是 controller
var configuration = _application.ServiceProvider
.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>().Value
.ConventionalControllers
.ConventionalControllerSettings
.GetSettingOrNull(typeInfo.AsType());
return configuration != null;
}
}
SelectorModel
Microsoft.AspNetCore.Mvc.ApplicationModels.SelectorModel 是 ASP.NET Core 中的一个类,用于 表示 MVC 控制器中的路由和动作选择器信息。它包含了路由模板、HTTP 方法和约束等信息,可以帮助 MVC 框架进行路由匹配和动作选择。
public class SelectorModel
{
public SelectorModel()
{
ActionConstraints = new List<IActionConstraintMetadata>();
EndpointMetadata = new List<object>();
}
public SelectorModel(SelectorModel other)
{
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
ActionConstraints = new List<IActionConstraintMetadata>(other.ActionConstraints);
EndpointMetadata = new List<object>(other.EndpointMetadata);
if (other.AttributeRouteModel != null)
{
AttributeRouteModel = new AttributeRouteModel(other.AttributeRouteModel);
}
}
//路由属性
public AttributeRouteModel? AttributeRouteModel { get; set; }
//请求约束 http method
public IList<IActionConstraintMetadata> ActionConstraints { get; }
//端点元素据
public IList<object> EndpointMetadata { get; }
}
HttpMethodHelper abp HttpMethodHelper
namespace Volo.Abp.Http;
public static class HttpMethodHelper
{
public const string DefaultHttpVerb = "POST";
public static Dictionary<string, string[]> ConventionalPrefixes { get; set; }
= new Dictionary<string, string[]>
{
{"GET", new[] {"GetList", "GetAll", "Get"}},
{"PUT", new[] {"Put", "Update"}},
{"DELETE", new[] {"Delete", "Remove"}},
{"POST", new[] {"Create", "Add", "Insert", "Post"}},
{"PATCH", new[] {"Patch"}}
};
public static string GetConventionalVerbForMethodName(string methodName)
{
foreach (var conventionalPrefix in ConventionalPrefixes)
{
if (conventionalPrefix.Value.Any(prefix => methodName.StartsWith(
prefix, StringComparison.OrdinalIgnoreCase)))
{
return conventionalPrefix.Key;
}
}
return DefaultHttpVerb;
}
public static string RemoveHttpMethodPrefix([NotNull] string methodName,
[NotNull] string httpMethod)
{
Check.NotNull(methodName, nameof(methodName));
Check.NotNull(httpMethod, nameof(httpMethod));
var prefixes = ConventionalPrefixes.GetOrDefault(httpMethod);
if (prefixes.IsNullOrEmpty())
{
return methodName;
}
return methodName.RemovePreFix(prefixes);
}
public static HttpMethod ConvertToHttpMethod(string httpMethod)
{
switch (httpMethod.ToUpperInvariant())
{
case "GET":
return HttpMethod.Get;
case "POST":
return HttpMethod.Post;
case "PUT":
return HttpMethod.Put;
case "DELETE":
return HttpMethod.Delete;
case "OPTIONS":
return HttpMethod.Options;
case "TRACE":
return HttpMethod.Trace;
case "HEAD":
return HttpMethod.Head;
case "PATCH":
return new HttpMethod("PATCH");
default:
throw new AbpException("Unknown HTTP METHOD: " + httpMethod);
}
}
}
IApplicationModelConvention
// 继承于 asp.net core IApplicationModelConvention ,就可以自动的生成 controller ,action
public interface IAbpServiceConvention : IApplicationModelConvention
{
}
AbpServiceConvention 完成服务到控制器的自动转换
namespace Volo.Abp.AspNetCore.Mvc.Conventions;
/*
添加 abp 定义 IApplicationModelConvention 应用程序模型转换 给 asp.net core mvc
应用程序模型转换 运行用户自定义 controller 的转换
private static void AddConventions(MvcOptions options, IServiceCollection services)
{
options.Conventions.Add(new AbpServiceConventionWrapper(services));
}
*/
public class AbpServiceConvention : IAbpServiceConvention, ITransientDependency
{
public ILogger<AbpServiceConvention> Logger { get; set; }
protected AbpAspNetCoreMvcOptions Options { get; }
protected IConventionalRouteBuilder ConventionalRouteBuilder { get; }
public AbpServiceConvention(
IOptions<AbpAspNetCoreMvcOptions> options,
IConventionalRouteBuilder conventionalRouteBuilder)
{
ConventionalRouteBuilder = conventionalRouteBuilder;
Options = options.Value;
Logger = NullLogger<AbpServiceConvention>.Instance;
}
public void Apply(ApplicationModel application)
{
ApplyForControllers(application);
}
protected virtual void ApplyForControllers(ApplicationModel application)
{
RemoveDuplicateControllers(application);
RemoveIntegrationControllersIfNotExposed(application);
//获取所有的 controller ,应用服务在这里已经被转换为 controller 了
//但是 web api 相关的 attribute 路由属性还没有被添加
//如下的方法会添加路由属性
//application.Controllers
foreach (var controller in GetControllers(application))
{
//这里的会获取所有的可转换为 controller 的类型,包含 asp.net core 自己提供的
var controllerType = controller.ControllerType.AsType();
// 获取 ConventionalControllerSetting abp 的配置类,里面有程序集及相关可以转换为
// controller 的服务类
var configuration = GetControllerSettingOrNull(controllerType);
// 类型是否实现远程服务接口,过滤abp 自己转换的 controller ,
if (ImplementsRemoteServiceInterface(controllerType))
{
//{ "AppService", "ApplicationService", "IntService",
// "IntegrationService", "Service" };
controller.ControllerName = controller.
ControllerName.RemovePostFix(ApplicationService.CommonPostfixes);
//用户自定义的配置
configuration?.ControllerModelConfigurer?.Invoke(controller);
ConfigureRemoteService(controller, configuration);
}
else
{
var remoteServiceAttr = ReflectionHelper.
GetSingleAttributeOrDefault<RemoteServiceAttribute>(controllerType.GetTypeInfo());
if (remoteServiceAttr != null && remoteServiceAttr.IsEnabledFor(controllerType))
{
ConfigureRemoteService(controller, configuration);
}
}
}
}
protected virtual void RemoveIntegrationControllersIfNotExposed(
ApplicationModel application)
{
if (Options.ExposeIntegrationServices)
{
return;
}
var integrationControllers = GetControllers(application)
.Where(c => IntegrationServiceAttribute.IsDefinedOrInherited(
c.ControllerType))
.ToArray();
application.Controllers.RemoveAll(integrationControllers);
}
protected virtual IList<ControllerModel> GetControllers(ApplicationModel application)
{
return application.Controllers;
}
protected virtual void RemoveDuplicateControllers(ApplicationModel application)
{
var controllerModelsToRemove = new List<ControllerModel>();
if (Options.ControllersToRemove.Any())
{
var removeControllerModels = GetControllers(application)
.Where(cm => Options.ControllersToRemove.Contains(cm.ControllerType))
.ToArray();
if (removeControllerModels.Any())
{
controllerModelsToRemove.AddRange(removeControllerModels);
Logger.LogInformation($"Removing the controller{
(removeControllerModels.Length > 1 ? "s" : "")}
{removeControllerModels.Select(c =>
c.ControllerType.AssemblyQualifiedName)!.JoinAsString(", ")}
from the application model");
}
}
foreach (var controllerModel in GetControllers(application))
{
var replaceControllersAttr = ReflectionHelper.
GetSingleAttributeOrDefault<ReplaceControllersAttribute>(
controllerModel.ControllerType);
if (replaceControllersAttr != default)
{
var replaceControllerModels = GetControllers(application)
.Where(cm => replaceControllersAttr.ControllerTypes.
Contains(cm.ControllerType))
.ToArray();
if (replaceControllerModels.Any())
{
controllerModelsToRemove.AddRange(replaceControllerModels);
Logger.LogInformation(
$"Removing the controller{(
replaceControllerModels.Length > 1 ? "s" : "")}
{replaceControllersAttr.ControllerTypes.
Select(c => c.AssemblyQualifiedName)!.JoinAsString(", ")}
from the application model since {
(replaceControllerModels.Length > 1 ?
"they are" : "it is")} replaced by the controller:
{controllerModel.ControllerType.AssemblyQualifiedName}");
}
}
}
foreach (var controllerModel in GetControllers(application))
{
if (!controllerModel.ControllerType.IsDefined(typeof(
ExposeServicesAttribute), false))
{
continue;
}
if (Options.IgnoredControllersOnModelExclusion.
Contains(controllerModel.ControllerType))
{
continue;
}
var exposeServicesAttr = ReflectionHelper.
GetSingleAttributeOrDefault<ExposeServicesAttribute>(
controllerModel.ControllerType);
if (exposeServicesAttr!.IncludeSelf)
{
var exposedControllerModels = GetControllers(application)
.Where(cm => exposeServicesAttr.ServiceTypes.Contains(cm.ControllerType))
.ToArray();
if (exposedControllerModels.Any())
{
controllerModelsToRemove.AddRange(exposedControllerModels);
Logger.LogInformation($"Removing the controller{(
exposedControllerModels.Length > 1 ? "s" : "")}
{exposeServicesAttr.ServiceTypes.Select(
c => c.AssemblyQualifiedName)!.JoinAsString(", ")}
from the application model since {(exposedControllerModels.Length
> 1 ? "they are" : "it is")} replaced by the controller:
{controllerModel.ControllerType.AssemblyQualifiedName}");
}
continue;
}
var baseControllerTypes = controllerModel.ControllerType
.GetBaseClasses(typeof(Controller), includeObject: false)
.Where(t => !t.IsAbstract)
.ToArray();
if (baseControllerTypes.Length == 0)
{
continue;
}
var baseControllerModels = GetControllers(application)
.Where(cm => baseControllerTypes.Contains(cm.ControllerType))
.ToArray();
if (baseControllerModels.Length == 0)
{
continue;
}
controllerModelsToRemove.Add(controllerModel);
Logger.LogInformation($"Removing the controller {
controllerModel.ControllerType.AssemblyQualifiedName}
from the application model since it replaces the controller(s):
{baseControllerTypes.Select(c => c.AssemblyQualifiedName)!.
JoinAsString(", ")}");
}
application.Controllers.RemoveAll(controllerModelsToRemove);
}
//应用程序服务类配置
protected virtual void ConfigureRemoteService(ControllerModel controller,
ConventionalControllerSetting? configuration)
{
ConfigureApiExplorer(controller);
ConfigureSelector(controller, configuration);
//参数绑定
ConfigureParameters(controller);
}
protected virtual void ConfigureParameters(ControllerModel controller)
{
/* Default binding system of Asp.Net Core for a parameter
* 1. Form values
* 2. Route values.
* 3. Query string.
*/
foreach (var action in controller.Actions)
{
foreach (var prm in action.Parameters)
{
if (prm.BindingInfo != null)
{
continue;
}
if (!TypeHelper.IsPrimitiveExtended(prm.ParameterInfo.ParameterType,
includeEnums: true))
{
if (CanUseFormBodyBinding(action, prm))
{
prm.BindingInfo = BindingInfo.GetBindingInfo(new[] {
new FromBodyAttribute() });
}
}
}
}
}
protected virtual bool CanUseFormBodyBinding(ActionModel action,
ParameterModel parameter)
{
//We want to use "id" as path parameter, not body!
if (parameter.ParameterName == "id")
{
return false;
}
if (Options.ConventionalControllers
.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;
}
// api 端点发现,是否被 swagger 发现
protected virtual void ConfigureApiExplorer(ControllerModel controller)
{
if (Options.ChangeControllerModelApiExplorerGroupName &&
controller.ApiExplorer.GroupName.IsNullOrEmpty())
{
controller.ApiExplorer.GroupName = controller.ControllerName;
}
if (controller.ApiExplorer.IsVisible == null)
{
// attribute.IsEnabledFor(method) &&
// attribute.IsMetadataEnabledFor(method);
IsMetadataEnabled 为 false 不能被 swagger 方法但是依然可以被客户端使用
controller.ApiExplorer.IsVisible =
IsVisibleRemoteService(controller.ControllerType);
}
// action 是否能被发现
foreach (var action in controller.Actions)
{
ConfigureApiExplorer(action);
}
}
protected virtual void ConfigureApiExplorer(ActionModel action)
{
if (action.ApiExplorer.IsVisible != null)
{
return;
}
var visible = IsVisibleRemoteServiceMethod(action.ActionMethod);
if (visible == null)
{
return;
}
action.ApiExplorer.IsVisible = visible;
}
//
protected virtual void ConfigureSelector(
ControllerModel controller, ConventionalControllerSetting? configuration)
{
RemoveEmptySelectors(controller.Selectors);
var controllerType = controller.ControllerType.AsType();
var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault
<RemoteServiceAttribute>(controllerType.GetTypeInfo());
if (remoteServiceAtt != null && !remoteServiceAtt.IsEnabledFor(controllerType))
{
return;
}
if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null))
{
return;
}
var rootPath = GetRootPathOrDefault(controller.ControllerType.AsType());
foreach (var action in controller.Actions)
{
ConfigureSelector(rootPath, controller.ControllerName, action, configuration);
}
}
protected virtual void ConfigureSelector(string rootPath, string
controllerName, ActionModel action, ConventionalControllerSetting? configuration)
{
RemoveEmptySelectors(action.Selectors);
var remoteServiceAtt = ReflectionHelper.
GetSingleAttributeOrDefault<RemoteServiceAttribute>(action.ActionMethod);
if (remoteServiceAtt != null && !remoteServiceAtt.
IsEnabledFor(action.ActionMethod))
{
return;
}
if (!action.Selectors.Any())
{
AddAbpServiceSelector(rootPath, controllerName, action, configuration);
}
else
{
NormalizeSelectorRoutes(rootPath, controllerName, action, configuration);
}
}
//
protected virtual void AddAbpServiceSelector(string rootPath,
string controllerName, ActionModel action,
ConventionalControllerSetting? configuration)
{
var httpMethod = SelectHttpMethod(action, configuration);
var abpServiceSelectorModel = new SelectorModel
{
AttributeRouteModel =
CreateAbpServiceAttributeRouteModel(
rootPath, controllerName, action, httpMethod, configuration),
ActionConstraints = { new HttpMethodActionConstraint(new[] { httpMethod }) }
};
action.Selectors.Add(abpServiceSelectorModel);
}
protected virtual string SelectHttpMethod(ActionModel action,
ConventionalControllerSetting? configuration)
{
return HttpMethodHelper.GetConventionalVerbForMethodName(action.ActionName);
}
protected virtual void NormalizeSelectorRoutes(string rootPath,
string controllerName, ActionModel action, ConventionalControllerSetting? configuration)
{
foreach (var selector in action.Selectors)
{
var httpMethod = selector.ActionConstraints
.OfType<HttpMethodActionConstraint>()
.FirstOrDefault()?
.HttpMethods?
.FirstOrDefault();
if (httpMethod == null)
{
httpMethod = SelectHttpMethod(action, configuration);
}
if (selector.AttributeRouteModel == null)
{
selector.AttributeRouteModel =
CreateAbpServiceAttributeRouteModel(
rootPath, controllerName, action, httpMethod, configuration);
}
if (!selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Any())
{
selector.ActionConstraints.Add(
new HttpMethodActionConstraint(new[] { httpMethod }));
}
}
}
protected virtual string GetRootPathOrDefault(Type controllerType)
{
var controllerSetting = GetControllerSettingOrNull(controllerType);
if (controllerSetting?.RootPath != null)
{
return controllerSetting.RootPath;
}
var areaAttribute = controllerType
.GetCustomAttributes().OfType<AreaAttribute>().FirstOrDefault();
if (areaAttribute?.RouteValue != null)
{
return areaAttribute.RouteValue;
}
return ModuleApiDescriptionModel.DefaultRootPath;
}
protected virtual ConventionalControllerSetting?
GetControllerSettingOrNull(Type controllerType)
{
return Options.ConventionalControllers.
ConventionalControllerSettings.GetSettingOrNull(controllerType);
}
protected virtual AttributeRouteModel
CreateAbpServiceAttributeRouteModel(string rootPath, string controllerName,
ActionModel action, string httpMethod, ConventionalControllerSetting? configuration)
{
return new AttributeRouteModel(
new RouteAttribute(
ConventionalRouteBuilder.Build(rootPath, controllerName,
action, httpMethod, configuration)
)
);
}
protected virtual void RemoveEmptySelectors(IList<SelectorModel> selectors)
{
selectors
.Where(IsEmptySelector)
.ToList()
.ForEach(s => selectors.Remove(s));
}
protected virtual bool IsEmptySelector(SelectorModel selector)
{
return selector.AttributeRouteModel == null
&& selector.ActionConstraints.IsNullOrEmpty()
&& selector.EndpointMetadata.IsNullOrEmpty();
}
protected virtual bool ImplementsRemoteServiceInterface(Type controllerType)
{
return typeof(IRemoteService).GetTypeInfo().IsAssignableFrom(controllerType);
}
protected virtual bool IsVisibleRemoteService(Type controllerType)
{
if (!IsGlobalFeatureEnabled(controllerType))
{
return false;
}
var attribute = ReflectionHelper.
GetSingleAttributeOrDefault<RemoteServiceAttribute>(controllerType);
if (attribute == null)
{
return true;
}
return attribute.IsEnabledFor(controllerType) &&
attribute.IsMetadataEnabledFor(controllerType);
}
protected virtual bool? IsVisibleRemoteServiceMethod(MethodInfo method)
{
var attribute = ReflectionHelper.
GetSingleAttributeOrDefault<RemoteServiceAttribute>(method);
if (attribute == null)
{
return null;
}
return attribute.IsEnabledFor(method) &&
attribute.IsMetadataEnabledFor(method);
}
protected virtual bool IsGlobalFeatureEnabled(Type controllerType)
{
var attribute = ReflectionHelper.
GetSingleAttributeOrDefault<RequiresGlobalFeatureAttribute>(controllerType);
if (attribute == null)
{
return true;
}
return GlobalFeatureManager.Instance.IsEnabled(attribute.GetFeatureName());
}
}
👍🎉🎊