Using the following model, I expect that when the Submit button is hit, the Edit Method to Fire, and the model parameter to have the adjusted values. But it keeps returning an instance of the class with all null values. It is as if no model binding ever happens.
class Trait
{
string Name { get; set; }
// other properties
}
class DesignViewModel
{
Dictionary<Trait, int> Allocation { get; set; }
}
Controller
public ActionResult Create()
{
var model = new DesignViewModel();
// retrieve traits from database
foreach(var trait in Repository.Traits)
model.Allocation.Add(trait, 0);
return View(model);
}
[HttpPost]
public ActionResult Edit(DesignViewModel model)
{
// nothing works yet, so I don't have a lot of code here...
}
HTML
Top Level Page
<%# Page Title="" Language="C#" MasterPageFile="~/Areas/Setup/Views/Shared/Setup.master"
Inherits="System.Web.Mvc.ViewPage<OtherModel>" %>
<% Html.RenderAction("Design", "Test"); %>
Partial View
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DesignViewModel>" %>
<% using (Html.BeginForm("Edit", "Test", FormMethod.Post ) ) {%>
<div id="eq">
<% foreach (var trait in Model.Allocations) { %>
<div style="margin: 15px;">
<%: trait.Key.Name %>
<br />
<span class="slider"></span>
<%: Html.TextBox(trait.Key.Name, trait.Value, new { #class = "spent" , #readonly = "readonly" })%>
</div>
<% } %>
</div>
<p>
<input type="submit" value="Submit" />
</p>
<% } %>
You need to add [HttpPost] to your Edit method for it to be fired during POST requests.
Related
I am trying to put a checkboxlist onto a simple form in asp.NET MVC 2. This is the code for it:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm("HandSurvey", "Resources", FormMethod.Post, new { runat="server" }))
{ %>
<asp:CheckBoxList id="chkListChemical" runat="server">
<asp:ListItem>Fiberglass & resins</asp:ListItem>
<asp:ListItem>Heavy duty oil paints & stains</asp:ListItem>
<asp:ListItem>Mechanics - tools, grease/oil</asp:ListItem>
<asp:ListItem>Metalworking fluids</asp:ListItem>
<asp:ListItem>Paint & Stains in use</asp:ListItem>
<asp:ListItem>Exposure to solvents</asp:ListItem>
<asp:ListItem>Difficult soils</asp:ListItem>
<asp:ListItem>Hydrocarbons</asp:ListItem>
</asp:CheckBoxList>
<% }
%>
</asp:Content>
When I hit this page it gives this error:
Control 'ctl00_MainContent_chkListChemical_0' of type 'CheckBox' must be placed inside a form tag with runat=server.
I thought I was specifying the runat attribute correctly. Or is there something else that I am missing here? If I don't use the helper class and just use a regular form tag, it works.
In ASP.NET MVC you should avoid using server controls. Basically everything that has the runat="server" should not be used (except the content placeholder in WebForms view engine). In ASP.NET MVC you design view models:
public class MyViewModel
{
[DisplayName("Fiberglass & resins")]
public bool Item1 { get; set; }
[DisplayName("Heavy duty oil paints & stains")]
public bool Item2 { get; set; }
...
}
then you have controller actions that manipulate the view model:
// Action that renders the view
public ActionResult Index()
{
var model = new MyViewModel();
return View(model);
}
// Handles the form submission
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// TODO: Process the results
return View(model);
}
and finally you have a strongly typed view:
<% using (Html.BeginForm("HandSurvey", "Resources")) { %>
<div>
<%= Html.CheckBoxFor(x => x.Item1) %>
<%= Html.LabelFor(x => x.Item1) %>
</div>
<div>
<%= Html.CheckBoxFor(x => x.Item2) %>
<%= Html.LabelFor(x => x.Item2) %>
</div>
...
<p><input type="submit" value="OK" /></p>
<% } %>
UPDATE:
As requested in the comments section in order to have those checkboxes dynamically generated from a database it is a simple matter of adapting our view model:
public class ItemViewModel
{
public int Id { get; set; }
public string Text { get; set; }
public bool IsChecked { get; set; }
}
and now we will have our controller action to return a list of this view model:
public ActionResult Index()
{
// TODO: obviously those will come from a database
var model = new[]
{
new ItemViewModel { Id = 1, Text = "Fiberglass & resins" },
new ItemViewModel { Id = 2, Text = "Heavy duty oil paints & stains" },
};
return View(model);
}
and the view will now simply become:
<% using (Html.BeginForm("HandSurvey", "Resources")) { %>
<%= Html.EditorForModel() %>
<p><input type="submit" value="OK" /></p>
<% } %>
and the last part would be to define an editor template for our view model (~/Views/Shared/EditorTemplates/ItemViewModel.ascx):
<%# Control
Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<AppName.Models.ItemViewModel>"
%>
<div>
<%= Html.HiddenFor(x => x.Id) %>
<%= Html.CheckBoxFor(x => x.IsChecked) %>
<%= Html.DisplayFor(x => x.Text) %>
</div>
How can I create a form in ASP.NET MVC2, send the data to a controller that adds something to the database and then redirects to the home page? Can you give me an example/snippet of how it's done in the View?
For some reason, I have a bug in my form. Here's the code:
AddEvent.aspx
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Add Event</h2>
<% using (Html.BeginForm()) { %>
<div>
<%= Html.LabelFor(x => x.EventName) %>:
<%= Html.TextBoxFor(x => x.EventName) %>
</div>
<div>
<%= Html.LabelFor(x => x.EventDate) %>:
<%= Html.TextBoxFor(x => x.EventDate) %>
</div>
<div>
<%= Html.LabelFor(x => x.EventLocation) %>:
<%= Html.TextBoxFor(x => x.EventLocation) %>
</div>
<div>
<%= Html.LabelFor(x => x.EventDescription) %>: </br>
<%= Html.TextAreaFor(x => x.EventDescription) %>
</div>
<input type="submit" value="Submit" />
<% } %>
HomeController.cs
public ActionResult AddEvent()
{
return View();
}
[HttpPost]
public ActionResult AddEvent(Event e)
{
e.EventCreatorName = Session["UserName"].ToString();
DatabaseModels db = new DatabaseModels();
db.AddEvent(e);
return RedirectToAction("Index", "Home");
}
DatabaseModels.cs
public bool AddEvent(Event e)
{
anEvent eventToAdd = new anEvent();
eventToAdd.creator_nickname = e.EventCreatorName;
eventToAdd.event_category = 1; // TODO
if (e.EventDate == null)
{
eventToAdd.event_date = new DateTime();
}
else
{
eventToAdd.event_date = DateTime.Parse(e.EventDate);
}
eventToAdd.event_location = e.EventLocation;
eventToAdd.event_name = e.EventName;
m_db.AddToevents(eventToAdd);
m_db.SaveChanges();
return true;
}
I type in details in the form and I get the following Exception:
This property cannot be set to a null value.
on event_location. Can anyone help solve this?
The asp.net/mvc site contains numerous examples, videos and tutorials about MVC that are worth reading. Here's an example of how the scenario you are asking about could be implemented:
Model:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Controller:
public class PersonsController: Controller
{
public ActionResult Index()
{
return View(new Person());
}
[HttpPost]
public ActionResult Index(Person person)
{
// The person object here will have it's FirstName
// and LastName properties bound to whatever values
// the user entered in the corresponding textboxes in the form
// TODO: save person to database
// redirect to /home/index
return RedirectToAction("index", "home");
}
}
View:
<%# Page
Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<AppName.Models.Person>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm()) { %>
<div>
<%= Html.LabelFor(x => x.FirstName) %>:
<%= Html.TextBoxFor(x => x.FirstName) %>
</div>
<div>
<%= Html.LabelFor(x => x.LastName) %>:
<%= Html.TextBoxFor(x => x.LastName) %>
</div>
<input type="submit" value="Save" />
<% } %>
</asp:Content>
Now you might be wondering about the TODO part. Usually I create a repository to decouple my data access logic from my controller:
public interface IPersonsRepository
{
void Save(Person person);
}
and then use constructor injection of this repository into my controller:
public class PersonsController: Controller
{
private readonly IPersonsRepository _repository;
public PersonsController(IPersonsRepository repository)
{
_repository = repository;
}
public ActionResult Index()
{
return View(new Person());
}
[HttpPost]
public ActionResult Index(Person person)
{
// The person object here will have it's FirstName
// and LastName properties bound to whatever values
// the user entered in the corresponding textboxes in the form
// save person to database
_repository.Save(person);
// redirect to /home/index
return RedirectToAction("index", "home");
}
}
Obviously now the last part that's left is the implementation of this repository. This will depend on how/where your data is stored and the particular data access technology you would be using. So are you using a relational database, flat text file, XML file, object database, some database stored on the cloud, ... how are you going to access it: EF, NHibernate, Linq-to-XML, some REST API, ...
Once you make your choice you simply implement the interface and instruct your DI framework to pass the proper implementation to the controller constructor.
I cannot make sense of this...
The simplified code below OUGHT to insert a new form field of the current time's second value.
Instead, even though the list manipulation happens correctly (as verifiable within the controller and in the debug output), the View does render an additional element, but seems to be just a copy of the final field's value (prior to submit). This can be verified by changing a field prior to submit - the values stick and the new element is a duplicate of the submitted final value.
The part that really cooks my noodle is that if I trivially change the operation from an Insert() to an Add(), the Add() works as expected!!
Using this example Model:
public class MyViewData
{
List<string> stringData = new List<string>();
public List<string> StringData { get { return stringData; } }
}
And this Controller:
public class TestController : Controller
{
public ActionResult Index()
{
return View(new MyViewData());
}
[HttpPost]
public ActionResult Index(MyViewData data)
{
data.StringData.Insert(0, DateTime.Now.Second.ToString());
return View(data);
}
}
And this View:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Forms.Controllers.MyViewData>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Test
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<%
System.Diagnostics.Debug.WriteLine("---");
for (var i = 0; i < Model.StringData.Count; i++)
System.Diagnostics.Debug.WriteLine("{0}", Model.StringData[i]);
%>
<% using (Html.BeginForm()) { %>
<% for (var i = 0; i < Model.StringData.Count; i++) { %>
<%: Html.TextBoxFor(model => model.StringData[i])%></br>
<% } %>
<div><input type="submit" value="Do it" /></div>
<% } %>
</asp:Content>
This is the normal behavior of standard HTML helpers and is by design. When rendering the input field they will first look at the request POSTed values and only after in the ViewData and view model. This basically means that you cannot change POSted values in your controller action.
So if you have a form with an input field:
<%= Html.TextBoxFox(x => x.Id) %>
which is posted to the following action
[HttpPost]
public ActionResult Index(MyModel model)
{
model.Id = "some new value";
return View(model);
}
when rendering the view back the html helper will use the posted value. You could either write a custom html helper that does the job for you or handle it manually (absolutely not recommended but for the record):
<input type="text" name="StringData[<%= i %>]" value="<%= Model.StringData[i] %>" />
The contactAddModel.Search always comes through as null - any ideas?
View declaration
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<rs30UserWeb.Models.StatusIndexModel>" %>
ViewModels
public class StatusIndexModel
{
public ContactAddModel contactAddModel;
public StatusMessageModel statusMessageModel;
}
public class ContactAddModel
{
[Required(ErrorMessage="Contact search string")]
[DisplayName("Contact Search")]
public string Search { get; set; }
}
View content
<% using (Html.BeginForm("AddContact", "Status")) { %>
<div>
<fieldset>
<legend>Add a new Contact</legend>
<div class="editor-label">
<%= Html.LabelFor(m => m.contactAddModel.Search) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(m => m.contactAddModel.Search)%>
<%= Html.ValidationMessageFor(m => m.contactAddModel.Search)%>
</div>
<p>
<input type="submit" value="Add Contact" />
</p>
</fieldset>
</div>
<% } %>
Controller
[HttpPost]
public ActionResult AddContact(Models.ContactAddModel model)
{
if (u != null)
{
}
else
{
ModelState.AddModelError("contactAddModel.Search", "User not found");
}
return View("Index");
}
You should modify the action AddContact like this
AddContact(Models.ContactAddModel contactAddModel)
just replace "model" with "contactAddModel"
I try this, but don't work.
bemutatkozas#Modify = null all the time.
public class Iroda
{
public Dictionary<int,string> bemutatkozas { get; set; }
}
public ActionResult Index()
{
var dct = new Dictionary<int, string>();
dct.Add(1, "magyar");
dct.Add(2, "angol");
dct.Add(3, "olasz");
return View(new Iroda { bemutatkozas = dct });
}
[HttpPost]
public ActionResult Modify(Dictionary<int,string> bemutatkozas)
{
return View();
}
<% using (Html.BeginForm("Modify","Iroda"))
{%>
<%= Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<%= Html.EditorFor(o=>o.bemutatkozas,"MultiLanguageEditor") %>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Dictionary<int, string>>" %>
<% int i = 0; %>
<% foreach (var s in Model)
{ %>
<%= Html.Hidden(Html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix+"["+i+"].key", s.Key) %>
<%= Html.TextBox(Html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix+"["+i+"].value",s.Value) %>
<% i++; %>
<% }%>
Whats the solution?
Thx!