I'm new to MVC, so I'm trying to figure out some best practices.
Suppose I have a controller HomeController method Index(MyViewModel model):
public ActionResult Index(MyViewModel model)
{
//if loading the page for the first time, do nothing
//if the page has been posted data from somewhere, then I want to use
// some of the arguments in model to load other data, like say search results
}
When I navigate to the /Index page, I (myself) expect the model object to come through as null, but it doesn't. MVC (somehow) creates a MyViewModel for me.
My question is, what's the best way or most consistent to determine if model was created automatically, or via a post?
Ideas:
Create a property on MyViewModel that gets set when the view is posting back
Check for if the Request.HttpMethod == "GET" or "POST"
Something else?
You should use different actions for your GET and POST requests. Don't try and make a single method do too much.
[HttpGet]
public ActionResult Index()
{
// handle the GET request
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
if (ModelState.IsValid)
{
// it's a post and the data is valid
}
}
The correct method will then be called depending on whether it's a GET or POST
Create two actions, one which accepts a model instance and one which doesn't.
Even though you're "going to the same page" you are in fact performing two distinctly different actions. The first action loads an initial page, the second action posts some value to be acted upon. Two actions means two methods:
[HttpGet]
public ActionResult Index()
{
// perform any logic, but you probably just want to return the view
return View();
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// respond to the model in some way
return View(model);
// or return something else? a redirect? it's up to you
}
Note that this kind of breaks your restful URLs. Consider semantically what you're doing in these actions:
Viewing an index
Posting to an index
The first one makes sense, but the second one probably doesn't. Normally when you POST something you're doing something related to a model or action of some sort. "Index" doesn't really describe an action. Are you "Create"-ing something? Are you "Edit"-ing something? Those sound like more meaningful action names for the POST action.
Related
Afternoon Folks,
I have a question in reference to how to redirect to another model within my mvc project.
I have a controller with CRUD operations, and i have a [HttpPost] action on Create. The system saves the data to the database as required but instead of me sending the user back to the "Index" page i want to sent them to another model.
I know how to use the redirect option...
// POST: CarerDetailsNew/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CarerViewModel carerViewModel)
{
if (ModelState.IsValid)
{
db.CarerDetails.Add(carerViewModel.carer);
db.SaveChanges();
return RedirectToAction("Edit", carerViewModel.carer);
//return RedirectToRoute("ClientRecordsController");
}
return View(carerViewModel);
}
However i have read that i may be able to use the "RedirectToRoute" option to get back to another model.
The model that i am in is my "Carer" model but i want to get back to my "Client" model and specifically the "Edit".
I have tried to replace my RedirectToAction to one of the following but this fails to see the "Client" model.
return RedirectToRoute("Edit", clientViewRecord.client);
Or
return RedirectToRoute(ClientRecordsController, "Edit", clientViewRecord.client);
Any suggestions are more than welcome as i am struggling to get this to work.
Regards
Betty
I have an question about the get and post process in ASP.NET MVC 4.
I'm sure that often talk about but it isn't easy to search for this topic.
Let me try to explain:
I start my controller with the standard method:
[HttpGet]
public ActionResult Item()
So, in this function I retrieve a lot of important data as example the user id and so on.
In my case, I even collect data in my viewbag() to decide if a form has to be displayed or not.
Now, if I start a post back:
[HttpPost]
public ActionResult Item(FormCollection formCollection)
the function gives as standard View() back.
The Problem is now, that after the post method, the business logic (retrieve user id and so on) of the GET method isn't called... I have tried to solve it with
return this.RedirectToAction("Item");
but is that really the solution for repeat the logic out of the start (get)? And how can I give the new values from the post method to the get method?
Best regards,
Patrik
That pattern is called Post/Redirect/Get.
To pass additional data to GET method you can use TempData and ModelStateToTempDataAttribute from MvcContrib - it passing ModelState to tempdata if Redirect is returned and tempdata to modelstate if View is returned.
[HttpGet]
[ModelStateToTempData]
public ActionResult Item(int id)
{
// prepare view
return View();
}
[HttpPost]
[ModelStateToTempData]
public ActionResult Item(FormCollection formCollection)
{
// do some business logic
int id = service.DoBusinessLogicAndReturnSomeId();
return this.RedirectToAction("Item", new { id });
}
You should avoid to have business logic in GET. All business logic should be inside POST method and after you invoke that you can redirect to GET where you prepare your view.
I'm working on my first ASP.NET MVC 3 application and I've got two Controllers with Views, IceCreamController/IceCreamView and RecipeController/RecipeView, and on each the user can make a selection and display the recipe.
Selection causes a PartialView to be displayed which an Edit link on it. When clicked the EditView for this recipe is displayed, allowing the user to edit the attributes of the recipe item selected.
Great. This works fine except currently the POST action in the RecipeController looks like so:
[HttpPost]
public ActionResult Edit(RecipeViewModel viewModel)
{
// updates the underlying model with the viewModel
// other things not germane to the discussion
return View();
}
and that ends up always showing the Index view for Recipe, which isn't what I want. Rather, I'd like to be able to do is send the user back to the appropriate View (IceCreamView or RecipeView) when they've submitted their changes.
I assume that others have done something similar to this. How do you communicate which Controller/Action should be redirected to when the Edit is done?
Note:
I added a bit above to clarify that I've got two separate Controllers (IceCreamController and RecipeController) and each has a View that can select and ultimately do a
#Html.Partial("_Recipe", model.recipe)
to display the details of a particular recipe. My problem is how to get the page redirected back to either IceCreamView or RecipeView by the Edit Action on RecipeController - essentially, how do I communicate where it should go since the recipe details could have been displayed by either path.
Solution Employed:
As you can read below in the comments to Darrin's answer, since I've got more than a single controller involved, a solution is to utilize the viewmodel to pass in the controller/action that should be redirected to following when the Edit post action is completed.
As I've got more than a single instance of this situation (arriving at an Edit page via multiple paths), I think creating a simple BaseViewModel to hold this functionality might be in order and then have all the applicable viewmodels inherit from that BaseViewModel.
I'm don't think it needs to be anything more than something like:
public BaseViewModel
{
public BaseViewModel(string controller, string action)
{
ControllerName = controller ?? string.empty;
ActionName = action ?? string.empty;
}
public string ControllerName { get; set; }
public string Action { get; set; }
}
And then a viewmodel's constructor could just be modified to pass in the controller/action and hand that off to the base class.
There may be other solutions to this and if so, I'd like to hear them.
[HttpPost]
public ActionResult Edit(RecipeViewModel viewModel)
{
// updates the underlying model with the viewModel
// other things not germane to the discussion
return View("IceCreamView");
}
or if you wanted to redirect you could have a controller action that would serve this view and then return RedirectToAction("IceCream"); which is probably more correct rather than directly returning a view from a POST action in case of success.
Let's say I have a Controller that handles a CRUD scenario for a 'Home'. The Get would look something like this:
[HttpGet]
public ActionResult Index(int? homeId)
{
Home home = homeRepo.GetHome(homeId.Value);
return Json(home, JsonRequestBehavior.AllowGet);
}
So far so good. Then I add a post action for adding new ones.
[HttpPost]
public ActionResult Index(Home home)
{
//add the new home to the db
return Json(new { success = true });
}
Awesome. But when I use the same scheme to handle puts (updating an existing home)...
[HttpPut]
public ActionResult Index(Home home)
{
//update existing home in the db
return Json(new { success = true });
}
We run into a problem. The method signatures for Post and Put are identical, which of course C# doesn't like. I could try a few things, like adding bogus parameters to the signature, or changing the method names to directly reflect CRUD. Those are hacky or undesirable, though.
What is the best practice for going about preserving RESTful, CRUD style controllers here?
This is the best solution that I know of:
[HttpPut]
[ActionName("Index")]
public ActionResult IndexPut(Home home)
{
...
}
Basically the ActionNameAttribute was created to deal with these scenarios.
HttpPut and HttpDeletes are restricted by some firewalls so at times simply HttpPost and HttpGet are used. If a record ID is passed in (or some other criteria) you know its an update. Granted - this is for you to determine, httpput may work just fine for you, this is just a warning on it, it usually isn't a big deal.
Either method used - beware of users trying to inject false IDs into the page in order to forcing updates of records they don't have access to. I get around this issue by hashing in this case home.HomeId on the view when we render it
ViewData["IdCheck"] = Encryption.ComputeHash(home.HomeId.ToString());
in your view:
<%: Html.Hidden("IdCheck", ViewData["IdCheck"]) %>
in your HttpPost or HttpPut method (whichever is doing the update)
if (Encryption.ComputeHash(home.HomeId.ToString()) != (string)Request.Form["IdCheck"])
{
throw new Exception("Hashes do not match");
}
Again - this same security issue exists no matter which method you use to do your update if you are trusting form data.
I've created a routing structure whereas the action part of the URL serves as a dynamic handler for picking a specific user created system name. i.e.
http://mysite.com/Systems/[SystemName]/Configure, where [SystemName] designates the name of the system they would like to configure.
The method that routes the system is the following:
public ActionResult Index(string systemName, string systemAction)
{
ViewData["system"] = _repository.GetSystem(systemName);
if (systemAction != "")
{
return View(systemAction);
}
else
{
// No Id specified. Go to system selection.
return View("System");
}
}
The above method sets the system to configure and routes to a static method where the view is displayed and a form awaits values.
The question I have is that when I create my configuration view, I lose my posted values when the form is submitted because it routes back to the above Index controller. How can I determine if data is being posted when hitting my above Index controller so that I can make a decision?
Thanks!
George
Annotate the controller method that handles the POST like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(string systemName, string systemAction)
{
// Handle posted values.
}
You can have a different method in your controller that handles the GETs:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Index(string systemName, string systemAction)
{
// No posted values here.
}
Note that, although I have copied the same method and parameters in each case, the signature for the second method (parameters and types) will have to be different, so that the two methods are not ambiguous.
The NerdDinner tutorial has examples of this.