Validation of Viewmodel containing nested domain objects - asp.net-mvc

I have the following 2 entities:
public class Product
{
[Key]
public int ID { get; set; }
[Required]
public string Name { get; set; }
public virtual Category Category { get; set; }
}
public class Category
{
[Key]
public int ID { get; set; }
[Required]
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
and a view model
public class ProductCreateOrEditViewModel
{
public Product Product { get; set; }
public IEnumerable<Category> Categories { get; set; }
}
The create view for Product uses this ViewModel. The category ID is set as follows in the view:
<div class="editor-field">
#Html.DropDownListFor(model => model.Product.Category.ID,new SelectList
(Model.Categories,"ID","Name"))
#Html.ValidationMessageFor(model => model.Product.Category.ID)
</div>
When the form posts I get an instance of the view model with a product and the selected category object set but since the "Name" property of Category has a [Required] attribute the ModelState is not valid.
As far as creating a Product goes I don't need or care for the "Name" property. How can I get model binding to work such that this is not reported as a ModelState error?

You should create a correct ViewModel for your View.
The best approach imo is not to expose your domain entities to the view.
You should do a simple DTO flattening from your entities to your viewmodel.
A class like that
public class ProductViewModel
{
public int ID { get; set; }
[Required]
public string Name { get; set; }
public int CategoryId? { get; set; }
public SelectList Categories { get; set; }
}
From your controller you map the product to your viewmodel
public ViewResult MyAction(int id)
{
Product model = repository.Get(id);
//check if not null etc. etc.
var viewModel = new ProductViewModel();
viewModel.Name = model.Name;
viewModel.CategoryId = model.Category.Id;
viewModel.Categories = new SelectList(categoriesRepo.GetAll(), "Id", "Name", viewModel.CategoryId)
return View(viewModel);
}
Then in the action that respond to the post, you map back your viewModel to the product
[HttpPost]
public ViewResult MyAction(ProductViewModel viewModel)
{
//do the inverse mapping and save the product
}
I hope you get the idea

Related

Populate Dropdownlist from Model in ASP.NET MVC

I am working on a CRUD ASP.NET Core MVC application. I have two entities Product and Categrory, i want to populate a DropDownlist from model "Category" in the "Product" View. Here is my code:
public class Product
{
[Key]
public int ProductId { get; set; }
public string ProductName { get; set; }
public string Description { get; set; }
[ForeignKey("CategoryId")]
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
}
public class Category
{
[Key]
public int CategoryId { get; set; }
[Required]
public string CategoryName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
ProductController.cs:
[HttpGet]
public IActionResult Create()
{
List<Category> categories = _dbcontext.Category.ToList();
ViewBag.bpCategories = new SelectList(categories, "CategoryId", "Category");
Product product = new Product();
return View(product);
}
and in Create.cshtml i used this code to display the Dropdownlist:
<div class="form-group">
<label asp-for="CategoryId" class="control-label"></label>
<select asp-for="CategoryId" class="form-control" asp-items="ViewBag.bpCategories"></select>
</div>
But this code throws Nullreference exception. Any suggestions??
Maybe an error comes from SelectList constructor. Try this:
ViewBag.bpCategories = new SelectList(categories, "CategoryId", "CategoryName");
Use "CategoryName" as text value instead of "Category".There is no Category property in your Category class.
The third parameter is the data text field. Check here:
https://learn.microsoft.com/en-us/dotnet/api/system.web.mvc.selectlist.-ctor?view=aspnet-mvc-5.2#system-web-mvc-selectlist-ctor(system-collections-ienumerable-system-string-system-string)

Implement a collection of an object in my view model - ASP.net MVC

I'm Completely new in ASP.net MVC
I have two model first is category
public class Category
{
public virtual int Id { get; set; }
[Required]
public virtual string CategoryName { get; set; }
public virtual List<SubCategory> SubCategories { get; set; }
}
and next subcategory model:
public class SubCategory
{
public virtual int Id { get; set; }
public virtual int CategoryId { get; set; }
[Required]
public virtual string SubCategoryName { get; set; }
public virtual Category Category { get; set; }
public virtual List<Question> Questions { get; set; }
}
I need to have a list of my categories and also a list of subcategories both in my view so this is my view model
public class CategorySubCategoryViewMoel
{
public ICollection<Category> Category { get; set; }
public SubCategory SubCategory { get; set; }
}
and here is my view
#model FinalSearch5.Models.CategorySubCategoryViewMoel
<ul>
#foreach (var category in Model.Category)
{
<li>#category.CategoryName</li>
}
</ul>
here is my controller
public ActionResult Index()
{
var cats = db.Categories.ToList();
return View(cats);
}
It's not implementing completely yet but until here there is an Error
The model item passed into the dictionary is of type 'System.Collections.Generic.List1[FinalSearch5.Models.Category], but this dictionary requires a model item of type 'FinalSearch5.Models.CategorySubCategoryViewMoel'.`
Appreciate if help me what is the problem thank you.
You view expects a model which is typeof CategorySubCategoryViewMoel but in the Index() method, you return a model which is List<Category>. In order for it to work, you could change the method to
public ActionResult Index()
{
var model = new CategorySubCategoryViewMoel
{
Category = db.Categories.ToList()
}
return View(model );
}
Note that the property is a collection, so its name should be pluralized, and need only be IEnumerable i.e. public IEnumerable<Category> Categories { get; set; }
However its unclear what the purpose of your view model is. Its only other property is public SubCategory SubCategory { get; set; } and you never assign a value to it, or use it in the view. You code could use your current Index() method and then the view would be
#model List<Category>
<ul>
#foreach(var category in Model)
{
<li>
#category.CategoryName
<ul>
#foreach(var subCategory in category.SubCategories)
{
<li>#subCategory.SubCategoryName</li>
}
</ul>
<li>
}
<ul>

MVC Multiple tables one model

I am having difficulty with my understanding of MVC coming from an aspx world.
I have a Model called CustomerGarment. This has a Order and a Customer along with a few garments.
public class CustomerGarment
{
public int CustomerGarmentId { get; set; }
public virtual Customer Customer { get; set; }
public virtual Order Order { get; set; }
public virtual GarmentJacket GarmentJacket { get; set; }
public virtual GarmentShirt GarmentShirt { get; set; }
}
I have a method for get and post. When the page loads, it creates a new CustomerGarment instance and querys the database to fill the Customer and Order variables. I then use the viewbag to show on the screen a list of GarmentJackets and GarmentShirts
The page then views and using the view I can access the model perfectly. Drop downs load with the viewbag contents and I can access all Customer and Order variables using the model I have passed.
The problem I then face is when I use the HttpPost. The model is not passed back with the information I passed to it.
public ActionResult AddGarments(int orderId, int customerId)
{
CustomerGarment cg = new CustomerGarment();
cg.Order = (from a in db.Orders where a.OrderId == orderId select a).FirstOrDefault();
cg.Customer = (from a in db.Customers where a.CustomerId == customerId select a).FirstOrDefault();
var jackets = from a in db.GarmentJackets orderby a.Type, a.SleeveLengthInches, a.ChestSizeInches select a;
var shirts= from a in db.GarmentKilts orderby a.PrimarySize, a.DropLength select a;
ViewBag.GarmentJacket = new SelectList(jackets, "GarmentJacketId", "GarmentJacketId");
ViewBag.GarmentShirt = new SelectList(shirts, "GarmentShirtId", "GarmentShirtId");
return View(cg);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddGarments(CustomerGarment cg)
{
// Here, I do not have the customer info for example
db.CustomerGarments.Add(cg);
db.SaveChanges();
return RedirectToAction("Index");
return View(cg);
}
This is a bit of my view
#Html.HiddenFor(model => model.Order.OrderId)
#Html.HiddenFor(model => model.Order.CustomerId)
<div class="display-field">
#Html.DisplayFor(model => model.Customer.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.GarmentJacket, "Jacket")
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.GarmentJacket, (SelectList)ViewBag.GarmentJacket, new {style="width:312px;height:30px;margin-top:2px;margin-bottom:5px"})
</div>
EDIT
My Garment Jacket Model
public class GarmentJacket : Garment
{
public int GarmentJacketId { get; set; }
[Required]
public string Type { get; set; }
[Required]
[Display(Name = "Chest Size")]
public int ChestSizeInches { get; set; }
[Required]
[Display(Name = "Sleeve Length")]
public int SleeveLengthInches { get; set; }
}
public class Garment
{
[DataType(DataType.Date)]
public DateTime? DateRetired { get; set; }
[Required]
public string Barcode { get; set; }
[Required]
public bool Adults { get; set; }
}
In your CustomerGarment class, you should have:
public class CustomerGarment
{
public int CustomerGarmentId { get; set; }
public int CustomerId { get; set; }
public int OrderId { get; set; }
public int GarmentJacketId { get; set; }
public int GarmentShirtId { get; set; }
public virtual Customer Customer { get; set; }
public virtual Order Order { get; set; }
public virtual GarmentJacket GarmentJacket { get; set; }
public virtual GarmentShirt GarmentShirt { get; set; }
}
And, then, in your View, your DropDownList will look like:
#Html.DropDownListFor(m => m.GarmentJacketId, (SelectList)ViewBag.GarmentJacket, new {style="width:312px;height:30px;margin-top:2px;margin-bottom:5px"})
Your DropDownList only posts one value, which is the GarmentJacketId. You can't bind that Id to the whole GarmentJacket class.
By the way, you also need to replace your hidden inputs with these:
#Html.HiddenFor(model => model.OrderId)
#Html.HiddenFor(model => model.CustomerId)
I think I know your problem. As you suggested in you comment above you need to post everything you want retained in the view. This is one of the differences beteween webforms and MVC, webforms has viewstate that could contain information that you don't explicitly add to the view and post back, giving the impression of state. In MVC you have to add it to the view.
On the other hand you don't need to pass in more information than you need either. You pass inn the customerId as a hidden field. On post method you get the customer from the db using the Id, then you add the order to the customer.
I have some questions about your design, but given that a customer holds a collection of Orders, you could do something like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddGarments(CustomerGarment cg)
{
// Get the customer from the database
var customer = db.Customers.Find(c=>c.id==cb.Customer.Id)
var order = new Order();
//Create your order here using information from CustomerGarment model
//If the model already holds a valid Order object then just add it.
//i.e. you could get a Garment object from the DB using the GarmentId from
//the ViewModel if you really need more than just the Id to create the order
customer.Orders.Add(order);
db.SaveChanges();
return RedirectToAction("Index");
}

Load Navigation Property by its Key

I am using the EF CF approach and have:
the following data model:
public class Category {
[Key]
public int CategoryID { get; set; }
[Required]
public string CategoryName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
public class Product {
[Key]
public int ProductID { get; set; }
[Required]
public string ProductName { get; set; }
public virtual Category Category { get; set; }
}
the following DataContect definition:
public class EFCFContext : DbContext {
public EFCFContext() : base("EFCFConnection") { }
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
}
The purpose of this code is as follows:
I have an ASP.NET MVC 4 application. There is a form for editing the Product entity, and a "lookup" selector/editor for the Category entity bound with the Category.CategoryID property.
When submitting the form to the corresponding HttpPost Action, the Product.Category.CategoryName is still empty. I do not have a direct instance of the Category object in this scope:
//Category cat = new Category();
//cat.CategoryName = "Fake Name";
//context.Categories.Add(cat);
//context.SaveChanges();
EFCFContext context = new EFCFContext();
Product prod = new Product();
prod.ProductName = "Fake Name";
prod.Category = new Category();
prod.Category.CategoryID = cat.CategoryID;
//prod.Category.CategoryName is null. ModelState Error is added.
context.SaveChanges();
As a result, some Model Validation errors occur.
I need to assign/reload the Product's Category property explicitly, since I already know the CategoryID.
So my question is:
How do I force the navigation property to load by its Key/ID without the extra steps (and thus avoid the ModelState Errors)?
I may be going in the wrong direction. Any guidance will be helpful.

Display data from multiple models in one view

In the MVC 3 i want to display data from two models viz. Student and Enrollment into a single view.
Student model
public class Student
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int student_id { get; set; }
public string student_name { get; set; }
public string father { get; set; }
}
Enrollment model
public class Enrollment
{
[Key]
public int enrollment_id { get; set; }
public string rollno { get; set; }
public int student_id { get; set; }
public string registration_no { get; set; }
public date registration_date { get; set; }
}
My ViewModel looks like this
public class StudentEnrollmentViewModel
{
public Student_Info Student_Info { get; set; }
public Enrollment_Info Enrollment_Info { get; set; }
[Key]
public int ID { get; set; }
}
How do I retrieve the data from the two model and assign it to a viewmodel so that I can display it in a view? I am using Repository Design Pattern.
Generally speaking, a controller would be responsible for contacting the model, storing the result set returned in a variable/array/struc that the view would consume. The model and view components/classes would be registered in the controller.
An example:
<event-handler event="display.institutions" access="public">
<notify listener="userInstitutionRights" method="getInstitutionsWithDataRightsNOXML" resultKey="request.institutions" />
<view-page name="userNav" contentKey="request.userNav"/>
<view-page name="userInstitutions" contentKey="request.pageContent"/>
<announce event="assemblePage" />
</event-handler>
Event display.institutions is calling a model component userInstitutionRights and storing the result in a resultKey request.institutions and is including two view pages userNav, userInstitutions where the resultKey is available to each.
You can use DynamicPage, Look into following example
We need to use a Dynamic view page. (More Information)
Follow following steps:
Create DynamicViewPage type
public class DynamicViewPage : ViewPage
{
public new dynamic Model { get; private set; }
protected override void SetViewData(ViewDataDictionary viewData)
{
base.SetViewData(viewData);
Model = ViewData.Model;
}`
}
Your Controller will look like
public ActionResult Account(string returnUrl)
{
LoginModel loginmodel = null;//Initialize Model;
RegistrationModel registrationModel = null ;//Initialize Model;
// Any Extra logic
return View("Account", new
{
Login = loginmodel,
Register = registrationModel
});
}
your View should Inherit from
Inherits="DynamicViewPage"
Now #Model.Login will give you Loginmodel
#Model.Register will give you RegisterModel
It should work as you expected.........

Resources