I am attempting to clean up a lot of my code which relates to creating web service requests, and mapping back and forth between view models and data transfer objects. I currently have the following setup in my code:
public class Request1 : IRequest<Type1>
{
public Type1 Data { get; set; }
}
public interface IRequest<T>
{
T Data { get; set; }
}
public class Type1
{
string Account {get; set; }
...
}
public static class Mapper
{
public static TOut CreateRequest<TOut, TIn>(TIn data) where TIn : IRequest<TIn>, new()
{
var request = new TOut();
request.Data = data;
return request;
}
}
The above enables me to use generics to create the Request1
object by passing in the data really simply.
var model = new ViewModel();
var data = Mapper.mapFromViewModel(model);
var request = Mapper.CreateRequest<Request1,Type1>(data);
This is GREAT in my opinion, however it has one flaw.
The data I pass in has to be of the dto type (Type1
) in the request. What I would like to do is pass in the data in its raw form, so in the UI I would be passing in the view model data, and the type of Request I want. The method would work out the mapping between the view model and the dto type, and then create the request for me.
So if I use a bit of AutoMapper
to handle the mapping between the view model and the dto type I can get to this:
public TOut CreateRequest2<TOut, TDto, TModelIn>(TModelIn data)
where TOut : IRequest<TDto>, new()
{
var request = new TOut();
request.Data = Map<TModelIn, TDto>(data);
return request;
}
Which I can use like this:
var model = new ViewModel();
var request = Mapper.CreateRequest2<Request1,Type1,ViewModel>(model);
This is almost there... but this is where I hit the wall of my knowledge.
I would like to be able to cut the TDto
requirement from the call, so the caller only has to know about the Request
, and the ViewModel
types.
Something like:
public TOut CreateRequest3<TOut, TModelIn>(TModelIn data)
{
// Cast? the TOut to IRequest<>
// Extract the TDto type from the IRequest<> object and
// pass the TDto type into the method I created earlier.
return CreateRequest2<TOut,TDto,TModelIn>(data);
}
Does anyone know if this can be achieved?
The upside of this is to remove the need for my UI code to know what DTO is required on a message, and let that be handled by automapper based on the view model it is given.
UPDATE: So following suggestions by @EpicSam I now have the following that works:
public TOut CreateRequest3<TOut, TModelIn>(TModelIn data) where TOut : class, new()
{
var interfaces = typeof(TOut).GetInterfaces().FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IRequest<>));
var dtoType = interfaces?.GenericTypeArguments.FirstOrDefault();
var result = typeof(Mapper)
.GetMethod(nameof(Mapper.CreateRequest))
.MakeGenericMethod(typeof(TOut), dtoType, typeof(TModelIn))
.Invoke(this, new object[ ] {data});
return result as TOut;
}
This is ugly, but is ok, as its only ever written once. But.. what other things do I need to know about this code. As it uses reflection, is it going to hammer my performance, or should I not even worry about it....
UPDATE 2: The reflection code is about 3x slower than the original code I posted. So even though I could choose to make my code nicer and cleaner, I'll opt to keep it as it is, and work more efficiently.