I am using a custom model binder to bind my complex type.
Here's the model:
[ModelBinder(typeof(SupplierModelBinder))]
public class SupplierModel
{
public string VendorId { get; set; }
public string VendorName { get; set; }
public override string ToString()
{
return VendorId;
}
}
Here's the binder:
public class SupplierModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
string key = bindingContext.ModelName;
ValueProviderResult val = bindingContext.ValueProvider.GetValue(key);
if (val != null)
{
string s = val.AttemptedValue as string;
if (s != null)
{
return new SupplierModel() { VendorId = s };
}
}
return null;
}
}
I'm rendering the model using Html.ActionLink calls the UserModel's ToString method. When that GETs to the server it uses the result of that to bind the model.
This works great (I'm not bothered about the VendorName property here), however it does rely on overriding ToString in the model class so I can use that value in the model binder.
How can I separate the binding/unbinding of my complex type from the ToString method?
I don't want to have to override ToString just so my model gets rendered correctly for my binder to interpret. For other types I'll have to (de)serialise to JSON or simiar, which I don't want to be in ToString.
I've managed to figure out how to do this without using a custom binding. The trick is realising that if I have things like Supplier.VendorId="XXXX" as part of the query string of the action link that gets rendered then it gets mapped correctly.
I used reflection to see what HtmlHelper.ActionLink() does when it's passed an object, which is that it creates an instance of RouteValueDictionary<string, object>, which creates a key for each property of the object.
This default implementation is close to what I want, but I need it to deal with properties of properties.
Luckly there's an overload of ActionLink() that takes a RouteValueDictionary<string, object> directly, so that left me with the problem of constructing one with the Property.SubProperty type keys correctly.
I ended up with the code below, which firstly uses the RouteValueDictonary's constructor to get a key for each property.
Then it removes any that won't get bound according to the Bind attribute (if the class has one), which tidies up the resulting querystring quite a bit.
The main part it does though is to look for any properties of type ...Model (the type name ending with "Model") and add that object's properties to the dictionary. I needed to use some rule for whether to recurse or not otherwise it would try and walk the properties of things like lists of objects etc. I figure that I'm already using a convention for my model classes, so I could stick to it.
public static RouteValueDictionary ToRouteValueDictionary(this object obj)
{
var Result = new RouteValueDictionary(obj);
// Find any ignored properties
var BindAttribute = (BindAttribute)obj.GetType().GetCustomAttributes(typeof(BindAttribute), true).SingleOrDefault();
var ExcludedProperties = new List<string>();
if (BindAttribute != null)
{
ExcludedProperties.AddRange(BindAttribute.Exclude.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
}
// Remove any ignored properties from the dictionary
foreach (var ExcludedProperty in ExcludedProperties)
{
Result.Remove(ExcludedProperty);
}
// Loop through each property, recursively adding sub-properties that end with "Model" to the dictionary
foreach (var Property in obj.GetType().GetProperties())
{
if (ExcludedProperties.Contains(Property.Name))
{
continue;
}
if (Property.PropertyType.Name.EndsWith("Model"))
{
Result.Remove(Property.Name);
var PropertyValue = Property.GetValue(obj, null);
if (PropertyValue != null)
{
var PropertyDictionary = PropertyValue.ToRouteValueDictionary();
foreach (var Key in PropertyDictionary.Keys)
{
Result.Add(string.Format("{0}.{1}", Property.Name, Key), PropertyDictionary[Key]);
}
}
}
}
return Result;
}
Related
I created some custom attribute to apply it on a class member:
[
System.AttributeUsage(
AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true
)
]
internal class ActionAttribute : Attribute
{
private Action action;
public ActionAttribute(Action action)
{
this.action = action;
}
public Action getThis()
{
return this.action;
}
}
But am struggling on how to retrieve it's value using reflection.
This is my attempt:
public static Device Serialize(string deviceName, Dictionary<string, dynamic> fields)
{
var itce = devices[deviceName];
Type objectType = itce.GetType();
MemberInfo[] fieldsInfo = objectType.GetMembers();
foreach (var field in fieldsInfo.Where(p => p.MemberType == MemberTypes.Property))
{
Console.WriteLine(field.Name);
object[] actionAttributes = field.GetCustomAttributes(typeof(ActionAttribute), false);
foreach (var cAttr in actionAttributes)
{
Console.WriteLine("Attrs: " + cAttr.GetType());
}
}
return itce;
}
Where in the variable itce I just retrieve a previously allocated instance of a type that contains those attributes using a factory pattern.
The thing I want it's is actual value, but I only can read it's class definition full name. It's obvious, I am asking for it to the GetType() method, but I have only four opts available like ToString() and things like that. I am imagine that I am missing some type cast probably? Don't know. Hope someone could help me with this.
BTW, Action type it's just an enum:
enum Action
{
Read,
Write
}
and, a simple example of the usage of the attributes:
public class Device : Display
{
[Action(Action.Read)]
[Action(Action.Write)]
public string device_name { get; set; }
public Device(string device_name)
{
this.device_name = device_name;
}
}
So, the idea, it's to retrieve the value of the attribute whenever a type has a field annotated. Above, Device has two annotations, Read and Write. I want to recover with reflection the actual value or values attached to that field.
device_name has two attributes, so I need to recover Action.Read and Action.Write.
Thanks.
As #freakish pointed, solution it's pretty straightfoward.
object[] actionAttributes = field.GetCustomAttributes(typeof(ActionAttribute), false);
foreach (var cAttr in actionAttributes)
{
var attr = (ActionAttribute)cAttr;
Console.WriteLine("Attrs: " + attr.getThis());
}
Casting the var cAttr to the attribute type allows me to easily access the info that holds the field attribute.
Instead of using var for your loop control variable and it being object, specify the type as your attribute:
object[] actionAttributes = field.GetCustomAttributes(typeof(ActionAttribute), false);
foreach (ActionAttribute cAttr in actionAttributes)
{
Console.WriteLine("Attrs: " + cAttr.getThis());
}
Of course, you really ought to be using a public read-only property, rather than a private field and a method, to access that value.
I want to create a class with fixed properties and the capability to extend them as dynamic or ExpandoObject can.
e.g:
public class DynamicInstance : DynamicObject
{
public string FixedTestProperty { get; set; }
}
Usage:
DynamicInstance myCustomObj = new DynamicInstance();
myCustomObj.FixedTestProperty = "FixedTestValue";
myCustomObj.DynamicCreatedTestProperty = "Custom dynamic property value";
Finally if I serialize that class with json.net or something else output something like that:
{
FixedTestProperty: 'FixedTestValue',
DynamicCreatedTestProperty: 'Custom dynamic property value'
}
You need to inherit DynamicObject and override the TryGetMember and TrySetMember methods. Here is a class which has one property named One. However, you can add more to it dynamically.
public class ExpandOrNot : DynamicObject
{
public string One { get; set; }
// The inner dictionary.
Dictionary<string, object> dictionary
= new Dictionary<string, object>();
// This property returns the number of elements
// in the inner dictionary.
public int Count
{
get
{
return dictionary.Count;
}
}
// If you try to get a value of a property
// not defined in the class, this method is called.
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
string name = binder.Name.ToLower();
// If the property name is found in a dictionary,
// set the result parameter to the property value and return true.
// Otherwise, return false.
return dictionary.TryGetValue(name, out result);
}
// If you try to set a value of a property that is
// not defined in the class, this method is called.
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
dictionary[binder.Name.ToLower()] = value;
// You can always add a value to a dictionary,
// so this method always returns true.
return true;
}
}
Usage
dynamic exp = new ExpandOrNot { One = "1" };
exp.Two = "2";
More info here.
<== Fiddle Me ==>
This is possible using TrySetMember on DynamicObject.
The example at the bottom of this shows how to do it: https://msdn.microsoft.com/en-us/library/system.dynamic.dynamicobject.trysetmember(v=vs.110).aspx
I mistakenly posted this question already at the SharePoint part.
I need to map one model onto an other. Everything works well but the last property throws a TargetParameterCountException. The property which throws the exception is called "Item" this property is not defined by me, I assume that this is a property from the dictionary.
I already tried to use all five parameters instead of only one (as described here Moq + Unit Testing - System.Reflection.TargetParameterCountException: Parameter count mismatch) but unfortunately i will get the same exception. I would really appreciate it if someone could help me.
Kinde Regards and Thanks
Sandro
That is a excerpt of the Source Model, all other properties are implemented in exactly the same way:
public class DataModel : Dictionary<string, object> {}
public class DiscussionDataModel : DataModel
{
public DiscussionDataModel(Dictionary dictionary) : base(dictionary){}
public FieldUserValue Author
{
get { return (FieldUserValue) this["Author"]; }
set { this["Author"] = value; }
}
public double AverageRating
{
get { return (double) this["AverageRating"]; }
set { this["AverageRating"] = value; }
}
}
And that is a excerpt the target Model, all other properties are implemented in exactly the same way:
public class DiscussionModel : BaseModel
{
public FieldUserValue Author { get; set; }
public double AverageRating { get; set; }
}
And this is the generic extension method to map the DataModel onto the BaseModel:
public static T ToModel(this DataModel dataModel) where T : BaseModel
{
try
{
T model = Activator.CreateInstance();
if (dataModel != null)
{
PropertyInfo[] propertyInfos = dataModel.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo propertyInfo in propertyInfos)
{
object value = propertyInfo.GetValue(dataModel);
if (value == null) { break; }
PropertyInfo modelPropertyInfo = model.GetType().GetProperty(propertyInfo.Name);
modelPropertyInfo?.SetValue(model, value);
}
return model;
}
}
catch (Exception ex)
{
throw;
}
return null;
}
The problem is that the Item property is indexed, i.e. it has a parameter. C# normally does not allow this, but other .NET languages such as VB.NET do. Thus, this concept is known to the CLR and thus also to Reflection. In C#, there is only one way to create an indexed property, namely through an indexer. What this does at a CLR-level is to create an indexed propery called Item, so you might have just stumbled across an indexer.
So the solution is to check the property info whether it has parameters and continue the for loop if this is the case. There is no chance for you to know generically what objects to pass into an indexed property.
I have an attribute for validation called Required() : BaseAttribute, and I can track it using the following code: (The BaseAttribute just implements the IsValid() Method.)
public static String Validate(object Object)
{
StringBuilder builder = new StringBuilder();
if (Object != null)
{
Type ObjectType = Object.GetType();
PropertyInfo[] Properties = ObjectType.GetProperties();
foreach (PropertyInfo Property in Properties)
{
object[] Attributes = Property.GetCustomAttributes(typeof(BaseAttribute), true);
foreach (object Attribute in Attributes)
builder.AppendLine(((BaseAttribute)Attribute).IsValid(Property, Object));
}
}
return builder.ToString();
}
The problem is, this works:
class roh {
[Required()]
public string dah { get; set; }
}
class main {
Console.WriteLine(Validate(new roh()));
}
but this doesn't:
class fus {
private roh _roh
public roh Roh {
get { if (_roh == null)
_roh = new roh;
return _roh; }
set { _roh = value; }
}
}
class roh {
[Required()]
public string Dah { get; set; }
}
class main {
Console.WriteLine(Validate(new fus()));
}
How can I modify my Validate method so that it can recursively find the custom attributes no matter how deep the object is?
You could use the Microsoft Enterprise Library. It has some build in validation blocks (similar in style to what you're using here) and does support recursive object validation down the object graph, I believe.
You reference the EntLib Validation DLL, and can either use the built-in validation or write your own. You can then validate it using a simple Validation.Validate(myObject) call.
Hope that might help :)
You already said the magic word - recursion. For every property you visit, call Validate on the object it stores, and voila.
One caveat is infinite recursion - this will work OK if your object graph is a tree. If it's more complex, you'll need to track which objects you have already visited.
I have an IIdentifiable interface:
public interface IIdentifiable
{
int Id { get; set; }
}
And a simple class Foo:
public class Foo : IIdentifiable
{
public int Id { get; set; }
public string Name { get; set; }
}
When I have a page that needs to add a set of Foo and the specify one as the default, I'd have a view model like this:
public class BarViewModel
{
public IList<Foo> Foos { get; set; }
public Foo DefaultFoo { get; set; }
}
So in reality I'd like to only pass the Ids around and not the actual full objects inside hidden inputs (that's just nasty and not required). All bar cares about (int the database at least) is between a Bar and it's Foo.Ids and the default Foo.Id.
I was hoping to easily add a model binder that would be able to accept all IIdentifiables and then set the Id if only an int is set for the value provider. The problem that I ran into is that I can't do something like the following and have it set the Id (since model binders don't look at the derived type chain...ugh):
ModelBinders.Binders[typeof(IIdentifiable)] = new IdentifiableModelBinder();
So I decided to extend the DefaultModelProvider to allow this capability for if the type is an IIdentifiable and the value found in the value provider is just a string/int, then create the model and set the Id property to the matching value:
public class DefaultWithIdentifiableModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var modelType = bindingContext.ModelType;
bool isList = false;
// Determine the real type of the array or another generic type.
if (modelType.IsArray)
{
modelType = modelType.GetElementType();
isList = true;
}
else if (modelType.IsGenericType)
{
var genericType = modelType.GetGenericTypeDefinition();
if (genericType == typeof(IEnumerable<>) || genericType == typeof(IList<>) || genericType == typeof(ICollection<>))
{
modelType = modelType.GetGenericArguments()[0];
isList = true;
}
}
// The real model type isn't identifiable so use the default binder.
if (!typeof(IIdentifiable).IsAssignableFrom(modelType))
{
return base.BindModel(controllerContext, bindingContext);
}
// Get the value provider for the model name.
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
// Get the string values from the value provider.
var stringValues = valueProviderResult != null ? (IEnumerable<string>)valueProviderResult.RawValue : Enumerable.Empty<string>();
int tempFirstId = -1;
// If the first element is an integer, we assume that only the Ids are supplied
// and therefore we parse the list.
// Otherwise, use the default binder.
if (stringValues.Any() && int.TryParse(stringValues.First(), out tempFirstId))
{
var listType = typeof(List<>).MakeGenericType(new Type[] { modelType });
var items = (IList)base.CreateModel(controllerContext, bindingContext, listType);
// Create each identifiable object and set the Id.
foreach (var id in stringValues)
{
var item = (IIdentifiable)Activator.CreateInstance(modelType);
item.Id = int.Parse(id);
items.Add(item);
}
if (items.Count == 0)
{
return null;
}
// Determine the correct result to return.
if (bindingContext.ModelType.IsArray)
{
var array = Array.CreateInstance(modelType, items.Count);
items.CopyTo(array, 0);
return array;
}
else if (isList)
{
return items;
}
else
{
return items[0];
}
}
else
{
return base.BindModel(controllerContext, bindingContext);
}
}
}
I'm just not sure if this is really necessary for what I'm trying to do. If anyone could leave feedback/suggestions/improvements on this type of model binding it would be greatly appreciated.
EDIT: Here's a simple example:
An order has many items, so instead of loading the whole object graph of the Item, only the Id is really required for use with the ORM and in the database. Therefore, the item class is loaded with the Id and then the item can be added to the list of items for the order:
public class Order
{
List<Item> Items { get; set; }
}
var order = new Order();
order.Items.Add(new Item() { Id=2 });
order.Items.Add(new Item() { Id=5 });
This is because the postback to complete the order doesn't send the whole Item, it just sends the Ids.
This is my core requirement. When a postback occurs, I need to build the Items from an Id from the postback. Regardless of if this is a view model or the actual domain model, I still need a way of easily converting from ints to the domain model with the Id set. Make sense?
I have experienced similar problem as following solution.
Firstly, create your interface. It's name is IInterface in following example.
After that, create your CustomBinderProvider like below;
public class IInterfaceBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType.GetInterface(nameof(IInterface)) != null)
{
return new BinderTypeModelBinder(typeof(IInterface));
}
return null;
}
}
Now, you can implement your binder as follows;
public Task BindModelAsync(ModelBindingContext bindingContext)
{
// Do something
var model = (IInterface)Activator.CreateInstance(bindingContext.ModelType);
// Do something
bindingContext.Model = model;
bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
return Task.FromResult(model);
}
Finally, you have to insert your provider to Startup class as follows;
services.AddMvcCore(x =>
{
x.ModelBinderProviders.Insert(0, new IInterfaceBinderProvider());
})
This seems like a decent way but not very extensible. If you're unlikely to have this same situation arise with different interfaces, then it's a fine solution.
An alternative would be to use reflection at startup to find all types that implement IIdentifiable and assign the custom model binder for all of them.
Well, lets suppose you need Foo and its data somewhere. Therefore you save the Name for it (maybe along with other properties) and retrieve all that data from persistence as a Foo instance and this is exactly what your domain entity for. It contains all the data related to Foo.
On the other side you have a View where the only thing you want operate with is Id so you create the ViewModel which contains the Id as property (and maybe more Ids like ParentFooId for example) and this is what your ViewModel for. It contains only data specific to your View - its like an interface between your View and Controller.
That way everything is done with DefaultModelBinder. For example, if you have:
an id (of type int) parameter in your RouteData dictionary or have it posted via form;
BarViewModel instance as a parameter of your controller's Action;
Id property of BarViewModel
then on request the value of that barViewModel.Id property will be the value from your RouteData (or your form) because DefaultModelBinder is capable of that. And you only create custom IModelBinder for really unusual scenario.
I just don't see a reason to overcomplicate things.
Makes sense?