I have a Bussiness Entity that is recognized by 2 Keys, for example:
class UserItem {
[Key]
[Column(Order = 1)]
public string UserId {get;set;}
[Key]
[Column(Order = 2)]
public string ItemName {get; set;}
public int Count {get; set;}
}
Now using ASP.NET Web Api, how can I make an HTTP GET or HTTP DELETE to accept multiple parameters? Currently, the default generated template only accept 1 key:
class ItemController : ApiController {
.....
//api/item/[key]
[HttpGet]
[ResponseType(typeof(UserItem))]
public async Task<IHttpActionResult> GetUserItem(string id)
{
UserItem item = await db.useritems.FindAsync(id);
......
}
......
}
db is my datacontext, i'm using EntityFramework 6 with ASP.NET Web Api 2
Map your route like below will allow you to pass two parameter, you can add more that two parameter this way
[HttpGet]
[Route("api/item/{id1}/{id2}")]
[ResponseType(typeof(UserItem))]
public async Task<IHttpActionResult> GetUserItem(string id1, string id2)
{
UserItem item = await db.useritems.FindAsync(id1);
......
}
I've been able to send multiple parameters from the default WebApi setup.
Just do
Public async Task<IHttpActionResult> GetUserItem(string UserId, string ItemName)
{
UserItem item = await db.useritems.FirstOrDefault(c=>.UserId==UserId && c.ItemName==ItemName);
}
Where are you running into issues?
Related
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);
}
I am getting a self Referencing issue with EF and I'm trying to over come it but still allow the Service to be able to perform a GET passing in {[FromODataUri] int key} a key and return an IQuerable Obj to get the Expanded tables if necessary. Below is a slimmed down version of the tables. Any suggestions on how to handle the situation.
public class People
{
public int PeopleId {get;set;}
public string PeopleName {get;set;}
public int? ProductId{get;set;}
public virtual Product Product{get;set;}
}
The ProductId is a PK in Product but its not required. As per the convention it doesn't have to be Decorated with the PK DataAnnotation overide.
public class Product
{
public Product()
{
PeopleCollection = HashSet<People>();
}
public int ProductId {get;set;}
public string ProductName {get;set;}
public virtual ICollection<People> Peoples{get;set;}
}
In this case I recommend using DTO's or using anonymous objects, for example:
public IHttpActionResult Get() {
var response = db.YourTable.Select(x=>new{
x.PeopleId,
x.PeopleName,
x.ProductId,
Product= new {
x.ProductId,
x.ProductName
}
}).toList();
return Ok(response);
}
Thats how I would do it with anonymous objects, If you want to use DTO's you just need to map them, hope this is what you are looking for.
For just a specific id:
public IHttpActionResult Get(int id) {
var response = db.YourDb.Select(x=>new{
x.PeopleId,
x.PeopleName,
x.ProductId,
Product= new {
x.ProductId,
x.ProductName
}
})Where(x=>x.PeopleId == id).toList();
return Ok(response);
}
Note, this method is using query string parameters
I figured this out after some time. The self referencing issue will come up if you are Inheriting from APIController but if you switch to inherit from ODataController everything works.
So
public class MyController : ApiController
{
..... Bunch of code here
}
To
public class MyController : ODataController
{
..... Bunch of code here
}
I am trying to use model binding from query parameters to an object for searching.
My search object is
[DataContract]
public class Criteria
{
[DataMember(Name = "first_name")]
public string FirstName { get; set; }
}
My controller has the following action
[Route("users")]
public class UserController : Controller
{
[HttpGet("search")]
public IActionResult Search([FromQuery] Criteria criteria)
{
...
}
}
When I call the endpoint as follows .../users/search?first_name=dave the criteria property on the controller action is null.
However, I can call the endpoint not as snake case .../users/search?firstName=dave and the criteria property contains the property value. In this case Model Binding has worked but not when I use snake_case.
How can I use snake_case with Model Binding?
You need to add [FromQuery] attribute to the model properties individually
public class Criteria
{
[FromQuery(Name = "first_name")]
public string FirstName { get; set; }
}
Solution for .net core 2.1, 2.2, 3.0 and 3.1
Or without attributes you can do something like this which is cleaner I think (of course if the model properties are same as query parameters).
Meanwhile I use it in .net core 2.1, 2.2 and 3.0 preview & 3.1.
public async Task<IActionResult> Get([FromQuery]ReportQueryModel queryModel)
{
}
For anyone that got here from search engine like me:
To make it work on asp.net core 3.1+
public async Task<IActionResult> Get([FromQuery] RequestDto request);
public class RequestDto
{
[FromQuery(Name = "otherName")]
public string Name { get; set; }
}
Will read json property otherName into RequestDto.Name so basically you have to use FromQuery in 2 places.
Above answers are IMHO too complicated for such a simple thing already provided in asp.net framework.
In my case, I had an issue where my parameter name was option and in my class I also had the property called option so it was collapsing.
public class Content
{
public string Option { get; set; }
public int Page { get; set; }
}
public async Task<IActionResult> SendContent([FromQuery] Content option)
changed the parameter to something else:
public async Task<IActionResult> SendContent([FromQuery] Content contentOptions)
According to #Carl Thomas answer, here is the easier and the strongly typed way to have snake case FromQuery name:
CustomFromQuery
public class CustomFromQueryAttribute : FromQueryAttribute
{
public CustomFromQuery(string name)
{
Name = name.ToSnakeCase();
}
}
StringExtensions
public static class ObjectExtensions
{
public static string ToSnakeCase(this string o) => Regex.Replace(o, #"(\w)([A-Z])", "$1_$2").ToLower();
}
Usage
public class Criteria
{
[CustomFromQuery(nameof(FirstName))]
public string FirstName { get; set; }
}
If the
public async Task<IActionResult> Get([FromQuery] RequestDto request);
not work for anyone, you can try [FromRoute]
public async Task<IActionResult> Get([FromRoute] RequestDto request);.
In your dto you must keep the [FromQuery]
public class RequestDto
{
[FromQuery(Name = "otherName")]
public string Name { get; set; }
}
I have an Employee class, taken from a database. In that Employee class, I want to add a Manager object, which is also an Employee, based on the managerCode that is in the Employee database. See below for the class.
The managerCode is not defined as a key, . I don’t need recursion, i.e. I don’t need the Manager’s manager, etc. Just one level, the Employee and his manager.
Using .NET 4.5, c#, OData v4
I am using OData to send back the Employee, but the Manager part isn’t added in the response, even if it’s there in the object I try to send.
What am I missing? Something in the WebApiConfig?
Thanks
Employee class, first 4 fields are directly taken from database.
Class Employee
{
[Key]
public int id { get; set; }
public string employeeCode { get; set; }
public string employeeName { get; set; }
public string managerCode { get; set; }
[NotMapped]
public Employee Manager { get; set; }
}
Controller class. GetEmployeeById(id) will get Employee(s) with their Manager.
[HttpGet]
[EnableQuery]
[ODataRoute("employeeById(id={id})")]
public IHttpActionResult employeeById([FromODataUri]int id)
{
var sets = dbContext.GetEmployeeById(id);
if (!sets.Any())
return NotFound();
return this.CreateOKHttpActionResult(sets);
}
WebApiConfig
public static void Register(HttpConfiguration config)
{
config.MapODataServiceRoute("ODataRoute", "odata",GetEdmModel(),
new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
config.EnsureInitialized();
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.Namespace = "employee_odata";
builder.ContainerName = "employee_odataContainer";
builder.EntitySet<Employee>("Employee");
builder.EnableLowerCamelCase();
var unboundGetEmployee = builder.Function("employeeById");
unboundGetEmployee.Returns<Employee>();
unboundGetEmployee.Parameter<int>("id");
unboundGetEmployee.Namespace = "employee_odata.Functions";
return builder.GetEdmModel();
}
SOLUTION
Remove unboundGetEmployee from WebApiConfig (not needed).
Make Manager item in Employee class virtual, without the [NotMapped]:
public virtual Manager Manager { get; set; }
Controller:
[EnableQuery]
[ODataRoute("Employee({id})")]
public IHttpActionResult employeeById([FromODataUri]int id)
{
//handle data return normally...
//I have to detect $expand=Manager, to fetch the actual Manager object.
//otherwise it's null (not linked with primary key)
}
With these fee changes, $expand is working well.
You need to add $expand to show navigation property, like
localhost\odata\employee_odata.Functions.employeeById(id=1)?$expand=Manager
And for get employee by Id, I suggest you to use this method in controller:
[EnableQuery]
public IHttpActionResult Get(int id)
{
var sets = dbContext.GetEmployeeById(id);
if (!sets.Any())
return NotFound();
return this.CreateOKHttpActionResult(sets);
}
Then request like localhost\odata\Employee(1) can route to that method.
I am using request and response models to encapsulate data that needs to be passed to methods in my ASP.NET Web Api, using [FromUri] and [FromBody] when necessary.
There are instances, however, in which I would like to use both Uri and Body to populate the properties of my request model. An example would be in updating a user, where the UserId should be passed in the Uri, but the data to update would be passed in the body content. My desired implementation would look something like this:
Model:
public class UpdateUserRequestModel
{
public string UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string DisplayName { get; set; }
}
Api Method:
[HttpPut]
[Route("Update/{UserId}")]
// PUT: api/Users/Update/user#domain.net
public async Task UpdateUserAsync(UpdateUserRequestModel model)
{
// Method logic
}
I would like the property UserId to be obtained [FromUri], but the rest to be obtained [FromBody], all while keeping everything all parameters in a single object. Is this possible?
Not the cleanest solution but it should work:
[HttpPut]
[Route("Update/{UserId}")]
// PUT: api/Users/Update/user#domain.net
public async Task UpdateUserAsync(UpdateUserRequestModel model, int UserId)
{
model.UserId = UserId;
}