OData route exception

2019-07-09 04:30发布

问题:

I am new to this so i will start with the code and after that i will explain. The problem is this

[HttpGet, ODataRoute("({key})")]
public SingleResult<Employee> GetByKey([FromODataUri] string key)
{
var result = EmployeesHolder.Employees.Where(id => id.Name == key).AsQueryable();
return SingleResult<Employee>.Create<Employee>(result);
}


[HttpGet, ODataRoute("({key})")]
public SingleResult<Employee> Get([FromODataUri] int key)
{
var result = EmployeesHolder.Employees.Where(id => id.Id == key).AsQueryable();
return SingleResult<Employee>.Create<Employee>(result);
}

I have those 2 actions but for one i want to search by a string and for the other by number (although this is not the problem). If i leave it this way it will work for the (int) case but for the string "....odata/Employees('someName')" i will get a : HTTP 404 (and it's normal) but if i try to be more specific with the method which takes a string

Code in webApiConfig.

ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Employee>("Employees");
builder.Function("GetByKeyFromConfig").Returns<SingleResult<Employee>>().Parameter<string>("Key");

Code in Controller

[ODataRoutePrefix("Employees")]
public class FooController : ODataController 
{

     [HttpGet, ODataRoute("GetByKeyFromConfig(Key={key})")]
            public SingleResult<Employee> GetByKey([FromODataUri] string key)
            { ... } 
}

i get an expcetion

{"The complex type 'System.Web.Http.SingleResult`1[[OData_Path.Employee, OData_Path, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' refers to the entity type 'OData_Path.Employee' through the property 'Queryable'."}

If i change the return type in for the method in WebApiConfig

builder.Function("GetByKeyFromConfig").Returns<Employee>().Parameter<string>("Key");

I get this which i have no idea why.

{"The path template 'Employees/GetByKeyFromConfig(Key={key})' on the action 'GetByKey' in controller 'Foo' is not a valid OData path template. The request URI is not valid. Since the segment 'Employees' refers to a collection, this must be the last segment in the request URI or it must be followed by an function or action that can be bound to it otherwise all intermediate segments must refer to a single resource."}

I have searched and tried to get explanation , but each time i found an answer it does not work. i am struggling for days.


After the updates taken from the 2 answers

still have the Invalid OData path template exception

WebApiConfig Code

 ODataModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<Employee>("Employees").EntityType.HasKey(p => p.Name);

            var employeeType = builder.EntityType<Employee>();
                employeeType.Collection.Function("GetByKey").Returns<Employee>().Parameter<int>("Key");



            config.EnableUnqualifiedNameCall(unqualifiedNameCall: true);
            config.MapODataServiceRoute(
                   routeName: "ODataRoute",
                   routePrefix: null,
                   model: builder.GetEdmModel());

Controller Code

  [EnableQuery, HttpGet, ODataRoute("Employees/GetByKey(Key={Key})")]
        public SingleResult<Employee> GetByKey([FromODataUri] int Key)
        {
            var single = Employees.Where(n => n.Id == Key).AsQueryable();
            return SingleResult<Employee>.Create<Employee>(single);
        }

I've also tried using a specific Namespace

builder.Namespace = "NamespaceX";
[EnableQuery, HttpGet, ODataRoute("Employees/NamespaceX.GetByKey(Key={Key})")]

And

[EnableQuery, HttpGet, ODataRoute("Employees/NamespaceX.GetByKey")]

回答1:

While you can solve your problem with OData functions, a cleaner solution would be to use alternate keys. As Fan indicated, Web API OData provides an implementation of alternate keys that will allow you to request Employees by name or number with a more straightforward syntax:

GET /Employees(123) 
GET /Employees(Name='Fred')

You will need to add the following code to your OData configuration.

using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;

// config is an instance of HttpConfiguration
config.EnableAlternateKeys(true);

// builder is an instance of ODataConventionModelBuilder
var edmModel = builder.GetEdmModel() as EdmModel;
var employeeType = edmModel.FindDeclaredType(typeof(Employee).FullName) as IEdmEntityType;
var employeeNameProp = employeeType.FindProperty("Name");

edmModel.AddAlternateKeyAnnotation(employeeType, new Dictionary<string, IEdmProperty> { { "Name", employeeNameProp } });

Make sure you add the alternate key annotations before you pass the model to config.MapODataServiceRoute.

In your controller, add a method to retrieve Employees by name (very similar to the GetByKey method in your question).

[HttpGet]
[ODataRoute("Employees(Name={name})")]
public IHttpActionResult GetEmployeeByName([FromODataUri] string name)
{
    var result = EmployeesHolder.Employees.FirstOrDefault(e => e.Name == name);

    if (result == null)
    {
        return this.NotFound();
    }

    return this.Ok(result);
}


回答2:

Can this document about function support for OData/Webapi help you? http://odata.github.io/WebApi/#04-06-function-parameter-support there is some problem in your second approach.

  1. you are call with Employees/GetByKeyFromConfig(Key={key}) so you should declare the function like:

    builder.EntityType<Employee>().Collection.Function("GetByKeyFromConfig") 
    
  2. you should call with namespace, like Employees/yournamespace.GetByKeyFromConfig

The first scenario can you use this feature? http://odata.github.io/WebApi/#04-17-Alternate-Key

Hope this can help,Thanks.

Update: Function declaration is right now, the error message shown because your ODataRoute is wrong, remove your ODataRoute, that function can be called as Employees/Default.GetByKey(1), WebAPI/OData can route to this function by default.

Or add the namespace to ODataRoute, it's Default by default, change builder.Namespace is not right, you have to change the function's namespace:

var func = employeeType.Collection.Function("GetByKey").Returns<Employee>().Parameter<int>("Key");
func.Namespace = "NamespaceX";

Then ODataRoute like [EnableQuery, HttpGet, ODataRoute("Employees/NamespaceX.GetByKey(Key={Key})")] should work.



回答3:

There are two issues with the code you paste, 1. You try to bind a function to Employees collection, the model build is incorrect. It should be something like var employeeType = builder.EntityType(); employeeType .Collection.Function("GetByKeyFromConfig").Returns().Parameter("Key"); You can refer to examples in link https://github.com/OData/ODataSamples/tree/master/WebApi/v4/ODataFunctionSample for different ways to bind functions.

  1. In the ODataRoute, we either need to specify the function with namespace or enable unqualified function call via this config in register method "config.EnableUnqualifiedNameCall(unqualifiedNameCall: true);". Refer to link http://odata.github.io/WebApi/#03-03-attrribute-routing and search unqualified.

Let me know if this does not resolve your issue.