question on MVC data annotations, editor templates, and validation - asp.net-mvc-2

Context: MVC 2 app using EF.
Why does some of this work, but some doesn't? The display name gets set to "Court Name" but no validation is happening, i.e. I don't see the error message. I'm thinking the editor templates are interfering with validation, or else Model.IsValid doesn't work with a complex view model.
Any thoughts?
PARTIAL CLASS:
[MetadataType(typeof(CourtMetaData))]
public partial class Court
{
private class CourtMetaData
{
[Required(ErrorMessage = "Court Name is required")]
[DisplayName("Court Name")]
public System.String CourtName
{
get;
set;
}
}
}
CONTROLLER:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult New(CourtsNewViewModel court)
{
if (ModelState.IsValid)
{
db.AddCourt(court);
return View("List");
}
else
{
return View("New", court);
}
}
VIEW MODEL:
public class CourtsNewViewModel : ViewModelBase
{
public Court Court {get; private set; }
public IEnumerable<CourtType> CourtTypes { get; private set; }
public CourtsNewViewModel()
{
CourtTypes = db.GetAllCourtTypes();
}
}
VIEW:
Html.EditorFor(model => model.Court.CourtName, "LongString")
EDITOR TEMPLATE:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<System.String>" %>
<div class="editor-row">
<div class="editor-label">
<%= Html.LabelFor(model => model) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model)%>
<%= Html.ValidationMessageFor(model => model) %>
</div>
</div>

Here's the gotcha:
public ActionResult New(CourtsNewViewModel court)
The court variable is already used as you have a Court property on your model.
Now rename the parameter:
public ActionResult New(CourtsNewViewModel courtModel)
Everything should work as expected.

Related

MVC How to pass in a relational ID from a model into a partial view with a different model

I want to add a create function to my page, it has a buildings page and a rooms page however depending on which building is chosen rooms corresponding to its ID are shown. I have that completed but I wanted to add a create function to add more rooms when in a specific building. I have all the required code for creating/inserting in a partial view but I need to pass in relational value without having to put it in myself. Similar to this action link:
#Html.ActionLink("Edit", "Edit", **new { id=item.Id }**)
Here is my room page:
#model TerminalHost.Models.buildingInfo
#{
ViewBag.Title = "Home Page";
}
<h3>Rooms in: #Model.buildingName</h3>
<ol class="round">
<li class="one">
<h5>Please begin creating your network structure:</h5> <button id="modal-opener">Add a new room</button>
</li>
<li class="two">
</li>
</ol>
#*rooms that are specific to a building are shown here*#
#Html.Partial("rooms", Model.roomId)
<div id="Modal" title="room Details">
#Html.Partial("roomForm", new TerminalHost.Models.roomInfo())
</div>
my displayed rooms partial view:
#model IEnumerable<TerminalHost.Models.roomInfo>
<div class="Container">
#foreach (var item in Model)
{<div class="Box">
<div>
<h4 class="Heading">#item.roomName</h4>
<h4 class="Heading">#item.roomNumber</h4>
<p>#item.roomDesc1</p>
<p>#item.roomDesc2</p>
<p>#item.roomDesc3</p>
</div>
</div>
}
</div>
my roomForm partial view:
#model TerminalHost.Models.roomInfo
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm("roomForm", "Home"))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>roomInfo</legend>
<div class="editor-label">
#Html.LabelFor(model => model.roomNumber)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.roomNumber)
#Html.ValidationMessageFor(model => model.roomNumber)
</div>
....
//Here I would like the building Id to be passed automatically from the buildingInfo model which is being called in the rooms view
<div class="editor-label">
#Html.LabelFor(model => model.buildingId)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.buildingId)
#Html.ValidationMessageFor(model => model.buildingId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
This is the model that I'm using to store data with:
public class buildingInfo
{
public int Id { get; set; }
public int buildingNumber { get; set; }
public String buildingName { get; set; }
public String buildingDesc1 { get; set; }
public String buildingDesc2 { get; set; }
public String buildingDesc3 { get; set; }
public virtual ICollection<roomInfo> roomId { get; set; }
}
Here is the roomInfo model:
public class roomInfo
{
public int Id { get; set; }
public int roomNumber { get; set; }
public String roomName { get; set; }
public String roomDesc1 { get; set; }
public String roomDesc2 { get; set; }
public String roomDesc3 { get; set; }
public int buildingId { get; set; }
//public virtual ICollection<rackInfo> rackId { get; set; }
}
If anymore information is needed please ask and I will provide it.
I was thinking to a solution using ViewDataDictionary but I just realised you can pass the value through the constructor of your object.
So you can try this:
#Html.Partial("roomForm",new TerminalHost.Models.roomInfo { buildingId=Model.Id })
I hope it will help you.
I have seen the other Question. But I prefer to answer here maybe it will solve the second problem. Because there are many solutions. But I think maybe the first I gave doesn't allow you to populate this value for some reason.
Could you try this:using a overload of Html.Partial() method and passing a ViewDataDictionary with the value you want.
You need to change this line:
#Html.Partial("roomForm", new TerminalHost.Models.roomInfo())
To:
#Html.Partial("roomForm", new TerminalHost.Models.roomInfo(), new ViewDataDictionary { {"buildingId", Model.buildingId} } )
And in your partial view, you can retrieve it like this:
#model TerminalHost.Models.roomInfo
#{
ViewBag.Title = "Create";
}
#{
int buildingId = Convert.ToInt32(ViewData["buildingId"]);
}
........
<div class="editor-field">
#Html.TextBoxFor(model => model.buildingId,new {#Value=buildingId })
#Html.ValidationMessageFor(model => model.buildingId)
</div>
I hope by doing in this way it will solve the second problem.

asp:CheckBoxList in a form

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>

Getting checKbox value at post in MVC2

I am trying to display a list of dynamic check boxes and allow the user to select one or more. Once returned to the controller, I would need to take the ids of all the checked ones and write a record for each to the database.
Below are the pieces of code that relate to this.
DTO
public class OfficeVisitPartOfBodyDisplay
{
public int PartOfBodyId { get; set; }
public string PartOfBodyName { get; set; }
public bool PartOfBodyChecked { get; set; }
}
Model
public class OfficeVisitModel
{
public OfficeVisitEntity OfficeVisit { get; set; }
public TList<PartOfBodyEntity> PartOfBodies { get; set; }
public TList<OfficeVisitPartOfBodyEntity> OfficeVisitPartOfBodies { get; set; }
public List<OfficeVisitPartOfBodyDisplay> OfficeVisitPartOfBodyDisplays { get; set; }
public string PatientName { get; set; }
}
View (The part in question)
<div data-role="fieldcontain">
<fieldset data-role="controlgroup">
<legend>Pain Area(s):</legend>
<% foreach (OfficeVisitPartOfBodyDisplay officeVisitPartOfBodyDisplay in Model.OfficeVisitPartOfBodyDisplays)
{ %>
<label for="partofbodydisplay<%= officeVisitPartOfBodyDisplay.PartOfBodyId %>"><%= officeVisitPartOfBodyDisplay.PartOfBodyName%></label>
<input type="checkbox" id="partofbodydisplay<%= officeVisitPartOfBodyDisplay.PartOfBodyId%>" name="partofbodydisplay" value="<%= officeVisitPartOfBodyDisplay.PartOfBodyName%>" />
<% } %>
</fieldset>
</div>
Controller, the OfficeVisitPartOfBodyDisplays in the model and the partofbodydisplay always come back with no data.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ActivePainArea(OfficeVisitModel model, OfficeVisitPartOfBodyDisplay[] partofbodydisplay, string submitButton)
{
switch (submitButton)
{
case "Save":
model.Message = "Save Coming Soon";
return View(model);
case "Cancel":
model.Message = "Cancel Coming Soon";
return View(model);
case "Complaint":
return RedirectToAction("ActiveComplaint", new { patientId = model.OfficeVisit.PatientId, readOnly = false });
case "Patient History":
model.Message = "Patient History Coming Soon";
return View(model);
//return RedirectToAction("ActivePatientHistory", new { patientId = model.OfficeVisit.PatientId });
default:
return View(model);
}
}
give this a try:
Controller:
public ActionResult ActivePainArea(OfficeVisitModel officeVisitPartOfBodyDisplay, string submitButton) {
View:
<div data-role="fieldcontain">
<% using (Html.BeginForm()) { %>
<fieldset data-role="controlgroup">
<legend>Pain Area(s):</legend>
<% var i = 0; %>
<% foreach (var officeVisitPartOfBodyDisplay in Model.OfficeVisitPartOfBodyDisplays) { %>
<label for="partofbodydisplay<%= officeVisitPartOfBodyDisplay.PartOfBodyId %>">
<%= officeVisitPartOfBodyDisplay.PartOfBodyName%></label>
<%: Html.CheckBox("OfficeVisitPartOfBodyDisplays[" + i.ToString() + "].PartOfBodyChecked", officeVisitPartOfBodyDisplay.PartOfBodyChecked)%>
<% i++; %>
<% } %>
<input type="submit" value="save" />
</fieldset>
<% } %>
</div>
Also you might want to reference this page for more information:
Phil Haacked Model Binding To A List

Problem with validating a viewmodel using fluent validation

I am trying to validate a viewmodel using fluent validation. When i post the viewmodel object, the modelstate.isvalid always returns false. When i have checked the values on the posted object, the properties which were used to get the data to be shown in dropdowns are also being validated. How to resolve this. Am i doing it in a wrong way, pls help.
I'm new to ASP.net MVC and just trying out using fluent validation and fluent NHibernate mappings in this project.
More details as follows:
I have a domain model object as below:
[Validator(typeof(TestRequirementValidator))]
public class TestRequirement
{
public virtual int Id { get; private set; }
public virtual int SampleId { get; set; }
public virtual int TestId { get; set; }
public virtual int StandardId { get; set; }
public virtual string Description { get; set; }
public virtual Sample Sample { get; set; }
public virtual Test Test { get; set; }
public virtual Standard Standard { get; set; }
}
I have created a view model as below:
[Validator(typeof(TestRequirementViewModelValidator))]
public class TestRequirementViewModel
{
public TestRequirement TestRequirement;
public SelectList Samples;
public SelectList Tests;
public SelectList Standards;
public TestRequirementViewModel()
{
ISession _session = FNHsessionFactory.GetSessionFactory();
this.TestRequirement = new TestRequirement();
this.Samples = new SelectList(from S in _session.Linq<Sample>() select S, "Id", "Name");
this.Tests = new SelectList(from T in _session.Linq<Test>() select T, "Id", "Name");
this.Standards = new SelectList(from St in _session.Linq<Standard>() select St, "Id", "Name");
}
}
Model Validator is as below:
public class TestRequirementValidator : AbstractValidator<TestRequirement>
{
public TestRequirementValidator()
{
RuleFor(x => x.SampleId)
.NotEmpty()
.WithMessage("This field is required")
.DisplayName("Sample Name");
RuleFor(x => x.TestId)
.DisplayName("Test Name");
RuleFor(x => x.StandardId)
.NotEmpty()
.WithMessage("This field is required")
.DisplayName("Standard Name");
RuleFor(x => x.Description)
.NotEmpty()
.WithMessage("This field is required")
.Length(0, 10)
.WithMessage("Length of this field cannot be more than 10 characters");
}
}
View model validator is as below:
public class TestRequirementViewModelValidator:AbstractValidator-TestRequirementViewModel-
{
public TestRequirementViewModelValidator()
{
RuleFor(x => x.TestRequirement)
.SetValidator(new TestRequirementValidator());
}
}
View is as below:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<foo.Models.ViewModels.TestRequirementViewModel>" MasterPageFile="~/Views/shared/site.master" %>
<asp:Content ContentPlaceHolderID="MainContent" ID="MainContentContent" runat="server">
<h3><%= Html.Encode(ViewData["Message"]) %></h3>
<% using (Html.BeginForm()) {%>
<%= Html.ValidationSummary(true) %>
<fieldset>
<legend>Create Test Requirement</legend>
<div class="editor-label">
<%= Html.LabelFor(model => model.TestRequirement.SampleId) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(model => model.TestRequirement.SampleId, new SelectList(Model.Samples.Items, Model.Samples.DataValueField, Model.Samples.DataTextField), "Select Sample") %>
<%= Html.ValidationMessageFor(model => model.TestRequirement.SampleId) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.TestRequirement.TestId) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(model => model.TestRequirement.TestId, new SelectList(Model.Tests.Items, Model.Tests.DataValueField, Model.Tests.DataTextField), "Select Test") %>
<%= Html.ValidationMessageFor(model => model.TestRequirement.TestId) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.TestRequirement.StandardId) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(model => model.TestRequirement.StandardId, new SelectList(Model.Standards.Items, Model.Standards.DataValueField, Model.Standards.DataTextField), "Select Standard") %>
<%= Html.ValidationMessageFor(model => model.TestRequirement.StandardId) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.TestRequirement.Description) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.TestRequirement.Description) %>
<%= Html.ValidationMessageFor(model => model.TestRequirement.Description) %>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
<%= Html.ClientSideValidation<TestRequirement>("TestRequirement") %>
</asp:Content>
Controller is as below:
public ActionResult TestRequirement()
{
TestRequirementViewModel NewTestRequirement = new TestRequirementViewModel();
return View(NewTestRequirement);
}
[HttpPost]
public ActionResult TestRequirement(TestRequirementViewModel NewTestRequirement)
{
if(ModelState.IsValid)
{
ISession _session = FNHsessionFactory.GetSessionFactory();
_session.SaveOrUpdate(NewTestRequirement.TestRequirement);
ViewData["Message"] = "New Test Requirement has been created successfully";
return View();
}
return View(NewTestRequirement);
}
I can't help with Fluent Validation, but as you've tagged this as fluent-nhibernate I thought I should comment on your NHibernate usage.
Your view model should not be using NHibernate in its constructor; in fact, your view model should just be a data structure that gets populated by an external service. Similarly, you might want to do the same with your controller; it's common for people to extract data access into a repository to isolate their controllers (and make testing easier, you are testing aren't you?).
If you're using a repository, you can then project your view model from your entity; you can do that either by using NHibernate projections and transformers or by using a tool like AutoMapper.

Why won't my Model accept what I'm selecting from the DropDownList?

I edit the Dinner (from the NerdDinner tutorial) and save it, but the Country isn't persisted. Here's my code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace NerdDinner.Models
{
public class DinnerFormViewModel
{
private static string[] _countries = new[]{
"USA",
"Bolivia",
"Canada"
};
public Dinner Dinner { get; private set; }
public SelectList Countries { get; private set; }
public DinnerFormViewModel(Dinner dinner)
{
Dinner = dinner;
Countries = new SelectList(_countries, dinner.Country);
}
}
}
Then in my controller I use it like so:
[HttpGet]
public ActionResult Edit(int id)
{
Dinner dinner = dinnerRepository.GetDinner(id);
return View(new DinnerFormViewModel(dinner));
}
[HttpPost]
public ActionResult Edit(int id, FormCollection formValues)
{
Dinner dinner = dinnerRepository.GetDinner(id);
if (TryUpdateModel(dinner))
{
dinnerRepository.Save();
return RedirectToAction("Details", new { id = dinner.DinnerID });
}
return View(new DinnerFormViewModel(dinner));
}
Then, here is the View:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<NerdDinner.Models.DinnerFormViewModel>" %>
<div class="editor-label">
<%: Html.LabelFor(model => model.Dinner.Country) %>
</div>
<div class="editor-field">
<%: Html.DropDownListFor(model => model.Dinner.Country, Model.Countries) %>
<%: Html.ValidationMessageFor(model => model.Dinner.Country) %>
</div>
When trying to edit an existing dinner and change the Country, it doesn't persist the change. Thanks!
So are you saying that when you call TryUpdateModel(dinner), the dinner properties are being updated with what's in the form?
If that's the case, because Country is a child of the model and you are calling the html helpings like so, <%: Html.DropDownListFor(model => model.Dinner.Country, Model.Countries) %>, I'm thinking that the inputs will have a prefix of Country.
Try changing your TryUpdateModel(dinner) to TryUpdateModel(dinner, "Dinner").
HTHs,
Charles