ABP AbpAspNetCoreMvcModule
ABP AbpAspNetCoreMvcModule
2023/6/1
➡️

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 的方式:

  1. ServiceBasedControllerActivator : IControllerActivator
  2. 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());
    }
}
👍🎉🎊