简体   繁体   English

ASP.NET Web API中的多个PUT方法

[英]Multiple PUT methods in ASP.NET Web API

I have a controller Groups with the following actions: 我有一个控制器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)

And what I would like to do is use standard REST routing to call the standard get, post, put, delete mehods. 我想做的是使用标准的REST路由来调用标准的get,post,put,delete mehods。 But call the PutAddContacts and PutRemoveContacts if the action names are appended to the url, for example: 但是如果动作名称被附加到url,则调用PutAddContactsPutRemoveContacts ,例如:

GET groups/ - calls Get method 获取组/ - 调用Get方法

POST groups/ - calls Post method POST组/ - 调用Post方法

PUT groups/ - calls Put method PUT组/ - 调用Put方法

DELETE groups/ - calls Delete method DELETE groups / - 调用Delete方法

PUT groups/addcontacts - calls PutAddContacts method PUT groups / addcontacts - 调用PutAddContacts方法

PUT groups/removecontacts - calls PutRemoveContacts method PUT groups / removecontacts - 调用PutRemoveContacts方法

Is it possible to set up routing to do this or do I need to go down the RPC route for routing if I want to use action names in my URL's? 是否可以设置路由来执行此操作,或者如果我想在URL中使用操作名称,是否需要沿着RPC路由进行路由?

What you have now 你现在有什么
To utilize your methods as above you'll need to go RPC. 要使用上述方法,您需要使用RPC。 That is because your example is already half way steeped in the RPC style of doing things. 这是因为你的例子已经完成了RPC的做事方式。 Default WebAPI routes encourage RESTful setups, but if you made a minor alteration to your routes everything would start working. 默认的WebAPI路由鼓励RESTful设置,但如果您对路由进行了微小的更改,一切都将开始工作。 For example you could change your default route to something like a typical MVC route: 例如,您可以将默认路由更改为典型的MVC路由:

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

After adding the route, call things in typical MVC fashion where you use the controller name & action. 添加路由后,以典型的MVC方式调用您使用控制器名称和操作的内容。 From your question, however, I suspect you actually want to be RESTful, instead of just getting it to work so read on... 但是,从你的问题来看,我怀疑你确实想要成为RESTful,而不仅仅是让它工作所以请继续阅读......

Being RESTful 保持沉默
REST doesn't require HTTP , although the two are often discussed together. REST 不需要HTTP ,尽管两者经常在一起讨论。 REST is really about every resource having a semantically accurate representation. REST实际上是关于具有语义准确表示的每个资源。 When using HTTP that means unique URI's that respect HTTP semantics. 使用HTTP时,这意味着尊重HTTP语义的唯一URI。 So for example, a call using HTTP GET should never modify data because that violates HTTP's definition of GET and confused HTTP infrastructure like caches. 因此,例如,使用HTTP GET的调用永远不应该修改数据,因为这违反了HTTP的GET定义和混淆HTTP基础设施(如缓存)。

POST/PUT vs MERGE/PATCH POST / PUT与MERGE / PATCH
We're all familiar with GET, POST, PUT, HEAD, etc.. as HTTP methods. 我们都熟悉GET,POST,PUT,HEAD等作为HTTP方法。 Generally , GET is for retrieving, POST is for adding, and PUT is for modifying (although subject to lots of debate). 通常 ,GET用于检索,POST用于添加,PUT用于修改(尽管存在很多争议)。 Yet, you have two types of modifications: adding items and removing items from a collection. 但是,您有两种类型的修改:添加项目和从集合中删除项目。 So are those both PUT or something else? PUT还是别的什么呢? The community hasn't quite settled on how to do this. 社区还没有完全确定如何做到这一点。

  • Option 1: Custom media type - The HTTP spec really allows for all sorts methods, it's the browsers that really restrict us to the familiar subset. 选项1:自定义媒体类型 - HTTP规范确实允许所有排序方法,它是真正限制我们熟悉的子集的浏览器。 So you can create MERGE (a Roy Fielding work around) or PATCH (an oData work around) methods and define the behavior for this new media type -- maybe one for adding and one for removing. 因此,您可以创建MERGE(Roy Fielding解决方法)或PATCH(oData work around)方法,并定义此新媒体类型的行为 - 可能是一个用于添加,另一个用于删除。

  • Option 2: Use POST/PUT - Use PUT for both adding and removing contacts. 选项2:使用POST / PUT - 使用PUT添加和删除联系人。 Just treat the list of ID's like a toggle (if exists remove, if missing add) or alternatley include enough information to know what to do. 只需将ID列表视为切换(如果存在则删除,如果缺少添加)或者altatley包含足够的信息以了解要执行的操作。 Then return an HTTP 303 indicating to the client it has a stale state and refresh. 然后返回HTTP 303,指示客户端它具有陈旧状态并刷新。

  • Option 3: Full List - If your set is a reasonable size, you can always pass a complete list of contacts every time you want to update. 选项3:完整列表 - 如果您的设置大小合理,则每次要更新时都可以传递完整的联系人列表。 This way the logic is a super simple wipe and replace. 这种逻辑是一种超级简单的擦除和替换。

What really matters from a RESTful perspective is that your application behaves in a consistent way across all methods. 从RESTful角度来看,真正重要的是您的应用程序在所有方法中以一致的方式运行。 So if MERGE means add, it should always mean add. 因此,如果MERGE意味着添加,它应该始终意味着添加。 If you expect a complete set of ID's passed to PUT then always pass a complete set. 如果您希望将一组完整的ID传递给PUT,那么总是传递一套完整的ID。

Controller Design 控制器设计
If it were me, I would break your controller into multiple controllers. 如果是我,我会把你的控制器分成多个控制器。 One controller deals with Groups another deals Contacts (as a group) and a third deals with one contact within a group. 一个控制器处理组另一个交易联系人(作为一个组),第三个处理一个组内的一个联系人。 Something like ... 就像是 ...

//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

If you want your URL's to appear nested (eg: /api/Group/Contacts , /api/Group/Contact ), you can look at this other post I wrote . 如果您希望您的URL显示为嵌套(例如: /api/Group/Contacts/api/Group/Contact ),您可以查看我写的这篇文章 IMHO, asp.net's routing needs a tune up to support nesting a bit easier ...but that's a different issue;-) 恕我直言,asp.net的路由需要调整以支持嵌套更容易......但这是一个不同的问题;-)

To echo what EBarr said, doing hierarchical routing in Web API can be a bit of a pain. 为了回应EBarr所说的,在Web API中进行分层路由可能会有点痛苦。 I use it a lot in my services so I built a replacement routing service for Web API. 我在我的服务中经常使用它,所以我为Web API构建了替换路由服务。 The Nuget is here and the source is on GitHub Nuget在这里 ,源代码在GitHub上

This approach to routing requires you to build up your URI namespace as a hierarchy of path segments. 这种路由方法要求您将URI命名空间构建为路径段的层次结构。 Rather than matching complete URI patterns to controllers, you attach controllers to arbitrary points in the tree of path segments. 您可以将控制器附加到路径段树中的任意点,而不是将完整的URI模式与控制器匹配。

Just to give you an idea of what it would look like I created a small self-host sample with URIs similar to what you are trying to do: 只是为了让您了解它的外观我创建了一个类似于您尝试的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())
                   };
    }
}

You should be able to just paste this code into a console app and add a reference to the Microsoft.AspNet.WebApi.SelfHost and Tavis.WebApiRouter nugets and try it out. 您应该能够将此代码粘贴到控制台应用程序中,并添加对Microsoft.AspNet.WebApi.SelfHost和Tavis.WebApiRouter nugets的引用并进行试用。 If you are curious as to how far you can go with this kind of routing, there is a more complex sample here . 如果您对这种路由可以走多远感到好奇,那么这里有一个更复杂的示例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM