Reflection set value of a properties property - c#

I have 2 classes:
public class CustomerViewModel {
public SystemViewModel system { get;set; }
}
public class SystemViewModel {
public bool isReadOnly { get; set; }
}
On the method controller action I have a custom filter attribute which executes some code and determines whether or the user has ReadOnly or Write access. This attribute can be applied to multiple actions across multiple controllers.
So far using reflection I can get access to the model using:
var viewModel = filterContext.Controller.ViewData.Model;
I can not cast this model to CustomerViewModel because on a different action it might be something like SalaryViewModel. What I do know is that any model that requires the readonly property will have SystemViewModel property.
From my custom filter I need a way to be able to change the value of readonly.
So far I have this:
public override void OnActionExecuted(ActionExecutedContext filterContext) {
var viewModel = filterContext.Controller.ViewData.Model;
var systemViewModelPropertyInfo = model.GetType()
.GetProperties()
.FirstOrDefault(p => p.PropertyType == typeof(SystemViewModel));
if (systemViewModelPropertyInfo != null) {
// Up to here, everything works, systemViewModelPropertyInfo is of
// type PropertyInfo, and the systemViewModelPropertyInfo.PropertyType
// shows the SystemViewModel type
// If we get here, the model has the system property
// Here I need to try and set the IsReadOnly property to true/false;
// This is where I need help please
}
}
SOLVED
Thanks to everyone who pitched in to help solve this. Special thanks to Julián Urbano for having the solution I was looking for.
Here is my resulting code from within my filter:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
try
{
var viewModel = filterContext.Controller.ViewData.Model;
var systemViewModelPropertyInfoCount = viewModel.GetType().GetProperties().Count(p => p.PropertyType == typeof(SystemViewModel));
if(systemViewModelPropertyInfoCount == 1)
{
var systemViewModelPropertyInfo = viewModel.GetType().GetProperties().First(p => p.PropertyType == typeof(SystemViewModel));
if(systemViewModelPropertyInfo != null)
{
var systemViewModel = systemViewModelPropertyInfo.GetValue(viewModel, null) as SystemViewModel;
if(systemViewModel != null)
{
var admin = GetAdmin(filterContext.HttpContext.User.Identity.Name);
if(admin != null && _adminService.HasPermission(admin, _privilege, Access.Level.ReadWrite))
systemViewModel.ReadOnly = false;
else
systemViewModel.ReadOnly = true;
}
}
} else if(systemViewModelPropertyInfoCount > 1)
{
throw new Exception("Only once instance of type SystemViewModel allowed");
}
}
catch (Exception exception)
{
Log.Error(MethodBase.GetCurrentMethod(), exception);
filterContext.Controller.TempData["ErrorMessage"] = string.Format("Technical error occurred");
filterContext.Result = new RedirectResult("/Error/Index");
}
finally
{
base.OnActionExecuted(filterContext);
}
}

I can not cast this model to CustomerViewModel because on a different action it might be something like SalaryViewModel. What I do know is that any model that requires the readonly property will have SystemViewModel property.
option 1
Seems to me that the best option is to write an interface like:
public interface IWithSystemViewModel {
SystemViewModel System {get;}
}
and implement it from your classes, much like:
public class CustomerViewModel : IWithSystemViewModel{
public SystemViewModel System { get;set; }
}
public class SalaryViewModel : IWithSystemViewModel{
public SystemViewModel System { get;set; }
}
so you can cast it and access the isReadOnly property:
IWithSystemViewModel viewModel = filterContext.Controller.ViewData.Model as IWithSystemViewModel;
if(viewModel!=null){
viewModel.System.isReadOnly ...
}
option 2
If you want to stick to using reflection:
var viewModel = filterContext.Controller.ViewData.Model;
SystemViewModel theSystem = viewModel.GetType().GetProperty("system")
.GetValue(viewModel, null) as SystemViewModel;
theSystem.isReadOnly ...
Tricky thing: in your code, you select the property whose type is SystemViewModel. But what if the object actually has several SystemViewModel properties that you don't know about? Are you sure you're accessing the proper one? You may force all of them to use the same name, but then again, that would be like using the interface in option 1 above.
I'd definitely go with option 1.

var viewModel = new CustomerViewModel();
var systemViewModelPropertyInfo = viewModel.GetType()
.GetProperties()
.FirstOrDefault(p => p.PropertyType == typeof(SystemViewModel));
if (systemViewModelPropertyInfo != null) {
var systemViewModelProperty = systemViewModelPropertyInfo.GetValue(viewModel, null) as SystemViewModel;
// get the desired value of isReadOnly here...
var isReadOnly = false;
// here, systemViewModelProperty may be null if it has not been set.
// You can decide what to do in that case. If you need a value to be
// present, you'll have to do something like this...
if (systemViewModelProperty == null) {
systemViewModelPropertyInfo.SetValue(viewModel, new SystemViewModel { isReadOnly = isReadOnly }, null);
}
else {
systemViewModelProperty.isReadOnly = isReadOnly;
}
}
That said, this whole process would probably be easier if you implemented an interface...
public interface IHaveSystemViewModel {
SystemViewModel system { get; set; }
}
var model = viewModel as IHaveSystemViewModel;
if (model != null) {
// again, you need to make sure you actually have a reference here...
var system = model.system ?? (model.system = new SystemViewModel());
system.isReadOnly = false; // or true
}

Related

Get InterfaceMethod-pendant from TargetMethod by InterfaceMapping

I have a property on a class which is implemented by an interface. Now I want to get all attributes from a specific type declared on that property and their interface Pendants.
In order to regard multi implementation with implicit and explicit members I wrote an test-class (with xUnit).
[DebuggerDisplay("{Tooltip}")]
[AttributeUsage(AttributeTargets.Property)]
public class TooltipAttribute : Attribute
{
public TooltipAttribute(string tooltip)
{
Tooltip = tooltip;
}
public string Tooltip { get; set; }
}
public interface IAmGood
{
[Tooltip("GOOD: I am a very generic description.")]
int Length { get; }
}
public interface IAmBad
{
[Tooltip("BAD: This description is not wanted to be shown.")]
int Length { get; }
}
public class DemoClassImplicit : IAmGood, IAmBad
{
[Tooltip("GOOD: Implicit")]
public int Length => throw new NotImplementedException();
[Tooltip("BAD: Explicit")]
int IAmBad.Length => throw new NotImplementedException();
}
public class DemoClassExplicit : IAmGood, IAmBad
{
[Tooltip("GOOD: Explicit")]
int IAmGood.Length => throw new NotImplementedException();
[Tooltip("BAD: Implicit")]
public int Length => throw new NotImplementedException();
}
public class DemoClassImplicitForBoth : IAmGood, IAmBad
{
[Tooltip("I am GOOD and BAD")]
public int Length => throw new NotImplementedException();
}
public class TestClass
{
[Fact]
public void GetTooltipFromImplicit()
{
var demoClassImplicit = new DemoClassImplicit();
var propertyInfo = demoClassImplicit.GetType().GetRuntimeProperty("Length");
var tooltips = GetTooltipAttribute<TooltipAttribute>(propertyInfo);
Assert.Equal(2, tooltips.Count());
Assert.All(tooltips, o => Assert.Contains("GOOD", o.Tooltip));
}
[Fact]
public void GetTooltipFromExplicit()
{
var demoClassImplicit = new DemoClassExplicit();
var propertyInfo = demoClassImplicit.GetType().GetRuntimeProperties().First(o => o.Name.EndsWith(".Length"));
var tooltips = GetTooltipAttribute<TooltipAttribute>(propertyInfo);
Assert.Equal(2, tooltips.Count());
Assert.All(tooltips, o => Assert.Contains("GOOD", o.Tooltip));
}
[Fact]
public void GetTooltipFromImplicitForBoth()
{
var demoClassImplicit = new DemoClassImplicitForBoth();
var propertyInfo = demoClassImplicit.GetType().GetRuntimeProperty("Length");
var tooltips = GetTooltipAttribute<TooltipAttribute>(propertyInfo);
Assert.Equal(3, tooltips.Count());
}
/// <summary>
/// The core method.
/// </summary>
public IEnumerable<T_Attribute> GetTooltipAttribute<T_Attribute>(PropertyInfo propInfo)
where T_Attribute : Attribute
{
var result = new List<T_Attribute>(propInfo.GetCustomAttributes<T_Attribute>());
var declaringType = propInfo.DeclaringType;
// The get method is required for comparing without use the prop name.
var getMethodFromGivenProp = propInfo.GetGetMethod(true);
// Check for each interface if the given property is declared there
// (it is not a naming check!).
foreach (var interfaceType in declaringType.GetInterfaces())
{
var map = declaringType.GetInterfaceMap(interfaceType);
// Check if the current interface has an member for given props get method.
// Attend that compare by naming would be cause an invalid result here!
var targetMethod = map.TargetMethods.FirstOrDefault(o => o.Equals(getMethodFromGivenProp));
if (targetMethod != null)
{
// Get the equivalent get method on interface side.
// ERROR: The error line!
var interfaceMethod = map.InterfaceMethods.FirstOrDefault(o => o.Name == targetMethod.Name);
if (interfaceMethod != null)
{
// The get method does not help to get the attribute so the property is required.
// In order to get the property we must look which one has the found get method.
var property = interfaceType.GetProperties().FirstOrDefault(o => o.GetGetMethod() == interfaceMethod);
if (property != null)
{
var attributes = property.GetCustomAttributes<T_Attribute>();
if (attributes != null)
{
result.AddRange(attributes);
}
}
}
}
}
return result;
}
}
The test method 'GetTooltipFromExplicit' fails because in the core method is a comparison by name. I marked the line above with // ERROR: The error line!.
I have no idea how to find the method-pendant inside of 'InterfaceMapping'-class.
The solution was to know that the order of the two collections in InterfaceMapping are mirrorred.
So replace the line below is the solution:
// ERROR: The error line!
var interfaceMethod = map.InterfaceMethods.FirstOrDefault(o => o.Name == targetMethod.Name);
// SOLUTION: The working line:
var interfaceMethod = map.InterfaceMethods[Array.IndexOf(map.TargetMethods, targetMethod)];
This detailed was explained on official member documentation (but not on the class itself). See:
https://learn.microsoft.com/en-us/dotnet/api/system.reflection.interfacemapping.interfacemethods?view=netcore-3.1#remarks
https://learn.microsoft.com/en-us/dotnet/api/system.reflection.interfacemapping.targetmethods?view=netcore-3.1#remarks

audit trail with entity framework

I have fields for audit trail in each table (InsertedBy, InsertedDate, UpdatedBy and UpdatedDate), I build solution to reduce redundant before by override savechange():
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries().Where(e =>
e.State == System.Data.Entity.EntityState.Added || e.State == System.Data.Entity.EntityState.Modified))
{
Auditing.ApplyAudit(entry, User);
}
return base.SaveChanges();
}
public class Auditing
{
public static void ApplyAudit(DbEntityEntry entityEntry, int User)
{
Type type = entityEntry.Entity.GetType();
if (entityEntry.State.ToString() == "Added")
{
if (type.GetProperty("InsertedBy") != null)
{
entityEntry.Property("InsertedBy").CurrentValue = User;
}
if (type.GetProperty("InsertedDate") != null)
{
entityEntry.Property("InsertedDate").CurrentValue = DateTime.Now;
}
}
else if (entityEntry.State.ToString() == "Modified")
{
if (type.GetProperty("InsertedBy") != null)
{
entityEntry.Property("InsertedBy").IsModified = false;
}
if (type.GetProperty("InsertedDate") != null)
{
entityEntry.Property("InsertedDate").IsModified = false;
}
if (type.GetProperty("UpdatedBy") != null)
{
entityEntry.Property("UpdatedBy").CurrentValue = User;
}
if (type.GetProperty("UpdatedDate") != null)
{
entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now;
}
}
}
}
the question is:
is using reflection within each entity before modified or added waste in memory and performance ? if yes is there is best practice for this ?
is this another code snippet better in performance or just use reflection also?
public static void ApplyAudit(DbEntityEntry entityEntry, long User)
{
if (entityEntry.State.ToString() == "Added")
{
entityEntry.Property("InsertedBy").CurrentValue = User;
entityEntry.Property("InsertedDate").CurrentValue = DateTime.Now;
}
else if (entityEntry.State.ToString() == "Modified")
{
entityEntry.Property("InsertedBy").IsModified = false;
entityEntry.Property("InsertedDate").IsModified = false;
entityEntry.Property("UpdatedBy").CurrentValue = User;
entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now;
}
}
is entityEntry.Property("InsertedBy") uses reflection ?
Reflection is slow (slow is subjective) and if you want to avoid it, then you need to get rid of such code as below:
Type type = entityEntry.Entity.GetType();
if (type.GetProperty("InsertedBy") != null)
Even if it was not slow, the code above is still "buggy" because a programmer may mistakenly write InsertBy instead of InsertedBy. This can easily be avoided with help from the compiler using the approach below.
Use an interface and implement it in all entities that require audit.
public interface IAuditable
{
string InsertedBy { get; set; }
// ... other properties
}
public class SomeEntity : IAuditable
{
public string InsertedBy { get; set; }
}
public class Auditor<TAuditable> where TAuditable : IAuditable
{
public void ApplyAudit(TAuditable entity, int userId)
{
// No reflection and you get compiler support
if (entity.InsertedBy == null)
{
// whatever
}
else
{
// whatever
}
}
}
As mentioned in the comments, you will get compiler support and reflection is not used anymore. I would even go a step further and not pass the int userId. I will bring the code for figuring out the userId and put it in this class. That way the class is self sufficient and clients do not need to provide it this information.
Usage:
var e = new SomeEntity();
var auditor = new Auditor<SomeEntity>();
auditor.ApplyAudit(e, 1); // 1 is userId, I am just hardcoding for brevity
Or use it from your context:
public override int SaveChanges()
{
var auditables = ChangeTracker.Entries().Where(e =>
e.State == System.Data.Entity.EntityState.Added || e.State == System.Data.Entity.EntityState.Modified)
.OfType<IAuditable>();
var auditor = new Auditor<IAuditable>();
foreach (var entry in auditables)
{
// 1 is userId, I am just hardcoding for brevity
auditor.ApplyAudit(entry, 1);
}
return base.SaveChanges();
}
This means that all entities who are auditable will need to implement the IAuditable interface. EF generates partial classes for your entities but do not modify those partial classes because the next time you run the custom tool, it will be wiped out.
Instead, create another partial class with the same name and implement the IAuditable.
public partial class SomeEntity : IAuditable {}
An even better approach is to create a custom T4 template so it creates the partial class with the code : IAuditable. Please see this article for how to do that.

Update a specific column of a List <T>

First they forgive me for my English since it is not my native language.
I have a method which receives a generic list List<T>. what I want is to foreach through the whole list and be able to update a column called Eliminated of each class T and which is of the boolean type, is it possible to do? can anybody help me.
This is what I have so far:
// Delete (Change status delete = true)
public void Delete<T>(List<T> data)
{
if (data != null)
{
data.ForEach(x =>
{
...
});
}
}
Thanks in advance!
Instead of T i would use an interface, because otherwise in the foreach you cannot access the property Eliminated.
Here the interface:
interface IExample {
bool IsEliminated { get; set; }
}
and here the method with the ForEach loop.
public void Delete<T>(List<T> data) where T : IExample
{
if (data != null)
{
data.ForEach(x =>
{
x.Eliminated = true;
});
}
}
If you want a generic method to update a list of any type, you could do something like this:
public void Update<T>(List<T> data, Action<T> func)
{
if (data == null)
throw new ArgumentNullException(nameof(data));
data.ForEach(func);
}
Note I've change the null check to throw if you pass in a null list. You could just return here instead, this way eliminates some nesting.
This allows you to pass in an action that you apply to every item in a collection. You would use it like this:
var data = new List<YourClass> = GetData();
Update(data, item => item.Eliminated = true);
Your T has no property called Eliminated. Your compiler cannot guarantee that any T you will ever use with this method will have that member, so you are not allowed to compile it that way.
You could put a constraint on your T that allows the compiler to make sure the property exists:
public interface Eliminatable
{
bool Eliminated { get; set; }
}
public void Delete<T>(List<T> data) where T : Eliminatable
{
if (data != null)
{
data.ForEach(x => { x.Eliminated = true; });
}
}
Or (and some may say this is a hack) you can just trust your users that they will in fact pass something as T that confirms to your pattern:
public void Delete<T>(List<T> data)
{
if (data != null)
{
data.ForEach(x => { dynamic d = x; d.Eliminated = true; });
}
}
Now this will fail if the property is not there. At runtime. Not nice. But it "works".

ValidateProperties code

Hi all i am using mvvmcross and portable class libraries , so i cannot use prism or componentmodel data annotations, to validate my classes. basically i have a modelbase that all my models inherit from.
My validate code below is horribly broken, basically im looking for the code that data annotations uses to iterate thru all the properties on my class that is inheriting the base class ,
i have written various attributes that are there own validators inheriting from "validatorBase" which inherits from attribute. i just cannot for the life of me figure out thecode that says ... ok im a class im going to go through all the properties in me that have an attribute of type ValidatorBase and run the validator. my code for these are at the bottom
public class ModelBase
{
private Dictionary<string, IEnumerable<string>> _errors;
public Dictionary<string, IEnumerable<string>> Errors
{
get
{
return _errors;
}
}
protected virtual bool Validate()
{
var propertiesWithChangedErrors = new List<string>();
// Get all the properties decorated with the ValidationAttribute attribute.
var propertiesToValidate = this.GetType().GetRuntimeProperties()
.Where(c => c.GetCustomAttributes(typeof(ValidatorBase)).Any());
foreach (PropertyInfo propertyInfo in propertiesToValidate)
{
var propertyErrors = new List<string>();
TryValidateProperty(propertyInfo, propertyErrors);
// If the errors have changed, save the property name to notify the update at the end of this method.
bool errorsChanged = SetPropertyErrors(propertyInfo.Name, propertyErrors);
if (errorsChanged && !propertiesWithChangedErrors.Contains(propertyInfo.Name))
{
propertiesWithChangedErrors.Add(propertyInfo.Name);
}
}
// Notify each property whose set of errors has changed since the last validation.
foreach (string propertyName in propertiesWithChangedErrors)
{
OnErrorsChanged(propertyName);
OnPropertyChanged(string.Format(CultureInfo.CurrentCulture, "Item[{0}]", propertyName));
}
return _errors.Values.Count == 0;
}
}
here is my validator
public class BooleanRequired : ValidatorBase
{
public override bool Validate(object value)
{
bool retVal = true;
retVal = value != null && (bool)value == true;
var t = this.ErrorMessage;
if (!retVal)
{
ErrorMessage = "Accept is Required";
}
return retVal;
}
}
and here is an example of its usage
[Required(ErrorMessage = "Please enter the Amount")]
public decimal Amount
{
get { return _amount; }
set { _amount = value; }//SetProperty(ref _amount, value); }
}

Check Glass Mapper V3 types at runtime

Using Glass Mapper V3, is it possible to check whether a Sitecore item supports a specific Glass Mapper class/interface?
Given these classes
[SitecoreType]
public partial interface IPage : IGlassBase
{
// ... some properties here ...
}
[SitecoreType]
public partial interface IRateableItem : IGlassBase
{
// ... some properties here ...
}
I would like to do something like this
var context = SitecoreContext();
var item = context.GetCurrentItem<IRateableItem>();
if (item != null)
// it's an item that is composed of the Rateable Item template
Unfortunately, if I do that, I do get an item of the type IRateableItem returned, regardless of whether the current item is composed of that template or not.
Dan
Another solution would be to create a custom task that runs in the ObjectConstruction pipeline.
Something like this:
public class LimitByTemplateTask : IObjectConstructionTask
{
private static readonly Type _templateCheck = typeof (ITemplateCheck);
public void Execute(ObjectConstructionArgs args)
{
if (args.Result != null)
return;
if ( _templateCheck.IsAssignableFrom(args.AbstractTypeCreationContext.RequestedType))
{
var scContext = args.AbstractTypeCreationContext as SitecoreTypeCreationContext;
var config = args.Configuration as SitecoreTypeConfiguration;
var template = scContext.SitecoreService.Database.GetTemplate(scContext.Item.TemplateID);
//check to see if any base template matched the template for the requested type
if (template.BaseTemplates.All(x => x.ID != config.TemplateId) && scContext.Item.TemplateID != config.TemplateId)
{
args.AbortPipeline();
}
}
}
}
public interface ITemplateCheck{}
You would then change you IRateableItem inteface to have the template ID it needs to match and inherit from ITemplateCheck:
[SitecoreType(TemplateId = "CF9B175D-872E-439A-B358-37A01155EEB1")]
public interface IRateableItem: ITemplateCheck, IGlassBase{}
Finally you will need to register the new task with the Castle IOC container in GlassMapperScCustom:
public static void CastleConfig(IWindsorContainer container){
var config = new Config();
container.Register(
Component.For<IObjectConstructionTask>().ImplementedBy<LimitByTemplateTask>(),
);
container.Install(new SitecoreInstaller(config));
}
I haven't had a chance to test this so let me know if there are any problems.
I used this code to determine if an Item could be loaded as a Glass model of a certain type.
I've used your IRateableItem type as an example:
public void Main()
{
var item = Sitecore.Context.Item;
if (item.TemplateID.Equals(GetSitecoreTypeTemplateId<IRateableItem>()))
{
// item is of the IRateableItem type
}
}
private ID GetSitecoreTypeTemplateId<T>() where T : class
{
// Get the GlassMapper context
var context = GetGlassContext();
// Retrieve the SitecoreTypeConfiguration for type T
var sitecoreClass = context[typeof(T)] as SitecoreTypeConfiguration;
return sitecoreClass.TemplateId;
}
private SitecoreService GetSitecoreService()
{
return new SitecoreService(global::Sitecore.Context.Database);
}
private Glass.Mapper.Context GetGlassContext()
{
return GetSitecoreService().GlassContext;
}
EDIT:
Add this extension method so that you can determine whether a Template inherits from a certain base template.
public static bool InheritsFrom(this TemplateItem templateItem, ID templateId)
{
if (templateItem.ID == templateId)
{
return true;
}
foreach (var template in templateItem.BaseTemplates)
{
if (template.ID == templateId)
{
return true;
}
if (template.InheritsFrom(templateId))
{
return true;
}
}
return false;
}
So now you can do this:
if (item.Template.InheritsFrom(GetSitecoreTypeTemplateId<IRateableItem>()))
{
// item is of type IRateableItem
}
I haven't found a solution to the null-check as well. But what you can do is this:
First add the TemplateId to the SitecoreType attribute for both your models:
[SitecoreType(TemplateId = "{your-template-id}")]
Then in your code use the GetCurrentItem<>() method with the InferType=true parameter:
var context = SitecoreContext();
var item = context.GetCurrentItem<IGlassBase>(InferType: true);
if (item is IRateableItem)
{
var rateableItem = item as IRateableItem;
// do more...
}
By adding the TemplateID and using the InferType:true parameter, Glass will try to map the item to a better object then IGlassBase, based on the TemplateID.
If there is a nicer solution to solve this, I'm interested as well.

Categories

Resources