I have a SQL Server 2012 in which I have AWARD table with two columns TITLE and MONTH. TITLE is varchar(256) and cannot be NULL. MONTH is int and can be NULL.
With VS2012 Ultimate and EF 5.0.0, the TextBoxFor helper in MVC4 app is not producing validation (data-val="required" and data-val-required="required message") for the TITLE columne above, but in the same View, MONTH is getting the correct validation markup. The .edmx designer does show TITLE is NonNullable, BUTT, the automatically generated AWARD.cs file does not have the [Required]
attribute for the TITLE column.
What can I try?
#model MyProject.Models.AWARD
#{
ViewBag.Title = "Add Award";
Layout = "~/Views/Shared/_EditorLayout.cshtml";
}
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Add Award</legend>
<table>
<tr>
<td>
#Html.LabelFor(model => model.TITLE)
</td>
<td>
#Html.TextAreaFor(model => model.TITLE)
<br />#Html.ValidationMessageFor(model => model.TITLE)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(model => model.MONTH)
</td>
<td>#Html.DropDownListFor(model => model.MONTH, new SelectList(MyProject.Models.Helpers.Months, "key","value"), "[Not Selected]")
<br />#Html.ValidationMessageFor(model => model.MONTH)
</td>
</tr>
<tr>
<td>
<input type="submit" value="Add" />
</td>
<td>
#Html.ActionLink("Cancel", "Index", null, new { #class = "cancel-button" })</td>
</tr>
</table>
</fieldset>
}
You shouldn't really be binding your views directly to your data mapping entities. You should create view model classes to wrap the data you pass to and from your view and then populate your data objects from the controller.
You can then perform the required validation on your view model without affecting your generated mapping classes.
Model
public class AwardViewModel
{
[Required, StringLength(30)]
public string Title { get; set; }
....
}
View
#model AwardViewModel
#using (Html.BeginForm()) {
#Html.EditorFor(m => m.Title)
#Html.ValidationMessageFor(m => m.Title)
...
}
Controller
[HttpPost]
public ActionResult Create (AwardViewModel model)
{
/* Create new AWARD in entity context, populate
with relevant fields from model and commit. */
}
Related
I have a main viewmodel that contains a list of child viewmodels to be edited:
public class MyObjectGridViewModel
{
public string Comment { get; set; }
public List<MyObject> Objects { get; set; } = new List<MyObject>();
}
The structure of MyObject is really not relevant at all. Then I have an editor template for the Objects collection, like this:
#model MyObject
<tr>
<td class="hidden">
#Html.HiddenFor(m => m.Id)
</td>
<td>
#Html.DisplayFor(m => m.Name)
</td>
<td>
#Html.CheckBoxFor(m => m.IsBanned, new { #class = "checkbox" })
</td>
</tr>
And then a form view to edit individual MyObject models inline, i.e. without a separate Edit view:
#model MyObjectGridViewModel
#using (Html.BeginForm("Ban", "Access", FormMethod.Post, new { role = "form" }))
{
#Html.TextBoxFor(m => m.Comment)
<table class="table bans">
#Html.EditorFor(m => m.Objects)
</table>
<input type="submit" value="Save" />
}
And a post action on the controller:
[HttpPost]
public ActionResult Ban(MyObjectGridViewModel model)
{
var items = model.Objects.Count(); // items == 0
}
Yet when I click the submit button and invoke this action, the model.Objects list has zero items. The editor template displays an expected 5 items, I check 1 or 2 checkboxes, submit, and lose all the items in the Objects property.
What is wrong here?
I found some old code, and it looks like the solution is to have an editor template for the main view model, and inside that, use another one for the collection item. I now have:
MyObjectGridViewModel.cshtml:
#model MyObjectGridViewModel
#Html.EditorFor(model => Model.Objects)
MyObject.cshtml:
#model MyObject
<tr>
<td class="hidden">
#Html.HiddenFor(m => m.Id)
</td>
<td>
#Html.DisplayFor(m => m.Name)
</td>
<td>
#Html.CheckBoxFor(m => m.IsBanned, new { #class = "checkbox" })
</td>
</tr>
And in the form, instead of
#Html.EditorFor(m => Model.Objects)
I now have
#Html.EditorFor(m => Model)
And all seems to work fine.
I am working in an ASP.NET Web Application, using MVC template.
I am trying send or pass an ObjectViewModel from a View.cshtml to a Controller, but in the Controller the ObjectViewModel arrived as null.
The idea is the follow:
I show the view IndexUsersInActiveDirectory.cshtml with a collection of object of type RegisterViewModel. Into this view there is an #Html.ActionLink for send a element of the collection to the Register controller:
IndexUsersInActiveDirectory.csthml
#model IEnumerable<SecureEscuelaWithIdentity.Models.RegisterViewModel>
#{
ViewBag.Title = "IndexUsersInActiveDirectory";
}
<h2>IndexUsersInActiveDirectory</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.UserName)
</th>
<th>
#Html.DisplayNameFor(model => model.Email)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.UserName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Email)
</td>
<td>
#Html.ActionLink("Select", "Register", new { item })
</td>
</tr>
}
</table>
The controller Register is the next:
//
// GET: /Account/Register
[Authorize(Roles = "Admin")]
public ActionResult Register(RegisterViewModel model)
{
return View(model);
}
The class RegisterViewModel is:
public class RegisterViewModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[Display(Name = "Email")]
public string Email { get; set; }
}
The view Register.cshtml
#model SecureEscuelaWithIdentity.Models.RegisterViewModel
#{
ViewBag.Title = "Register";
}
<h2>#ViewBag.Title.</h2>
#using (Html.BeginForm("RegisterInApp", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Create a new account.</h4>
<hr />
#Html.ValidationSummary()
<li>#Html.ActionLink("ActiveDirectory", "IndexUsersInActiveDirectory", "Account")</li>
<div class="form-group">
#Html.LabelFor(m => m.UserName, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.UserName, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Email, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Email, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Register" />
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
How to send the entire collection element (as model) to the controller?
There are 2 problems
1) You are missing a null on the 4th parameter of ActionLink
#Html.ActionLink("Select", "Register", new { item }, null)
Without that 4th parameter it assumes the 3rd parameter is the HTML attributes and not the querystring parameters.
I find this little 4th parameter "quirk" to be one of the most annoying overrides in MVC.
2) Your item needs to be simple data values that can be passed via the querystring
As your item is not a simple value type, you need to pass back its unique values. e.g.:
#Html.ActionLink("Select", "Register", new { name = item.Name, email = item.Email }, null)
Action links do not do a postback, they just create anchor links in the page, so any values have to be passed in the query string.
Change the receiving action to match:
[Authorize(Roles = "Admin")]
public ActionResult Register(string name, string email)
{
RegisterViewModel model = new RegisterViewModel()
{
Name = name,
Email = email
};
return View(model);
}
But I leave those details up to you :)
The solution proposed by TrueBlueAussie is suitable for scenarios where there is no problem to show querystring in the address bar of the browser.
If so, the #Html.ActionLink could be like this:
#Html.ActionLink("Select", "Register", new {item}, null)
since the controller will take 'item' as a RegisterViewModel object (although in reality be a querystring). Note: not forgetting the 4th parameter that contributed TrueBlueAussie "null".
The Register controller looks like this:
[Authorize (Roles = "Admin")]
public ActionResult Register (RegisterViewModel model)
{
return View (model);
}
It works!
As mention above, this displays the querystring in the Register view, which is not correct in my implementation. I do not want to show this querystring in the address bar.
To achieve this, I added an intermediate controller responsible of receiving the 'item' (from IndexUsersInActiveDirectory.csthml) as a RegisterViewModel object (actually it is a querystring) and add this Object to TempData dictionary with a key called "model".
Here is the controller RegisterTemp:
[Authorize (Roles = "Admin")]
public ActionResult RegisterTemp (RegisterViewModel model)
{
TempData ["model"] = model;
return RedirectToAction ("Register");
}
In the view 'IndexUsersInActiveDirectory.csthml' the #ActionLink point to controller RegisterTemp rather than the controller Register, being as follows:
# Html.ActionLink ("Select", "RegisterTemp", new {item}, null)
Importantly RegisterTemp returns a "RedirectToAction" to Register view.
And finally, when the RegisterTemp controller returns RedirectToAction to Register controller, it takes the element with the "model" key from the TempData dictionary in order to return it to the view. Thus, the Register controller is:
//
// GET: / Account / Register
[Authorize (Roles = "Admin")]
public ActionResult Register ()
{
return View (TempData ["model"]);
}
I mentioned that, the solution proposed by #TrueBlueAussie is fully functional for the scenario that is not important to show the querystring.
The solution that I have just detailed now, is for avoid showing that querystring.
If there is a more correct solution to solve my scenario, is welcome.
You must iterate the elements with a "for" loop, as in for
(int i; i <= elements.Length -1; i++)
elements[i].etc;
So the collection don't get lost on post.
Read this for more detail:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
I am working on this project. We are using EF and backside data models. We are mapping them to viewmodels that have the same property names. This has created 4 separate view models, which I'm ok with. Now, I have organizations that can have 2 addresses, multiple access codes and multiple domain addresses. In my partial views, I have to bind the view to an IEnumerable to list all of the possible codes/domains/etc. When I'm in the "details" page for an organization, which is using a details page that is populated by partials, I would like to add a text box for adding a new access code, or domain. Unfortunatly, I can't figure out how to achive this.
Here is my code:
ViewModel for OrganizationAccessCodes:
namespace AdminTool.Models.ViewModel
{
public class OrganizationAccessCodeView
{
[AtLeastOneRequired(ErrorMessageResourceType = typeof(AdminResource), ErrorMessageResourceName = "OrganizationAccessCodeRequired")]
[Display(ResourceType = typeof(AdminResource), Name = "OrganizationAccessCode")]
[RegularExpression(#"(0-9)", ErrorMessageResourceType = typeof(AdminResource), ErrorMessageResourceName = "OrganizationAccessCodeFormatError")]
public string AccessCode { get; set; }
public bool Active { get; set; }
public int OrganizationID { get; set; }
}
}
Here is the Details html:
#model AdminTool.Models.ViewModel.OrganizationView
#using AdminTool.App_GlobalResources;
#{
ViewBag.Title = "Details";
}
<div id="idm_main">
<div class="form_section">
<h3>#AdminResource.OrganizationDetailsHeader</h3>
<div class="form_item">
<h4>#AdminResource.OrganizationNameHeader</h4>
<div class="item">
#Html.DisplayFor(model => model.OrganizationName)
</div>
#Html.Partial("_AddressPartial", Model.OrganizationAddress)
</div><br /><br />
<div class="form_item">
<h4>#AdminResource.OrganizationAccessCodeHeader</h4>
#Html.Partial("_AccessCodePartial", Model.AccessCodes)
</div><br /><br />
<div class="form_item">
<h4>#AdminResource.OrganizationDomainsHeader</h4>
#Html.Partial("_EmailDomainPartial", Model.DomainAddresses)
</div>
<div class="clearBoth"></div>
</div>
</div>
and here is the partial for the access code:
#model IEnumerable<AdminTool.Models.ViewModel.OrganizationAccessCodeView>
#foreach (var item in Model)
{
<tr>
<td>
#item.AccessCode
</td>
<td>
#Html.ActionLink("Deactivate", "Edit", new {})
</td>
</tr>
}
<div class="form_item">
<div class="item">
#Html.LabelFor()
</div>
<div class="item">
#Html.TextBoxFor()
#Html.ValidationMessageFor()
</div>
</div>
I'm not completely done with the partial view because of this problem I'm having. I want to know if there is a way (other than Tuple) in order to have a text box for the access code object that is in the view model?
You need to define what your label and textbox are for in your partial view. If the property is called Address:
#foreach (var item in Model)
{
#Html.LabelFor(m => item.Address)
#Html.TextBoxFor(m => item.Address)
}
I am having some trouble. I have a view as below
#model IEnumerable<Tipstr.Models.Posts>
<div class ="mainC">
<div class = "leftContent">
#Html.Partial("_LeaderboardPartial")
</div>
<div class = "rightContent">
#foreach (var item in Model) {
<h1 class ="ptitle">
#Html.DisplayFor(modelItem => item.title)
</h1>
<p class ="date">
posted #Html.DisplayFor(modelItem => item.date)
</p>
<p class ="post">
#Html.DisplayFor(modelItem => item.body)
</p>
<hr />
}
</div>
</div>
<div class="Cpad">
<br class="clear" /><div class="Cbottom"></div><div class="Cbottomright">
</div>
</div>
And I have a partial, _LeaderboardPartial, as shown
#model IEnumerable<Tipstr.Models.Leaderboard>
<table id="pattern-style-a" summary="Meeting Results">
<thead>
<tr>
<th scope="col">Username</th>
<th scope="col">Tipster Score</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model) {
<tr>
<td>#Html.DisplayFor(modelItem => item.userName)</td>
<td> #Html.DisplayFor(modelItem => item.score)</td>
</tr>
}
</tbody>
</table>
I can't seem to get it to work I think it has something to do with passing in one model from the layout and then another from the partial. How can I get around this? I get the following error:
The model item passed into the dictionary is of type 'System.Collections.Generic.List1[Tipstr.Models.Posts]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable1[Tipstr.Models.Leaderboard]'.
As #David Tansey said, you can have a view model that contains a reference to the two types
public class MyViewModel
{
public MyViewModel(IEnumerable<Posts> posts,
IEnumerable<Leaderboard> leaderboard)
{
//you can add null checks to ensure view model invariants
this.Posts = posts;
this.Leaderboard = leaderboard;
}
public IEnumerable<Posts> Posts { get; private set; }
public IEnumerable<Leaderboard> Leaderboard{ get; private set; }
}
And in your main view you will call Html.Partial like this
#model MyViewModel
<div class ="mainC">
<div class = "leftContent">
#Html.Partial("_LeaderboardPartial", this.Model.Leaderboard)
</div>
....
If you do this, you should change the foreach to iterate through Model.Posts instead of Model
#foreach (var item in this.Model.Posts)
This line of code:
#Html.Partial("_LeaderboardPartial") in the first view implies sending along its own model IEnumerable<Tipstr.Models.Posts> to the partial.
The partial expects a reference to type IEnumerable<Tipstr.Models.Leaderboard>.
Perhaps you need a view model that contains a reference to one of each of these types?
I have a simple loginform based on the following modelitem
public class LogOnModelItem
{
[Required(ErrorMessage="Brukernavn er påkrevd")]
[DisplayName("Brukernavn")]
public string UserName { get; set; }
[Required(ErrorMessage="Passord er påkrevd")]
[DataType(DataType.Password)]
[DisplayName("Passord")]
public string Password { get; set; }
[DisplayName("Husk meg?")]
public bool RememberMe { get; set; }
}
The view is :
<h2>Login</h2>
<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)
<table>
<tr>
<td>#Html.LabelFor(model => model.UserName)</td>
<td>
#Html.EditorFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</td>
</tr>
<tr>
<td>#Html.LabelFor(model => model.Password)</td>
<td>
#Html.EditorFor(model => model.Password)
#Html.ValidationMessageFor(model => model.Password)
</td>
</tr> <tr>
<td>#Html.LabelFor(model => model.RememberMe)</td>
<td>
#Html.EditorFor(model => model.RememberMe)
#Html.ValidationMessageFor(model => model.RememberMe)
</td>
</tr>
</table>
<p>
<input type="submit" value="Create" />
</p>
}
The controller:
public ActionResult Login(LogOnModelItem lmi)
{
return View(lmi);
}
When I load this up in the browser the validation is apparently run on pageload. This then outlines the username and password in red, with the error message supplied after each line. I have 3 questions for this, in order of importance:
1: How do I make sure the validation is not run until after the user presses submit.
2: How do I display the validation summary?
3: How do I make the "username" field choosen by default and ready to accept input?
Answer to #1:
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Login(LogOnModelItem lmi)
{
return View(lmi);
}
Answer to #2
#Html.ValidationSummary(false)
Answer to #3
<script type="text/javascript">
$(document).ready(function () {
$('#UserName').focus();
});
</script>
1: How do I make sure the validation is not run until after the user presses submit
This should already be the case. If you have a GET action rendering this form and supplying a view model there won't be any validation errors. It's the POST action that would show the validation messages and if you have client validation enabled it could also happen onblur.
2: How do I display the validation summary?
You already did: #Html.ValidationSummary(true)
3: How do I make the "username" field choosen by default and ready to accept input?
You could use javascript:
$(function() {
$('#UserName').focus();
});