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