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

本文关键字:上下文 HAEOAS URI 超媒体 基于 Web API REST | 更新日期: 2023-09-27 17:58:52

我正在开发一个新的asp.net web api restful服务,并花了一些时间学习有关该主题的Pluralsight课程。其中一个比较好的方法是深入研究超媒体(HAEOAS)的设计和实现。

我跟踪了视频中的实现,因为它非常直接,而且作为mvc/web api的新手,看到它端到端地工作真的很有帮助。

然而,当我开始深入研究我的实现时,使用UrlHelp()来计算要返回的链接就开始失败了。

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

所有的结果都经过一个ModelFactory,它转换我的POCO以返回结果,然后再次发布、修补和放置。

我试图以一种更复杂的方式来实现这一点,允许ModelFactory处理链接创建的所有智能,因为它是使用Request对象构建的。

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

我的目标:

1) 在结果集中(即"Get()"返回的结果集合),根据需要包括项目总数、页面总数、下一页和上一页。我已经实现了一个自定义的json转换器,可以在地面上丢弃空链接。例如,当你在第一页时,我不会打印出"prevPage"。这在今天是有效的。

2) 在单独的结果(即"Get(id)"返回的结果)中,要包含到self的链接,请包含rel、链接所代表的方法以及它是否被模板化。这在今天是有效的。

什么是坏的:

正如您将在下面的输出中看到的,有两件事是"错误的"。当您查看新的单个项目的"POST"链接时,URL是正确的。这是因为我剥离了URI的最后一部分(去掉了资源ID)。然而,当返回结果集时,"POST"的URI现在是不正确的。这是因为路由不包括单独的资源id,因为调用了"Get()",而不是"Get(id)"。

同样,根据被击中的方法,可以更改实现以产生不同的链接,将它们从工厂中拉出并放入控制器,但我相信我只是缺少了一些明显的东西。

这个新手有没有路由和Web API的指针?

控制器获取()

[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 Create()

 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
               };
    }

获取()的结果

{
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的不足之处在于我需要处理上下文数据(如请求)。由于我的HAEOAS实现已经被拉出并放入MessageHandler中,AutoMapper再次成为一个可行的选项。

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

我扩展了Ben的解决方案(下面的链接),它满足了我对它的每一个要求。我相信,用所需的HAEOAS数据"丰富"处理程序中的返回结果是可行的。我唯一需要直接在处理程序之外设置链接的时间是当我进入诸如分页之类的事情时,只有控制器拥有必要的信息来决定链接应该是什么样子。在这一点上,我只需将链接添加到我的模型上的集合,该集合将一直传递到可能添加更多链接的处理程序。

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

我已经使用ASP.NET Core扩展了Ben的方法。我的方法使用ResultFilter,其中响应用链接装饰。链接生成器(enricher)用于支持超媒体链接的每个模型。由于没有关于链接格式的官方标准,所以使用了Paypal的定义。请查看我的博客生成ASP.NET核心Web API 的超媒体链接