3

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.

Nick
  • 1,116
  • 2
  • 12
  • 24
  • 2
    Maybe using custom attributes (https://msdn.microsoft.com/en-us/library/84c42s56.aspx) to define a mapping is the key? – ViRuSTriNiTy Nov 14 '16 at 13:18
  • Not sure why that would help. I know what the `TDto` type is, as its in the `IRequest` on the request class. Its just how I get it out of there. – Nick Nov 14 '16 at 13:28
  • Getting the type is easy: `typeof(TOut).GenericTypeArguments[0]` (you probably want to create a base `IRequest` interface so you can at least constrain `TOut` to that). But you can't pass it statically (you can invoke the method through reflection, but that's fairly ugly). I think Automapper does have some overloads for passing the type at runtime, though. – Jeroen Mostert Nov 14 '16 at 13:39
  • I understand your question in a way that you need a mapping between `TModelIn` and `TDto`. Therefore adding a custom attribute to a model like `[DtoMapping("YourDtoClassName")]` would enable to query the attribute from a model and find the `TDto` class. – ViRuSTriNiTy Nov 14 '16 at 13:42
  • @ViRuSTriNiTy - Ah ok. No, the mapping between the model and DTO is held in AutoMapper. At the moment, my calling code has to know the Request type, the DTO type within the Request, and the Model type that currently has the data. What I want is the caller to be agnostic of the underlying DTO type. and let the mapper and automapper under the hood work it out. – Nick Nov 14 '16 at 13:46
  • Possible duplicate of [How do I use reflection to call a generic method?](http://stackoverflow.com/questions/232535/how-do-i-use-reflection-to-call-a-generic-method) – Clint Nov 14 '16 at 14:22
  • @Nick Ok, then add the custom attribute to the DTO class. The attribute would then tell you which request class to use. This should be possible because you have a DTO instance after `Map()` returns which carries the request class info. – ViRuSTriNiTy Nov 14 '16 at 14:44
  • How about defining which TModelIn belongs to which DTO in the namespace where you create the request? – Glubus Nov 14 '16 at 15:33
  • @Glubus The DTO currently reside in a shared assembly as they are used in the web service code, data access layer, and also the UI. The view models are only in the UI, so the UI knows of the existence of the DTO, but the DTO does not know of the UI view models. – Nick Nov 14 '16 at 15:44
  • @ViRuSTriNiTy The view models can map to more than one DTO, so even if i did put the DTO on a CustomAttribute, I would still have an issue decoding which one to convert it into. – Nick Nov 14 '16 at 15:46
  • reflection will hammer your performance. but with intelligent caching it is usually acceptable. you could look into expressions if you are unhappy with the performance. – Dbl Nov 14 '16 at 16:06

2 Answers2

2

Generics are meant for compile time, not runtime. If you need it at runtime you'd have to use reflection and do something like this:

public TOut CreateRequest3<TOut, TModelIn>(TModelIn data) {
    var type = typeof(TOut).GetProperty("Data").PropertyType;
    return typeof(Mapper).GetMethod("CreateRequest2").MakeGenericMethod(new Type[] {TOut, type, TModelIn} ).Invoke(this, new Object[] {data});
}

This is quite ugly, perhaps there is a different approach to what you wish to achieve?

Good Night Nerd Pride
  • 8,245
  • 4
  • 49
  • 65
EpicSam
  • 185
  • 8
  • How do you get the `Data.GetType()`? The TOut doesnt know of the `IRequest<>` interface on that method. Otherwise that may just work, albeit in an ugly way. – Nick Nov 14 '16 at 14:36
  • @Nick, this code won't work als the generic parameters aren't constrainted in a way that the compiler can know what `new TOut().Data` even means. It's a common idea but it fails due to the constraint issue. – ViRuSTriNiTy Nov 14 '16 at 14:46
  • @Nick We should be able to get it with even more ugly reflection, see my edit. – EpicSam Nov 14 '16 at 15:04
  • @EpicSam I have updated the original code to use something similar. In my eyes its not as ugly as the above as its not as brittle thanks to the `nameof()`. but its still not the nicest piece of code I have ever written. – Nick Nov 14 '16 at 15:19
  • @Nick Glad you got it to work for you! Not using strings in your solution is also definitely an improvement that will make it less brittle. Be carefull of using too much reflection to solve these kind of problems with generics though. We are essentially taking compile time safety out of generics by doing so. – EpicSam Nov 14 '16 at 15:36
1

You can do it like this, abusing extension methods:

public interface IRequest
{
}
public interface IRequest<T> : IRequest
{
    T Data { get; set;}
}

public static class Mapper 
{ 
    public static TOut CreateRequest<TOut>() where TOut : IRequest, new() 
    {
        return new TOut();
    }

    public static IRequest<TDto> From<TDto,TModel>(this IRequest<TDto> request, TModel data)
    {
        request.Data=Map<TModel,TDto>(data);
        return request;
    }

    public static TOut Map<TIn,TOut>(TIn input)
    {
        // Only for this example, you need to provide your own implemenation.
        return (TOut)(object)((Model)(object)input).Value;
    }

}

Then you can invoke it like this:

Mapper.CreateRequest<SomeRequestType>().From(myModel);

See here

CSharpie
  • 9,195
  • 4
  • 44
  • 71
  • Marking this as the answer, as this is what I would have gone with. I use an instance of AutoMapper in the Map function which slows this option down to much. However the answer does not use reflection, nor is it brittle or ugly. – Nick Nov 15 '16 at 13:07