I’m making an MVC online web shopping app, so after the shopping cart page I’ve got ProcessStepOne action in which the user should fill his data.
[Authentication]
public ActionResult ProcessStepOne()
{
ProcessOrderViewModel model = GetOrderData();
return View("ProcessOrderStepOne", model);
}
private ProcessOrderViewModel GetOrderData()
{
ProcessOrderViewModel model = (ProcessOrderViewModel)Session["Process"];
if(model==null)
{
model = new ProcessOrderViewModel();
}
return model;
}
My ProcessOrderViewModel model is:
public class ProcessOrderViewModel
{
public ProcessOrderViewModel()
{
this.PrivateIndividualData = new PrivateIndividualModel();
this.OrderDiscoutPrice = new OrderDiscountPriceModel();
}
public PrivateIndividualModel PrivateIndividualData { get; set; }
public OrderDiscountPriceModel OrderDiscoutPrice { get; set; }
}
And my View ProcessOrderStepOne is:
#model ProcessOrderViewModel
<form id="formOrderData" method="post" action="#Url.Action("ProcessStepTwo")">
<div class="row">
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label> First Name </label>
#Html.TextBoxFor(x => x.PrivateIndividualData.FirstName, new { #class = "form-control" })
#Html.ValidationMessageFor(x => x.PrivateIndividualData.FirstName)
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label> Last Name </label>
#Html.TextBoxFor(x => x.PrivateIndividualData.Family, new { #class = "form-control" })
#Html.ValidationMessageFor(x => x.PrivateIndividualData.Family)
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label>Email</label>
#Html.TextBoxFor(x => x.PrivateIndividualData.Email, new { #class = "form-control" })
#Html.ValidationMessageFor(x => x.PrivateIndividualData.Email)
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label for="tel">#StringResources.GetResourceString("UserManagement", "Register_PhoneLabel")</label>
#Html.TextBoxFor(x => x.PrivateIndividualData.Telephone, new { #class = "form-control" })
#Html.ValidationMessageFor(x => x.PrivateIndividualData.Telephone)
</div>
</div>
</div>
</form>
So, my second step is just checking the values the user has entered and then show him for verification. If everything is OK I save the data in the session so the user can return to the previous ProcessStepOne action or proceed to making order with the MakeOrder action.
public ActionResult ProcessStepTwo(ProcessOrderViewModel model)
{
if (ModelState.IsValid)
{
Session["Process"] = model;
return View("ProcessOrderStepTwo", model);
}
return View("ProcessOrderStepOne", model);
}
And the view is:
#model ProcessOrderViewModel
<section class="form-section">
<p>
<a href='#Url.Action("ProcessStepOne")'>CHANGE</a>
<span class="text-semibold">Name:</span> #Model.PrivateIndividualData.FirstName <br>
<span class="text-semibold">Last Name:</span> #Model.PrivateIndividualData.Family <br>
<span class="text-semibold">E-mail:</span> #Model.PrivateIndividualData.Email<br>
<span class="text-semibold">Телефон:</span>#Model.PrivateIndividualData.Telephone <br>
</p>
</section>
<a href='#Url.Action("MakeOrder")'>PROCEED TO MAKE ORDER</a>
And, here is my last action which just gets the data from the session:
public ActionResult MakeOrder()
{
var data = (ProcessOrderViewModel)System.Web.HttpContext.Current.Session["Process"];
// make order with this data
return View("Thank You");
}
So my questions are: in the MakeOrder action I just take the model from the session without any verification. How can I validate it again and also is it right storing the data in session so I can return to make modifications or proceed to make an order. Is this the right thing to do because I can't think of another way.
In the MakeOrder action I just take the model from the session without any verification. How can I validate it again?
You can use TryValidateModel in your controller to re-validate your model after retrieving it from Session. However, from my testing it appears that this method only validates one level deep in the model. With this in mind, you code would be something like the following:
public ActionResult MakeOrder()
{
var data = (ProcessOrderViewModel)System.Web.HttpContext.Current.Session["Process"];
bool individualDataValid = TryValidateModel(data.PrivateIndividualData);
bool discountDataValid = TryValidateModel(data.OrderDiscoutPrice);
if (individualDataValid && discountDataValid)
{
// make order with this data
return View("Thank You");
}
//Error condition
return View("Error");
}
Is it right storing the data in session so I can return to make modifications or proceed to make an order. Is this the right thing to do because I can't think of another way.
Personally, I avoid using Session if I can. However, I think it is perfectly justifiable for a "Shopping Basket" requirement such as yours. What I would say is that from a maintenance and testing perspective, it may be better if you create a wrapper object for Session access. See Session variables in ASP.NET MVC.
This has the following benefits:
All your Session access is in one place. Not scattered liberally across controllers.
No "Magic Strings" used across your solution for session access.
As an added bonus, because you have abstracted session access out into an object, your controllers can be more "testable" (provided it is not a static wrapper).
Related
I apologize if the heading is not very clear.
So, what I want to do is following.
I have a model class called "Class" that contains a list of "Students". I have a view that shows a checklist of Students. From that checklist I want to pass the selected students back to controller, where I will update the Database and my class will have new students and the added students will have a new class.
Here is what I have done so far but now I am stuck. Thanks in advance.
These are my controller actions
[HttpGet]
public ActionResult AddStudents(int? id)
{
List<Student> students = (from std in db.Students
where std.St_cl_fk_id == null select std).ToList();
//ViewBag.students = students;
return View("Add_Students",students);
}
[HttpPost, ActionName("AddStudents")]
public ActionResult AddStudentsPost(int? id,List<Student> students)
{
if(ModelState.IsValid)
{
var temp = id;
Class #class = db.Classes.Find(id);
foreach (var item in students)
{
if (Request.Form[item.St_id.ToString()] != null)
{
#class.Students.Add(item);
}
}
db.Entry(#class).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index", "Classes");
}
return View("Add_Student");
}
Here is my view
#model IEnumerable<GMASchoolProject.Models.Student>
#{
ViewBag.Title = "Add_Students";
}
<h4>Student List</h4>
#using (Html.BeginForm())
{
<div class="row">
<div class="col-lg-8 col-lg-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
Check Students
</div>
<div class="form-group">
<div class="col-md-10">
<table>
#foreach (var std in Model)
{
<tr>
<td><input type="checkbox" name="#std.St_id" value="#std.IsSelected" /></td>
<td>#Html.Label((string)std.St_name)</td>
</tr>
}
</table>
</div>
</div>
<div class="form-group" style="margin:15px,0,15px,0;">
<div class="col-md-offset-5 col-md-2">
<input type="submit" value="Add Students" class="btn btn-primary" />
</div>
</div>
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index", null, new { #class = "btn btn-danger" })
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Please let me know if there is anything else that I am doing wrong, I am a newbie in this area. THanks
First, instead of binding the view to a list of students, bind the model to your Class object that has a list of students in it. That way, when you submit (post), you are sending the whole Model object into the Controller.
Change your AddStudentsPost method to take in Class object and do your logic there.
Finally, change the name of the "Class" object to something else, such as, "Course". Class should be reserved for actual classes so not to cause confusion.
You aren't far off, so keep going
I have a shared partial view which consist of a form. This partial view is shared between two different controller. Let's call them Controller1 and Controller2. The problem is, whether I am using the Controller1's Insert view or Controller2's Insert view, the action method Insert of Controller1 is always being called.
Let's say I am filling the form at the localhost:58888/Controller2/Insert and then push the submit button, in this situation the Insert action of Controller1 is operated and debugger never reached to Controller2's Insert method. After a summary of my problem, I would like to share with you some of my code.
Here is the shared partial view;
#model TabNetMVC.Models.EOrder.Product
<div class="panel panel-default">
<div class="panel-body">
<div class="form-horizontal">
<div class="form-group" lang="tr">
#Html.Label("Ürün Adı", new { #class = "control-label col-md-2 text-left" })
<div class="col-md-4">
#Html.DropDownListFor(model => model.Name,
new List<SelectListItem> {
new SelectListItem {
Text="Test-1",
Value="1",
},
new SelectListItem {
Text="Test-2",
Value="2",
},
}, "Kategori Seçiniz", new { #class = "form-control", id = "itemDropDown" })
<p style="display: none; color: red;" id="dropdownValidation">Lütfen Ürün Seçiniz!</p>
</div>
</div>
<div class="form-group" lang="tr">
#Html.Label("Miktar", new { #class = "control-label col-md-2 text-left" })
<div class="col-md-4">
#Html.TextBoxFor(model => model.Quantity, new { #class = "form-control", id = "itemQuantity" })
#Html.ValidationMessage("Quantity", "", new { #class = "text-danger" })
<p style="display: none; color: red;" id="quantityValidation">Lütfen Miktar Seçiniz!</p>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-4">
<button class="btn btn-success col-lg-4" type="submit" id="btnAdd" onclick="return Validation(#((int)Enums.MissingItem.Add))"><i class="fa fa-plus" aria-hidden="true"></i> Add</button>
</div>
</div>
</div>
</div>
</div>
Here, Product model which is used at this shared view is a base class of both of other two models which is being used by Controller1 and Controller2. I tried to make us of polymorphism to make this shared view as generic as possible.
Lastly, I am sharing two different action method of each Controller.
Controller1's Insert method;
[HttpPost]
public ActionResult Insert(MissingProduct product)
{
if (SessionHelper.Products == null)
{
SessionHelper.Products = new List<Product>();
}
if(product.Statu == (int)Enums.MissingItem.SendConfirm)
{
//Operations for inserting into database placed here...
SessionHelper.Products = new List<Product>();
}
else if (product.Statu == (int)Enums.MissingItem.Add && ModelState.IsValid)
{
SessionHelper.Products.Add(product);
}
return PartialView("~/Views/EOrder/ItemListControl.cshtml");
}
Controller 2's Insert method;
[HttpPost]
public ActionResult Insert(SupplementaryProduct product)
{
if (SessionHelper.Products == null)
{
SessionHelper.Products = new List<Product>();
}
if (product.Statu == (int)Enums.MissingItem.SendConfirm)
{
//Operations for inserting into database placed here...
SessionHelper.Products = new List<Product>();
}
else if (product.Statu == (int)Enums.MissingItem.Add && ModelState.IsValid)
{
SessionHelper.Products.Add(product);
}
return PartialView("~/Views/EOrder/ItemListControl.cshtml");
}
As you see, both of action methods are almost identical except the data type is MissingProduct at Controller1's Insert method and SupplementaryProduct at the Controller2's Insert method. Lastly note that, both of the Insert action has its own view which renders this shared partial view into itself.
What can be causing this? Thanks in advance...
This looks like an issue with the form definition. When you define a form, you tell it which URL to submit itself to. And I suspect this is where the problem is, it is somehow declared to always submit to Controller1.
So in your shared view check the declaration of the form, either with <form> or with Html.BeginForm or with Ajax.BeginForm.
Ozan. can you expose and checks the action attribute value of the form in the shared partial view?
...<form ... action='/Controller1/Insert' ...>
In many cases this causes the extrange behavior.
Please checks any html helper that renders your form code ...
Thanks
I'm working on an application that uses a View Model to pass data to a view, however when I attempt to save changes the user makes to the data and post it back to the controller for processing and storing in a database, I'm getting null values for everything in my view model. I've tried various other questions on here and none of the answers I've found have been able to solve this issue. I think it has something to do with the my use of a ListBox to display some of the data, but I'm fairly new to ASP.net MVC 5 and am not quite sure what I need to be doing here.
Below is the code for my view model, the relevant sections from the controller, and the view.
View Model
public class OrganizationViewModel
{
public Organization Organization { get; set; }
public IEnumerable<SelectListItem> RelatedContacts { get; set; }
}
Sending Data from Controller to View
[HttpGet]
public ActionResult Edit(Organization organization)
{
IEnumerable<SelectListItem> contactsList = GetContacts(organization);
var viewModel = new OrganizationViewModel()
{
Organization = organization,
RelatedContacts = contactsList
};
return View("Edit", viewModel);
}
Receiving Data from View
[HttpPost]
public ActionResult SaveOrganization(OrganizationViewModel organizationViewModel)
{
organizationViewModel.Organization.Id = (int)TempData["Id"];
organizationViewModel.Organization.LastEdited = DateTime.Today;
organizationViewModel.Organization.DateAdded = (DateTime)TempData["DateAdded"];
TempData.Clear();
if (ModelState.IsValid)
{
organizationRepository.SaveOrganization(organizationViewModel.Organization);
return RedirectToAction("Index", "Organization");
}
IEnumerable<SelectListItem> contactsList = GetContacts(organizationViewModel.Organization);
var viewModel = new OrganizationViewModel()
{
Organization = organizationViewModel.Organization,
RelatedContacts = contactsList
};
return View("Edit", viewModel);
}
The Edit View
<div class="panel">
<div class="panel-heading">
<h3>Editing #(Model.Organization.Name)</h3>
</div>
#using (Html.BeginForm("SaveOrganization", "Organization", FormMethod.Post))
{
<div class="panel-body">
#Html.HiddenFor(m => Model.Organization.Name)
<div class="row">
<div class="col-md-3 form-group">
<label>Organization Name</label>
#Html.TextBox("Name", null, new { #class = "form-control" })
</div>
<div class="col-md-3 form-group">
<label>Address</label>
#Html.TextArea("Address", null, new { #class = "form-control" })
</div>
</div>
<div class="row">
<div class="col-md-3 form-group">
<label>Related Contacts</label>
#Html.ListBox("RelatedContacts", Model.RelatedContacts, new { #class = "form-control", size = 10 })
</div>
<div class="col-md-3 form-group">
<label>Custom Fields</label>
<br />
<input type="button" value="Add New Field" class="btn btn-default" />
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label>Notes</label>
#Html.TextArea("Notes", null, 10, 500, new { #class = "form-control", #style = "width: 100%; max-width: 100%;" })
</div>
</div>
</div>
<div class="panel-footer">
<input type="submit" value="Save" class="btn btn-primary" />
#Html.ActionLink("Cancel and return to List", "CancelEditOrAdd", null, new { #class = "btn btn-default" })
</div>
}
Answering my own question for anyone who stumbles across this and wants to see the exact changes I made based on #Stephen Muecke's comments.
Changed all the #html.something()s into #html.somethingfor()s for fixed the issue with not receiving postback data for the Organization in the View Model, and then I created a new property in the view model to correspond with the selected index in the ListBox, which now reads #Html.ListBoxFor(m => m.SelectedIndexes, Model.RelatedContacts, ...). I'm now getting all the proper postback data and can relate the selected index to the Organization's related contacts.
Edit: Also changed my View Model to contain the properties of my Organization class and not an actual Organization object itself as a property.
Within a page I have a section where a collection was being checked for null and then based upon the number of items a partial view was rendered out.
#if (#Model.AssociatedNarrative != null)
{
<hr />
<div id="narratives" class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title"><a data-toggle="collapse" href="#collapseNarrative">Narratives [ #Model.AssociatedNarrative.Count() ]</a></h4>
</div>
<div id="collapseNarrative" class="panel-collapse collapse">
<div class="panel-body">
#foreach (var narr in #Model.AssociatedNarrative)
{
#Html.Partial("_NfirsAssociatedNarrative", narr)
}
</div>
</div>
</div>
</div>
</div>
}
</div>
partial view
<div class="col-md-12">
<p><strong>Date Entered</strong> #Html.DisplayFor(x => x.DateEntered)</p>
</div>
#if (!string.IsNullOrWhiteSpace(#Model.Title))
{
<div class="col-md-12">
<p><strong>Narrative Title</strong> #Html.DisplayFor(x => x.Title) </p>
</div>
}
<div class="col-md-12">
<p>#Html.DisplayFor(x => x.NarrativeText)</p>
<hr />
</div>
I changed this functionality so that the data only loads on a click event. Where I am currently stuck is in figuring out the best way to return/render a partial view to the UI when the attached model is a collection. How would I pass this to the UI so that it (partial) renders for each item? Does this need to be handled in the ajax return call or is there a way I can allow the razor syntax / mvc to handle this?
public ActionResult GetNarratives(string id)
{
//Get Narratives
sampleModel fm = new sampleModel();
List<Narrative> narr = new List<Narrative>();
narr = fm.GenerateMockBaseNarratives(4);
return PartialView("_AssociatedNarrative", narr);
}
I would appreciate any suggestions on how to best render the partial view when a collection is the returned type.
-cheers
On first pass through, the code seems to look good, but maybe you are looking for RenderPartial instead of Partial:
#foreach (var narr in #Model.AssociatedNarrative)
{
#Html.RenderPartial("_NfirsAssociatedNarrative", narr)
}
you will need to handle the returned partial view via ajax.
#Ajax.BeginForm(......){ }
The click event will submit the form, and you can define your AjaxOptions to let the form know what you want to update, whether its Post/Get, your insertion mode etc.
In order to get a collection to render within partial view I modified the partial view to now reference a Collection type and then wrapped the contents within a for loop:
#model List<MyApp.Business.Narrative>
<!-- Associated Narrative-->
#for (int i = 0; i < #Model.Count(); i++)
{
<div class="col-md-12">
<p><strong>Date Entered</strong> #Html.DisplayFor(x => x[i].DateEntered)</p>
</div>
if (!string.IsNullOrWhiteSpace(#Model[i].Title))
{
<div class="col-md-12">
<p><strong>Narrative Title</strong> #Html.DisplayFor(x => x[i].Title) </p>
</div>
}
<div class="col-md-12">
<p>#Html.DisplayFor(x => x[i].NarrativeText)</p>
<hr />
</div>
}
From the base view I used #Ajax.ActionLink to reference the controller and method
#Ajax.ActionLink("Narratives", "GetNarratives", new { id = 4 },
new AjaxOptions {
UpdateTargetId = "narrContainer",
InsertionMode = InsertionMode.Replace,
HttpMethod = "GET" })
InsertMode along with UpdateTargetId allowed me to replace a div element in the DOM narrContainer with the partial view
I have a controller with 2 actions: 1 for displaying a form and 2nd for processing the submitted form. Smth like this:
public ActionResult Create()
{
TestModel model = new TestModel() { Value1 = "aaa" };
return View(model);
}
[HttpPost]
public ActionResult Create(TestModel model)
{
model.Value2 = "bbb";
return View(model);
}
As you can see I pre-populate Value1 with "aaa" so it appears on the form - this part works fine. However, when I submit the form I would like to fill other properties of the model object and re-display the form using the same view.
The problem is that on submit the form still displays the original model object and not the updated one i.e. only Value1 is populated with "aaa" and Value2 is empty.
The view I use is the Auto-generated one by scaffolding option:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>TestModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Value1)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Value1)
#Html.ValidationMessageFor(model => model.Value1)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Value2)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Value2)
#Html.ValidationMessageFor(model => model.Value2)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
You have to call ModelState.Clear() so the result will be the expected. MVC does assumptions when POSTed data is sent back to the client. But beware, clearing it manually might cause undesired results ...
You can read more about here: Asp.net MVC ModelState.Clear and http://blogs.msdn.com/b/simonince/archive/2010/05/05/asp-net-mvc-s-html-helpers-render-the-wrong-value.aspx