WebAPI OData v3 Composite Key Delete - c#

I'm having some routing problems with OData v3, composite keys and deleting items. I've set up my controller and entities as below (stubbed the methods here, they're complete in my implementation) and can run basic queries on the data (filtering etc. for GET)
When I call the url http://localhost:62658/OData/ProductStockLimit(StockLimitGroupId=1,ProductRegexMatch=Test) with DELETE however I keep getting 404's with the message "No HTTP resource was found that matches the request URI"
I assume the routing isn't picking up this method but I have no idea why as all of my other OData routes are working correctly with deletes, the only difference I can see is that this is a composite key one.
Anyone else had this problem?
public class ProductStockLimit
{
[Key, Column(Order = 2)]
public string ProductRegexMatch { get; set; }
[Key, ForeignKey("StockLimitGroup"), Column(Order = 1)]
public int StockLimitGroupId { get; set; }
public virtual StockLimitGroup StockLimitGroup { get; set; }
[Column(Order = 3)]
public double Quantity { get; set; }
}
namespace Website.Areas.OData.Controllers
{
public class ProductStockLimitController : ODataController
{
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<ProductStockLimit> Get()
{
}
public IHttpActionResult Post(ProductStockLimit item)
{
}
public HttpResponseMessage Delete( [FromODataUri]int StockLimitGroupId,[FromODataUri] string ProductRegexMatch)
{
}
}
}

From what I've looked at, it seems the OData v3 implementation doesn't handle composite keys properly. This link has a routing convention class which when applied handles them correctly.
Quick word of caution don't use the parameter name "key" for your action method as this will cause it to try and add another "key" element in the dictionary causing an exception.

Related

Azure Functions HttpTrigger all properties null on int overflow

I use some Azure Functions as WebApi. Now I have the following DTO to create a vehicle:
public class CreateVehicleDto
{
public string LicensePlate { get; set; }
public int? Mileage { get; set; }
public string Manufacturer { get; set; }
}
My method header looks like this:
[FunctionName("CreateVehicle")]
public async Task<ActionResult<ReadVehicleDto>> CreateVehicle([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "vehicles")] CreateVehicleDto createVehicleDto){}
The problem is that when my client sends a mileage higher than int.MaxValue all properties of the DTO are null and the method runs without throwing any exception whatsoever.
Is there a way to handle that? In case of a too high mileage I want to return a BadRequestResult.
I've also tried to use the System.ComponentModel.DataAnnotations to set a maximum like this [Range(0, int.MaxValue)] and validate it with the System.ComponentModel.DataAnnotations.Validator. But when the object gets validated it's already too late because all properties of the DTO get passed into the method with a value of null.

EF Core - Don't include navigation properties in query

I'm having a really tough time finding guidance on how to AVOID including navigation properties in EF Core in a database-first approach. Using unmodified scaffolded web API controllers in Visual Studio, I have an entity that looks like this (simplified for example):
public partial class ProjectPhase
{
public ProjectPhase()
{
Projects = new HashSet<Project>();
}
public int PhaseId { get; set; }
public string PhaseName { get; set; }
public virtual ICollection<Project> Projects { get; set; }
}
My request is the default scaffolded HTTP GET request:
// GET: api/Phases
[HttpGet]
public async Task<ActionResult<IEnumerable<ProjectPhase>>> GetPhases ()
{
return await _context.ProjectPhases.ToListAsync();
}
The return value looks like this:
...{
"phaseId": 1,
"phaseName": "Pilot",
"projects": []
},...
I want this request to NOT include projects in the returned object. How do I do this?
if you want to read-only an entity use AsNoTracking.
The AsNoTracking() extension method returns a new query and the returned entities will not be cached by the context (DbContext or Object Context).
[HttpGet]
public async Task<ActionResult<IEnumerable<ProjectPhase>>> GetPhases()
{
return await _context.ProjectPhases.AsNoTracking().ToListAsync();
}
And another way to make the final object better by using a DTO class that matches the entity class and use automapper maybe for mapping.
For my purposes, using something like Automapper was a bit overkill. I just wanted to exclude a few navigation properties, so I just used the JsonIgnore attribute like so.
public int PhaseId { get; set; }
public string PhaseName { get; set; }
[System.Text.Json.Serialization.JsonIgnore]
public virtual ICollection<Project> Projects { get; set; }
Hope this helps someone else!

FromRoute does not set Url parameters in separate DTO class

When creating a new Web API project you might come up with controller endpoints expecting url params, body values and maybe queries, especially for PATCH routes.
So let's assume you would like to update your shop basket by changing the amount of a product. The endpoint expects the order id and product id from the url and the amount from the body.
[HttpPatch("{orderId}/products/{productId}")]
public async Task<IActionResult> Update(Dto dto)
{
return Ok(dto);
}
The matching Dto should hold the values from the whole request
public class Dto
{
[FromRoute]
public int OrderId { get; set; }
[FromRoute]
public int ProductId { get; set; }
[FromBody]
public int Amount { get; set; }
}
When calling the API via PATCH https://localhost:5001/orders/123/products/456 Amount is correct but both ID parameters are 0. I think they won't be set and will have their default value.
Am I missing something?
I had the same problem. This helped me.
In the controller, you need to specify [FromRoute]:
[HttpPatch("{orderId}/products/{productId}")]
public async Task<IActionResult> Update([FromRoute] Dto dto)
{
return Ok(dto);
}
In the DTO, on the fields that should be obtained from the body, you need to specify [FromBody]:
public class Dto
{
public int OrderId { get; set; }
public int ProductId { get; set; }
[FromBody]
public int Amount { get; set; }
}
Since this is a web api project, the [ApiController] attribute applies inference rules for the default data sources of action parameters. The Dto is a complex type, so it will use [FromBody] as default.
When [FromBody] is applied to a complex type parameter, any binding source attributes applied to its properties are ignored. This is why you can't get the OrderId and ProductId, the [FromRoute] attribute on them are ignored.
You can find it from the official documentation:
https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1#binding-source-parameter-inference
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-3.1#frombody-attribute
One solution is that you can receive them separately:
[HttpPatch("{orderId}/products/{productId}")]
public async Task<IActionResult> Update(Dto dto, int orderid, int productId)
{
return Ok(dto);
}

net::ERR_CONNECTION_RESET 200 (OK) when doing GET method

I changed my Apartments Model Class by adding a BuyerID which is a foreign key to another Buyer Class like this:
public class Apartment
{
[Key]
public int ID { get; set; }
public string Title { get; set; }
public int NbofRooms { get; set; }
public int Price { get; set; }
public string Address { get; set; }
public int BuyerId { get; set; }
}
Also I have my Buyers Model Class as the following:
public class Buyer
{
[Key]
public int ID { get; set; }
public string FullName { get; set; }
public int Credit { get; set; }
public ICollection<Apartment> apartments { get; set; }
}
So it also contains a collection of Apartments.
and because of this maybe my Get method isn't working anymore and is returning the following error: GET http://localhost:54632/api/Apartments net::ERR_CONNECTION_RESET 200 (OK)
The only GET Method not working is this one:
// GET: api/Apartments
[HttpGet]
public IEnumerable<Apartment> GetApartments()
{
return _context.Apartments;
}
Otherwise the others such as this:
// GET: api/Apartments/5
[HttpGet("{id}")]
public async Task<IActionResult> GetApartment([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var apartment = await _context.Apartments.SingleOrDefaultAsync(m => m.ID == id);
if (apartment == null)
{
return NotFound();
}
return Ok(apartment);
}
is working fine.Also if I try the link on chrome it returns the apartments but if I try it on Postman or Angular App it returns the error. What could be the cause of this error?
Thank you.
I had the same problem, and it was due to having created a self-referencing loop in the data I was trying to serialize. Looking at the recent change you had made it looks like you also created an object tree with a self referencing loop by referencing back to a Buyer from Apartments.
Json.Net gets upset by this and gives up. I would expect an exception to be thrown as in this question, but I didn't get one, I had the same symptoms as you describe.
If you are having the same root problem, it is solved by setting JSON.Net to detect and ignore self referencing loops during startup configuration as explained here or here for asp.net core.
Asp.Net:
HttpConfiguration config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter
.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
Asp.net Core:
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
open chrome then open DevTools by pressing F12 and navigate to network tab. Find your API request and select copy > copy as cURL
now you can compare curl request and postman request in order to see difference. The difference will give you the problem.

Set required fields based on HTTP Verb

Is it possible to set an optional [Required] attribute, applicable on PATCH or PUT. I have the following code but no matter what the controller call it will always be required.
public class Car
{
[DataMember(Order = 0)]
public string CarId { get; set; }
[DataMember(Order = 1)]
[Required]
public string IsIncluded { get; set; }
}
Controller;
[HttpPatch]
public HttpResponseMessage PatchCar(Car car)
{
// check if submitted body is valid
if (!ModelState.IsValid)
{
// Something is bad!
}
}
What I want is something like the following;
public class Car
{
[DataMember(Order = 0)]
public string CarId { get; set; }
[DataMember(Order = 1)]
[Required(Patch = True, Put = False]
public string IsIncluded { get; set; }
}
Then my ModelState will take the very into account.
I thought about creating separate derived classes for each action (verb), but the code quickly becomes incredibly verbose.
This is one of the drawbacks of using data annotations for validation unfortunately they cannot be conditionally added.
There are a number of options to you...
Create separate models (or view models) for each verb.
Look into something like this.. http://andrewtwest.com/2011/01/10/conditional-validation-with-data-annotations-in-asp-net-mvc/ which extends required to be IfRequired and adds conditional validation to data annotations. (You would need to roll your own I should think and it may get clumsy!)
Try something like FluentValidation.
http://fluentvalidation.codeplex.com/ (this could be a good option depending on your application requirements).
Hope this helps!

Categories