65.9K
CodeProject 正在变化。 阅读更多。
Home

开发可扩展的 Web API

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (4投票s)

2019年7月2日

CPOL

2分钟阅读

viewsIcon

12802

使用 ASP.NET Web API2 开发一个非常简单的 Web API 架构,其操作可以通过仅添加新的操作类型来扩展

  

引言

我最近需要准备一个 Web API,它有几个小操作,并且会不断添加新的操作。 我决定使用可扩展的架构,而不是添加许多包含少量操作的控制器。 因此,只能通过添加新的操作类型来添加新操作。

架构

该架构和项目非常简单。 该项目是使用 Visual Studio 2017 的 Web API 2 项目模板创建的。 只有一个 API 控制器,名为 OperationsController,其中只有一个名为 Process 的操作。 有一个简单的 IOperation 接口,一个操作必须实现该接口,以及一个名为 OperationBase 的基类,用于样板代码。

AutofacAutofac WebAPI2 集成包用于自动发现操作类型并将它们注入到控制器中。

代码

IOperation 接口如下

public interface IOperation
{
    string Name { get; }
    string Description { get; }
    Type ParameterClassType { get; }
    object Execute(object prm);
}

ParameterClassTypeExecute 方法需要的参数对象的类型。 它用于解析 request 主体中的 JSON 数据。 如果 Execute 方法返回一个非 null 值,它将作为结果返回给调用者,否则只返回 Ok 响应。

OperationController 及其 Process 操作如下

public class OperationsController : ApiController
{

   //Implemented operations are injected in the constructor
   public OperationsController(IEnumerable<IOperation> supportedOperations)
   {
      _supportedOperations = new List<IOperation>(supportedOperations);
   }

   //Single action that gets the operation name and 
   //reads the operation parameters as JSON from request body

   [HttpPost]
   public async Task<IHttpActionResult> Process(string operationName)
   {
      //Find the operation
      IOperation operation = _supportedOperations.FirstOrDefault(x => x.Name == operationName);
      if (operation == null)
           return BadRequest($"'{operationName}' is not supported.");

      //Get the request body as string
      string jsonBody = await Request.Content.ReadAsStringAsync();
      object operationParams = null;  

      try
      {
          //Parse the JSON data in the request body t construct operation's parameter object
          if (operation.ParameterClassType != null)
                operationParams = Newtonsoft.Json.JsonConvert.DeserializeObject
                                  (jsonBody, operation.ParameterClassType);

          object result = operation.Execute(operationParams);

          //Return the result value as JSON to the caller
          if (result != null)
              return Json(result);
      }
      catch(Exception ex)
      {
           return InternalServerError(ex);
      }

      //Return Ok if the operation has no return value
      return Ok();          
   }    
}

只需要 operationName 值的路由配置如下

config.Routes.MapHttpRoute(
      name: "DefaultApi",
      routeTemplate: "api/{operationName}",
      defaults: new { controller = "operations", action = "process", operationName = "help" }
);

此路由配置将所有请求路由到 OperationsControlllerProcess 操作。 如果没有提供操作名称,则执行帮助。

Autofac 依赖注入已配置并设置为 Web API 管道的依赖关系解析器

ContainerBuilder builder = new ContainerBuilder();

//Register controllers
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());

//Register all operations
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
           .Where(t=> t.IsAssignableTo<IOperation>())
           .As<IOperation>();

var container = builder.Build();

//Set Autofac container as the dependency resolver
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

使用 Postman 进行测试

测试无参数操作“help”。

从“help”操作返回的响应。

测试“sum”操作,该操作需要一个具有属性“Number1”和“Number2”的对象。

从“sum”操作返回的响应。

后续步骤

为了仅演示这个想法,代码保留得非常简单。 将命令处理操作与控制器类分开是件好事。

帮助命令仅显示操作的名称和描述,但最好提供特定操作是否需要参数以及它们的名称类型。 因为操作不是控制器操作,所以 Swagger 不会帮助提供参数信息。

也没有自动验证。 可以在操作的 Execute 方法之前完成验证,或者每个操作都可以执行自己的验证逻辑。

还应该添加身份验证和授权操作以保护 Web API。

历史

  • 2019 年 7 月 3 日:初始版本
© . All rights reserved.