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

Jam*_*gan 8 c# rest asp.net-mvc hateoas asp.net-web-api

我正在开发一个新的asp.net web api restful服务,并花了一些时间在这个主题上的一些Pluralsight课程.其中一个更好的潜水深入设计和超媒体(HATEOAS)的实现.

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

但是,当我开始深入研究我的实现时,使用UrlHelper()来计算返回的链接开始分崩离析.

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

所有结果都通过一个ModelFactory,它将我的POCO转换为返回结果,然后再返回post,patch和puts.

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

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

我的目标:

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

2)在单个结果中(即"Get(id)"返回的结果),包括指向self的链接,包括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
                  }
            );
    }
Run Code Online (Sandbox Code Playgroud)

控制器获取(id)

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

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

        return Ok(TheModelFactory.Create(entity));
    }
Run Code Online (Sandbox Code Playgroud)

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
               };
    }
Run Code Online (Sandbox Code Playgroud)

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
               };
    }
Run Code Online (Sandbox Code Playgroud)

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
}]
Run Code Online (Sandbox Code Playgroud)

}

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
Run Code Online (Sandbox Code Playgroud)

}

更新1

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

Jam*_*gan 6

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

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