Understanding [HttpPost], [HttpGet] and Complex Actionmethod parameters in MVC - c#

I am very very new to MVC the design-pattern and also the Framework. I am also not extremely well- versed in fundamentals of ASP.NET Forms. However, I do understand the basics of web development and HTTP Post and GET as well.
Now, I have been going through some MVC tutorials and I though I was getting a good hold of how MVC the pattern works and also how "Routing Engine" works. Then suddenly I came across a code which looks like folloing:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return Content("Thanks", "text/html");
}
}
I have few questions looking at it:
My understanding of routing engine was that the control is passed to a particular ActionMethod based on URL and normally URL are basically Controller/ActionMethod/Id kind where paramter to action menthod are rather Primitive types. In this example above what kind of URL would it take to call "
public ActionResult Index(MyViewModel model)?"
Since NyViewModel is a complex type, you can not pass it as part of URL. How can you call it?
Why is this second method adorned with [HttpPost] when the first method does not require any attributes? Are there any guidelines on when to use [Http] attributes and when not?
I think I am missing a big pice in the puzzle and both the questions are interrelated. However, need some help in making sense with the relationship

The [HttpPost] attribute tells the routing engine to send any POST requests to that action method to the one method over the other. This is a type of overloading.
Why is this second method adorned with [HttpPost] when the first method does not require any attributes?
The default for a method is [HttpGet]. Because of that, no attribute is needed.
Are there any guidelines on when to use [Http] attributes and when not?
Ideally, attributes should be on every method, in order to avoid confusion. As you get more familiar with how things are working, you will often take shortcuts (as with everything else), and omit them when you know that they are not necessary.
Since MyViewModel is a complex type, you can not pass it as part of URL. How can you call it?
The data will be turned into the model from the data in the body of the request. This can come either as a JSON object, or as Form data. (There are tricks to get the object initialized from the URL, but they can be a little complicated and advanced.)

Generally, complex objects are passed in the HTTP body with verbs that support it such as POST and PUT. The body content must pass Model Binding validation. That basically means that if it's a POST request with Content-Type: application/json, it must deserialize from JSON into MyViewModel. If the content is XML, it must deserialize as XML.
General convention is to have all the primitive types that can be found in the URL path, query, and headers first, then one complex type from the POST (or PUT) body after that. I believe it's possible to put complex types elsewhere, but then you're getting into type converters and custom attributes which you should probably hold off on if you're a beginner.
Why is this second method adorned with [HttpPost] when the first method does not require any attributes? Are there any guidelines on when to use [Http] attributes and when not?
"[HttpPost]" is telling the routing engine that this method overload is only available via HTTP POST. Attempting to PUT /home/index with a body will fail with 404 Not Found, in this case. The parameter-free version of Index() doesn't require it because it can work with any HTTP verb, including GET, POST, and PUT.

Best Practice - Request handling
It is best practice to only use public methods in your controller which are going to be serviced either with a view or with json. For all public methods in your controller, it is best practice to either mark them with an [HttpGet] or an [HttpPost], or one of the other types which I wont cover as they are more edge case scenario.
These Http attributes restrict the method to only servicing those specific types of requests. While the default is [HttpGet], I have found that not marking [HttpGet] in all scenarios can at times lead to unexpected behavior when there are naming conflicts.
Best Practice - PRG
Post-Redirect-Get is a design pattern which essentially stipulates that any time you are going to be sending a response which came from a POST request, you should redirect to a get in order to send the response. This protects from a number of scenarios, including not posting again if the back button is used.
The redirect usually comes in the form of the [HttpPost] ActionResult using return RedirectToAction("MyHttpGetAction");.
Posting complex models
There are multiple ways which you can send a complex model. The main difference is that if you are using a GET request it is in the url, and if you are using a POST request it is in the request headers. If you use ajax then the difference becomes blurred as you will almost always send it in the body.

Related

Asp.Net Web Api : Handling two get request with same signature

Current situation
[HttpGet]
public HttpResponseMessage CheckProfileStatus(int id)
{
//check profile status from third party
return //profile status and HttpStatus;
}
[HttpGet]
public HttpResponseMessage ProcessTheProfile(int profileId)
{
//check profile status from third party again, if status is "Good", do something
return //someMessage and HttpStatus;
}
Url to call CheckProfileStatus method
myUrl/api/Profile/123
Url to call ProcessTheProfile method
myUrl/api/Profile/?profileId=123
I hope the comments in the code makes the situation clear.
I don't really like the fact that I have two HttpGet methods with same signature in one controller (even though it works).
I don't know if it is best practice.
Question:
Should I extract these two methods in two seperate controllers, is it completely fine to have two HttpGet methods with same signature in one controller or is there better way to handle this situation?
First, this is confusing to any client of the API.
You have 2 GET methods which are virtually identical. In fact most people prefer to add a route covering your first option which basically sets the profileId to the value after the forward slash.
Second, the expectation is that when you issue a GET against an entity with an ID, you get the representation of that entity. This is not what's happening here.
You need to decide which kind of API do you want. A generic one where stuff like this is fine :
myUrl/api/profile/process/123 or process/profile, or whatever else makes sense to the API.
If your API is supposed to be RESTful ( which basically means you will have one method per HTTP verb and nothing more ) then you need to add a new controller for ProcessProfile and then your url can look like this :
myUrl/api/ProcessProfile/123
None of these options is set in stone, they are just that, options.
The comments to the OP thread give some good options as well, which should be considered.

ASP.NET Core Web API multiple actions with the same action verb but different signature

Let's say I have
[HttpPost]
public ActionResult<Object> Login([FromBody]LoginViewModel loginViewModel)
{
....
}
[HttpPost]
public ActionResult Logout()
{
....
}
in the same controller.
And I am getting AmbiguousActionException: Multiple actions matched.
The following actions matched route data and had all constraints
satisfied:
...Login
...Logout
I can simply fix it by using the route attribute but I don't understand why core doesn't bind it. I mean the signature is different. ?
The routing mechanism for web api, both Core and .NET framework works the same way. First they take into account controller name, then they look for proper http method and finally they look if the query string parameters match (or parameters contained withing url specified with Route). So if your 2 actions would differ by parameters taken from url, then there is no ambiguity. Parameters carried by body are not analyzed by routing mechanism, even the fact they are or there are none. This stems from a fact that parameters coming by url are just plain strings - easy to compare against. Whereas body is a json and it is more tricky to analyze it. In fact in Login method case there is no parameter loginViewModel in the request - the whole request body gets serialized to LoginViewModel object.

How to return a StatusCode object outside of an MVC Controller

I'm refactoring an MVC controller into smaller pieces in different classes, and I've come to a part where I'm returning a "return StatusCode(500,"Some message")" item.
It seems as though I'm not able to do this outside of a controller as far as I can tell.
Is this possible, or must I find an alternative way to return a response, and convert it to the appropriate type back in the calling code (The MVC controller)?
A problem I can see myself having, is that the original type of the MVC action is IActionResult, so within the action I've been returning whatever is the appropriate response, sometimes view(,model), sometimes Json() and other times StatusCode(<500>,), i.e. my responses aren't consistent.
Is there a natural way for me to just pickup and move this code into another class to make it work, or will I have to hand craft my own object to represent what I would otherwise and returned in the controller, and if so, what is recommended?
In short, ideally I want to be able to return an action result such as "return View("my view name", myModel) in a class outside of a controller.
How can I achieve this?
It feels quite dirty, but perhaps a single object containing a statuscode, message, object (to serve as the model), view name and a new enum called ActionResultType, with a value for "StatusCode", "Json" and "View", so I can reasonably easily map it back to it's appropriate natural state when the controller receives the response.
Perhaps I'm going a bit far and instead should just focus on getting the main parts of the logic out of the controller, rather than trying to build a more fleshed out response somewhere else?
If possible, I'd love to get each action result to about 10 lines, but unless I can figure out a nice way to return the appropriate response from somewhere else, it will probably be a struggle.
You can substitute those for:
var result = new ObjectResult("Some message");
result.StatusCode = statusCode;
return result;
That's what the StatusCode helper method does anyway.
If you don't need the message, then you can do just:
return new StatusCodeResult(500);
Just add using Microsoft.AspNetCore.Mvc; at the top of the file.
If you need shared code between controllers, you can setup a base controller to hold the shared code and inherit that base controller on any other controller that needs access.
ActionResult holds all of the things you want. All the things you want in your custom object all exist as subclasses of ActionResult. JsonResult is a subclass of ActionResult. There are several subclasses for each error code. I believe HttpNotFoundResult is another one.
I would suggest keeping most things ActionResult/Redirect/View/StatusCode related inside your controllers. That is where it is typically located.

Syntactic sugar in Web Api - how to omit [FromBody]

Is it possible to omit writing [FromBody]?
[HttpPost]
public string SomeMethod([FromBody]SomeModel model)
{
return "OK";
}
Should I apply some global attribute? How to achieve this?
The model binding attributes in MVC 5 specify a "BindingSource" for each parameter on an action and can specify for each property on a controller, too. You can see specifically the code where it picks it up for the FromBody attribute in the BodyModelBinder
Let me say first that you should beware that as of beta6 (and I think some point in beta5), having multiple parameters/properties with BindingSource.Body is not allowed, and you will not be able to use your actions if you have multiple parameters left as the default. This might actually be desirable if you want to annotate more; my guess is that you do not.
You can see in the DefaultApiDescriptionProvider where the default source is set; unfortunately, there is no hook at this point to tie into, and the method itself is private and not virtual; you'll need to re-implement the entire class.
Once you have, though, it's rather easy to register it in your ConfigureServices to use it instead:
services.TryAdd(ServiceDescriptor.Transient<IApiDescriptionProvider, YourApiDescriptionProvider>());
You can see the default being registered in the source, too.
probably more than one way to achieve this.. here is one way: a custom ActionValueBinder..
Write a custom class to extend from DefaultActionValueBinder.
public class FromBodyParameterBindingActionValueBinder : DefaultActionValueBinder
Override the GetParameterBinding method.
protected override HttpParameterBinding
GetParameterBinding(HttpParameterDescriptor parameter)
{
return parameter.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Post)
|| parameter.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Put) ?
parameter.BindWithAttribute(new FromBodyAttribute()) :
base.GetParameterBinding(parameter);
}
basically what we're saying is that if the Web API system gets a POST or PUT call, make FromBody as the default parameter binding methodology.
Once we have our custom binder, register it globally.
GlobalConfiguration.Services.Replace(typeof(IActionValueBinder), new FromBodyParameterBindingActionValueBinder());
what this does is that for every POST/PUT request, Web API will try to create the model from the request Body. you don't need to provide the [FromBody] attribute explicitly.
having said that, you need to understand the implications of what you're requesting..
Web API has some default rules on model binding. (can be changed)
primitive action parameters are first, looked into the query string
then request body.
complex action parameters are first, looked into the request body.
then query string.
also, web api can only read the first [FromBody] action parameter.. subsequent parameters cannot be read from the body.
the reason i am enumerating the above rules, is so that you're aware of the behavior when you expect some model to be created, but web api does not seem to create it from the body.
for me, explicitly providing [FromBody] is a lot more readable and predictable.. but if you're in full control of your APIs in terms of Http Verbs and expect all requests to be via the Body, it is a perfectly valid thing to do.

Web API - REST help and clarification

I'm writing an API using WebAPI, and whilst I'm designing this from scratch, I'm trying to make it RESTful. In the past when doing web service work, I've always used RPC patterns (ASMX, SOAP, etc) - eg. using an arbitrary method name that matches what I want it to do. To me this seems much more intuitive and explicit than REST, but given WebApi seems to be more RESTful by nature (I know you can change the route so it's not), I've decided to try and make it RESTful.
I understand (I think!) the basics - eg. POST for create, PUT for update when specifying an ID, GET to get, DELETE to delete, etc, etc.
My confusion is how to deal with returning collections vs a single object. For example, say that I've got an API controller called UsersController. As far as I gather, I'd have the following URLs:
GET: /api/users (lists all users)
GET: /api/users/1 (lists details about user with ID 1)
POST: /api/users (creates a new user with details sent in the POST data)
PUT: /api/users/1 (updates the existing user with ID 1 with details sent in the POST data)
DELETE: /api/users/1 (deletes the existing user with ID 1)
For the first URL above, I need to also send various filter/search criteria. Does this criteria just get passed through as query string parameters? Or should I be using a completely different controller for this "search" functionality? Eg. UserSearchController? If so, should that be a POST in this case or still a GET? Actually as I write this, I'm wondering whether a separate controller makes sense anyway, as I'll probably want more detail returned in the GET for a single user than in the search results. Does it make it not RESTful if the same controller does return different data for a single object GET vs a GET returning a collection?
For the first URL above, I need to also send various filter/search criteria. Does this criteria just get passed through as query string parameters?
It definitely makes sense to use query string for specifying filter/search parameters.
should I be using a completely different controller for this "search" functionality? Eg. UserSearchController?
You should not do that. There are couple of reasons that i see here:
There will be almost identical functionality in GET: /api/users
You can easily implement GET: /api/users, GET: /api/users?filter=...&sort=... and GET: /api/users/1 in one method:
//If you are using EF it could look like
//filter and sort arguments could be added here as well
public HttpResponseMessage Get(int? id)
{
if(id.HasValue)
{
return Request.CreateResponse(
HttpStatusCode.OK,
Context.Users.SingleOrDefault<Users>(u => u.Id == id));
}
var users = Context.Users.Select(apply filter).OrderBy(apply sort).ToList();
return Request.CreateResponse(HttpStatusCode.OK, users);
}
You can take a look at OData - it may help you with the implementation.
Spreading this logic between different controllers compromises single responsibility principle - your users controller should handle all the logic related to users and only this logic
If so, should that be a POST in this case or still a GET?
If you want to make your API RESTful you should be using GET. BUT you should be aware, that returning an array of JSON objects through GET could be potentially vulnerable to JSON hijacking. One of the simplest solutions to this exploit is allowing to get JSON arrays through POST only (there are other solutions as well).
I'll probably want more detail returned in the GET for a single user than in the search results. Does it make it not RESTful if the same controller does return different data for a single object GET vs a GET returning a collection?
This is totally fine to return more details for a single object than for a collection. It doesn't affect RESTfulness of your API in any way.
COMMENT
You wrote:
PUT for update when specifying an ID
Actually it is not entirely correct:
PUT should be used for complete replacement of the entire entity
PATCH should be used to perform a partial update.
If you want to get passed through criteria as query string parameters to URI, you can do that using Attribute Routing into WebApi. I think attribute-routing-in-web-api helps to you.
Yes, I would pass filter parameters as query string options. The 'restfulness' of your application is not dependent on controller structure, so you can follow a structure that best suits your application

Categories

Resources