【问题标题】:HTTP method for association bind/unbind关联绑定/解除绑定的HTTP方法
【发布时间】:2012-07-15 22:48:46
【问题描述】:

我正在开发一个基于 MVC4 RC 并使用数据库优先实体框架作为模型的 Web API。 我拥有的 2 个实体是 ItemGroup。 这两个实体之间存在多对多关系。

现在,在使用标准 HTTP 方法(GETPOSTPUTDELETE)非常轻松地实现了两者的 CRUD 操作 API 之后,我来到了我想要的地方实现项目与组的绑定和解除绑定。

我尝试了其他动词,例如LOCKUNLOCK,但没有成功(他们似乎不支持它们),并试图以某种方式操纵POSTPUT 命令,再次,没有成功。

你们中的任何好人都知道如何实现这一点吗?

非常感谢!

【问题讨论】:

    标签: entity-framework asp.net-mvc-4 asp.net-web-api


    【解决方案1】:

    您可以将多对多表示为根资源上的子集合。例如。您有 /items/1234 和 /groups/4567 - 您可以将组作为 /items/1234/groups 或 /groups/4567/items 的子集合

    任何一种方式都同样有效。我通常会使用PUT 设置关系并使用DELETE 删除它 - 有人会说这不是真正的 REST,但在我使用它的场景中它工作得很好。

    PUT /items/1234/groups/4567 - 在项目 1234 和组 4567 之间创建关系 DELETE /items/1234/groups/4567 - 删除项目 1234 和组 4567 之间的关系

    这篇文章对我帮助很大。当我最后一次调查这个时......

    How to handle many-to-many relationships in a RESTful API?

    更新:路由

    因此,对于这些更复杂的场景,我们最终只是使用了更具体的路线。试图将所有内容都塞进一条通用路线会很快变得丑陋。我们有一套单元测试,可确保相关 URL 被路由到正确的控制器和操作。

        // routes
        routes.MapHttpRoute(
            name: "items.groups",
            routeTemplate: "items/{itemId}/groups/{groupId}",
            defaults: new { controller = "ItemGroup", groupId = RouteParameter.Optional });
    

    ItemGroupController 然后具有 Get、Delete 和 Put 方法。我们像这样进行单元测试...

        // unit tests
        [Test]
        public void PutItemGroup()
        {
            RoutingResult routingResult = this.GenerateRoutingResult(HttpMethod.Put, "~/items/1234/groups/4567");
            Assert.IsNotNull(routingResult);
            Assert.AreEqual("ItemGroup", routingResult.Controller);
            Assert.AreEqual("Put", routingResult.Action);
            Assert.AreEqual("1234", routingResult.RouteData.Values["itemId"]);
            Assert.AreEqual("4567", routingResult.RouteData.Values["groupId"]);
        }
    
        [Test]
        public void GetItemGroups()
        {
            RoutingResult routingResult = this.GenerateRoutingResult(HttpMethod.Get, "~/items/1234/groups");
            Assert.IsNotNull(routingResult);
            Assert.AreEqual("ItemGroup", routingResult.Controller);
            Assert.AreEqual("GetAll", routingResult.Action);
            Assert.AreEqual("1234", routingResult.RouteData.Values["itemId"]);
        }
    
        [Test]
        public void GetItemGroup()
        {
            RoutingResult routingResult = this.GenerateRoutingResult(HttpMethod.Get, "~/items/1234/groups/4567");
            Assert.IsNotNull(routingResult);
            Assert.AreEqual("ItemGroup", routingResult.Controller);
            Assert.AreEqual("Get", routingResult.Action);
            Assert.AreEqual("1234", routingResult.RouteData.Values["itemId"]);
            Assert.AreEqual("4567", routingResult.RouteData.Values["groupId"]);
        }
    
        [Test]
        public void DeleteItemGroup()
        {
            RoutingResult routingResult = this.GenerateRoutingResult(HttpMethod.Delete, "~/items/1234/groups/4567");
            Assert.IsNotNull(routingResult);
            Assert.AreEqual("ItemGroup", routingResult.Controller);
            Assert.AreEqual("Delete", routingResult.Action);
            Assert.AreEqual("1234", routingResult.RouteData.Values["itemId"]);
            Assert.AreEqual("4567", routingResult.RouteData.Values["groupId"]);
        }
    
        private RoutingResult GenerateRoutingResult(HttpMethod method, string relativeUrl)
        {
            HttpConfiguration httpConfiguration = new HttpConfiguration(this.HttpRoutes);
            HttpRequestMessage request = new HttpRequestMessage(method, string.Format("http://test.local/{0}", relativeUrl.Replace("~/", string.Empty)));
            IHttpRouteData routeData = this.HttpRoutes.GetRouteData(request);
    
            Assert.IsNotNull(routeData, "Could not locate route for {0}", relativeUrl);
    
            this.RemoveOptionalRoutingParameters(routeData.Values);
    
            request.Properties.Add(HttpPropertyKeys.HttpRouteDataKey, routeData);
            request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, httpConfiguration);
    
            IHttpControllerSelector controllerSelector = new DefaultHttpControllerSelector(httpConfiguration);
            HttpControllerContext controllerContext = new HttpControllerContext(httpConfiguration, routeData, request)
                {
                    ControllerDescriptor = controllerSelector.SelectController(request)
                };
    
            HttpActionDescriptor actionDescriptor = controllerContext.ControllerDescriptor.HttpActionSelector.SelectAction(controllerContext);
            if (actionDescriptor == null)
            {
                return null;
            }
    
            return new RoutingResult
                {
                    Action = actionDescriptor.ActionName,
                    Controller = actionDescriptor.ControllerDescriptor.ControllerName,
                    RouteData = routeData
                };
        }
    
        private void RemoveOptionalRoutingParameters(IDictionary<string, object> routeValueDictionary)
        {
            int count = routeValueDictionary.Count;
            int index1 = 0;
            string[] strArray = new string[count];
            foreach (KeyValuePair<string, object> keyValuePair in routeValueDictionary)
            {
                if (keyValuePair.Value == RouteParameter.Optional)
                {
                    strArray[index1] = keyValuePair.Key;
                    ++index1;
                }
            }
    
            for (int index2 = 0; index2 < index1; ++index2)
            {
                string key = strArray[index2];
                routeValueDictionary.Remove(key);
            }
        }
    
        private class RoutingResult
        {
            public string Controller { get; set; }
    
            public string Action { get; set; }
    
            public IHttpRouteData RouteData { get; set; }
        }
    

    干杯, 院长

    【讨论】:

    • 谢谢院长!我现在遇到路由规则问题。我使用了 /api/{controller}/{id}/{action}/{id2},其中 id、action 和 id2 是可选的,目的是 /api/groups/1/items/2 上的 PUT 动词将路由到我的绑定方法,并且在同一方法上的 DELETE 将路由到取消绑定方法。似乎无法让这个工作。有什么线索吗?
    • 哈哈,最近玩得很开心;为每个案例制定一个通用规则可能会痛苦!我们最终将我们的规则分解为许多处理此类特定情况的规则。在某种程度上,WCF Web Api(当前框架的前身)中基于属性的路由使这种流程变得更好。我将编辑响应以添加一些我们最终使用的示例路由...
    • 非常感谢,院长!这真的很有帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多