【问题标题】:Web API action selection based on Accept header基于 Accept 标头的 Web API 操作选择
【发布时间】:2013-08-28 15:35:54
【问题描述】:

排队长介绍...

我有一个资源定义在

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 显示在系统中所有Widgets 的摘要网格上,它会在特定@ 的编辑页面上请求FullWidgetForEditing 987654331@,它会请求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 才能实现这一点(然后确保我的重新实现保持最新)?

【问题讨论】:

  • 活动挂图,我也在寻找您所描述的内容 - 一个 URL 应该代表 1 个对象(不是它的格式或返回类型)。你有没有找到解决办法?我很困惑为什么覆盖默认选择器会占用这么多代码,你不应该只处理你的接受标头,如果你没有得到匹配,将请求传递给基类吗?我错过了什么?

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


【解决方案1】:

所以这是你需要思考的问题。这真的是三种表示形式,还是三种不同的资源?如果您决定缓存这些表示,URI 是否足以识别缓存的表示,或者您是否还需要更改接受标头?

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

当您想要支持仅能够支持特定媒体类型的不同客户端设备时,通常需要多种表示形式。例如设备 A 获得 Rep1,设备 B 获得 Rep2。您很少希望设备 A 同时访问设备 A 和 Rep1 和 Rep2。

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

【讨论】:

  • 就问题本身而言,是的,您是对的。总的来说,活动挂图指向一个有趣的话题,那就是 API 版本控制和使用 MediaTypes 或 X-MediaTypes 路由......
猜你喜欢
  • 2017-10-24
  • 1970-01-01
  • 2012-12-09
  • 2015-07-15
  • 2013-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多