Web API parameter binding not working - asp.net-web-api

I have two entity classes Donor and User. Donor is subclass of User as given below
public abstract class User {
[Key]
public long Id { get; set; }
...
}
public class Donor : User {
public string AlternateMobileNumber { get; set; }
...
}
A web api controller action shown below receives POST request with JSON data in the body
public class DonorsController : ApiController {
//POST: api/Donors/EditDonor
[HttpPost]
public HttpResponseMessage EditDonor([FromBody] Donor donor) {
...
}
}
Here is how the post request looks like
POST http://localhost:xxxxx/Donor/api/Donors/EditDonor HTTP/1.1
Content-Length: 477
Cache-Control: no-cache
Content-Type: application/json;charset=UTF-8
Accept: */*
...
{"Id":12,"UserId":"donor9#abc.com","FirstName":"firstname9c","MiddleName":"M","LastName":"lastname9","Gender":"Male","MobileNumber":"7777777777","BloodGroup":"A+ve","OfficeLocality":{"Id":2,"Name":"Malad"},"ResidenceLocality":{"Id":2,"Name":"Malad"},"Organization":"organization9","Designation":"designation9","TimesDonated":1,"DateLastDonated":"Mon Dec 01 00:00:00 GMT+05:30 2014","LastDonatedAt":"Hosp 1","IsSDPDonor":false,"IsIntrestedDonor":false,"Comments":"Some Comment"}
The issue is that the Donor object is empty (not null but empty, without values applied to properties of Donor).
If I change Donor object and use a Data Transfer Object (DonorDO) with the same properties but without any inheritance, then the properties are correctly populated in that Data object. For example the Donor Data Object is like below.
//Plain Data Transfer Object without inheritance
public class DonorDO {
public long Id { get; set; }
public string AlternateMobileNumber { get; set; }
...
}
Why does DonorDO get its property values bound correctly, while the Donor Entity object remains empty with none of the properties bound. Is it related to inheritance or some other issue.

I've noticed that you are POSTing to "http: // localhost : xxxxx/Donor/api/Donors/EditDonor" which is wrong, you need to POST to "http: // localhost : xxxxx/api/Donors" (you need to POST to the controller base address only, and controller name should be after "api" not before).
This code is 100% working :
using System.Net;
using System.Net.Http;
using System.Web.Http;
//[Authorize]
public class DonorsController : ApiController
{
[HttpGet]
public Donor Get(int id)
{
return new Donor
{
Id = id,
AlternateMobileNumber = "0000",
};
}
//POST: api/Donors/EditDonor
[HttpPost]
public HttpResponseMessage EditDonor([FromBody] Donor donor)
{
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
public abstract class User
{
public long Id { get; set; }
}
public class Donor : User
{
public string AlternateMobileNumber { get; set; }
}
Now, using fiddler I called this method as you can see in the bellow image, and on the controller side you can see the passed donor argument.
Hope this helps.

Related

Asp.net core 2.2 web api FromForm of complex object contains only null values

Hi I am trying to build an endpoint for slack commands in asp.net core 2.2.
I have a data structure representing a commandrequest from slack like so:
public class SlackCommandDTO
{
[FromForm(Name = "token")]
public string Token { get; set; }
[FromForm(Name = "team_id")]
public string TeamId { get; set; }
[FromForm(Name = "team_domain")]
public string TeamDomain { get; set; }
[FromForm(Name = "channel_id")]
public string ChannelId { get; set; }
[FromForm(Name = "channel_name")]
public string ChannelName { get; set; }
[FromForm(Name = "user_id")]
public string UserId { get; set; }
[FromForm(Name = "user_name")]
public string UserName { get; set; }
[FromForm(Name = "command")]
public string Command { get; set; }
[FromForm(Name = "text")]
public string Text { get; set; }
[FromForm(Name = "response_url")]
public string ResponseUrl { get; set; }
[FromForm(Name = "trigger_id")]
public string TriggerId { get; set; }
}
My controller to receive data looks like this:
[Route("api/[controller]")]
[ApiController]
public class CustomerServiceController : ControllerBase
{
// POST api/customerservice
[HttpPost]
public void Post([FromForm] SlackCommandDTO command)
{
Console.Write(command.Token);
}
}
my startup.cs looks like this
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
I have tried setting the compatability settings in startup.cs to 2.1 and 2.2.
The result is always an instance of the object that contain null in all properties.
I have tried setting the decorator to [FromBody] (not that that is supposed to work) but in that case I get 415 unsupported media type (as it should).
I have tried sending the requests with content-type x-www-form-urlencoded and form-data as well as text/plain and application/json. the latter two return 415.
I have also tried sending the request through swagger with the same result and curl both using -d keyword and -F keyword for each pair of data.
If I am missing some information please let me know, I am drawing a blank here on how to solve it so please help.
The data I am receiving is from Slack according to this article about implementing slash commands in slack.
https://api.slack.com/slash-commands#responding_to_commands
I have solved my problem.
My issue was the fundamental misunderstanding that the parameters would be bound as a single object when using the FromForm attribute when actually I was supposed to parameterize each field as a string input in the post method like so:
[Route("api/[controller]")]
[ApiController]
public class CustomerServiceController : ControllerBase
{
// POST api/customerservice
[HttpPost]
public void Post([FromForm] string token,
[FromForm] string team_id,
[FromForm] string team_domain,
[FromForm] string channel_id,
[FromForm] string channel_name,
[FromForm] string user_id,
[FromForm] string user_name,
[FromForm] string command,
[FromForm] string text,
[FromForm] string response_url,
[FromForm] string trigger_id)
{
Console.Write(token);
}
}
[FromForm] is not for annotating properties on your model. It's for indicating how an action param will be bound. If you were accepting JSON, you could achieve this via [JsonProperty], but there's no way to change the property names for binding from form. They need to match, i.e. you'll either need to change your properties to stuff like Team_Id (with the underscore) or change your field names to stuff like teamId (without the underscore).

protocol WebException Error when using Required attribute

I have created ASP.NET Web API and calling the post method using json from client side and it's working fine :
public class ValueController : ApiController
{
public void Post([FromBody]model value)
{
}
}
public class model
{
public decimal value { get; set; }
}
string JSONString = "{\"value\":\"999.99\"}";
but when I use the [Required] attribute in the value property,
public class model
{
[Required]
public decimal value { get; set; }
}
it's start giving protocol Error when call the GetResponse() method..
Since decimal is a Value type and if we use the Required attribute to a value type, it will cause an error..
it will always have a value (the default of 0) if the incoming request does not provide the value.
Now I am using [DataMember(isRequired=true)] instead of [Required] attribute

Set a ASP.NET WEB.API model property to be read-only for consumers of the API?

What's the best way of ensuring that a property of a model can only be set by the ASP.NET WEB.API service? To a consumer of the service, that property is read-only.
For example:
public class MyModel
{
[Required]
public string CanBeSetByConsumer { get; set; }
// Can only be set by the service
public int Id { get; set; }
}
public class MyModelController : ApiController
{
public MyModel Get(int id)
{
// get MyModel by Id
return new MyModel();
}
public MyModel Post(MyModel myData)
{
// save myData to a store and generate an ID
// return myData with ID populated with a 201 Created
}
}
In the above example, the consumer of the API can POST:
{
"CanBeSetByConsumer" : "SomeValue"
}
The consumer can also GET:
{
"Id" : 1234,
"CanBeSetByConsumer" : "SomeValue"
}
What I would like to do is return a 400 BAD REQUEST if the client POSTs:
{
"Id" : 1234,
"CanBeSetByConsumer" : "SomeValue"
}
Here is one way to do it. Note that the POST model does not contain the Id property.
public class MyGetModel
{
[Required]
public string CanBeSetByConsumer { get; set; }
public int Id { get; set; }
}
public class MyPostModel
{
[Required]
public string CanBeSetByConsumer { get; set; }
}
public class MyModelController : ApiController
{
public MyGetModel Get(int id)
{
// get MyModel by Id
return new MyGetModel();
}
public MyGetModel Post(MyPostModel myData)
{
// save myData to a store and generate an ID
// return myGetData with ID populated with a 201 Created
}
}
Then if you have a lot of shared properties, you can have both of these inherit from an abstract class MyModel.
Another way to do it could be to add an action filter to the post action. In that action filter class, you would override the OnActionExecuting method, inspect the POST values collection for a value under the Id key, and set your 400 BAD REQUEST response there.
public class PreventIdValueAttribute
: System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// check request for id value, and if present,
// set the result to a 400 bad request HttpResponseMessage
}
}
[PreventIdValue]
public MyModel Post(MyModel myData)
{
// save myData to a store and generate an ID
// return myData with ID populated with a 201 Created
}
Note that with the second option, your MyModel instance will still have an Id value in the Post action, but its value will be zero.

Web API FormURLEncoded media type formatter not working for complex type

I have the following ComplexType which I have defined on the PUT action for my controller as follows:
[DataContract(Namespace = "http://schemas.abc.com/formurl", Name = "ComplexData")]
public class ComplexData {
[DataMember] public string Name { get; set; }
[DataMember] public Int32 ID { get; set; }
[DataMember] public DataExchangeList DataExchange { get; set; }
}
[DataContract(Namespace = "http://schemas.abc.com/formurl", Name = "DataItem")]
public class DataItem {
[DataMember] public string Name { get; set; }
[DataMember] public string Value { get; set; }
}
[CollectionDataContract(Namespace = "http://schemas.abc.com/formurl", Name = "DataExchange")]
public class DataExchangeList : List<DataItem> {
}
Here's my controller:
public class ValuesController : ApiController
{
// PUT api/values
public HttpResponseMessage Put(ComplexData data)
{
return Request.CreateResponse<ComplexData>(HttpStatusCode.OK, data);
}
}
When trying to pass the ComplexData type on PUT, the collection DataExchange does not bind properly:
PUT http://localhost:53954/api/values HTTP/1.1
User-Agent: Fiddler
Content-Type: application/x-www-form-urlencoded
Accept: application/xml
Host: localhost:53954
Content-Length: 106
Id=123456789&Name=Complex&DataExchange[0][Name]=Data&DataExchange[0][Value]=ComplexType
The response which simply returns back the data shows the collection is empty:
<ComplexData xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.abc.com/formurl">
<DataExchange />
<ID>123456789</ID>
<Name>Complex</Name>
</ComplexData>
Isn't this supposed to work with the FormUrlEncodedMediaTypeFormatter?
Is there anything wrong with the way I'm passing the Collection data as follows?
DataExchange[0][Name]=Data&DataExchange[0][Value]=ComplexType
Any suggestions would be appreciated.
Thanks,
Sid

Need example for web api post with multiple parameter

In c#, Need example for web api post with multiple parameter ,Below i have attached my sample code.Please look into the InsertLeave method.In my code without CompanyId parameters working fine. When i add the companyid not able to invoke.
namespace AeS.SaaSAPI_2116
{
[RoutePrefix("{CompanyId}/{SecurityKey}")]
public class LeaveController : ApiController
{
[HttpPost]
[Route("{APIName}/x")]
public string InsertLeave(List<LeaveRequest> objList, string CompanyId)
{
foreach (LeaveRequest LR in objList)
{
}
return "Sucess ";
}
}
}
public class LeaveRequest
{
[Required]
public string EMP_STAFFID { get; set; }
[Required]
public string LEAVE_TYPE { get; set; }
}
}
I think you can use send your parameter to the server by request body, You will have to create a single class that wrapping your all parameters.You can use model binding to resolve this kind of issue

Resources