基于Accept标头的Web API操作选择

时间:2013-08-26 07:48:20

标签: c# asp.net-web-api asp.net-web-api-routing

排队长篇介绍......

我在

定义了资源
http://my-awesome-product.com/api/widgets/3

表示一个id为3的窗口小部件。在Web API中,我将定义一个控制器来为该资源提供服务,如下所示:

public class WidgetsController : ApiController
{
    public Widget Get(int id)
    {
        return new Widget(...);
    }
}

现在,Widget类可能非常大,并且希望将带宽从数据库保存到Web服务器,并且从Web服务器到客户端,我创建了几个包含受限编号的DTO类总体Widget的字段。例如:

public class WidgetSummary
{
    public int Id { get; set; }
    public string Code { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
}

public class FullWidgetForEditing
{
    public int Id { get; set; }
    public string Code { get; set; }
    public string Description { get; set; }
    public decimal Weight { get; set; }
    public decimal Price { get; set; }
    public Color Color { get; set; }
    public decimal Width { get; set; }
    public decimal Height { get; set; }
    public decimal Depth { get; set; }
}

public class WidgetForDropDownList
{
    public int Id { get; set; }
    public string Code { get; set; }
}

使用这些Widget表示形式,客户端会请求WidgetSummary在系统中所有Widget的摘要网格上显示,它会请求FullWidgetForEditing在特定Widget的修改页面上,它会请求WidgetForDropDownList在订单表单的下拉列表中使用。

根据我对REST的理解,访问Widget应该有一个URL,因为它是一个单一资源,无论其表示如何,客户端都应指定具有媒体类型的Accept标头以不同形式检索Widget的参数,例如my-awesome-product.type=widgetsummarymy-awesome-product.type=fullwidgetforeditingmy-awesome-product.type=widgetfordropdownlist

我可以通过检查请求的标题来实现Web API中的那样:

public class WidgetsController : ApiController
{
    public object Get(int id)
    {
        if (Request.Headers.Accept.Any(x => x.Parameters.Any(y => y.Name == "my-awesome-product.type" && y.Value == "widgetsummary")))
        {
            return new WidgetSummary(...);
        }
        else if (Request.Headers.Accept.Any(x => x.Parameters.Any(y => y.Name == "my-awesome-product.type" && y.Value == "fullwidgetforediting")))
        {
            return new FullWidgetForEditing(...);
        }
        else if (Request.Headers.Accept.Any(x => x.Parameters.Any(y => y.Name == "my-awesome-product.type" && y.Value == "widgetfordropdownlist")))
        {
            return new WidgetForDropDownList(...);
        }

        throw new HttpResponseException(HttpStatusCode.NotAcceptable);
    }
}

然而,随着类型数量的增加并使单元测试更加困难,这会很快变得混乱。我真正想做的是:

public class WidgetsController : ApiController
{
    [HttpGet(MediaType = "my-awesome-product.type", MediaTypeValue = "widgetsummary")]
    public WidgetSummary GetWidgetSummary(int id)
    {
        return new WidgetSummary();
    }

    [HttpGet(MediaType = "my-awesome-product.type", MediaTypeValue = "fullwidgetforediting")]
    public FullWidgetForEditing GetFullWidgetForEditing(int id)
    {
        return new FullWidgetForEditing();
    }

    [HttpGet(MediaType = "my-awesome-product.type", MediaTypeValue = "widgetfordropdownlist")]
    public WidgetForDropDownList GetWidgetForDropDownList(int id)
    {
        return new WidgetForDropDownList();
    }
}

具有基于媒体类型的特定操作方法的Web API路由。我调查了覆盖ApiControllerActionSelector,然而,我开始为此属性提取大量现有代码,因为我真的想要默认操作选择,但是在模糊操作的情况下,我想要过滤操作列表基于媒体类型。我真的希望它是非侵入式的,这样我就可以将常规路由,标准属性路由(在Web API v2中)和这个假设属性路由混合在同一个控制器中(如果需要)。

提问时间:目前是否可以使用Web API实现这样的路由策略?我是否必须完全重新实现ApiControllerActionSelector以实现此目的(然后确保我的重新实现保持最新)?

1 个答案:

答案 0 :(得分:2)

所以这是你需要思考的问题。那些真的是三种表征,还是三种截然不同的资源?如果您决定缓存这些表示,URI是否足以识别缓存的表示,或者您是否还需要改变accept头?

如果您接受它们与资源不同,那么它们应该由不同的URL标识。如果您决定在回复中使用超链接,您是否希望能够独立于“widgeteditform”指向“widgetsummary”?

当您想要支持仅支持某些媒体类型的不同客户端设备时,通常需要多个表示。例如设备A获取Rep1,设备B获得Rep2。您希望Device A同时访问Device A和Rep1以及Rep2。

只是我的想法......