编程语言
首页 > 编程语言> > c# – ASP.NET Core在Web API中处理自定义响应/输出格式的方法

c# – ASP.NET Core在Web API中处理自定义响应/输出格式的方法

作者:互联网

我想创建自定义JSON格式,它会将响应包装在数据中并返回Content-Type之类的

vnd.myapi+json

目前我创建的类似于我在控制器中返回的包装类,但如果可以在引擎盖下处理它会更好:

public class ApiResult<TValue>
{
    [JsonProperty("data")]
    public TValue Value { get; set; }

    [JsonExtensionData]
    public Dictionary<string, object> Metadata { get; } = new Dictionary<string, object>();

    public ApiResult(TValue value)
    {
        Value = value;
    }
}

[HttpGet("{id}")]
public async Task<ActionResult<ApiResult<Bike>>> GetByIdAsync(int id)
{
    var bike = _dbContext.Bikes.AsNoTracking().SingleOrDefault(e => e.Id == id);
    if (bike == null)
    {
        return NotFound();
    }
    return new ApiResult(bike);
}

public static class ApiResultExtensions
{
    public static ApiResult<T> AddMetadata<T>(this ApiResult<T> result, string key, object value)
    {
        result.Metadata[key] = value;
        return result;
    }
}

我想回复如下:

{
    "data": { ... },
    "pagination": { ... },
    "someothermetadata": { ... }
}

但是分页必须以某种方式添加到我的控制器动作中的元数据中,当然这里有一些关于内容协商的文章:https://docs.microsoft.com/en-us/aspnet/core/web-api/advanced/formatting?view=aspnetcore-2.1但是我仍然想确定我是在正确的轨道上.

如果使用我的自定义格式化程序来处理它,那么我如何添加像分页一样的元数据,除了“数据”而不是它的内部?

当有一个自定义格式化程序时,我还想通过某种方式从我的控制器或某种机制向它添加元数据,因此格式可以是可扩展的.

上述方法的一个优点或缺点是它适用于所有序列化程序xml,json,yaml等.通过使用自定义格式化程序,它可能只适用于json,我需要创建几个不同的格式化程序来支持我的所有格式想.

解决方法:

好的,在花了大量时间使用ASP.NET Core之后,基本上有3种方法可以解决这个问题.这个话题本身非常复杂和广泛,想想和老实说,我认为没有灵丹妙药或最佳实践.

对于自定义Content-Type(假设你想要实现application / hal json),官方方式和最优雅的方法是创建custom output formatter.这样你的动作就不会知道输出格式,但你仍然可以控制由于依赖注入机制和作用域生存期,控制器内部的格式化行为.

1. Custom output formatters

这是OData official C# librariesjson:api framework for ASP.Net Core使用的最流行的方式.可能是实现超媒体格式的最佳方式.

要从控制器控制自定义输出格式化程序,您必须创建自己的“上下文”以在控制器和自定义格式化程序之间传递数据,并将其添加到具有作用域生存期的DI容器中:

services.AddScoped&LT APICONTEXT&GT();

这样,每个请求只有一个ApiContext实例.您可以将它注入控制器和输出格式化程序,并在它们之间传递数据.

您还可以使用ActionContextAccessor和HttpContextAccessor,并在自定义输出格式化程序中访问控制器和操作.要访问控制器,您必须将ActionContextAccessor.ActionContext.ActionDescriptor强制转换为ControllerActionDescriptor.然后,您可以使用IUrlHelper和操作名称在输出格式化程序内生成链接,以便控制器不受此逻辑的影响.

IActionContextAccessor是可选的,默认情况下不会添加到容器中,要在项目中使用它,您必须将其添加到IoC容器中.

services.AddSingleton< IActionContextAccessor,ActionContextAccessor>()

在自定义输出格式化器中使用服务:

You can’t do constructor dependency injection in a formatter class. For example, you can’t get a logger by adding a logger parameter to the constructor. To access services, you have to use the context object that gets passed in to your methods.

https://docs.microsoft.com/en-us/aspnet/core/web-api/advanced/custom-formatters?view=aspnetcore-2.0#read-write

Swashbuckle支持:

Swashbuckle显然不会使用这种方法和带过滤器的方法生成正确的响应示例.您可能需要创建自定义document filter.

示例:如何添加分页链接:

通常使用specification pattern解决分页,过滤,您通常会在[Get]操作中为规范提供一些通用模型.然后,您可以在格式化程序中识别当前执行的操作是否按其参数类型或其他方式返回元素列表:

var specificationParameter = actionContextAccessor.ActionContext.ActionDescriptor.Parameters.SingleOrDefault(p => p.ParameterType == typeof(ISpecification<>));
if (specificationParameter != null)
{
   // add pagination links or whatever
   var urlHelper = new UrlHelper(actionContextAccessor.ActionContext);
   var link = urlHelper.Action(new UrlActionContext()
   {
       Protocol = httpContext.Request.Scheme,
       Host = httpContext.Request.Host.ToUriComponent(),
       Values = yourspecification
   })
}

优点(或不):

>您的操作没有定义格式,他们对格式或如何生成链接以及放置链接的位置一无所知.他们只知道结果类型,而不知道描述结果的元数据.
>可重用,您可以轻松地将格式添加到其他项目,而无需担心如何在您的操作中处理它.与链接相关的一切,格式化都在引擎盖下处理.你的行动中不需要任何逻辑.
>序列化实现由您决定,您不必使用Newtonsoft.JSON,例如可以使用Jil.

缺点:

>这种方法的一个缺点是它只适用于特定的Content-Type.因此,为了支持XML,我们需要创建另一个带有Content-Type的自定义输出格式化程序,如vnd.myapi xml而不是vnd.myapi json.
>我们没有直接处理行动结果
>实施起来可能更复杂

2. Result filters

结果过滤器允许我们定义在操作返回之前将执行的某种行为.我认为它是某种形式的后钩.我不认为这是包装我们回应的正确位置.

它们可以按行动或全局应用于所有操作.

就个人而言,我不会将它用于此类事情,而是将其用作第三种选择的补充.

包含输出的示例结果过滤器:

public class ResultFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is ObjectResult objectResult)
        {
            objectResult.Value = new ApiResult { Data = objectResult.Value };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
    }
}

你可以在IActionFilter中使用相同的逻辑,它也可以工作:

public class ActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        if (context.Result is ObjectResult objectResult)
        {
            objectResult.Value = new ApiResult { Data = objectResult.Value };
        }
    }
}

这是包装响应的最简单方法,特别是如果您已经拥有带控制器的现有项目.所以如果你关心时间,请选择这个.

3.在您的操作中明确格式化/包装结果

(我在问题中这样做的方式)

这也用在这里:https://github.com/nbarbettini/BeautifulRestApi/tree/master/src亲自实现https://github.com/ionwg/ion-doc/blob/master/index.adoc我认为这将更适合自定义输出格式化程序.

这可能是最简单的方法,但它也会将API“密封”为特定格式.这种方法有优点,但也存在一些缺点.例如,如果您想更改API的格式,则无法轻松完成,因为您的操作与特定的响应模型相结合,并且如果您的操作中有该模型的某些逻辑,例如,您’为next和prev添加分页链接.您几乎必须重写所有操作和格式化逻辑以支持该新格式.使用自定义输出格式化程序,您甚至可以支持这两种格式,具体取决于Content-Type标头.

好处:

>适用于所有内容类型,格式是API的组成部分.
>当使用ActionResult< T>时,Swashbuckle开箱即用. (2.1),您还可以为您的操作添加[ProducesResponseType]属性.

缺点:

>您无法使用Content-Type标头控制格式.对于application / json和application / xml,它始终保持不变. (也许这有利吗?)
>您的操作负责返回格式正确的响应.像:返回新的ApiResponse(obj);或者你可以创建扩展方法并像obj.ToResponse()一样调用它,但你总是要考虑正确的响应格式.
>理论上自定义Content-Type如vnd.myapi json没有任何好处,并且仅为名称实现自定义输出格式化器没有意义,因为格式化仍然是控制器操作的责任.

我认为这更像是正确处理输出格式的捷径.我认为在single responsibility principle之后它应该是输出格式化器的工作,因为名称表明它格式化输出.

4. Custom middleware

您可以做的最后一件事是自定义中间件,您可以从那里解析IActionResultExecutor并像在MVC控制器中那样返回IActionResult.

https://github.com/aspnet/Mvc/issues/7238#issuecomment-357391426

如果需要访问控制器信息,还可以解析IActionContextAccessor以访问MVC的操作上下文并将ActionDescriptor强制转换为ControllerActionDescriptor.

文件说:

Resource filters work like middleware in that they surround the execution of everything that comes later in the pipeline. But filters differ from middleware in that they’re part of MVC, which means that they have access to MVC context and constructs.

但这并不完全正确,因为您可以访问操作上下文,并且可以从中间件返回作为MVC一部分的操作结果.

如果您有任何要添加的内容,请分享您自己的经验和优点或缺点随时发表评论.

标签:c,asp-net-core,asp-net-core-mvc,asp-net-core-webapi
来源: https://codeday.me/bug/20190527/1162148.html