I'm trying to understand the best way to map the fields in an Edit Model (posted back from a view form) to a Domain Model so that I can SubmitChanges() and update the database record (via Linq to SQL). My example will be simple and the answer to it is probably to just do it manually. The question I'm asking is for models with many more fields where it could still be done manually and maybe that's the answer - but is there an easier way (using AutoMapper perhaps)?
My Domain Model:
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public int Color { get; set; }
public string SerialNumber { get; set; }
}
My View/Edit Model:
public class ProductVM
{
public int ProductId { get; set; }
public string Name { get; set; }
public IEnumberable<Color> Colors { get; set; } // To populate a dropdown in the view with color key/values
}
My Controller Action:
[HttpPost]
public ActionResult UpdateProduct(ProductVM product)
{
var productService = new ProductService();
productService.Update(product);
}
The Update method in my service layer:
public void Update(ProductVM productVM)
{
Product product = dataContext.Products.Single(p=>p.ProductId == productVM.ProductId);
// What's the best way to map the fields of productVM into product so I can submit the changes?
dataContext.SubmitChanges();
}
I just want to map the fields from productVM that match up with the fields in product. The Edit Model has fields that don't exist in the Domain Model and the Domain Model has fields that don't exist in the Edit Model. My ultimate goal is to update the fields in the database from the fields in the Edit Model.
Thanks
Use Automapper
public void Update(ProductVM productVM)
{
Product product = dataContext.Products.Single(p=>p.ProductId == productVM.ProductId);
AutoMapper.Mapper.DynamicMap<ProductVM, Product >(productVM, product );
dataContext.SubmitChanges();
}
Related
ASP.NET 4.5, MVC 5, EF6 code first
I'm a newbie and probably asking something long-known but I couldn't find solution on the web, probably because I don't know correct terminology to formulate this question.
To simplify things, let's say I have two model classes Teacher and Kid; One kid can be assigned only to one teacher, but one teacher can have many kids. As I'm using code first, my database is constructed from these model classes:
public class Kid
{
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public virtual Teacher { get; set; }
}
public class Teacher
{
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public virtual ICollection<Kid> Kids { get; set; }
}
Now, I need to have a view for adding new kid with:
Textbox for Kid's name;
Dropdown with list of Teachers
So, I'm creating a data transfer object, specifically for that view:
public class AddNewKidViewDTO
{
public IEnumerable<SelectListItem> Teachers { get; set; }
public int SelectedTeacherId { get; set; }
public Kid Kid { get; set; }
}
I also have a method for populating IEnumerable Teachers:
public AddNewKidViewDTO LoadTeachersForDropDownList()
{
... //get the list of Teachers
AddNewKidViewDTO addNewKidViewDTO = new AddNewKidViewDTO();
List<SelectListItem> selectListItems = new List<SelectListItem>();
foreach (teacher in Teachers)
{
selectListItems.Add (new SelectListItem
{
Text = teacher.Name.ToString(),
Value = teacher.Id.ToString()
});
}
addNewKidViewDTO.Teachers = selectListItems;
return addNewKidViewDTO;
}
and in the view AddNewKid.cshtml
<form>
#Html.LabelFor(model => model.Kid.Name)
#Html.TextBoxFor(model => model.Kid.Name, new {id ="Name"}
<br/>
#Html.LabelFor(model => model.Kid.Teacher)
#Html.DropDownListFor(model => model.SelectedTeacherId, Model.Teachers)
</form>
Form gets submitted and in the controller I get my populated AddNewKidViewDTO model:
[HttpPost]
public ActionResult SaveNewKid (AddNewKidViewDTO addNewKidViewDTO)
{
if (ModelState.IsValid)
{
//here is where the problem comes
}
}
ModelState.IsValid in my case will always return false.
Because when it starts validating AddNewKidViewDTO.Kid, Teacher is compulsory field but in my addNewKidViewDTO model it's null. I have the necessary teacher Id contained in addNewKidViewDTO.SelectedTeacherId only.
My question is, what is an elegant way to validate my model before passing to my inner business logic methods?
Any help is appreciated.
There are multiple possible solutions:
Changing your AddNewKidViewDTO and decorating it with the DataAnnotaions for validation:
public class AddNewKidViewDTO
{
public IEnumerable<SelectListItem> Teachers { get; set; }
[Range(1, 2147483647)] //Int32 max value but you may change it
public int SelectedTeacherId { get; set; }
[Required]
public string KidName { get; set; }
}
Then you can create Kid object manually in case that your model valid.
UPDATE (to address your comment)
If you use this approach your action will look like this:
[HttpPost]
public ActionResult SaveNewKid (AddNewKidViewDTO addNewKidViewDTO)
{
if (ModelState.IsValid)
{
using (var dbContext = new yourContext())
{
var teacher = dbContext.Teachers.FirstOrDefault(t=>t.id == addNewKidViewDTO.SelectedTeacherId );
if(teacher == default(Teacher))
{
//return an error message or add validation error to model state
}
//It is also common pattern to create a factory for models
//if you have some logic involved, but in this case I simply
//want to demonstrate the approach
var kid = new Kid
{
Name = addNewKidViewDTO.KidName,
Teacher = teacher
};
dbContext.SaveChanges();
}
}
}
Write a custom model binder for AddNewKidViewDTO that will initialize Teacher property in Kid object so once you actually use Model.IsValid the property will be initialized.
I am new to asp.net, there are some questions on stack overflow but they don't fulfill my purpose. My question is..
How would I implement view model for the following two models?
public class model1
{
int student-id{ get;set}
string student-name{get; set;}
}
public class model2
{
int course-code{get; set;}
string course-name{get; set;}
}
Now I want to write a view model that could pass to a view and this view displays student-name and corresponding course-names.
Note: a student can enrolled in more than one course.
First of all you should modify your model. Student and courses have to be related. You can implement these relations like:
public class Student
{
public int Id { get; set }
public string Name { get; set; }
public ICollection<Course> Courses { get; set; }
}
public class Course
{
public int Code { get; set; }
public string Name{ get; set; }
public ICollection<Student> Students { get; set; }
}
After - you create view model. View model class must contain only what you actually need in you view. In you case - student and courses names. You can consider several options here. If you want just to display all course names in one line you can build you StudentViewModel like this:
public class StudentViewModel
{
public string Name { get; set; }
// In this case you can just join all courses' names to one string using string.Join(", ")
public string Courses { get; set; }
}
... or like this - if you want courses' names separated (to use them in some select or list html element). But you can create JoinedCources property which will return courses' names joined into one string.
public class StudentViewModel
{
public string Name { get; set; }
public ICollection<string> Courses { get; set; }
public string JoinedCources {
get {
return string.Join(", ", Courses);
}
}
}
Note: this is view model for only one student! If you want to display view which shows you the list of students and their courses you should either create new view model with property which is collection of StudentViewModel or in your view define model like #model ICollection<StudentViewModel> instead of #model StudentViewModel.
Now you have to map your model to view model. For example in your controller action when you get your student from database (or any other data source - file or web service):
public ActionResult StudentDetails(int studentId)
{
var student = _dataSource.GetStudent(studentId);
var model = AutoMapper.Mapper.Map<StudentViewModel>(student);
return View(model);
}
Now few words about mapping. AutoMapper is external class library you should definitely get to learn about if you want to work with view models and mapping in the future. It will help you simplify action method code and make it more readable. But since you're new to ASP. Net you can implement mapping by your self for the first time. For example like below:
public ActionResult StudentDetails(int studentId)
{
var student = _dataSource.GetStudent(studentId);
var model = new StudentViewModel()
{
Name = student.Name,
Courses = student.Courses.Select(c => c.Name)
}
return View(model);
}
public class StudentViewModel
{
public string Name{get;set;}
public int StudentId{ get;set}
public List<model2> Courses
}
You can consider combining the two models into the above view model if the goal is to display a student with course info.
Example:
I have table Orders and table OrderPositions.
public partial class Orders
{
public Orders()
{
this.OrderPositions = new HashSet<OrderPositions>();
}
public int OrderId { get; set; }
public string Title { get; set; }
public virtual ICollection<OrderPositions> OrderPositions { get; set; }
}
public partial class OrderPositions
{
public int OrderPositionId { get; set; }
public int OrderId { get; set; }
public string Name { get; set; }
public virtual Orders Orders { get; set; }
}
On the view user can modify single record from OrderPositions table.
In controller:
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
So parameter orderPosition.Orders should be = null because on the form in view user can modify only order position. But can user hack it? I mean that in parameter orderPosition.Orders won't be null and I update record not only in table OrderPositions but also in table Orders? Or ASP.NET MVC prevent from that situation?
It really depends on what you do here
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
If you're saving the whole entity then yes there is nothing stopping a user passing over addition entity properties. There are a few ways to prevent this though, here are a couple...
1.Create a new entity at the point of saving
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = orderPosition.OrderPositionId,
OrderId = orderPosition.OrderId,
Name = orderPosition.Name
};
//Then save this new entity
}
}
2.Create a Model specific to the entity's action
public class EditOrderPosition
{
[Required]
public int PositionId { get; set; }
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
[HttpPost]
public ActionResult Edit(EditOrderPosition model)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = model.PositionId,
OrderId = model.Id,
Name = model.Name
};
//Then save this new entity
}
}
I generally go with the 2nd method as it stops direct user involvement with my entities. As a rule of thumb I never use entity objects as parameters in controller actions.
Hope this helps
Yes they can. This is one reason I do not expose my entities as a parameter to action methods, instead I use DTOs that only have the properties that I expect.
This is an example of the Mass Assignment Vulnerability.
Yes, there is nothing preventing a rogue app calling your endpoint with arbitrary data. Always validate everything serverside.
i want to be able to display and update my User's Organisations preferably using the htmlhelper Html.TextBoxFor(
I have an entityframework 5 database first database with relationships defined as expected on the 3 tables
User
Organisation
UserOrganisation
which yield the classes below
public partial class User
{
public System.Guid UserId { get; set; }
public string Fullname { get; set; }
...
}
public partial class Organisation
{
public int OrganisationID { get; set; }
public string Title { get; set; }
...
}
public partial class UserOrganisation
{
public System.Guid UserId { get; set; }
public int OrganisationID { get; set; }
}
I pass in the user as the model and also populate a list of potential organisations in the viewbag i.e.
ViewBag.PossibleOrganisations = OrganisationFactories.GetOrganisations()
and the razor markup is.
#Html.ListBoxFor(model => model.UserOrganisations,
new MultiSelectList(ViewBag.PossibleOrganisations,"OrganisationID","Title"))
Now this displays the list of Organisations correctly and i can multiselect them. But it doesn't show the selected Organisations, and it also wont write this back to the database when posting back (incidentally all other fields did write back prior to this change).
Does anyone have any suggestions or examples of a multiselect list working in this fashion?
Cheers
Tim
I am having a hard time solving the following with an MVC view.
My goal is to display data from multiple tables in a single MVC view. The bulk of the data comes from a table called Retailers. I also have another table called RetailerCategories which stores the retailerid from the Retailers table and also a categoryid linking to a Category table.
Note that there are multiple records for each retailerid in the RetailerCategories table.
In the view I want to show a list of retailers and with each retailer I want to show the list of categories applicable to them.
What would be the best way to accomplish this? Some of the things I have tried are covered in Can you help with this MVC ViewModel issue?
This however does not appear to be the right approach.
You need a view model specifically tailored to the needs of this view. When defining your view models you shouldn't be thinking in terms of tables. SQL tables have absolutely no meaning in a view. Think in terms of what information you need to show and define your view models accordingly. Then you could use AutoMapper to convert between your real models and the view model you have defined.
So forget about all you said about tables and focus on the following sentence:
In the view I want to show a list of
retailers and with each retailer I
want to show the list of categories
applicable to them.
This sentence is actually very good as it explains exactly what you need. So once you know what you need go ahead and modelize it:
public class CategoryViewModel
{
public string Name { get; set; }
}
public class RetailerViewModel
{
public IEnumerable<CategoryViewModel> Categories { get; set; }
}
Now you strongly type your view to IEnumerable<RetailerViewModel>. From here it is easy-peasy to do what you want in the view:
showing a list of retailers with each retail having a list of associated categories.
this could be also helpful;
video from chris pels
It is simple just do what I say step by step.
add connection string into web.config file
select models from solution explorer and add 4 classes as following
1st class for first table "i have employ table which have 3 columns
public class Employ
{
[Key]
public int Emp_id { get; set; }
public string Emp_name { get; set; }
public string Emp_city { get; set; }
}
2nd class for my tempo table
public class tempo
{
[Key]
public int ID { get; set; }
public int Emp_Id { get; set; }
public string subject { get; set; }
public string hobby { get; set; }
}
Now I create a third class in model folder which contain value that i want from employ table and tempo table
public class Alladd
{
public int ID { get; set; }
public int Emp_Id { get; set; }
public string subject { get; set; }
public string hobby { get; set; }
public string Emp_name { get; set; }
public string Emp_city { get; set; }
}
and the final class is datacontext class
public class DataContext:DbContext
{
public DataContext() : base("DefaultConn")//connection string
{
}
public DbSet<Employ> Empdata { get; set; }
public DbSet<tempo> Tempdata { get; set; }
}
now go to the Home controller and add code as below
public ActionResult file()
{
// IList<tempo> tempi=new List<tempo>();
IEnumerable<Alladd> model = null;
// model = getVerifydetails(id);
// return View(objcpModel);
List<Alladd> verify = new List<Alladd>();
cn.Open();
if (cn.State == ConnectionState.Open)
{
string query = "select Employ.Emp_name,Employ.Emp_id,Employ.Emp_city,tempo.hobby,tempo.id,tempo.subject from Employ inner join tempo on Employ.Emp_id=tempo.Emp_id;";//joining two table
SqlCommand cmd=new SqlCommand(query,cn);
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
verify.Add(new Alladd { Emp_name = dr[0].ToString(), Emp_Id= Convert.ToInt32(dr[1].ToString()), Emp_city = dr[2].ToString(), hobby = dr[3].ToString(),ID = Convert.ToInt32(dr[1].ToString()),subject= dr[4].ToString()});//filling values into Alladd class
}
cn.Close();
}
return View(verify);
}
now the final step is so simple
go to solution explorer
select views folder and left click on it and select add view
now name it as "file" which we give it into controller
check on create strongly type view
select model class from dropdown-> Alladd
select scaffold templet ->List
hit Add button
Now you're done
Happy coding...