How to prevent HTTP 404 for a custom action in an odata controller?

烈酒焚心 提交于 2019-12-11 06:05:12

问题


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 :

  1. 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.

  2. 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&parameterB=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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!