Fluent interfaces make code easier to read, understand, and use. They can also provide a layer of separation and decoupling to make it easier to reuse components in a straightforward fashion. In this post I will walk through the scenario of creating a fluent interface with a “backwards design.” This means I’ll start with the end in mind: what I want my interface to look like, and build the API to support that.
The scenario is a common one that illustrates the point nicely: a feed reader. Regardless of using a fluent interface, you are likely to be following the MVVM model and practice SOLID principles. (If not, please read the linked article for a jumpstart).
The individual feed item can be modeled simply like this:
public class FeedItem { public string Id { get; set; } public DateTime Posted { get; set; } public string Title { get; set; } public string Description { get; set; } }
Knowing the definition of the feed item now makes it possible to create a view model to host it:
public partial class ViewModel { public ViewModel() { FeedItems = new ObservableCollection<FeedItem>(); DesignData(); if (DesignerProperties.IsInDesignTool) return; } public ObservableCollection<FeedItem> FeedItems { get; private set; } }
The design data can then populate the sample feeds:
public partial class ViewModel { [Conditional("DEBUG")] protected void DesignData() { if (!DesignerProperties.IsInDesignTool) { return; } for (var x = 0; x < 20; x++) { var feedItem = new FeedItem(); feedItem.Id = Guid.NewGuid().ToString(); feedItem.Posted = DateTime.Now.AddDays(-20 + x); feedItem.Title = "Blog Post Entry"; feedItem.Description = "This is a sample blog post entry for the purpose of design-time data on the design surface."; FeedItems.Add(feedItem); } } }
This simply wires up a short list for data-binding. With some simple XAML, the entire structure is complete:
<Grid x_Name="LayoutRoot" Background="White"> <Grid.DataContext> <FluentBackwardsDesign:ViewModel/> </Grid.DataContext> <ListBox ItemsSource="{Binding FeedItems}"> <ListBox.ItemTemplate> <DataTemplate> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <TextBlock FontWeight="Bold" Text="{Binding Posted}"/> <TextBlock Text="{Binding Title}" FontStyle="Italic" HorizontalAlignment="Right" Margin="20 0 0 0"/> </StackPanel> <TextBlock TextWrapping="Wrap" Grid.Row="1" Text="{Binding Description}"/> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid>
This project is set as a Silverlight OOB application with elevated trust to avoid any cross-domain issues with loading the feed. To load the feed requires several steps:
- Initiate the request to fetch the feed using the
WebClient
- Receive the response
- Stuff the response into a string
- Pass the string into an
XmlReader
- Pass the
XmlReader
into theSyndicationFeed
- Iterate the feeds and populate the feed items
The non-fluent way to perform these steps may look something like this:
protected void OldWay() { var wc = new WebClient(); wc.DownloadStringCompleted += _WcDownloadStringCompleted; wc.DownloadStringAsync(_targetUri); } void _WcDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { ((WebClient) sender).DownloadStringCompleted -= _WcDownloadStringCompleted; using (var stringReader = new StringReader(e.Result)) { using (var reader = XmlReader.Create(stringReader)) { var feed = SyndicationFeed.Load(reader); foreach(var feedItem in feed.Items.Select( item => new FeedItem { Id = item.Id, Posted = item.PublishDate.Date, Title = item.Title.Text, Description = item.Summary.Text })) { FeedItems.Add(feedItem); } } } }
This code adds a lot to the view model. It violates the Single Responsibiity Principle by requiring the view model to understand how to fetch the data, how to handle the return, how to parse it and then feed it in. The various steps are also not reusable or unit testable as they depend on the live feed and connection.
Good practices would dictate moving away from this model to decoupled classes with separated responsibilities. Why not go ahead and make this fluent?
Ideally, the pattern of opening some data (whether a feed or other format), parsing it and populating a list will be easy and fluent. Starting with the end in mind, what if the code looked like this?
ServiceLocator .GetHelper<FeedItem>() .For(FeedItems) .From(_targetUri) .ParseWith(new FeedParser()) .Go();
This would be all of the code necessary to make the view model work: get a helper for the type of list, indicate what collection should be populated, provide the target URI, inject the strategy to map the format to a known entity, and kick it all off.
The model for fluent extensions is to provide a method on the class (whether a local method or an extension method) that ultimately returns itself. This allows for chaining of multiple method calls because each call returns the class to make it available for the next. Knowing this, the contract for the helper can be defined like this:
public interface IWebClientHelper<T> { IWebClientHelper<T> For(ICollection<T> target); IWebClientHelper<T> From(Uri uri); IWebClientHelper<T> ParseWith(IParser<T> parse); void Go(); }
The parser contract can also be inferred, because it would take in the string resulting from the download and return the collection.
public interface IParser<T> { IEnumerable<T> Parse(string src); }
Now it’s possible to implement the parser. The implementation is nice because it can be unit tested by supplying a predefined string and comparing against the expected output:
public class FeedParser : IParser<FeedItem> { public IEnumerable<FeedItem> Parse(string src) { using (var stringReader = new StringReader(src)) { using (var reader = XmlReader.Create(stringReader)) { var feed = SyndicationFeed.Load(reader); return feed.Items.Select( item => new FeedItem { Id = item.Id, Posted = item.PublishDate.Date, Title = item.Title.Text, Description = item.Summary.Text }); } } } }
The implementation of the web helper can store the stateful information about the call, then generically implement the call:
public class WebClientHelper<T> : IWebClientHelper<T> { private Uri _uri; private ICollection<T> _collection; private IParser<T> _parser; public IWebClientHelper<T> For(ICollection<T> target) { _collection = target; return this; } public IWebClientHelper<T> From(Uri uri) { _uri = uri; return this; } public IWebClientHelper<T> ParseWith(IParser<T> parse) { _parser = parse; return this; } public void Go() { if (_uri == null) { throw new ArgumentException("Uri must be set."); } if (_parser == null) { throw new ArgumentException("No parser was specified."); } if (_collection == null) { throw new ArgumentException("No collection was specified."); } var wc = new WebClient(); wc.DownloadStringCompleted += _WcDownloadStringCompleted; wc.DownloadStringAsync(_uri); } private void _WcDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { ((WebClient) sender).DownloadStringCompleted -= _WcDownloadStringCompleted; foreach (var item in _parser.Parse(e.Result)) { _collection.Add(item); } } }
This could be abstracted even further by providing a strategy for the web client download so that process can be mocked as well.
Finally, instead of dealing directly with the helper, a typical implementation would provide some sort of Dependency Injection/Inversion of Control to fire this off. This example simulates that using a simple service locator:
public static class ServiceLocator { public static IWebClientHelper<T> GetHelper<T>() { return new WebClientHelper<T>(); } }
Anything that depends on the fluent object can now be easily mocked as well, so the fluent interface is perfectly testable. And that’s it … working with the end in mind, this exercise simplified the view model and provided a reusable, fluent interface for grabbing information from remote websites and parsing it into typed collections.
Download the source here.