Given the following view model and action using the DefaultModelBinder, it seems to ignore the dictionary, but bind all other properties correctly. Am I missing something here? Looking at the MVC source code this seems legit.
Thanks
public class SomeViewModel
{
public SomeViewModel()
{
SomeDictionary = new Dictionary<string, object>();
}
public string SomeString { get; set; }
public IDictionary<string, object> SomeDictionary { get; set; }
}
[HttpPost]
public ActionResult MyAction(SomeViewModel someViewModel)
{
//someViewModel.SomeString binds correctly
//someViewModel.SomeDictionary is null
}
<%# Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<SomeViewModel>" MasterPageFile="~/Views/Shared/Site.Master" %>
<asp:Content runat="server" ID="Content2" ContentPlaceHolderID="MainContent">
<% using (Html.BeginForm("MyAction", "MyController")) {%>
<%= Html.EditorFor(m => m.SomeString) %>
<%= Html.EditorFor(m => m.SomeDictionary["somevalue"]) %>
<input type="submit" value="Go" />
<%} %>
</asp:Content>
And for reference, the HTML output is:
<input class="text-box single-line" id="SomeString" name="SomeString" type="text" value="" />
<input class="text-box single-line" id="Somedictionary_somevalue_" name="SomeDictionary[somevalue]" type="text" value="" />
EDIT: The above will not work as pointed out below, however I prefer this layout and the following quick hack works for my needs, call this just after posting...
someViewModel.SomeDictionary = (from object key in Request.Form.Keys
where key.ToString().StartsWith("SomeDictionary[")
select new
{
Key = key.ToString().Replace("SomeDictionary[", string.Empty).Replace("]", string.Empty),
Value = (object)Request.Form[key.ToString()]
}).ToDictionary(arg => arg.Key, arg1 => arg1.Value);
It needs some tidying up ofcourse :)
You may take a look at this post to see how dictionaries should be binded. I am afraid that using strongly typed EditorFor helpers you won't be able to achieve this and you will have to generate the fields manually.
Related
I am working on a test mvc project and since it's my 1st time working in mvc environment I am almost lost and is completely different compared to asp.net web forms.
I am trying to put a textbox and a button on a form, but when I am using <%= Html.TextBox("name") %> for textbox for example, the code displays as a text on the screen and is not rendered as a textbox. When I am using html markup for textbox and button I can see the textbox but shouldn't <%= Html.TextBox("name") %> be correct way to do that?
Here is what I have here:
#{
Layout = "~/_SiteLayout.cshtml";
Page.Title = "Welcome to my Web Site!";
}
<p>
ASP.NET Web Pages make it easy to build powerful .NET based applications for the web.
Enter your name: <%= Html.TextBox("name") %>
<input id="Text1" type="text" />
<input id="Button1" type="button" value="button" />
</p>
Which way should I go, can I go with the standard html format or what am I doing wrong that the textbox from <%= Html.TextBox("name") %> doesn't get displayed?
Thanks in advance, Laziale
You are using ASPX syntax. For Razor, it would be something like this:
#Html.TextBox("TextBoxName")
So your code would look like:
<p>
ASP.NET Web Pages make it easy to build powerful .NET based applications for the web.
Enter your name:
#Html.TextBox("name")
<input id="Button1" type="button" value="button" />
</p>
In addition to the previous answers, if you are referencing a Model on your View page, then you can use the Razor HTML Helpers with Lambda expressions.
Updated Example (This update is in response to the Laziale's comment):
In your Models directory you have a User class:
namespace MvcApplication.Models
{
public class User
{
public string Name { get; set; }
}
}
In your Controllers directory, you have a UserController:
namespace MvcApplication.Controllers
{
public class UserController : Controller
{
//
// GET: /User/
public ActionResult Index()
{
return View();
}
}
}
In your Views directory, you have a sub-directory named "User" which contains an "Index.cshtml" file:
#model MvcApplication.Models.User
#using (Html.BeginForm())
{
#Html.TextBoxFor(x => x.Name)
<input type="submit" />
}
MVC/Razor will create the following HTML:
<html>
<head>...</head>
<body>
<form action="/User" method="post">
<input id="Name" name="Name" type="text" value="" />
<input type="submit" />
</form>
</body>
</html>
You use the Razor syntax which is denoted using # at the start:
#Html.TextBox("name")
OK, I'm sorry if the tile of the question was unclear, and if you understand what I mean, please don't hesitate to help me think of a better one.
Anyway, I have a <input type="submit"> element for my form, and I want it to return the same URL as the URL of the page the element is on.
Currently, if I click the button, it takes me from /Calculate/Quadratic to /Calculate/QuadraticForm
In my controller for this view, I have the following code:
[AcceptVerbs(HttpVerbs.Get)]
public ViewResult Quadratic()
{
ViewData["Root1"] = "";
ViewData["Root2"] = "";
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ViewResult Quadratic(QuadCalc boss)
{
ViewData["Root1"] = x1;
ViewData["Root2"] = x2;
return View();
}
And here is the markup and code for my Quadratic view page, which includes the form which includes the submit button I've been referring to:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Quadratic</h2>
<% using(Html.BeginForm("QuadraticForm", "Calculate")) %>
<% { %>
<div>
a: <%= Html.TextBox("quadraticAValue") %>
<br />
b: <%= Html.TextBox("quadraticBValue") %>
<br />
c: <%= Html.TextBox("quadraticCValue") %>
<br />
<input type="submit" id="quadraticSubmitButton" value="Calculate!" />
<br />
<p><%= ViewData["Root1"] %></p>
<p><%= ViewData["Root2"] %></p>
</div>
<% } %>
</asp:Content>
Therefore, all I really want is to have the submit button return the same page, but the HTTP post will aid the application in passing new ViewData. Unless I'm interpreting this all wrong.
The problem is in your BeginForm method that calls the QuadraticForm action
<% using(Html.BeginForm("QuadraticForm", "Calculate")) %>
If you want to give an ID to the form you should use
<% using (Html.BeginForm("Quadratic", "Calculate", FormMethod.Post, new { id = "QuadraticForm" })) { %>
If you dont mind about the ID and want to just return to the same action just use
<% using(Html.BeginForm() %>
The parameters are:
The action name
The controller Name
The form method (get/post)
The form attributes
I have a view that is strongly typed:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MPKwithMVC.Models.SmartFormViewModel>" %>
Works great to generate the view, but when I post, I have an ActionResult defined:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Next(MPKwithMVC.Models.SmartFormViewModel model)
{ .. }
Which I would imagine get hit when my next button is clicked (it works if I change the argument to a FormsCollection). I instead get a message saying "No parameterless constructor defined for this object".
What am I doing wrong?
My SmartFormsViewModel is:
[Serializable]
public class SmartFormViewModel
{
public List<Question> Questions { get; set; }
public List<Answer> Answers { get; set; }
public SmartFormViewModel(List<Question> questions, List<Answer> answers)
{
this.Questions = questions;
this.Answers = answers;
}
public SmartFormViewModel()
{
}
}
And here is the View:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MPKwithMVC.Models.SmartFormViewModel>" %>
<%# Import Namespace="MPKwithMVC.Models" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
SmartForms
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>
Questionaire</h2>
<% using (Html.BeginForm("Next", "SmartForms"))
{ %>
<div style="float: left; margin-right: 2em;">
<% Html.RenderPartial("NavigationPanel", Model); %>
</div>
<div>
<table>
<%
foreach (Question question in (Model.Questions))
{ %>
<tr>
<td>
<div style="text-align: right; width: 20em;">
<%= Html.Encode(question.QuestionText)%>
</div>
</td>
<td>
<div style="float: left;">
<% if (question.QuestionType == 1)
{ %>
<%= Html.TextBoxFor(model => model.Answers[(int)question.QuestionID - 1].AnswerValue) %>
<% } %>
<% if (question.QuestionType == 2)
{ %>
<%= Html.RadioButton("yn" + question.QuestionID, "Yes", false)%>Yes
<%= Html.RadioButton("yn" + question.QuestionID, "No", true)%>No
<% } %>
</div>
<% if (question.Required == true)
{ %>
<div style="color: Red; float: right; margin-left: 3px;">
*</div>
<% } %>
</td>
</tr>
<%
} %>
<tr>
<td>
<%
if (ViewData["errorMsg"] != null)
{%>
<div style="color:Red;">
<%= Html.Encode(ViewData["errorMsg"].ToString()) %>
</div>
<% } %>
</td>
<td>
<div style="margin-top: 1em;">
<button name="button" value="next">Next</button>
</div>
</td>
</tr>
</table>
</div>
<% } %>
</asp:Content>
Have a look at the input names that are being generated within your HTML. I think you have an issue with the naming of your controls thus the default model binding is failing since you mentioned that the using FormCollection works correctly. I am making this assumption since I don't know what your Questions and Answers classes look like
<%= Html.TextBoxFor(model =>
model.Answers[(int)question.QuestionID - 1].AnswerValue) %>
Won't this render something similar to the following; which if I'm not mistaken will not bind to your model. The same applies for the RadioButtons.
<input type="text" name="Answers[0].AnswerValue" id="Answers_0__AnswerValue" value="somevalues"/>
The RadioButton helper should be
<%= Html.RadioButton("Questions[" + question.QuestionID + "].ID", "Yes", false)%> // you now get a list of questions
<input type="radio" name="Questions[1].ID" id="Questions_1__ID" value="No" checked="checked"/>
There are some ways you can try to resolve this:
Correctly name your input controls so that they match you ViewModel. (This includes posting back all the required fields for you model - I think that that default values are used when the model binding occurs if its not posted)
Create a custom model binder
You may need to tell the Binder what the Prefix of the input fields are. ([Bind] attribute) to specifically include or exclude form fields.
Create a new Model containing the values you expect to post back
I think that overall that your approach needs to slightly change. From the info provided, your Q&As are closely related. Depending on the question type your 'answer' is either boolean or freetext. At the moment your are not posting a List<Questions> back to the server. Answers, yes, but they are not I don't think that it is recognised as List<Answers>.
Haacked has a post which I think is related to your issue and this SO question further indicates that it may still applly to ASP-MVC-2.
As the error message suggest, your SmartFormViewModel class needs to contain a parameterless constructor.
This is a great example of what you would like to do
I would also suggest you use the strongly typed helpers from Html extensions to generate your form fields.
e.g.
Html.HiddenFor(x => x.SomeField)
Html.TextBoxFor(x => x.SomeEditableField)
Make sure that your <form>'s action points to your Next method.
I am currently using the EntityFramework to bind my ASP.NET MVC project to a MySQL database and one of my entities, Product, has an Images property containing a collection of ProductImages. I have built a form to allow the user to modify a given Product and this form includes fields for editing all of the images associated to that Product as well. After reading Phil Haack's and Dan Miser's posts on the matter I have a decent idea of what needs to happen, but I can't seem to make it work for some reason...
Here is my Product form:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<KryptonCMS.Models.Product>" %>
<%# Import Namespace="KryptonCMS.Core" %>
<%# Import Namespace="KryptonCMS.Models.ViewModels" %>
<% using (Html.BeginForm())
{%>
<ul class="gallery">
<%
var index = 0;
foreach (var image in Model.ImageList.OrderBy(p => p.Order))
{
%>
<li>
<% Html.RenderPartial("ProductImageForm", image, new ViewDataDictionary(ViewData) { { "index", index } }); %>
</li>
<%
index++;
}
%>
</ul>
<p>
<input type="submit" name="btnSave" value="Save" />
<input type="submit" name="btnCancel" value="Cancel" />
</p>
<% } %>
And here is the definition for ProductImageForm:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<KryptonCMS.Models.ProductImage>" %>
<%# Import Namespace="KryptonCMS.Core" %>
<div>
<%
var fieldPrefix = string.Format("images[{0}]", ViewData["index"]); %>
<%=Html.Hidden(fieldPrefix + "ID", Model.ID) %>
<img src="<%=UtilityManager.GetProductImagePath(Model.Product.ID, Model.FileName, true) %>"
alt="" /><br />
<label for="Description">
Description:</label>
<%=Html.TextBox(fieldPrefix + "Description", Model.Description) %><br />
<label for="Order">
Order:</label>
<%=Html.TextBox(fieldPrefix + "Order", Model.Order)%><br />
</div>
And finally my ProductsController actions:
public ActionResult Edit(int id)
{
var product = productsRepository.GetProduct(id);
if (product == null)
return View("NotFound", new MasterViewModel());
// else
return View(ContentViewModel.Create(product));
}
[AcceptVerbs(HttpVerbs.Post), ValidateInput(false)]
public ActionResult Edit(int id, FormCollection formCollection)
{
var product = productsRepository.GetProduct(id);
if (formCollection["btnSave"] != null)
{
if (TryUpdateModel(product) && TryUpdateModel(product.Images, "images"))
{
productsRepository.Save();
return RedirectToAction("Details", new { id = product.ID });
}
return View(ContentViewModel.Create(product));
}
// else
return RedirectToAction("Details", new { id = product.ID });
}
The HTML output for a single ProductImageForm looks like this:
<div>
<input id="images[0]ID" name="images[0]ID" type="hidden" value="1" />
<img src="/Content/ProductGallery/3/thumbs/car1.jpg"
alt="" /><br />
<label for="Description">
Description:</label>
<input id="images[0]Description" name="images[0]Description" type="text" value="FAST CAR" /><br />
<label for="Order">
Order:</label>
<input id="images[0]Order" name="images[0]Order" type="text" value="1" /><br />
</div>
I have tried all sorts of methods of reorganizing my form including taking the Image collection out of the Product form and placing it in its own (which I really don't want to do), but nothing is working. Is there something blatatently wrong with my approach here?
You are missing dots in inputs' names:
<%= Html.Hidden(fieldPrefix + ".ID", Model.ID) %>
<%= Html.TextBox(fieldPrefix + ".Description", Model.Description) %>
<%= Html.TextBox(fieldPrefix + ".Order", Model.Order) %>
Check this blog post: http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
my first question here.. :)
Let's begin with the code...
my page is
<form id="form1" runat="server">
<% using (Ajax.BeginForm(null)){%>
<%=Html.DropDownList("DdlScelta",MVC.Models.SelectLists.ConventionIdsSelectList, "Select by this list")%>
<%=Ajax.ActionLink("Show the Data", "SetData", new AjaxOptions { UpdateTargetId = "msg" })%>
<span id="msg"></span>
</form>
and this is my controller method
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult SetData(FormCollection form1)
{
//form1["DdlScelta"] etc
}
I've also tried with a better way like
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult SetData(string DdlScelta)
{
//not important code
}
but NOTHING to do, as soon one of the 2 actionResult is catched, I have a null value..
Thank you to any who can help me :)
You have to submit the form
<input type="submit" value="Somevalue" />
and have the form like this
Ajax.BeginForm("actionName", "controllerName", ajaxOptions)
Something like this
<% using (Ajax.BeginForm("actionName", "controllerName", ajaxOptions))
{%>
//form stuff
<input type="submit" value="Somevalue" />
<% } %>