Monday, December 3, 2012

Sample Fluent API barebone code

using System;
using System.Collections.Generic;

using System.Linq;
using System.Text;
using System.Linq.Expressions;




namespace Sample
{
    using DomainToInputMappings;
    
    internal class Program
    {
        private static void Main(string[] args)
        {
            var x = new FlightInput();
            Console.ReadKey();
        }
    }
}


// NHibernate can use these domain classes directly:
namespace DomainClasses
{
    public class Flight
    {
        public virtual Country Country { get; set; }
        public virtual City City { get; set; }
        public virtual int StayDuration { get; set; }
    }
    
    public class Country
    {
        public virtual string CountryCode { get; set; }
        public virtual string CountryName { get; set; }
        public virtual int Population { get; set; }
    }
    
    public class City
    {
        public virtual Country Country { get; set; }
        
        public virtual int CityID { get; set; }
        public virtual string CityName { get; set; }
    }
    
}

namespace DomainToInputMappings
{
    using DomainClasses;
    using InputMapper;
    using FinderControls;
    
    
    // On some systems, we don't directly map the domain classes to input,
    // should flatten the domain classes to DTO (e.g. x.Country.CountryCode to x.CountryCode) first,
    // then map the input to the DTO instead,
    // so it's easier and lighter(CountryName and Population won't be transferred) to transfer
    // and use the object across the wire. e.g. Silverlight, jQuery ajax, etc  
    class FlightInput : InputMap<Flight>
    {
        public FlightInput()
        {
            Input(x => x.Country.CountryCode)
                .DisplayWidth(200)
                    .Color(ConsoleColor.Blue)
                    .UseFinder<CountryFinder>().SelectedID(x => x.SelectedCountryCode);
            
            
            
            // The advantage of fluent API, aside from there's autocomplete,
            // we don't need to do stringly-typed approach when referring to the property of an object,
            // we can refer to the property in a strongly-typed manner (e.g. x.Country.CountryCode).
            // With attributes-based API, we have to do this instead:
            
            // Compilers can't catch error if you misspelled Country.ConutryCode if you make stringly-typed API
            // [DisplayWidth(200)]
            // [Color(KnownColor.Blue)]
            // [UseFinder(typeof(CityFinder), SelectedID = "SelectedCityID", CascadingField = "Country.CountryCode")]
            
            
            Input(x => x.City.CityID)
                .DisplayWidth(200)
                    .Color(ConsoleColor.Blue)
                    .UseFinder<CityFinder>().SelectedID(x => x.SelectedCityID).CascadingField(x => x.Country.CountryCode);
            
            Input(x => x.StayDuration)
                .DisplayWidth(100)
                    .Color(ConsoleColor.Green)
                    .UseSpinner(1, 10);
        }
    }
    
}


namespace FinderControls
{
    public class CountryFinder
    {
        public int SelectedCountryCode { get; set; }
        public bool MultiSelect { get; set; }
    }
    
    public class CityFinder
    {
        public int SelectedCityID { get; set; }
        public bool MultiSelect { get; set; }
    }   
}




namespace InputMapper
{
    using ExpressionGetter;
    public abstract class InputMap<T>
    {
        public InputPart<T> Input<TProp>(Expression<Func<T, TProp>> p)
        {
            Console.WriteLine(new PropertyPathVisitor().GetPropertyPath(p));
            
            
            var inputPart = new InputPart<T>();
            return inputPart;
        }
    }
    
    
    public class InputPart<T>
    {
        public InputPart<T> DisplayWidth(int width)
        {
            return this;
        }
        
        public InputPart<T> Color(ConsoleColor color)
        {
            return this;
        }
        
        public FinderPart<T, TFinder> UseFinder<TFinder>()
        {
            return new FinderPart<T, TFinder>();
        }
        
        
        public InputPart<T> UseSpinner(int from, int to)
        {
            return this;
        }
    }
    
    public class FinderPart<T, TFinder>
    {
        public FinderPart<T, TFinder> SelectedID<TFinderProp>(Expression<Func<TFinder, TFinderProp>> x)
        {
            return this;
        }
        
        public FinderPart<T, TFinder> CascadingField<TProp>(Expression<Func<T, TProp>> x)
        {
            return this;
        }
    }
}

namespace ExpressionGetter
{
    // We can’t make an apple pie from scratch, some ingredients have to come from somewhere:
    
    // PropertyPathVisitor sourced from: http://www.thomaslevesque.com/2010/10/03/entity-framework-using-include-with-lambda-expressions/
    class PropertyPathVisitor : ExpressionVisitor
    {
        private Stack<string> _stack;
        
        public string GetPropertyPath(Expression expression)
        {
            _stack = new Stack<string>();
            Visit(expression);
            return _stack
                .Aggregate(
                    new StringBuilder(),
                    (sb, name) =>
                    (sb.Length > 0 ? sb.Append(".") : sb).Append(name))
                    .ToString();
        }
        
        
        
        protected override Expression VisitMember(MemberExpression expression)
        {
            if (_stack != null)
                _stack.Push(expression.Member.Name);
            return base.VisitMember(expression);
        }
        
        
    }
    
}


Rationale for fluent-based API: http://www.ienablemuch.com/2012/11/fluent-based-apis.html

No comments:

Post a Comment