I have a simple question. I'm implementing a CRUD for a n:m relationship (users / roles namely) using the SimpleMembershipProvider (which is awkward by its own nature).
I created a view class:
public class AssignedRoleData
{
public string RoleName { get; set; }
public bool Assigned { get; set; }
}
I added this to my user class:
public List<AssignedRoleData> AssignedRoles { get; set; }
This is my AssignedRoleData.cshtml:
#model NfseEasyWeb.Models.AssignedRoleData
#Html.HiddenFor(model => model.RoleName)
#Html.LabelFor(model => model.Assigned, Model.RoleName)
#Html.EditorFor(model => model.Assigned)
I want them to appear on a horizontal line.
<table>
<tr>
#foreach (var item in Model.AssignedRoles) {
<td>
<div class="editor-field">
#Html.Editor What goes here?
</div>
</td>
}
</tr>
</table>
I know I could just use
#Html.EditorFor(model => model.AssignedRoles)
And the framework would detect it's a collection and render as many AssignedRoleData.cshtml as necessary, but they appear in pure form in html, vertical, I want them to appear on a horizontal line.
This is what I wanted. Did it applying CSS to the editor template, now need to work on margin.
Thanks
The way to go in this case would be create the template for the single entity and use an EditorFor for the collection in the view, like this:
#Html.EditorFor(model => model.AssignedRoles)
The MVC framework figures it's a collection and renders as many templates as necessary (MVC can't override EditorTemplate name when used in EditorFor for child object). This should be the preferred way, instead of concocting for loops in the elements, or using Viewbag (I say this by experience and reading, it's better not to stray from the pattern).
As the rendering part, to make the checkboxes appear side by side, it's possible to use CSS:
<div class="checkbox-editor-field">
#Html.HiddenFor(model => model.RoleName)
#Html.LabelFor(model => model.Assigned, Model.RoleName)
#Html.EditorFor(model => model.Assigned)
</div>
And create a CSS class for this:
.checkbox-editor-field {
display:inline-block;
}
So that the div elements will be accomodated in a single block in the same line.
Related
The Model:
class Address
{
public string City { get; set; }
public string Zip { get; set; }
}
The Controller:
[HttpPost]
public ActionResult GetAddress(Address model)
{
if (!String.IsNullOrEmpty(model.Zip))
{
model.City = GetCityByZip(model.Zip);
}
return View(model);
}
The View:
<div class="formrow">
#Html.LabelFor(model => model.City)
#Html.TextBoxFor(model => model.City)
#Html.ValidationMessageFor(model => model.City)
</div>
<div class="formrow">
#Html.LabelFor(model => model.Zip)
#Html.TextBoxFor(model => model.Zip)
#Html.ValidationMessageFor(model => model.Zip)
</div>
The problem is whenever the city is being modified, it never gets reflected on the view. During debugging, the model.City contains the correct value but it doesn't show up on view. Even something as simple as #Html.TextBoxFor(model => model.City) doesn't display the correct model.City value.
HtmlHelpers get the model values from the model state and not the model when you update and return the model. In order to update and return the model, add this line of code in your post method:
ModelState.Clear();
or you could set the value of city in the ModelState itself:
ModelState["City"].Value = GetCityByZip(model.Zip);
As Tommy noted, this is, somewhat counterintuitively, the correct behavior since form data submitted on post gets first priority when binding the data to the returned view. This makes some sense as the user is likely to have made a validation error when re-returning the same view and gets to resume their form entry as is without the problems of losing form input when restoring a page
One other option is to manually insert the value for the input
So instead of this:
#Html.TextBoxFor(model => model.City)
Do this instead:
<input type="text" name="City" value="#Model.City" />
* which will grab the value directly off the model
Or even better:
<input type="text" value="#Model.City"
name="#Html.NameFor(model => model.City)"
id="#Html.IdFor(model => model.City)" />
*Note: this won't bring in data-val attributes. If you're using them on this property for client side validation, you'll need to build list of data validation attributes for a given element
Additional Resources
HiddenFor not getting correct value from view model
HTML.HiddenFor is not updating on a postback
ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes
I'm new to MVC, please help me out for this.
I have to manage the profile of Employee in my application.So i created 3 model
DetailPersonal(contain field- First Name,Middle Name, Last Name, DOB)
DetailAddress(contain field- AddressLine1,AddressLine2,City,State,etc)
DetailContact(contain field-Primary Mobile no.,Secondary Mobile No, Off. No. etc)
Can i create a single view for these models and do ajax posting.
I want to display these in tabs Like Personal || Address || Contact
The cleanest way would be to create a new class with three properties, and use that as your model.
Or, if you don't want to do that, you could use a list of objects:
#model List<object>
#{
var detailPersonal = Model[0] as DetailPersonal;
var detailAddress = Model[1] as DetailAddress;
var detailContact = Model[2] as DetailContact;
}
And pass all three from the controller:
View(new object[] {MyDetailPersonal, MyDetailAddress, MyDetailContact})
But it could be more prone to error.
This situation is very common where you have defined classes that need to be combined before being presented to the user.
Typically, this is handled by using a View Model to consolidate separate classes into something that can be modified by the end user. Translating between your entity objects and your view models can be done in the constructor for a simple application.
I would look at this:
What is ViewModel in MVC?
Also, if you want to introduce more sophistication, I would read up on the repository pattern, and also look up domain layer. Those two subjects deal with the situation you are facing.
Will the user submit the contents of all three models using a single form (a shared submit button)? I think that makes the most sense, unless there are circumstances that make AJAX necessary. If I'm right about that, here's what you would do.
Create a new model with three model-typed properties. Let's call it CustomerModel.
public class CustomerModel
{
[DisplayName("Address")]
public DetailAddress AddressDetails { get; set; }
[DisplayName("Contact information")]
public DetailContact ContactDetails { get; set; }
[DisplayName("Personal information")]
public DetailPersonal PersonalDetails { get; set; }
}
Then create the editor templates in the framework-defined folder. These contain the editable fields for each model.
~/Views/Shared/EditorTemplates/DetailAddress.cshtml
~/Views/Shared/EditorTemplates/DetailContact.cshtml
~/Views/Shared/EditorTemplates/DetailPersonal.cshtml
Then add the editors to your main form. Something like...
#using (Html.BeginForm())
{
<div id="addressDetailsTab">
#Html.LabelFor(model => model.AddressDetails)
#Html.EditorFor(model => model.AddressDetails)
</div>
<div id="contactDetailsTab">
#Html.LabelFor(model => model.ContactDetails)
#Html.EditorFor(model => model.ContactDetails)
</div>
<div id="personalDetailsTab">
#Html.LabelFor(model => model.PersonalDetails)
#Html.EditorFor(model => model.PersonalDetails)
</div>
<input type="submit" value="Submit" />
}
I am trying to create a form which allows the creation and editing of a hierarchy of entities using MVC.
The top level is project which can have 1 or many environments associated with it. The environments are predefined, eg Dev, Test and Prod.
For each environment that is added it is possible to add several interfaces.
So a user would enter the project information. Select which environments which are relevant and then for each environment section add several interfaces.
I've created 3 view models, project, environment and interface. Like so
public class ProjectViewModel
{
public int Id { get; set; }
public string ProjectTitle { get; set; }
public List<SelectListItem> EnvironmentChoices { get; set; }
public List<EnvironmentViewModel> EnvironmentModel { get; set; }
}
public class EnvironmentViewModel
{
public IList<InterfaceViewModel> Interfaces { get; set; }
public string Environment { get; set; }
}
public class InterfaceViewModel
{
public string InterfaceName { get; set; }
}
Then created 1 project template and 2 editor templates for the environment model and the interface model.Like so
<p>
#Html.LabelFor(x => x.ProjectTitle)
#Html.TextBoxFor(x => x.ProjectTitle)
#Html.ValidationMessageFor(x => x.ProjectTitle)
</p>
<p>
#Html.LabelFor(x => x.EnvironmentModel)
#for (int i = 0; i < Model.EnvironmentChoices.Count; i++)
{
#Html.CheckBoxFor(m => m.EnvironmentChoices[i].Selected, new { id = Model.EnvironmentChoices[i].Value })
#Html.HiddenFor(m => m.EnvironmentChoices[i].Value)
#Html.DisplayFor(m => m.EnvironmentChoices[i].Text)
}
</p>
<p>
#Html.EditorFor(x => x.EnvironmentModel)
</p>
for the environment template
#model EnvironmentViewModel
<fieldset style="margin-left: -10px;">
<legend>#Model.Environment</legend>
#Html.ActionLink("Add Interface", "AddInterface", Model, new { #class = "button icon-file-plus" })
#Html.EditorFor(x => x.Interfaces)
</fieldset>
for the interface template
#model InterfaceViewModel
<p>
#Html.LabelFor(x => x.InterfaceName)
#Html.TextBoxFor(x => x.InterfaceName)
#Html.ValidationMessageFor(x => x.InterfaceName)
</p>
What I am finding is that when I click the add button on the environment section. The controller only picks up the environment model and loses the project model context so cannot modify it to add the new interface model.
Am I going about this in the wrong way? If so, are there examples of best practice. If not, what am I doing wrong.
Thanks
Thanks to Stephen Muecke for pointing me in the right direction.
I found this example to be very close to what I was trying to achieve
http://jarrettmeyer.com/post/2995732471/nested-collection-models-in-asp-net-mvc-3
Basically it uses to MVC to dynamic render JavaScript which in turn will add a detail view model to the form which conforms to the MVC indexing convention. Therefore when the master model and sub models are posted back the whole thing is bound to one big hierarchical model.
In order to experience the new features of Entity Framework I created a new MVC 4 internet application. I connected it to an existing database and generated the Model classes with the dbContext generator.
By running the application I got some validation errors while editing a form. As example for a DateTime field the system was complaining if the date was inserted as "12/10/2012" instead of 2012-10-12 (as in the SQ Server notation). I tried to find the validation code in the project, but I could not find it anywhere in the generated code.
One of my model classes is the following:
public partial class Artist
{
public Artist()
{
}
public int Id { get; set; }
public string Name { get; set; }
public Nullable<System.DateTime> DateOfBirth { get; set; }
public virtual Countries Countries { get; set; }
}
How can I customize the validation errors if using the Database first approach? If I decor the models with my validation attributes, then they would be erased once the model classes are generated again.
Moreover in a "real world" project where the use of an existing database is mandatory, what would be the best approach with the development of the model classes? Extending the classes automatically generated by adding partial classes with the same name?
EDIT (Introduced part of the View):
#using (Html.BeginForm()) {
#Html.ValidationSummary(false)
<fieldset>
<legend>Movie</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DateOfBirth)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DateOfBirth)
#Html.ValidationMessageFor(model => model.DateOfBirth)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Well, you can always use a metadata class
public partial class ArtistMetaData
{
[Required]
[StringLength(20)]
public string Name;//it can be a field instead of a property, must just have the same name and type than in your Model class
}
and a partial class
[MetadataType(typeof(ArtistMetaData)]
public partial class Artist {
}
Or (my preferred solution) you can use an external Validation library, like the excellent FluentValidation
You have a "basic" validation by default (which can be removed in the global.asax), checking : that the non nullables values... are not null (like a default Required attribute), and that values are of the right type.
Check your browser and machine culture settings and then the jquery validation cultures.
I tend to have this problems since I have the Spanish config, but mvc comes with the English dates and currency formats, etc. For example dd/mm/yyyy vs mm/dd/yyyy
You could also use a regular expression attribute to validate the field as you wish.
I am working on an app where I have a requirement to be able to load object properties at runtime from a database. The customer wants to be able to add attributes to the database and have them show up in the app. I am accomplishing this by giving my model a list of Field objects that contain a name, a type, and a value. This works well for displaying and editing project properties, but I'm having trouble with validation in the editor view. Thanks for your help.
I want to be able to do this in my Edit action:
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
Normal view:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
</fieldset>
}
What I need to do:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
for(i = 1 to n) {
<div class="editor-label">
#Html.LabelFor(model => model.Fields[i].Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Fields[i].Value)
#Html.ValidationMessageFor(model => model.Fields[i].Value)
</div>
}
</fieldset>
}
Model:
public class Movie
{
public Movie()
{
this.Fields = new List<Field>();
}
public List<Movie> Movies { get; set; }
}
Field Class:
public class Field
{
public string Name { get; set; }
public Type Type { get; set; }
public object Value { get; set; }
}
I believe something like this should work:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
#{int i = 0;}
#foreach(var field in model.Fields) {
var htmlFieldName = string.Format("Fields[{0}]", i);
<div class="editor-label">
<label for="#htmlFieldName">#field.Title</label>
</div>
<div class="editor-field">
#Html.EditorFor(model => field, null, htmlFieldName)
#Html.ValidationMessage(htmlFieldName)
</div>
}
</fieldset>
}
(Note that I made up how you're producing your label text, since using the actual value as the label didn't make sense to me).
The POST should end up with values like this:
ID=123
Fields[0]=Jaws
Fields[1]=VeggieTales
...
... and that should automatically bind to your Movie model, provided the model has, for example, a List<string> named Fields. If your model doesn't look like that, this should at least get you on the right track.
Update
In your comment, you explain that you are trying to produce an editor for an object. There are two major points of difficulty here:
MVC relies on the static type returned in the lambda expression you give to EditorFor to determine which kind of editor it should produce. To override this, you will need to provide a specific template name where my original suggestion shows you providing null:
#Html.EditorFor(model => field.Value, field.Type.Name, htmlFieldName + ".Value")
You'll probably need to tweak this to make it provide the right template name for types like Integer, but this should give you the general idea.
When posting back, there is no way for the server to know that Field[0] is an int, etc. You can either:
Provide hidden values to specify each type, and then use a custom model binder that can consume this information to build each Field based on the combined Type and Value.
Recreate the structure of the Movie object on the server side based on the Movie's ID, and then walk through each of it's Fields calling:
TryUpdateModel((dynamic)field, string.Format("Field[{0}]", i));
There are probably other options, but that's about all the time I'm willing to put into this today.