mvc3 form for a IEnumerable - asp.net-mvc-3

I have a model that I would like have a mass update page for, but am having trouble.
My ViewModel:
public class ApproveView
{
public IEnumerable<MyObject> ObjectList { get; set; }
}
In my view I have:
foreach (var item in Model.ObjectList)
{
<div>
<table class="form" width="100%">
<tr>
<td>#Html.LabelFor(model => item.Accurate)<br />
#Html.RadioButtonFor(model => item.Accurate, true) Yes
#Html.RadioButtonFor(model => item.Accurate, false) No
#Html.ValidationMessageFor(model => item.Accurate)
</td>
<td>
#Html.LabelFor(model => item.Comments)<br />
#Html.TextAreaFor(model => item.Comments)<br />
#Html.ValidationMessageFor(model => item.Comments)
</td>
</tr>
</table>
#Html.HiddenFor(model => item.ID)
#Html.HiddenFor(model => item.CreatedOn)
#Html.HiddenFor(model => item.CreatedBy)
#Html.HiddenFor(model => item.ModifiedOn)
#Html.HiddenFor(model => item.ModifiedBy)
<hr />
}
This loops over my objects and prints the form. The trouble is that all of the fields of the same type have the same name. So, for example, all of my radio buttons are connected and I can only select one.
how do I make the names for each field unique and associated with that object? Am I even on the right track or is there a better way to do this?

Check this post: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
You need to create editor for your entity.
Hope it helps.

You could also do it a bit more manually:
#{var count = 0;}
#foreach (var item in Model.ObjectList){
<div>
<table class="form">
<tr>
<td>#Html.Label("Accurate" + count, item.Accurate)<br />
#Html.RadioButton("AccurateTrue" + count, true) Yes
#Html.RadioButton("AccurateFalse" + count, false) No
#Html.ValidationMessage("ValidationAccurate" + count, item.Accurate)
</td>
<td>
#Html.Label("CommentsLabel" + count, item.Comments)<br />
#Html.TextArea("Comments" + count, item.Comments)<br />
#Html.ValidationMessage("ValidationComment" + count, item.Comments)
</td>
</tr>
</table>
#Html.Hidden("ID" + count, item.ID)
#Html.Hidden("CreatedOn" + count, item.CreatedOn)
#Html.Hidden("CreatedBy" + count, item.CreatedBy)
#Html.Hidden("ModifiedOn" + count, item.ModifiedOn)
#Html.Hidden("ModifiedBy" + count, item.ModifiedBy)
<hr />
#count++;
#}

Related

IEnumerable Model from view to Controller is NULL [duplicate]

This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 4 years ago.
I am trying to return table values of my view back to the controller to save on db but I keep getting null. I can post the values without problem and bind them to the view.
I cannot understand why, I am using a server side view model.
Is there any way to perform this?
View:
#model IEnumerable<MultiEdit.Models.TableViewModel>
#using (Ajax.BeginForm("Save", "UUTs", new AjaxOptions
{
HttpMethod = "Post",
}, new { id = "tableForm" }))
{
#Html.AntiForgeryToken()
<div class="row" style="padding-top:10px;">
<div class="col-lg-12">
<table class="table table-bordered table-striped ">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.IsChassis)
</th>
<th>
#Html.DisplayNameFor(model => model.Justification)
</th>
</tr>
</thead>
<tbody id="tblMultiEdit">
#foreach(var item in Model)
{
<tr>
<td>
#Html.CheckBoxFor(modelItem => item.CheckIsChassis)
</td>
<td>
#Html.EditorFor(modelItem => item.Justification)
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
Controller:
public void Save(IEnumerable<TableViewModel> vm)
{
DoSomething();
}
You need to iterate through your collection with a for loop and index qualifier. Something like the below. The syntax is not exact but you should be able to see what I mean.
#for(var index = 0; index <= Model.Count - 1; index++)
{
<tr>
<td>
#Html.CheckBoxFor(modelItem => Model[index].CheckIsChassis)
</td>
<td>
#Html.EditorFor(modelItem => Model[index].Justification)
</td>
</tr>
}
This is required so the index can be used to create the unique id's of the Enumerable items and aids model binding
Hope that helps
I resolved it by using IList insead of IEnumerable.
View:
#model IList<MultiEdit.Models.TableViewModel>
#Html.CheckBoxFor(modelItem => modelItem[index].CheckIsChassis, new { #class = "form-control" })
Controller:
public void Save(IList<TableViewModel> vm)
{
var x = vm;
}

MVC ViewModel data gets clear after posting and showing validation error

my problem it's kinda strange. So in a Index.cshtml i populate with data and in some place i use
#Html.Action("_Create")
To create a partialView where i populate with some data from the Server and with some empty fields that need to be completed for submit.
[HttpGet]
public PartialViewResult _Create()
{
var userOrder = _orderRepository.CurrentUser(User.Identity.GetUserName());
var something = new CurrentListOrderViewModel()
{
PersonName = userOrder.Select(p=>p.PersonName).FirstOrDefault(),
Funds = userOrder.Select(p=>p.Funds).FirstOrDefault(),
Order = "",
OrderCosts = 0,
Restaurant = "",
TodayOrderDate = DateTime.Now,
WindowsName = User.Identity.GetUserName()
};
return PartialView(something);
}
[HttpPost]
[ValidateAntiForgeryToken]
public PartialViewResult _Create([Bind(Include = "PersonName,Funds,Order,OrderCosts,WindowsName,TodayOrderDate")]CurrentListOrderViewModel model)
{
if(ModelState.IsValid)
{
return PartialView();
}
return PartialView(model);
}
So the problem is when the page loads with HTTPGET everything goes fine, the table get's populated from the database and if i change in the _Create.cshtml the #Html.EditorFor into something else so i can disable it, i lose the data. What could be the cause? I don't really understand it.
#model FoodOrderQubizInterfaces.ViewModel.Order.CurrentListOrderViewModel
#using(Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<tr>
<td>
#Html.EditorFor(model => model.PersonName)
#Html.ValidationMessageFor(model => model.PersonName)
</td>
<td>
#Html.EditorFor(model => model.Funds, new { htmlAttributes = new { disabled = "disabled" }, })
#Html.ValidationMessageFor(model => model.Funds)
</td>
<td>
#Html.EditorFor(model => model.Order)
#Html.ValidationMessageFor(model => model.Order)
</td>
<td>
#*#Html.DropDownListFor(model => model.Restaurant,*#
<select>
<option value="1">Bigys</option>
<option value="2">Zulu</option>
</select>
</td>
<td>
#Html.EditorFor(model => model.OrderCosts)
#Html.ValidationMessageFor(model => model.OrderCosts)
</td>
<td>
#Html.EditorFor(model => model.WindowsName)
#Html.ValidationMessageFor(model => model.WindowsName)
<input type="submit" value="Save/Modify" class="btn btn-default" />
</td>
<td>
#Html.EditorFor(model => model.TodayOrderDate)
#Html.ValidationMessageFor(model => model.TodayOrderDate)
</td>
<td>
</td>
</tr>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Hope you guys can help me with some advice cause i'm out of ideas
I found the problem, after i search the web for some time. The cause was when you submit and you have the disabled attribute added it will not add to the model cause he thinks the model is disabled so the user couldn't interact with it.
So to fix it i changed from
<td>
#Html.EditorFor(model => model.Funds, new { htmlAttributes = new { disabled = "disabled" }, })
#Html.ValidationMessageFor(model => model.Funds)
</td>
To
#Html.TextBoxFor(model => model.Funds, new { #readonly = true })
And in that was it keeps the data. I hope it will help someone else with my problem

MVC3 Razor and the browser (IE9) back button issue

I am developing a MVC 3 Razor app and I'm having issues when I click the back button in the browser. My application workflow:
Select a facility from a dropdown list
A WebGrid gets populated with a list of the facility's buildings.
Click an image to edit a building
Click the browser's back button and the dropdown list in step 1 appears without the CSS formatting. If I click F5 to refresh then everything resets and the CSS formatting is back.
I'm using VS2010 and the ASP.NET development server with IE9 as the default browser. I have added the OutputCache attribute to every ActionResult in the controller.
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]
Here's my link in the WebGrid built from a PartialView
grid.Column(header: "",
format: #<text><a href="#Url.Action("Edit", "BuildingModels", new { #id = item.FACInventoriesId })"><img src="#Url.Content("~/Content/images/edit.png")"
alt='Edit' title='Edit' border='0'/></a></text>)
How do I get a browser to show the WebGrid (step 2) when the back button is click from editing a building (step 4)? Also any ideas why the CSS formatting is missing when I click the back button?
Here's the Controller code:
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]
public ViewResult Index()
{
ViewBag.Systems = buildingsVM.GetSystemsList();
return View();
}
[HttpPost]
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]
public ActionResult GetFacilityDetails(int systemId, string facilityId)
{
try
{
ViewBag.Systems = buildingsVM.GetSystemsList();
var facility = buildingsVM.GetFacilityDetails(systemId, facilityId);
facility.Buildings = buildingsVM.GetFacilityBuildings(systemId, facilityId);
var bldgsHtml = ViewsUtility.RenderPartialViewToString(this, "_Buildings", facility.Buildings);
TempData["CurrentFacility"] = facility;
return Json(new { ok = true, facdata = facility, bldgs = bldgsHtml, message = "ok" });
}
catch (Exception ex)
{
return Json(new { ok = false, message = ex.Message });
}
}
[HttpPost]
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]
public ActionResult GetSystemFacilities(int systemId)
{
try
{
var facilities = buildingsVM.GetFacilitesBySystemId(systemId);
return Json(new { ok = true, data = facilities, message = "ok" });
}
catch (Exception ex)
{
return Json(new { ok = false, message = ex.Message });
}
}
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]
public ActionResult Edit(int id)
{
var facility = TempData["CurrentFacility"] as FacilityModel;
return View(buildingsVM.GetBuilding(id));
}
Code from Partial View:
#model IEnumerable<COPSPlanningWeb.Models.BuildingModel>
<!-- Current Buildings from partial view -->
#{
if (Model != null && Model.Count() > 0)
{
var grid = new WebGrid(rowsPerPage: 50, defaultSort: "BuildingNumber"); //ajaxUpdateContainerId: "tabs-2",
grid.Bind(Model, rowCount: Model.Count(), autoSortAndPage: false);
grid.Pager(WebGridPagerModes.All);
#grid.GetHtml(
tableStyle: "webgridDisplay",
alternatingRowStyle: "alt",
columns: grid.Columns(
//grid.Column(format: (item) => Html.ActionLink("Edit", "Edit", new { EmployeeID = item.EmployeeID, ContactID = item.ContactID })),
grid.Column("BuildingNumber", header: "Building Number", style: "webgridDisplayCenter"),
grid.Column("ConstructionDate", header: "Construction Date", format: #<text>#item.ConstructionDate.ToString("MM/dd/yyyy")</text>),
grid.Column("ExtSquareFeet", header: "Exterior Sq. Ft.", format: (item) => string.Format("{0:n0}", item.ExtSquareFeet)),
grid.Column("IntSquareFeet", header: "Interior Sq. Ft.", format: (item) => string.Format("{0:n0}", item.IntSquareFeet)),
grid.Column("IU_Avail", header: "IU Available"),
grid.Column("SpaceAvail", header: "Space Available"),
grid.Column("FixedAssetValue", header: "Fixed Asset Value", format: (item) => string.Format("{0:C}", item.FixedAssetValue)),
grid.Column("FixedEquipValue", header: "Fixed Equipment Value", format: (item) => string.Format("{0:C}", item.FixedEquipValue)),
grid.Column(header: "",
format: #<text><a href="#Url.Action("Edit", "BuildingModels", new { #id = item.FACInventoriesId })"><img src="#Url.Content("~/Content/images/edit.png")"
alt='Edit' title='Edit' border='0'/></a></text>),
grid.Column(header: "",
format: #<text><a href="#Url.Action("Delete", "BuildingModels", new { #id = item.FACInventoriesId })"><img src="#Url.Content("~/Content/images/trash.png")"
alt='Delete' title='Delete' border='0'/></a></text>)
))
}
}
Code from Edit view:
#model COPSPlanningWeb.Models.BuildingModel
#{
ViewBag.Title = "Add/Edit Inventory";
}
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<table style="width: 100%;" class="display">
#Html.HiddenFor(model => model.FacilityId)
#Html.HiddenFor(model => model.FACInventoriesId)
<tr>
<th colspan="2" style="text-align: left;">
Building Information - Edit Inventory
</th>
</tr>
<tr>
<td class="fieldlabel">
Facility Name
</td>
<td class="fielddata">
</td>
</tr>
<tr>
<td class="fieldlabel">
Building Number
</td>
<td class="fielddata">
#Html.EditorFor(model => model.BuildingNumber)
#Html.ValidationMessageFor(model => model.BuildingNumber)
</td>
</tr>
<tr>
<td class="fieldlabel">
Construction Date
</td>
<td class="fielddata">
#Html.EditorFor(model => model.ConstructionDate, "DateTime")
#Html.ValidationMessageFor(model => model.ConstructionDate)
</td>
</tr>
<tr>
<td class="fieldlabel">
Exterior Sq. Ft.
</td>
<td class="fielddata">
#Html.EditorFor(model => model.ExtSquareFeet)
#Html.ValidationMessageFor(model => model.ExtSquareFeet)
</td>
</tr>
<tr>
<td class="fieldlabel">
Interior Sq. Ft.
</td>
<td class="fielddata">
#Html.EditorFor(model => model.IntSquareFeet)
#Html.ValidationMessageFor(model => model.IntSquareFeet)
</td>
</tr>
<tr>
<td class="fieldlabel">
IU Available
</td>
<td class="fielddata">
#Html.EditorFor(model => model.IU_Avail)
#Html.ValidationMessageFor(model => model.IU_Avail)
</td>
</tr>
<tr>
<td class="fieldlabel">
Space Available
</td>
<td class="fielddata">
#Html.EditorFor(model => model.SpaceAvail)
#Html.ValidationMessageFor(model => model.SpaceAvail)
</td>
</tr>
<tr>
<td class="fieldlabel">
Fixed Asset Value
</td>
<td class="fielddata">
#Html.EditorFor(model => model.FixedAssetValue)
#Html.ValidationMessageFor(model => model.FixedAssetValue)
</td>
</tr>
<tr>
<td class="fieldlabel">
Fixed Equipment Value
</td>
<td class="fielddata">
#Html.EditorFor(model => model.FixedEquipValue)
#Html.ValidationMessageFor(model => model.FixedEquipValue)
</td>
</tr>
<tr>
<td class="fieldlabel">
Total Fixed Asset Value
</td>
<td class="fielddata">
#Html.EditorFor(model => model.TotalFixedAssetValue)
#Html.ValidationMessageFor(model => model.TotalFixedAssetValue)
</td>
</tr>
<tr>
<td class="fieldlabel">
Total Fixed Equipment Value
</td>
<td class="fielddata">
#Html.EditorFor(model => model.TotalFixedEquipValue)
#Html.ValidationMessageFor(model => model.TotalFixedEquipValue)
</td>
</tr>
<tr>
<td colspan="2">
<table class="display" style="text-align: center;">
<tr>
<td>
#Html.ActionLink("Add/Edit Spaces", "Index")
</td>
<td>
<input type="submit" value="Save Changes" class="button" />
</td>
<td>
#Html.ActionLink("Back to Buildings List", "Index")
</td>
</tr>
</table>
</td>
</tr>
</table>
}
When I click the back button from the Edit view I expected to see the WebGrid again (building list), but instead I get the first view without any CSS formatting.
Thanks to Luis I was able to resolve the CSS formatting issue, but I'm still not seeing the WebGrid when I click the back button. I'm using JSON to populate the WebGrid could this be the problem? Should I use a form post after the item in the dropdown has been selected?
Something similar happened to me making an app for the intranet... but hey cheer up, at least your company uses IE9... I had to make miracles trying to make a MVC3 Razor app with JQuery work with IE7...
Ok now to what's important, I had a similar issue with the cache of IE, it appears that the cache of this browser works "differently" from the normal-new-age browsers, you could try this:
Press F12 and go to the tab Cache and check Always refresh from server
Then check if everything works as it should do, if it does, tell your network administrator to make a new policy for all the IE browsers that are going to use this new app you're making.
Also check this https://superuser.com/questions/81182/how-to-force-internet-explorer-ie-to-really-reload-the-page
Hope it helps!
You need to add the Location to the attribute to get this to work with IE9
[OutputCache(Location = OutputCacheLocation.ServerAndClient, NoStore = true, Duration = 0, VaryByParam = "None")]

MVC 3 Razor - Form not posting back to controller

I am using MVC 3 and Razor, attempting to post a form back to a controller from a telerik window (telerik.window.create) that loads a partial view. Im not sure how to post this so ill just post the code in order of execution and explain it as I go.
First an anchor is clicked, and onClick calls:
function showScheduleWindow(action, configId) {
var onCloseAjaxWindow = function () { var grid = $("#SubscriptionGrid").data("tGrid"); if (grid) { grid.rebind(); } };
CreateAjaxWindow("Create Schedule", true, false, 420, 305, "/FiscalSchedule/" + action + "/" + configId, onCloseAjaxWindow);
}
And the CrateAjaxWindow method:
function CreateAjaxWindow(title, modal, rezible, width, height, url, fOnClose) {
var lookupWindow = $.telerik.window.create({
title: title,
modal: modal,
rezible: rezible,
width: width,
height: height,
onClose: function (e) {
e.preventDefault();
lookupWindow.data('tWindow').destroy();
fOnClose();
}
});
lookupWindow.data('tWindow').ajaxRequest(url);
lookupWindow.data('tWindow').center().open();
}
Here is the partial view that is being loaded:
#model WTC.StatementAutomation.Web.Models.FiscalScheduleViewModel
#using WTC.StatementAutomation.Model
#using WTC.StatementAutomation.Web.Extensions
#using (Html.BeginForm("Create", "FiscalSchedule", FormMethod.Post, new { id = "FiscalScheduleConfigForm" }))
{
<div id="FiscalScheduleConfigForm" class="stylized">
<div class="top">
<div class="padding">
Using fiscal year end: #Model.FiscalYearEnd.ToString("MM/dd")
</div>
<div class="padding Period">
<table border="0">
<tr>
<th style="width: 120px;"></th>
<th>Effective Date</th>
<th>Next Run</th>
<th>Start From Previous</th>
</tr>
<tr>
<td>
#Html.CheckBoxFor(m => m.HasMonthly)
<label>Monthly</label>
</td>
<td>
#{ var month = Model.GetForFiscalPeriod(FiscalPeriodStatementSchedule.FiscalPeriod.Monthly);}
#month.BaseSchedule.StartDate.ToString("MM/01/yyyy")
</td>
<td>
#month.BaseSchedule.NextScheduleRun.ToString("MM/dd/yyyy")
</td>
<td class="previous">
#(month.HasRun ? Html.CheckBoxFor(m => month.BaseSchedule.StartFromPreviousCycle, new { #disabled = "disabled", #readonly = "readonly" }) : Html.CheckBoxFor(m => month.BaseSchedule.StartFromPreviousCycle))
</td>
</tr>
<tr>
<td>
#Html.CheckBoxFor(m => m.HasQuarterly) Quarterly
</td>
<td>
#{ var quarter = Model.GetForFiscalPeriod(FiscalPeriodStatementSchedule.FiscalPeriod.Quarterly);}
#quarter.BaseSchedule.StartDate.ToString("MM/01/yyyy")
</td>
<td>
#quarter.BaseSchedule.NextScheduleRun.ToString("MM/dd/yyyy")
</td>
<td class="previous">
#(quarter.HasRun ? Html.CheckBoxFor(m => quarter.BaseSchedule.StartFromPreviousCycle, new { #disabled = "disabled", #readonly = "readonly" }) : Html.CheckBoxFor(m => quarter.BaseSchedule.StartFromPreviousCycle))
</td >
</tr>
<tr>
<td>
#Html.CheckBoxFor(m => m.HasAnnual) Annual
</td>
<td>
#{ var annual = Model.GetForFiscalPeriod(FiscalPeriodStatementSchedule.FiscalPeriod.Annual);}
#annual.BaseSchedule.StartDate.ToString("MM/01/yyyy")
</td>
<td>
#annual.BaseSchedule.NextScheduleRun.ToString("MM/dd/yyyy")
</td>
<td class="previous">
#(annual.HasRun ? Html.CheckBoxFor(m => annual.BaseSchedule.StartFromPreviousCycle, new { #disabled = "disabled", #readonly = "readonly" }) : Html.CheckBoxFor(m => annual.BaseSchedule.StartFromPreviousCycle))
</td>
</tr>
<tr>
<td>
#Html.CheckBoxFor(m => m.HasSemiAnnual) Semi-annual
</td>
<td>
#{ var semi = Model.GetForFiscalPeriod(FiscalPeriodStatementSchedule.FiscalPeriod.SemiAnnual);}
#semi.BaseSchedule.StartDate.ToString("MM/01/yyyy")
</td>
<td>
#semi.BaseSchedule.NextScheduleRun.ToString("MM/dd/yyyy")
</td>
<td class="previous">
#(semi.HasRun ? Html.CheckBoxFor(m => semi.BaseSchedule.StartFromPreviousCycle, new { #disabled = "disabled", #readonly = "readonly" }) : Html.CheckBoxFor(m => semi.BaseSchedule.StartFromPreviousCycle))
</td>
</tr>
</table>
</div>
<div class="padding StartDay">
<span>Run on day:</span>
#Html.TextBoxFor(model => model.StartDay)
<span>of every period.</span>
</div>
</div>
<div class="bottom">
<div class="padding">
<div style="float: left;">
#if (Model.ShowSuccessSave)
{
<div id="successSave" class="label">Changes saved succesfully</div>
}
#Html.ValidationSummary(true)
#Html.HiddenFor(x => x.SubscriptionId)
#Html.HiddenFor(x => x.DeliveryConfigurationId)
#Html.HiddenFor(x => x.FiscalYearEnd)
</div>
<a id="saveSchedule" class="btn" href="">Save</a>
</div>
</div>
</div>
}
<script type="text/javascript">
$(function () {
$('a#saveSchedule').click(function () {
$(this).closest("form").submit();
return false;
});
});
</script>
And finally the controller method:
[HttpPost]
public ActionResult Create(FormCollection formValues, int subscriptionId, int deliveryConfigurationId, int startDay, DateTime fiscalYearEnd)
{
if (ModelState.IsValid)
{
var selectedSchedules = GetCheckedSchedulesFromForm(formValues);
var startFromPrevious = GetFromPreviouSelections(formValues);
this.AddModelErrors(_fiscalScheduleService.AddUpdateSchedules(selectedSchedules, subscriptionId, deliveryConfigurationId, startDay, startFromPrevious));
}
return new RenderJsonResult { Result = new { success = true, action = ModelState.IsValid ? "success" : "showErrors",
message = this.RenderPartialViewToString("_FiscalScheduleConfigForm",
BuildResultViewModel(deliveryConfigurationId, subscriptionId, fiscalYearEnd, ModelState.IsValid)) } };
}
As you can see I am using jQuery to post back to the controller, which I have done on several occasions in the applicaiton, this seems to work fine normally. But with this form, for some reason it is not posting back or stepping into the Create method at all. I am speculating that it has something to do with the parameters on the controller method. But I am fairly new to MVC (coming from ASP.NET world) so Im not really sure what I am doing wrong here. Any help would be greately appreciated!
I was able to get it to post to the controller by modifying the textboxfor for the startDay:
Changed from:
#Html.TextBoxFor(model => model.StartDay)
To:
#Html.TextBoxFor(model => model.StartDay, new { id = "startDay" })
My guess is that you're running on a virtual directory in IIS? That url you're generating is likely the culprit.
Hit F12, check out the network tab (and enable tracing) and see what it's trying to request.
Instead of building the link through text, why not use #Url.Action()? You could store this in an attribute on the a tag (like in an attribute called data-url, for example) and then use that info to make your call. It's pretty easy to pull out the attribute with jQuery, something like this:
$('.your-link-class').click(function(){
var url = $(this).attr('data-url');
// proceed with awesomesauce
});
Would something like that work for you?
[shameless self plug] As far as the controller action signature goes, you might want to look into model binding if you can. One simple class and many of your headaches will go away. You can read more here, read the parts on model binding. There are downloadable samples for different approaches.
Cheers.

ViewModel not updating on POST

I have a Controller and View which allow a user to 'checkout' the contents of a shopping cart.
All was working fine when my CheckoutController was returning an Order model to the Checkout view. However, I now want to also display the contents of the shopping cart on the Checkout view so have modified the Controller and View to use a viewmodel containing the Order and CartItems.
I have created a CheckoutViewModel and modified the GET: and POST: ActionResults to use this viewmodel.
Problem is the CheckOutViewModel is not being properly populated during the POST:
I am getting an 'Object reference not set to an instance of an object' error on this line 'checkoutViewModel.Order.Username = User.Identity.Name;' in the POST: ActionResult but I am not sure exactly what is not being instansiated.
If need be, I can post the original working coded that used the Oreder model instead of the CheckoutViewModel.
I'm sure I have some simple sturctural or syntax problem, I just can't tell what it is because I'm still trying to get my head around the best way to do things in MVC.
GET: ActionResult
//GET: /Checkout/AddressAndPayment
public ActionResult AddressAndPayment()
{
//To pre-populate the form, create a new Order object and get the ShoppingCart, populate the ViewModel and pass it to the view
var order = new Order();
order.Username = User.Identity.Name;
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
storeDB.SaveChanges();
var cart = ShoppingCart.GetCart(this.HttpContext);
// Set up our ViewModel
var viewModel = new CheckoutViewModel
{
CartItems = cart.GetCartItems(),
CartTotal = cart.GetTotal(),
Order = order
};
// Return the view
return View(viewModel);
}
POST: ActionResult
//POST: /Checkout/AddressAndPayment
[HttpPost]
public ActionResult AddressAndPayment(FormCollection values)
{
var checkoutViewModel = new CheckoutViewModel();
TryValidateModel(checkoutViewModel);
try
{
checkoutViewModel.Order.Username = User.Identity.Name;
checkoutViewModel.Order.OrderDate = DateTime.Now;
//Save Order
storeDB.Orders.Add(checkoutViewModel.Order);
storeDB.SaveChanges();
//Process the order
var cart = ShoppingCart.GetCart(this.HttpContext);
cart.CreateOrder(checkoutViewModel.Order);
storeDB.SaveChanges();
//Send 'Order Confirmation' email
ViewData["order"] = checkoutViewModel.Order;
UserMailer.OrderConfirmation(checkoutViewModel.Order).SendAsync();
return RedirectToAction("Complete", new { id = checkoutViewModel.Order.OrderID });
}
catch
{
//Invalid - redisplay with errors
return View(checkoutViewModel);
}
}
View
#model OrderUp.ViewModels.CheckoutViewModel
#{
ViewBag.Title = "AddressAndPayment";
}
<script src="../../Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<h2>Pick Up Details</h2>
<fieldset>
<legend>When are you gonna be hungry?</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Order.Phone)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Order.Phone)
#Html.ValidationMessageFor(model => model.Order.Phone)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Order.PickUpDateTime)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Order.PickUpDateTime)
#Html.ValidationMessageFor(model => model.Order.PickUpDateTime)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Order.Notes)
</div>
<div class="editor-multiline-field">
#Html.EditorFor(model => model.Order.Notes)
#Html.ValidationMessageFor(model => model.Order.Notes)
</div>
</fieldset>
<input type="submit" value="Submit Order" />
}
<div style="height:30px;"></div>
<h3>
<em>Review</em> your cart:
</h3>
<table>
<tr>
<th>
Menu Item
</th>
<th>
Price (each)
</th>
<th>
Notes
</th>
<th>
Quantity
</th>
<th></th>
</tr>
#foreach (var item in Model.CartItems)
{
<tr id="row-#item.RecordID">
<td>
#Html.ActionLink(item.MenuItem.Name, "Details", "Store", new { id = item.MenuItemID }, null)
</td>
<td>
#Html.DisplayFor(modelItem => item.MenuItem.Price)
</td>
<td>
#Html.DisplayFor(modelItem => item.Notes )
</td>
<td id="item-count-#item.RecordID">
#item.Count
</td>
<td>
</td>
</tr>
}
<tr>
<td >
Total
</td>
<td id="cart-total">
#String.Format("${0:F2}", Model.CartTotal)
</td>
<td>
</td>
<td>
</td>
<td>
</td>
</tr>
</table>
You should use CheckoutViewModel as an input parameter to your HttpPost method and then try debug and see if you are still not getting that model populated after the form post.
[HttpPost]
public ActionResult AddressAndPayment(CheckOutViewModel checkoutViewModel)
{
TryValidateModel(checkoutViewModel);
try
{
checkoutViewModel.Order.Username = User.Identity.Name;
checkoutViewModel.Order.OrderDate = DateTime.Now;
//Save Order
storeDB.Orders.Add(checkoutViewModel.Order);
storeDB.SaveChanges();
//Process the order
var cart = ShoppingCart.GetCart(this.HttpContext);
cart.CreateOrder(checkoutViewModel.Order);
storeDB.SaveChanges();
//Send 'Order Confirmation' email
ViewData["order"] = checkoutViewModel.Order;
UserMailer.OrderConfirmation(checkoutViewModel.Order).SendAsync();
return RedirectToAction("Complete", new { id = checkoutViewModel.Order.OrderID });
}
catch
{
//Invalid - redisplay with errors
return View(checkoutViewModel);
}
}

Resources