2012-09-10 82 views
5

我有一个控制器Groups使用以下操作:多PUT方法

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,放,删除mehods。但拨打PutAddContactsPutRemoveContacts如果动作名称添加到URL,例如:

GET集团/ - 调用get方法

岗位群/ - 调用Post方法

PUT组/ - 电话把方法

删除组/ - 调用删除方法

PUT组/ addcontacts - 调用PutAddContacts方法

PUT组/ removecontacts - 调用PutRemoveContacts方法

是否有可能建立路由做到这一点还是需要下井RPC路线,如果我想在我的网址的使用操作名称路由?

回答

11

现在你有什么
要利用你的方法,如上所述,你需要去RPC。那是因为你的例子已经完全沉浸在RPC风格的做事中。默认的WebAPI路线鼓励REST风格的设置,但是如果您对路线做了小幅改动,所有事情都会开始工作。例如,你可以改变你的默认路由就像一个典型的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的列表视为切换(如果存在删除,如果缺少添加)或者包含足够的信息来知道该怎么做。然后返回一个HTTP 303,指示客户端它有一个陈旧的状态并刷新。

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

从RESTful的角度来看,真正重要的是您的应用程序在所有方法中的行为都是一致的。所以如果MERGE意味着添加,它应该总是意味着添加。如果你期望一套完整的ID传递给PUT,那么总是传递一个完整的集合。

控制器设计
如果是我,我会打破你的控制器到多个控制器。一个控制器处理组另一个交易联系人(作为一个组),第三个处理组中的一个联系人。喜欢的东西...

//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),你可以看看this other post I wrote。恕我直言,asp.net的路由需要一个调整,以支持嵌套更容易一点...但这是一个不同的问题;-)

+0

Thx的真棒回答!我同意你的说法,即REST更多地是关于语义的,这就是为什么我相信我的例子仍然是REST。而有趣的是,我实际上早先遇到了你的其他帖子,并使用子文件夹实现了类似的操作,并为每个子文件夹设置了一个路径,如果没有人回复标出我的解决方案,我将自己回答这个帖子,但我猜我现在不需要打扰了:D – jcvandan

+0

很高兴为您服务。我恰好正在开发一个在webapi测试版期间开始的大型API项目。我花了一段时间才弄清楚了MVC之间微妙的设计变化,我可以在控制器中使用一堆方法,而使用API​​控制器来扩展控制器泛化。 – EBarr

+0

@EBarr我会很好奇你对我在这个问题的答案中演示的分层路由的反馈。 –

3

要回应什么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工具的引用并试用。如果您对这种路由能走多远感到好奇,则可以使用更复杂的示例here

+0

有趣。我现在正处于紧张的最后期限,但我回到了我定期写的很长的答案。我打算编写自己的路由处理程序。我真正想要的是尊重我的命名空间(所以'UI.Parent.Child.GrandChild'中的类将映射到'/ api/Parent/Child/Grandchild'.也许你为我节省了时间:-)。我会圈回来评估,但这可能需要我几个星期。 – EBarr

+0

@Ebarr编写一些反映名称空间并查找ApiControllers并自动构建层次结构的代码应该相当简单。只是需要某种约定的参数。我也有我的名字空间匹配我的URI,所以这将对我有用。 –