Search code examples
c#asp.netasp.net-web-apiodataasp.net-web-api2

OData route exception


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")]

Solution

  • 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);
    }