Lets say I have Order resource, and an action public ActionResult UpdateOrder(Order order).
I am trying to make REST api. And I want to push this order to next status.
For this purpose I have a method named ChangeOrderStatus(string newStatus, int orderId) on backend which does everything about status change (send some mails, do logging, insert some things into DB. etc...) and that method will be called from controller action. The point is that changing order status is complex operation, not just replace old filed value with new one.
But my question is should I write new controller action (route) for status change, for instance public ActionResult UpdateOrderStatus(string newStatus, int orderId) and call ChangeOrderStatus there or I should put everything in UpdateOrder(Order order) method? For instance, part of the UpdateOrder(Order order) method in that case would be:
if (order.Status != oldOrder.Status) {
ChangeOrderStatus(order.Status, order.Id);
}
I would do it with first approach, but some people tell me that the second approach is better, because there should be a single entry point for resource update, so my status update or any other update related to order resource should be in UpdateOrder method... So I am not sure what is the right approach...
If you only want to update an OrderStatus I would use a resource like /orders/{id}/status. The backing controller should be capable of handling all steps neccessary. It you would alter the items of an order, this would be /orders/{id}/items or /orders/{id}/items/{id} for a single one.
To update the Order itself this would got to /orders/{id}.
Related
I am trying to build sth pretty simple, but I try to do it the correct way. But I struggle to figure out what is best.
I have a process chain where the user has to fill in some fields in different forms. Sometimes it depends from the user inputs which form the user is shown next.
[HttpGet]
public IActionResult Form1(Form1Vm f1vm)
{
return View(f1vm);
}
[HttpPost]
[ActionName("Form1")]
public IActionResult Form1Post(Form1Vm f1vm)
{
//process the data etc
//prepare the new viewmodel for the next form view (f2vm)
//Option1:
return View("Form2", f2vm);
//Option2:
return RedirectToAction("Form2", f2vm);
//for Option 2 I would need an additional HttpGet Action Method in which I
//would have to call Modelstate.Clear(); in order to not have the
//immediate validation errors on page load
//also all the properties of my viewmodel are passed as get parameters
//what looks pretty nasty for me
}
//More form views action methods should be added here...:
What is the better way? As mentioned in my comments above I have quite a big disadvantage for using the RedirectToAction option. However if I use the direct View(); call, I don't take care on https://en.wikipedia.org/wiki/Post/Redirect/Get and the user cannot simply refresh a page without getting a warning that his form is submitted once again.
Do I miss another way or don't see something obvious?
Edit: I just thought about a 3rd way, which I have seen quite often: Not transfering the whole VM to a HttpGet method but only the ID. I'd then have to load all the data stored previously directly from the db, map it again to my new VM and then call the View(); with this VM. Right now I think this is the "best" solution, however I feel like it is pretty laborious...
As per the dicussions, I would suggest using depending on your preference :
1) Save to db at the end of each form post and as you suggested use the I'd to redirect to a GET.
2) Depending on the the number of form pages and your requirements, retrieving values that a form needs on the get would be standard practice. This ensures that if a user drops off a form at any stage you can then start them off where they left off.
3) I wouldn't setup the viewmodel for the next form in the post of the previous. Generally as part of the single responsibility principle you want to ensure that your methods have only one reason to change.
4) PostRedirectGet pattern should be implemented with this to ensure data is not saved multiple times if a user refreshes after a post.
I'm having a problem on what is the best approach to design my service layer and use them in my controller. Here is my concern.
Currently I'm using this to delete categories
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Delete(List<Guid> ids)
{
if(ids == null || ids.Count == 0)
return RedirectToAction("List");
_categoryService.DeleteCategories(_categoryService.GetCategoryByIds(ids));
_categoryService.SaveChanges();
return RedirectToAction("List");
}
my concern is should I just pass ids to DeleteCategories then call the GetCategoryByIds inside the DeleteCategories. And If I'm only going to delete 1 Category, is it better to add another method like DeleteCategory then in the controller check the length of the ids and if it is only 1, use DeleteCategory instead,
my concern is should I just pass ids to DeleteCategories then call the GetCategoryByIds inside the DeleteCategories.
Just pass the ID's to the DeleteCategories method. I wouldn't even bother calling GetCategoryByIds inside of it. There's no need to query the database for all the rest of the category information if you're just planning on deleting it.
And If I'm only going to delete 1 Category, is it better to add another method like DeleteCategory then in the controller check the length of the ids and if it is only 1, use DeleteCategory instead
I wouldn't bother with creating another method. You could just pass a list with one value in it. There's nothing a DeleteCategory method could do that you can't already do with DeleteCategories.
It all depends on your business logic
If the user has the option to select more than one category and delete them at once, then it makes sense to have the delete method accepts a list of ID and delete them all, if the user can delete only one category, then it should be DeleteById(int categoryId).
As for calling GetCategoryByIds, still depends on your logic, if you have some kind of authorization, then you must retrieve the category first, make sure the user has an access to delete the provided category and proceed if everything is fine.
A note about your code, your service should have the logic for data manipulation encapsulated inside, you should not expose the SaveChanges method and give the control to the controller to call it, or at least implement the Unit of Work pattern if you need to implement some kind of transaction.
I'm writing in C# for ASP.NET Web API 2. What I want is a catch-all method that will execute for every single request that comes to my Web API.
If the method returns null, then the original routing should continue, seeking out the correct method. However, if the method returns, say, an HTTPResponseMessage, the server should return that response and not proceed on to normal routing.
The use case would be the ability to handle various scenarios that may impact the entire API. For example: ban a single IP address, block (or whitelist) certain user agents, deal with API call counting (e.g. someone can only make X requests to any API method in Y minutes).
The only way I can imagine to do this right now is to literally include a method call in each and every new method I write for my API. For example,
[HttpGet]
public HttpResponseMessage myNewMethod()
{
// I want to avoid having to do this in every single method.
var check = methodThatEitherReturnsResponseOrNull(Request);
if (check != null) return (HttpResponseMessage)check;
// The method returned null so we go ahead with normal processing.
...
}
Is there some way to accomplish this in routing?
This is what Action Filters are for. These are Attributes that you can place either globally, at the class (Controller), or at the method (Action) levels. These attributes can do preprocessing where you execute some code before your action executes or post processing where you execute code after the action executes.
When using pre processing you have the option to return a result to the caller and not have your method (action) be fired at all. This is good for model validation, authorization checks, etc.
To register a filter globally edit the WebApiConfig.cs file.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new YourFilterAttribute()); // add record
// rest of code
}
}
To create a custom attribute inherit from System.Web.Http.Filters.ActionFilterAttribute or you can implement interface System.Web.Http.Filters.IActionFilter or you can implement IAuthorizationFilter/AuthorizationFilterAttribute if you specifically want to allow/deny a request.
It also sounds like you want to create multiple attributes, one for each role like IP filtering or count calling etc. That way it would be more modular instead of one enormous authorization filter.
There are many tutorials out there like this one (chosen at random in my Google search results). I am not going to post code because you did not do so either so I would just be guessing as to what you wanted to do.
I'm just starting out learning ASP.NET MVC 3. I've been going through the Music store sample on the ASP.NET website, as well as starting to develop my own site, but I'm having some trouble understanding how I should setup my controller action methods.
On my site each user that is logged in will be able to perform standard CRUD operations on their Projects. I've set this up similar to the Music Store sample with the following action methods on a ProjectController. So far this makes sense to me.
ActionMethod View
ProjectController.Index() Lists the active users's projects
ProjectController.Details(int id) Shows details for project 123
ProjectController.Create() Shows a form to edit a new project
ProjectController.Create(FormCollection col) Adds a new project with the form contents
ProjectController.Edit() Shows a form to edit a new project
ProjectController.Edit(int id, FormCollection col) Adds a new project with the form contents
ProjectController.Deiete(int id) Shows a delete confirmation form
ProjectController.Delete(int id, FormCollection col) Deletes a project with the provided id.
In addition, users will be able to add Items to each project. Items can not exist on their own and must be associated to a project when being created. What I'm having trouble understanding is how to pass along the reference to the project an Item should be created in. For example in my item controller I have a pair of Create() action methods similar to the controller above.
ItemController.Create() Shows a form to create a new item
ItemController.Create(FormCollection col) Creates a new item with the details from the form.
Yet I don't understand how the first Create() method passes a reference to the project which the new Item should be created in since the View() helper method can only accept one object parameter. Should I just add a reference to a project to a property of the ViewBag? I'm new to dynamic types as well and the ViewBag just seems magic to me at the point. So I'm a little hesitant to use it. I've also always thought strongly typed design is better. So should I create a separate "NewItemData" model object that contains a reference to a new Item as well as the project it is being added to?
Once the form knows which project it is adding an item to how should it pass this information back when submitted? Should there be a hidden "ProjectID" field in the form? Or should the form POST back to a URL with the project id in the query string?
www.mysite.com/Item/Create?ProjectID=1234
Finally, I also want to be able to list the items that are added to each project. Should this be part of the ItemController or the ProjectController. For simplicities sake I'm sticking with the default Controller\Action[ID] URL routing. A few of my ideas are listed below. I'm leaning towards the last option, but would really like to hear what others with more experience with this stuff think.
Action Method URL
ItemController.Index(int ProjectID) \Item?ProjectID=1234
ItemController.List(int id) \Item\List\1234
ProjectController.Items(int id) \Project\Items\1234
To answer your last question, it depends. Do Items in your model exist independently of a project? If the answer is no, then I would tend to do
ProjectController.AddItem(int id)
ProjectController.Items(int id)
where id represents the projectID.
The name of the parameters you use in the action signature directly correspond to values from the routedata and request values. {controller}/{action}/{id} is a pattern that uses the braced names as keys in the route dictionary. If you wanted you could change the routes to be {controller}/{action}/{projectid} for that action and your method signature could be the (int projectid) signature.
I don't recommend you do this just to get awesome signatures. Use comments instead if you think people will get confused.
Without changing the route pattern, if you would prefer your urls to be /project/items?projectid=3 than /project/items/3 then the action would be this:
ProjectController.Items(int projectId)
I like the prettier urls, so i'd be more apt to use the id version. That being said, if Items do not exist independently of the Project object, I would be be more likely to do this. However, if you are going to have a TON of different actions that can be performed on an Item, it would make sense to separate them into ItemController and ProjectController.
It comes down, to a large extent, about what makes sense for your application and how many actions you think a controller should have on it.
In this case you'd create a hidden field with the name=projectId and then in your Create controller have an action method.
[HttpPost]
public ActionResult Create( int projectId, FormCollection postData )
Usually you'd also use a strongly typed viewmodel so instead the FormCollection parameter use:
[HttpPost]
public ActionResult Create( int projectId, Item or ItemViewModel postData )
And as long as the name attributes match the properties in Item or ItemViewModel MVC's ModelBinder will take care of hydrating those values.
i wish to have a simple Action in my controller that accepts a few optional values and some integer values.
this is my route i wish to have:
HTTP.POST
/review/create
and this is the Action method i would like...
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult Create(int userId,
int addressId,
byte baseScore,
byte reviewType,
string subject,
string description)
{ ... }
I'm under the uneducated impression that all of those arguments above will be populated by the forms collection values ... but it's not happening. Also, I have no idea how I would write a route, to handle those ... because those values are form post data....
here's my global.asax....
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Api - Search methods.
routes.MapRoute(
"Search Methods",
"{controller}/{action}"
);
In fact, the action method is never called because it doesn't seem to find it :(
But, if create and action without any of those arguments, then it finds it ?????????
How would you write a route and action method to accept some require and some optional arguments, for the route /review/create ?
As far as i can see you may rewrite your controller action like this:
public ActionResult Create(int foo, int bar, byte blah, string name, int? xxx) {
// code here
}
The ModelBinder will then ensure that foo,bar and blah are set. Name and xxx may be null. I can't test it a the moment, but i think return type of the action should be ActionResult.
If you are POST'ing a form, just make sure that the elements in your form (textboxes, checkboxes, textarea, etc) have id's that match the parameters in your method. As an alternative you can pass a FormCollection to the method, and do myFormCollection["foo"] to get a string representation of the value (which can then be parsed to an int).
From my experience, you are missing a number of key elements and concepts with this question.
First and foremost, I don't believe you can execute a POST without a form. The form has to contain the controls from which you pull the values that get passed to the controller method. If the goal is to simply unit test your POST controller method, then just call the method directly in your test, which it appears that you're doing, based on one of your comments. If you involve the view, then you're doing integration testing, not unit testing. Regardless of the test type, the test will always fail because you are choosing not to build the form. Even if you manage to force the POST using Fiddler, Firebug or any other mechanism, you're still not testing the view, you're testing the HTTP protocol.
I highly recommend that you employ a web application testing tool, such as WatiN or Selenium, to test your web pages, rather than throw together a quick-and-dirty test that really doesn't test anything useful.
In your post request set content-type="application/json; charset=UTF-8" and pass the values for the method parameter in JSON format. This should make Asp.MVC not to look in FormCollection for those values.