问题
I have an ASP.Net
WebApi2
project hosting odata both ApiController
and ODataController
.
And I want to add a custom action in an ODataController
.
I saw this seems to be achievable by either adding [HttpPost]
attribute on the desired action, or by configuring the ODataConventionModelBuilder with a specific FunctionConfiguration when using the MapODataServiceRoute.
To distinguish between odata routes and webapi routes we use the following scheme :
- odata : http://localhost:9292/myProject/odata
- webapi : http://localhost:9292/myProject/api
I tried both these solution without success which all led to get an HTTP 404 result.
My custom action is defined as following:
public class SomeModelsController : ODataController
{
//...
[EnableQuery]
public IHttpActionResult Get()
{
//...
return Ok(data);
}
public IHttpActionResult MyCustomAction(int parameterA, int parameterB)
{
//...
return Json(data);
}
//...
}
So as you guessed it, the Get call on the controller perfectly work with odata. However the MyCustomAction is a bit more difficult to setup properly.
Here is what I have tried :
Setting an [HttpPost] attribute on MyCustomAction
[HttpPost] public IHttpActionResult MyCustomAction(int parameterA, int parameterB) { //... return Json(data); }
I also tried decorating MyCustomAction with the
[EnableQuery]
attribute.
Also, I tried adding the[AcceptVerbs("GET", "POST")]
attribute on the method without changes.Configuring the ODataConventionModelBuilder
private static IEdmModel GetEdmModel() { var builder = new ODataConventionModelBuilder { Namespace = "MyApp", ContainerName = "DefaultContainer" }; // List of entities exposed and their controller name // ... FunctionConfiguration function = builder.Function("MyCustomAction ").ReturnsFromEntitySet<MyModel>("SomeModels"); function.Parameter<int>("parameterA"); function.Parameter<int>("parameterB"); function.Returns<MyModel>(); return builder.GetEdmModel(); }
Also tried decoration of MyCustomAction with
[EnableQuery]
,HttpPost
and[AcceptVerbs("GET", "POST")]
attributes.
I still get HTTP 404 result.
My query url is as follow:http://localhost:9292/myProject/odata/SomeModels/MyCustomAction?parameterA=123¶meterB=123
I also tried to POST parameters on
http://localhost:9292/myProject/odata/SomeModels/MyCustomAction
with the same result. Actually with or without parameters I get HTTP 404 status.
回答1:
I've created a working example from scratch with Visual Studio 2017. If you want more info you can read this tutorial:
https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/odata-actions-and-functions
Create a new ASP.Net Web Application (no .Net Core)
Choose WebApi Template
Install from NuGet the package Microsoft.AspNet.OData (I have used v. 6.0.0)
Create a simple model class into Models folder
TestModel.cs
namespace DemoOdataFunction.Models
{
public class TestModel
{
public int Id { get; set; }
public int MyProperty { get; set; }
public string MyString { get; set; }
}
}
- Configure WebApiConfig
WebApiConfig.cs
using DemoOdataFunction.Models;
using System.Web.Http;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
namespace DemoOdataFunction
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "MyNamespace";
builder.EntitySet<TestModel>("TestModels");
ActionConfiguration myAction = builder.EntityType<TestModel>().Action("MyAction");
myAction.Parameter<string>("stringPar");
FunctionConfiguration myFunction = builder.EntityType<TestModel>().Collection.Function("MyFunction");
myFunction.Parameter<int>("parA");
myFunction.Parameter<int>("parB");
myFunction.ReturnsFromEntitySet<TestModel>("TestModels");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "odata",
model: builder.GetEdmModel()
);
}
}
}
- Create the controller TestModelsController into Controllers folder
TestModelsController.cs
using DemoOdataFunction.Models;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.OData;
using System.Web.OData.Query;
namespace DemoOdataFunction.Controllers
{
public class TestModelsController : ODataController
{
IQueryable<TestModel> testModelList = new List<TestModel>()
{
new TestModel{
MyProperty = 1,
MyString = "Hello"
}
}.AsQueryable();
[EnableQuery]
public IQueryable<TestModel> Get()
{
return testModelList;
}
[EnableQuery]
public SingleResult<TestModel> Get([FromODataUri] int key)
{
IQueryable<TestModel> result = testModelList.Where(t => t.MyProperty == 1);
return SingleResult.Create(result);
}
[HttpPost]
public IHttpActionResult MyAction([FromODataUri] int key, ODataActionParameters parameters)
{
string stringPar = parameters["stringPar"] as string;
return Ok();
}
[HttpGet]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All, MaxExpansionDepth = 2)]
public IHttpActionResult MyFunction(int parA, int parB)
{
return Ok(testModelList);
}
}
}
- Edit Web.config changing the handlers section in system.webServer
web.config
<system.webServer>
<handlers>
<clear/>
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="/*"
verb="*" type="System.Web.Handlers.TransferRequestHandler"
preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
[...]
</system.webServer>
That's all.
This is the request for MyAction:
POST
http://localhost:xxxx/odata/TestModels(1)/MyNamespace.MyAction
{
"stringPar":"hello"
}
This is the request for MyFunction:
GET
http://localhost:xxxx/odata/TestModels/MyNamespace.MyFunction(parA=1,parB=2)
回答2:
I am using HTTP POST with route on the controller functions like below:
[HttpPost]
[Route("{application}/{envName}/date/{offset}")]
[ResponseType(typeof(DateInfo))]
public async Task<IHttpActionResult> SetDateOffsetForEnvironmentName(string application, string envName, string offset)
{
}
can you try setting the route on the function and then call the post method on it like this:
POST /status/environments/ATOOnline/PTH/date/0
Also try and capture a request through Fiddler and see what is being passed.
来源:https://stackoverflow.com/questions/45275892/how-to-prevent-http-404-for-a-custom-action-in-an-odata-controller