Reputation: 91
In my model classes I have multiple properties that are 'protected internal set'. Once these properties are created they should not be able to be modified. Having said that I am having a hard time creating a model binder that will allow these properties to be set upon creation. What is the best way to proceed in order to be able to set these properties just once?
Upvotes: 3
Views: 3546
Reputation: 4641
I did this using a custom model binder. Not only do I have private setters, but the argument to my MVC action was an interface, and I had no parameter-less constructors (the trifecta!); none of which work with the default model binder.
In your case, you just have to make a custom model binder for your type. Create a new instance of your class with default values. Then just use Reflection to set the properties on the class. The reflection methods don't care about the property's accessibility. Something like this:
// assuming your class looks like this
public class MyClass
{
public int MyInt { get; private set; }
public string MyString { get; private set; }
public MyClass(int myInt, string myString)
{
MyInt = myInt;
MyString = myString;
}
}
// model binder is like this
public class MyModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext,
ModelBindingContext bindingContext,
Type modelType)
{
// initialize with default values, they will be overwritten
MyClass myClass = new MyClass(default(int), default(string));
// get the reflection info on the int property
PropertyInfo intProperty = typeof(MyClass).GetProperty("MyInt");
// get the int value from the request
int myIntValue = int.Parse(bindingContext.ValueProvider
.GetValue(intProperty.Name).AttemptedValue);
// btw, attempted value is a string
// set the property value, SetValue ignores accessibility modifiers
intProperty.SetValue(myClass, myIntValue, null);
// do the same stuff for MyString property
PropertyInfo stringProperty = typeof(MyClass).GetProperty("MyString");
string myStringValue = bindingContext.ValueProvider
.GetValue(stringProperty.Name).AttemptedValue;
stringProperty.SetValue(myClass, myStringValue, null);
return myClass;
}
}
// Your controller action is like this
public ActionResult MyAction([ModelBinder(typeof(MyModelBinder))]
MyClass myClass)
{
...
}
I suppose you could have just passed the proper values into the constructor without using reflection to set them:
...
int myIntValue = int.Parse(bindingContext.ValueProvider
.GetValue(intProperty.Name).AttemptedValue);
string myStringValue = bindingContext.ValueProvider
.GetValue(stringProperty.Name).AttemptedValue;
MyClass myClass = new MyClass(myIntValue, myStringValue);
...
But in case you need to do more, reflection can allow you to get around the access modifier restrictions (for valid infrastructural reasons of course! :D). In my case, I didn't want to write a model binder for every class that could implement the interface, so I used even more reflection to match the class, find a constructor, pass it the default values, then loop through each property on the class and set it from the request values. I have a separate method that validates the message before running actions based off it.
Upvotes: 1
Reputation: 8474
Can you have you protected set method setting a private field only if it is not already set?
public string PropertyName
{
get
{
return this.privateField;
}
protected set
{
if (this.privateField == null)
{
this.privateField = value;
}
}
}
private string privateField;
Upvotes: 0
Reputation: 93464
Unfortunately, this is not something you can do automatically. You'll have to create a unique model binder for each type, and then create the objects using constructor parameters.
Upvotes: 1