How to create a Fluent Interface with Generics
Asked Answered
S

2

7

I wanted to create a fluent interface that can be used like so:

void Main() {
    ModelStateMappings.MapDomainModel<Book>().MapViewModel<BookViewModel>()
        .Properties(book => book.Author, vm => vm.AuthorsName)
        .Properties(book => book.Price, vm => vm.BookPrice);

    ModelStateMappings.MapDomainModel<Store>().MapViewModel<StoreViewModel>()
        .Properties(store => store.Owner, vm => vm.OwnersName)
        .Properties(store => store.Location, vm => vm.Location);
}

I wanted end up with a collection that looked something like this:

static class ModelStateaMappings {
    private static IList<ModelMappings> mappings;
    // other methods in here to get it working
}

class ModelMappings {
    public Type DomainModelType {get;set;}
    public Type ViewModelType {get;set;}
    public IList<PropertyMapping> PropertyMappings {get;set;}
}

class PropertyMapping {
    public Expression<Func<object, object>> DomainProperty {get;set;}
    public Expression<Func<object, object>> ViewModelProperty {get;set;}
}

I was not able to get the above accomplished but I did create something similar which works in a similar fashion but I don't particularly like how I had to setup the fluent interfaces. I would rather have it read like the way I have it above.

Sprinkler answered 6/1, 2014 at 22:40 Comment(4)
What does the interface that you have now look like? What was the specific problem that prevented you from making it look the way you wanted?Kaitlinkaitlyn
I couldn't figure out how to get the generic inference all the way down to the .Properties() method w/out passing both types in at the same time. The way I would like to do it is pass each generic one at a time, then bring them together in the Property() methodSprinkler
Sounds like you need an array of types. Have you had a look at Automapper?Kaitlinkaitlyn
I've seen automapper, that's not what I need. I'm not really using this to map properties between models.. I wanted to use it to get expressions of which properties in the domain model are related to properties in the viewmodel.. but the properties could be of different typesSprinkler
U
2

You can achieve it with following code

static class ModelStateMappings
{
    public static DomainModelMapping<TDomainModel> MapDomainModel<TDomainModel>()
    {
        // edit the constructor to pass more information here if needed.
        return new DomainModelMapping<TDomainModel>();
    }
}

public class DomainModelMapping<TDomainModel>
{
    public ViewModelMapping<TDomainModel, TViewModel> MapViewModel<TViewModel>()
    {
        // edit the constructor to pass more information here if needed.
        return new ViewModelMapping<TDomainModel, TViewModel>();
    }
}

public class ViewModelMapping<TDomainModel, TViewModel>
{
    public ViewModelMapping<TDomainModel, TViewModel>
        Properties<TDomainPropertyType, TViewModelPropertyType>(
            Expression<Func<TDomainModel, TDomainPropertyType>> domainExpr,
            Expression<Func<TViewModel, TViewModelPropertyType>> viewModelExpr)
    {
        // map here
        return this;
    }
}

You don't have to specify all previously set generic types because they are already remembered as generic parameters of returned type. Generic parameters for Properties method call can be skipped because they will be inferred by compiler. And you get better typing than using objects everywhere.

Of course that's the simplest version. You can pass much more information between these types, because you specify how next necessary type is created.

It also make calling MapViewModel without calling MapDomainModel first impossible (as soon as you make the constructors internal and close everything in separate dll), what should be a good thing.

Unpeople answered 6/1, 2014 at 22:56 Comment(0)
S
5

There are two common ways to create a fluent interface.

One way is to add to the current instance of the class being built and return this from each method.

Something like this:

public class NamesBuilder
{
    private List<string> _names = new List<string>();
    public NamesBuilder AddName(string name)
    {
        _names.Add(name);
        return this;
    }
}

The problem with this kind of builder is that you can write buggy code easily:

var namesBuilder = new NamesBuilder();

var namesBuilder1 = namesBuilder.AddName("John");
var namesBuilder2 = namesBuilder.AddName("Jack");

If I saw this code I would expect that namesBuilder1 and namesBuilder2 would each only have one name, and that namesBuilder wouldn't have any. However the implementation would have both names in all three variables as they are the same instance.

The better way to implement a fluent interface is to create a chain on builder classes that are lazily evaluated so that you create the final class once you're done building. Then if you branch in the middle of the building process you can not make a mistake.

Here's the kind of code I would expect to write:

var bookMap =
    ModelStateMappings
        .Build<Book, BookViewModel>()
        .AddProperty(book => book.Author, vm => vm.AuthorsName)
        .AddProperty(book => book.Price, vm => vm.BookPrice)
        .Create();
        
var bookStore =
    ModelStateMappings
        .Build<Store, StoreViewModel>()
        .AddProperty(store => store.Owner, vm => vm.OwnersName)
        .AddProperty(store => store.Location, vm => vm.Location)
        .Create();

The code to make this work is a little more complicated than the "names" example.

public static class ModelStateMappings
{
    public static Builder<M, VM> Build<M, VM>()
    {
        return new Builder<M, VM>();
    }
    
    public class Builder<M, VM>
    {
        public Builder() { }
        
        public Builder<M, VM> AddProperty<T>(
            Expression<Func<M, T>> domainMap,
            Expression<Func<VM, T>> viewModelMap)
        {
            return new BuilderProperty<M, VM, T>(this, domainMap, viewModelMap);
        }
        
        public virtual Map Create()
        {
            return new Map();
        }
    }
    
    public class BuilderProperty<M, VM, T> : Builder<M, VM>
    {
        private Builder<M, VM> _previousBuilder;
        private Expression<Func<M, T>> _domainMap;
        private Expression<Func<VM, T>> _viewModelMap;
        
        public BuilderProperty(
            Builder<M, VM> previousBuilder,
            Expression<Func<M, T>> domainMap,
            Expression<Func<VM, T>> viewModelMap)
        {
            _previousBuilder = previousBuilder;
            _domainMap = domainMap;
            _viewModelMap = viewModelMap;
        }
        
        public override Map Create()
        {
            var map = _previousBuilder.Create();
            /* code to add current map to Map class */
            return map;
        }
    }
}

The other advantage to this type of builder is that you also maintain strongly-typed property fields.

Of course you would need to put in the correct code for your mapping in the Create method.

Sherikasherill answered 7/1, 2014 at 5:20 Comment(0)
U
2

You can achieve it with following code

static class ModelStateMappings
{
    public static DomainModelMapping<TDomainModel> MapDomainModel<TDomainModel>()
    {
        // edit the constructor to pass more information here if needed.
        return new DomainModelMapping<TDomainModel>();
    }
}

public class DomainModelMapping<TDomainModel>
{
    public ViewModelMapping<TDomainModel, TViewModel> MapViewModel<TViewModel>()
    {
        // edit the constructor to pass more information here if needed.
        return new ViewModelMapping<TDomainModel, TViewModel>();
    }
}

public class ViewModelMapping<TDomainModel, TViewModel>
{
    public ViewModelMapping<TDomainModel, TViewModel>
        Properties<TDomainPropertyType, TViewModelPropertyType>(
            Expression<Func<TDomainModel, TDomainPropertyType>> domainExpr,
            Expression<Func<TViewModel, TViewModelPropertyType>> viewModelExpr)
    {
        // map here
        return this;
    }
}

You don't have to specify all previously set generic types because they are already remembered as generic parameters of returned type. Generic parameters for Properties method call can be skipped because they will be inferred by compiler. And you get better typing than using objects everywhere.

Of course that's the simplest version. You can pass much more information between these types, because you specify how next necessary type is created.

It also make calling MapViewModel without calling MapDomainModel first impossible (as soon as you make the constructors internal and close everything in separate dll), what should be a good thing.

Unpeople answered 6/1, 2014 at 22:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.