DEV Community

Ahmed Fouad
Ahmed Fouad

Posted on • Originally published at Medium on

Building your First async fluent API using interface extension methods pattern

Fluent Interface is an advanced API design that really on extension methods and methods chaining. When of the most famous examples of fluent API is the linq extension methods for IEnumerable.

var passed=Students.Where(s=>s.Passed).Select(s=>s.Name).ToList();

In the asynchronous Programming, async/await, fluent APIs are kind of limited due to the nature of the Task object that it is hot build default, in this article, we will overcome this problem and will build a small library that allows fluent extension methods.

as an example,

var data=await GetDataAsync()
         .AsRepeatable(TimeSpan.FromSeconds(10))
         .FirstOrDefaultAsync(data=>data.Any());

another example could be

var data=await GetNewsAsync()
         .Where(newsList=>newsList.Date>=DateTime.Today)
         .SelectMany(newsList=>newsList.NewsItems)
         .ToList();

Just to give you an idea of how this code could be written using Imperative approach

 var newsList=await GetNewsAsync();
 if(newsList.Date>=DateTime.Today)
    {
       return newsList.Select(news=>news.NewsItems).ToList();
    }
else
       return new List<NewsItem>();

It is up to you to choose which code fashion you would like, if you like the second one, you can stop reading this article as in the rest of the article we will extend the c sharp language to make the first one applicable.

Good you decided to continue that show that you are a very elegant software engineer so please grape your coffee and you can invite me on as well using

Buy AhmedFouad a Coffee. ko-fi.com/ahmedfouad

Step 1: Convert your async Task to Task>

This will help us to get rid of the NullReferenceExceptions and concentrate only on the extension methods.

public static Func<Task<Option<T>>> ToOption<T>(this Func<Task<T>> context)
{
     return new Func<Task<Option<T>>>(async () =>{
     try
       {
          var result = await context.Invoke();
          return result.SomeNotNull();
       }
     catch (Exception e)
       {
          return Option.None<T>();
       }});

}

This method simply invokes the async method and return Option.None when the value is null or exception happened or the value as some when there is a value.

we will need to install the option type nugget package

Optional 4.0.0

Step 2: Build a Wrapper class in which we will implement our extension methods

public class ExtendedTask<T>
    {
        private readonly Func<Task<Option<T>>> \_taskOption;
        public ExtendedTask(Func<Task<T>> task)
        {
            \_taskOption = task.ToOption();
        }
        public ExtendedTask(Func<Task<Option<T>>> task)
        {
            \_taskOption = task;
        }
    }

form here it is very easy to implement our extension methods, we will just delegate the implementation to the option type.

public ExtendedTask<T> Where(Func<T,bool> condition)
        {
            var conditionedTask = \_taskOption.Select(func => new Func<Task<Option<T>>>(async () =>
            {
                var result = await func.Invoke();
                return result.Where(condition);
            }));

            return new ExtendedTask<T>(conditionedTask);
        }

It will return the new ExtendedTask of value when the condition is meet and ExtendedTask of None otherwise.

Now we will implement the select method and select async method using the same strategy.

public ExtendedTask<TP> Select<TP>(Func<T,TP> selector)
        {
            var conditionedTask = \_taskOption.Select(func => new 
            Func<Task<Option<TP>>>(async () =>
            {
                var result = await func.Invoke();

                return result.Select(selector);
            }));

          return new ExtendedTask<TP>(conditionedTask);
        }

public ExtendedTask<TP> SelectAsync<TP>(Func<T, Task<TP>> selector)
        {
            var conditionedTask = \_taskOption.Select(func => new  
            Func<Task<Option<TP>>>(async () =>
            {
                var result = await func.Invoke();

                var [@select](http://twitter.com/select) = await result.Select(selector.Invoke)
                 .ValueOr(()=>Task.FromResult(default(TP)));

                return [@select](http://twitter.com/select).SomeWhen(p =>
                {
                    var b = p.Equals(default(TP));
                    return !b;
                });
            }));

Step 3: Implement the AsTask() method

public async Task<T> AsTask()
        {
            return (await \_taskOption.Select(async task => await 
                    task())).ValueOrDefault();
        }

This method simply returns a new task that executes the decorated task and will simply return the default of T when the value is None.

Step4: Implement AsRepeatable() method

As the repeatable method is a transformation method that transforms a scalar Task to a Vector IAsyncEnumerable

public IAsyncEnumerable<T> AsRepeatable()
        {
            return new TaskAsyncEnumerable<T>( \_taskOption);
        }
        public IAsyncEnumerable<T> AsRepeatable(TimeSpan timeSpan)
        {
            return new TaskAsyncEnumerable<T>(\_taskOption ,timeSpan);
        }

And here is the Implementation of TaskAsyncEnumerable

It is quite straight forward The enumerator will keep executing the task on iterating and will only stop when the Iterating stop or when the value becomes null.

Step 5: Let’s try it

var getDataAsync = new ExtendedTask<News>(GetDataAsync);
var data = await getDataAsync
                .AsRepeatable(TimeSpan.FromSeconds(1))
                .FirstOrDefaultAsync(data => data.Items.Any());

Step 6

If you like the article please share it, follow me on medium and on twitter to get my upcoming

Ahmed Fouad

and pay me the coffee at

Buy AhmedFouad a Coffee. ko-fi.com/ahmedfouad

Oldest comments (0)