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