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.
Related
I have 2 errors because of my little knowledge of PostSharp (last version). My project's three aspect classes, but I only get 2 errors. When the getall method runs in my mvc project, I want log information to be generated in my database and C:/Log/Log.txt. But no logs are created. Here is what I want from you. No matter how I write the code block, my problem solves? I have some validation and Transaction processes, but I don't think it has anything to do with the error I'm getting, so there's no need for details. Firstly, I've gotten the following warning.
enter image description here
To solve this, I followed the procedure below. LogAspect and FluentValidationAspect classes have been giving error.
[LogAspect(AspectPriority = 1), FluentValidationAspect(AspectPriority = 2), TransactionScopeAspect(AspectPriority = 3)]
And again, I got the following error. (CS 7036)
enter image description here
I want to do some operations in my BookManager class. (Log, Validation, Transaction). Here are the codes;
[LogAspect(AspectPriority = 1), FluentValidationAspect(AspectPriority = 2), TransactionScopeAspect(AspectPriority = 3)]
public class BookManager : IBookService
{
private IBookDal _bookDal;
private IPersonDal _personDal;
/* private readonly IQueryableRepository<Book> _queryable; */
public BookManager(IBookDal bookDal, IPersonDal personDal/*IQueryableRepository<Book> queryable */)
{
_personDal = personDal;
/*_queryable = queryable; */
_bookDal = bookDal;
}
[FluentValidationAspect(typeof(BookValidator))]
public Book Add(Book book)
{
return _bookDal.Add(book);
}
public void Delete(Book book)
{
_bookDal.Delete(book);
}
[LogAspect(typeof(DatabaseLogger))]
public List<Book> GetAll()
{
return _bookDal.GetList();
}
[TransactionScopeAspect]
public void TransactionalOperation(Person person, Book book)
{
_personDal.Delete(person);
// Business Codes
_bookDal.Add(book);
}
public Book GetById(int bookId)
{
return _bookDal.Get(p=>p.BookId==bookId);
}
[FluentValidationAspect(typeof(BookValidator))]
public Book Update(Book book)
{
return _bookDal.Update(book);
}
}
[Serializable]
[MulticastAttributeUsage(MulticastTargets.Method, TargetMemberAttributes = MulticastAttributes.Instance)]
public class LogAspect : OnMethodBoundaryAspect
{
private Type _loggerType;
[NonSerialized]
private LoggerService _loggerService;
public LogAspect(Type loggerType)
{
_loggerType = loggerType;
}
public override void RuntimeInitialize(MethodBase method)
{
if (_loggerType.BaseType != typeof(LoggerService))
{
throw new Exception("Wrong logger type.");
}
_loggerService = (LoggerService)Activator.CreateInstance(_loggerType);
base.RuntimeInitialize(method);
}
public override void OnEntry(MethodExecutionArgs args)
{
if (!_loggerService.IsInfoEnabled)
{
return;
}
try
{
var logParameters = args.Method.GetParameters().Select((t, i) => new LogParameter
{
Name = t.Name,
Type = t.ParameterType.Name,
Value = args.Arguments.GetArgument(i)
}).ToList();
var logDetail = new LogDetail
{
FullName = args.Method.DeclaringType == null ? null : args.Method.DeclaringType.Name,
MethodName = args.Method.Name,
Parameters = logParameters
};
_loggerService.Info(logDetail);
}
catch (Exception)
{
}
}
}
[Serializable]
public class FluentValidationAspect : OnMethodBoundaryAspect
{
Type _validatorType;
public FluentValidationAspect(Type validatorType)
{
_validatorType = validatorType;
}
public override void OnEntry(MethodExecutionArgs args)
{
var validator = (IValidator)Activator.CreateInstance(_validatorType);
var entityType = _validatorType.BaseType.GetGenericArguments()[0];
var entities = args.Arguments.Where(t => t.GetType() == entityType);
foreach (var entity in entities)
{
ValidatorTool.FluentValidate(validator, entity);
}
}
}
I want to tell you something that you have to consider. Also, I did assembly level logging. This is the code.
[assembly: LogAspect(typeof(JsonFileLogger), AttributeTargetTypes = "ELibrary.Library.Business.Managers.BookManager*")]
Finally I want to add, can you explain the solution of this error by rewriting the wrong block?
Ordering aspects is documented here. You are getting the warning because PostSharp does not have information on the ordering of some transformations. In your case I'd add the following attribute on LogAspect:
[AspectTypeDependency(AspectDependencyAction.Order, AspectDependencyPosition.Before, typeof(FluentValidationAspect))]
And then on FluentValidationAspect:
[AspectTypeDependency(AspectDependencyAction.Order, AspectDependencyPosition.Before, typeof(TransactionScopeAspect))]
This should totally order those three aspects and you should get rid of warnings.
Afterwards, you were getting C# errors because your aspect constructors simply have a parameter that you did not specify. AspectPriority needs to be specified on individual attributes.
I currently have a multi one-to-many relationship hierarchy database tblProjects->tblLines->tblGroups->tblStations etc. And an Entity framework 6 model.
These entity framework classes all implement a base class "tblBase":
public abstract class TblBase : INotifyPropertyChanged
{
private int _id;
public int ID
{
get
{
return _id;
}
set
{
_id = value;
NotifyPropertyChanged();
}
}
private Nullable<int> _coid;
public Nullable<int> COID
{
get
{
NotifyPropertyChanged();
return _coid;
}
set
{
_coid = value;
NotifyPropertyChanged();
}
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
NotifyPropertyChanged();
}
}
I have a treeview that allows me to select any node as the parent type, and currently I have a method for each type that allows me to reload all the child entities.
I would like to see how this could be made generic:
private async static Task<bool> RefreshLinesAsync(LocalUser ThisUser, ProjectEntities DBContext, object Entity)
{
List<object> NonExistingNodes = new List<object>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
//Scan through all DB child entities and reload their DB values
foreach (var SubEntity in DBContext.tblLines.Where(x => x.ProjectID == PassedEntity.ID).ToList())
{
await DBContext.Entry(SubEntity).ReloadAsync().ContinueWith(x =>
{
if (!x.IsFaulted)
{
if ((SubEntity.COID.GetValueOrDefault() != 0) && (SubEntity.COID.GetValueOrDefault() != ThisUser.ID))
NotifyCOIDConflict(SubEntity, new CheckedOutArgs()
{
ConflictCOID = SubEntity.COID.GetValueOrDefault()
});
bContinue = true;
}
}, TaskScheduler.FromCurrentSynchronizationContext());
if (bContinue)
//Continue to child entities method
await RefreshGroupsAsync(ThisUser, DBContext, SubEntity);
}
return true;
}
private async static Task<bool> RefreshGroupsAsync(LocalUser ThisUser, ProjectEntities DBContext, object Entity)
{
List<object> NonExistingNodes = new List<object>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
foreach (var SubEntity in DBContext.tblGroups.Where(x => x.LineID == PassedEntity.ID).ToList())
{
await DBContext.Entry(SubEntity).ReloadAsync().ContinueWith(x =>
{
if (!x.IsFaulted)
{
if ((SubEntity.COID.GetValueOrDefault() != 0) && (SubEntity.COID.GetValueOrDefault() != ThisUser.ID))
NotifyCOIDConflict(SubEntity, new CheckedOutArgs()
{
ConflictCOID = SubEntity.COID.GetValueOrDefault()
});
bContinue = true;
}
}, TaskScheduler.FromCurrentSynchronizationContext());
if (bContinue)
await RefreshStationsAsync(ThisUser,DBContext, SubEntity);
}
return true;
}
The only method I can see useful is Set(), although it does not provide a Where() method, which is critical since I do not want to retrieve the entire table.
You can make your functions generic. They maybe like this one:
private async static Task<bool> RefreshLinesAsync<TEntity>(LocalUser ThisUser, ProjectEntities DBContext, TEntity Entity) where TEntity : TblBase
{
List<TEntity> NonExistingNodes = new List<TEntity>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
foreach (var SubEntity in DBContext.Set<TEntity>().Where(x => (x as TblBase).ProjectID == PassedEntity.ID).ToList()) {
//Your other code here...
}
}
The where clause in function definition, make you sure that this method can be called only with subclasses of TblBase.
EDIT:
I forgot to mention that you need to cast SubEntity as TblBase inside foreach loop to use it...
EDIT (in response of comments):
If you need to get all TblBase subclasses from your entity, you cannot make your function so generic if you keep them in separate tables: It will became hardly mantainable when you have to add more subclasses.
I suggest you to use a single table through Table Per Hierarchy (see this article in MSDN) changing TblBase from abstract to concrete class, then you can get all of them this way:
var allSubClassEntities = DBContext.Set<TblBase>();
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".
I have three columns in the db table that looks as follow:
When I add a new row, it should store the value on column fieldname in uppercase. How can I do that?
Since you tagged the question with entity framework, I assume you want to do it in your data layer or close to DB. There's a number of ways for doing this.
You could override SaveChanges() in your context. This will move the logic away from the model, but still ensure that the correct value is saved. Also, if you want it on several entities you can use an interface. When it's an interface you can do it for several of your entities without any duplicate code, as long as it's the same property. Otherwise you would need an attribute and reflection. Reusability is pretty high, but it adds some overhead to your SaveChanges().
public class CustomerEntity()
{
public string Name {get;set;}
}
public MyCustomContext : DbContext
{
// Other stuff...
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<CustomerEntity>())
{
if (entry.State == EntityState.Modified || entry.State == EntityState.Added)
{
// Possibly check for null or if it's changed at all.
entry.Entity.Name = entry.Entity.Name.ToUpper();
}
}
return base.SaveChanges();
}
}
And with an interface:
public interface INameIsAlwaysUpperCase
{
string Name {get;set;}
}
public MyCustomContext : DbContext
{
// Other stuff...
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<INameIsAlwaysUpperCase>())
{
if (entry.State == EntityState.Modified || entry.State == EntityState.Added)
{
// Possibly check for null or if it's changed at all.
entry.Entity.Name = entry.Entity.Name.ToUpper();
}
}
return base.SaveChanges();
}
}
You can add a custom validation. This will throw exception if it's not saved correctly. That way you can move the responsibility to the consumer of the model. However, depending on your scenario, you might not want to throw an exception. This is my favourite since it forces the consumer to do it the right way. As per comments, why throw when you can silently convert it? Yes, it's a valid question. For me it's about forcing the consumer of the data layer to use it correctly, and not let the daya layer decide what to to with the data. I personally don't like it when the business layer asks the data layer to save one thing, and then the data layer saves another thing. If lower case isn't a valid option, then it shouldn't be saved. I don't think it's much more different from using [Required]. But it's really about context and what works in your particular case.
public class CustomerEntity() : IValidatableObject
{
public string Name {get;set;}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// Possibly check for null here as well...
if (this.Name.ToUpper() != this.Name)
{
yield return new ValidationResult("You need to save as upper!");
}
}
}
Use a property that manages this for you. This may be the simplest solution, even if I like to keep my entities "clean". It's absolutely the solution that will require least effort. However, the reusability is low, and what if you use your entitites all over the application and want the value to be lower case until it's actually saved? That's not possible. But, again, I think it comes down to your particular situation. If you want the value to be upper case even before you save it, this is probably the best solution!
public class CustomerEntity()
{
string _name;
public string Name
{
get { return _name; }
set { _name = value.ToUpper(); } // Check for null ?
}
}
Do it when saving. This moves the logic to when you're saving your entity. This is probably the least preferable option, since the reusability is non-existing. What happens in Update()? However, the OP specifically states "When I add a new row", so it may only be applicable when adding new entities. And in that case it could very well be the most prefered choice since it allows updates to have lower case. But it would have to depend on the use case.
public void AddCustomer(string name)
{
var customer = new CustomerEntity
{
Name = name.ToUpper()
};
_context.Customers.Add(customer);
}
Just use properties. If your model is as below:
public class MyModel
{
public int Id { get; set; }
public string Description { get; set; }
public string LanguageCode { get; set; }
public string FiledName { get; set; }
}
Then, change it to:
public class MyModel
{
private string fieldName;
public int Id { get; set; }
public string Description { get; set; }
public string LanguageCode { get; set; }
public string FiledName
{
get { return filedName; }
set
{
if(!string.IsNullOrEmpty(value))
fieldName = value.ToUpper();
else
fieldName = value;
}
}
}
Try this.
public string FiledName
{
get { return filedName; }
set
{
filedName = !string.IsNullOrEmpty( value ) ? value.ToUpper() : value;
}
}
Using a ValueConverter on the Property could be an effective way to do this.
public class YourDbContext : DbContext
{
public YourDbContext(DbContextOptions<YourDbContext> options)
: base(options)
{
}
public DbSet<Row> Rows { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
var converter = new ValueConverter<string, string>(
v => v.ToUpper(), // writing
v => v
);
// just one property
modelBuilder.Entity<Row>()
.Property(x => x.Column)
.HasConversion(converter);
// all of the string properties
foreach (var entityType in builder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType == typeof(string))
{
builder.Entity(entityType.Name)
.Property(property.Name)
.HasConversion(converter);
}
}
}
}
}
It's also possible to use a Custom Attribute :
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class Standardized : Attribute
{}
Then decorate properties inside your model :
public class MyModel
{
public string Id{ get; set; }
[Required]
[Standardized]
public string Description { get; set; }
}
Taken from #smoksnes accepted answer, inside your DbContext class, override SaveChanges(), SaveChangesAsync() (EF Core 5.x) and add a private method using reflection to obtain decorated properties and apply transformations, like this :
public override int SaveChanges()
{
StandardizeBeforeSaving();
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
StandardizeBeforeSaving();
return await base.SaveChangesAsync(cancellationToken);
}
private void StandardizeBeforeSaving()
{
foreach (var entry in ChangeTracker.Entries())
{
if (entry.State == EntityState.Modified || entry.State == EntityState.Added)
{
var properties = entry.Entity
.GetType()
.GetProperties()
.Where(prop => Attribute.IsDefined(prop, typeof(Standardized)) && prop.PropertyType == typeof(string));
foreach (var property in properties)
{
var value = entry.CurrentValues[property.Name]?.ToString() ?? string.Empty;
entry.CurrentValues[property.Name] = value.Standardize();
}
}
}
}
Just be aware that reflection could be slower than other techniques presented in accepted answer. But for most scenarios (ie. user updates or creates couple of entities with not that many properties) it should be fine.
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
}