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?
Related
I have an ASP.NET MVC (Not Core) project where I have run into some problems, and I think finally getting around to learning how to properly use generics could be a solution to my problems.
My case is that I have a SQL connection, that returns data and depending on the result of one field in the SQL, I want to use two different models. The models have a lot of properties in common so I thought the best practice would be to create a method that selects which of the models to create, fill in the differences, return the model and then continue to fill in the "common" properties.
I have tried to read a little on Generics but I am quite new to this so I haven't made any big strides.
My code example looks like this:
public ResultVM MainClass()
{
var resultData = new ResultVM();
// ... SQL returns data
while (reader.Read())
{
resultData.Add(reader);
}
return resultData;
}
public object CreateObject(SqlDataReader indata)
{
if((indata["Associate"].ToString()) == "0")
{
var individual = new Individual();
}
else
{
var group = new Group();
}
return object;
}
How can I dynamically (depending on the value of Associate field) create an individual or a group?
I suggest working directly with System.Type in your case. Here can be multiple more elegant solutions of your problem, depending of what you actually need:
indata.GetFieldType(int ordinal) will return the .NET type of your field
Serialize data with type handling, then you can simply get type after non generic deserialization. For example:
var result = JsonConvert.DeserializeObject(dataJson);
result will have Type of your actual object type. You can check it writing result.GetType() and create an object of this type. For more advanced use see Activator.CreateInstance(...)
For the most cases using interface is the best way:
interface ISomething
{
// ...
}
class Individual : ISomething
{
// ...
}
class Group : ISomething
{
// ...
}
Then you cat build your non generic method this way:
public ISomething CreateObject(SqlDataReader indata)
{
if((indata["Associate"].ToString()) == "0")
return new Individual();
else
return new Group();
}
Your generic object creation may look something like this:
public T CreateObject<T>(SqlDataReader indata)
{
var o = new T();
return o;
}
Where T is type, that you pass outside this method: CreateObject<YourType>(indata)
T can be any Type you want including Interfaces. Also working with generics you may want check types by using is keyword, but I recommend using interfaces and avoid is checks at all.
Example:
if(yourObject is YourType)
{
(yourObject as YourType).SomeSpecificToYourTypeMethod();
// ...
}
What about implementing an interface with all the common properties?
Something like
interface IInterface
{
string CommonProperty1 { get; set; }
string CommonProperty2 { get; set; }
}
class Individual : IInterface
{
// ...
}
class Group : IInterface
{
// ...
}
public IInterface CreateObject(SqlDataReader indata)
{
if((indata["Associate"].ToString()) == "0")
{
var individual = new Individual();
// ...
return individual;
}
else
{
var group = new Group();
// ...
return group;
}
}
I'm currently having some issues with a method I made. I use reflection to run through my class and get all it's properties. I use this to cast my models to DTO and vice-versa.
The problem I am encountering is that, whenever my class has another class as an attribute, I get an error.
Object of type 'UserTypeProxy' cannot be converted to type 'MyNamespace.DTO.UserTypeDto'.
This is my code:
public static T Cast<T>(object myobj)
{
Type _objectType = myobj.GetType();
Type target = typeof(T);
var x = Activator.CreateInstance(target, false);
var z = from source in _objectType.GetMembers().ToList()
where source.MemberType == MemberTypes.Property
select source;
var d = from source in target.GetMembers().ToList()
where source.MemberType == MemberTypes.Property
select source;
List<MemberInfo> members = d.Where(memberInfo => d.Select(c => c.Name)
.ToList().Contains(memberInfo.Name)).ToList();
PropertyInfo propertyInfo;
object value;
foreach (var memberInfo in members)
{
propertyInfo = typeof(T).GetProperty(memberInfo.Name);
var propy = myobj.GetType().GetProperty(memberInfo.Name);
value = propy.GetValue(myobj, null);
propertyInfo.SetValue(x, value, null); //<-- this is the line that gives the error
}
return (T)x;
}
As a previous commenter states, this is not the kind of code you should be writing/maintaining yourself. Frameworks like AutoMapper were built specifically to solve the problem you are attacking - converting model objects to DTOs. The right long-term choice would be to start leveraging such a framework instead of reinventing the wheel.
In the meanwhile the following code is a short-term solution for your issue. Keep in mind that while this may solve the specific case you mention in your question, object mapping has many corner cases and eventually you will run into another. I would recommend only using this as a temporary fix until you migrate to using AutoMapper or a similar framework.
Based on your description and your code, here is an example which models your failure:
static void Main(string[] args)
{
var user = new UserModel
{
Name = "User McUserson",
Age = 30,
Buddy = new UserModel
{
Name = "Buddy McFriendly",
Age = 28
}
};
// This fails saying that UserModel cannot be converted to UserDto
var userDto = Cast<UserDto>(user);
}
class UserModel
{
public String Name { get; set; }
public int Age { get; set; }
public UserModel Buddy { get; set; }
}
class UserDto
{
public String Name { get; set; }
public int Age { get; set; }
public UserDto Buddy { get; set; }
}
The problem is that the Buddy property, unlike all the others, has a different type in the model and DTO classes. A UserModel is simply not assignable to a UserDto. The only exception to this is if the value is null.
For properties which are class types, instead of setting the target equal to the source you need to map the source type to the target type: UserModel -> UserDto. This can be done with a recursive call.
Before I show you the code which solves this issue, let's talk about naming for a minute. Calling your function Cast() is very misleading. The operation we are really doing here is taking some source object and mapping its property values onto some target object of a specific type (with possible recursive mappings for properties which are class types).
Given this terminology, here is some updated code which solves this specific issue:
public static T MapProperties<T>(object source)
{
return (T)MapProperties(source, typeof(T));
}
public static object MapProperties(object source, Type targetType)
{
object target = Activator.CreateInstance(targetType, nonPublic: false);
Type sourceType = source.GetType();
var sourcePropertyLookup = sourceType.GetProperties().ToDictionary(p => p.Name);
var targetPropertyLookup = targetType.GetProperties().ToDictionary(p => p.Name);
var commonProperties = targetPropertyLookup.Keys.Intersect(sourcePropertyLookup.Keys);
foreach (var commonProp in commonProperties)
{
PropertyInfo sourceProp = sourcePropertyLookup[commonProp];
PropertyInfo targetProp = targetPropertyLookup[commonProp];
object sourcePropValue = sourceProp.GetValue(source);
if(sourcePropValue == null || targetProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType))
{
targetProp.SetValue(target, sourceProp.GetValue(source));
}
else
{
object mappedValue = MapProperties(sourceProp.GetValue(source), targetProp.PropertyType);
targetProp.SetValue(target, mappedValue);
}
}
return target;
}
You can use this in the same way you've used your previous code:
static void Main(string[] args)
{
var user = new UserModel
{
Name = "User McUserson",
Age = 30,
Buddy = new UserModel
{
Name = "Buddy McFriendly",
Age = 28
}
};
// This works!
var userDto = MapProperties<UserDto>(user);
}
Aside from some optimizations the key differences from your code is in the if-else block. There we check if we can assign the source value to the target directly, in which case we do what your code was doing so far. Otherwise it assumes we need to recursively map the value over. This new section is what solves the issue of converting a source property of a model class type to a target property of a DTO class type.
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 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;
}
I'm currently struggling with finding a better way to populate my ViewModel objects with my Entitiy objects. I have the following Web Api controller method:
[HttpGet]
public IEnumerable<ClientSearchViewModel> FindClients(string query)
{
var clients = _clientService.SearchClient(query).ToList();
var returnClients = new List<ClientSearchViewModel>();
foreach (var client in clients)
{
returnClients.Add(new ClientSearchViewModel(client));
}
return returnClients;
}
And I'm doing this in my ClientSearchViewModel constructor:
public ClientSearchViewModel(Client client)
{
this.Id = client.Id;
this.FirstName = client.PersonName.FirstName;
this.LastName = client.PersonName.LastName;
}
Is there another way other than going through the list of returned objects and creating a new ViewModel list?
I strongly suggest use of a mapping plugin for this, such as:
AutoMapper
or
ValueInjector
Plugins like this will allow you to map between the objects being used internally or in your data layer, with your external objects (DTOs/ViewModels). They handle a number of things out of the box such as automatic mapping of any like named properties with the same type, but also allow for a lot of control in the specific mapping of properties or types, for those times when you need something more custom.
For a brief comparison of the two, there isn't much better than hearing the authors themselves respond: AutoMapper vs ValueInjecter
Personally, I find ValueInjector to be quicker to use, while having more control overall, but I also find it to be much less readable/inuitive than AutoMapper, which can require a bit more code to accomplish similar goals. As such, I'd pick the one that you find you and/or your team will prefer the syntax of and how easily you can grasp the concepts vs how much power you really need.
So I had the same miff... I can't say that I've benchmarked my solution, but it does seem to run reasonably fast...
3 bits:
public static T Transform<T>(this object convertFrom) where T : class, new()
{
return (T) (new ServiceExtension().Transform(convertFrom, typeof (T)));
}
private class ServiceExtension
{
public object Transform(object convertFrom, Type convertTo)
{
object _t = Activator.CreateInstance(convertTo);
if (convertFrom == null) return _t;
var convertType = convertFrom.GetType();
foreach (
var property in _t.GetType().GetProperties().Where(f => f.CanWrite && f.GetSetMethod(true).IsPublic)
)
{
if (property.GetCustomAttributes(typeof (TransformAttribute), true).Any())
{
var transform =
(property.GetCustomAttributes(typeof (TransformAttribute), true).FirstOrDefault() as
TransformAttribute);
var transformname = transform.RelatedField ?? property.Name;
if (convertType.GetProperty(transformname) == null)
throw new ArgumentException(
string.Format(
"We were unable to find property:\"{0}\" on {1}. Please check the RelativeField value on the {2} for \"{0}\"",
transformname, convertFrom.GetType().Name, convertTo.Name));
var theValue = convertType.GetProperty(transformname).GetValue(convertFrom);
if (isCollection(theValue))
{
foreach (var item in (theValue as ICollection))
{
var someVal = new object();
var newToType = property.PropertyType.GetGenericArguments().FirstOrDefault();
if (!String.IsNullOrEmpty(transform.FullyQualifiedName))
someVal =
Transform(
item.GetType().GetProperty(transform.FullyQualifiedName).GetValue(item),
newToType);
else
someVal = Transform(item, newToType);
if (property.GetValue(_t) == null)
throw new NullReferenceException(
string.Format(
"The following property:{0} is null on {1}. Likely this needs to be initialized inside of {1}'s empty constructor",
property.Name, _t.GetType().Name));
property.PropertyType.GetMethod("Add")
.Invoke(property.GetValue(_t), new[] {someVal});
//property.SetValue(_t, theValue.Transform(theValue.GetType()));
}
}
else
property.SetValue(_t, theValue);
}
//property.SetValue(_t, property.GetValue(convertFrom, null), null);
}
return _t;
}
public bool isCollection(object o)
{
return o is ICollection
|| typeof (ICollection<>).IsInstanceOfType(o);
}
}
public class TransformAttribute : Attribute
{
public string RelatedField { get; private set; }
public string FullyQualifiedName { get; set; }
public TransformAttribute()
{
}
public TransformAttribute(string relatedField)
{
RelatedField = relatedField;
}
}
such that the end result is: myObject.Transform()
But the decorations let you account for differences between your POCO and your ViewModel