Introduction
In our article series Learning MVC, we learned a lot about MVC, about various techniques to communicate to a database in MVC applications, and a few internal concepts too.
When we indulge in realtime programming we encounter not only one but many challenges in the code. This article explains a new concept, Auto Mapper, in MVC applications to overcome one of the major challenges we encounter while communicating with data entities and binding them to our model.
Challenge
Sometimes while interacting with realtime (database) entities and binding our model to them, we end up in a situation like:
- var dbContext = new MyDBDataContext();
- var userDetails = dbContext.Users.FirstOrDefault(userId => userId.UserId == id);
- var user = new LearningMVC.Models.User();
- if (userDetails != null)
- {
- user.UserId = userDetails.UserId;
- user.FirstName = userDetails.FirstName;
- user.LastName = userDetails.LastName;
- user.Address = userDetails.Address;
- user.PhoneNo = userDetails.PhoneNo;
- user.EMail = userDetails.EMail;
- user.Company = userDetails.Company;
- user.Designation = userDetails.Designation;
- }
- return View(user);
The code shown above is not very hard to understand. In the code above, an instance of MyDBDataContext (using "var dbContext = new MyDBDataContext();") is created from a LinqTOSql Context class, thereafter user details from a user's specific table has been fetched and stored in the userDetails variable. We had an existing model named "User" ("LearningMVC.Models.User()"), that has similar properties as that of the Users class generated from the database, now we initialize the properties of the instance of our model from the properties of the instance of the User class from the database, so that we can populate our View in a MVC application.
We see here that there are 8 properties similar to each other but each set lies in a separate class, one in the Model and one in the Users class. And what we do is, one by one we bind these properties to our model and it to the View. Now the problem is what, if we have 100 column records coming from the database, and also our model has the same number of properties, and the code must be repeated 6-7 times at various scenarios, now do we still follow such a strategy of binding each property from the db to the model, trust me the code will be 100 pages large, and will require 5 times the anount of effort just to bind the model from the domain entities.
To overcome this tedious situation AutoMapper is introduced. It not only reduces the effort, but also limits the execution time that has been taken by such a large number of lines to execute.
Auto Mapper
AutoMapper is a open source provided in
GitHub.
As per the
AutoMapper CodePlex web page "AutoMapper is an object-object mapper. Object-object mapping works by transforming an input object of one type into an output object of a different type. What makes AutoMapper interesting is that it provides some interesting conventions to take the dirty work out of figuring out how to map type A to type B. As long as type B follows AutoMapper's established conventions, almost zero configuration is needed to map two types." Therefore, it provides the solution for our mapping issues.
Install AutoMapper
First install the NuGet Package Manager in your Visual Studio IDE. Once done, go to "Tools" -> "Library Packet Manager" -> "Packet manager Console".
Then in the console window opened at the bottom of Visual Studio, type:
PM> Install-Package AutoMapper
Press Enter. This will install AutoMapper and the next time you open MVC application in Visual Studio, it will automatically add a DLL reference to the project.
AutoMapper in Action
Let's create an MVC application first. You can create an MVC application, and connect it with the database using LINQ to SQL following my article
http://www.c-sharpcorner.com/UploadFile/1492b1/part-2-creating-mvc-application-perform-crud-operations/
I have also attached the code of an existing MVC application used without AutoMapper.
Now let's evaluate all the Controller Actions one by one and convert the code using AutoMapper.
Step 1: Create a database for an existing application, the database script is attached with the source code.
Open an Existing MVC Application in Visual Studio.
See that AutoMapper is referenced in the project, now use that namespace in MyController as in the following:
Step 2: Index Action:
In the very first action of our controller MyController (can be found in the Controllers folder), the Index Action, we see the code:
- public ActionResult Index()
- {
- var dbContext = new MyDBDataContext();
- var userList = from user in dbContext.Users select user;
- var users = new List<LearningMVC.Models.User>();
- if (userList.Any())
- {
- foreach (var user in userList)
- {
- users.Add(new LearningMVC.Models.User()
- {
- UserId = user.UserId,
- Address = user.Address,
- Company = user.Company,
- FirstName = user.FirstName,
- LastName = user.LastName,
- Designation = user.Designation,
- EMail = user.EMail,
- PhoneNo = user.PhoneNo
- });
- }
- }
- return View(users);
- }
Now where will AutoMapper fit in here, you know that, it will be used to replace the property mapping done one by one in the code, therefore, just at the first line of code, define an AutoMap.
To create the default mapping, call "Mapper.CreateMap<T1, T2>()" with proper types. In this case, T1 will be "LearningMVC.User" and T2 will be "LearningMVC.Models.User".
Mapper.CreateMap<LearningMVC.User, LearningMVC.Models.User>();
LearningMVC.User -> DTO Object Class
LearningMVC.Models.User -> Model Class to bind the View
So, here we define a mapping between DTO and a Model class using the AutoMapper class.
Now inside the foreach loop, replace the entire code by:
- LearningMVC.Models.User userModel = Mapper.Map<LearningMVC.User, LearningMVC.Models.User>(user);
- users.Add(userModel);
Finally call the Mapper.Map<T1, T2>(obj1) to get the mapped object of T2.
So, our final action code is:
- public ActionResult Index()
- {
- Mapper.CreateMap<LearningMVC.User, LearningMVC.Models.User>();
- var dbContext = new MyDBDataContext();
- var userList = from user in dbContext.Users select user;
- var users = new List<LearningMVC.Models.User>();
- if (userList.Any())
- {
- foreach (var user in userList)
- {
- LearningMVC.Models.User userModel = Mapper.Map<LearningMVC.User, LearningMVC.Models.User>(user);
- users.Add(userModel);
- }
- }
- return View(users);
- }
We see now, we escaped that boring work of matching properties one by one. Now run the application, and you'll see the application running as before.
Step 3: Details Action:
Existing Code
- public ActionResult Details(int? id)
- {
- var dbContext = new MyDBDataContext();
- var userDetails = dbContext.Users.FirstOrDefault(userId => userId.UserId == id);
- var user = new LearningMVC.Models.User();
- if (userDetails != null)
- {
- user.UserId = userDetails.UserId;
- user.FirstName = userDetails.FirstName;
- user.LastName = userDetails.LastName;
- user.Address = userDetails.Address;
- user.PhoneNo = userDetails.PhoneNo;
- user.EMail = userDetails.EMail;
- user.Company = userDetails.Company;
- user.Designation = userDetails.Designation;
- }
- return View(user);
- }
New Code with AutoMapper
- public ActionResult Details(int? id)
- {
- var dbContext = new MyDBDataContext();
- Mapper.CreateMap<LearningMVC.User, LearningMVC.Models.User>();
- var userDetails = dbContext.Users.FirstOrDefault(userId => userId.UserId == id);
- LearningMVC.Models.User user = Mapper.Map<LearningMVC.User, LearningMVC.Models.User>(userDetails);
- return View(user);
- }
Step 4: Create Action (POST)
Existing Code
- [HttpPost]
- public ActionResult Create(LearningMVC.Models.User userDetails)
- {
- try
- {
- var dbContext = new MyDBDataContext();
- var user = new User();
- if (userDetails != null)
- {
- user.UserId = userDetails.UserId;
- user.FirstName = userDetails.FirstName;
- user.LastName = userDetails.LastName;
- user.Address = userDetails.Address;
- user.PhoneNo = userDetails.PhoneNo;
- user.EMail = userDetails.EMail;
- user.Company = userDetails.Company;
- user.Designation = userDetails.Designation;
- }
- dbContext.Users.InsertOnSubmit(user);
- dbContext.SubmitChanges();
- return RedirectToAction("Index");
- }
- catch
- {
- return View();
- }
- }
New Code with AutoMapper
- [HttpPost]
- public ActionResult Create(LearningMVC.Models.User userDetails)
- {
- try
- {
- Mapper.CreateMap<LearningMVC.Models.User, LearningMVC.User>();
- var dbContext = new MyDBDataContext();
- var user = Mapper.Map<LearningMVC.Models.User, LearningMVC.User>(userDetails);
- dbContext.Users.InsertOnSubmit(user);
- dbContext.SubmitChanges();
- return RedirectToAction("Index");
- }
- catch
- {
- return View();
- }
- }
We have now interchanged the mapping, because we now need to read from the Model and bind to our DTO for Create Action, so just interchange the mapping, and run the application. Now our T1 is Model and T2 is DTO.
Step 5: Edit Action:
Existing Code
- public ActionResult Edit(int? id)
- {
- var dbContext = new MyDBDataContext();
- var userDetails = dbContext.Users.FirstOrDefault(userId => userId.UserId == id);
- var user = new LearningMVC.Models.User();
- if (userDetails != null)
- {
- user.UserId = userDetails.UserId;
- user.FirstName = userDetails.FirstName;
- user.LastName = userDetails.LastName;
- user.Address = userDetails.Address;
- user.PhoneNo = userDetails.PhoneNo;
- user.EMail = userDetails.EMail;
- user.Company = userDetails.Company;
- user.Designation = userDetails.Designation;
- }
- return View(user);
- }
New Code with AutoMapper
- public ActionResult Edit(int? id)
- {
- Mapper.CreateMap<LearningMVC.User, LearningMVC.Models.User>();
- var dbContext = new MyDBDataContext();
- var userDetails = dbContext.Users.FirstOrDefault(userId => userId.UserId == id);
- var user = Mapper.Map<LearningMVC.User, LearningMVC.Models.User>(userDetails)
- return View(user);
- }
Step 6: Delete Action:
Existing Code
- public ActionResult Delete(int? id)
- {
- var dbContext = new MyDBDataContext();
- var user = new LearningMVC.Models.User();
- var userDetails = dbContext.Users.FirstOrDefault(userId => userId.UserId == id);
- if (userDetails != null)
- {
- user.FirstName = userDetails.FirstName;
- user.LastName = userDetails.LastName;
- user.Address = userDetails.Address;
- user.PhoneNo = userDetails.PhoneNo;
- user.EMail = userDetails.EMail;
- user.Company = userDetails.Company;
- user.Designation = userDetails.Designation;
- }
- return View(user);
- }
New Code using AutoMapper
- public ActionResult Delete(int? id)
- {
- var dbContext = new MyDBDataContext();
- Mapper.CreateMap<LearningMVC.User, LearningMVC.Models.User>();
- var userDetails = dbContext.Users.FirstOrDefault(userId => userId.UserId == id);
- var user = Mapper.Map<LearningMVC.User, LearningMVC.Models.User>(userDetails);
- return View(user);
- }
ForMember() and MapFrom() in AutoMapper
The two important functions in AutoMapper play an important role in object mapping, Suppose our model/viewmodel class has a property, FullName, and from the DTO we want to add the FirstName and Last Name of the user to make it a full name and bind it to the model, to overcome these kinds of scenarios ForMember() and MapFrom() are used. See the following code:
- Mapper.CreateMap<LearningMVC.User, LearningMVC.Models.User>().ForMember(emp => emp.Fullname,
- map => map.MapFrom(p => p.FirstName + " " + p.LastName));
Here we are saying that ForMember FullName in our model class, map properties from FirstName and LastName of User DTO.
The code is itself self-explanatory.
This kind of mapping is also called Custom Mapping.
Conclusion
In this article, we learned how to do custom mapping and entity-to-entity mapping using AutoMapper. Since this was just a glimpse of the concept there is nuch more to explore in this topic in detail.
I have skipped the POST methods for Edit and Delete, this will be a kind of homework for you. Once you completely follow and understand, you can easily complete those two pending actions as well. Let me see the source code as well.
Read more:
Other Series
My other series of articles:
For more informative articles visit my Blog.
Happy Coding.