6

我有一个Groups具有以下操作的控制器:

public GroupModel Get(int ID)

public GroupModel Post(CreateGroupModel model)

public void Put(PublicUpdateGroupModel model)

public void PutAddContacts(UpdateContactsModel model)

public void PutRemoveContacts(UpdateContactsModel model)

public void Delete(int ID)

而我想做的是使用标准的 REST 路由来调用标准的 get、post、put、delete 方法。但如果操作名称附加到 url,请调用PutAddContactsand PutRemoveContacts,例如:

GET groups/ - 调用 Get 方法

POST 组/ - 调用 Post 方法

PUT 组/ - 调用 Put 方法

DELETE groups/ - 调用 Delete 方法

PUT 组/添加联系人 - 调用 PutAddContacts 方法

PUT groups/removecontacts - 调用 PutRemoveContacts 方法

是否可以设置路由来执行此操作,或者如果我想在我的 URL 中使用操作名称,我是否需要沿着 RPC 路由进行路由?

4

2 回答 2

12

您现在拥有
的 要使用上述方法,您需要使用 RPC。那是因为您的示例已经一半沉浸在 RPC 做事风格中。默认的 WebAPI 路由鼓励 RESTful 设置,但是如果您对路由进行了微小的更改,一切都会开始工作。例如,您可以将默认路由更改为典型的 MVC 路由:

routes.MapRoute( name    : "Default",       
                 url     : "{controller}/{action}/{id}",
                 defaults: new { controller = "Home", 
                                 action     = "Index", 
                                 id         = UrlParameter.Optional });

添加路由后,以典型的 MVC 方式调用事物,使用控制器名称和操作。然而,从你的问题来看,我怀疑你实际上想要成为 RESTful,而不是让它工作,所以请继续阅读......

成为 RESTful
REST不需要 HTTP,尽管这两者经常一起讨论。REST 实际上是关于具有语义准确表示的每个资源。使用 HTTP 时,这意味着尊重 HTTP 语义的唯一 URI。例如,使用 HTTP GET 的调用不应该修改数据,因为这违反了 HTTP 对 GET 的定义,并混淆了缓存等 HTTP 基础设施。

POST/PUT 与 MERGE/PATCH
我们都熟悉 GET、POST、PUT、HEAD 等 HTTP 方法。通常,GET 用于检索,POST 用于添加,PUT 用于修改(尽管有很多争论)。但是,您有两种类型的修改:添加项目和从集合中删除项目。那么这些都是 PUT 还是其他?社区还没有完全确定如何做到这一点。

  • 选项 1:自定义媒体类型 - HTTP 规范确实允许各种方法,是浏览器真正将我们限制在熟悉的子集中。因此,您可以创建MERGE(一种 Roy Fielding 解决方法)或 PATCH(一种 oData 解决方法)方法并定义这种新媒体类型的行为——可能一个用于添加,一个用于删除。

  • 选项 2:使用 POST/PUT - 使用 PUT 添加和删除联系人。只需将 ID 列表视为切换(如果存在,则删除,如果缺少添加)或交替包含足够的信息以知道该做什么。然后返回一个 HTTP 303 向客户端指示它具有陈旧状态并刷新。

  • 选项 3:完整列表- 如果您的集合大小合理,您可以在每次想要更新时传递完整的联系人列表。这种方式的逻辑是一个超级简单的擦除和替换。

从 RESTful 的角度来看,真正重要的是您的应用程序在所有方法中都以一致的方式运行。因此,如果 MERGE 意味着添加,它应该始终意味着添加。如果您希望将一组完整的 ID 传递给 PUT,则始终传递一组完整的 ID。

控制器设计
如果是我,我会把你的控制器分成多个控制器。一个控制器处理组,另一个处理联系人(作为一个组),第三个处理组内的一个联系人。就像是 ...

//api/Group/
public List<GroupModel> Get()
public GroupModel Get(int ID)
public GroupModel Post(GroupModel model)  //add a group
public GroupModel Put(GroupModel model)   //update a group (see comments above)
public void Delete(int ID)


//api/GroupContacts/
public ContactsModel Get()                    //gets complete list
public void PostContacts(ContactsModel model) //pushes a COMPLETE new state
public void Delete()                          //delete entire group of contacts


//api/GroupContact/354/
public ContactModel Get(int id)             //get contact id #354
public void PostContact(ContactModel model) //add contact (overwrite if exits)
public void Delete(int id)                  //delete contact if exists

如果您希望您的 URL 显示为嵌套的(例如:/api/Group/Contacts、、/api/Group/Contact),您可以查看我写的其他帖子。恕我直言,asp.net 的路由需要调整以更轻松地支持嵌套......但这是一个不同的问题;-)

于 2012-09-10T11:58:15.483 回答
4

呼应 EBarr 所说的,在 Web API 中进行分层路由可能有点痛苦。我在我的服务中经常使用它,所以我为 Web API 构建了一个替代路由服务。Nuget 在这里,源代码在GitHub 上

这种路由方法要求您将 URI 命名空间构建为路径段的层次结构。不是将完整的 URI 模式与控制器匹配,而是将控制器附加到路径段树中的任意点。

只是为了让您了解它的外观,我创建了一个小型自托管示例,其 URI 与您尝试做的类似:

internal class Program
{
   private static void Main(string[] args)
   {
    var baseAddress = new Uri("http://oak:8700/");

    var configuration = new HttpSelfHostConfiguration(baseAddress);
    var router = new ApiRouter("api", baseAddress);

    // /api/Contacts
    router.Add("Contacts", rcs => rcs.To<ContactsController>());

    // /api/Contact/{contactid}
    router.Add("Contact", rc =>
                          rc.Add("{contactid}", rci => rci.To<ContactController>()));

    // /api/Group/{groupid}
    // /api/Group/{groupid}/Contacts
    router.Add("Group", rg =>
                        rg.Add("{groupid}", rgi => rgi.To<GroupController>() 
                                                       .Add("Contacts", rgc => rgc.To<GroupContactsController>())));


    configuration.MessageHandlers.Add(router);

    var host = new HttpSelfHostServer(configuration);
    host.OpenAsync().Wait();

    Console.WriteLine("Host open.  Hit enter to exit...");

    Console.Read();

    host.CloseAsync().Wait();
  }
}

public class GroupController : TestController { }
public class ContactsController : TestController { }
public class ContactController : TestController { }
public class GroupContactsController : TestController { }


public class TestController : ApiController
{
    public HttpResponseMessage Get()
    {
        var pathRouteData = (PathRouteData) Request.GetRouteData();

        var paramvalues = new StringBuilder();

        foreach (KeyValuePair<string, object> keyValuePair in pathRouteData.Values)
        {
            paramvalues.Append(keyValuePair.Key);
            paramvalues.Append(" = ");
            paramvalues.Append(keyValuePair.Value);
            paramvalues.Append(Environment.NewLine);
        }

        var url = pathRouteData.RootRouter.GetUrlForController(this.GetType());

        return new HttpResponseMessage()
                   {
                       Content = new StringContent("Response from " + this.GetType().Name + Environment.NewLine
                                                   + "Url: " + url.AbsoluteUri
                                                   + "Parameters: " + Environment.NewLine
                                                   + paramvalues.ToString())
                   };
    }
}

您应该能够将此代码粘贴到控制台应用程序中并添加对 Microsoft.AspNet.WebApi.SelfHost 和 Tavis.WebApiRouter nuget 的引用并尝试一下。如果您对这种路由能走多远感到好奇,这里有一个更复杂的示例

于 2012-09-10T15:08:21.663 回答