I'm using .net MVC.
I have some values in a form like:
#Html.TextBoxFor(model => model.Name, new { #class = "form-control" })
On the controller, I get the data like:
public ActionResult NovaPessoa(Person person)
{
The problem is that I just can get values that I have placed the #Html.TextBoxFor markup.
All the other complex information, like person.ContactInformation is lost after submiting and I can't use the SaveChanges in Entity Framework, because it will give me an invalid object after using the Atach method.
The question is: Do I need to use the #Html.TextBoxFor markup for all my model properties, even if I'm not using then to display anything, just to have them on Controller?
You are correct. What people (incorrectly) do normally, is use HiddenFor:
#Html.HiddenFor(model => model.ContactInformation)
What you should be doing, is cutting down your model into a view model with only the appropriate properties.
So, don't use this model:
public class PersonVM {
public int PersonId { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
public string ContactInformation { get; set; }
}
..if all you're doing is updating the contact information. Instead, create a new class for your model:
public class PersonContactInfoEditVM {
public int PersonId { get; set; }
public string ContactInformation { get; set; }
}
That's all you need. This saves you from creating invalid objects when you don't add 30 HiddenFor elements to your page .. resulting in very broken data.
You might be thinking "ugggghhhh, all that manual mapping from PersonContactInfoEditVM to Person... I don't want to be writing that sort of code". No one does.. which is why the following libraries exist:
AutoMapper
ValueInjector
Related
I am trying to perform CRUD operations on two classes PurchaseDetail and ItemDetail. Made PurchaseDetail a base class and ItemDetail as child class. So on a single razor view I am passing the child class object. Now when I'm performing CRUD operation on child class everything works fine. But when trying the same with base class I'm getting null object while adding data by calling a create(ItemDetail obj), on HttpPost.
Any solution for this? Is it a good practice to pass multiple classes to a view by using inheritance?
Edit
Elaboration:
public class PurchaseDetail
{
public int ID { get; set; }
[Required]
[Display(Name = "PPRF Name")]
public string pprfName { get; set; }
public int DepartmentID { get; set; }
[Display(Name ="Department")]
public string DepartmentName { get; set; }
public int DepartmentSerial { get; set; }
public List<SelectListItem> MaterialTypes { get; set; }
Above class is my base class with few properties. Then Below is my child class.
public class ItemDetail: PurchaseDetail
{
public int ItemID { get; set; }
public int ItemCategoryID { get; set; }
public string ItemCategoryName { get; set; }
public int ProductID { get; set; }
If I would take both in different pages there was not any problem. But I want to pass both class onto same view so I went for Inheritance. I am popping up form for ItemDetail from the same view, to add an item.
#model AIIMSINTRANET.Models.ItemDetail
Firstly I am inserting ItemDetail by using Ajax call. Here things are working fine.
public JsonResult Add(ItemDetail item)
{
//Insert Logic
}
After this I want to insert PurchaseDetail in it's respective table. Since the view was same so every fields like TextBox, Dropdowns were filled while calling this method.
public ActionResult Create()
{
ItemDetail obj = new ItemDetail();
obj.DeptIndentNo = GetIndentNumber2();
obj.IndentDate = DateTime.Now.Date;
obj.MaterialTypes = PurchaseProposalDO.PopulateMaterialType();
//Initialized all other fields
}
Even fields like DropDown of ItemDetail class got populated during above function call. Now when I am submitting the form to Insert PurchaseDetail, it calls below function:
[HttpPost]
public ActionResult Create(ItemDetail obj)
{
// Insert Logic Here
}
Now here is my problem. Why am I getting obj as null ?
Even when I am inputting everything.
<div class="form-group">
#Html.LabelFor(model =>model.pprfName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model =>model.pprfName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.pprfName, "", new { #class = "text-danger" })
</div>
</div>
Please help.
Without seeing the class definitions in question it's hard to answer definitively, but it sounds like what you've got is an Order Header/Order Line type relationship. If that's the case, using inheritance is absolutely the wrong approach for solving this. You should use a view model that encapsulates the other two objects and pass that. Something like:
public class OrderViewModel
{
public PurchaseDetail PurchaseDetail { get; set; }
public ItemDetail ItemDetail { get; set; }
}
Or, if there could be many ItemDetails for a single PurchaseDetail:
public class OrderViewModel
{
public PurchaseDetail PurchaseDetail { get; set; }
public IEnumerable<ItemDetail> ItemDetails { get; set; }
}
Alternatively, you could have the view model pass only the properties of interest to the view. Again, without seeing the definition of the classes it's hard to be more specific.
I've been searching for a way to post all the information of a model which contains other models and I believe I can just send the object to my view and go off of the 50 examples I've looked at and can render everything just fine.
Here's my model I'm talking about named Equipment.
public int id { get; set; }
public String name { get; set; }
public ManufacturerItem manufacturerItem { get; set; }
public EquipmentType equipmentType { get; set; }
public SupportItem supportItem{ get; set; }
public Placement placement{ get; set; }
public Boolean status { get; set; }
public DateTime endOfLife{ get; set; }
public String notes{ get; set; }
public Purchase purchase{ get; set; }
public Boolean mes{ get; set; }
public DateTime reviewedDate{ get; set; }
Based on the tons of examples I've read I know I can render these like this:
#Html.EditorFor(model => model.name)
#Html.EditorFor(model => model.manufacturerItem.model.name)
In other research I did stumble upon building forms for deep View Model graphs in ASP.NET MVC which I may consider in using, but that was posted back in MVC 2 days. I'm using MVC 5. So I don't know how relative that is today.
So let's say I have another model named Book with {id, Title, Author} and you could edit the book name and author. Now in this model, on edit, my controller could be as such:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,Author)"] Book book)
{ ... -insert code- ...}
Going off of this idea, what would be my controller method signature be for the Equipment model? Do I include the other objects as their own types?
I'm not using EF or linq-to-sql because I have to use stored procedures. So I want to get all this information neatly packaged and passed off to the repository that will take care of parameter assignment and calling of the stored procedure.
Going off of this idea, what would be my controller method signature
be for the Equipment model?
Have you tried using the following signature:
[HttpPost]
public ActionResult Edit(Equipment model)
{
...
}
By the way if your view doesn't contain a form allowing to edit all the properties of the Equipment model object graph you may consider using a view model containing only the properties that are included as input fields in your form. Then on the server you will get the corresponding Equipment instance from your backend using the id, update only the properties that were sent from the HTML form and save the results back.
For example:
[HttpPost]
public ActionResult Edit(EquipmentViewModel model)
{
Equipment equipement = backend.GetById(model.Id);
// set the properties that are coming from the UI:
equipment.name = model.Name;
equipment.supportItem = model.SupportItem;
...
// save the updated entity back
backend.Update(equipment);
}
In this example the EquipmentViewModel will contain only the properties that you have corresponding input fields in your view and which the user is supposed to edit and not the entire domain model object graph.
I have an application that allows user to create new items. As it stands now the user need to fill out Title, body and category.
The category is a textbox but I would like to convert it to a dropdownlist. This is all connected to a database and when the user submits it the data should be saved in the database. Everything works fine as it stands now, I am only having trouble implementing the dropdownlist.
My model:
public class NewsItem
{
public int ID { get; set; }
[Required(ErrorMessage = "Title is Required")]
public string Title { get; set; }
[Required(ErrorMessage = "Body is Required")]
public DateTime DateCreate { get; set; }
public string Category { get; set; }
}
What is the quickest/best way implementing this. Should I do it in the model or can I assign the values available in the view?
Thanks in advance!
First, some semantics. Since Entity Framework comes along with MVC, the assumption is that your POCOs are "models". (Unfortunately, Microsoft kind of piles on by putting scaffolded POCOs in a "Models" folder). However, these are not models in terms of the "Model" in Model-View-Controller; instead, they are merely "entities", which is a fancy way of saying "pretty much just a DTO EF can use to stuff data from the database into."
I point that out to say this: no, you shouldn't put your drop down list choices on your entity. But, you shouldn't rely on the view for this either. What you really want here is a view model. You create a class that has just the fields you need to edit and any additional business logic your view needs (such as the choices of categories), and then you map your entity to and from this view model. As an example:
public class NewsItemViewModel
{
[Required(ErrorMessage = "Title is Required")]
public string Title { get; set; }
[Required(ErrorMessage = "Body is Required")]
public DateTime DateCreate { get; set; }
public string Category { get; set; }
public IEnumerable<SelectListItem> CategoryChoices { get; set; }
}
Notice that while this class is mostly the same, it doesn't contain an Id property, because this is not something you'd want the user to edit. Also, it includes a CategoryChoices property to hold the items for your drop down list.
Then in your controller, you would do something like:
public ActionResult CreateNewsItem()
{
var model = new NewsItemViewModel();
model.CategoryChoices = db.Categories.Select(m => new SelectListItem { Value = m.Name, Text = m.Name });
return View(model);
}
Basically, you're just newing up the view model so you can feed it to the view. You fill in your category choices before actually returning it, though. I've assumed they're also entities, but you would use whatever method you needed to fetch them here, otherwise.
For your post action:
[HttpPost]
public ActionResult CreateNewsItem(NewsItemViewModel model)
{
if (ModelState.IsValid)
{
// map view model to entity
var newsItem = new NewsItem
{
Title = model.Title,
Category = model.Category,
// and so on
}
db.NewsItems.Add(newsItem);
db.SaveChanges();
return RedirectToAction("Index");
}
model.CategoryChoices = db.Categories.Select(m => new SelectListItem { Value = m.Name, Text = m.Name });
return View(model);
}
I'm just doing a manual mapping from the view model to a new news item, here, but for real world scenarios, you'd probably want to integrate a mapping library for this, such as AutoMapper. Also, take note that in the case where there's an error, you must refill the category choices before returning the view again. These will not be posted by your form, so the model that was passed in will not have them.
Finally, in your view:
#model Namespace.To.NewsItemViewModel
...
#Html.DropDownListFor(m => m.Category, Model.CategoryChoices)
I use the EF-CF, and have the following entities:
Countries, Companies
Companies has a property called CountryId with StringLength attribute and the min and max restrictions (min 3 chars, max 3 chars, country id is ISO-Alpha-3). When the user needs to create a Company, I show a html element with all available countries. This is perfect!
However, when the I execute the jquery validator to the form, this checks for 3 selected options and not the length selected option value.
I need the StringLengthAttribute in my Country Model, I cannot remove it.
I hope to "remove" or "hide" the StringLengthAttribute in the call:
#Html.ValidationMessageFor(model => model.CountryId)
Thanks!
I think I understand your question. A possible solution would be to use a ViewModel to pass to the view as oppose to using the Company entity directly. This would allow you to add or remove data annotations without changing the entity model. Then map the data from the new CompanyViewModel over to the Company entity model to be saved to the database.
For example, the Company entity might look something like this:
public class Company
{
public int Id { get; set; }
[StringLength(25)]
public string Name { get; set; }
public int EmployeeAmount { get; set; }
[StringLength(3, MinimumLength = 3)]
public string CountryId {get; set; }
}
Now in the MVC project a ViewModel can be constructed similar to the Company entity:
public class CompanyViewModel
{
public int Id { get; set; }
[StringLength(25, ErrorMessage="Company name needs to be 25 characters or less!")]
public string Name { get; set; }
public int EmployeeAmount { get; set; }
public string CountryId { get; set; }
}
Using a ViewModel means more view presentation orientated annotations can be added without overloading entities with unnecessary mark-up.
I hope this helps!
Ready!
I remove the rule for the html control.
$("##(Html.HtmlIdNameFor(model => model.CountryId))").rules("remove", "rangelength");
The "rangelength" is the jquery validation rule for the StringLengthAttribute.
Where "Html.HtmlIdNameFor" is a helper to get the "Id" generated by ASP.NET MVC.
Review How to get the HTML id generated by asp.net MVC EditorFor
I have the following Model classes (I am using EF code first to generate the tables).
public class MyClass
{
....
[Required, ForeignKey("Address")]
public int Address1Id { get; set; }
virtual public Address Address1 { get; set; }
[Required, ForeignKey("Address")]
public int Address12d { get; set; }
virtual public Address Address2 { get; set; }
[Required, ForeignKey("Address")]
public int Address3Id { get; set; }
virtual public Address Address3 { get; set; }
....
}
public class Address { .... }
I wish the create view of MyClass shows all the address fields and the addresses will be saved in the table Address first when saving the MyClass. But the scaffolding generated a DropDownList box for the addresses. How to modify the code to make it acting like these address fields are coded in the class MyClass directly and let the controller to save the addresses in table Address and AddressID in table MyClass?
<div class="editor-label">
#Html.LabelFor(model => model.Address1Id, "Address")
</div>
<div class="editor-field">
#Html.DropDownList("Address1Id", String.Empty)
#Html.ValidationMessageFor(model => model.Address1Id)
</div>
Update:
I tried to create the following ViewModel, but the scaffolding complain there is no key defined in the class.
public class DealViewModel
{
public Deal Deal { get; set; }
public Address Address { get; set; }
}
Your foreign key attributes are not correct:
[Required, ForeignKey("Address1")]
...
[Required, ForeignKey("Address2")]
...
[Required, ForeignKey("Address3")]
...
(And Address12d is a typo I guess)
It sounds like you're asking how to flatten out the model for an edit/create view in MVC. You don't want to have to create a Person, and then go create an Address, but instead create it in one screen. If that's what you're asking, yes you can do it! In fact, doing what you want is as simple as changing your create view to something like:
<h3>Address 1</h3>
#Html.EditorFor(x => x.Address1)
<h3>Address 2</h3>
#Html.EditorFor(x => x.Address2)
...etc
By default, MVC is smart enough to guess what an 'editor' for your Address should look like. Text fields for strings, checkboxes for bools, etc. If your [HttpPost] controller looks something like this:
[HttpPost]
public ActionResult Create(Person person)
{
if (ModelState.IsValid)
{
var context = new AppDbContext();
context.People.Add(person);
context.SaveChanges();
return RedirectToAction("Index");
}
return View(personViewModel);
}
If you examine person in the debugger you should see that the Address properties are all filled in. Yup, the MVC ModelBinder is that smart! You shouldn't have to do anything else.
Note: As you progress in your project and your data model inevitably becomes more complex, you may run into problems passing model objects back and forth from controller to view. I strongly recommend following a pattern of using ViewModels, which are plain objects that represent a 'model' for the view, that is, it should bundle up all of the data going to and from the client. This will allow you to flatten out your data model and only include the fields you actually need. You might not need to use this pattern for this particular example, but it will be helpful in the future.
Hope this helps!