AutoMapper: Keep Destination Value if the property doesnt exists in source - asp.net

I tried to search a lot, and try different options but nothing seems to work.
I am using ASP.net Identity 2.0 and I have UpdateProfileViewModel . When Updating the User info, I want to map the UpdateProfileViewModel to ApplicationUser (i.e. the Identity Model); but I want to keep the values, I got from the db for the user. i.e. the Username & Email address, that doesn't needs to change.
I tried doing :
Mapper.CreateMap<UpdateProfileViewModel, ApplicationUser>()
.ForMember(dest => dest.Email, opt => opt.Ignore());
but I still get the Email as null after mapping:
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
user = Mapper.Map<UpdateProfileViewModel, ApplicationUser>(model);
I also tried this but doesn't works:
public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var existingMaps = Mapper.GetAllTypeMaps().First(x => x.SourceType.Equals(sourceType) && x.DestinationType.Equals(destinationType));
foreach (var property in existingMaps.GetUnmappedPropertyNames())
{
expression.ForMember(property, opt => opt.Ignore());
}
return expression;
}
and then:
Mapper.CreateMap<UpdateProfileViewModel, ApplicationUser>()
.IgnoreAllNonExisting();

All you need is to create a mapping between your source and destination types:
Mapper.CreateMap<UpdateProfileViewModel, ApplicationUser>();
and then perform the mapping:
UpdateProfileViewModel viewModel = ... this comes from your view, probably bound
ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
Mapper.Map(viewModel, user);
// at this stage the user domain model will only have the properties present
// in the view model updated. All the other properties will remain unchanged
// You could now go ahead and persist the updated 'user' domain model in your
// datastore

Related

Claims-based authorization and where to add what the user can do

I just implemented a web api using claims-based authorization. A user can login in the system and a set of claims are pulled from the database and added to the httpContext.User.Identity depending on what the user can do.
After registering the policies in Startup.cs with something like:
services.AddAuthorization(options =>
{
options.AddPolicy(PoliciesDefinitions.RequiresVehicleList, policy => policy.RequireClaim(Permissions.VehiclesList.ToString()));
...
});
I can use the Authorize attribute on the controllers method that I want to authorize with something like:
Authorize(Policy=PoliciesDefinitions.RequiresDriversList)]
[HttpGet]
public ActionResult Get() { ... }
This works ok but today I was reading microsoft documentation a bit more thoroughly and I came across this statement in the Claims-based authorization documentation:
A claim is a name value pair that represents what the subject is, not
what the subject can do
At this time I'm doing exactly what microsfot suggests not to do. I'm adding what the user can do (permissions) to the identity . So, this leads me to think, am I doing it wrong? If the answer is yes, where would you store the user permissions and how would authorization work?
This allows for the KVP, and multiple values.
// with Razor, you did not specific if it was core 2, 3.1 or Razor
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
services.AddAuthorization(options =>
{
options.AddPolicy("Vendors", policy =>
policy.RequireClaim("Type.Tykt.org", "Dealer", "Driver", "WholeSaler", "Asset", "Repair"));
});
}
Option 2:
Also there is a claims collection, you can add it after user successfully logs in.
var user = new User {
Email = "xyz#tykt.org",
Name = "xyz"
}
user.Claims.Add(new IdentityUserClaim<string>
{
ClaimType="your-type", // your key
ClaimValue="your-value" // your value
});
await userManager.CreateAsync(user);
Update Ref MSDN:
Its really your choice on how you store retrieve, if I'm understanding the question, your question specifically around is the value of the claim.
Typically, the mapping and verification happens in something like a PermissionHandler : IAuthorizationHandler or a generic approach MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>. Which, loads the values, and handles the requirement verification of a specific permission for e.g. min age, but the actual claims (what are you stating/min age policy vs the value is usually in the DB, like DOB=1/1/1990) travel with the Principal object. Now, where you choose to retrieve the value of the claim is upto you
In the below function he is getting value for the Key from the Context and then validating
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
c.Issuer == "http://contoso.com"))
{
return Task.CompletedTask;
}
var dateOfBirth = Convert.ToDateTime(
// He gets the value on the server-side from any store or 3rd party relayer
context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth &&
c.Issuer == "http://contoso.com").Value);
int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}
if (calculatedAge >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}

Extra info when using Web Api/OWIN Auth Token

I'm using out-of-the-box auth with Individual User Accounts that comes with the Visual Studio template for Web Api. I consume the api in an Angular.js front end.
Not surprisingly, I need to store extra information about the user like first and last names. I would like to set set these extra values in the Register method.
Q. Should I extend the user model or should I store this information as claims?
It would be a bonus if this information was 'automatically' returned in the response from /Token without adding extra code.
I decided to return the extra info in the response to the /Token call.
In ApplicationOAuthProvider (which is part of the template project) I changed CreateProperties and adjusted calls to CreateProperties in 2 places to pass the user, not just the username:
public static AuthenticationProperties CreateProperties(ApplicationUser user)
{
var firstNameClaim = user.Claims.FirstOrDefault(c => c.ClaimType == ClaimTypes.GivenName);
var lastNameClaim = user.Claims.FirstOrDefault(c => c.ClaimType == ClaimTypes.Surname);
var roles = user.Claims.Where(c => c.ClaimType == ClaimTypes.Role).Select(c => c.ClaimValue);
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", user.UserName },
{"firstName", firstNameClaim != null ? firstNameClaim.ClaimValue : "" },
{"lastName", lastNameClaim != null ? lastNameClaim.ClaimValue : "" },
{"roles", string.Join( ",", roles) }
};
return new AuthenticationProperties(data);
}

Can IUserMapper be used to Change User Details

In Nancy FX how can I use the IUserMapper (if at all) to change a logged in users account details (name, email, password)?
// registering is straight forward
Post["/register", true] = async(parameters, ct) =>
{
var user = this.BindAndValidate<UserRegistration>();
var response = await mapper.RegisterUser(user); // user is registered
...
}
// but how can I change a registered user's details?
Post["/profile", true] = async(parameters, ct) =>
{
this.RequiresAuthenticationAndLogOut();
var user = this.BindAndValidate<UserRegistration>();
var response = await mapper.?????(user);
...
}
You wouldn't use the IUserMapper at all, this really only exists for authentication purposes and nothing more.
When a user is authenticated then you get access to the UserName property. If you setup your mapper to assign the user's Id to the UserName then you can load your user, modify, and commit.
i.e:
Post["/profile", true] = async(parameters, ct) =>
{
this.RequiresAuthenticationAndLogOut();
var user = this.BindAndValidate<UserRegistration>();
var existingUser = await db.LoadAsync(int.Parse(CurrentUser.UserName));
existingUser.Name = user.Name;
...
return ...;
}
Also, you should never persist an object that's been bound from a client. The user may submit additional information you don't want them to.
Also I don't know where you got your IUserMapper from because in Nancy there is no Register.

Using a custom method in LINQ to Entities

I have a LINQ expression:
var users = db.Relationships.Where(i => i.RelationshipId== relId)
.Select(s => s.User).Distinct().Select(s => new UserViewModel() {
Username = s.Username,
LastActiveDateTime = s.LastActive, // DateTime, I want it to be a string filtered through my custom GetFriendlyDate method
}).ToList();
// convert all DateTimes - yuck!
foreach (var userViewModel in users) {
userViewModel.LastActive = DateFriendly.GetFriendlyDate(userViewModel.LastActiveDateTime);
}
This solution is working, but it feels wrong to
have to iterate over all users after getting them from the db, just to reformat a property on every single one
have a DateTime property on my ViewModel just so that it can later be converted to a string and never touched again
Is there any way I can use the GetFriendlyDate method directly within the query?
Possible solutions, worth to mention:
Have a getter property of your ViewModel, which would return transformed string, something like:
public string LastActive
{
get
{
return DateFriendly.GetFriendlyDate(LastActiveDateTime);
}
}
Though it not solves your problem with existing LastActiveDateTime column, transformation will be applied only at moment of usage (in your view, most likely - anyways if you will try use it somewhere latter in query, it will not work for reason you already know), so no need to iterate manually.
Create View, which will transform data on server side; so your data will already be returned in format you need, if you're using DBFirst, probably, it's easiest and fastest solution;
Finally, you can use ToList() twice, once before selecting new ViewModel() (or call AsEnumerable(), or find other way to materialize query). It will fetch data from database and will allow you perform any C#-side functions you want directly in query after ToList(). But, as mentioned before - it's about getting all data, which matched criteria up to ToList() - in most cases it's not appropriate solution.
And here is some additional readings:
How can I call local method in Linq to Entities query?
I tested it in LINQpad and it works.
It still kinda iterates over users (with LINQ) but you don't have to add DateTime property to your viewmodel class. Also you could convert collection of Users to collection of UserViewModel objects with Automapper. It would still iterate over users of course but you wouldn't see it.
Had to create some setup code of course because I don't have your database.
void Main()
{
var db = new List<User> {
new User { LastActive = DateTime.Now, Username = "Adam", Lastname = "Nowak" },
new User { LastActive = DateTime.Now.AddYears(1), Username = "Eve", Lastname = "Kowalska"}
};
// select only properties that you need from database
var users = db
.Select(user => new { Username = user.Username, LastActive = user.LastActive})
.Distinct()
.ToList();
var usersVM = from u in users
select new UserViewModel { Username = u.Username, LastActiveDateTime = DateFriendly.GetFriendlyDate(u.LastActive)};
usersVM.Dump();
}
class User
{
public DateTime LastActive;
public string Username;
public string Lastname;
};
class UserViewModel
{
public string Username;
public string LastActiveDateTime;
}
static class DateFriendly
{
public static string GetFriendlyDate(DateTime date)
{
return "friendly date " + date.Year;
}
}
And this outputs
Username LastActiveDateTime
Adam friendly date 2013
Eve friendly date 2014
There is no direct Concert.ToDate method available for LINQ. But you can try using the DateAdd method from the SqlFunctions class:
var users = db.Relationships.Where(i => i.RelationshipId== relId)
.Select(s => new
{
s.User.Username,
LastActive=SqlFunctions.DateAdd("d",0, s.LastActive)
})
.ToList().Select(s => new UserViewModel()
{
Username = s.Username,
LastActiveDateTime = s.LastActive
});
Wouldn't the following work?
var users = db.Relationships.Where(i => i.RelationshipId== relId)
.Select(s => s.User).Distinct().Select(s => new UserViewModel() {
Username = s.Username,
LastActiveDateTime = DateFriendly.GetFriendlyDate(s.LastActive)
}).ToList();

assign out parameter totalRecords when using caching

I'm using asp.net Sqlmembership.GetAll() method ( paging overload ) . now I want to add a caching layer to cache results but there is problem with GetAll method's out parameter that returns the total number of records . how can I assign a value to totalRecords parameter when data are retrieved from cache ?
If my understanding is correct this little flow will help you to achieve your goal
This is a basic flow:
When you want to access the cached object, ask it to the cache provider
If the object is not null, then cast to the correct type and return the object from the cache (in this case, the list of users). End process
If the object is null then
Retrieve the object from its original source (using the GetAll methods)
Save the retrieved object to the cache
Return the retrieved object. End process
In any case, I would recommend you to work with a custom domain class instead of the MembershipUser class
This is a basic example:
public IEnumerable<DomainUser> GetDomainUsers()
{
var context = HttpContext.Current;
var cache = context.Cache;
var domainUsers = cache["domainUsers"] as IEnumerable<DomainUser>;
if (domainUsers == null)
{
domainUsers = Membership.GetAllUsers().OfType<MembershipUser>().Select(x => new DomainUser
{
Email = x.Email,
Username = x.UserName
});
cache.Insert(
"domainUsers", // cache key
domainUsers, // object to cache
null, // dependencies
DateTime.Now.AddMinutes(30), // absoulute expiration
Cache.NoSlidingExpiration, // slading expiration
CacheItemPriority.High, // cache item priority
null // callback called when the cache item is removed
);
context.Trace.Warn("Data retrieved from its original source");
}
else
{
context.Trace.Warn("Data retrieved from cache");
}
return domainUsers;
}

Resources