Ninos
Ninos

Reputation: 229

OData get resource by ID and name

I have this code sample that I'm trying to make the 3rd Get method to work, and also have $expand to work as explained below.

If I ONLY have the methods Get() and Get([FromODataUri] int key), I can call them both with these respective routes /odata/Products and /odata/Products(1)?$expand=Vendor. However, I get the below problems when I make the following changes.

  1. If I only change the parameter name key to id or anything else in method Get([FromODataUri] int key), then the method is no longer called. The route call of /odata/Products(1) goes to the Get() method which always returns all collection.

  2. If I put the parameter name back to key, but change the method to something else e.g. GetByKey([FromODataUri] int key), then the same problem above happens again.

  3. Also, if I call with /odata/Products?key=1?$expand=Vendor, the call goes to GetByKey([FromODataUri] int key), but I don't get the Vendor expanded.

  4. And adding the 3rd method below GetByName([FromODataUri] string name), I get "error 404 not found" when called with this route, /odata/Products('Product 1'). And if called with /odata/Products?name='Product 1' or /odata/Products?name='Product 1'$expand=Vendor, then the Get() method is triggered and again Vendor doesn't get expanded.

I would appreciate your input on this.

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}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        ODataModelBuilder builder = new ODataConventionModelBuilder();
        builder.EntitySet<Product    ("Products");
        config.MapODataServiceRoute("ODataRoute", "odata", builder.GetEdmModel());

    }
}

public class ProductsController : ODataController
{
    #region 

    ProductsContext db = new ProductsContext();
    private bool ProductExists(int key)
    {
        return db.Products.Any(p =     p.Id == key);
    }
    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }

    #endregion

    [HttpGet]
    [EnableQuery]
    public IQueryable<Product     Get()
    {
        return db.Products.AsQueryable();
    }

    [HttpGet]
    [EnableQuery]
    public SingleResult<Product     Get([FromODataUri] int key)
    {
        IQueryable<Product     result = db.Products.Where(p =     p.Id == key).AsQueryable();
        return SingleResult.Create(result);
    }

    [HttpGet]
    [EnableQuery]
    public SingleResult<Product     GetByName([FromODataUri] string name)
    {
        IQueryable<Product     result = db.Products.Where(p =     p.Name == name).AsQueryable();
        return SingleResult.Create(result);
    }
}

public class Product
{
    public int Id { get; set; }

    public string Name { get; set; }

    public decimal Price { get; set; }

    public string Category { get; set; }

    public Vendor Vendor { get; set; }

}

public class Vendor
{
    public int Id { get; set; }

    public string Name { get; set; }

}

public class ProductsContext : IDisposable
{
    public ICollection<Product     Products = new List<Product    ()
    {
        new Product()
        {
            Category = "A",
            Id = 1,
            Name = "Product 1",
            Price = 10,
            Vendor = new Vendor()
            {
                Id = 1,
                Name = "Vendor 1"
            }
        },

        new Product()
        {
            Category = "A",
            Id = 2,
            Name = "Product 2",
            Price = 15,
            Vendor = new Vendor()
            {
                Id = 1,
                Name = "Vendor 1"
            }
        },

        new Product()
        {
            Category = "B",
            Id = 3,
            Name = "Product 3",
            Price = 10,
            Vendor = new Vendor()
            {
                Id = 2,
                Name = "Vendor 2"
            }
        },
    };

    public void Dispose()
    {

    }
}

Upvotes: 0

Views: 8631

Answers (2)

lencharest
lencharest

Reputation: 2935

Your code is using the built-in routing conventions of the ASP.NET implementation of OData, and the issues you are having are a result of those conventions.

  1. In the URI path /odata/Products(1), the (1) part is called a key segment. By convention, key is the name of the parameter used to hold the value of the key segment. Unfortunately, this does not match the convention of naming the key properties of your models as Id. You can override this routing convention by using attribute routing, but it is not worth the effort in my opinion.

  2. The routing convention is expecting a method named GetProduct or just Get. Again, you can override the conventional behavior by using ODataRouteAttribute.

  3. The proper URI for retrieving a member of the Products entity set is /odata/Products(id). In my test environment, GET /odata/Products?key=1&$expand=Vendor is not routed to GetByKey. Rather, it is routed to the parameterless Get method, as it should be according to the routing conventions (because there is no key segment in the request URI).

  4. If the Name property of the Product entity is an alternate key, then you can take advantage of the alternate key support in ASP.NET OData. You can retrieve Products by name as follows: GET /odata/Products(Name='name'). See my answer to a previous question for a description of how to implement alternate keys in your controller.

Upvotes: 3

Sam Xu
Sam Xu

Reputation: 3380

For, #1, #2, #4 don't work because the action names or parameters don't follow up Web API OData routing convention. Conventions are a set of default rules that the developer should follow up to make sure Web API OData can route the request to correct method in the controller.

For #3, because it uses the Web API routes, that's

api/{controller}/{action}/{id}

Not use the Web API OData routing.

Hope the material here can help you understanding the routing in Web API OData. Thanks.

Upvotes: 2

Related Questions