I am trying to figure out how to generically map a domain model to a presentation model. For example, given the following simple objects and interfaces ...
// Product
public class Product : IProduct
{
public int ProductID { get; set; }
public string ProductName { get; set; }
}
public interface IProduct
{
int ProductID { get; set; }
string ProductName { get; set; }
}
// ProductPresentationModel
public class ProductPresentationModel : IProductPresentationModel
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public bool DisplayOrHide { get; set; }
}
public interface IProductPresentationModel
{
int ProductID { get; set; }
string ProductName { get; set; }
bool DisplayOrHide { get; set; }
}
I would like to be able to write code like this ...
MapperObject mapper = new MapperObject();
ProductService service = new ProductService();
ProductPresentationModel model = mapper.Map(service.GetProductByID(productID));
... in which the "MapperObject" could automatically figure out which properties map across the two objects and what sort of objects it is mapping using something like reflection, convention-based mapping, etc. So, I could then just as easily try to map objects like UserPresentationModel and User with the same MapperObject.
Is this possible? If so, how?
EDIT: Just for clarity, here is an example of a non-generic MapperObject that I am currently using:
public class ProductMapper
{
public ProductPresentationModel Map(Product product)
{
var presentationModel = new ProductPresentationModel(new ProductModel())
{
ProductID = product.ProductID,
ProductName = product.ProductName,
ProductDescription = product.ProductDescription,
PricePerMonth = product.PricePerMonth,
ProductCategory = product.ProductCategory,
ProductImagePath = product.ProductImagePath,
ProductActive = product.ProductActive
};
return presentationModel;
}
}
I am still trying to work out how to get this to work with List, instead of just a single Product, but that's a different topic :)
I see want you want. You want to map your domain entities (Product) to aome kind of DTO object (ProductPresentationModel) for communication with your clients (GUI, external services etc).
I you have all this functionality you're looking for packed into AutoMapper framework.
You can write like this with AutoMapper:
Mapper.CreateMap();
look at this wiki https://github.com/AutoMapper/AutoMapper/wiki/Flattening
Good luck.
/Best Regards Magnus
Related
Let's take an example model:
public class Person
{
public string Name {get; set;}
public List<Guitar> Guitars {get; set;}
}
public class Guitar
{
public string Brand {get; set;}
public string Model {get; set;}
}
I need to create a View with a list of all People and a count of how many guitars they have. To do that I would like to pass a ViewModel like this one populated with EF without loading all Guitards:
// This will be send to the View, I can also just send a List
public class ViewModelPassed
{
List<PeopleGuitarViewModel> AllPeople { get; set; }
}
// This should be populated from EF
public class PeopleGuitarViewModel
{
public string Name { get; set; }
public int NumberOfGuitars { get; set; }
}
Can I query EF to a different class, and avoid bringing the whole list of guitars and instead get a COUNT in SQL?
Thanks!
You should provide primary key to your models for EF core to create the relationship:
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public List<Guitar> Guitars { get; set; }
}
public class Guitar
{
public int ID { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
}
Also use public for AllPeople property in ViewModelPassed:
public List<PeopleGuitarViewModel> AllPeople { get; set; }
Then query like :
ViewModelPassed modelPassed = new ViewModelPassed();
modelPassed.AllPeople = new List<PeopleGuitarViewModel>();
modelPassed.AllPeople = _applicationDbContext.Persons.Select(x =>
new PeopleGuitarViewModel
{
Name = x.Name,
NumberOfGuitars = x.Guitars.Count
}).ToList();
One way is to do it like this:
var viewModel=context.Persons.Select(x=>new PeopleGuitarViewModel
{
Name=x.Name,
NumberOfGuitars=Guitars.Count()
}).ToList();
this kind of query is also named Projection and is consider best practice (do not send full entity to the view).
There are 2 libraries which I often use for such projections: AutoMapper and/or Mapster.
Hope this help you.
When populating your ViewModel, you can use Linq to get the count. Like this:
PeopleGuitarViewModel p= new PeopleGuitarViewModel();
p.Name = person.Name;
p.NumberOfGuitars = person.Guitars.Count;
Where person is an object of your Person class.
I'm trying my best to follow some MVC guidelines and for now, I have created a model with the fields I need from an entity I have. I have created the following Model class:
public class PersonStyle
{
public string Name { get; set; }
public int? Age { get; set; }
public string City { get; set; }
public string Style { get; set; }
}
My Entity is sometihng like:
public class PersonOE
{
public string Name { get; set; }
public int? Age { get; set; }
public string City { get; set; }
}
Im trying to build a constructor for the following:
PersonON personBus = new personBus();
List<PersonStyle> personStyleList = new List<PersonStyle>(personBus.getPeople()); //getPeople(); returns a PersonOE list
For this all, I need suggestions on how to create the PersonStyle constructor that will put "null" into the only different variable from the PersonOE model.
All you need to do is use LINQ to generate your properties. Though, adding your Style to each of them could get a little harder to deal with, but you haven't given any input on where they're coming from. This is a quick and easy way to convert from your data to model objects.
List<PersonStyle> personStyleList = personBus.GetPeople()
.Select(p => new PersonStyle {
Name = p.Name,
Age = p.Age,
City = p.City
});
I have three objects:
public class Part
{
[Key]
public int ID { get; set; }
public string Number { get; set; }
public string Name { get; set; }
public int descID { get; set; }
}
public class Description
{
[Key]
public int descID { get; set; }
public string descName { get; set; }
public string description { get; set; }
}
public class GridPart
{
public string name{ get; set; }
public string number { get; set; }
public string description { get; set; }
}
I'm using LINQ to join Part and Description on the descID column:
public ActionResult Index()
{
var myParts = from p in db.Parts
join d in db.Description on p.descID equals d.DescriptorID
select new { Description = d.description, Name = p.name};
List<GridPart> partsList = new List<GridPart>();
foreach (var m in myParts)
{
GridPart gp = new GridPart();
gp.Name = m.name;
gp.description = m.description;
partsList.Add(gp);
}
return View(partsList);
}
If I was just using the Parts table, in the view I would do:
#model IEnumerable<MyApp.Models.Part>
What do I do if I'm using the joined table? This uses both Parts and Description, not to mention my List of GridParts, how do I pass this through to display all of the data I need?
Any advice is greatly appreciated.
If you pass an anonymous type to your view it won't be strongly typed. You can refer to your model like this instead
#Html.TextBox("Name")
or
#Html.Display("Name")
Although this will work I would advice against it - The better solution would be to go with a Viewmodel instead. This will make your view strongly typed.
Edit: Looking on this again I see that your actually not parsing the anonymous type to your view. Your parsing a list of GridParts.
You should be able to strongly type your view like your tried - just refer to GridParts instead of parts.
#model IEnumerable<MyApp.Models.GridPart>
I use Asp.Net MVC, Entity Framework. I have a form it looks like below.
Here, dropdownlist is filled from a table(types). Checkboxes is filled from another table(test). Tables are like below:
public class Types
{
public int TypesID{get;set;}
public string TestName { get; set; }
public string TestExplanation { get; set; }
public int TestTime { get; set; }
}
public class Tests
{
public int TestID{get;set;
public string Name { get; set; }
public string Code { get; set; }
}
public class Types_Tests
{
public int Types_TestsID{ get; set; }
public int TypesID { get; set; }
public int TestsID { get; set; }
public virtual Types Types { get; set; }
public virtual Tests Tests { get; set; }
}
Types_test table is relation table between Types and Tests. When I click Kaydet button, it shuld save type and checked tests. I made this operation using ViewBag, javascript and hdnvalue.I added checked checkboz values to a hdntext. I made saving process like below:
[HttpPost]
public ActionResult Index(string drpType, string hdntesttypes)
{
var TypeList = Types.GetAll();
ViewBag.TypesList = new SelectList(TypeList, "Id", "Name");
var testypeList = testTypes.GetAll();
ViewBag.TestTypesList = new SelectList(testypeList, "Id", "TestName");
GenericRepository<TestDisabledTypes> testDisabledRepository = new GenericRepository<TestDisabledTypes>(_context);
if (!string.IsNullOrEmpty(hdntesttypes))
{
string[] disabletypesArray = hdntesttypes.Split(',');
using (TransactionScope trns = new TransactionScope())
{
for (int i = 0; i < disabletypesArray.Length; i++)
{
Test_Types types = new Test_Types ();
types.TestTypesID = Convert.ToInt32(disabletypesArray[i]);
types.TypesID = Convert.ToInt32(drpType);
testDisabledRepository.Insert(types);
}
trns.Complete();
}
}
return View();
}
It wokrs. But I search better solution for this process. Can someone give me any idea?
Thanks.
If you don't need additional attributes for your entity class, you don't need create link table.
Just define the following class, and EF will generate the link table for you automatically.
public class Type
{
public int TypesID{get;set;}
public string TestName { get; set; }
public string TestExplanation { get; set; }
public int TestTime { get; set; }
public ICollection<Test> Tests { get; set; }
}
public class Test
{
public int TestID{get;set;
public string Name { get; set; }
public string Code { get; set; }
public ICollection<Type> Types {get;set;}
}
Well, in EntityFramework if you want to create a many to many relation object you need to create new object of "linking" entity. Unfortunately, it is not possible to add first object, add second object and say "Guys, you are in many to many relationships. Are you happy then?" :) You need to create relation object, set appropriate fields in it (I think these are ids of two objects itself) and add it to relation collection (entity) in your model. But before doing so you need to be sure that objects with data you are linking with are already exists in database. Otherwise you'll get an error
Also it's not necessary to create manually transaction because EF does it for you automatically each time you get/save your data
I'm very new to ASP.NET MVC, so forgive me if this is something I should know. I haven't seen any obvious documentation on it, so here goes:
I have a LINQ to Entities data model and a MVC project. I use a lot of javascript/jquery, so have opted to access my data from the client through a WebAPI as json objects. However, I don't want to pass all the entity object properties though to the client, so I have added separate models to my MVC project in which I handle MVC model validation and Binding to my Views. Also, in order to work with it in my jquery, I have created json versions of the models.
This is only the start of the project and I don't want to start it off on the wrong foot. Having three versions of my models for each entity in my business layer is going to be a nightmare! I am sure that the overall structure of my project is a very common one, but can't see many developers settling for such duplication of code. There must be a better way of implementing it.
Any thoughts? Really appreciate any input.
In answer to your comment above - you can create your javascript viewmodel as a standard js object. I tend to use Knockout.js so it would look like this:
jsController.Resource = function (data) {
self.UserId = ko.observable(data.UserId);
self.FullName = ko.observable(data.Name);
self.RoleName = ko.observable(data.RoleName);
self.RoleId = ko.observable(data.RoleId);
}
and then use an ajax post method to post it to your MVC action
jsController.addToUndertaking = function (resource, isAsync) {
mylog.log("UndertakingId at post = " + jsController.undertakingId);
var action = $.ajax({
type: "POST",
url: "/TeamMember/AddUserToUndertaking",
data: resource,
cache: false,
async: isAsync
});
action.done(function () {
resource.AllocatedToUndertaking(true);
//Do other funky stuff
});
};
Create your MVC action so that it accepts a forms collection as so:
public ActionResult AddUserToUndertaking(FormCollection postedResource)
{
if (Request.IsAjaxRequest() == false)
{
const string msg = "Non ajax request received";
Logger.ErrorFormat(msg);
throw new SecurityException(msg);
}
if (postedResource == null)
{
Logger.Debug("Null resource posted - terminating.");
return new HttpStatusCodeResult(500);
}
var resource = new AllocatedResourceAjaxViewModel(postedResource);
//Do something Funky
return new HttpStatusCodeResult(200);
}
and then you create your MVC viewmodel from the forms collection (i tend to do this by passing in the forms collection as a constructor method to the viewmodel).
public class AllocatedResourceAjaxViewModel
{
public int UserId { get; set; }
public string Name { get; set; }
public string RoleName { get; set; }
public int RoleId { get; set; }
public AllocatedResourceAjaxViewModel()
{}
public AllocatedResourceAjaxViewModel(NameValueCollection formData)
{
UserId = JsFormDataConverter.Int(formData["UserId"]);
Name = Convert.ToString(formData["FullName"]);
RoleName = Convert.ToString(formData["RoleName"]);
RoleId = JsFormDataConverter.Int(formData["RoleId"]);
}
}
As a null int in your javascript VM will lead to a string of 'undefined' being passed you need a converter method to safely extract non strings.
public static class JsFormDataConverter
{
public static bool Boolean(string formValue, bool defaultValue = false)
{
if (formValue.ToLower() == "true") return true;
if (formValue.ToLower() == "false") return false;
return defaultValue;
}
public static int Int(string formValue, int defaultValue = 0)
{
int result;
return int.TryParse(formValue, out result)
? result
: defaultValue;
}
}
and there you go. I am sure you can improve on the above but that will get you going.
The way that I have always worked is that you have your Models e.g. Order & OrderLines which are where you store all your data and get hydrated either directly from the database by SQL or (more usually these days ) by an ORM such as NHibernate or Entity Framework.
You then have ViewModels - these are used to transport the data from your application to the views - either directly ie a strongly typed view bound to say an OrderViewModel or via an action returning a JsonResult.
A OrderViewModel is not a duplication of Order as it is designed to only hold the data that is needed to be presented on the screen (If you have many different views displaying an Order in different ways it could be perfectly acceptable to have many different ViewModels -one for each view containing only the fields needed for each view). ViewModels should also not contain any complex types except other ViewModels. this helps keep accidental data access out of the views (think security and performance).
So Given
public class Order
{
public int Id { get; set; }
public DateTime OrderDate { get; set; }
public User User { get; set; }
public string Description { get; set; }
public List<OrderLine> OrderLines { get; set; }
}
public class OrderLine
{
public int Id { get; set; }
public Order Order { get; set; }
public String Description { get; set; }
public int Quantity { get; set; }
public int Weight { get; set; }
public int Price { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
You could have the two ViewModels
public class OrderViewModel
{
public int ID { get; set; }
public List<OrderLineViewModel> OrderLines { get; set; }
public DateTime OrderDate { get; set; }
public int UserId { get; set; }
public string Description { get; set; }
}
public class OrderLineViewModel
{
public int Id { get; set; }
public int OrderId { get; set; }
public String Description { get; set; }
public int Quantity { get; set; }
public int Weight { get; set; }
public int Price { get; set; }
}
The view models could then be serialized into JSON as needed or marked up with validation attributes etc.