5.2 ABP表现层 - 动态WebApi层

5.2.1 建立动态WebApi控制器

这是一篇关于ASP.NET Web API的文档。如果你对ASP.NET感兴趣,请阅读ASP.NET Core文档。

Abp框架能够通过应用层自动生成web api:

    public interface ITaskAppService : IApplicationService
    {
        GetTasksOutput GetTasks(GetTasksInput input);
        void UpdateTask(UpdateTaskInput input);
        void CreateTask(CreateTaskInput input);
    }

并且,我们想要暴露这个服务作为Web API Controller给客户端。那么,Abp框架通过一行关键代码的配置就可以自动、动态的为应用层建立一个web api 控制器:

Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder.For<ITaskAppService>("tasksystem/task").Build();

这样就OK了!建好的webapi控制器 /api/services/tasksystem/task 所有的方法都能够在客户端调用。webapi控制器通常是在模块初始化的时候完成配置。

ITaskAppService 是应用层服务(application service)接口,我们通过封装让接口实现一个api控制器。ITaskAppService不仅限于在应用层服务使用,这仅仅是我们习惯和推荐的使用方法。 tasksystem/task 是api 控制器的命名空间。一般来说,应当最少定义一层的命名空间,如:公司名称/应用程序/命名空间/命名空间1/服务名称。api/services/ 是所有动态web api的前缀。所以api控制器的地址一般是这样滴:/api/services/tasksystem/taskGetTasks 方法的地址一般是这样的:/api/services/tasksystem/task/getTasks 。因为在传统的js中都是使用 驼峰式 命名方法,这里也不一样。

ForAll方法

在程序的应用服务层建立多个api控制器可能让人觉得比较枯燥,DynamicApiControllerBuilper提供了建立所有应用层服务的方法,如下所示:

Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder
    .ForAll<IApplicationService>(Assembly.GetAssembly(typeof(SimpleTaskSystemApplicationModule)), "tasksystem")
    .Build();

ForAll方法是一个泛型接口,第一个参数是从给定接口中派生的集合,最后一个参数则是services命名空间的前缀。ForAll集合有ITaskAppService和 IpersonAppService接口。根据如上配置,服务层的路由是这样的:'/api/services/tasksystem/task'和'/api/services/tasksystem/person'。

服务命名约定:服务名+AppService(在本例中是person+AppService) 的后缀会自动删除,生成的webapi控制器名为“person”。同时,服务名称将采用峰驼命名法。如果你不喜欢这种约定,你也可以通过 WithServiceName 方法来自定义名称。如果你不想创建所有的应用服务层中的某些服务,可以使用where来过滤部分服务。

重写ForAll

我们可以在ForAll方法后面重写配置,如:

Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder
    .ForAll<IApplicationService>(Assembly.GetAssembly(typeof(SimpleTaskSystemApplicationModule)), "tasksystem")
    .Build();

Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder
    .For<ITaskAppService>("tasksystem/task")
    .ForMethod("CreateTask").DontCreateAction().Build();

在上面代码中,我们为指定的程序集里面的所有Application服务创建了动态WebAPI Controllers。然后为应用服务(ITaskAppService>重写配置忽略CreateTask方法。

ForMethods

当我们使用ForAll方法的时候,可以使用 ForMethods 方法来更好的调整服务的方法,如:

Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder
    .ForAll<IApplicationService>(Assembly.GetExecutingAssembly(), "app")
    .ForMethods(builder =>
    {
        if (builder.Method.IsDefined(typeof(MyIgnoreApiAttribute)))
        {
            builder.DontCreate = true;
        }
    })
    .Build();

在这个示例中,我们使用了一个自定义特性 MyIgnoreApiAttribute 来检查所有的方法,如果动态WebAPI Controller的Action上有该特性,那么我们不会为该Action创建Web API。

Http 谓词

默认,所有方法的创建是: POST。为了能够使用动态Web API Action,客户端应该使用POST发送请求。我们可以改变这个行为以不同的方式。

WithVerb 方法

我们可以对某个方法使用 WithVerb,如下:

Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder
    .For<ITaskAppService>("tasksystem/task")
    .ForMethod("GetTasks").WithVerb(HttpVerb.Get)
    .Build();
Http 特性

我们可以在服务接口的方法上使用HttpGet,HttpPost...等特性:

public interface ITaskAppService : IApplicationService
{
    [HttpGet]
    GetTasksOutput GetTasks(GetTasksInput input);

    [HttpPut]
    void UpdateTask(UpdateTaskInput input);

    [HttpPost]
    void CreateTask(CreateTaskInput input);
}

为了能使用这些特性,我们应该在项目中引用这个包:Microsoft.AspNet.WebApi.Core nuget package。

命名约定

我们可以使用 WithConventionalVerbs 方法来代替Http谓词,如下所示:

Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder
    .ForAll<IApplicationService>(Assembly.GetAssembly(typeof(SimpleTaskSystemApplicationModule)), "tasksystem")
    .WithConventionalVerbs()
    .Build();

在这种情况下,ABP可以通过方法名字的前缀来判定Http谓词:

  • Get:如果方法的名字是以 Get 开头

  • Put:如果方法的名字是以 Put 或者 Update 开头

  • Delete:如果方法的名字是以 Delete 或者 Remove 开头

  • Post:如果方法的名字是以 Post,Create 或者 Insert 开头

  • Path:如果方法的名字是以 Path 开头

  • 否则 Post 被作为Http谓词的默认设置

如前所述:我们可以覆盖某个特定的方法。

API Explorer

默认在API Explorer所有的动态WebApi控制器都是可见的(在Swagger中是有效的)。你可以使用 DynamicApiControllerBuilder API来控制这个默认行为,或者使用 RemoteService 特性来定义它。

RemoteService Attribute

你也可以在 接口上或者接口中的方法上 使用 RemoteService 特性来开启/禁用(IsEnabled)动态WebApi或者API Explorer设置(IsMetadataEnabled)。

5.2.2 使用动态JavaScript代理

你可以通过ajax来动态创建web api控制器。Abp框架对通过动态js代理建立web api 控制器做了些简化,你可以通过js来动态调用web api控制器:

abp.services.tasksystem.task.getTasks({
    state: 1
}).done(function (data) {
//use data.tasks here..
});

js代理是动态创建的,页面中需要添加引用:

<script src="/api/AbpServiceProxies/GetAll" type="text/javascript"></script>

服务方法返回约定(可参见jQuery.Deferred),你可以注册done,fail,then... 等回调方法。服务方法内部使用的是 abp.ajax,如果有需要的话可以使用它们处理错误和显示错误。

1. Ajax参数

自定义ajax代理方法的参数:

Abp.services.tasksystem.task.createTask({
    assignedPersonId: 3,
    description: 'a new task description...'
},{ //override jQuery's ajax parameters
    async: false,
    timeout: 30000
}).done(function () {
    Abp.notify.success('successfully created a task!');
});

所有的jq.ajax参数都是有效的。

除了标准的JQuery.ajax参数,为了禁用错误信息自动显示,你可以添加AJAX选项:abpHandleError: false

2. 单一服务脚本

'/api/abpServiceProxies/GetAll'将在一个文件中生成所有的代理,通过 '/api/abpServiceProxies/Get?name=serviceName' 你也可以生成单一服务代理,在页面中添加:

<script src="/api/abpServiceProxies/Get?name=tasksystem/task" type="text/javascript"></script>

3. Augular框架支持

Abp框架能够公开动态的api控制器作为angularjs服务,如下所示:

(function() {
    angular.module('app').controller('TaskListController', [
        '$scope', 'abp.services.tasksystem.task',
        function($scope, taskService) {
            var vm = this;
            vm.tasks = [];
            taskService.getTasks({
                state: 0
            }).success(function(data) {
                vm.tasks = data.tasks;
            });
        }
    ]);
})();

我们可以将名称注入服务,然后调用此服务,跟调用一般的js函数一样。注意:我们成功注册处理程序后,他就像一个augular的$http服务。ABP框架使用angular框架的$http服务,如果你想通过$http来配置,你可以设置一个配置对象作为服务方法的一个参数。

要使用自动生成的服务,需要在页面中添加:

<script src="~/Abp/Framework/scripts/libs/angularjs/abp.ng.js"></script>
<script src="~/api/AbpServiceProxies/GetAll?type=angular"></script>

Enable/Disable

如果你向上面一样使用 ForAll 方法,那么你可以使用 RemoteService 特性来禁用某个服务或方法。请在 服务的接口 中使用该特性,而不是在服务类中。

包装结果

ABP通过 AjaxResponse 对象来 包装 动态Web API Actions的返回值。你可以Enable/Disable包装对每个方法或应用服务。如下所示:

public interface ITestAppService : IApplicationService
{
    [DontWrapResult]
    DoItOutput DoIt(DoItInput input);
}

我们对DoIt禁用了包装。这个特性应该在添加在接口里面的方法描述上,而不是扩展类上。

如果你想更精确的控制返回值到客户端,禁用包装是非常有用的。特别是在使用第三方客户端库的时候,禁用包装是有必要的,因为ABP的标准包装 AjaxResponse 可能不能与之一起使用。在这种情况下,你也应该自己处理异常,因为异常处理会被禁用掉(DontWrapResult特性有个属性:WrapOnError,该属性可以被用来开启异常处理并对异常进行包装)。

注意:动态脚本代理能够使用该结果,如果该结果是未包装,而且且能够正常的被处理。

关于参数绑定

ABP在运行时创建API Controllers。所以ASP.NET Web API 的模型和参数绑定被使用来绑定模型和参数。你可以读取该文档获取更多信息。

FormUri 和 FormBody 特性

FromUri 和 FromBody 特性能够在服务接口中被使用增进对绑定的控制。

数据传输对象 VS 原生类型

对于应用服务和Web API控制器,我强烈建议使用DTO来作为方法参数。但是你也可以使用原生类型(如:string,int,bool ...或可空类型,如:int?,bool? ...) 作为服务方法的参数。可以使用多个参数;但是,在这些参数中有且只能有一个 Complex-Type 参数,这是ASP.NET Web API 规定的。