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.
Related
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 have a DropDownListFor control that I am wanting to show a display value that resides in a property within a model/class (this is the Rule class.) The view's model is actually a collection of these model/classes. However, when I select the item from the DropDownList, I want to send back the entire model as a parameter. I have this working perfectly with the following code, but the Name property within the parameter is coming back as null. The other properties all have appropriate values.
View Code:
#model List<StockTrader.Web.Data.Rule>
#{
ViewBag.Title = "Configure Rules";
}
<h2>#ViewBag.Title</h2>
<h4>Choose a rule to edit:</h4>
<form method="post" id="rulesform" action="SaveRules">
#Html.DropDownListFor(m => m.First().RuleID, new SelectList(Model.AsEnumerable(), "RuleID", "Name"))
<div style="margin-bottom: 15px;">
<label>Value:</label><br />
<input type="number" name="Value" style="margin-bottom: 15px;" /><br />
<button>Save Value</button>
</div>
Controller Code:
public ActionResult SaveRules(Rule model)
{
//do something
}
Rule Class:
public class Rule
{
public int RuleID { get; set; }
public string Name { get; set; }
public int Value { get; set; }
public bool IsDeleted { get; set; }
}
We do have Kendo controls, so if another control would be more appropriate, that is an option.
I would be glad to provide anymore code or information you might need.
Any thoughts or ideas?
EDIT:
So it turns out this is what I needed to do, the accepted answer got me to this point so I'm going to leave it checked.
View Code (w/script included):
#Html.DropDownListFor(m => m.First().RuleID, new SelectList(Model.AsEnumerable(), "RuleID", "Name"), new { id = "ruleid", #onchange = "CallChangefunc(this)" })
#Html.HiddenFor(m => m.First().Name, new { id = "rulename" })
function CallChangefunc(e) {
var name = e.options[e.selectedIndex].text;
$("#rulename").val(name);
}
You will need a hidden field for it,and use dropdownlist on change event on client side to update hidden field:
#Html.DropDownListFor(m => m.First().RuleID, new SelectList(Model.AsEnumerable(), "RuleID", "Name"),new { id= "ruleid" })
#Html.HiddenFor(m=>m.First().Name,new { id="rulename" })
and jquery code:
$("#ruleid").change(function(){
$("#rulename").val($(this).text());
});
Second option isif Rule collection is coming from database you can fetch RuleName by using id to by querying db in action.
it can be achieved by using UIHint
On your model class, on the RuleID property, add an annotation for UIHint. It basically lets you render a partial (cshtml) for the property. So, on the partial, you can have the template for generating the dropdwon with required styling. When Page is generated. Now you can use the same Html.DropDownListFor for RuleID and UI generates a dropdown for it.
This will avoid having additional jQuery code to get the dropdown value, and code is more concise and testable.
I'm working on an EntityFramework project and running into an interesting problem. What I want to do is create a database entity using a view, but in order to do that I need to create another database entity of a different type that the first entity needs to be associated with, and I'm trying to do this from the same view.
For example, we have a Person, and each Person will have a recurring appointment. However, appointments can be recurring on different types of criteria. Right now I'm trying to get this working on appointments that will be on a daily basis (meaning, for example, every Monday, Wednesday, and Friday) So here, my model is something like:
DailyAppointment implements the abstract class AppointmentFrequency
Person has an AppointmentFrequency associated with it. Here is the code behind my model (database generated using code-first migrations).
AppointmentFrequency :
public abstract class AppointmentFrequency
{
[KeyAttribute]
public int Identity { get; set; }
}
DailyAppointment :
public class DailyAppointment : AppointmentFrequency
{
public bool Monday { get; set; }
// ... Variable for each day of the week.
}
Person:
public class Person
{
[Key]
public int Identity { get; set; }
//... Other information
[ForeignKey("AppointmentFrequency_Identity")]
public virtual AppointmentFrequency AppointmentFrequency { get; set; }
public int? AppointmentFrequency_Identity { get; set; }
}
So in our view, when we create a Person, we want to have an AppointmentFrequency associated with them.
Currently, my approach involves a partial view inside the view that creates a Person:
#using (Html.BeginForm("AddPerson", "ControllerName", FormMethod.Post, new { role = "form", #class = "form-inline" }))
{
... //This is where we get information about the Person
Model.Person.AppointmentFrequency = new DailyAppointment();
var dailyAppointment = Model.Person.AppointmentFrequency as DailyAppointment;
if (dailyFrequency != null)
{
#Html.Partial("_DailyAppointmentEditor", Model.Person.AppointmentFrequency as DailyAppointment);
}
<div class="form-group">
<button class="btn btn-primary" type="submit">Add</button>
</div>
}
(I have also tried doing this a few similar ways, such as sending the dailyAppointment variable into the partial view instead)
My partial view looks like this:
#model Database.Entities.DailyAppointment
#Html.LabelFor(model => model.Monday)
#Html.CheckBoxFor(model => model.Monday, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Monday, null, new { #class = "text-danger" })
#Html.LabelFor(model => model.Tuesday)
#Html.CheckBoxFor(model => model.Tuesday, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Tuesday, null, new { #class = "text-danger" })
... //The rest of the days and script bundling
Inside my controller, I am only creating a Person, and was hoping that the Appointment would be created by the Framework, but that doesn't seem to be the case.
[HttpPost]
public ActionResult AddPerson(Person person){
this.db.People.AddOrUpdate(person);
this.db.SaveChanges();
return this.View();
}
I know one way of doing this would be to collect the data and then use it in the form post sent to the controller, creating the frequency, and then adding the reference to the person object and creating it in the database. I have actually done that and know that it works, but I feel like there must be a more friendly way of doing this within the framework.
Part of the challenge here is that I'm hoping to make this extensible while still using the same design. Let me know if I can give any more information, but if you have any suggestions for this approach or for a different approach I can take, I would greatly appreciate it! Thank you.
I have a user object, which has three bits of data, the user, their subscription level and a list of all groups that they ARE or COULD be a member of. I need to generate a view that lets me edit this data in a smart way and I want it to be tied to my strongly typed model.
I would like to have one check box for each possible group, and allow the user of my app to check or uncheck boxes to change membership. The problem I'm having is that my action
[HttpPost]
public ActionResult Edit(UserModel usr)
{
}
never receives the values from the checkboxes in the view, but the other properties are correctly populated.
My first view looked like this:
...
#Html.EditorFor(m => m.UserName)
#Html.EditorFor(m => m.Subscription)
#Html.EditorFor(m => m.Membership) // just displays the string and bool values
...
My second stab [also failed]
...
#Html.EditorFor(m => m.UserName)
#Html.EditorFor(m => m.Subscription)
#foreach(var itm in Model.Membership)
{
#Html.EditorFor(_ => itm)
}
...
I also tried, and failed to hand craft the <input> tags without using #Html. with no luck.
This is what the UserModel looks like:
public class UserModel
{
public string UserName { get; set; }
public int Subscription { get; set; }
public Dictionary<String, bool> Membership { get; set; } // this is the problem
}
There may be a better way to solve this, but I ended up switching from Dictionary<String,Bool> to a List<SelectListItem>
The view code that generates correct post data is as follows:
...
<ul>
#for (int i = 0; i < Model.Membership.Count; i++ )
{
<li>
#Html.HiddenFor(_ => Model.Membership[i].Text)
#Html.HiddenFor(_ => Model.Membership[i].Value)
#Html.CheckBoxFor(_ => Model.Membership[i].Selected) #Model.Membership[i].Text
</li>
}
</ul>
...
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.