.NET MVC API - dots (period) in get request - c#

I'm trying to setup Facebook Notification API.
I have an APi Controller with RealtimeUpdate() - Get, will be used just for verification of endpoint.
As is written in Fb Docs:
Firstly, Facebook servers will make a single HTTP GET to your callback
URL when you try to add or modify a subscription. A query string will
be appended to your callback URL with the following parameters:
hub.mode - The string "subscribe" is passed in this parameter
hub.challenge - A random string
hub.verify_token - The verify_token value you specified when you created the subscription
From here I have a problem - I have no idea how to handle this dots in query params names. I google a lot, and did not find the solution.
Can somebody please say to me how to get data from this hub.* values?
Thank you!

Update your method signature using the FromUri attributes, like this:
public string Get(
[FromUri(Name="hub.mode")]string mode,
[FromUri(Name="hub.challenge")]string challenge,
[FromUri(Name="hub.verify_token")]string verifyToken
)
{
/* method body */
}
The parameters will be bound from the query string using the specified names.

Slightly different form Steve's answer.
In case you need to have a normal controller instead of an Api one (if you are inheriting from Controller rather tha ApiController), the follow worked for me:
namespace Name
{
public class Hub
{
public string Mode { get; set; }
public string Challenge { get; set; }
// ReSharper disable once InconsistentNaming
public string Verify_Token { get; set; }
}
public class FacebookWebHooksController : Controller
{
[System.Web.Http.HttpGet, ActionName("Callback")]
[AllowAnonymous]
public ContentResult CallbackGet(Hub hub)
{
if (hub.Mode == "subscribe" && hub.Verify_Token == "YOUR_TOKEN")
return Content(hub.Challenge, "text/plain", Encoding.UTF8);
return Content(string.Empty, "text/plain", Encoding.UTF8);
}
}
[HttpPost]
[AllowAnonymous]
public ActionResult Callback()
{
Request.InputStream.Seek(0, SeekOrigin.Begin);
var jsonData = new StreamReader(Request.InputStream).ReadToEnd();
}
}

The Model Binder has some illegal characters, of which I believe '.' is a special character, used primarily to bind complex objects. When all else fails, you can look at Request.QueryString and Request.Form directly, just like in ASP.NET WebForms.
You can also try using a complex object that has a Property named hub with subproperties mode, challenge, and verify_token. This might just do the trick.

Related

Passing List object and int to web api

I have a web api core project that if I send just the list parameter than the API receives the values, however if I send both parameters that the controller is looking for then both parameters are seen as null
My contoller:
[HttpPost]
[Route("/jobApi/RunBD")]
public int RunBDReport([FromBody]int month, [FromBody] IEnumerable<ClientModel> clients)
{
billingDetailCycle objBillDetail = new billingDetailCycle();
if (ModelState.IsValid)
{
return objBillDetail.Run(clients.ToList(), month);
}
else
{
return 500;
}
}
ClientModel:
public class ClientModel
{
public string BlockOfBus { get; set; }
public string ClientId { get; set; }
public string Location { get; set; }
public string SuppressSsn { get; set; }
}
The request I am sending:
{"month":7,
"ClientModel":[{"blockOfBus":"XXX",
"clientId":"123456",
"location":"",
"suppressSsn":"N"}]}
This causes both parameters to be seen as null by the controller, however if I send my request like this:
[{"blockOfBus":"XXX",
"clientId":"123456",
"location":"",
"suppressSsn":"N"}]
Then the controller is able to see the list object I am sending (however it obviously returns 500 as the model is not valid)
[FromBody] can only be used once since the request body can only be read once.
Don't apply [FromBody] to more than one parameter per action method. Once the request stream is read by an input formatter, it's no longer available to be read again for binding other [FromBody] parameters.
Reference Model Binding in ASP.NET Core
Create a single model that matches the expected data.
public class DbReport {
public int month { get; set; }
public ClientModel[] ClientModel { get; set; }
}
And update the action accordingly
[HttpPost]
[Route("/jobApi/RunBD")]
public int RunBDReport([FromBody]DbReport report) {
billingDetailCycle objBillDetail = new billingDetailCycle();
if (ModelState.IsValid) {
return objBillDetail.Run(report.ClientModel.ToList(), report.month);
} else {
return 500;
}
}
There can be only one parameter modified with [FromBody] attribute. So you need to either modify your method like this :
[Route("/jobApi/RunBD/{month}")]
public int RunBDReport(int month, [FromBody] IEnumerable<ClientModel> clients)
Then make the request like this :
url :/jobApi/RunBD/7
body :
[{"blockOfBus":"XXX",
"clientId":"123456",
"location":"",
"suppressSsn":"N"}]
Or modify both your method and model like this :
public class BdPayload{
public int Month {get; set;}
public IEnumerable<ClientModel> ClientModel {get;set;}
}
[Route("/jobApi/RunBD")]
public int RunBDReport( [FromBody] BdPayload model)
and then you can use the second request's body.
Try:
{"month":7,
"clients":[{"blockOfBus":"XXX",
"clientId":"123456",
"location":"",
"suppressSsn":"N"}]}
It looks like your ClientModel enumerable is mistitled in the payload
Try changing the route to:
[Route("/jobApi/RunBD/{month}")]
public int RunBDReport([FromUri]int month, [FromBody] IEnumerable<ClientModel> clients)
The payload needs to be passed as an array, like in Jonathan's answer.
There are few simple rules that help you get through these kind of issues when trying to pass data to your Web API endpoint. These are the default rules based on which the parameter binding happens. Based on these rules, you need to be applying the attributes like [FromBody] and [FromUri]
GET method call takes both primitive and complex types as a part of the query string
POST method call takes a primitive type parameter by default in the query string and the complex type needs to be passed as a part of the request body.
PUT and PATCH follow similar default rules as that of POST.
DELETE method's default rules are inline with the GET method.
Here by primitive types, I mean types like int and complex types are the classes that we create.
You can tackle the problem that you're dealing with by applying any of the solutions that others have already mentioned -- like moving your complex type into your request body and passing the primitive type through the query string OR wrapping both the primitive and complex types into a single model and deserialize the request body to the model type (which is done as a part of the parameter binding inherently).

How can I send file to list - .net core web api - postman

I'm trying to send a file from a postman to a web api, method on web api is expecting a list of type which contains doc type, file and folder name.. posted below:
Web api method:
[HttpPost("post-images")]
public async Task<IList<RepositoryItem>> PostAnImages (IList<PostRepoRequest> request)
{
// rest of the code
}
PostRepoRequest class:
public class PostRepoRequest
{
public FileType FileType { get; set; }
public IFormFile File { get; set; }
public string Folder { get; set; }
}
As it's possible to notice I've never received a file, its allways null,
I've tried also setting a header content-type as multipart/form-data but that didn't worked aswell..
What might be the trick here?
Thanks
Cheers
You need to change the request body with dot pattern like this:
Then you need to add [FromForm] attribute to the controller input parameter.
Also note that the variable names in the postman and controller must match.
[HttpPost("post-images")]
public async Task<IList<RepositoryItem>> PostAnImages ([FromForm]IList<PostRepoRequest> repositoryItems)
With these changes, you will be able to get the request correctly:
Try to send file as separated parameter
[HttpPost("post-images")]
public async Task<IList<RepositoryItem>> PostAnImages (IList<PostRepoRequest> request, [FromForm]List<IFormFile> files)
{
// rest of the code
}
and in client (assuming that can be Angular):
let input = new FormData();
for (var i = 0; i < this.filesToUpload.length; i++) {
input.append("files", this.filesToUpload[i]);
}
There is an invisible selectbox, just move your cursor to left and you'll see the selectable area. Then select it as a file.
And add [FromBody] at he beginnig of ypur method parameter like ([FromBody]IList<PostRepoRequest> request)
And last, update the Key to ...[0][file] (you've forgot [])

How to pass two filled class models via an Angular http.post 'body' to the controller?

I am receiving an error message, "Sequence contains no elements" while trying to update a table in SQL from Angular 7 to an AspNet Core controller by passing two model parameters using an "http.post".
I am passing the data from the form to the class models with no problem because I can see the payload data in the browser console. However, when trying to pass the models as parameters in my api service to the controller, all of the parameters in the model are null. I usually don't have an issue when passing one model parm thru, but passing two of them to get to my controller with a [FromBody] doesn't seem to want to work for me.
I tried to wrap the models in curly brackets to pass them, to no avail:
UpdateService(serviceAddress: ServiceAddressModel, contact: ContactModel) {
let reqHeader = new HttpHeaders();
let body = { svc: serviceAddress, cnt: contact };
reqHeader.append('Content-Type', 'application/json');
return this.http.post(this.baseurl + 'api/customermanagement/update-service-address-info', body, { headers: reqHeader });
When I view the request / response in the browser console, I can see the data within the payload, so I know that the data is ready to pass.
My controller is set up as follows:
[Route("update-service-address-info")]
public bool UpdateServiceAddressAccount([FromBody] ServiceAddressEntity svc_id, [FromBody] ContactEntity cnt_id)
{
return serviceAddressService.UpdateServiceAddressAccount(svc_id, cnt_id);
}
Using breakpoints in this call shows null for all values.
If I can properly pass the parameters to my interface, I should be good-to-go. I am sensing that I am not structuring the parameters properly in the http.post body.
Your request body, { svc: serviceAddress, cnt: contact } is received as a json string, e.g. {"svc":{"serviceAddressProperty1":"value",...},"cnt":{"contactProperty1":"value",...}}. The parameters to your action method are bound via the default model binding mechanism (unless you provide your own custom model binding implementation). The default mechanism attempts to create instances by binding from the top level of the json object received with the request. enter code here
In simpler terms, lets assume you class ServiceAddressModel is defined like this:
public class ServiceAddressModel
{
public string Name { get; set; }
public string Property2 { get; set; }
}
the model binder looks for properties with the names "name" and "property2" at the top level of the json tree. If found, these are bound to the Name and Property2 properties of the created instance.
In your case, wrapping your models in a class that can make svc_id and cnt_id the top level properties would work fine. Like this example:
public class MyRequest
{
public ServiceAddressModel svc_id { get; set; }
public ContactEntity cnt_id { get; set; }
}
Then you can declare your action like
[Route("update-service-address-info")]
public bool UpdateServiceAddressAccount([FromBody] MyRequest request)
{
return serviceAddressService.UpdateServiceAddressAccount(request.svc_id, request.cnt_id);
}
Snake casing, camel casing should be allowed by default (you will have to try it, I havent tested that part). That is, if you declare your properties as SvcId and CntId (if you prefer more natural C# naming conventions) it should be able to bind correctly from JSONs with "svc_id" or "cnt_id".
Another option would be to implement custom model binders, but that might be a longer and more complex route.
Hope this helps.
Just try to pass the value like this and see
let body = { svc_id: serviceAddress, cnt_id: contact };

ASP.NET Core 2 MVC App "POCO" Model binding not binding

I've got a pretty basic ASP.NET Core 2 Web App (no razor views, just MvcCore with json responses).
I'm trying to do a pretty simple GET request in Postman and my Controller Action isn't binding the query string parameters to my custom POCO.
here is a sample url which postman tries to hit: http://localhost:51459/orders?Query=iphone&MinimumPrice=22
public class OrderQuery
{
public string Query { get; set; }
public decimal? MinimumPrice { get; set; }
public decimal? MaximumPrice { get; set; }
}
[Route("orders")]
public class OrdersController : ControllerBase
{
[HttpGet("")]
public async Task<ActionResult> GetOrdersAsync(OrderQuery query)
{
// query.Query is null.
// all the properties of query are null.
}
}
Now I can step through the method (i.e. breakpoint is hit), so the route does get 'found' and 'handled'.
Secondly, I've also tried sprinkling [FromQuery] attributes on the properties in the POCO.
Lastly, I've tried changing the case in the request but I thought model biding is case insensitive.
Can anyone see what I'm doing wrong? Is there a particular middleware I should check to see if I've wired up/not wired up?
OMG # me :(
So the variable name in the method signature is query and the (string) querystring key is also query.
The model binding was getting confused with which query do I mean? The property of the OrderQuery class? Or trying to set the string to actual method variable, which it cannot do.
Solution: renamed the entire signature to: public async Task<ActionResult> GetOrdersAsync(OrderQuery orderQuery)
** Note the method signature variable name change **
Doh! :)
TL;DR; Don't name the POCO variable name to a form/querystring/route key.

complex object is empty when use in Web API input

I have a method in Web API that I used the object as input, but when I try to run the API using URI the fields inside the object are Null.
this is my method:
[HttpGet]
[Route("AddUser/{user}")]
public async Task<string> CreateUser([FromUri]AddUser user)
{
//LoansApiTrace.Trace.Verbose(EventId.Start, () => string.Format("{0}: {1}", "AddUser", user.));
string Exception = await Repository.AddUserAsync(user);
return Exception;
}
This is AddUser object:
public class AddUser
{
public string UserEmailAddress { get; set; }
public string PasswordHash { get; set; }
public string Salt { get; set; }
public string RemoteRefNumber { get; set; }
}
and this is the URI:
http://localhost:59509/Adduser/user=test#yahoo.com,pass,salt,remref/
it goes to the method but UserEmailAddress , PasswordHash ,..all 4 are empty.
This is a really a bad practice to pass secret data through URI like you're doing. Then I will not attempt to give a solution for that to work.
The best practice is to pass that kind of data through your request body and use Http POST method :
[HttpPost]
[Route("AddUser/{userId}")]
public async Task<string> CreateUser(string userId, [FromBody]AddUser user)
{
// Find a user by userId
// Then update the user data.
}
you use an URI like this => http://localhost:59509/Adduser/12345 where 12345 is the user id.
you need to make sure that the selected HTTP method is POST
you need to write the data of AddUser into the request body
It also recommanded to use HTTPS when user need to send that type of data.
Consider Using POST If Applicable
While it may not be do-able, you may want to consider adding these fields within a <form> and simply posting them to the server in the body as opposed to using the actual URL itself. Passwords, salts and hashes generally aren't something that you want to get passed around like that.
If You Must Use A GET
Have you tried passing the values in as proper query-string parameters instead?
http://localhost:59509/Adduser/user?UserEmailAddress=test#yahoo.com&PasswordHash=abc&Salt=123&RemoteRefNumber=foo
This should set the following properties based on your current routes:
user = "user"
UserEmailAddress = "test#yahoo.com"
PasswordHash = "abc"
Salt = "123"
RemoteRefNumber = "foo"
MVC has to have some idea of how to bind these properties to those on your class, so unless the names match as expected, it will not know how to map something like "user" to "UserEmailAddress". As mentioned earlier, this isn't ideal and can present all sorts of security issues (so only use something like this on prototype / non-production environments).

Categories