zoukankan      html  css  js  c++  java
  • 【ABP框架系列学习】介绍篇(1)

     

    0.引言

    该系列博文主要在【官方文档】及【tkbSimplest】ABP框架理论研究系列博文的基础上进行总结的,或许大家会质问,别人都已经翻译过了,这不是多此一举吗?原因如下:

    1.【tkbSimplest】的相关博文由于撰写得比较早的,在参照官方文档学习的过程中,发现部分知识未能及时同步(当前V4.0.2版本),如【EntityHistory】、【Multi-Lingual Engities】章节未涉及、【Caching】章节没有Entity Caching等内容。

    2.进一步深入学习ABP的理论知识。

    3.借此机会提高英文文档的阅读能力,故根据官方当前最新的版本,并在前人的基础上,自己也感受一下英文帮助文档的魅力。

    好了,下面开始进入正题。

    1.APB是什么?

    ABP是ASP.NET Boilerplate的简称,从英文字面上理解它是一个关于ASP.NET的模板,在github上已经有5.7k的star(截止2018年11月21日)。官方的解释:ABP是一个开源且文档友好的应用程序框架。ABP不仅仅是一个框架,它还提供了一个最徍实践的基于领域驱动设计(DDD)的体系结构模型。

    ABP与最新的ASP.NET COREEF CORE版本保持同步,同样也支持ASP.NET MVC 5.x和EF6.x。

    2.一个快速事例

     让我们研究一个简单的类,看看ABP具有哪些优点:

    public class TaskAppService : ApplicationService, ITaskAppService
        {
            private readonly IRepository<Task> _taskRepository;
    
            public TaskAppService(IRepository<Task> taskRepository)
            {
                _taskRepository = taskRepository;
            }
    
            [AbpAuthorize(MyPermissions.UpdateTasks)]
            public async Task UpdateTask(UpdateTaskInput input)
            {
                Logger.Info("Updating a task for input: " + input);
    
                var task = await _taskRepository.FirstOrDefaultAsync(input.TaskId);
                if (task == null)
                {
                    throw new UserFriendlyException(L("CouldNotFindTheTaskMessage"));
                }
    
                input.MapTo(task);
            }
        }

    这里我们看到一个Application Service(应用服务)方法。在DDD中,应用服务直接用于表现层(UI)执行应用程序的用例。那么在UI层中就可以通过javascript ajax的方式调用UpdateTask方法。

    var _taskService = abp.services.app.task;
    _taskService.updateTask(...);

    3.ABP的优点

    通过上述事例,让我们来看看ABP的一些优点:

    依赖注入(Dependency Injection):ABP使用并提供了传统的DI基础设施。上述TaskAppService类是一个应用服务(继承自ApplicationService),所以它按照惯例以短暂(每次请求创建一次)的形式自动注册到DI容器中。同样的,也可以简单地注入其他依赖(如事例中的IRepository<Task>)。

    部分源码分析:TaskAppService类继承自ApplicationService,IApplicaitonServcie又继承自ITransientDependency接口,在ABP框架中已经将ITransientDependency接口注入到DI容器中,所有继承自ITransientDependency接口的类或接口都会默认注入。

     //空接口
      public interface ITransientDependency
      {
    
      }
       
      //应用服务接口
      public interface IApplicationService : ITransientDependency
      {
    
      }
    
      //仓储接口
      public interface IRepository : ITransientDependency
      {
            
      }
    View Code
     public class BasicConventionalRegistrar : IConventionalDependencyRegistrar
        {
            public void RegisterAssembly(IConventionalRegistrationContext context)
            {
                //注入到IOC,所有继承自ITransientDependency的类、接口等都会默认注入
                context.IocManager.IocContainer.Register(
                    Classes.FromAssembly(context.Assembly)
                        .IncludeNonPublicTypes()
                        .BasedOn<ITransientDependency>()
                        .If(type => !type.GetTypeInfo().IsGenericTypeDefinition)
                        .WithService.Self()
                        .WithService.DefaultInterfaces()
                        .LifestyleTransient()
                    );
    
                //Singleton
                context.IocManager.IocContainer.Register(
                    Classes.FromAssembly(context.Assembly)
                        .IncludeNonPublicTypes()
                        .BasedOn<ISingletonDependency>()
                        .If(type => !type.GetTypeInfo().IsGenericTypeDefinition)
                        .WithService.Self()
                        .WithService.DefaultInterfaces()
                        .LifestyleSingleton()
                    );
    
                //Windsor Interceptors
                context.IocManager.IocContainer.Register(
                    Classes.FromAssembly(context.Assembly)
                        .IncludeNonPublicTypes()
                        .BasedOn<IInterceptor>()
                        .If(type => !type.GetTypeInfo().IsGenericTypeDefinition)
                        .WithService.Self()
                        .LifestyleTransient()
                    );
            }
    View Code

    仓储(Repository):ABP可以为每一个实体创建一个默认的仓储(如事例中的IRepository<Task>)。默认的仓储提供了很多有用的方法,如事例中的FirstOrDefault方法。当然,也可以根据需求扩展默认的仓储。仓储抽象了DBMS和ORMs,并简化了数据访问逻辑。

                                  

    授权(Authorization):ABP可以通过声明的方式检查权限。如果当前用户没有【update task】的权限或没有登录,则会阻止访问UpdateTask方法。ABP不仅提供了声明属性的方式授权,而且还可以通过其它的方式。

    部分源码分析:AbpAuthorizeAttribute类实现了Attribute,可在类或方法上通过【AbpAuthorize】声明。

       [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
       public class AbpAuthorizeAttribute : Attribute, IAbpAuthorizeAttribute
       {
            /// <summary>
            /// A list of permissions to authorize.
            /// </summary>
            public string[] Permissions { get; }
    
            /// <summary>
            /// If this property is set to true, all of the <see cref="Permissions"/> must be 
                granted.
            /// If it's false, at least one of the <see cref="Permissions"/> must be granted.
            /// Default: false.
            /// </summary>
            public bool RequireAllPermissions { get; set; }
    
            /// <summary>
            /// Creates a new instance of <see cref="AbpAuthorizeAttribute"/> class.
            /// </summary>
            /// <param name="permissions">A list of permissions to authorize</param>
            public AbpAuthorizeAttribute(params string[] permissions)
            {
                Permissions = permissions;
            }
        }
    View Code

     通过AuthorizationProvider类中的SetPermissions方法进行自定义授权。

     public abstract class AuthorizationProvider : ITransientDependency
        {
            /// <summary>
            /// This method is called once on application startup to allow to define 
                permissions.
            /// </summary>
            /// <param name="context">Permission definition context</param>
            public abstract void SetPermissions(IPermissionDefinitionContext context);
        }
    View Code

    验证(Validation):ABP自动检查输入是否为null。它也基于标准数据注释特性和自定义验证规则验证所有的输入属性。如果请求无效,它会在客户端抛出适合的验证异常。

    部分源码分析:ABP框架中主要通过拦截器ValidationInterceptor(AOP实现方式之一,)实现验证,该拦截器在ValidationInterceptorRegistrar的Initialize方法中调用。

    internal static class ValidationInterceptorRegistrar
        {
            public static void Initialize(IIocManager iocManager)
            {
                iocManager.IocContainer.Kernel.ComponentRegistered += Kernel_ComponentRegistered;
            }
    
            private static void Kernel_ComponentRegistered(string key, IHandler handler)
            {
                if (typeof(IApplicationService).GetTypeInfo().IsAssignableFrom(handler.ComponentModel.Implementation))
                {
                    handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(ValidationInterceptor)));
                }
            }
        }
    View Code
     public class ValidationInterceptor : IInterceptor
        {
            private readonly IIocResolver _iocResolver;
    
            public ValidationInterceptor(IIocResolver iocResolver)
            {
                _iocResolver = iocResolver;
            }
    
            public void Intercept(IInvocation invocation)
            {
                if (AbpCrossCuttingConcerns.IsApplied(invocation.InvocationTarget, AbpCrossCuttingConcerns.Validation))
                {
                    invocation.Proceed();
                    return;
                }
    
                using (var validator = _iocResolver.ResolveAsDisposable<MethodInvocationValidator>())
                {
                    validator.Object.Initialize(invocation.MethodInvocationTarget, invocation.Arguments);
                    validator.Object.Validate();
                }
                
                invocation.Proceed();
            }
        }
    View Code

    自定义Customvalidator类

     public class CustomValidator : IMethodParameterValidator
        {
            private readonly IIocResolver _iocResolver;
    
            public CustomValidator(IIocResolver iocResolver)
            {
                _iocResolver = iocResolver;
            }
    
            public IReadOnlyList<ValidationResult> Validate(object validatingObject)
            {
                var validationErrors = new List<ValidationResult>();
    
                if (validatingObject is ICustomValidate customValidateObject)
                {
                    var context = new CustomValidationContext(validationErrors, _iocResolver);
                    customValidateObject.AddValidationErrors(context);
                }
    
                return validationErrors;
            }
        }
    View Code

    审计日志(Audit Logging):基于约定和配置,用户、浏览器、IP地址、调用服务、方法、参数、调用时间、执行时长以及其它信息会为每一个请求自动保存。

    部分源码分析:ABP框架中主要通过拦截器AuditingInterceptor(AOP实现方式之一,)实现审计日志,该拦截器在AuditingInterceptorRegistrar的Initialize方法中调用。

    internal static class AuditingInterceptorRegistrar
        {
            public static void Initialize(IIocManager iocManager)
            {
                iocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) =>
                {
                    if (!iocManager.IsRegistered<IAuditingConfiguration>())
                    {
                        return;
                    }
    
                    var auditingConfiguration = iocManager.Resolve<IAuditingConfiguration>();
    
                    if (ShouldIntercept(auditingConfiguration, handler.ComponentModel.Implementation))
                    {
                        handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(AuditingInterceptor)));
                    }
                };
            }
    View Code
      
            private static bool ShouldIntercept(IAuditingConfiguration auditingConfiguration, Type type)
            {
                if (auditingConfiguration.Selectors.Any(selector => selector.Predicate(type)))
                {
                    return true;
                }
    
                if (type.GetTypeInfo().IsDefined(typeof(AuditedAttribute), true))
                {
                    return true;
                }
    
                if (type.GetMethods().Any(m => m.IsDefined(typeof(AuditedAttribute), true)))
                {
                    return true;
                }
    
                return false;
            }
        }
    View Code
     internal class AuditingInterceptor : IInterceptor
        {
            private readonly IAuditingHelper _auditingHelper;
    
            public AuditingInterceptor(IAuditingHelper auditingHelper)
            {
                _auditingHelper = auditingHelper;
            }
    
            public void Intercept(IInvocation invocation)
            {
                if (AbpCrossCuttingConcerns.IsApplied(invocation.InvocationTarget, 
                   AbpCrossCuttingConcerns.Auditing))
                {
                    invocation.Proceed();
                    return;
                }
    
                if (!_auditingHelper.ShouldSaveAudit(invocation.MethodInvocationTarget))
                {
                    invocation.Proceed();
                    return;
                }
    
                var auditInfo = _auditingHelper.CreateAuditInfo(invocation.TargetType, 
                invocation.MethodInvocationTarget, invocation.Arguments);
    
                if (invocation.Method.IsAsync())
                {
                    PerformAsyncAuditing(invocation, auditInfo);
                }
                else
                {
                    PerformSyncAuditing(invocation, auditInfo);
                }
            }
    
            private void PerformSyncAuditing(IInvocation invocation, AuditInfo auditInfo)
            {
                var stopwatch = Stopwatch.StartNew();
    
                try
                {
                    invocation.Proceed();
                }
                catch (Exception ex)
                {
                    auditInfo.Exception = ex;
                    throw;
                }
                finally
                {
                    stopwatch.Stop();
                    auditInfo.ExecutionDuration = 
                    Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds);
                    _auditingHelper.Save(auditInfo);
                }
            }
    
            private void PerformAsyncAuditing(IInvocation invocation, AuditInfo auditInfo)
            {
                var stopwatch = Stopwatch.StartNew();
    
                invocation.Proceed();
    
                if (invocation.Method.ReturnType == typeof(Task))
                {
                    invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithFinally(
                        (Task) invocation.ReturnValue,
                        exception => SaveAuditInfo(auditInfo, stopwatch, exception)
                        );
                }
                else //Task<TResult>
                {
                    invocation.ReturnValue = 
                    InternalAsyncHelper.CallAwaitTaskWithFinallyAndGetResult(
                        invocation.Method.ReturnType.GenericTypeArguments[0],
                        invocation.ReturnValue,
                        exception => SaveAuditInfo(auditInfo, stopwatch, exception)
                        );
                }
            }
    
            private void SaveAuditInfo(AuditInfo auditInfo, Stopwatch stopwatch, Exception 
            exception)
            {
                stopwatch.Stop();
                auditInfo.Exception = exception;
                auditInfo.ExecutionDuration = 
                Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds);
    
                _auditingHelper.Save(auditInfo);
            }
        }
    View Code

    工作单元(Unit Of Work):在ABP中,应用服务方法默认视为一个工作单元。它会自动创建一个连接并在方法的开始位置开启事务。如果方法成功完成并没有异常,事务会提交并释放连接。即使这个方法使用不同的仓储或方法,它们都是原子的(事务的)。当事务提交时,实体的所有改变都会自动保存。如上述事例所示,甚至不需要调用_repository.Update(task)方法。

    部分源码分析:ABP框架中主要通过拦截器UnitOfWorkInterceptor(AOP实现方式之一,)实现工作单元,该拦截器在UnitOfWorkRegistrar的Initialize方法中调用。

    internal class UnitOfWorkInterceptor : IInterceptor
        {
            private readonly IUnitOfWorkManager _unitOfWorkManager;
            private readonly IUnitOfWorkDefaultOptions _unitOfWorkOptions;
    
            public UnitOfWorkInterceptor(IUnitOfWorkManager unitOfWorkManager, IUnitOfWorkDefaultOptions unitOfWorkOptions)
            {
                _unitOfWorkManager = unitOfWorkManager;
                _unitOfWorkOptions = unitOfWorkOptions;
            }
    
            /// <summary>
            /// Intercepts a method.
            /// </summary>
            /// <param name="invocation">Method invocation arguments</param>
            public void Intercept(IInvocation invocation)
            {
                MethodInfo method;
                try
                {
                    method = invocation.MethodInvocationTarget;
                }
                catch
                {
                    method = invocation.GetConcreteMethod();
                }
    
                var unitOfWorkAttr = _unitOfWorkOptions.GetUnitOfWorkAttributeOrNull(method);
                if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled)
                {
                    //No need to a uow
                    invocation.Proceed();
                    return;
                }
    
                //No current uow, run a new one
                PerformUow(invocation, unitOfWorkAttr.CreateOptions());
            }
    
            private void PerformUow(IInvocation invocation, UnitOfWorkOptions options)
            {
                if (invocation.Method.IsAsync())
                {
                    PerformAsyncUow(invocation, options);
                }
                else
                {
                    PerformSyncUow(invocation, options);
                }
            }
    
            private void PerformSyncUow(IInvocation invocation, UnitOfWorkOptions options)
            {
                using (var uow = _unitOfWorkManager.Begin(options))
                {
                    invocation.Proceed();
                    uow.Complete();
                }
            }
    
            private void PerformAsyncUow(IInvocation invocation, UnitOfWorkOptions options)
            {
                var uow = _unitOfWorkManager.Begin(options);
    
                try
                {
                    invocation.Proceed();
                }
                catch
                {
                    uow.Dispose();
                    throw;
                }
    
                if (invocation.Method.ReturnType == typeof(Task))
                {
                    invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(
                        (Task) invocation.ReturnValue,
                        async () => await uow.CompleteAsync(),
                        exception => uow.Dispose()
                    );
                }
                else //Task<TResult>
                {
                    invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult(
                        invocation.Method.ReturnType.GenericTypeArguments[0],
                        invocation.ReturnValue,
                        async () => await uow.CompleteAsync(),
                        exception => uow.Dispose()
                    );
                }
            }
        }
    View Code

    异常处理(Exception):在使用了ABP框架的Web应用程序中,我们几乎不用手动处理异常。默认情况下,所有的异常都会自动处理。如果发生异常,ABP会自动记录并给客户端返回合适的结果。例如:对于一个ajax请求,返回一个json对象给客户端,表明发生了错误。但会对客户端隐藏实际的异常,除非像上述事例那样使用UserFriendlyException方法抛出。它也理解和处理客户端的错误,并向客户端显示合适的信息。

    部分源码分析:UserFriendlyException抛出异常方法。

     [Serializable]
        public class UserFriendlyException : AbpException, IHasLogSeverity, IHasErrorCode
        {
            /// <summary>
            /// Additional information about the exception.
            /// </summary>
            public string Details { get; private set; }
    
            /// <summary>
            /// An arbitrary error code.
            /// </summary>
            public int Code { get; set; }
    
            /// <summary>
            /// Severity of the exception.
            /// Default: Warn.
            /// </summary>
            public LogSeverity Severity { get; set; }
    
            /// <summary>
            /// Constructor.
            /// </summary>
            public UserFriendlyException()
            {
                Severity = LogSeverity.Warn;
            }
    
            /// <summary>
            /// Constructor for serializing.
            /// </summary>
            public UserFriendlyException(SerializationInfo serializationInfo, StreamingContext context)
                : base(serializationInfo, context)
            {
    
            }
    
            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="message">Exception message</param>
            public UserFriendlyException(string message)
                : base(message)
            {
                Severity = LogSeverity.Warn;
            }
    
            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="message">Exception message</param>
            /// <param name="severity">Exception severity</param>
            public UserFriendlyException(string message, LogSeverity severity)
                : base(message)
            {
                Severity = severity;
            }
    
            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="code">Error code</param>
            /// <param name="message">Exception message</param>
            public UserFriendlyException(int code, string message)
                : this(message)
            {
                Code = code;
            }
    
            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="message">Exception message</param>
            /// <param name="details">Additional information about the exception</param>
            public UserFriendlyException(string message, string details)
                : this(message)
            {
                Details = details;
            }
    
            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="code">Error code</param>
            /// <param name="message">Exception message</param>
            /// <param name="details">Additional information about the exception</param>
            public UserFriendlyException(int code, string message, string details)
                : this(message, details)
            {
                Code = code;
            }
    
            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="message">Exception message</param>
            /// <param name="innerException">Inner exception</param>
            public UserFriendlyException(string message, Exception innerException)
                : base(message, innerException)
            {
                Severity = LogSeverity.Warn;
            }
    
            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="message">Exception message</param>
            /// <param name="details">Additional information about the exception</param>
            /// <param name="innerException">Inner exception</param>
            public UserFriendlyException(string message, string details, Exception innerException)
                : this(message, innerException)
            {
                Details = details;
            }
        }
    View Code

    日志(Logging):由上述事例可见,可以通过在基类定义的Logger对象来写日志。ABP默认使用了Log4Net,但它是可更改和可配置的。

    部分源码分析:Log4NetLoggerFactory类。

     public class Log4NetLoggerFactory : AbstractLoggerFactory
        {
            internal const string DefaultConfigFileName = "log4net.config";
            private readonly ILoggerRepository _loggerRepository;
    
            public Log4NetLoggerFactory()
                : this(DefaultConfigFileName)
            {
            }
    
            public Log4NetLoggerFactory(string configFileName)
            {
                _loggerRepository = LogManager.CreateRepository(
                    typeof(Log4NetLoggerFactory).GetAssembly(),
                    typeof(log4net.Repository.Hierarchy.Hierarchy)
                );
    
                var log4NetConfig = new XmlDocument();
                log4NetConfig.Load(File.OpenRead(configFileName));
                XmlConfigurator.Configure(_loggerRepository, log4NetConfig["log4net"]);
            }
    
            public override ILogger Create(string name)
            {
                if (name == null)
                {
                    throw new ArgumentNullException(nameof(name));
                }
    
                return new Log4NetLogger(LogManager.GetLogger(_loggerRepository.Name, name), this);
            }
    
            public override ILogger Create(string name, LoggerLevel level)
            {
                throw new NotSupportedException("Logger levels cannot be set at runtime. Please review your configuration file.");
            }
        }
    View Code

    本地化(Localization):注意,在上述事例中使用了L("XXX")方法处理抛出的异常。因此,它会基于当前用户的文化自动实现本地化。详细见后续本地化章节。

    部分源码分析:......

    自动映射(Auto Mapping):在上述事例最后一行代码,使用了ABP的MapTo扩展方法将输入对象的属性映射到实体属性。ABP使用AutoMapper第三方库执行映射。根据命名惯例可以很容易的将属性从一个对象映射到另一个对象。

    部分源码分析:AutoMapExtensions类中的MapTo()方法。

     public static class AutoMapExtensions
        {
    
            public static TDestination MapTo<TDestination>(this object source)
            {
                return Mapper.Map<TDestination>(source);
            }
    
    
            public static TDestination MapTo<TSource, TDestination>(this TSource source, TDestination destination)
            {
                return Mapper.Map(source, destination);
            }
    
            ......
      }
    View Code

    动态API层(Dynamic API Layer):在上述事例中,TaskAppService实际上是一个简单的类。通常必须编写一个Web API Controller包装器给js客户端暴露方法,而ABP会在运行时自动完成。通过这种方式,可以在客户端直接使用应用服务方法。

    部分源码分析:......

    动态javascript ajax代理(Dynamic JavaScript AJAX Proxy):ABP创建动态代理方法,从而使得调用应用服务方法就像调用客户端的js方法一样简单。

    部分源码分析:......

    4.本章小节

    通过上述简单的类可以看到ABP的优点。完成所有这些任务通常需要花费大量的时间,但是ABP框架会自动处理。

    除了这个上述简单的事例外,ABP还提供了一个健壮的基础设施和开发模型,如模块化、多租户、缓存、后台工作、数据过滤、设置管理、领域事件、单元&集成测试等等,那么你可以专注于业务代码,而不需要重复做这些工作(DRY)

  • 相关阅读:
    HDU 5273 Dylans loves sequence 暴力递推
    HDU 5285 wyh2000 and pupil 判二分图+贪心
    HDU 5281 Senior's Gun 贪心
    HDU 5651 xiaoxin juju needs help 逆元
    HDU 5646 DZY Loves Partition
    HDU 5366 The mook jong
    HDU 5391Z ball in Tina Town 数论
    HDU 5418 Victor and World 允许多次经过的TSP
    HDU 5642 King's Order dp
    抽屉原理
  • 原文地址:https://www.cnblogs.com/OlderGiser/p/9998692.html
Copyright © 2011-2022 走看看