I am asked to implement a REST Web API to a specific route, where either of two different Json Datatypes may be posted.
This results in the following exception being thrown:
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints.
Is there an Attribute that can be placed on the Web Methods, referencing Properties of the Json payloads so as to disambiguate the two possible Datatypes?
This was covered here but I'll add a little bit.
It's not good API design to do that and goes against Swagger / OpenAPI specifications to do what you're asking.
The only way to do this with the same HTTP method (POST in your case) is to have one action that takes in both models. Check which one isn't null to then route to the correct method to handle that logic.
If you can get away with using a different HTTP verb you could technically do that and have two separate action methods (like POST and PUT), but you wouldn't be using them "correctly" and based on your question and need, I doubt you can do that anyway.
You can read the request body as a string and then try to decide which type to deserialize in:
[HttpPost]
[Route("api/mypath")]
public async Task<IActionResult> MyMethod()
{
request.Body.Position = 0;
var reader = new StreamReader(request.Body, Encoding.UTF8);
var body = await reader.ReadToEndAsync();
if(body.Contains("A))
{
var A = JsonConvert.DeserializeObject<A>(body);
}
else{
var B = JsonConvert.DeserializeObject<B>(body);
}
}
And add a middleware to enable request buffering:
app.Use(next => context => {
context.Request.EnableBuffering();
return next(context);
});
You can read more about it here
I am using Laravel 5.4. I want to use query string like below:
tempsite.com/lessons?id=23
For getting this how routes are to be modified. It is possible to give route in the following way.
Route::get('lessons/id={id}', ['as' => 'lessons.index', 'uses' => 'Lessons\LessonController#index']);
But adding '?' is not getting for me. Please help us to provide a solution as early as possible.
If you are using resourceful controllers, your routes are all handled for you so you would simply put
Route::resource('lessons', 'Lessons\LessonController');
You can then use route model binding to bind the model instance which matches that particular ID.
Route::model('lesson', Lesson::class);
This would be done in your RouteServiceProvider.
I would also suggest having a good read of the following documentation on the laravel website https://laravel.com/docs/5.4/routing. It provides really good insight in to how routes work and how they should be structured.
Instead of tempsite.com/lessons?id=23
Pass it like this tempsite.com/lessons/23
and in the route
Route::get('lessons/{id}', ['as' => 'lessons.index', 'uses' => 'Lessons\LessonController#index']);
to get the id in your controller, write your function like this
public function index($id)
{
//do anything with $id from here
}
There is no need to define query string parameters in your routes. You can return query string parameters in your controller like so:
URL example: tempsite.com/lessons?id=23
public function lessons(Request $request)
{
$request->get('id'); // Using injection
Request::get('id'); // Using the request facade
request()->get('id'); // Using the helper function
}
You could even validate the parameter:
public function lessons(Request $request)
{
$this->validate($request, ['id' => 'required|integer']);
}
Note: If you want to make the URL not accessible if the ID is omitted, see #DarkseidNG answer.
I was able to inform laravel to accept query stringed requests on my route by affixing the url with a forward slash, like so
// web.php
Route::get('/path/', "Controller#action");
With the above, mysite/path?foo=bar&name=john does not throw 404 errors.
How do you properly create a Web API POST of complex object or multiple parameters using Angular2?
I have a service component in Angular2 as seen below:
public signin(inputEmail: string, inputPassword: string): Observable<Response> {
return this.http.post('/api/account/signin', JSON.stringify({ Email: inputEmail, Password: inputPassword}), this.options);
}
The targeted web api is seen below:
[HttpPost]
[Route("signin")]
public async Task<IActionResult> Signin(string email, string password)
{
....
}
This does not work because I need to convert the parameters of the web api into a single POCO class entity with Email and Password properties and put the [FromBody] attribute: Signin([FromBody] Credential credential)
Without using [FromURI] (POST requests with query strings?), how can I make POSTs of multiple parameters or complex objects without converting these parameters into a single POCO class?
Because what if I have numerous Web API POST actions with parameters like (string sensitiveInfo1, string name, int sensitiveInfo2) or (ClassifiedInfo info, string sensitiveInfo1, string sensitiveInfo2), do I need to convert them all to POCO classes and always use [FromBody]?
PS.
I was using RestangularJS before and it can posts anything (mulitple primitive objects and complex objects) without my Web API actions having [FromBody] attributes. Will about to investigate how RestangularJS do it.
Without using [FromURI] (POST requests with query strings?), how can I make POSTs of multiple parameters or complex objects without converting these parameters into a single POCO class?
I know its not what you want to hear but out of the box this is not possible. It is not a limitation of the browser code that is making the request. This means it does not matter if you are using Angular, JQuery, straight JavaScript, or even RestangularJS. This is a limitation (I use that word loosely as I am sure this is by design) of Web API (any version). Here is the documentation on this design: Parameter Binding in ASP.NET Web API by Mike Wasson.
At most one parameter is allowed to read from the message body. So this will not work:
// Caution: Will not work!
public HttpResponseMessage Post([FromBody] int id, [FromBody] string name) { ... }
So the question becomes, what are your options?
Create a model
This is the thing you were trying to avoid but I list it first because this is how Web API was intended to behave. I have not yet heard a compelling reason not to do this. This approach allows you to extend your model easily without having to change the method signature. It also allows for model validation on the model itself. Personally I really like this approach.
public class SignInModel{
public string Email {get;set;}
public string Password {get;set;}
}
[HttpPost]
[Route("signin")]
public async Task<IActionResult> Signin(SignInModel signInModel)
{
// ....
}
I did not repeat your existing JavaScript code because what you have works as is with the above web api code
URL
Again, what you were trying to avoid. This does make what you want possible with the limitation that you have to pass these parameters using the Query string on the URL. The JavaScript would change but the signature you had on the Web API method would not.
public signin(inputEmail: string, inputPassword: string): Observable<Response> {
return this.http.post('/api/account/signin/?email=inputEmail&password=inputPassword', null, this.options);
}
I did not repeat your existing Web API code because what you have works as is with the above web JavaScript code (by default FromUri is assumed I believe)
Custom Model Binder
See Passing multiple POST parameters to Web API Controller Methods by Rick Strahl. This option allows you to create a custom model binder that could do what you are asking. It is a whole bunch of extra code though for, IMHO, not much benefit. Maybe there are situations where it would be useful although I really cannot think of any off the top of my head.
Dynamic
Finally you could also pass in a dynamic object as the parameter of your Web API. This is essentially the same as receiving the JSON as a string and making your Controller code responsible for the deserialization of content. Again, I believe that this would make your code worse in most situations as you have to implement custom validation and type checks. This answer was proposed previously on SO by Bes Ley. Again, maybe there are situations where it would be useful although I really cannot think of any off the top of my head.
If you call Web API 2.2 post method from Angular 2 type script, dont forget to add following header content and parameter object.
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
var params = new URLSearchParams();
params.set('userid', '102');
params.set('username', 'foo');
return this._http.post('http://localhost:6579/api/PostUser', params.toString(), { headers: headers }).map(res => res.json());
Perhaps you should post with options:
{
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
})
}
and encode data like
jQuery.param({user:'bla', password: 'bla'});
WebAPI does not provide this out of the box. If you try to get understanding of web API bindings, you might be able to figure out why.
I think this article might help.
The generic rules are:
– simple, string-convertible parameters (value types, strings, Guids, DateTimes and so on) are by default read from URI
– complex types are by default read from the body
– collections of simple parameters are by default read from the body too
– you cannot compose a single model based on input from both URI and request body, it has to be one or the other
I have fixed the issue of Angular2 HTTP Post ASP.NET MVC Web API
let headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8');
let params: URLSearchParams = new URLSearchParams();
params.set('value', '2');
let options = new RequestOptions({
headers: headers//,
//search: params
});
let content = new URLSearchParams();
content.set('StudentName', 'Inderjit Singh';
content.set('Mobile', '+919041165398');
content.set('Nationality', 'Indian');
content.set('AdmissionNo', '6');
content.set('SectionCode', '1');
content.set('Gender', 'Male');
content.set('RegNo', '18585');
content.set('ClassCode', '1');
this.http.post('YOUR_URL', content.toString(), { headers: headers }).map((res: Response) => { console.log("data is==>" + res.text()); }).subscribe();
WebApi will be able to deserialize your Credential object provided the JSON object has the same field names (I am not sure about case so you may be right here). You seem to be missing the headers from the post call in your Angular2 component.
Can you check the Content-Type using Chrome Debugger or Fiddler? It should be application/json.
Try this, passing a complex class object into a single data parameter.
var SearchQuery = function () {
this.Alphabet = null;
this.Search = false;
this.Keyword = null;
this.RegionList = null;
};
var para = new SearchQuery();
{ data: JSON.stringify(para) } - Post Data
you can receive it using a JObject in your API controller and deserialize it as according to your classes.
Hi I have a common function to get a Client Name dynamicaly. Now I want to call that function to every view (Twig). I am following it like this:
//My controler
public function getSchoolNameAction(){
$session = new Session();
$dm = $this->getDocumentManager();
$commonFunction = new CommonFunctions();
return $schoolName= $commonFunction->schoolName($dm,$session);
}
//My View (search.html.twig)
{% render controller('EduAccountBundle:Ledger:getSchoolName') %}
But its showing an error that :
he controller must return a response (null given). I need to make it for every view. Please guide me how to fix this
Don't define a controller as a service, controllers should be used only to take a request and to produce a response (you're just returning a value, that isn't acceptable for Symfony logic)
Unless you want to return a rendered template (or produce a valid response, such like a json response) to put where you're calling the common action (you could do that of course), I will recommend to write a custom twig extension
My Symfony2 API uses FOSRestBundle and JMSSerializer, with property annotations, but there are many times when I don't want to expose every property. I understand JMS has exclusion groups, but I can't figure out how to include those in my Symfony controllers. There should be a way to use PHP on a dynamic basis but that seems to be missing from the documentation too.
If you use View class like in this example, you can set serialization context with setSerializationContext method
public function getUsersAction()
{
$data = // get data, in this case list of users.
$view = $this->view($data, 200)
->setSerializationContext(SerializationContext::create()->setGroups(array('list')))
;
return $this->handleView($view);
}
Since FOSRest 2.0 version you must use this:
$view = $this->view($response, $code);
$view->setContext($view->getContext()->setGroups(['get_client']));