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

时间:2012-09-10 09:53:35

标签: c# .net asp.net-web-api

我有一个控制器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 mehods。但是,如果操作名称附加到网址,请调用PutAddContactsPutRemoveContacts,例如:

GET groups / - 调用Get方法

POST组/ - 调用Post方法

PUT groups / - 调用Put方法

删除组/ - 调用删除方法

PUT groups / addcontacts - 调用PutAddContacts方法

PUT groups / removecontacts - 调用PutRemoveContacts方法

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

2 个答案:

答案 0 :(得分: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 doesn't require HTTP,虽然这两者经常在一起讨论。 REST实际上是关于具有语义准确表示的每个资源。使用HTTP时,这意味着尊重HTTP语义的唯一URI。因此,例如,使用HTTP GET的调用永远不应该修改数据,因为这违反了HTTP的GET定义和混淆HTTP基础设施,如缓存。

POST / PUT vs MERGE / PATCH
我们都熟悉GET,POST,PUT,HEAD等作为HTTP方法。 通常,GET用于检索,POST用于添加,PUT用于修改(尽管有很多争议)。但是,您有两种类型的修改:添加项目和从集合中删除项目。 PUT还是别的什么呢?关于如何执行此操作的社区hasn't quite settled

  • 选项1:自定义媒体类型 - HTTP规范确实允许所有排序方法,它是真正限制我们熟悉的子集的浏览器。因此,您可以创建MERGE (a Roy Fielding work around) or PATCH (an oData work around)方法并定义此新媒体类型的行为 - 可能是一个用于添加,另一个用于删除。

  • 选项2:使用POST / PUT - 使用PUT添加和删除联系人。只需将ID列表视为切换(如果存在则删除,如果缺少添加)或者altatley包含足够的信息以了解要执行的操作。然后返回一个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

如果您希望您的网址显示为嵌套(例如:/api/Group/Contacts/api/Group/Contact),您可以查看this other post I wrote。恕我直言,asp.net的路由需要调整以支持嵌套更容易......但这是一个不同的问题; - )

答案 1 :(得分:4)

为了回应EBarr所说的,在Web API中进行分层路由可能会有点痛苦。我在我的服务中经常使用它,所以我为Web API构建了替换路由服务。 Nuget为here,来源位于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 nugets的引用并进行试用。如果您对使用这种路由可以走多远感到好奇,那么有一个更复杂的样本here