I'm used to web forms, but am switching to MVC 5 and have a question about creating a multi step application form.
This form is like a wizard then will display information entered in each step at the end, then submit.
Is it easier to write this using html form in the .cshtml or do it all in the controller?
THank you
MVC, as its name suggests, has a Model, a View, and Controller. To create a form, you set up a class that will act as your Model, containing the properties that need to be worked with in a particular view. This is a different thing than your entity, the class that corresponds to a table in your database. You can sometimes use the entity as the Model, but especially in the case of a multi-step form, you don't want to persist the data until the end, which means, they'll need to be separate.
This brings us to the topic of view models, which is actually from another different pattern called MVVM. Regardless, your Model for these views will be a series of view models that contain just the information that the particular step needs to collect. At the end, you will piece all of the collected data together by creating an instance of your entity and mapping the property values from each view model over to it. Then, you will save the entity.
Now, as far as persisting the collected data between requests goes, that's where your session comes in. You'll merely add each posted view model into your Session object, and then at the end, fetch all of them from the Session object to create your entity.
So each POST action will have something like the following:
[HttpPost]
public ActionResult Step1(Step1ViewModel model)
{
if (ModelState.IsValid)
{
Session["Step1"] = model;
return RedirectToAction("Step2");
}
// errors
return View(model);
}
Then, your final POST action:
[HttpPost]
public ActionResult StepFinal(StepFinalViewModel)
{
if (ModelState.IsValid)
{
var myEntity = new MyEntity();
var step1 = Session['Step1'] as Step1ViewModel;
myEntity.SomeField = step1.SomeField;
// ... repeat for field in view model, then for each step
db.MyEntities.Add(myEntity);
db.SaveChanges();
Session.Remove('Step1');
// repeat for each step in session
return RedirectToAction("Success");
}
// errors
return View(model);
}
All of your form information will be in the .cshtml file like this:
#using (Html.BeginForm("Controller Action Method", "Controller Name", FormMethod.Post, new { id = "Form Name" }))
{
// Form Elements here
}
Then you can simply add a submit button that submits the form to your Controller for processing.
Related
I'm working on a Web Application project using C# and MVC that will take in two URLs in a form and use them to create an instance of a class I have created called "ImageSwap." This model has some data (a username of the person performing the swap; two variables which hold the URLs of two images to be swapped; two variables which save the actual names of these files without all of the rest of the URL information; and two arrays which represent the file locations to check for these files). Right now, I have it so that the initial index view creates the instance of the class and passes it to the same view with the information put in through the form, submitted via POST, like so:
public ActionResult Index()
{
Ops.Operations.Models.ImageSwapModel newImageSwap = new Models.ImageSwapModel();
return View(newImageSwap);
}
[HttpPost]
public ActionResult Index(ImageSwapModel imageSwap)
{
var oldFileFound = false;
var newFileFound = false;
if (ModelState.IsValid)
{
//Perform data manipulation and set needed values
}
}
It then performs some functions on the data, such as parsing out the filename at the end of the URL, and a directory number (which is the first part of this filename, i.e. directory#_fileName.jpg). All of this works fine.
My problem is that I would like to pass this model to another view once it has data populated in all of its fields by this initial ActionResult so that I can have a verification view, which would allow the user to preview the two files side by side so that they can ensure they are swapping the appropriate images. They should then be able to hit another submit button which will initiate the actual moving/replacing of the images and be taken to a page confirming.
Is there a way to pass data from this controller to a different view? My confusion arises because I cannot create another version of an ActionResult of Index with the same input, but I do not want to have the actual swapping of the images occur without a preview and a prompt. Should I re-write my Index view so that it utilizes partial views in order to accomplish this? What is the easiest way to have data persist through multiple steps and views?
What is the easiest way to have data persist through multiple steps
and views?
Your question sounds like you're trying to achieve what you can easily do with sessions. The session object allows you to persist data between requests simply by adding it to the Session object on the HttpContext that exists within the base class that your controller extends, like so:
(Note the Serializable attribute. This allows your object to be serialized into the session object).
[Serializable]
public class ImageSwapModel {
// Your class's properties
}
Then in your controller you can do the following:
[HttpPost]
public ActionResult Index(ImageSwapModel imageSwap)
{
var oldFileFound = false;
var newFileFound = false;
if (ModelState.IsValid)
{
this.HttpContext.Session["ImageSwap"] = imageSwap;
}
}
When you want to retrieve the model you can grab it from the session like so:
var imageSwap = (ImageSwapModel)this.HttpContext.Session["ImageSwap"];
Taking it one step further:
Whilst the above will work fine, generally it's not a good practice to reference the HttpContext object directly in your code as it creates unnecessary coupling to the HttpContext object that can easily be avoided. Instead you should opt to inject an instance of the session object via Dependency Injection. Here is a similar answer that provides a basic idea as to how you can do this.
You can return different views with Models being passed to them in your one Index action like
if(some condition)
{
Return View("ViewVersion1", MyModelVersion1);
}
else
{
Return View("ViewVersion2", MyModelVersion2);
}
This is mostly a follow-up to a comment in this issu, but I don't have enough reputation to comment ...
ASP.Net MVC Postback a label value to your controller
Let's say I have a simple model:
public class SimpleClass
{
public String Label { get; set; }
public String FirstName { get; set; }
}
Label is changed based on user/client so it can't be a DataAttribute. If when posted back processing problems occur, we need to redraw the entire page. This is the crux of the problem of the previous post. The accepted solution is to do this:
#Html.DisplayTextFor(model => model.Label)
#Html.HiddenFor(model => model.Label)
#Html.EditorFor(model => model.FirstName)
That makes sense in that it works. But our models are much more complicated and extensive. This method will result in a ton of hidden fields which seems like a very dirty solution.
This brings me to JP's comment:
ASP.Net MVC Postback a label value to your controller
The solution there is to reload the model. But it's not just a reload, it's also a merge since you want to preserve any client-side data changes.
default: SimpleClass { Label="TheLabel", FirstName="Rob"}
postedback: SimpleClass { Label="", FirstName="Steve" }
we want: SimpleClass { Label="TheLabel", "FirstName="Steve" }
My question is does MVC have a good way to know what fields were postedback so it merges correctly? We would need to only merge postedback fields not blank properties.
Or is it better to just ajaxify the entire postback and not do a form submit? This avoids all model reload issues on submit.
Update
To give Pablo credit I accepted his solution. To see my simple example of his solution, check Robert Harvey's comment in the Answers below:
ASP.Net MVC Postback and Models
The main problem here is in trying to fit WebForms' PostBack concepts into MVC. There is no such thing as a stateful postback where things just automatically retain their state.
You only have ViewModels that are bound to the view, and ViewModels that are posted by the view to the Controller. They don't even necessarily need to be of the same Type. Meaning, the controller should only receive the data that the user indeed can change, not large objects with many properties that were part of the initial ViewModel but are read-only.
Labels commonly represent read-only texts and they are not editable form elements. Which is why you have to use hidden fields for that.
And yes, sometimes that implies that you have to reload the original data in the controller, and sync up with new data that you posted, which isn't necessarily a bad thing. If you bind read-only data to a view, which the user can't manually edit, you shouldn't really trust that data coming back in a post afterwards. Just because your html might try to make it read-only doesn't mean I can't manipulate the post and ultimately change your "read-only" data without you knowing.
I just read the second question you mentioned, and from the looks of it, his main problem was that he was trying to reuse the same ViewModel again, so all the data was missing and the model wasn't valid. The solution to that is indeed quite simple, ONLY post what you need, as a new ViewModel type, and have the controller take care of the rest.
[Moved from OP]
I think this is what Pablo is suggesting for those who are wondering. It seems to be a good pattern to resolve this problem.
Models:
public class SimpleClass : SimpleClassPostBack
{
public String Label { get; set; }
public SimpleClass()
{
// simulate default loading
Label = "My Label";
FirstName = "Rob";
}
}
// contains only editable by the user fields
public class SimpleClassPostBack
{
public String FirstName { get; set; }
}
Controller Actions:
[HttpGet]
public ActionResult SimpleClassExample3()
{
SimpleClass simpleClass = new SimpleClass();
return View(simpleClass);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SimpleClassExample3(SimpleClassPostBack postBackSimpleClass)
{
Boolean errorOccurred = true;
if (!errorOccurred)
{
// do whatever success action is necessary
}
// redraw the page, an error occurred
// reload the original model
SimpleClass simpleClass = new SimpleClass();
// move the posted back data into the model
// can use fancy reflection to automate this
simpleClass.FirstName = postBackSimpleClass.FirstName;
// bind the view
return View(simpleClass);
}
View:
#model SimpleClass
#{
ViewBag.Title = "Simple Class Example3";
}
<h2>Simple Class Example3</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<label for="FirstName">#Html.DisplayFor(m => m.Label)</label>
#Html.EditorFor(m => m.FirstName)
<br/>
<button>Submit</button>
}
You should only send data from the client to the server that the server can't "figure out" on its own. If the server knows what the labels were when the user first navigated to that view then if the user cannot modify them, the server will be able to know what the labels are when reloading the view.
Use hidden fields to identify the database objects. So your SimpleClass should probably have some sort of Id which you will use in the hidden input. Use the EditorFor for FirstName. Now when the form is posted, use the sent Id to find the correct SimpleClass from the database and modify its FirstName property with the value posted. The Label property will be null which is ok since you don't need to save it. Now if there's a problem in the post and you want to send the same view back like it was, you need to repopulate the Label the same way you did when the user arrived to the view for the first time. The values of Id and FirstName properties will be automatically sent back to the view with the model state.
In summary:
Only post data that is needed to identify something and what the user
can edit in that view.
Don't trust the client to send you anything valid. The user can change the values of the hidden field labels to anything.
I have an MVC 3 project, that I am starting, using c# and Razor. I have a page, that has about 20 input fields that will be used. I create my ViewModel to pass the data to the View to create the page. I am confused as to, how I get the values of the fields, when the user submits the form.
Does my Controller have to have a parameter for every input field on my page? Is there any way to have the Controller get all the data on the page and then I can parse through it? The parameter list would be huge.
You can just use the same model that you passed to your view as the parameter in your subsequent action.
An example:
//This is your initial HTTP GET request.
public ActionResult SomeAction() {
MyViewModel model;
model = new MyViewModel();
//Populate the good stuff here.
return View(model);
}
//Here is your HTTP POST request; notice both actions use the same model.
[HttpPost]
public ActionResult SomeAction(MyViewModel model) {
//Do something with the data in the model object.
}
The model object in the second method will automatically be populated from the data included in the HTTP request (the technical name is "Model Binding").
In your controller's action, expect to receive the same "model" you passed back to the view. This will works if you generated your 'input controls' properly (by using Html.TextBoxFor() or by setting the Name attribute to the same name of your model's properties).
public ActionResult MyAction(MyViewModel model)
{
...
}
Note MVC will use a ModelBinder to figure out how to create and fill the properties of the object your action is expected based on the fields submitted from the user.
If you want to capture all inputs from the user you can make your action to receive an object of type FormCollection:
public ActionResult MyAction(FormCollection values)
{
...
}
Please create an mvc action in your controller taking the model as parameter
Like this:
[HttpPost] or [HttpGet]
public ActionResult Employee(EmployeeModel employee)
{
// now you will have all the input inside you model properties
//Model binding is doen autoamtically for you
}
Background
I'm learning the ropes around how to use ASP.NET MVC properly, and ran into this issue with models:
I have a model that describes a contact I can get that out of the form for creating a new contact, but say when we edit a form, I retrieve it from the repository, show the fields on the contact form and then get the contact object and send that to the model.
Problem
I have a business rule that some fields are not allowed to be edited after creation and other fields are only available after editing.
I receive a dirty object from the user (one with fields they should touch) and using the MVC Binding method (sspecifying the object in the method signature) the users inserts a non-editable field contact_dob.
Question
Should I instead retrieve the record again, overwrite only the fields I want to update and then send it to the database?
What's the best method when I don't want to retrieve the Entire object again from the database, do I just redo another EntityModel that's a lighter version of the main model and use that back and forth?
Am I going about this the wrong way? What are the best practices for limiting what users can edit?
I think you can build your model, the Contact class, and in the edit view you should allow only fields that can be edited, and hide or set as not editable the fields you don't want to be edited, then in your controller you'll get the original contact and update it with the values of the fields you allowed in the edit view like:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {
Contact contact = repository.GetById(id);
try {
UpdateModel(contact);
repository.Save(contact);
return RedirectToAction("Details", new { id=contact.Id });
}
catch(Exception ex) {
// Do something...
}
return View(contact);
}
It sounds like the best solution would be to use a custom ViewModel. This is an object that contains all the fields that you would want the user to submit to the controller.
You will need to reload the contact object from the database - I don't think you can get around that without opening yourself up to other issues.
public ActionResult Edit(ContactViewModel viewModel)
{
var contact = repository.GetContacts().WithId(viewModel.Id);
// Update the contact with the fields from the viewModel
repository.Save(contact);
}
You should use the EXCLUDE and INCLUDE constraints in Action method. This way your model will exclude unwanted fields during model binding.
public ActionResult Create([Bind(Exclude="contact_dob")] Contact contact)
{
_db.AddToContacts(contact);
_db.SaveChanges();
return RedirectToAction("Index");
}
The 'best practice' is to have validation done against your submitted model and not allow changes to certain fields. You can use JQuery / JavaScript to grey out textboxes that cannot be changed; as well as validation on the Model side to disallow changes to certain fields (by comparing them against what came from the database).
You can use Model Validation to disallow changes to certain fields. ASP.NET MVC 2 has this functionality. You don't even need to re-retrieve the object.
In the 'NerdDinner Walkthrough' (ASP.NET MVC 1.0), there's a walkthrough of Validation.
So I've return View(object);'d, and I press submit on that page, how do I get that object back in the controller? any ideas?
my viewpage is
public partial class Index : ViewPage<List<Models.Pricing>>
and
public ActionResult Index(int id, FormCollection datesForm,
[Bind(Prefix="")]List<Pricing> model)
{
return View("Index", new{id});
}
Because you are really trying to retrieve a list of models (of type Pricing), you will either need to develop a custom IModelBinder and use it or iterate through the form collection and pull the data for each pricing model out of the form parameters and reconstitute them. Given your code, though, I don't see why you need to do this.
Is it really the case that you want to get the model data associated with the given id? Or is there more code than what you've shown? In the former case, the best thing to do is probably re-run the query using the id and not bother with the extra parameters to the controller action.