Consider following compostion relation between Child & Root classes-
public class Child
{
public string ChildProperty { get; set; }
}
public class Root
{
public Child child { get; set; }
public string RootProperty { get; set; }
}
Creating strongly typed view for Root class, does not include child property.
<fieldset>
<legend>Root</legend>
<div class="editor-label">
#Html.LabelFor(model => model.RootProperty)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.RootProperty)
#Html.ValidationMessageFor(model => model.RootProperty)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
How do I generate scaffolding template to include child properties as well?
Thank you for your help.
Generally you would need to create your own T4 templates that do just that:
https://stackoverflow.com/a/16072432/176877
But, I'm not certain you could actually loop through those child models; I'm fairly certain you would need to take things a step farther and actually generate a Custom Scaffold with your own PowerShell script that loops through those models and calls a new set of templates you create.
http://blog.stevensanderson.com/2011/04/07/mvcscaffolding-creating-custom-scaffolders/
In other words, you have a lot of work ahead of you if you want to do this.
Related
I have a data-first set-up so my models are generated by the entity framework from my database and there is no default [Required] annotations. I have a simple table with three fields. One ID and two VARCHAR / text based fields.
No matter what I try, I cannot get the CRUD forms to stop validation. I disabled in the Web.config, I add [ValidateInput(false)] to the Create() method in the controller, but has no effect. I set the #Html.ValidationSummary to false,
This is the basic view:
#using (Html.BeginForm()) {
#Html.ValidationSummary(false)
<fieldset>
<legend>CallType</legend>
<div class="editor-label">
#Html.LabelFor(model => model.CALLTYPE)
</div>
<div class="editor-field">
#Html.TextBox("calltype", "", new { style = "width: 50px;" })
#Html.ValidationMessageFor(model => model.CALLTYPE)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DESCRIPTION)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DESCRIPTION)
#Html.ValidationMessageFor(model => model.DESCRIPTION)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Model (generated by Framework):
public partial class CALLTYPES2
{
public int ID { get; set; }
public string CALLTYPE { get; set; }
public string DESCRIPTION { get; set; }
}
Even if I insert just one character in each field, it still says: "The Value 'x' is invalid"
(I leave the validation messages on so I can see what is going on.)
What am I supposed to do? And how would I validate these fields later on - can I just add [Required] to Model generated code? What if I regenerate the Model from the database?
Does this have something to do with the model state in the controller?
[HttpPost]
public ActionResult Create(CALLTYPES2 calltype)
{
if (ModelState.IsValid)
{
db.CALLTYPES2.Add(calltype);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(calltype);
}
Not sure what I am missing and the tutorials I have read do not shed much light. Thanks for your response and apologies for my ignorance.
UPDATE
Found my error - The object name "calltype" in the Method Create() is the same as the name/id of the form field "calltype". I guess the binder tries to bind the string "calltype" to the object "calltype". Renamed it to:
public ActionResult Create(CALLTYPES2 ctype)
Now it works in both the Edit and Create Windows. "ctype" is not clashing with "calltype".
You forgot to include the ID field in your form. You could include it as a hidden field:
#Html.HiddenFor(model => model.ID)
Now the value of the ID property will be sent to the server when the form is submitted and the default model binder should not complain.
maybe this question is seen repeatedly around here but i was not able to find a answers.
my project is about reservations for hotels. I have a class Reservation witch has a Icollection of ChoosenRooms and a class that represents de User making the reservation, and other stuff like dates and other stuff.
The process is this:
In my first view I get the chosen rooms, dates, etc, then i pass that to my second view where i´m going to get the user info, and then i have a third view where i want to show all the gathered information so the user can finally click a button to save the data.
My problem is that i need to pass the reservation object class across all these views. In my testing i see that primitive types pass just fine BUT The iColletion of ChoosenRooms is lost when i post back from the view to the next controller action.
can someone post some example how to, Posting back from a view to a controller, complex types like ChoosenRooms inside another class Reservations, are not lost in the process?
Or maybe explain why this info is lost?
the code:
public class Reserva
{
public virtual int ID { get; set; }
[NotMapped]
public string[] q { get; set; }
[Display(Name = "Cliente")]
public virtual Utilizador utilizador { get; set; }
[Display(Name = "Quarto")]
public virtual ICollection<Quartos> ChoosenRooms{ get; set; }
[Display(Name = "Serviços Adicionais")]
public virtual ICollection<ReservasItens> itens { get; set; }
The view
#model SolarDeOura.Models.Reserva
#{
ViewBag.Title = "AddReservaUser";
var _reserva = TempData["reserva"] as Reserva;
}
<h2>AddReservaUser</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)
<fieldset>
<legend>Reserva</legend>
<div class="editor-label">
#Html.LabelFor(model => model.dtEntrada)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.dtEntrada)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.dtSaida)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.dtSaida)
</div>
<div class="editor-label">
#Model.q.Count() Choosen Rooms
</div>
#foreach (var q in Model.ChoosenRooms)
{
<ul>
<li>
#Html.DisplayFor(modelitem => q.descricao)
</li>
</ul>
}
posting back from here is the Problem. In this view " #foreach (var q in Model.ChoosenRooms)" has data but posting back the data is lost.
The concept of model binder at this point was not very clear to me and some knowledge about this topic is all it takes to solve the question.
In resume:
The view gets a model which is a complex type: class [reserva] has a collection of [ChoosenRooms] which is also a complex type.
The line #Html.DisplayFor(modelitem => q.descricao) renders the necessary html elements to display the data, but not the necessary html to be posted back to the controller (input element or hidden field ) so the model binder fails.
Also the controller (post) action argument didn't had the property name that matches the field, in this case it needed to be a String[] type since its a collection of values.
I would also recommend reading about Display Templates and Editor Templates.
I have a ViewModel class composed of several nested classes:
public class UserAccountViewModel : UserProfileViewModel
{
public UserAccountEmailViewModel UserAccountEmail { get; set; }
public UserAccountLocationViewModel UserAccountLocation { get; set; }
public UserAccountPasswordViewModel UserAccountPassword { get; set; }
}
The HTML rendered from this (pay attention to model.UserAccountEmail.Email):
<div class="editor-label">
#Html.LabelFor(model => model.UserAccountEmail.Email)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserAccountEmail.Email)
#Html.ValidationMessageFor(model => model.UserAccountEmail.Email)
</div>
Is this:
name="UserAccountEmail.Email"
I would prefer the name to simply be Email
Changing ViewData.TemplateInfo.HtmlFieldPrefix didn't help. Overloading the htmlFieldName in #Html.EditorFor isn't going to work because I still want the label and validation message to match the rendered HTML element (no overload for htmlFieldName in these functions).
I'd prefer to not create partials for this.
Edit:
Meh...using partials actually isn't too bad. It actually makes quite a lot of sense.
See my answer here about this design, it might make sense for this particular strategy
Many models to view with many partial views
So I have this demo project almost completely working.
public class Project
{
public int ID { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
public int ID { get; set; }
[Required]
public string Name { get; set; }
public int ProjectID { get; set; }
public virtual Project Project { get; set; }
}
Controller
public ActionResult Edit(int id)
{
var project = db.Projects.Where(p=>p.ID==id).Single();
return View(project);
}
[HttpPost]
public ActionResult Edit(Project project)
{
if (ModelState.IsValid)
{
var dbProject = db.Projects.Where(p => p.ID == project.ID).Single();
UpdateModel(dbProject);
db.SaveChanges();
TempData["Success"] = "Modelo Valido";
}
return RedirectToAction("Index");
}
View//strongly typed for project
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Project</legend>
#Html.HiddenFor(model => model.ID)
<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>
<h1>Tasks</h1>
#Html.EditorFor(m => m.Tasks)
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
EditorTemplate
#model MvcApplication2.Models.Task
<span>Task</span>
<br />
#Html.LabelFor(m => m.Name)
#Html.EditorFor(m => m.Name)
#Html.HiddenFor(m => m.ID)
#Html.HiddenFor(m => m.ProjectID)
#Html.ValidationMessageFor(m => m.Name)
The view displays this
The problem is that when I submit the form the Tasks are populated with everything except the virtual Project property... so the error i get it is
The operation failed: The relationship could not be changed because
one or more of the foreign-key properties is non-nullable. When a
change is made to a relationship, the related foreign-key property is
set to a null value. If the foreign-key does not support null values,
a new relationship must be defined, the foreign-key property must be
assigned another non-null value, or the unrelated object must be
deleted.
Here is a pic of my debugging breakpoint result
Please Help.
UPDATE:
I have changed my controller action to this
[HttpPost]
public ActionResult Edit(Project project)
{
if (ModelState.IsValid)
{
db.Entry(project).State = EntityState.Modified;
db.SaveChanges();
TempData["Success"] = "Modelo Valido";
return RedirectToAction("Index");
}
return View(project);
}
it is still not working correctly.
Now changes made to the Name of the project are updated correctly in the database. but changes made to any Task Name are ignored completely.
i believe #Html.EditorFor(m => m.Tasks) is generating html like (approximately)
<label>Name</label>
<input type="text" name="Tasks[0].Name" id="auto-gen-id"/>
<input type="hidden" name="Tasks[0].ID" id = "auto-gen-id"/>
<input type="hidden" name="Tasks[0].ProjectID" id = "auto-gen-id"/>
<!--html for validation span-->
Above is the approximate html generated for first Task in Collection and similar html will be generated for each task in the collection. The only difference is that index will be incremented in name attributes of all inputs i.e Tasks[1].Name, Tasks[1].ProjectID etc. This portion will actually bind to the Collection<Task> Tasks property of Project but you can see that in detail portion you don't have any inputs like
<input type="whatever" name="Tasks[0].Project.ProjectID" .../>
<input type="whatever" name="Tasks[0].Project.Name" ..../>
Modelbinder needs input elements with proper naming conventions to bind values to all properties of action method parameters. For testing purpose you can inlude these two lines in your Editor template for Task
#Html.TextBoxFor(x=>x.Project.ID)
#Html.TextBoxFor(x=>x.Project.Name)
input proper values for them in the form and you will have Project property of Task populated with these values. But may not be what you desire i.e entering project information twice and this may not be needed (if u are using Linq to sql its sure not needed). When you call your ORM for attaching entities to db entities it will know which Project elements, current Task belongs to.
Side Note: When you have problems with modelbinding, always pay attention to generated html. Generated html will dictate which form values will map to which properties of the model as long as you are using default modelbinder. it becomes especially important if you are having master detail kind of scenario as in your example.
I have found a way to get this to work but Im not completely happy with the approach.
see this question on how to refactor my current code to see how I am currently (hopefully temporarily doing it)
Help improving (refactoring) my code. Automapper - EF - asp.net mvc-3
I'm working with MVC3, and using Entity Framework 4.0 Entities as my model. So far, everything works great as far as using it as a model (all the crud operations/page generations work out of the box). I'm wondering, though, how do you get the same robust labels and validation information as when you generate a model manually?
Here's an example of what I mean. This is a class generated by the sample MVC3 project:
public class LogOnModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
With the example above, you can specify what gets rendered in a label for the field (Display), and what type of field to use (Password). However, when I try to use the entity framework and push it to the view below, I see the automatically generated labels are just the field names, and not anything I want the user to see/have to read:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Person</legend>
<div class="editor-label">
#Html.LabelFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.MiddleName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MiddleName)
#Html.ValidationMessageFor(model => model.MiddleName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Birthdate)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Birthdate)
#Html.ValidationMessageFor(model => model.Birthdate)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>}
My question is: How do I add these extra decorations to the entities that are generated using EF4? Is there something besides System.ComponentModel.DataAnnotations that I should be using? I know entities get regenerated and it's probably not a good idea to add this to entities' code directly, but for some reason I can't think of a better approach than manually entering the label text in the view (lame, there's no reason to have to do that, this is MVC!). I want to keep it so that the application is dynamic enough to be able to have the correct display information for my model come through and keep an MVC approach. How do I do it?
I haven't done this for ASP.NET MVC (only for Silverlight) but I believe the same principles would apply. You can create a "metadata buddy class" as below, because the types generated by EF should be partial, thus you can add a bit more to them (like the MetadataTypeAttribute) and then you create this sibling class that holds the metadata.
It's kind of ugly, but should work. It goes something like this (assuming the EF entity is named "Person"):
[MetadataType(typeof(PersonMetadata))]
public partial class Person {
// Note this class has nothing in it. It's just here to add the class-level attribute.
}
public class PersonMetadata {
// Name the field the same as EF named the property - "FirstName" for example.
// Also, the type needs to match. Basically just redeclare it.
// Note that this is a field. I think it can be a property too, but fields definitely should work.
[Required]
[Display(Name = "First Name")]
public string FirstName;
}
Same as above but with all the details, and it works
And Here is the Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace Validate.Models
{
[MetadataType(typeof(PersonMetadata))]
public partial class Person
{
// Note this class has nothing in it. It's just here to add the class-level attribute.
}
public class PersonMetadata
{
// Name the field the same as EF named the property - "FirstName" for example.
// Also, the type needs to match. Basically just redeclare it.
// Note that this is a field. I think it can be a property too, but fields definitely should work.
[Required]
[Display(Name = "Enter Your Name")]
public string FirstName;
}
}
Like Austin Lamb's answer, but instead, nesting the MetaData class within the entity class, thereby reducing the number of classes in your public namespace list, and eliminating the need to have a unique name for each metadata class.
using System.ComponentModel.DataAnnotations;
namespace Validate.Models
{
[MetadataType(typeof(MetaData))]
public partial class Person
{
public class MetaData
{
[Required]
[Display(Name = "Enter Your Name")]
public string FirstName;
//...
}
}
}