I need to build an API using ASP.NET Web API (version 4.5.2). To get started, I'm just trying to create a basic endpoint that adds some numbers. In an attempt to do this, I've created:
[RoutePrefix("api/test")]
public class MyController : ApiController
{
[HttpGet]
public IEnumerable<int> Calulate(decimal[] op1, decimal[] op2)
{
var results = new List<Calculation>();
for (var i=0; i<op1.Length; i++)
{
var calculation = new Calculation();
calculation.Operand1 = op1[i];
calculation.Operand2 = op2[i];
calculation.Calculate();
results.Add(calculation);
}
return results;
}
public class Calculation
{
public int Operand1 { get; set; }
public int Operand2 { get; set; }
public int Result { get; set; }
public void Calculate()
{
this.Result = this.Operand1 + this.Operand2;
}
}
}
I am now trying to hit this endpoint via the Postman Chrome app. When I run it via Postman, I'm getting an error. Here is what I'm doing:
In Postman, I've put "http://localhost:50668/api/test/calculate" in the URL field next to the "GET" drop down. I then click "Send". I'm receiving the following error:
{
"Message": "An error has occurred.",
"ExceptionMessage": "Can't bind multiple parameters ('op1' and 'op2') to the request's content.",
"ExceptionType": "System.InvalidOperationException",
"StackTrace": "..."
}
I think (I don't know) the cause is because I'm not passing the values to the API from Postman correctly. However, I'm not sure how to do that. How do I pass an array of values to an API?
Short answer
To send arrays of decimals, WebApi expects url signature like:
GET http://localhost:50668/api/test/calculate?Operand1=1.0&Operand1=2.0&Operand2=3.0&Operand2=4.0
That url will send [1.0,2.0] as Operand1 and [3.0,4.0] as Operand2.
Long answer
By calling your api using GET http://localhost:50668/api/test/calculate, you actually send nothing to your server. (aside of headers content)
If you want to send data to your server, you have (at least) 2 options:
Option 2: Use GET method if operation is idempotent
Like William Xifaras already pointed out, specify that your inputs will come from the URL so WebApi interprets properly. To do so, use [FromUri].
[HttpGet]
[Route("calculate")]
public List<Calculation> CalculateWithGet([FromUri]decimal[] Operand1, [FromUri]decimal[] Operand2)
{
var results = new List<Calculation>();
for (var i = 0; i < Operand1.Length; i++)
{
var calculation = new Calculation();
calculation.Operand1 = Operand1[i];
calculation.Operand2 = Operand2[i];
calculation.Calculate();
results.Add(calculation);
}
return results;
}
public class Calculation
{
public decimal Operand1 { get; set; }
public decimal Operand2 { get; set; }
public decimal Result { get; set; }
public void Calculate()
{
Result = this.Operand1 + this.Operand2;
}
}
With a REST client, it should look like:
With GET, data is sent via the URL
Note that if you use GET Method, the server will expect to receive inputs from the URL. You should therefore send queries like:
GET http://localhost:50668/api/test/calculate?op1=1.0&op1=2.0&op2=3.0&op2=4.0
Use POST method if operation is not idempotent
Since the operation does some server side calculation, I pretend it may not always be idempotent. If it is the case, POST might be more appropriate.
[HttpPost]
[Route("calculate")]
public List<Calculation> CalculateWithPost(CalculationInputs inputs)
{
var results = new List<Calculation>();
for (var i = 0; i < inputs.Operand2.Length; i++)
{
var calculation = new Calculation();
calculation.Operand1 = inputs.Operand1[i];
calculation.Operand2 = inputs.Operand2[i];
calculation.Calculate();
results.Add(calculation);
}
return results;
}
public class CalculationInputs
{
public decimal[] Operand1 { get; set; }
public decimal[] Operand2 { get; set; }
}
public class Calculation
{
public decimal Operand1 { get; set; }
public decimal Operand2 { get; set; }
public decimal Result { get; set; }
public void Calculate()
{
Result = this.Operand1 + this.Operand2;
}
}
With POST, data is sent via the body
With that structure, the server expects to receive inputs from the request body. WebApi will deserialize the body if it matches the signature of your function.
With a REST client, it should look like:
Sidenote
The nuget package used to get the SwaggerUI generated (printscreens) can be find here. Very useful to run adhoc tests on WebApis.
Add from [FromUri] before the parameter.
public IEnumerable<int> Calulate([FromUri] decimal[] op1, [FromUri] decimal[] op2)
To force Web API to read a complex type from the URI, add the
[FromUri] attribute to the parameter
http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
I think you can pass as a JSON array
http://localhost:50668/api/test/calculate?op1=[1,2,3]&op2=[4,5,6]
Hope this helps
Related
I have written the below simple Web API method.
[HttpGet]
[HttpPost]
public int SumNumbers([FromUri]Numbers calc, [FromUri]Operation op)
{
int result = op.Add ? calc.First + calc.Second : calc.First - calc.Second;
return op.Double ? result * 2 : result;
}
Below is the model class for Numbers:
public class Numbers
{
public int First { get; set; }
public int Second { get; set; }
}
Below is the model class for Operation:
public class Operation
{
public bool Add { get; set; }
public bool Double { get; set; }
}
Below is how I am trying to test in Postman. But, as you can see I am getting "0" as output. When debugged the code, understood that values are not passing from Postman into code.
One another user also posted the same problem here. But, whatever the resolution he showed, I am doing already, but I am not getting answer.
Can anyone please suggest where I am doing wrong?
There are 2 major issues with your post, firstly your controller (due to [FromUri]
binding) is specifying that the arguments need to be passed as Query String parameters and not Http Header values.
The second issue is that you have defined two complex type parameters that you want to obtain the values form the URI, this is not supported.
How to pass in Uri complex objects without using custom ModelBinders or any serialization?
This is a great writeup on how to fully exploit the [FromUriAttribute][2] up to ASP.Net Core 2.2, many of the principals apply to the FromQueryAttribute which is still used the current in ASP.Net 6.
We can use [FromUri] to bind multiple primitive typed parameters, or we can bind 1 single complex typed parameter. You cannot combine the two concepts, the reason for this is that when a complex type is used ALL of the query string arguments are bound to that single complex type.
So your options are to create a new complex type that combines all the properties from both types, or declare all the properties of both types as primitive parameters to the method:
http://localhost:29844/api/bindings/SumNumbers1?First=3&Second=2&Add=True&Double=False
http://localhost:29844/api/bindings/SumNumbers2?First=3&Second=2&Add=True&Double=False
[HttpGet]
[HttpPost]
public int SumNumbers1([FromUri] int First, [FromUri] int Second, [FromUri] bool Add, [FromUri] bool Double)
{
int result = Add ? First + Second : First - Second;
return Double ? result * 2 : result;
}
[HttpGet]
[HttpPost]
public int SumNumbers2([FromUri] SumRequest req)
{
int result = req.Add ? req.First + req.Second : req.First - req.Second;
return req.Double ? result * 2 : result;
}
public class SumRequest
{
public int First { get; set; }
public int Second { get; set; }
public bool Add { get; set; }
public bool Double { get; set; }
}
It is also technically possible to use a nested structure, where you wrap the multiple complex with a single outer complex type. Depending on your implementation and host constraints you may need additional configuration to support using . in the query parameters, but a nested or wrapped request implementation would look like this:
http://localhost:29844/api/bindings/SumNumbers3?Calc.First=3&Calc.Second=2&Op.Add=True&Op.Double=False
[HttpGet]
[HttpPost]
public int SumNumbers3([FromUri] WrappedRequest req)
{
int result = req.Op.Add ? req.Calc.First + req.Calc.Second : req.Calc.First - req.Calc.Second;
return req.Op.Double ? result * 2 : result;
}
public class WrappedRequest
{
public Numbers Calc { get; set; }
public Operation Op { get; set; }
}
It is also possible to use a combination of Http Headers and query string parameters, however these are generally harder (less common) to manage from a client perspective.
It is more common with complex parameter scenarios (not to mention more REST compliant) to force the caller to use POST to access your calculation endpoint, then multiple complex types are simpler to support from both a client and API perspective.
If you want to receive parameters using FromUri, shouldn't you pass them in the URL when doing the GET call? A simpler call would be something like this:
[HttpGet]
[Route("{first:int}/{second:int}")]
public int SumNumbers([FromUri]int first, [FromUri]int second)
{
return first+second;
}
And in Postman your call should be more like this (the url)
http://localhost:29844/api/bindings/SumNumbers/5/7
and this would return 12!
Now if you want to pass First and Second as headers and not in the url then you don't want to use FromUri and then your code would change a little bit (you will then need to read the request and dissect it to get every header alone. Something like this:
HttpRequestMessage request = Request ?? new HttpRequestMessage();
string first = request.Headers.GetValues("First").FirstOrDefault();
string second = request.Headers.GetValues("Second").FirstOrDefault();
I am trying to call a WebAPI method from Angular 5 like this:
selectClaims(state: DataSourceRequestState):Observable<DataResult>
{
return this.http.get<GridDataResult>(`${this.apiUrl}/SelectClaims?${toDataSourceRequestString(state)}`);
}
Which calls the API method as expected. The API method is:
[Route("SelectClaims")]
[HttpGet]
public IHttpActionResult SelectClaims([FromUri][DataSourceRequest]DataSourceRequest ClaimsRequest)
{
if(ClaimsRequest == null)
ClaimsRequest=new DataSourceRequest { Page=1, PageSize=20 };
var result = _db.Claims.ToDataSourceResult(ClaimsRequest, c => { c.SortHistory(); return c; });
return Ok(result);
}
The trouble is that ClaimsRequest only de-serializes Page and PageSize correctly. Filters and Sorts don't come through:
Fiddler tells me that the URL from Angular is:
GET /api/v1/Claims/SelectClaims?filter=id~eq~2&page=1&sort=firstName-asc&pageSize=20 HTTP/1.1, but in the controller both filter and sort are null.
If I create a URL through Swagger like: 'http://localhost:50223/api/v1/Claims/SelectClaims?ClaimsRequest.page=1&ClaimsRequest.pageSize=11&ClaimsRequest.sorts=firstName-desc' I do see a sort array in the API method, but the "Member" field is null.
Any attempt to add a filter through Swagger like 'http://localhost:50223/api/v1/Claims/SelectClaims?ClaimsRequest.page=1&ClaimsRequest.pageSize=11&ClaimsRequest.filters=id~eq~2' results in a "Cannot create an instance of an interface." error.
The state is a DataSourceRequestState in the angular component from a Kendo Grid for Angular.
I have simulated this in a simple test program and everything works fine there. The only difference in my test program is that the API controller targets .Net Core and the real system targets .Net 4.6.1.
Do I have to de-serialize manually in .Net 4.6.1 for some reason, or is something else going on here?
It should be a POST not a GET. Something like this:
return this.http.post<GridDataResult>(`${this.apiUrl}/SelectClaims`, toDataSourceRequestString(state)});
I needed it to be a GET (URL) so i created a new object
public class GridParamaterBinder
{
public int Page { get; set; }
public int PageSize { get; set; }
public string Filter { get; set; }
public string Sort { get; set; }
public DataSourceRequest ToDataSourceRequest(IConfigurationProvider mapper, Func<string, string> OverDefaultParamaterMapping)
{
DataSourceRequest result = new DataSourceRequest();
result.Page = Page;
result.PageSize = PageSize;
result.Sorts = GridDescriptorSerializer.Deserialize<SortDescriptor>(Sort);
result.Filters = FilterDescriptorFactory.Create(Filter);
return result;
}
}
and used it instead of the Telerik effort.
in API I Bind it like so
public virtual DataSourceResult Get([FromUri]GridParamaterBinder request)
And then used it like
DataSourceResult results = query.ToDataSourceResult(request.ToDataSourceRequest(), r => (r)));
Thanks #KevDevMan for your solution. I found this example,
then I changed my API controller like this and it worked like a charm :
[HttpGet, Route("for-kendo-grid")]
public DataSourceResult GetProducts([System.Web.Http.ModelBinding.ModelBinder(typeof(WebApiDataSourceRequestModelBinder))] DataSourceRequest request)
explanation here
I'm following Scott Allen's MVC4 course on PluralSight (I'm using MVC5 and WebAPI2 but they should be the same) and I am trying to pass an object via HTTP PUT. The model binder should bind it, but I am getting NULL for the parameter.
public HttpResponseMessage PutObjective(int id, [FromBody] Objective objective)
{
if (ModelState.IsValid && id == objective.ObjectiveID)
{
//todo: update - look up id, replace text
return Request.CreateResponse(HttpStatusCode.OK, objective);
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
and in my front-end javascript I am doing the following (I'm creating an object for testing, so ignore 'objective' passed in):
var updateObjective = function (objective) {
var myobj = { "ObjectiveID": "3", "ObjectiveDescription": "test" };
return $.ajax(objectiveApiUrl + "/" + objective.ObjectiveID, {
type: "PUT",
data: myobj
});
}
My class looks like this:
public class Objective
{
public int ObjectiveID { get; private set; }
public string ObjectiveDescription { get; set; }
public Objective (int Id, string Desc)
{
this.ObjectiveID = Id;
this.ObjectiveDescription = Desc;
}
}
Any thoughts on why 'objective' in the backend is always 'null' ?
I've done what Scott Allen is doing, even tried adding in [FromBody] but no luck. $.ajax should have the correct content type by default I understand, so no need to set it.
I had Fiddler2 but I'm unsure as to what I am looking at to be honest. I can see my object as JSON being sent to the backend.
Well, if you're familiar with Model Binding you'll have seen the issue in my Objective class:
public int ObjectiveID { get; private set; }
with a private set, no instance can be created of the Objective class. To make it work, the 'private' access specifier needs to be removed.
What needs to happen really is that Objective becomes ObjectiveViewModel, and we convert what comes back to an Objective domain object (which may have more properties than we need for this screen). This can have a private set.
I'm designing an application using ASP.NET Web API and Entity Framework 5 and LINQ to Entities. The Web API doesn't serve up the entities directly, it converts them to a set of data transfer objects that are similar but not identical to my entities. The API will be used by a Silverlight application initially but I will have to support non-.NET clients (e.g. iOS apps) down the road. I'd also like to give the client the ability to run a robust set of queries against the API.
These requirements have lead me to consider the query object pattern. Essentially, I want to create a homegrown query object client-side, post it to the Web API, and convert the query object to a lambda expression that I can use in LINQ to Entities. This last part is what's tripping me up.
Starting with a simple comparison query, I want to be able to convert an object that looks like the following into a lambda expression at runtime.
public enum QueryOperator
{
None = 0,
GreaterThan,
GreaterThanOrEqualTo,
EqualTo,
NotEqualTo,
LessThanOrEqualTo,
LessThan
}
public class SimpleQuery<T>
{
public SimpleQuery()
{
this.Field = null;
this.Operator = QueryOperator.None;
this.Value = null;
}
public string Field { get; set; }
public QueryOperator Operator { get; set; }
public object Value { get; set; }
public IEnumerable<T> Execute(IQueryable<T> queryTarget)
{
// ????
}
}
How can I do this?
I've had to do things like this in the past. Here's what I came up with:
public IEnumerable<T> Execute(IQueryable<T> queryTarget)
{
return queryTarget.Where(this.GetWhereExpression<T>());
}
private Expression<Func<T, bool>> GetWhereExpression<T>()
{
var param = Expression.Parameter(typeof(T), "x");
var prop = Expression.Property(param, this.Field);
var value = Expression.Constant(this.Value, prop.Type);
Expression compare = null;
switch(this.Operator)
{
case QueryOperator.EqualTo:
compare = Expression.Equal(prop, value);
break;
...
}
return Expression.Lambda(compare, param);
}
I have two actions in a controller. One that displays a form for file upload and another one that displays the results of the upload.
I have created a POCO called FileInfo i.e
public class FileInfo
{
public string Name { get; set; }
public int Length { get; set; }
public string FileType { get; set; }
public string ErrorMessage { get; set; }
}
When I submit the form, the Upload action creates and populates the FileInfo object and then redirects to the second action called results. I want to be able to use the same file info object in the results action.
I am able to get around this using TemPData[], but it is limited since it only holds object data for a single request. I presume there must be a better way to share abjects between controller actions.Any help is appreciated!
// Upload Action
List<FileInfo> fileInfo= new List<FileInfo>();
//populate the fileInfo object using fi.Add()
if ((status.ToString() == "OK"))
{
TempData["Info"] = fileInfo;
return RedirectToAction("Results");
}
else
{
return RedirectToAction("Index");
}
//Results action.
public ActionResult Results()
{
List<FileInfo> fi = TempData["Info"] as List<FileInfo>;
if (fi != null)
{
return View(fi);
}
else
{
return View("Index");
}
}
If you need something to stick around longer then one subsequent request, you will have to put it in Session or in persistent storage (e.g. database).