My question is very similar to this one: How do I map an OData query against a DTO to an EF entity? I have a simple setup to test the ASP.NET Web API OData V4 $filter functionality. What I would like to do is to “alias” some properties of the ProductDTO to match the properties of Product entity. The user will call the ProductsController for example with the following request:
GET products?$filter=DisplayName eq ‘test’
The Product class:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int Level { get; set; }
public Product()
{ }
}
The ProductDTO class:
public class ProductDTO
{
public int Id { get; set; }
public string DisplayName { get; set; }
public int DisplayLevel { get; set; }
public ProductDTO(Product product)
{
this.DisplayName = product.Name;
this.DisplayLevel = product.Level;
}
}
The ProductsController:
public class ProductsController : ApiController
{
public IEnumerable<ProductDTO> Get(ODataQueryOptions<Product> q)
{
IQueryable<Product> products = this._products.AsQueryable();
if (q.Filter != null) products = q.Filter.ApplyTo(this._products.AsQueryable(), new ODataQuerySettings()) as IQueryable<Product>;
return products.Select(p => new ProductDTO(p));
}
}
Of course I’m getting the following exception:
Could not find a property named 'DisplayName' on type 'TestAPI.Models.Product'
I tried to use the newly introduced aliasing feature by adding the following lines to the WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
…
IEdmModel model = GetModel();
config.MapODataServiceRoute("*", "*", model);
}
private static IEdmModel GetModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
EntitySetConfiguration<Product> products = builder.EntitySet<Product>("Product");
products.EntityType.Property(p => p.Name).Name = "DisplayName";
products.EntityType.Property(p => p.Level).Name = "DisplayLevel";
return builder.GetEdmModel();
}
}
I suppose that I'm using the aliasing feature incorrectly, because the same exception as described above is thrown. If I invoke the following request it works, but this is not what I'm trying to achieve:
GET products?$filter=Name eq ‘test’
Update:
I agree with gdoron, the Get
endpoint should look like this:
public IEnumerable<ProductDTO> Get(ODataQueryOptions<ProductDTO> q)
But this should be solvable without AutoMapper?
To use OData query options, you must enable them explicitly. You can enable them globally for the entire application, or enable them for specific controllers or specific actions. public static void Register(HttpConfiguration config) { // ... config.
The $select option specifies a subset of properties to include in the response body. For example, to get only the name and price of each product, use the following query: Console Copy. GET http://localhost/odata/Products?$select=Price,Name.
To accomplish this, you can define a data transfer object (DTO). A DTO is an object that defines how the data will be sent over the network. Let's see how that works with the Book entity. In the Models folder, add two DTO classes: C# Copy.
I found a solution without using AutoMapper.
The ProductsController now looks like this:
public class ProductsController : ApiController
{
public IEnumerable<ProductDTO> Get(ODataQueryOptions<ProductDTO> q)
{
IQueryable<Product> products = this._products.AsQueryable();
IEdmModel model = GetModel();
IEdmType type = model.FindDeclaredType("TestAPI.Models.Product");
IEdmNavigationSource source = model.FindDeclaredEntitySet("Products");
ODataQueryOptionParser parser = new ODataQueryOptionParser(model, type, source, new Dictionary<string, string> { { "$filter", q.Filter.RawValue } });
ODataQueryContext context = new ODataQueryContext(model, typeof(Product), q.Context.Path);
FilterQueryOption filter = new FilterQueryOption(q.Filter.RawValue, context, parser);
if (filter != null) products = filter.ApplyTo(products, new ODataQuerySettings()) as IQueryable<Product>;
return products.Select(p => new ProductDTO(p));
}
}
The WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
…
IEdmModel model = GetModel();
config.MapODataServiceRoute("*", "*", model);
}
private static IEdmModel GetModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
EntitySetConfiguration<Product> product = builder.EntitySet<Product>("Products");
product.EntityType.Name = "Product";
product.EntityType.Namespace = "TestAPI.Models";
product.EntityType.Property(p => p.Name).Name = "DisplayName";
product.EntityType.Property(p => p.Level).Name = "DisplayLevel";
return builder.GetEdmModel();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With