How to map an Url to a controller method in WebApi? - c#

I'm using the below model to bind the query string with my controller using [FromUri]
namespace MyApi.Models
{
public class Product
{
public int Id { get; set; }
public decimal Price { get; set; }
}
}
The controller action method code is
public ActionResult ActionMethod1( [FromUri] Product product)
The URL incoming is http://myapplication:2020/ActionMethod1/?Id=1&Cost=20
Now, internally the Cost in the query param should be mapped to Price in the Product class.
How can I do that ? I know I can use custom model binding but instead something like,
Is there any attribute that I can apply to the above class? Something like,
[BindingName("Cost")]
public decimal Price {get;set}

Related

Bind complex object from query string in post request

I have a class for holding both raw and encrypted values from query parameters:
public class SID
{
[FromQuery(Name = "sidInt")]
public int RawValue { get; set; }
[FromQuery(Name = "sid")]
public string EncryptedValue { get; set; }
public static implicit operator int(SID model)
{
return model.RawValue;
}
}
Having this I can successfully use it as a parameter for get requests:
/// GET: /{controller}/index?sid=my-encrypted-string
[HttpGet]
public IActionResult Index(SID id){
int resourceId = id;
//rest of the action code
}
Only encrypted value is provided in query string, raw integer value is automatically decrypted and added to the query string in my custom middleware. Notice that thanks to named [FromQuery] attributes I can use any parameter name in the action.
So far so good. But now I want to use the same SID class as a property in model for post request:
public class MyPostModel {
[Required]
[FromQuery]
public SID Id { get; set; }
public string Name { get; set; } // posted in body
// rest of the properties
}
/// POST: /{controller}/index?sid=my-encrypted-string + other fields in the body
[HttpPost]
public IActionResult Index(MyPostModel model){
int resourceId = model.Id;//model.Id is null here
//rest of the action code
}
But unfortunately I cannot make it working, Id in the model is not bound. I feel like I'm missing something obvious here. Is it possible? Do I need a custom model binder for this(I hope not, I want to use SID class as a property for several models and for different actions)?
Of course I can add sid parameter to the post action and reassign it in the action body explicitly but it looks wrong and too verbose:
[HttpPost]
public IActionResult Index(SID id, MyPostModel model){
model.Id = id;//id is correctly populated but model.Id is not
//rest of the action code
}
I think that your problem is that parameter is called "sid" and property in MyPostModel is "Id". Aside from that, I don't see any reason why it would not work. Can you change your MyPostModel:
public class MyPostModel {
[Required]
[FromQuery(Name = "sid")]
public SID Id { get; set; }
public string Name { get; set; } // posted in body
// rest of the properties
}

How to pass an array of objects through a query string and read them in asp.net Controller

I am building an API to generate Invoice Number.
I have the following InvItem class
public class InvItem
{
public string ItemCode;
public double Quantity;
public double SaleValue;
}
I have the following Controller method
[Route("api/InvoiceMaster/GetInvoiceNum")]
[HttpGet]
public IActionResult GetInvoiceNum(
string xDateTime,
string BuyerName,
double TotalBillAmount,
InvItem[] items
)
{
...
var invItems = new List<InvItem>();
invItems.AddRange(items);
...
return Ok();
}
Invoice can have one or more items. Now I want to call that method from postman (or any other application) using GET request.
I have already built that method using POST request and reading parameters from request body. But the requirement here is STRICTLY Get Request.
I have tried the following url but cannot get the value of items in controller's action method 'GetInvoiceNum'
https://localhost:44365/api/InvoiceMaster/GetInvoiceNum?xDateTime=2020-01-01 12:00:00&BuyerName=elon&TotalBillAmount=1519&items[0].ItemCode=001897&items[0].Quantity=1&items[0].SaleValue=19&items[1].ItemCode=002899&items[1].Quantity=1&items[1].SaleValue=1500
How can I pass this array of objects to api?
You have to add from [FromQuery]
public IActionResult GetInvoiceNum(
[FromQuery] string xDateTime,
[FromQuery] string BuyerName,
[FromQuery] double TotalBillAmount,
[FromQuery] InvItem[] items
)
and convert fields to properties by adding getters/setters
public class InvItem
{
public string ItemCode { get; set; }
public double Quantity { get; set; }
public double SaleValue { get; set; }
}

Why is [FromQuery] attribute required?

I created an api with the following action:
[HttpGet("GetStuff/{Name}")]
public ActionResult<string> GetStuff([FromRoute]GetStuffModel requestModel)
{
if (requestModel == null) return BadRequest();
var result = doStuff();
return Ok(result);
}
The model looks like this:
public class GetStuffModel
{
public string Name { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public double MyNumber { get; set; }
}
Now I am using swagger to test this and basically the above doesn't work as I think it should. What happens is when I submit a get request with swagger and look at the values in the model, I find that only Name is captured. Latitude and Longitude have a value of 0. However, if I change the model to this:
public class GetStuffModel
{
public string Name { get; set; }
[FromQuery]
public double Latitude { get; set; }
[FromQuery]
public double Longitude { get; set; }
public double MyNumber { get; set; }
}
Then everything gets captured. My question is why do I have to specify [FromQuery] when I have already declared [FromRoute] in the controller?
Edit: I also added MyNumber variable and that one also picks up without the need of [FromQuery]
For [FromRoute] attribute model binder tries to bind values from request route data. With the current setup route data contains only 1 value for Name (well, there also action and controller values there, but it doesn't matter now) so it's possible to bind only Name property of GetStuffModel (if you add string Action property to model you'll see it will be binded as well). When you add [FromQuery] attribute to model properties it overrides model binding behavior and allows to bind certain properties from query string. In this case swagger adds Latitude and Longitude as query parameters and binding works fine.

c# Webapi and model binding with fluentvalidation

how do I get asp.net webapi to look at the route data and body to bind to a complex object?
Using the following route "api/products/{productid}/manufacturers/{manufacturerId}" I need the productId and manufacturerId to bind to a model, so my controller method is as
public IHttpActionResult Create(CreateProductManufacturerModel
createProductManufacturerModel)
and my model is
public class CreateProductManufacturerModel
{
public int ProductId { get; set; }
public int ManufacturerId { get; set; }
public bool IsDefault { get; set; }
public string ManufacturerCode { get; set; }
public string Description { get; set; }
}
I know I could change my method to be as below, but I am using fluentvalidation to validate the whole createproductmanufacturermodel, this is done automatically (see- http://www.justinsaraceno.com/2014/07/fluentvalidation-with-webapi2/). So the productId and manufacturerId would not be validated correctly as the are set as zero.
public IHttpActionResult Create(int productId, int manufacturerId, CreateProductManufacturerModel
createProductManufacturerModel)
I've have tried a modelbinder but it then does not fire the fluentvalidation automatically. Not too sure if this matters, but the body being posted is in a json format.
Thanks in advance.
Paul
Inherit System.Web.Http.Filters.ActionFilterAttribute.
Override OnActionExecuting()
Extract routing data using actionContext.RequestContext.RouteData.Values
Extract model using actionContext.ActionArguments
Validate and assign routing data to model properties
Decorate your action with the new attribute you created.
If this is a less specific use case, you can use reflection to assign routing data to model properties according to param names.
Can you change your action signature to be:
public IHttpActionResult Create([FromBody]CreateProductManufacturerModel
createProductManufacturerModel){}
and then validate the 2 values you need: createProductManufacturerModel.ProductId and createProductManufacturerModel.ManufacturerId ?

Methodology for passing a mix of: list<>, object and primitives to an ASP MVC controller action

I'm fairly new to C#, and am creating my first MVC project, and am having a hard time figuring out the methodology of passing 3 parameters of varying types to a controller action. Here is my controller method:
public ActionResult Create(Notification notification, string hash, list<int> users){
//code inside method irrelevant...
}
and my Notification model:
public class Notification
{
public int ID { get; set; }
public string ApplicationID { get; set; }
public string Description { get; set; }
public System.DateTime DateStamp { get; set; }
}
Before I added the List<> parameter it worked fine by having the posted data (or querystring) like so:
ApplicationID=1&Description=yo&hash=abcdefg
And it magically knew that the two parameters ('ApplicationID' and 'Description') belonged to the notification object. But now I'd like to add in a list<> of ints.
Is this something that can be done and how would you format the data/querystring passed?
Is this something that can be done
Yes.
and how would you format the data/querystring passed?
Like this:
ApplicationID=1&Description=yo&hash=abcdefg&users=1&users=2&users=3
or if you prefer like this:
ApplicationID=1&Description=yo&hash=abcdefg&users[0]=1&users[1]=2&users[2]=3
Also you might find the following blog post an useful read.
But before transforming your controller action signatures into some spaghettified code and readers of your code having to cycle a couple of screens horizontally in order to see the millions of parameters this action takes, stop the madness and introduce a view model:
public class CreateViewModel
{
public Notification Notification { get; set; }
public string Hash { get; set; }
public List<int> Users { get; set; }
}
and then:
public ActionResult Create(CreateViewModel model)
{
//code inside method irrelevant...
}
and then:
notification.applicationID=1&notification.description=yo&hash=abcdefg&users[0]=1&users[1]=2&users[2]=3

Categories

Resources