Reputation: 229
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.
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.
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.
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.
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
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.
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.
The routing convention is expecting a method named GetProduct
or just Get
. Again, you can override the conventional behavior by using ODataRouteAttribute
.
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).
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
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