OData v4.0 - PUT, PATCH, DELETE возврат 404
У нас есть проект C# .NET Web Api со следующими пакетами Nuget, среди прочих:
- MVC 5.2.3
- Microsoft ASP.NET Web API 2.2 для OData v4.0 (версия 6.0.0)
- Microsoft.AspNet.OData.Versioning (версия 2.1.0)
В IIS глаголы PUT, PATCH и DELETE были включены для ExtensionlessUrl-Integrated-4.0 в файле applicationhost.config.
Ниже приведен WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
System.Web.Routing.RouteTable.Routes.Ignore("robots.txt");
System.Web.Routing.RouteTable.Routes.Ignore("{resource}.axd/{*pathInfo}");
// http://weblogs.asp.net/imranbaloch/handling-http-404-error-in-asp-net-web-api
System.Web.Routing.RouteTable.Routes.MapHttpRoute(
name: "Error404",
routeTemplate: "{*url}",
defaults: new { controller = "Error", action = "Handle404" }
);
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
// NOTE: Method below removed and functionality to replace it not working due to bug https://github.com/OData/WebApi/issues/812
//config.EnableCaseInsensitive(caseInsensitive: true);
// http://stackru.com/questions/30987439/elmah-axd-on-webapi-2-2-no-http-resource-was-found
config.Routes.MapHttpRoute(
"AXD", "{resource}.axd/{*pathInfo}", null, null,
new StopRoutingHandler());
// we will use attribute routing
config.MapHttpAttributeRoutes();
// set default page size and total number of rows to return from query
config.AddODataQueryFilter(new EnableQueryAttribute
{
PageSize = ConfigurationWrapper.Singleton.ODataPageSize,
MaxTop = ConfigurationWrapper.Singleton.ODataMaxTop,
MaxExpansionDepth = ConfigurationWrapper.Singleton.ODataMaxExpansionDepth
});
config.AddApiVersioning(o =>
{
o.AssumeDefaultVersionWhenUnspecified = true;
o.ReportApiVersions = true;
});
// http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-routing-conventions
// Create the default collection of built-in conventions
IList<IODataRoutingConvention> conventions = ODataRoutingConventions.CreateDefault();
// Insert the custom convention at the start of the collection; caters for ~/entityset/key/navigation/key
conventions.Insert(0, new NavigationIndexRoutingConvention());
config.MapODataServiceRoute(
routeName: "odata",
routePrefix: null,
model: EdmModelBuilder.GetEdmModel(),
pathHandler: new DefaultODataPathHandler(),
routingConventions: conventions,
batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
//config.MapVersionedODataRoutes(
//routeName: "odata",
//routePrefix: null,
//models: EdmModelBuilder.GetEdmModels(config),
//pathHandler: new DefaultODataPathHandler(),
//routingConventions: GetConventions());
// EnableDependencyInjection is required if you want to have OData routes and custom routes together in a controller
config.EnableDependencyInjection();
config.Count().Filter().OrderBy().Expand().Select().MaxTop(null); //new line
config.Formatters.Remove(config.Formatters.XmlFormatter);
// The XML formatter is not well enough supported by OData v4.0 (apparently works with OData v3.0), reverting to JSON only
config.Formatters.InsertRange(0, ODataMediaTypeFormatters.Create());
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
/* ReferenceLoopHandling.Ignore: Json.NET will ignore objects in reference loops and not serialize them. The first time an object is encountered
* it will be serialized as usual but if the object is encountered as a child object of itself the serializer will skip serializing it.
*/
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
config.Filters.AddRange(new List<IFilter>
{
new ForfrontAuthenticationAttribute(), // custom
new RateLimitAttribute(), // custom
new RequestAuditAttribute(), // custom
new SuppressResponseCodeSuccessAttribute(), // custom
new ExceptionHandlingAttribute() // custom
});
// http://weblogs.asp.net/imranbaloch/handling-http-404-error-in-asp-net-web-api
config.Services.Replace(typeof(IHttpControllerSelector), new HttpNotFoundAwareDefaultHttpControllerSelector(config));
config.Services.Replace(typeof(IHttpActionSelector), new HttpNotFoundAwareControllerActionSelector());
config.EnsureInitialized();
}
}
Действие контроллера, которое я хочу вызвать, определяется как:
[ApiVersion("1.0")]
[ODataRoutePrefix("MicrosoftDynamicsContactFieldMappings")]
[ControllerName("MicrosoftDynamicsContactFieldMappings")]
public class MicrosoftDynamicsContactFieldMappingsController : ForfrontODataController
{
// DELETE: MicrosoftDynamicsContactFieldMappings(5)
/// <summary>
/// We don't really delete records, but update, user doesn't need to know internal workings.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
[AcceptVerbs("DELETE")]
public IHttpActionResult Delete([FromODataUri] int key)
{
// this is not being called
}
}
Запрос REST API в fiddler имеет следующий формат:
DELETE http://dev2.e-shot.local/MicrosoftDynamicsContactFieldMappings(11)
HTTP/1.1
Host: dev2.e-shot.local
User-Agent: Fiddler
Authorization: Token [token value goes here]
Accept-Language: en-GB
Когда сделан запрос на УДАЛЕНИЕ (PATCH или PUT), возвращается 404. Похоже, маршрутизация OData не рассматривается.
Я надеялся, что не придется отлаживать сборки OData, любая помощь очень ценится.
Спасибо Рик
1 ответ
ОБНОВЛЕНИЕ: мне удалось заставить глаголы DELETE, PATCH и PUT работать, используя маршрутизацию атрибутов и избегая соглашения о маршрутизации OData.
[AcceptVerbs("DELETE")]
[Route("MicrosoftDynamicsContactFieldMappings({key})")]
public IHttpActionResult Delete([FromUri] int key)
{
}
[AcceptVerbs("PATCH", "MERGE")]
[Route("MicrosoftDynamicsContactFieldMappings({key})")]
public IHttpActionResult Patch([FromODataUri] int key,
Delta<MicrosoftDynamicsContactFieldMapping> item)
{
}
В моем случае
Имя таблицы было: Пользователи
Поле первичного ключа: IDUser
Мне пришлось переименовать имя столбца IDUser в UserId в модели EF и запустить обновление базы данных.
после выполнения этого кода ниже работал:
[Route("({key})")]
[HttpPatch]
public IActionResult Patch([FromODataUri]int key,
Delta<mUSER> userPatch)
{}