DataAnnotations on Entity not displaying in View - asp.net-mvc-3

I have a Supplier Entitiy that contains
ID - int
Status - string
Name - string
CreateDate- datetime
I am using the partial class method to create Data Annotations for the above Entity.as described here
namespace TemplateEx.Models
{
[MetadataType(typeof(SupplierMetadata))]
public partial class Supplier
{
// Note this class has nothing in it. It's just here to add the class-level attribute.
}
public class SupplierMetadata
{
// 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 = "Supplier Name")]
public string Name;
}
}
My defined a controller action as below
public ViewResult Details(int id)
{
Supplier supplier = db.Suppliers1.Single(s => s.ID == id);
return View(supplier);
}
When I create a view for this action and picked the Details scaffolding for the Supplier entity following is what I got as a view
#model TemplateEx.Models.Supplier
#{
ViewBag.Title = "Details";
}
<h2>Details</h2>
<fieldset>
<legend>Supplier</legend>
<div class="display-label">CreateDate</div>
<div class="display-field">
#Html.DisplayFor(model => model.CreateDate)
</div>
<div class="display-label">Status</div>
<div class="display-field">
#Html.DisplayFor(model => model.Status)
</div>
<div class="display-label">Name</div>
<div class="display-field">
#Html.DisplayFor(model => model.Name)
</div>
</fieldset>
<p>
#Html.ActionLink("Edit", "Edit", new { id=Model.ID }) |
#Html.ActionLink("Back to List", "Index")
</p>
Notice how the model.Name has a "Name" label instead of "Supplier Name".What am I doing wrong?

replace
<div class="display-label">Name</div>
with
<div class="display-label">#Html.LabelFor(model => model.Name)</div>
Edit :
For the second question, look here
How can i enforce the scaffolding automatically generated code to display the Label and the EditorFor text field at the same line in my asp.net mvc 3 (specially last answer)

Related

Checkboxlist - on Post: Create - NullReferenceException

StudentModel.
namespace mvcApp.Models
{
public class StudentModel
{
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Email Address")]
public string EmailAddress { get; set; }
public List<SchoolOrganization> Organizations { get; set; }
}
public class SchoolOrganization
{
public string Name { get; set; }
public bool IsInvolved { get; set; }
}
}
Student is involved in multiple organizations.
Controller
namespace mvcApp.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
public ActionResult StudentInformation()
{
// populate student with data
var student = new StudentModel() { FirstName = "Joe", LastName = "Doe", EmailAddress = "jdoe#hotmail.com"};
// Populate with Organizations
student.Organizations = new List<SchoolOrganization>();
student.Organizations.Add(new SchoolOrganization() { Name = "Math Club", IsInvolved = true});
student.Organizations.Add(new SchoolOrganization() { Name = "Chess Club", IsInvolved = false });
student.Organizations.Add(new SchoolOrganization() { Name = "Football", IsInvolved = true });
return View(student);
}
**[HttpPost]
public ActionResult StudentInformation(StudentModel student)
{
Response.Write("Name: " + student.FirstName);
foreach (var o in student.Organizations)
{
Response.Write(o.Name + " : " + o.IsInvolved.ToString());
}
return View();
}**
}
}
Data will be eventually populated from database.
View
#model mvcApp.Models.StudentModel
#{
ViewBag.Title = "StudentInformation";
}
<h2>StudentInformation</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>StudentModel</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.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.EmailAddress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.EmailAddress)
#Html.ValidationMessageFor(model => model.EmailAddress)
</div>
<div>
<table>
<tr>
<td>Organization Name</td><td>Is Involved</td>
</tr>
#for (int i = 0; i < Model.Organizations.Count; i++) <== System.NullReferenceException here
{
#Html.HiddenFor(m => m.Organizations[i].IsInvolved)
<tr>
<td>#Html.DisplayFor(m => m.Organizations[i].Name)</td>
<td>#Html.CheckBoxFor(m => m.Organizations[i].IsInvolved)</td>
</tr>
}
</table>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
The above code displays fine with HttGet. However, when i try to update i get System.NullReferenceException. https://www.dropbox.com/s/dz0bg3hkd0yq8e3/studentInformation.png?dl=0
Can anyone please help figure it what's going on?
Thank you.
In the code sample you have provided; the HomeController's [HttpPost] ActionResult for StudentInformation does not create and pass a new instance of the updated object model to the view, but instead runs a basic debug.writeline routine. As a result the dependent view for the [HttpPost] view of "StudentInformation.cshtml", does not receive a populated instance of the updated model...
Setting a breakpoint on the first line of the model for the "StudentInformation.cshtml" page and running the application will demonstrate that the model referenced at the top of the page, has no data within it...
This is why the [HttpPost] version of the page simply renders the blank model, without any altered data values you may have created, UNTIL it gets to the section where the view is dependent on a count of new data values which must be present within the model that is called at first line of the page... to continue.
An empty data model set results in a null reference because there are no values to count within it.
In order to view an updated group of settings, The [HttpPost] version of the view model must be passed an instance of the model that returns the new information as in "return View(nameOfViewDataSet)" (you build a data set again, and pass it as a new version of the model, with revised form data present).
Until there is data passed via the return View statement relative to the [HttpPost] version of the StudentInformation ActionResult, to the actual view, there will be no display data, and the count function will continue to return a null value.
I hope that is helpful.

Update model one of the properties of List type is null upon submit

I have Model class "Customer". One of its properties is a collection of object lets say "Order". I want that during edit I can modify the list of orders associated with the selected Customer that I want to update but when I submit the modified customer and modified orders the order object is null. Please help how can I send the modified orders on edit. Here's my code
Class
public class Customer
{
public int CustomerId { get; set; }
public string CustomerName { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public string OrderName { get; set; }
}
Edit View
#model MVCTestApp.Models.Customer
#{
ViewBag.Title = "Edit";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Customer</legend>
#Html.HiddenFor(model => model.CustomerId)
<div class="editor-label">
#Html.LabelFor(model => model.CustomerName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.CustomerName)
#Html.ValidationMessageFor(model => model.CustomerName)
</div>
#foreach (var order in Model.Orders)
{
<div class="editor-label">
#Html.Label(order.OrderName)
</div>
<div class="editor-field">
#Html.Editor(order.OrderName, new { id = order.OrderId })
</div>
}
<p><input type="submit" value="Save" /> </p>
</fieldset>
}
Submitted Edited View
Use a for-loop instead of foreach:
#for (var i=0;i<Model.Orders.Count();i++)
{
<div class="editor-label">
#Html.Label(Model.Orders[i].OrderName)
</div>
<div class="editor-field">
#Html.EditorFor(m=> Model.Orders[i].OrderName)
</div>
}
The reason for this issue can be easily seen on the rendered html of the foreach approach. The names of the elements do not have an index and they all have the same name. The model binder cannot infer how to pass it to your controller. Using a for-loop you are somewhat forcing (if that's the right term) the markup/html to have an index on your Orders objects. Having done that, the model binder can now properly map your inputs to the Orders field.
Oh by the way. Now that you are using a for-loop you need to verify that your Orders collection is not null. But I'm sure you can easily do that.
I'd recommend you to get orders in action like this:(check if order_OrderName is the name of the input filed in form, see the source for example with firebug or something)
public ActionResult Edit(int id, string[] order_OrderName , Customer customer )
{
//////
}

ASP.Net MVC 3 Data Annotation

I am building an ASP.Net MVC 3 Web application using Entity Framework 4.1. To perform validation within one of my Views which accepts a ViewModel. I am using Data Annotations which I have placed on the properties I wish to validate.
ViewModel
public class ViewModelShiftDate
{
public int shiftDateID { get; set; }
public int shiftID { get; set; }
[DisplayName("Start Date")]
[Required(ErrorMessage = "Please select a Shift Start Date/ Time")]
public DateTime? shiftStartDate { get; set; }
[DisplayName("Assigned Locum")]
public int assignedLocumID { get; set; }
public SelectList AssignedLocum { get; set; }
}
View
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<br />
<div class="editor-label">
#Html.LabelFor(model => model.shiftStartDate)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.shiftStartDate, new { #readonly = "readonly" })
#Html.ValidationMessageFor(model => model.shiftStartDate)
</div>
<br />
<div class="editor-label">
#Html.LabelFor(model => model.assignedLocumID)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.assignedLocumID, Model.AssignedLocum)
#Html.ValidationMessageFor(model => model.assignedLocumID)
</div>
<br />
<p>
<input type="submit" value="Save" />
</p>
<br />
}
The SelectList 'AssignedLocum' is passed into my View for a DropDownList, and the item selected is assigned to the property 'assignedLocumID'.
As you can see from my ViewModel, the only required field is 'shiftStartDate', however, when I hit the Submit button in my View, the drop down list 'AssignedLocum' also acts a required field and will not allow the user to submit until a value is selected.
Does anyone know why this property is acting as a required field even though I have not tagged it to be so?
Thanks.
Try to use default value for dropdown (for example "Please select")
#Html.DropDownListFor(model => model.assignedLocumID, Model.AssignedLocum, "Please select")

Retrieve a subset of a ViewModel from a RenderPartial on POST

I have a ViewModel which contains a child ViewModel. In a strongly typed Viewed of the parent, I want to RenderPartial on the child, and have results persist following POST.
But the child fields are always null.
This should work, shouldn't it? I am fairly new to MVC, hopefully I'm missing something simple. Hopefully someone can point it out!
Thanks!
Example
ViewModels
public class EggBoxViewModel
{
public string Brand { get; set; }
public int Price { get; set; }
public EggViewModel Egg { get; set; }
}
public class EggViewModel
{
public string Size { get; set; }
public bool IsBroken { get; set; }
}
Controller
public ActionResult Demo()
{
EggBoxViewModel eggBox = new EggBoxViewModel();
eggBox.Brand = "HappyEggs";
eggBox.Price = 3;
EggViewModel egg = new EggViewModel();
egg.Size = "Large";
egg.IsBroken = false;
eggBox.Egg = egg;
return View(eggBox);
}
[HttpPost]
public ActionResult Demo(EggBoxViewModel eggBox)
{
// here, eggBox.Egg is null
}
Views
"Demo"
#model MvcApplication1.ViewModels.EggBoxViewModel
#using (Html.BeginForm())
{
<h2>EggBox:</h2>
<p>
#Html.LabelFor(model => model.Brand)
#Html.EditorFor(model => model.Brand)
</p>
<p>
#Html.LabelFor(model => model.Price)
#Html.EditorFor(model => model.Price)
</p>
<p>
#{Html.RenderPartial("_Egg", Model.Egg);}
</p>
<input type="submit" value="Submit" />
}
"_Egg" (Partial)
#model MvcApplication1.ViewModels.EggViewModel
<h2>Egg</h2>
<p>
#Html.LabelFor(model => model.Size)
#Html.EditorFor(model => model.Size)
</p>
<p>
#Html.LabelFor(model => model.IsBroken)
#Html.CheckBoxFor(model => model.IsBroken)
</p>
Use an Editor Template, in most cases they're better for rendering child objects or collections. In your Views\Shared folder create a new folder called EditorTemplates if you don't already have one. Add a new partial view called EggViewModel and use the same code as in your partial view. This is the bit of magic that renders and names all your fields correctly. It will also handle a collection of EggViewModels without a for each loop as the Editor template will automatically render all the items passed in a collection:
#model MvcApplication1.ViewModels.EggViewModel
<h2>Egg</h2>
<p>
#Html.LabelFor(model => model.Size)
#Html.EditorFor(model => model.Size)
</p>
<p>
#Html.LabelFor(model => model.IsBroken)
#Html.CheckBoxFor(model => model.IsBroken)
</p>
Then in your Demo View use your new Editor Template instead of the partial:
<p>
#Html.EditorFor(x => x.Egg)
</p>
Here are the fields that are rendered:
In the post back you can that the EggViewModel is now part of the EggBoxViewModel:
Also in the ModelState you can see that the Egg fields are prefixed with the property name used in the EggBoxViewModel which makes them a subset of EggBox.
And...the great thing is that if you want to have a collection of Eggs in your EggBox it's really easy...just make your Egg property on EggBoxViewModel a collection:
public class EggBoxViewModel
{
public string Brand { get; set; }
public int Price { get; set; }
public ICollection<EggViewModel> Eggs { get; set; }
}
Add a second egg:
public ActionResult Demo()
{
EggBoxViewModel eggBox = new EggBoxViewModel();
eggBox.Brand = "HappyEggs";
eggBox.Price = 3;
EggViewModel egg = new EggViewModel();
egg.Size = "Large";
egg.IsBroken = false;
EggViewModel egg2 = new EggViewModel();
egg2.Size = "Medium";
egg2.IsBroken = false;
eggBox.Eggs = new List<EggViewModel>();
eggBox.Eggs.Add(egg);
eggBox.Eggs.Add(egg2);
return View(eggBox);
}
Change your View to render x.Eggs instead of x.Egg:
<p>
#Html.EditorFor(x => x.Eggs)
</p>
Then on post back you'll see there are 2 Eggs posted back:
The field names have automatically been indexed and named to create a collection of Eggs:
you'll have to change your partial view model to EggBoxViewModel so that the Egg object is exposed on the view. i.e:
#model MvcApplication1.ViewModels.EggBoxViewModel
<h2>
Egg</h2>
<p>
#Html.LabelFor(model => model.Egg.Size)
#Html.EditorFor(model => model.Egg.Size)
</p>
<p>
#Html.LabelFor(model => model.Egg.IsBroken)
#Html.CheckBoxFor(model => model.Egg.IsBroken)
</p>
thus, you'd call it from the main view as:
#{Html.RenderPartial("_Egg", Model);}
you should now see the Egg object in your model being returned in the HttpPost action.
[Update method 2]
ok, really quick edit!! You can keep everything the same as initially and try this in your partial (i'm not 100% certain that this will work tho):
#model MvcApplication1.ViewModels.EggViewModel
<h2>
Egg</h2>
<p>
#Html.LabelFor(model => model.Size)
#Html.Editor("Egg.Size", Model.Size)
</p>
<p>
#Html.LabelFor(model => model.IsBroken)
#Html.CheckBox("Egg.IsBroken", Model.IsBroken)
</p>
here, I just use #Html.Editor() and input the required model name, rather than #Html.EditorFor(). Ok, gorra dash... train to 'cache' ;-)

Model Binder & Hidden fields

I have a simplified test scenario useful for asking this question: A Product can have many Components, a Component can belong to many Products. EF generated the classes, I've slimmed them as follows:
public partial class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Component> Components { get; set; }
}
public partial class Component
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
The creation of a component is accomplished via these controller actions:
public ActionResult Create(int ProductId)
{
Product p = db.Products.Find(ProductId);
Component c = new Component();
c.Products.Add(p);
return PartialView(c);
}
[HttpPost]
public ActionResult Create(Component model)
{
db.Components.Add(model);
db.SaveChanges();
}
and the view returned by the GET method looks like this:
#model Test.Models.Product
<fieldset>
<legend>Product</legend>
<div class="display-label">Name</div>
<div class="display-field">#Model.Name</div>
</fieldset>
#Html.Action("Create", "Component", new {ProductId = Model.Id})
<p>
#Html.ActionLink("Edit", "Edit", new { id=Model.Id }) |
#Html.ActionLink("Back to List", "Index")
</p>
From which can be seen that the component creation is handled on the same page via the above Html.Action - the code for that view follows:
#model Test.Models.Component
#using Test.Models
<script type="text/javascript">
function Success() {
alert('ok');
}
function Failure() {
alert('err');
}
</script>
#using (Ajax.BeginForm("Create", "Component", new AjaxOptions
{
HttpMethod = "Post",
OnSuccess = "Success",
OnFailure = "Failure"
}))
{
<fieldset>
<legend>Components</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>
#Html.HiddenFor(x => x.Products.First().Id)
#Html.HiddenFor(x => x.Products)
#foreach (Product p in Model.Products)
{
#Html.Hidden("Products[0].Id", p.Id)
}
#foreach (Product p in Model.Products)
{
#Html.Hidden("[0].Id", p.Id)
}
</fieldset>
<input type="submit" value="go" />
}
ok. so this is what I'm struggling with: I need the model parameter of the [HttpPost]back to get properly populated i.e. it should contain a Product, since I can't create the new component with a null product. To get the product I need to look it up via the product's id. I expect I should be able to do:
model.Products.Add(db.Products.Find(model.Products.First().Id));
or some such thing, which relies on model receiving the id. This means the view has to place the id there, presumably in a hidden field, and as can be seen from my view code, I've made several attempts at populating this, all of which have failed.
Normally I prefer the *For methods since they become responsible for generating correct nomenclature. If .Products were singular (.Product), I could reference it as x => x.Product.Id and everything would be fine, but since it's plural, I can't do x => x.Products.Id so I tried x => x.Products.First().Id which compiles and produces the right value but gets name Id (which is wrong since the model binder thinks it's Component.Id and not Component.Products[0].Id.
My second attempt was to let HiddenFor iterate (like I would with EditorFor):
#Html.HiddenFor(x => x.Products)
but that produces nothing - I've read that this helper doesn't iterate. I tried x => x.Products.First() but that doesn't even compile. Finally, I decided to abandon the *For and code the name myself:
#foreach (Product p in Model.Products)
{
#Html.Hidden("Products[0].Id", p.Id)
and though that looks right, the postback doesn't see my value (Products.Count == 0). I saw in some posting that format should look like [0].Id but that doesn't work either. grr...
I gather I could code it like this:
#Html.Hidden("ProductId", p.Id)
and then redeclare my controller action like this:
[HttpPost] ActionResult Create(Component model, int ProductId)
but that seems eecky. it's hard to believe this is so difficult. can anyone help?
e
p.s. I have a project I could make available for download if anyone cares
Instead of writing those foreach loops try using editor templates:
<fieldset>
<legend>Components</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>
#Html.EditorFor(x => x.Products)
</fieldset>
and inside the corresponding editor template (~/Views/Shared/EditorTemplates/Product.cshtml)
#model Product
#Html.HiddenFor(x => x.Id)

Resources