简体   繁体   中英

Reflection set value of a properties property

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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM