I have a asp.net core API, that maps from DB entities to ViewModels. Some of the properties in the ViewModels should only be delivered to users with the admin role. Not only the value of the property but the property itself.
given the customer entity in the DB
{
public string Name { get; set; }
public int Priority { get; set; }
}
and the ViewModel
{
public string Name { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public int? Priority { get; set; }
}
When the Controller is serialising to JSON, it will look like this if Priority has a value
{
"name": "Customer Name",
"priority": 1
}
and like this, if Priority is null
{
"name": "Customer Name"
}
In the Map i need to check whether the Property has the JsonIgnore Attribute and if the User is in the Administrators Role. If the User is not in the Administrators Role and the Attribute is set, it should Map null.
Is there a way to loop through all Properties of the Destination and check for Attributes?
CodePudding user response:
An option is to use IValueResolver<in TSource, in TDestination, TDestMember> interface:
public class CustomerViewModelPriorityResolver: IValueResolver<Customer, CustomerViewModel, int?>
{
// As an example, if you have something similar
private readonly UserRepository _userRepository;
public CustomerViewModelPriorityResolver(IServiceProvider serviceProvider)
{
// You can use DI to inject whatever service you need.
// If, say, you have a UserRepository and you want to query the database
_userRepository = serviceProvider.GetService<UserRepository>();
}
public int? Resolve(Customer source, CustomerViewModel destination, int destMember, ResolutionContext context)
{
// determines whether the property has the [JsonIgnore] attribute
bool isJsonIgnore = typeof(CustomerViewModel ).GetProperty("Priority").GetCustomAttribute<JsonIgnoreAttribute>() is not null;
// If you want to query the database to get the user and his role (something similar)
var user = _userRepository.GetByName(source.Name);
var isAdmin = ...
// Continue with whatever logic you have
return // the value of 'Priority'; NULL or source.Priority;
}
}
And in the mapping Profile:
public class MapperProfile : Profile
{
public MapperProfile()
{
CreateMap<Customer, CustomerViewModel>()
.ForMember(customerVM => customerVM.Priority, opt => opt.MapFrom<CustomerViewModelPriorityResolver>());
}
}
EDIT
Since you want to do the same thing for many properties, there is another option. IMappingAction<in TSource, in TDestination>
public class CustomerViewModelAction : IMappingAction<Customer, CustomerViewModel>
{
private readonly UserRepository _userRepository;
public CustomerViewModelAction (IServiceProvider serviceProvider)
{
// DI
_userRepository = serviceProvider.GetService<UserRepository>();
}
public void Process(Customer source, CustomerViewModel destination, ResolutionContext context)
{
// Using DI, perform your querying logic to determine the role
var isAdmin = ...
if(isAdmin) {
// I don't know what you want to do if the user IS admin, but I'm assuming you just want to continue with the normal mapping
return;
}
var ignoredProperties = typeof(CustomerViewModel).GetProperties().Where(pi => pi.GetCustomAttribute<JsonIgnoreAttribute>() is not null);
foreach(var pi in ignoredProperties)
pi.SetValue(destination, null);
}
}
And the mapping profile:
public class MapperProfile : Profile
{
public MapperProfile()
{
CreateMap<Customer, CustomerViewModel>()
.AfterMap<CustomerViewModelAction>();
}
}
TIP:
Avoid doing DB calls in mapping. Suppose you want to map List<Customer>
to List<CustomerViewModel>
. You will end up with N-database calls.
You could instead add a property to Customer: bool IsAdmin
and populate it beforehand, when you are fetching data about the customer.