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.