Get request not sending parameter via url to the api controller - asp.net-core-webapi

I have this simple API controller in a NetCore 2.2 web game that is supposed to return a list of monsters based on the dungeonID(Guid).
So I use the URL, passing in the guid of the dungeonID, to that controller like this:
https://localhost:44361/MonsterList/GetMonsters/2f14c8gf-2e7e-466a-bcbg-f4440e92b3dg
But when I step through the code, I just see all zeroes for the dungeonID:
public async Task<JsonResult> GetMonsters(Guid dungeonID)
{
var monsters = await _context.MonsterList.Where(c => c.DungeonID == (dungeonID)).ToListAsync();
return Json(monsters);
}
This returns nothing because, for reasons I don't know, dungeonID is always all zeroes.
But this does work if I hard-code in the dungeonID:
https://localhost:44361/MonsterList/GetMonsters
public async Task<JsonResult> GetMonsters()
{
var monsters = await _context.MonsterList.Where(c => c.DungeonID == Guid.Parse("2f14c8gf-2e7e-466a-bcbg-f4440e92b3dg")).ToListAsync();
return Json(monsters);
}
I've seen lots of posts similiar to mine, like these:
asp.net webapi 2 post parameter is always null
Post parameter is always null
But nothing seems to work.
How do I add the ability to pass in a Guid parameter?
Thanks!

One point is 2f14c8gf-2e7e-466a-bcbg-f4440e92b3dg is not a GUID/UUID . Just try with a correct GUID :
https://localhost:44384/api/values/GetMonsters/2e6ae748-10c2-4e23-84c3-9d3db7c09631

Related

How to Overload HttpPost Web API Method Based Json Datatype Properties

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

Angular2 HTTP Post ASP.NET MVC Web API

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.

ASP MVC2 Calling different views

Hello fellow programmers!
I am currently stuck in a school assignment. I am working with ASP.NET, MVC2 (I know, it's old :( ) Here is the situation: I have a controller who will decide with an if statement what view to return. For example:
Here is a method within my controller:
public ActionResult Verification()
{
String lastName = Request["lastName"];
String name = Request["name"];
String dob = Request["dob"];
String phone = Request["phone"];
String selection = Request["selection"];
Verification verifying = new Verification(lastName, name, dob, phone, selection);
String other = verifying.returnIfGood();
if (!(other == ""))
{
return RedirectToAction("probleme1");
}
else
{
return RedirectToAction("probleme2");
}
}
RedirectToAction seem to point directly at a view, but I also need to pass the view a string to work with. I also noticed if I call a method within my controller, no view gets returned, which I am a little confused about. I am trying to stay away from ajax and jquery since I haven't seen those yet. Is there any way to do this using only c#?
Here you need to ajax call with query string like
Controller/action?viewname='abc'
Then
Get on actionresult method
Then redirect to action on the basis of viewName.
Or
Redirect(controller/actionName)
Your question is quite confusing as to what you're trying to accomplish, and some of the comments seem to indicate a misunderstanding of what is actually going on. Given that, I'm going to try to address your question but may be missing the mark.
RedirectToAction does not "point directly at a view". It causes an HTTP 302 response to be sent to the browser with a new URL which the browser then turns around and requests with a new GET operation.
Given that you haven't provided the code for the probleme1 and probleme2 action methods I have no idea what other logic might live there. If all they do is return a view you could rewrite your Verification method to return the view instead and include the "string to work with" as a model.
public ActionResult Verification()
{
// code omitted for brevity...
if (!(other == ""))
{
return View("probleme1", "some string");
}
else
{
return View("probleme2", "some other string");
}
}
If the logic in probleme1 and probleme2 is more complex and you really need to preserve the RedirectToAction approach you could set a value in TempData in your Verification method and then pass that value along in the other methods.
public ActionResult Verification()
{
// code omitted for brevity...
if (!(other == ""))
{
TempData.ModelString = "some string";
return RedirectToAction("probleme1");
}
else
{
TempData.ModelString = "some other string";
return RedirectToAction("probleme2");
}
}
public ActionResult probleme1()
{
return View(<view name>, TempData.ModelString);
}
In this case you need to use TempData and not ViewData as the former will survive the redirect while the latter will not. Put another way, TempData is for holding information between controller actions during a redirect, ViewData is for sending information from the controller to the view.

MVC RedirectToAction() any way to pass object to the target action?

The populated catList is always Count=0 when the code jumps to CreateProduct() so I take it it does not get delivered.
Considering RouteValueDictionary does not do this ? Any other way?
public ActionResult GetCats(int CatID)
{
List<Category> catList = new List<Category>();
if (CatID >= 0 )
{
catList = context.Categories.Where(x => x.PCATID == CatID).ToList();
}
return RedirectToAction("CreateProduct", "Admin", new { catList });
}
public ActionResult CreateProduct(List<Category> catList) { }
You are actually trying to use controllers to do data access.
Move the "GetCats" data retrieval into your business layer (Service object, Repository, whatever suits you).
Then, CreateProduct will need to be there twice (2 signatures). One with no parameters in which you are going to call "GetCats" from your business layer and send it to the view.
The other implementation is going to be the flagged with the HttpPostAttribute and will contain in parameters all the necessary information to create a cat.
That's all. Simple and easy.
You could place any items that you need in TempData then call RedirectToAction.
RedirectToAction simply returns a "302" code to the browser with the URL to redirect to. When this happens your browser performs a GET with that URL.
Your RouteValues need to be simple. You can't really pass complex object or collections using a route value.
if you don't care about the browser url changing you could just
return CreateProduct(catList)

mvccontrib test helper and verifying http post routes and parameters

In my Asp.net MVC app, I have two methods on a controller, one for when the user first arrives on the view and then one when they submit the form on said view.
public ActionResult Foo() {}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Foo(string id, Account accountToFoo) {}
In the second action, there's a custom model binder that's assembling the account object that I'm acting on, though that's really not important. This all works fine in testing locally on a server.
We try to be pretty good about writing unit tests to test all our different views are properly getting routed to, including those that are HTTP POST. To do so, we've been using mvccontrib's test helper.
Testing gets have been super simple
"~/account/foo/myusername".
Route().
ShouldMapTo<AccountController>(c => c.Foo("myusername"));
My question is in testing POST routes, how do I write the lambda that I would use to verify the post is receiving accurate values, similar to the GET test above?
For a POST, it looks something like:
"~/account/foo".
WithMethod(HttpVerbs.Post).
ShouldMapTo<AccountController>(a => something_something);
It's the something_something portion of my lambda that I'm having trouble with. Using arbitrary values doesn't work ("a => a.Foo(0, new Account()"). How would I specify the expected values as part of the test?
EDIT I was hoping there was something akin to the way Moq has lambdas for statements such as foo.Setup(s => s.Foo(It.IsAny(), It.Is(i => i > 32)) and so on. Even I have to explicitly supply the values, that's workable--I just can't seem to grok the desired structure to pass those explicit values.
Here's an example. Assuming you have the following action:
public AccountController : Controller
{
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Foo(string id)
{
return View();
}
}
And the following route registered:
RouteTable.Routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "home", action = "index", id = "" }
);
You could test it like this:
var routeData = "~/account/foo".WithMethod(HttpVerbs.Post);
routeData.Values["id"] = "123";
routeData.ShouldMapTo<AccountController>(c => c.Foo("123"));
Some tweaking might be necessary to include the second Account argument you have.
Using the mvccontrib helper syntax:
"~/account/foo".WithMethod(HttpVerbs.Post).ShouldMapTo<AccountController>(a => a.foo(null));
You pass null as the Foo(string id, Account accountToFoo) method is never executed as part of the routing test.

Resources