2

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?

SOfanatic
  • 5,523
  • 5
  • 36
  • 57

2 Answers2

1

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.

Community
  • 1
  • 1
Sethcran
  • 798
  • 5
  • 11
1

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

Rikon
  • 2,688
  • 3
  • 22
  • 32