Being able to define the routing like so:
Route("Calculator/AddTwoNumbers/a/{a}/b/{a}")]
public IHttpActionResult AddTwoNumbers(double a, double b)
is great to invoke an endpoint like this:
http://localhost:64466/Calculator/AddTwoNumbers/a/1/b/2
I am curious what to do in scenarios where the payload/data transport object (DTO) becomes more complex (i.e. hierarchical). Take above's excample, this may be a corresponding DTO:
public class PayLoad
{
public double a { get; set; }
public double b { get; set; }
}
something like this:
Route("Calculator/AddTwoNumbers/a/{a}/b/{a}")]
public IHttpActionResult AddTwoNumbers(PayLoad payLoad)
does not work as there does not seem to be 'model binding' (?) as in asp.net mvc.
What am I suppose to do if the DTOs are more complex?
You need to decorate your payLoad parameter definition with the [FromUri] attribute:
public IHttpActionResult AddTwoNumbers([FromUri] PayLoad payLoad)
See MSDN
I think there are 2 ways to solve get data with WepApi:
1- Using [FromUri]:
To force Web API to read a complex type from the URI, add the [FromUri] attribute to the parameter. The following example defines a GeoPoint type, along with a controller method that gets the GeoPoint from the URI.
public class GeoPoint
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public ValuesController : ApiController
{
public HttpResponseMessage Get([FromUri] GeoPoint location) { ... }
}
The client can put the Latitude and Longitude values in the query string and Web API will use them to construct a GeoPoint. For example:
http://localhost/api/values/?Latitude=47.678558&Longitude=-122.130989
2- Using [FromBody]:
To force Web API to read a simple type from the request body, add the [FromBody] attribute to the parameter:
public HttpResponseMessage Post([FromBody] string name) { ... }
This code is explained here: Parameter Binding in ASP.NET Web API
Related
My controller contains this code:
[HttpGet]
public IActionResult GetNewQuestLocation([FromQuery]UserLocationPreferences userLocationPrefs)
{
//code here
}
Here is the model used as the parameter:
public record UserLocationPreferences(Location UserLoc, UserSettingsDto UserSettings);
And its child parameters:
public record Location(double Latitude, double Longitude);
public record UserSettingsDto(double MaxDistance, double MinDistance);
Problem
I have minimal experience creating API's and I have ran into an issue where a complex-type within another complex-type shows the complex-type's property name in the parameter name of the query.
For instance, on Swagger this is how the above endpoint looks:
and its full URL looks like this:
https://exampleAPI.com/api/quest?UserLoc.Latitude=53.74&UserLoc.Longitude=-2.498&UserPrefs.MaxDistance=1.0&UserPrefs.MinDistance=0.5
I wish to remove the UserLoc and UserSettings in this endpoint by way of aliasing the parameter such that it does not contain the complex-type's property name, just the primitive within the type:
UserLoc.Latitude -> Latitude
UserSettings.MinDistance -> MinDistance
etc.
I believe it is something to do with model binding but i am new to that and I haven't found a resouce online that tackles this type of scenario.
If this is a bad idea, please let me know why. My reason for doing this is because I found the query a little long and it would be an issue if multiple nested types later down the line would extend the query size dramatically. My thought was to alias these parameters for ease of use.
Thanks in advance!
Here is a workaround to use model class instead of record:
public class Location
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public class UserSettingsDto
{
public double MaxDistance { get; set; }
public double MinDistance { get; set; }
}
Controller:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
public IActionResult GetNewQuestLocation([FromQuery] Location UserLoc, [FromQuery] UserSettingsDto UserSettings)
{
return Ok();
}
}
Result:
My thought was to alias these parameters for ease of use.
If you must use record, you can change the parameter name like below to reduce the query string length:
public record UserLocationPreferences(Location l, UserSettingsDto u);
Result:
The question title might be a bit difficult to understand, but I'll try to explain what I am asking here.
I have this sort of controller method in an ASP.NET MVC controller:
public IActionResult DoStuff([FromBody] RequestObject reqObj)
{
// do stuff
ReplyObject repObj = ProcessObject(reqObj);
// do more stuff
return Json(repObj);
}
where RequestObject looks like this:
public class RequestObject
{
public string Field { get; set; }
}
I am calling it from an Angular service like this:
this.http.post("/StuffController/DoStuff", { field: "field value goes here" })
Now this works OK. But now I need DoStuff() to handle different subclasses of RequestObj, for example:
public class RequestObjectA : RequestObject
{
public string FieldA { get; set; }
}
public class RequestObjectB : RequestObject
{
public string FieldB { get; set; }
}
and have DoStuff() somehow figure out whether it was given a RequestObjectA or a RequestObjectB.
Is this kind of thing possible? I have a feeling it is not as the JSON request transmits only the contents of the object, not its class, but I'd like to know for sure.
The short answer is that it is not possible.
The long answer is: your contract for the endpoint parameter is RequestObject class so nor additional parameter members will be respected during deserialization as nothing is known about them.
I'm making a (restful) Web API in .NET Core and stumbled among some problems.
I cannot seem to find how to pass multiple subscription ID's... I need to be able to show multiple periods(invoices) of multiple subscriptions.
My route at the moment is
[Route("tenants/{tenantId:long}/subscriptions/{subscriptionId:long}/invoices/{invoiceId:long}/categories")]
From this way it seems impossible for me to pass more subscription IDs.
Some terms I found but not fully understand are:
Model Binding
[FromQuery]
My classes:
public class Subscription
{
public string Name { get; set; }
public long TenantId { get; set; }
public string Guid { get; set; }
}
public class Invoice
{
public long SubscriptionId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public long PortalId { get; set; }
}
My controllers with routes [Route("tenants/{tenantId:long}/subscriptions")] and [Route("tenants/{tenantId:long}/subscriptions/{subscriptionId:long}/invoices")]
[HttpGet]
public IEnumerable<SubscriptionViewModel> Find(long tenantId)
{
var subscriptionList = _subscriptionManager.Find(tenantId);
...
return subscriptionViewModels;
}
[HttpGet]
public IEnumerable<InvoiceViewModel> Find(long subscriptionId)
{
var invoiceList = _invoiceManager.Find(subscriptionId);
...
return invoiceViewModels;
}
Please note that i'm using a Mapper for my data (which is why i'm using ViewModels).
The currently written code is for a specific subscription.
I am looking for a Route like /api/invoices?subscriptionId=x,y,z
I understand(?) I need the [FromQuery] for that, but I cannot seem to find out how, especially if my parameter (subscriptionId) stays the same.
for the requirement which you have mentioned as:
I am looking for a Route like /api/invoices?subscriptionId=x,y,z
You can do couple of things:
pass the subscriptionIds one after the other separated by & in the query string of the URL and change the input parameter of action method to accept array of subscriptionIds
example of route:
/api/invoices/find?subscriptionId=x&subscriptionId=y&subscriptionId=z
example of action method parameter accepting array of subscriptionIds:
public IEnumerable<InvoiceViewModel> Find([FromQuery]long[] subscriptionId)
pass the comma separated string as querystring in the URL and write a piece of logic in the action method to split the string based on comma to get an array of subscriptionIds
example of route:
/api/invoices/find?subscriptionIds=x,y,z
example of action method:
public IEnumerable<InvoiceViewModel> Find([FromQuery]string subscriptionIds)
{
var ids = subscriptionIds.Split(',').Select(int.Parse).ToArray();
// do the logic on multiple subscriptionIds
}
Apart from this, you can go for creating custom model binders as well as suggested in other answers.
Hope this helps.
There can be many ways to achieve this task (I can think of two-three for now).
1) instead of long subscriptionid take a string as an input and validate it before proceeding further.
[HttpGet]
public IEnumerable<InvoiceViewModel> Find(string subscriptionIds)
{
var list = validateInput(subscriptionIds);
var invoiceList = _invoiceManager.FindList(list);
...
return invoiceViewModels;
}
public IList<long> validateInput(string subscriptionIds)
{
var list = subscriptionIds.Split(",");
... // Code to convert each element in long and throw if it is not long
return longlist;
}
2) Create custom model binders.
Steps are mentioned here:
https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
=> [FromUri] attribute can be used to bind the Complex types from query string parameters but i am not sure how i would use that.
If you ask me, i would go for approach-1 (not to increase complexity).
You can create a specific Request view model which accepts a collection of invoice ids:
public class InvoiceRequestModel
{
IEnumerable<long> InvoiceIDS { get; set; }
}
and use it for your action method:
[Route("tenants/{tenantId:long}/subscriptions/{subscriptionId:long}/invoices")]
[HttpGet]
public IEnumerable<InvoiceViewModel> Get(InvoiceRequestModel requestModel)
{
}
In the case you want to use query parameters, mark your action parameter with the [FromQuery] attribute:
[Route("tenants/{tenantId:long}/subscriptions/{subscriptionId:long}/invoices")]
[HttpGet]
public IEnumerable<InvoiceViewModel> Get([FromQuery]IEnumerable<long> invoiceIDs)
{
}
and on creating the request, pass each value with the same key in the query string:
invoiceIDs=1&invoiceIDs=2&invoiceIDs=3
Finally, it will look like this:
tenants/{tenantId}/subscriptions/{subscriptionId}/invoices?invoiceIDs=1&invoiceIDs=2&invoiceIDs=3
This is in OWIN & .Net 4.5.2
Using debug I'm proving this controller's method is being called by the web request.
My thing is the request body contains a JSON stringified object:
"{ 'id':'12', 'text1':'hello', 'test2':'world' }"
Which is applicably encoded as it is transferred on the line.
I've tried so many things I'm so confused now.
How do I get the decoded string so I can JSON.Parse() that or better yet get .Net to just given me an object?
In one version (long ago now) I had a defined type for this object. If I need that great, not a high challenge. But if I only have the JSON object from the string that's fine too.
public class cController : ApiController {
[HttpPut]
public string put(string id) {
var bdy = this.Request.Content;
//Console.WriteLine("PUT containers {0}", body);
return string.Empty;
}
}
In case it helps, the bdy.ContentReadStream is null. I don't know if this is good, bad, or important. Maybe Request.Content isn't the way to go but seems to me like if I'm going to read the body as a stream then it shouldn't be null.
I also tried working through System.Web.HttpContext. If that is somehow the answer I have no problem going back to that. But I couldn't find the secret sauce.
Pass the desired model as a parameter to the action and the frame work should be able to parse it provided it is valid JSON
public class cController : ApiController {
[HttpPut]
public IHttpActionResult Put(string id,[FromBody] Model body) {
if(ModelState.IsValue) {
return Ok(body.text1);
}
return BadRequest();
}
}
Where Model is defined as
public class Model {
public string id { get; set; }
public string text1 { get; set; }
public string test2 { get; set; }
}
I'm having issues with DataMember-naming in UriParameters.
[DataContract]
public class testobj
{
[DataMember(Name = "Test")]
public string a {get; set; }
[DataMember(Name = "Test1")]
public string b {get; set; }
}
Then i have my controller:
public IHttpActionResult test([FromUri] testobj testparams)
{
return testparams;
}
In the response i get Test and Test1, that's correct. However i have to use a and b in the uriParameters, why can't i use Test and Test1 there?
How can i fix this?
You cannot pass an object type in the Query String, You have to pass it in the HTTP Packets Body, and use [FromBody] in your Controller methods Parameter.
here's a Stackoverflow link explaining more,
ASP.NET Web APi - Passing an object as parameter
Thank You,
Bhadhri