I am working on an MVC 5 application and have the following scenario.
The user clicks my registration link to create an account and is served with a view like so. Works as expected.
[HttpGet]
[AllowAnonymous]
public ActionResult RenderRegisterModal(string userType)
{
IList<UserTypesDto> userTypes = _userService.GetUserTypes();
var usersRegisterUserViewModel = new UsersRegisterUserViewModel {UserTypes = new List<SelectListItem>()};
usersRegisterUserViewModel.UserTypes.Add(new SelectListItem
{
Text = "Select a registration type",
Value = "0"
});
foreach (UserTypesDto userTypeDto in userTypes)
{
usersRegisterUserViewModel.UserTypes.Add(new SelectListItem
{
Text = userTypeDto.UserType,
Value = userTypeDto.UserTypeId.ToString()
});
}
return PartialView("_RegisterModal", usersRegisterUserViewModel);
}
Now based on the type of customer / userType they choose I need to present different views. Should not have any issue getting the view into the modal as it is ajax... So lets call this a pseudo wizard to collect data. The issue I am having and the basis for this probably simple question, that I am thinking too much about, is how do I save the data from each step?? Temp Table? InMemory Cache using the session id as the key? Cookies? "Gross"
The ActionMethod that the post goes to looks like this.
[AllowAnonymous]
[HttpPost]
public ActionResult Register(UsersRegisterUserViewModel usersRegisterUserViewModel)
{
//TODO Return a view for the next step based on the CustomerType contained in the viewModel
return View();
}
Any feedback would be greatly appreciated.
OPTION 1:
The easiest way, is that the NEXT view have a set of hidden fields with the information entered in the previous view and which was posted to the Action.
This way, once you post the second view, you will be posting all the information (previous one and the one you entered in the second view).
OPTION 2:
If you are not comfortable with the first approach, you can have several PartialViews that are shown or hidden in javascript on UserType combo changes. Your ViewModel should have all the propertied you need to hold the information before posting back to server.
This option came in 2 flavors: you render all your usertype partials at the begining (so you need to hide them at first), or you can get the partial via Ajax once the user selected one user type.
OPTION 3:
You can use Session to hold the sensitive data from the register form, redirect to the next view depending on user type, post the new form, and in the Action you retrieve the information from Session... and with all the information in your hands store it in database.
If you're using razor, persist the different parts of your model like this
#Html.EditorFor(model => model.data1)
#Html.EditorFor(model => model.data2) #*Wizard part 1*#
then in the next view for your wizard
#Html.HiddenFor(model => model.data1)
#Html.HiddenFor(model => model.data2)
#Html.EditorFor(model => model.data3)
#Html.EditorFor(model => model.data4) #*Wizard part 2*#
and then
#Html.HiddenFor(model => model.data1)
#Html.HiddenFor(model => model.data2)
#Html.HiddenFor(model => model.data3)
#Html.HiddenFor(model => model.data4)
#Html.EditorFor(model => model.data5) #*Wizard part 3...*#
and so on...
Related
Im kinda new in MVC4 and im not able to figure it out.
"CustomViewMOdel" "CustomViewMOdel"
"ControllerX" ----------------> "VIEW" -----------------> "ControllerY"
My problem is that i want to pass my customviewmodel to view (which is working just fine!). In the View im showing some of model's fields to users (which is working fine also). BUT Now i want user, to change ONE field of the models fields and then PASS the WHOLE model to Controller X (with all fields filled, including the field what user was able to change AND other fields what were just shown)
Can anyone give a very simple code example of how to do this?
You can just create a form that posts to another controller:
ControllerX:
public ActionResult DoSomething()
{
return View(new CustomVM());
}
ViewA
#Model CustomViewModel
#using Html.BeginForm("DoSomethingElse", "ControllerY")
{
#Html.EditorFor(vm => vm.SomeProperty)
<input type="submit" value="OK" />
}
ControllerY
public ActionResult DoSomethingElse(CustomViewModel vm)
{
// do something else
}
You can use #Html.HiddenFor(o => o.Property) on the form.
This will not show a property on it.
But the advanced user may change the property through a development console. So you should check all the changes in the ControllerY
Example:
#Html.HiddenFor(o => o.Id)
#Html.HiddenFor(o => o.Name)
#Html.EditorFor(o => o.Description)
<input type="submit" value="OK" />
This will only let the user change a description but still have "id" and "name" on the FormCollection.
I understand that I can use #Html.HiddenFor(m => m.parameter) and when the form is submitted, that parameter will be passed to the controller. My model has many properties.
Is there a shorter way of passing the entire model at once to the controller or must I do it one by one each time?
The model will be passed to the controller in its entirety, but the values of properties that are not bound by input or hidden fields will be lost.
You have to either bind the properties in the form on the client-side, or re-fetch the entity on the server-side.
You seem to be asking for something like #Html.HiddenFor(m => m.Model), and that is not possible. Sorry
One thing to keep in mind, if you have tons of hidden fields, you may be sending more data to the view than you really need. Consider employing view models
For anyone else who looks at this you can do a #Html.EditorForModel() in a hidden div. You'd also have to use #Html.EditorFor(model => model.ObjectProperty) for each object property of the model.
<div hidden="hidden">
#Html.EditorForModel()
#Html.EditorFor(model => model.ObjectProperty)
#Html.EditorFor(model => model.ListOfObjectsProperty)
</div>
The entire model will be posted if you are using a FORM element. Your elements using the Model obviously need to be inside the form element
You can also POST the form yourself say by using JQuery
See this other stack issue for that : jQuery AJAX submit form
Have a close look at the anwser by "Alfrekjv"
This is already built in. Consider this model:
public class MyModel
{
public string PropertyA { get; set; }
public string parameter { get; set; }
}
and now consider this action:
[HttpPost]
public ActionResult PostSomeData(MyModel model)
{
}
MVC will leverage the FormCollection and fill in the MyModel class where it can. If you don't have the PropertyA in the form then it will be null. But since you have an input for the parameter property it will be filled in.
You can check only the properties you want:
if (this.ModelState.IsValidField("Name"))
{
// .....
}
instead of:
if (this.ModelState.IsValid)
{
// .....
}
#using (Ajax.BeginForm("my_function", "my_controller", new AjaxOptions { InsertionMode = InsertionMode.Replace }, mymodel))
I'm creating some user profile edit forms in MVC4 at the moment and for testing I was rendering the UserId property into a readonly textbox on the form like this:
<li>
#Html.LabelFor(model => model.UserId)
#Html.TextBoxFor(model => model.UserId, new { #readonly="readonly"})
</li>
As I'm nearing completion of the edit form I removed this textbox as it's just using up real estate. Once I had done this the model sent back to the controller when saving had the integer default value of 0 and then the Entity Framework blows up as it cannot update any rows. So I added this to the form:
<li>
#Html.HiddenFor(model => model.UserId, new { #readonly="readonly"})
</li>
Is this a safe move? Should I be using the ViewBag for things like this? On the profile details page I render an edit button like this:
#Html.ActionLink("Edit", "Edit", new { id=Model.UserId })
Meaning that the UserId is rendered in the link. Is this safe and secure or do I need to rethink how I move the models and ids around the UI?
TIA,
Is this a safe move?
This will do the job of sending the id to the server. Just get rid of the readonly="readonly" attribute which makes very little sense for a hidden input.
Should I be using the ViewBag for things like this?
This doesn't change anything in terms of security. Any user could still put whatever id he wants. Whether you are using a hidden field or an ActionLink you are still sending the id as plain text to the server and anyone could forge a request and put whatever id he wants. So if you site uses some form of authentication you must absolutely check on the server side that the id that you received actually is a resource that belongs to the currently authenticated user before attempting to perform any actions on it. Otherwise some authenticated user could supply the id of a resource that belongs to another user and be able to update it. Of course that's just a hypothetical scenario, it's not clear at all if this is your case and whether this id needs to be secured.
If UserId is sensitive, then there are other options
Keep UserId server side only with Session state (if your architecture allows for Session)
Put it in an encrypted cookie. Note as per Darin, that these can be compromised.
If it isn't sensitive, then your HiddenFor is fine - post it back with the rest of the form.
Don't put it in your ActionLink Querystring unless this is part of your route (i.e. /Controller/Action/id)
I would strongly suggest using ValueInjecter. Here is a code snippet doing the same thing
[HttpGet]
public new ActionResult Profile()
{
var model = new ProfileModel();
model.InjectFrom<UnflatLoopValueInjection>(this.GetCurrentUser());
return View(model);
}
[HttpPost]
public new ActionResult Profile(ProfileModel model)
{
if (ModelState.IsValid)
{
this.GetCurrentUser().InjectFrom<UnflatLoopValueInjection>(model);
try
{
_userService.SaveOrUpdate(this.GetCurrentUser());
TempData["Success"] = "User was successfully updated.";
return RedirectToAction("Profile");
}
catch (Exception)
{
ModelState.AddModelError("Exception", "Unexpected error");
}
}
return View(model);
}
And here is the view...
#using (Html.BeginForm("Profile", "Account", FormMethod.Post, new { #class = "form-horizontal" }))
{
#Html.ValidationSummary(true, "Unable to update profile. Please correct the errors and try again.", new { #class = "alert alert-block alert-error" })
#Html.EditorForModel()
<div class="form-actions">
<input type="submit" value="Update" class="btn btn-primary" />
</div>
}
I am new to MVC and trying to implement what I would expect to be a common problem. I have a simple search form that I want to implement on each page of the site. I want this section to maintain its own code so that I don't have to duplicate it on each page.
So far I have been able to do this by calling a render action on the template page. The render action populates the quicksearch form. When I submit the form I am able to validate the form, however I have not found a way to redisplay the same page with the validation information. I would prefer a way that would just refresh the form area, but I would accept a full postback as long as the page is redisplayed.
Template Render Call
#{Html.RenderAction("Display", "QuickSearch");}
ActionController
[HttpPost]
public ActionResult Submit(QuickSearchModel qsModel)
{
if (!ModelState.IsValid)
{
return PartialView(qsModel);
}
//Perform redirect
}
[ChildActionOnly]
public ActionResult Display()
{
//populate model
return View(qsModel);
}
Quick Search View
<div>
#using (Html.BeginForm("Submit", "QuickSearch"))
{
#Html.ValidationSummary(true)
#Html.LabelFor(m => m.Destination)#Html.EditorFor(m => m.Destination)#Html.ValidationMessageFor(m => m.Destination)<br />
#Html.LabelFor(m => m.ArrivalDate)#Html.EditorFor(m => m.ArrivalDate)#Html.ValidationMessageFor(m => m.ArrivalDate)
#Html.LabelFor(m => m.DepartureDate)#Html.EditorFor(m => m.DepartureDate)#Html.ValidationMessageFor(m => m.DepartureDate)<br />
#Html.LabelFor(m => m.Adults)#Html.DropDownListFor(model => model.Adults, new SelectList(Model.AdultsSelectOptions, "value", "text", Model.Adults))<br />
#Html.LabelFor(m => m.Children)#Html.DropDownListFor(model => model.Children, new SelectList(Model.ChildrenSelectOptions, "value", "text", Model.Children))<br />
<input id="qsSubmit" name="qsSubmit" type="submit" value="Submit" />
}
</div>
Thanks in advance for any assistance!
I see that you have the following problems:
How to redirect back to the page, from which the search was made?
What if this original page was "POSTed to" - that is it was rendering something, based on POST request? In this case we will not
be able to "re-produce" this POST in any easy way;
After we have redirected to the original page, how to communicate the search model (or just it's errors), which we have failed to
validate?
Given all these challenges, I would first seriously consider making this search form in AJAX style. That would be much easier solution, if it fits you.
If AJAX is not an option I see following solutions to the respective questions:
I would make a hidden field in the search form with the URL of original page. When validation fails, we can redirect to this URL (just check that it is local URL and no one tries to brake something);
This is a major problem - trying to replay original POST is not easy, but it may not be needed either - just check if this is a problem anyway;
You could use TempData dictionary to communicate errors or model back to original page.
I have a form that lets the user enter in some text. It will be longer than a few characters so I want to use a TextArea instead of a TextBox.
The Html.TextBoxFor works without issue, and the Html.TextAreaFor works when I create the entry, but does not store the new value when I edit it and shows whatever the value was before I went to edit it upon saving.
On Page:
<div>
<label>Work Performed:</label>
<%: Html.TextAreaFor(model => model.WorkPerformed)%>
<%: Html.ValidationMessageFor(model => model.WorkPerformed) %>
</div>
Code Behind for Create:
maintPerformed.MaintDate = DateTime.Parse(Request.Form["MaintDate"]);
maintPerformed.WorkPerformed = Request.Form["WorkPerformed"];
maintPerformedRepository.Add(maintPerformed);
maintPerformedRepository.Save();
return RedirectToAction("Details", new { id = maintPerformed.ID });
Code Behind for Edit:
maintPerformed.MaintDate = DateTime.Parse(Request.Form["MaintDate"]);
maintPerformed.WorkPerformed = Request.Form["WorkPerformed"];
maintPerformedRepository.Save();
return RedirectToAction("Details", new { id = maintPerformed.ID });
What am I missing on the edit side of things?
In both cases you are redirecting to the Details action. So make sure that maintPerformedRepository.Save() actually does something and most importantly in your Details action look what value is fetched from the data store. I suspect that either the database is not updated or in your Details action you are fetching a wrong value for your model.
Remark: Instead of writing the ugly DateTime.Parse(Request.Form["MaintDate"]); and Request.Form["WorkPerformed"]; pass your view model as argument to your controller action and let the model binder populate it from the request values.