I have a need, to make a multi-step process in which my, let's say "big model" will be updated every step and in the end saved to a database. For now, I made it this way:
[HttpGet]
public ActionResult StepOne()
{
return View();
}
[HttpPost]
public ActionResult StepOne(StepOneViewModel viewModel)
{
return RedirectToAction("StepTwo", "SameController", viewModel);
}
[HttpGet]
public ActionResult StepTwo(StepOneViewModel stepOneViewModel)
{
var stepTwoViewModel = new StepTwoViewModel()
{
stepOneViewModel = stepOneViewModel
};
return View(stepTwoViewModel );
}
[HttpPost]
public ActionResult StepTwo(StepTwoViewModel viewModel)
{
return RedirectToAction("StepThree", "SameController", viewModel);
}
... And so on. This approach seems to be good because it's strongly typed in a view, and I can have a separate validation for every "smaller" view model. On the other hand, I have to pass every parameter of the previous view model via Hidden inputs (making a partial for example, not to mess the code too much).
But is there another, better way to do so ? Is it possible to pass the whole object with many properties to the hidden or something similar ?
Related
A lot of my plain content is in the database, accessed by a custom CMS. Around the application I display simple "thank you" messages, etc. which consist of a controller action (simplified):
public ActionResult DetailsUpdated()
{
return View();
}
and my view:
#Html.GetContent("DetailsUpdated")
I have quite a few of these and its quite annoying having a lot of view files with one-liners in. I want to be able to return that content as a View, I can do return ContentResult(ContentRepository.GetContent("KEY")); but this returns as plain-text and there is no master view rendered.
So, basically, grab the content from the DB via ContentRepository.GetContent("KEY") (returns a string) and inject it into a master view, where RenderBody() is called. I'd like to have a custom ActionResult so I can just do:
public ActionResult DetailsUpdated()
{
return DbContentResult();
}
and then the DbContentResult ActionResult will find the content key relative to the action and controller name, go to the database and retrieve the content and display it within its master view, no physical file view needed. Is this possible?
You may have one view file and refer to that view file from several actions:
public class FooBarController : Controller {
public ViewResult Foo() {
return View("FooView", ContentRepository.GetContent("KEY"));
}
}
In this case, you will be able to render the view whose path is ~/Views/Shared/FooView.cshtml (unless you override the default convention of course).
Edit:
As you indicated, you can make a custom ViewResult which does this for you:
public class DbContentResult : ViewResult {
public DbContentResult() {
this.ViewName = "FooView";
this.ViewData.Model = "Foo Model";
}
}
Usage:
public ActionResult Index() {
return new DbContentResult();
}
Or even better, write an extension method for Controller class which integrates with DbContentResult:
public static class ControllerExtensions {
public static ViewResult DbContentResult(this Controller controller) {
return new DbContentResult();
}
}
Usage:
public ActionResult Index() {
return this.DbContentResult();
}
for more detail about creating custom actionresult go here:-
http://www.professionals-helpdesk.com/2012/06/create-custom-actionresult-in-mvc-3.html
What is the difference between ViewResult() and ActionResult() in ASP.NET MVC?
public ViewResult Index()
{
return View();
}
public ActionResult Index()
{
return View();
}
ActionResult is an abstract class that can have several subtypes.
ActionResult Subtypes
ViewResult - Renders a specifed view to the response stream
PartialViewResult - Renders a specifed partial view to the response stream
EmptyResult - An empty response is returned
RedirectResult - Performs an HTTP redirection to a specifed URL
RedirectToRouteResult - Performs an HTTP redirection to a URL that is determined by the
routing engine, based on given route data
JsonResult - Serializes a given ViewData object to JSON format
JavaScriptResult - Returns a piece of JavaScript code that can be executed on the client
ContentResult - Writes content to the response stream without requiring a view
FileContentResult - Returns a file to the client
FileStreamResult - Returns a file to the client, which is provided by a Stream
FilePathResult - Returns a file to the client
Resources
What's the difference between ActionResult and ViewResult for action method? [ASP.NET Forums]
ActionResult is an abstract class.
ViewResult derives from ActionResult. Other derived classes include JsonResult and PartialViewResult.
You declare it this way so you can take advantage of polymorphism and return different types in the same method.
e.g:
public ActionResult Foo()
{
if (someCondition)
return View(); // returns ViewResult
else
return Json(); // returns JsonResult
}
It's for the same reason you don't write every method of every class to return "object". You should be as specific as you can. This is especially valuable if you're planning to write unit tests. No more testing return types and/or casting the result.
ViewResult is a subclass of ActionResult. The View method returns a ViewResult. So really these two code snippets do the exact same thing. The only difference is that with the ActionResult one, your controller isn't promising to return a view - you could change the method body to conditionally return a RedirectResult or something else without changing the method definition.
While other answers have noted the differences correctly, note that if you are in fact returning a ViewResult only it is better to return the more specific type rather than the base ActionResult type. An obvious exception to this principle is when your method returns multiple types deriving from ActionResult.
For a full discussion of the reasons behind this principle please see the related discussion here: Must ASP.NET MVC Controller Methods Return ActionResult?
In the Controller , one could use the below syntax
public ViewResult EditEmployee() {
return View();
}
public ActionResult EditEmployee() {
return View();
}
In the above example , only the return type varies . one returns ViewResult whereas the other one returns ActionResult.
ActionResult is an abstract class . It can accept:
ViewResult , PartialViewResult, EmptyResult , RedirectResult , RedirectToRouteResult , JsonResult , JavaScriptResult , ContentResult, FileContentResult , FileStreamResult , FilePathResult etc.
The ViewResult is a subclass of ActionResult.
In Controller i have specified the below code with ActionResult which is a base class that can have 11 subtypes in MVC like:
ViewResult, PartialViewResult, EmptyResult, RedirectResult,
RedirectToRouteResult, JsonResult, JavaScriptResult, ContentResult,
FileContentResult, FileStreamResult, FilePathResult.
public ActionResult Index()
{
if (HttpContext.Session["LoggedInUser"] == null)
{
return RedirectToAction("Login", "Home");
}
else
{
return View(); // returns ViewResult
}
}
//More Examples
[HttpPost]
public ActionResult Index(string Name)
{
ViewBag.Message = "Hello";
return Redirect("Account/Login"); //returns RedirectResult
}
[HttpPost]
public ActionResult Index(string Name)
{
return RedirectToRoute("RouteName"); // returns RedirectToRouteResult
}
Likewise we can return all these 11 subtypes by using ActionResult() without specifying every subtype method explicitly.
ActionResult is the best thing if you are returning different types of views.
To save you some time here is the answer from a link in a previous answer at https://forums.asp.net/t/1448398.aspx
ActionResult is an abstract class, and it's base class for ViewResult class.
In MVC framework, it uses ActionResult class to reference the object your action method returns. And invokes ExecuteResult method on it.
And ViewResult is an implementation for this abstract class. It will try to find a view page (usually aspx page) in some predefined paths(/views/controllername/, /views/shared/, etc) by the given view name.
It's usually a good practice to have your method return a more specific class. So if you are sure that your action method will return some view page, you can use ViewResult. But if your action method may have different behavior, like either render a view or perform a redirection. You can use the more general base class ActionResult as the return type.
I want to do some processing on a attribute before returning the view. If I set the appModel.Markup returned in the HttpPost ActionResult method below to "modified" it still says "original" on the form. Why cant I modify my attribute in a HttpGet ActionResult method?
[HttpGet]
public ActionResult Index()
{
return View(new MyModel
{
Markup = "original"
});
}
[HttpPost]
public ActionResult Index(MyModel appModel)
{
return View(new MyModel
{
Markup = "modified"
});
}
Because "original" is stored in ModelState. When form values are collected on MVC side, they are stored in ModelState object. You propably used Html.TextBox helper. When you recreate view after POST, it looks up into ModelState first and if there is posted value, it sets this value. Value in model object doesn't count anymore.
One of the solutions is to follow POST-REDIRECT-GET pattern. First POST, do something with data and then redirect:
[HttpPost]
public ActionResult Index(MyModel appModel)
{
//do something with data
return RedirectToAction("Index");
}
If you want to pass something between redirects, you can use TempData:
[HttpPost]
public ActionResult Index(MyModel appModel)
{
//do something with data
TempData["Something"] = "Hello";
return RedirectToAction("Index");
}
[HttpGet]
public ActionResult Index()
{
var something = TempData["Something"]; //after redirection it contains "Hello"
}
After redirect, ModelState is gone, so the is no value to override. POST-REDIRECT-GET pattern also helps to get rid of form reposting effect when you press F5 in browser.
There are a lot of articles devoted to working with data in MVC, and nothing about MVC 2.
So my question is: what is the proper way to handle POST-query and validate it.
Assume we have 2 actions. Both of them operates over the same entity, but each action has its own separated set of object properties that should be bound in automatic manner. For example:
Action "A" should bind only "Name" property of object, taken from POST-request
Action "B" should bind only "Date" property of object, taken from POST-request
As far as I understand - we cannot use Bind attribute in this case.
So - what are the best practices in MVC2 to handle POST-data and probably validate it?
UPD:
After Actions performed - additional logic will be applied to the objects so they become valid and ready to store in persistent layer. For action "A" - it will be setting up Date to current date.
I personally don't like using domain model classes as my view model. I find it causes problems with validation, formatting, and generally feels wrong. In fact, I'd not actually use a DateTime property on my view model at all (I'd format it as a string in my controller).
I would use two seperate view models, each with validation attributes, exposed as properties of your primary view model:
NOTE: I've left how to combining posted view-models with the main view model as an exercise for you, since there's several ways of approaching it
public class ActionAViewModel
{
[Required(ErrorMessage="Please enter your name")]
public string Name { get; set; }
}
public class ActionBViewModel
{
[Required(ErrorMessage="Please enter your date")]
// You could use a regex or custom attribute to do date validation,
// allowing you to have a custom error message for badly formatted
// dates
public string Date { get; set; }
}
public class PageViewModel
{
public ActionAViewModel ActionA { get; set; }
public ActionBViewModel ActionB { get; set; }
}
public class PageController
{
public ActionResult Index()
{
var viewModel = new PageViewModel
{
ActionA = new ActionAViewModel { Name = "Test" }
ActionB = new ActionBViewModel { Date = DateTime.Today.ToString(); }
};
return View(viewModel);
}
// The [Bind] prefix is there for when you use
// <%= Html.TextBoxFor(x => x.ActionA.Name) %>
public ActionResult ActionA(
[Bind(Prefix="ActionA")] ActionAViewModel viewModel)
{
if (ModelState.IsValid)
{
// Load model, update the Name, and commit the change
}
else
{
// Display Index with viewModel
// and default ActionBViewModel
}
}
public ActionResult ActionB(
[Bind(Prefix="ActionB")] ActionBViewModel viewModel)
{
if (ModelState.IsValid)
{
// Load model, update the Date, and commit the change
}
else
{
// Display Index with viewModel
// and default ActionAViewModel
}
}
}
One possible way to handle POST data and add validation, is with a custom model binder.
Here is a small sample of what i used recently to add custom validation to POST-form data :
public class Customer
{
public string Name { get; set; }
public DateTime Date { get; set; }
}
public class PageController : Controller
{
[HttpPost]
public ActionResult ActionA(Customer customer)
{
if(ModelState.IsValid) {
//do something with the customer
}
}
[HttpPost]
public ActionResult ActionB(Customer customer)
{
if(ModelState.IsValid) {
//do something with the customer
}
}
}
A CustomerModelBinder will be something like that:
public class CustomerModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name == "Name") //or date or whatever else you want
{
//Access your Name property with valueprovider and do some magic before you bind it to the model.
//To add validation errors do (simple stuff)
if(string.IsNullOrEmpty(bindingContext.ValueProvider.GetValue("Name").AttemptedValue))
bindingContext.ModelState.AddModelError("Name", "Please enter a valid name");
//Any complex validation
}
else
{
//call the usual binder otherwise. I noticed that in this way you can use DataAnnotations as well.
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
and in the global.asax put
ModelBinders.Binders.Add(typeof(Customer), new CustomerModelBinder());
If you want not to bind Name property (just Date) when you call ActionB, then just make one more custom Model Binder and in the "if" statement, put to return the null, or the already existing value, or whatever you want. Then in the controller put:
[HttpPost]
public ActionResult([ModelBinder(typeof(CustomerAModelBinder))] Customer customer)
[HttpPost]
public ActionResult([ModelBinder(typeof(CustomerBModelBinder))] Customer customer)
Where customerAmodelbinder will bind only name and customerBmodelbinder will bind only date.
This is the easiest way i have found, to validate model binding, and i have achieved some very cool results with complex view models. I bet there is something out there that i have missed, and maybe a more expert can answer.
Hope i got your question right...:)
So far I still have the standard routing. What I tried to do is
public Foo : Controller
{
public ActionResult Index(int id)
{
return View("List", repo.GetForId(id));
}
public ActionResult Index()
{
return View("List", repo.GetAll());
}
}
The URL I entered was
localhost/Foo/Index.
I was under the asumption that it was smart enough to figure out which method I wanted. All I get though is an error telling me that the call is ambigous. From reading the route tutorial I thought that this would work. What am I missing?
Sorry:
Duplicate. I am voting to close.
Method overloading (two actions with the same name) resolution is based on the HTTP verb. This means that if you want to have two actions with the same name you need to differentiate them by the HTTP verb they accept:
public ActionResult Index(int id)
{
return View("List", repo.GetForId(id));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index()
{
return View("List", repo.GetAll());
}
Thus when you call GET /Home/Index the first action will be invoked and when you call POST /Home/Index the second one will be invoked.
Also note that you should probably use nullable integer as the id argument because if no parameter is passed in the request, the model binder will fail.
A RESTful way to handle this, following conventions that I would suggest you is this:
public ActionResult Index()
{
return View(repo.GetAll());
}
public ActionResult Show(int id)
{
return View(repo.GetForId(id));
}
which will allow you to handle both /Home/Index and /Home/Show/10 requests. You probably will have different views as well because the first one will be strongly typed to IEnumerable<T> while the second one to T.