Web API中基于上下文的REST超媒体URI更改(HATEOAS)

我正在研究一个新的asp.net web api宁静服务,并花了一些时间与一些关于这个主题的Pluralsight课程。 一个更好的人深入设计和实施超媒体(HATEOAS)。

我跟着视频中的实现,因为它非常直接,并且对于mvc / web api是新手,它看起来很有帮助。

然而,只要我开始深入了解我的实现,使用UrlHelper()计算返回链接就开始崩溃。

在下面的代码中,我有一个简单的Get(),它返回一个特定资源的集合,然后返回一个允许返回单个资源的Get(int id)。

所有的结果都通过一个ModelFactory,它将我的POCO转换为返回结果,并在post,patch和put上再次返回结果。

我试图通过允许ModelFactory处理所有链接创建的智能,因为它是使用Request对象构造的,从而以更复杂的方式完成此操作。

现在我知道我可以通过简单地处理我的方法中的链接生成/包含权来解决所有这些问题,也许这就是答案,但我很好奇其他人如何处理它。

我的目标:

1)在结果集中(即由“Get()”返回的结果集合),以包括总项目数,总页数,必要时的下一页和上一页。 我已经实现了一个定制的json转换器来删除地面上的空链接。 例如,当您在第一页上时,我不打印出“prevPage”。 今天起作用。

2)在单个结果中(即由“Get(id)”返回的结果),包括链接到self,include rel,链接代表的方法以及它是否是模板化的。 今天起作用。

什么是坏的:

正如你将在下面的输出中看到的,有两件事情是“错误的”。 当您查看新的单个项目的“POST”链接时,该URL是正确的。 这是因为我删除了URI的最后部分(删除资源ID)。 但是,返回结果集时,“POST”的URI现在不正确。 这是因为路由不包含单个资源ID,因为调用了“Get()”,而不是“Get(id)”。

再次,实施可以改变产生不同的链接,取决于哪种方法被击中,将他们拉出工厂和控制器,但我想相信我只是错过了一些明显的东西。

这个新手到路由和Web API的任何指针?

控制器Get()

[HttpGet]
    public IHttpActionResult Get(int pageSize = 50, int page = 0)
    {
        if (pageSize == 0)
        {
            pageSize = 50;
        }

        var links = new List<LinkModel>();

        var baseQuery = _deliverableService.Query().Select();
        var totalCount = baseQuery.Count();
        var totalPages = Math.Ceiling((double) totalCount / pageSize);

        var helper = new UrlHelper(Request);
        if (page > 0)
        {
            links.Add(TheModelFactory.CreateLink(helper.Link("Deliverables",
                new
                {
                    pageSize,
                    page = page - 1
                }),
                "prevPage"));
        }
        if (page < totalPages - 1)
        {
            links.Add(TheModelFactory.CreateLink(helper.Link("Deliverables",
                new
                {
                    pageSize,
                    page = page + 1
                }),
                "nextPage"));
        }

        var results = baseQuery
            .Skip(page * pageSize)
            .Take(pageSize)
            .Select(p => TheModelFactory.Create(p))
            .ToList();

        return Ok(new DeliverableResultSet
                  {
                      TotalCount = totalCount,
                      TotalPages = totalPages,
                      Links = links,
                      Results = results
                  }
            );
    }

控制器获取(id)

        [HttpGet]
    public IHttpActionResult GetById(int id)
    {
        var entity = _deliverableService.Find(id);

        if (entity == null)
        {
            return NotFound();
        }

        return Ok(TheModelFactory.Create(entity));
    }

ModelFactory创建()

 public DeliverableModel Create(Deliverable deliverable)
    {
        return new DeliverableModel
               {
                   Links = new List<LinkModel>
                           {
                               CreateLink(_urlHelper.Link("deliverables",
                                   new
                                   {
                                       id = deliverable.Id
                                   }),
                                   "self"),
                                   CreateLink(_urlHelper.Link("deliverables",
                                   new
                                   {
                                       id = deliverable.Id
                                   }),
                                   "update", "PUT"),
                                   CreateLink(_urlHelper.Link("deliverables",
                                   new
                                   {
                                       id = deliverable.Id
                                   }),
                                   "delete", "DELETE"),
                               CreateLink(GetParentUri() , "new", "POST")
                           },
                   Description = deliverable.Description,
                   Name = deliverable.Name,
                   Id = deliverable.Id
               };
    }

ModelFactory CreateLink()

public LinkModel CreateLink(string href, string rel, string method = "GET", bool isTemplated = false)
    {
        return new LinkModel
               {
                   Href = href,
                   Rel = rel,
                   Method = method,
                   IsTemplated = isTemplated
               };
    }

Get()的结果

{
totalCount: 10,
totalPages: 4,
links: [{
    href: "https://localhost/Test.API/api/deliverables?pageSize=2&page=1",
    rel: "nextPage"
}],
results: [{
    links: [{
        href: "https://localhost/Test.API/api/deliverables/2",
        rel: "self"
    },
    {
        href: "https://localhost/Test.API/api/deliverables/2",
        rel: "update",
        method: "PUT"
    },
    {
        href: "https://localhost/Test.API/api/deliverables/2",
        rel: "delete",
        method: "DELETE"
    },
    {
        href: "https://localhost/Test.API/api/",
        rel: "new",
        method: "POST"
    }],
    name: "Deliverable1",
    description: "",
    id: 2
},
{
    links: [{
        href: "https://localhost/Test.API/api/deliverables/3",
        rel: "self"
    },
    {
        href: "https://localhost/Test.API/api/deliverables/3",
        rel: "update",
        method: "PUT"
    },
    {
        href: "https://localhost/Test.API/api/deliverables/3",
        rel: "delete",
        method: "DELETE"
    },
    {
        href: "https://localhost/Test.API/api/",
        rel: "new",
        method: "POST"
    }],
    name: "Deliverable2",
    description: "",
    id: 3
}]

}

Get(id)的结果

{
links: [{
    href: "https://localhost/Test.API/api/deliverables/2",
    rel: "self"
},
{
    href: "https://localhost/Test.API/api/deliverables/2",
    rel: "update",
    method: "PUT"
},
{
    href: "https://localhost/Test.API/api/deliverables/2",
    rel: "delete",
    method: "DELETE"
},
{
    href: "https://localhost/Test.API/api/deliverables/",
    rel: "new",
    method: "POST"
}],
name: "Deliverable2",
description: "",
id: 2

}

更新1

周五我发现并开始实施这里概述的解决方案:http://benfoster.io/blog/generating-hypermedia-links-in-aspnet-web-api。 Ben的解决方案经过深思熟虑,并允许我维护我的模型(存储在公共可用的库中以用于其他.NET(即RestSharp))解决方案,并允许我使用AutoMapper而不是实现自己的ModelFactory。 AutoMapper的缺点是当我需要处理上下文数据(如Request)时。 由于我的HATEOAS实现已经被拉出并进入MessageHandler,AutoMapper再次成为可行的选择。


我扩展了Ben的解决方案(下面的链接),它满足了我对它的所有要求。 我相信用所需的HATEOAS数据“处理”返回处理结果是一条可行的路。 我唯一需要在处理程序外部直接设置链接的时间是当我进入分页等事情时,只有控制器有必要的信息来决定链接应该是什么样子。 在那个时候,我只需在我的模型中添加链接到集合,然后链接到可能会添加更多链接的处理程序。

http://benfoster.io/blog/generating-hypermedia-links-in-aspnet-web-api


我已经使用ASP.NET Core扩展了Ben的方法。 我的方法使用了一个ResultFilter,其中的响应是用链接修饰的。 链接构建器(richher)用于支持超媒体链接的每个模型。 由于没有关于如何格式化链接的官方标准,所以使用了Paypal的定义。 请检查我的博客为ASP.NET Core Web API生成超媒体链接

链接地址: http://www.djcxy.com/p/78683.html

上一篇: REST Hypermedia URI Changes Based On Context in Web API (HATEOAS)

下一篇: Is the JsonSerializerSettings thread safe?