Modelbinding using Interfaces in ASP.NET MVC 2 - asp.net-mvc

I have the following View Data:
public class ShoppingCartViewData
{
public IList<IShoppingCartItem> Cart
{
get;
set;
}
}
I populate the viewdata in my controller:
viewData.Cart = CurrentSession.CartItems;
return View(viewData);
And send the data to the view and display it using:
<% for (int i = 0; i < Model.Cart.Count; i++ ) { %>
<%= Html.TextBoxFor(m => m.Cart[i].Quantity)%>
<%= Html.HiddenFor(m => m.Cart[i].Id) %>
<% } %>
I want to be able to catch the viewdata on the post. When I try:
[HttpPost]
public ActionResult UpdateCart(ShoppingCartViewData viewData)
{
...
}
When I run this I get a: System.MissingMethodException: Cannot create an instance of an interface.
Can anyone shed some light on this. What would I have to do to get this to work?
Many Thanks

You could try adding the formcollection as a parameter. And shouldn't viewdata be the viewmodel you're using?
[HttpPost]
public ActionResult UpdateCart(ShoppingCartViewModel viewModel, FormCollection collection)
{
...
}
Not sure if this is the exact solution, i'm also busy learning MVC2.0 and .NET4 ;-)

I'd create a model binder for your ViewModel, and then you can instantiate a concrete type that implements the appropriate interface when it binds to the method parameters.
You can insert logic into your model binder to read the form fields as appropriate and then instantiate the right IList or IShoppingCartItem data, so no need to worry about being pinned to a single implementation of the interface either.

Given my two comments this is how I would do it:
// you don't need this
// viewData.Cart = CurrentSession.CartItems;
// return View(viewData);
// do it like this
return View(CurrentSession.CartItems);
Then have a strongly typed View either this:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Administration.Master" Inherits="System.Web.Mvc.ViewPage<ShoppingCartViewData>" %>
or this:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Administration.Master" Inherits="System.Web.Mvc.ViewPage<List<IShoppingCartItem>>" %>
Also this code won't work. This will generate you a bunch of textboxes with the same name and id. You need to generate textboxes with a count and for that you won't be able to use
Html.TextBoxFor(). You will have to revert to Html.TextBox() or create a new extension TextBoxFor() method which would also accept a number (for you count).
<% for (int i = 0; i < Model.Cart.Count; i++ ) { %>
<%= Html.TextBoxFor(m => m.Cart[i].Quantity)%> // this won't work, if you want to post back all textboxes after they are edited
<%= Html.HiddenFor(m => m.Cart[i].Id) %>
<% } %>
HTH

I got the same exception after adding an 'int' parameter to an action method.
I discovered by putting a break point in the controllers constructor that one of the other action methods (not the one specified in the forms post arguments) was being called instead.

Related

How to pass multiple Html.DropDownList selected values from View( .aspx ) to MVC controller's action?

I need to pass multiple data ( probably 2 Html.DropDownList's selected values ) to MVC controller action method from MVC View ( .aspx). I think it would be from somehow Html.Hidden form , but how?
I am unable to get the selected value from Html.DropDownList and pass it as Html.Hidden("paramName", MvcStringSelectedValue) to controller's action.
My Code is :
based on<br />
<%: Html.DropDownList("Semester")%>
<%= Html.Hidden("strSemesterToBaseOn",returnedValueFromAbove)%>
<%: Html.ValidationSummary(true) %>
<input type="submit" value="Clone" />
<% } %>
<br/><br/>
Do I need to write the input tag of "submitt" 2 times or just only once?
Edit ( EXTRA CODE )
Controller's action method :
[HttpPost]
public ActionResult CloneSemesterData(string strSemesterToOrganize, string strSemesterToBaseOn)
{
.............................................................
..............................
}
HERE ( another Controller's method ) IS THE DROP DOWN LIST Filled with Semester values
public ActionResult DepartmentAdministration()
{
// Get list of semesters
var lr = new ListRepository();
ViewData["Semester"] = new SelectList(lr.ListSemester(3)); //this ListSemester(3) will generate the list with 3 strings ( e.g "WS 2012", "SS2010")
return View();
}
My View code in .aspx file is :
//this executes when radioButton ="Clone" is selected
<% using (Html.BeginForm("CloneSemesterData", "CourseNeededHours"))
{%>
<%= Html.DropDownList("Semester")%> // this is First drop down list box , from which selected value , I want to transfer as 1st parameter of controller's action method
<%: Html.ValidationSummary(true) %>
based On
<%= Html.DropDownList("Semester")%> //this is Second drop down list box, from which selected value, I want to transfer as 2nd parameter of controller's action method.
<input type="submit" value="Clone" />
<% } %>
ERROR:
Now, after fixing using Edit 2 : it is giving red lines under
as it is somehow not recognizing the ViewData["SemesterList"]...
"System.Web.Mvc.HtmlHelper does not contain a definition for 'DropDownList' and the best extension method overloaded 'System.Web.Mvc.Html.SelectExtensions.DropDownList(System.Web.Mvc.HtmlHelper, string,System.Collections.Generic.IEnumerable') has some invalid arguments".
Hope now it will clear, still ambiguity , do let me know then.
Regards
Usman
I am not really sure what you're asking here. You don't need any kind of hidden field to post the selected values of a dropdown. Your Dropdownlist code is invalid to begin with.
Typically you have something like this:
<%= Html.DropDownList("SemesterToOrganize", GetSemesterToOrganize()) %>
<%= Html.DropDownList("SemesterToBaseOn", GetSemesterToBaseOn()) %>
And in your controller:
[HttpPost]
public ActionResult MyAction(string SemesterToOrganize, string SemesterToBaseOn) {
// your code.
}
EDIT:
Based on what you've told us. You are relying on the behavior of MVC of populating the DropDownList because you are adding your list to the ViewData with the same name as your dropdownlist. This won't work for you. You will have to populate each dropdown list seperately.
In your controller, do something like this:
public ActionResult MyAction ()
{
ViewData["SemesterList"] = // list of semesters
return View();
}
Then, in your view you have:
<%= Html.DropDownList("SemesterToOrganize", ViewData["SemesterList"]) %>
<%= Html.DropDownList("SemesterToBaseOn", ViewData["SemesterList"]) %>
then your post method
[HttpPost]
public ActionResult MyAction(string SemesterToOrganize, string SemesterToBaseOn) {
// your code.
}
If you want to continue to argue that you can do it your way, then you won't solve your problem. Each dropdown must have it's own unique id, otherwise it will not post correctly. The only way to solve this problem is to give each it's own unique id. That breaks the behavior of the drop down automatically getting the data, so you MUST specify the list of data explicitly.
So stop arguing that this is an unimportant part of the problem. It's not. It's key to the problem.
EDIT2:
Based on your code above:
<%= Html.DropDownList("strSemesterToOrganize", (SelectList)ViewData["Semester"]) %>
<%= Html.DropDownList("strSemesterToBaseOn", (SelectList)ViewData["Semester"]) %>
That's all you need
If you had just given us this, and didn't argue, this would been solved a lot easier.
// Try this. Change names and put in the appropriate namespace.
//Your view
#model MvcApplication2.Models.CloneSemesterDataViewModel
#Html.LabelFor(x => x.SemesterToOrganize)
#Html.DropDownListFor(x => x.SemesterToOrganize, Model.ListofSemestersToOrganize)
--------
#Html.LabelFor(x => x.SemesterToBaseOn)
#Html.DropDownListFor(x => x.SemesterToBaseOn, Model.ListofSemestersToBaseOn)
//view model
namespace MvcApplication2.Models
{
public class CloneSemesterDataViewModel
{
public string SemesterToOrganize { get; set; }
public IEnumerable<SelectListItem> ListofSemestersToOrganize
{
get
{
return new List<SelectListItem> { new SelectListItem { Text = "SS2012" , Value = "SS2012"} };
}
}
public string SemesterToBaseOn { get; set; }
public IEnumerable<SelectListItem> ListofSemestersToBaseOn
{
get
{
return new List<SelectListItem> { new SelectListItem { Text = "SS2012", Value = "SS2012" } };
}
}
}
}
----------
Controller.
[HttpPost]
public ActionResult CloneSemesterData(CloneSemesterDataViewModel viewModel)
{
//viewModel.SemesterToBaseOn
//viewModel.SemesterToOrganize
}
// This should do the trick.

POST Model to Controller Method

I've been learning MVC 3 rapidly over the last couple weeks but something's come up that I just haven't been able to solve searching for hours. I'm developing a simple shopping cart and I'm trying to pass data in a linear path through the checkout process. I've been unable to get a model to POST to the next view no matter what I try.
To start with, the 'Cart' entity is being pulled from Session using an implementation of IModelBinder. It's essentially available for any method. It's been working great for a while. My issue is trying to pass the same model between /cart/confirm and /cart/checkout.
Can someone help figure out why the model is always empty in the controller for /cart/checkout?
public class CartController : Controller
{
public ActionResult Index (Cart cart)
{
//Works fine, anonymous access to the cart
return View(cart);
}
[Authorize]
public ActionResult Confirm (Cart cart)
{
//Turn 'Cart' from session (IModelBinder) into a 'Entities.OrderDetail'
OrderDetail orderDetail = new OrderDetail();
orderDetail.SubTotal = cart.ComputeTotalValue();
...
...
return View(orderDetail);
}
[Authorize]
public ActionResult Checkout(OrderDetail model)
{
//PROBLEM: model is always null here.
}
}
/Views/Cart/Index.aspx looks like this (sorry, no Razor):
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site-1-Panel.Master" Inherits="System.Web.Mvc.ViewPage<My.Namespace.Entities.Cart>" %>
...
...
<% using(Html.BeginForm("confirm", "cart")) { %>
Not much to see here, just a table with the cart line items
<input type="submit" value="Check Out" />
<% } %>
I suspect the problem is here, but I've tried every variation of Html.BeginForm() I can try and can't get the model to pass to /cart/checkout. Anyway, /Views/Cart/Confirm.aspx looks like this:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site-1-Panel.Master" Inherits="System.Web.Mvc.ViewPage<My.Namespace.Entities.OrderDetail>" %>
...
...
<% using (Html.BeginForm("checkout", "cart", Model)) { %>
<%: Model.DBUserDetail.FName %>
<%: Model.DBUserDetail.LName %>
<%: Html.HiddenFor(m => m.DBOrder.ShippingMethod, new { #value = "UPS Ground" })%>
<%: Html.HiddenFor(m => m.DBOrder.ShippingAmount, new { #value = "29.60" })%>
...
...
<input type="submit" value="Confirm & Pay" />
<% } %>
And finally /Views/Cart/Checkout.aspx looks like this:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site-1-Panel.Master" Inherits="System.Web.Mvc.ViewPage<My.Namespace.Entities.OrderDetail>" %>
...
...
<%: Html.Hidden("x_first_name", Model.DBUserDetail.FName) %>
<%: Html.Hidden("x_last_name", Model.DBUserDetail.LName) %>
...
It doesn't really matter what's here, an exception gets throw in the controller because the model is always null
Most likely your model state is invalid. Add this extension method and call it on the first line of the action like:
ModelState.DumpErrors();
Put a breakpoint one line after it and examine the Output window for more information about what is wrong with the binding.
Edit - The full extension method:
public static class ModelExtensions
{
public static void DumpErrors(this System.Web.Mvc.ModelStateDictionary ModelState)
{
var errors = from key in ModelState
let errorList = ModelState[key.Key].Errors
where errorList.Any()
select new
{
Item = key.Key,
Value = key.Value,
errorList
};
foreach (var errorList in errors)
{
System.Diagnostics.Debug.WriteLine("MODEL ERROR:");
System.Diagnostics.Debug.WriteLine(errorList.Item);
System.Diagnostics.Debug.WriteLine(errorList.Value);
foreach (var error in errorList.errorList)
{
System.Diagnostics.Debug.WriteLine(error.ErrorMessage);
System.Diagnostics.Debug.WriteLine(error.Exception);
}
System.Diagnostics.Debug.WriteLine("-----");
}
}
}
I don't know asp specifically, but it seems like you have access to cart in index and confirm b/c you are passing it explicitly. Since you are not passing it to checkout, you would not be able to access it. I might be totally off

ASP.NET MVC2 -- Trouble with Model Binding and Html.Textbox()

ASP.NET MVC Model Binding is still new to me and I'm trying to understand exactly how it works. Right now, I appear to be having problems with a feature of Html.Textbox()
Specifically, I have a View where I set Html.Textbox to a value both in the "Get" and the "Post". It sets fine in the "Get", but after the user submits a value during the "Post", I have the class change one of the values internally based on the other value submitted.
(I'm basically validating one value based on the other... I'm not sure if this is the right way to do this...)
Tracing through, I can see that the value has actually changed as expected both in the Model and in the View, but when it displays on my screen after the "Post", the value does not display as it was changed. Instead it is what it was set to originally.
Here's my simplified example:
The View shows a:
Drop-down with items from a SelectList (pre-selected as "Other")
a Read-only Text Box (with a pre-loaded value of 0)
Submit Button
User should pick a new value from the Drop-Down and click submit. The "Post" method in the controller picks up the new value from the Drop-Down and changes the Value in the Read-only text-box and re-displays.
(Yes, I'll eventually be doing this with JQuery, too...)
Here's my sample Model class:
public class SampleSubmission
{
public string Name { get; set; }
public int Volume { get; set; }
public readonly SortedList<string, int> NameVolumeList = new SortedList<string, int>();
// Standard Constructor
public SampleSubmission()
{
NameVolumeList.Add("Sample1", 10);
NameVolumeList.Add("Sample2", 20);
NameVolumeList.Add("Sample3", 50);
NameVolumeList.Add("Other", 0);
this.Name = NameVolumeList.Keys[0];
this.Volume = NameVolumeList[Name];
}
// Copy Constructor
public SampleSubmission(SampleSubmission samSub) : this()
{
this.Name = samSub.Name;
this.Volume = NameVolumeList[Name];
}
}
Here's the Controller:
public class SampleSubmissionController : Controller
{
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Index()
{
SampleSubmission sampleSub = new SampleSubmission();
return View(sampleSub);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(SampleSubmission sampleSub)
{
SampleSubmission samSub = new SampleSubmission(sampleSub);
return View(samSub);
}
}
Here's the View:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<MvcModelBindTest.Models.SampleSubmission>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm()) { %>
<%= Html.DropDownList("Name", new SelectList(Model.NameVolumeList.Keys.ToList())) %>
<%= Html.TextBox("Volume",Model.Volume) %>
<input type="submit" name="pick" id="pick" value="Pick" /> <% } %>
</asp:Content>
Any ideas as to why the new value does not display?
EDIT:
In order to fix the problem, I read the link given by "jfar" and made a 1-line change.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(SampleSubmission sampleSub)
{
SampleSubmission samSub = new SampleSubmission(sampleSub);
// Reset Model Value
ModelState.SetModelValue("Volume", new ValueProviderResult(
samSub.Volume, "", System.Globalization.CultureInfo.CurrentCulture));
return View(samSub);
}
This definitely works. Unfortunately, this feels like a gross hack to me. What if I had to update the values of multiple fields? There must be a better (simpler?) way of doing this.
EDIT2: Found my answer. See below...
From: How to clear textboxes defined with MVC HTML helpers
"The HTMLHelper's first look at the
ModelState and ViewData to see if any
values match their key and then
finally use whatever value you provide
them.
If you need to reset the textboxe's
value you also need to clear the
ModelState entry with the matching
key. Another alternative is
redirecting to the same page instead
of simply rendering a view via
javascript or with MVC.
I figured out the answer to my own question when I stumbled upon another variable that needed to be reset. As I was looking at the data structure, I realized what I wanted was the pristine state where there were no Keys in the ModelState.
ModelState.Remove(key);
Where "key" is the value you're trying to reset.
Another simple workaround is instead of
<%= Html.TextBox("Volume",Model.Volume) %>
Use HTML input tag
<input id="Volume" name ="Volume" value="#Model.Volume" />

Getting posted values in MVC PartialView

I've created a PartialView which I render with Html.RenderPartial, passing the name of the view and the strongly-typed data item to bind to (below):
<% Html.RenderPartial("SearchViewUserControl", ViewData["SearchData"]); %>
The partial view has a form containing a submit button:
<% using (Html.BeginForm("Search", "Home"))
{ %>
...
<div>
<input type="submit" value="Search" />
</div>
<% } %>
I've set a breakpoint in my controller's action method (below) but nothing is set in searchData. What am I doing wrong?
public ActionResult Search(SearchDomain searchData)
{
if (ModelState.IsValid)
{
}
return View();
}
You need to post the actual form elements for anybody to know whats wrong.
The form html is what sets the binding to SearchDomain. You want to have your form elements named like this:
<input name="searchData.SomeProperty">
For them to bind to your action parameter.
In order to pull a SearchDomain object out of your view from a controller method, your view has to either inherit from System.Web.Mvc.ViewPage<Models.SearchDomain>, or a custom ViewModel class that contains a SearchDomain object.
The other way to do it is to have your view inherit from System.Web.Mvc.ViewPage, and use UpdateModel to cast the view data to a SearchDomain object. Something like this:
public ActionResult Save()
{
SearchDomain domain = new SearchDomain ();
UpdateModel(domain , new[] { "Name", "Email", "Phone", ... });
return View(domain);
}
To be honest, I think RenderAction is much easier to use.

asp.net mvc and model binding objects with sub objects

I'm trying to get model bind a sub class in a form.
I have a Page class with a related PageContent class.
There are many PageContent objects in a Page object.
ie Page.PageContents
I return Page as the Model and I can see all the PageContent items.
However I'm a bit blurry on how to assign the PageContent items to the form so it'll keep it's model binding.
When I Post the Page model back into the controller, the PageContent list is empty.
Here is a snippet of the form:
<% using (Html.BeginForm("SavePage", "Admin")) {%>
<fieldset>
<legend>Fields</legend>
<%=Html.Hidden("PageId", Model.PageId) %>
<p>
<label for="Title">Title:</label>
<%= Html.TextBox("Title", Model.Title) %>
<%= Html.ValidationMessage("Title", "*") %>
</p>
<p>
<label for="SysName">SysName:</label>
<%= Html.TextBox("SysName", Model.SysName) %>
<%= Html.ValidationMessage("SysName", "*") %>
</p>
<%
int i = 0;
foreach (var pageContent in Model.PageContents)
{ %>
<div>
<%=Html.Hidden("PageContents[" + i + "].PageContentId", pageContent.PageContentId) %>
<%=Html.TextArea("PageContents[" + i + "].Content", pageContent.Content)%>
</div>
<%
i++;
} %>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
I'm thinking I haven't made the PageContents aspect of the form correctly.
EDIT:
Here is the basic controller method I POST to:
public ActionResult SavePage(Page page)
{
// do stuff with page.PageContents
return View("PageEdit", page);
}
I'm not using the Request.Form style
Please help :D
You said Page.PageContents is an EntitySet<PageContent> and that you are using auto-generated classes. So, I guess, you are using LINQ to SQL? If so, then you can use partial classes in order to add properties to the generated class
I remember that I had a similiar problem, I wasnt able to bind to EntitySet<T> properties. I think I fixed it by using a proxy / placeholder property which was an IList<T> what effectively set the actual property. So, in your case, that would look something like this:
// Your partial class, adding a property to the generated class
public partial class Page
{
// Bind to this property
public IList<PageContent> MyPageContents
{
set
{
var set = new EntitySet<PageContent>();
set.AddRange(value);
PageContents = set;
}
get { return PageContents; }
}
}
// This and probably a lot more is created by LINQ to SQL
public partial class Page {
// ...
public EntitySet<PageContent> PageContents
{
get;
set;
}
// ...
}
I think partial classes have to be in the same namespace as the auto generated class. More info here: Adding new methods to LINQ to SQL generated classes
And then, in your form, you bind to MyPageContents (you can use a better name of course) instead of PageContents.
Hopefully it works for you, it is quite some time ago since I last used LINQ to SQL.

Resources