I've got a ViewModel that takes some Model data and slightly alters it.
The way I'm doing it "works" since I just pass the DomainModel to the constructor for the ViewModel, but since I'm using AutoMapper on some of my one-to-one ViewModels, I thought I'd try and learn how to do the mapping across all ViewModels.
Here's an example of a ViewModel that does a little extra.
public class UsersDetailsViewModel
{
public string UserName { get; set; }
public string Email { get; set; }
public string Website { get; set; }
public int ID { get; set; }
public List<OpenID> OpenIds { get; set; }
public string UserAge { get; set; }
public string About { get; set; }
public string Slug { get; set; }
public System.DateTime LastSeen { get; set; }
public string Region { get; set; }
public string MemberSince { get; set; }
public string Reputation { get; set; }
public bool IsUserMatch { get; set; }
private readonly MarkdownDeep.Markdown _markdown;
public UsersDetailsViewModel(Domain.User user)
{
AuthUserData currentuser = AuthenticationHelper.RetrieveAuthUser;
_markdown.NoFollowLinks = true;
_markdown.SafeMode = true;
_markdown.ExtraMode = false;
_markdown.MarkdownInHtml = true;
// We want to ensure that the user has a username, even if they
// haven't set one yet. What this does is check to see if the
// user.UserName field is blank, and if it is, it will set the
// username to "UserNNNN" where NNNN is the user ID number.
_UserName = (user.UserName != null) ? user.UserName : "User" + user.ID.ToString;
// Nothing fancy going on here, we're just re-passing the object from
// the database to the View. No data manipulation!
_Email = user.Email;
_Website = user.WebSite;
_ID = user.ID;
// Get's a list of all of the user's OpenID's
_OpenIds = user.OpenIDs.ToList;
// Converts the users birthdate to an age representation
_UserAge = user.BirthDate.ToAge;
//IE: 29
// Because some people can be real ass holes and try to submit bad
// data (scripts and shitè) we have to modify the "About" content in
// order to sanitize it. At the same time, we transform the Markdown
// into valid HTML. The raw input is stored without sanitization in
// the database. this could mean Javascript injection, etc, so the
// output ALWAYS needs to be sanitized.
// This method below was used in conjunction with MarkDownSharp
// _About = Trim(Utilities.HtmlSanitizer.Sanitize(Markdown.Transform(user.About)))
_About = _markdown.Transform(user.About);
// Removes spaces from Usernames in order to properly display the
// username in the address bar
_Slug = Strings.Replace(user.UserName, " ", "-");
// Returns a boolean result if the current logged in user matches the
// details view of tBhe user in question. This is done so that we can
// show the edit button to logged in users.
_IsUserMatch = (currentuser.ID == user.ID);
// Grabs the users registration data and formats it to a <time> tag
// for use with the timeago jQuery plugin
_MemberSince = user.MemberSince;
// Grabs the users last activity and formats it to a <time> tag
// for use with the timeago jQuery plugin
_LastSeen = user.ActivityLogs.Reverse.FirstOrDefault.ActivityDate;
// Formats the users reputation to a comma Deliminated number
// IE: 19,000 or 123k
_Reputation = user.Reputation.ToShortHandNumber;
// Get the name of the users Default Region.
_Region = user.Region.Name.FirstOrDefault;
}
}
And here's how I currently utilize the above ViewModel
public ActionResult Details(int id)
{
User user = _userService.GetUserByID(id);
if (user != null) {
Domain.ViewModels.UsersDetailsViewModel userviewmodel = new Domain.ViewModels.UsersDetailsViewModel(user);
return View(userviewmodel);
} else {
// Because of RESTful URL's, some people will want to "hunt around"
// for other users by entering numbers into the address. We need to
// gracefully redirect them to a not found page if the user doesn't
// exist.
throw new ResourceNotFoundException();
}
}
How can I use (or should I use) AutoMapper to map my DomainModel to my ViewModel while doing the custom processing you see above?
On automapper where you create the Map you can specify additional processes for specific members of the destination type.
So where your default map would be
Mapper.Map<Domain.User, UsersDetailsViewModel>();
there is a fluent syntax to define the more complicated mappings:
Mapper.Map<Domain.User, UsersDetailsViewModel>()
.ForMember(vm=>vm.UserName, m=>m.MapFrom(u=>(u.UserName != null)
? u.UserName
: "User" + u.ID.ToString()));
Here the ForMember takes two Arguments the first defines the property that you are mapping to. The second provides a means of defining the mapping. For an example I have copped out and shown one of the easy mappings.
If you require a more difficult mapping, (such as your CurrentUser mapping) you can create a class that implements the IResolver interface, incorporate your mapping logic in that new clases and then add that into the mapping.
Mapper.Map<Domain.User, UsersDetailsViewModel>()
.ForMember(vm=>vm.IsUserMatch, m=>m.ResolveUsing<MatchingUserResolver>()));
when Mapper comes to do the mapping it will invoke your custom resolver.
Once you discover the syntax of the .ForMember method everything else kind of slots into place.
Custom mapping can be defined in global.ascx (at startup) by following codes :
AutoMapper.Mapper.CreateMap<Domain.User, UsersDetailsViewModel>()
.ForMember(o => o.Email, b => b.MapFrom(z => z.Email))
.ForMember(o => o.UserName , b => b.MapFrom(user => (user.UserName != null) ? user.UserName : "User" + user.ID.ToString));
you can do some initialization via BeforeMap () method. But you may need to do some changes in your viewmodel.
I think the syntax has slightly changed in 2019 (ASP.NET Core 2.2), this method is now handled with the MapperConfiguration and the static methods are no more available.
But I agree with #KJSR, this post is still really useful :-)
private Mapper UserMapper= new Mapper(new MapperConfiguration(cfg => (cfg.CreateMap<Domain.User, UsersDetailsViewModel>())
.ForMember(x=>x.Email, y=>y.MapFrom(z=>z.Email))
.ForMember(x => x.UserName , y => y.MapFrom(user => (user.UserName != null) ? user.UserName : "User" + user.ID.ToString))));
Related
I'm developing ASP.NET MVC appliation. I've found Fluent Validation great validation tool and it works, but with my current architecture it has one drawback. The validator does not care about Metadata. I'm using Metadata on seperate class for clarity.
Model
[MetadataType(typeof(DocumentEditMetadata))]
[Validator(typeof(DocumentValidator))]
public class DocumentEditModel
{
public string DocumentNumber { get; set; }
(etc...)
}
Metadata Model
public class DocumentEditMetadata
{
[Required]
[StringLength(50)]
[Display(ResourceType = typeof(Label), Name = "DocumentNumber")]
public string DocumentNumber { get; set; }
(etc...)
}
Can anyone point a solution? I need data annotations for localization of labels (hence the DisplayAttribute).
Think you need to write your own Display name resolver for fluent validation (guess this should be placed in your global.asax).
Caution
This solution is only trying to resolve the display name.
Your other "validation" attributes (Required, StringLength) should no more be used, as you will manage that with FluentValidation.
ValidatorOptions.DisplayNameResolver = (type, memberInfo, expression) =>
{
//this will get in this case, "DocumentNumber", the property name.
//If we don't find anything in metadata / resource, that what will be displayed in the error message.
var displayName = memberInfo.Name;
//we try to find a corresponding Metadata type
var metadataType = type.GetCustomAttribute<MetadataTypeAttribute>();
if (metadataType != null)
{
var metadata = metadataType.MetadataClassType;
//we try to find a corresponding property in the metadata type
var correspondingProperty = metadata.GetProperty(memberInfo.Name);
if (correspondingProperty != null)
{
//we try to find a display attribute for the property in the metadata type
var displayAttribute = correspondingProperty.GetCustomAttribute<DisplayAttribute>();
if (displayAttribute != null)
{
//finally we got it, try to resolve the name !
displayName = displayAttribute.GetName();
}
}
}
return displayName ;
};
Personal point of view
By the way, if you just use Metadata classes for clarity, don't use them !
It may be a solution if you have no choice (when entity classes are generated from an edmx and you really want to manage the display names this way), but I would really avoid them if it's not necessary.
public class CreateHireViewModel
{
[Display(Name = nameof(CreateHireViewModel.Title), ResourceType = typeof(Resource.HireResource.Hire))]
public string Title { get; set; }
}
public class CreateHireViewModelValidator : AbstractValidator<CreateHireViewModel>
{
public CreateHireViewModelValidator(IStringLocalizer<Resource.HireResource.Hire> l)
{
RuleFor(x => x.Title).NotEmpty().WithName(l[nameof(CreateHireViewModel.Title)]);
RuleFor(x => x.Title).Length(3, 50).WithName(l[nameof(CreateHireViewModel.Title)]);
}
}
I want to get some understanding about how exactly Automapper is working. I know the basic idea, before I just used so called ViewModels to send the information that the business needs extracted from one or more database tables. Now I'm working on a legacy project where Automapper is used and maybe it offers more than just that but for the given moment I want to undestand (be able) to map my Domain object(s) to my DTO object(s) or vice-versa, I'm not sure which one is the correct way to go since I'm not able to do it.
This is a simple example of a console project I've made to test some basic functionalities of Automapper:
Where the DTO_User.cs class is meant to be used to send a data to the front end. It looks like this:
public class DTO_User
{
public int ID { get; set; }
public string Name { get; set; }
}
And the User.cs is the class which represent the actual domain data :
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
}
So what I am trying to do. In my Program.cs I have a static method which creates some User obejcts:
public static List<Model.User> SeedUsers()
{
List<Model.User> users = new List<Model.User>()
{
new Model.User { ID = 1, Name = "Ivan", PhoneNumber = "1235436"},
new Model.User { ID = 2, Name = "Petkan", PhoneNumber = "00000000"},
new Model.User { ID = 3, Name = "Dragan", PhoneNumber = "11111111"},
new Model.User { ID = 4, Name = "Stoyan", PhoneNumber = "224545346"}
};
return users;
}
Then In my Main method I try to map this mocked data to my DTO List:
static void Main(string[] args)
{
Mapper.CreateMap<DTO.DTO_User, Model.User>();
//Mock user data as if it's taken from database
List<Model.User> users = new List<Model.User>();
users.AddRange(SeedUsers());//Simulate call to database
//Create List of DTO Users
List<DTO.DTO_User> dtoUsers = new List<DTO.DTO_User>();
//Now map the database users to our DTO Users
foreach (var user in users)
{
DTO.DTO_User u = Mapper.Map<Model.User, DTO.DTO_User>(user);
dtoUsers.Add(u);
}
}
I got the error inside the foreah loop here:
DTO.DTO_User u = Mapper.Map<Model.User, DTO.DTO_User>(user);
Saying that I have some invalid arguments. Obviously I don't really catch the idea how Automapper was meant to implement the actual mapping. The code above is what was looking most natural to me. I know that this is pretty basic so an actual solution won't be too challenging but I would really appreciate if someone explains to me where my logic cracks and what is the idea behind the working code.
Trying adding an additional mapping:
Mapper.CreateMap<Model.User, DTO.DTO_User>();
and changing your Map invocation to the following:
DTO.DTO_User u = Mapper.Map<DTO.DTO_User>(user);
I'm using EF(db first) and trying to add new row in table using the next code:
var user = new User();
//Some logic to fill the properties
context.Users.AddObject(user);
context.SaveChanges();
Before saving changes on EF i want to verify that all required (not null and with no default value) properties are filled. How can i get all such fields?
I've tried few ways, but can't achieve needed result. The last try was like that:
var resList = new List<PropertyInfo>();
var properties = type.GetProperties(BindingFlags.DeclaredOnly |
BindingFlags.Public |
BindingFlags.Instance).Where(p => !p.PropertyType.IsGenericType);
foreach (var propertyInfo in properties)
{
var edmScalarProperty =
propertyInfo.CustomAttributes.FirstOrDefault(
x => x.AttributeType == typeof (EdmScalarPropertyAttribute));
var isNullable = true;
if (edmScalarProperty != null)
{
var arg = edmScalarProperty.NamedArguments.FirstOrDefault(x => x.MemberName == "IsNullable");
if (arg != null)
{
isNullable = (bool) arg.TypedValue.Value;
}
}
if (!isNullable)
{
resList.Add(propertyInfo);
}
}
return resList;
Create a constructor with the required fields as parameters.
I always separate my domain objects from my EF objects (DTO objects). The domain object has only one constructor with the required fields. When I want to save these objects I convert them to DTO objects.
Have you looked at all into DataAnnotations for your model classes? Utilizing these (and using a separate object from the one EF creates for you) you can get pretty significant validation built into your models from the model level. Additionally, as L01NL pointed out, you can have your constructor take in parameters that require data.
Lots of information on Model and Validation can be found, one such example is:
http://msdn.microsoft.com/en-us/library/dd410405(v=vs.100).aspx
(look through this main section and its subsections)
using System.ComponentModel.DataAnnotations
public class Foo
{
public Guid Id { get; private set; }
[StringLength(50),Required]
public string FooName { get; private set; }
[Required]
public int Age { get; private set; }
// etc props
public Foo(string fooName, int age)
{
if (string.IsNullOrEmpty(fooName))
throw new ArgumentException("FooName cannot be null or empty"); // note there is also a "minimum length" data annotation to avoid doing something like this, was just using this as an example.
this.Id = Guid.NewGuid();
this.FooName = fooName;
this.Age = age;
}
}
public class YourController
{
[HttpPost]
public ActionResult Add(Foo foo)
{
if (!ModelState.IsValid)
// return - validation warnings, etc
// Add information to persistence
// return successful add?
}
}
Assume I have this concrete class:
public partial class User
{
public int ID { get; set; }
public string Email { get; set; }
public string FullName { get; set; }
}
And I want to create an anonymous instance that has a valid email address, and the fullname field is no more than 20 characters. I can do this:
var fixture = new Fixture();
var anonUser = fixture.Build<User>()
.With(x => x.Email, string.Format("{0}#fobar.com", fixture.Create<string>()))
.With(x => x.FullName, fixture.Create<string>()Substring(0,20))
.Create();
Is there a way that I can define this in one place, so that AF knows that I can get my customized anon class by using:
var newAnon = fixture.Build<User>();
You have various options. In my opinion, the best option is to apply the GOOS principle of listening to your tests. When the test becomes difficult to write, it's time to reconsider the design of the System Under Test (SUT). AutoFixture tends to amplify this effect.
Refactor to Value Objects
If you have a requirement that the Email and FullName properties should have particularly constrained values, it might indicate that instead of Primitive Obsession, the target API would benefit from defining explicit Email and FullName Value Objects. The canonical AutoFixture example is about phone numbers.
Use data annotations
You can also use data annotations to give AutoFixture hints about the constraints of the values. Not all data annotation attributes are supported, but you can use both MaxLength and RegularExpression.
It might look something like this:
public partial class User
{
public int ID { get; set; }
[RegularExpression("regex for emails is much harder than you think")]
public string Email { get; set; }
[MaxLenght(20)]
public string FullName { get; set; }
}
Personally, I don't like this approach, because I prefer proper encapsulation instead.
Use Customize
Instead of using the Build<T> method, use the Customize<T> method:
var fixture = new Fixture();
fixture.Customize<User>(c => c
.With(x => x.Email, string.Format("{0}#fobar.com", fixture.Create<string>())
.With(x => x.FullName, fixture.Create<string>().Substring(0,20)));
var newAnon = fixture.Create<User>();
Write a convention-driven Specimen Builder
Finally, you can write a convention-driven customization:
public class EmailSpecimenBuilder : ISpecimenBuilder
{
public object Create(object request,
ISpecimenContext context)
{
var pi = request as PropertyInfo;
if (pi == null)
{
return new NoSpecimen(request);
}
if (pi.PropertyType != typeof(string)
|| pi.Name != "Email")
{
return new NoSpecimen(request);
}
return string.Format("{0}#fobar.com", context.Resolve(typeof(string)));
}
}
This approach I really like, because I can put arbitrarily complex logic here, so instead of having to create a lot of one-off customizations, I can have a small set of conventions driving an entire test suite. This also tends to make the target code more consistent.
We're always told that a Controller should be skinny and that validation should be done in the Model, not the Controller. But consider the following example.
Here's is a simple Model and Controller for handling the POST from an edit screen, on which we can edit a Person object.
public class PersonEditModel
{
[Required(ErrorMessage = "No ID Passed")]
public int ID { get; set; }
[Required(ErrorMessage = "First name Required")]
[StringLength(50,ErrorMessage = "Must be under 50 characters")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last name Required")]
[StringLength(50,ErrorMessage = "Must be under 50 characters")]
public string LastName { get; set; }
}
public class PersonController : Controller
{
// [HttpGet]View, [HttpGet]Edit Controller methods omitted for brevity
[HttpPost]
public ActionResult Edit(PersonEditModel model)
{
// save changes to the record
return RedirectToAction("View", "Person", new { ID = model.ID});
}
}
The Model performs two kinds of validation here. It validates FirstName and LastName, but it also validates the private key (ID) used to access the record we wish to change. Should this validation be done in the Model also?
What if we then want to expand validation (as we should) to include a check to see if this record exists?
Normally, I would validate this in the controller:
[HttpPost]
public ActionResult Edit(PersonEditModel model)
{
using(DatabaseContext db = new DatabaseContext())
{
var _person = db.Persons.Where(x => x.ID == model.ID);
if(_person == null)
{
ModelState.AddError("This person does not exist!");
// not sure how we got here, malicious post maybe. Who knows.
// so since the ID is invalid, we return the user to the Person List
return RedirectToAction("List", Person");
}
// save changes
}
// if we got here, everything likely worked out fine
return RedirectToAction("View", "Person", new { ID = model.ID});
}
Is this bad practice? Should I be checking if the record exists in some kind of complex custom validation method in the model? Should I put it somewhere else entirely?
UPDATE
On a related note. Should a ViewModel contain the methods to populate the data?
Which of these is better practice - this
public class PersonViewModel
{
public Person person { get; set; }
public PersonViewModel(int ID){
using(DatabaseContext db = new DatabaseContext())
{
this.person = db.Persons.Where(x => x.ID == ID);
}
}
}
[HttpPost]
public ActionResult View(int ID)
{
return View("View", new PersonViewModel(ID));
}
Or this?
public class PersonViewModel
{
public Person person { get; set; }
}
[HttpPost]
public ActionResult View(int ID)
{
PersonViewModel model = new PersonViewModel();
using(DatabaseContext db = new DatabaseContext())
{
model.person = db.Persons.Where(x => x.ID == ID);
}
return View("View", model);
}
I generally prefer FluentValidation for all purposes. It also has a Nuget to install it out-of the box in VS.
Sample Validation Code from here:
using FluentValidation;
public class CustomerValidator: AbstractValidator<Customer> {
public CustomerValidator() {
RuleFor(customer => customer.Surname).NotEmpty();
RuleFor(customer => customer.Forename).NotEmpty().WithMessage("Please specify a first name");
RuleFor(customer => customer.Discount).NotEqual(0).When(customer => customer.HasDiscount);
RuleFor(customer => customer.Address).Length(20, 250);
RuleFor(customer => customer.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode");
}
private bool BeAValidPostcode(string postcode) {
// custom postcode validating logic goes here
}
}
Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult results = validator.Validate(customer);
bool validationSucceeded = results.IsValid;
IList<ValidationFailure> failures = results.Errors;
See?? It is very easy to validate any kind of models with Fluent Validation with clean methods. You can consider going through FluentValidation Documentation.
Where to validate?
Suppose you have a model as below:
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
virtual public ICollection<Image> Images { get; set; }
}
then, you will define another validator model in a similar class library or preferably a new class library that handles validation for all the models in the project.
public class CategoryValidator : AbstractValidator<Category>
{
public CategoryValidator()
{
RuleFor(x => x.Name).NotEmpty().WithMessage("Category name is required.");
}
}
So, you can do it in a separate validator model keeping your methods and domain models as clean as possible.
When we talk about Model, it includes your DAL and your business layer. For small apps or demos it is not unusual to see that kind of code in a controller, but normally you should give that role to the business or data layer :
[HttpPost]
public ActionResult Edit(PersonEditModel model)
{
// Validation round one, using attributes defined on your properties
// The model binder checks for you if required fields are submitted, with correct length
if(ModelState.IsValid)
{
// Validation round two, we push our model to the business layer
var errorMessage = this.personService.Update(model);
// some error has returned from the business layer
if(!string.IsNullOrEmpty(errorMessage))
{
// Error is added to be displayed to the user
ModelState.AddModelError(errorMessage);
}
else
{
// Update successfull
return RedirectToAction("View", "Person", new { ID = model.ID});
}
}
// Back to our form with current model values, as they're still in the ModelState
return View();
}
Here the goal is to free the controller from business logic validation and usage of the data context. It pushes submitted data and is notified if errors occurred. I used a string variable, but you can implement error management as you like. Evolving your business rules won't impact your controller at all.
There's absolutely nothing wrong with this. Your controllers are responsible for directing the flow of control when it comes to which view to be shown to your users. Part of doing that is ensuring that a view is getting a model that is in a usable state.
The controller doesn't care what the model is, or what the model contains, but it does care that it's valid. It's why ModelState.IsValid is so important, because the controller doesn't have to know how that validation is performed or what directly makes a model valid. Typically, any validation that needs to take place after ModelState.IsValid, can be pushed to another layer of your application, which again enforces separation-of-concerns.