I currently use the builder pattern to construct my MVC view models.
var viewModel = builder
.WithCarousel(),
.WithFeaturedItems(3),
.Build()
The problem I am coming up against is when I have to make a service call to an async method. This means that my builder method then has to return Task<HomeViewModelBuilder>
instead of HomeViewModelBuilder
. This prevents me from chaining the build methods as I have to await
them.
Example method
public async Task<HomeViewModelBuilder> WithCarousel()
{
var carouselItems = await _service.GetAsync();
_viewModel.Carousel = carouselItems;
return this;
}
Now I have to use await
to call the builder methods.
await builder.WithCarousel();
await builder.WithFeaturedItems(3);
Has anyone used async methods with the builder pattern? If so, is it possible to be able to chain the methods or defer the await
to the build method.
The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active.
For more information, I have an async / await intro on my blog. So additionally, if a method with multiple awaits is called by a caller, the responsibility for finishing every statement of that method is with the caller.
The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete.
A simple answer for that: No, we can't! Currently, class constructors do not return types, and an asynchronous method should return a Task type. Don't worry about that, because there is a solution for this.
I have not actually done this before, but here's an alternative to Sriram's solution.
The idea is to capture the tasks in the builder object instead of the result of the tasks. The Build
method then waits for them to complete and returns the constructed object.
public sealed class HomeViewModelBuilder
{
// Example async
private Task<Carousel> _carouselTask = Task.FromResult<Carousel>(null);
public HomeViewModelBuilder WithCarousel()
{
_carouselTask = _service.GetAsync();
return this;
}
// Example sync
private int _featuredItems;
public HomeViewModelBuilder WithFeaturedItems(int featuredItems)
{
_featuredItems = featuredItems;
return this;
}
public async Task<HomeViewModel> BuildAsync()
{
return new HomeViewModel(await _carouselTask, _featuredItems);
}
}
Usage:
var viewModel = await builder
.WithCarousel(),
.WithFeaturedItems(3),
.BuildAsync();
This builder pattern works with any numbers of asynchronous or synchronous methods, for example:
public sealed class HomeViewModelBuilder
{
private Task<Carousel> _carouselTask = Task.FromResult<Carousel>(null);
public HomeViewModelBuilder WithCarousel()
{
_carouselTask = _service.GetAsync();
return this;
}
private Task<int> _featuredItemsTask;
public HomeViewModelBuilder WithFeaturedItems(int featuredItems)
{
_featuredItemsTask = _featuredService.GetAsync(featuredItems);
return this;
}
public async Task<HomeViewModel> BuildAsync()
{
return new HomeViewModel(await _carouselTask, await _featuredItemsTask);
}
}
Usage is still the same.
As I said in comments, you could write Extension method for HomeViewModelBuilder
as well as Task<HomeViewModelBuilder>
and chain it.
public static class HomeViewModelBuilderExtension
{
public static Task<HomeViewModelBuilder> WithCarousel(this HomeViewModelBuilder antecedent)
{
return WithCarousel(Task.FromResult(antecedent));
}
public static async Task<HomeViewModelBuilder> WithCarousel(this Task<HomeViewModelBuilder> antecedent)
{
var builder = await antecedent;
var carouselItems = await builder.Service.GetAsync();
builder.ViewModel.Carousel = carouselItems;
return builder;
}
public static Task<HomeViewModelBuilder> WithFeaturedItems(this HomeViewModelBuilder antecedent, int number)
{
return WithFeaturedItems(Task.FromResult(antecedent), number);
}
public static async Task<HomeViewModelBuilder> WithFeaturedItems(this Task<HomeViewModelBuilder> antecedent, int number)
{
var builder = await antecedent;
builder.ViewModel.FeaturedItems = number;
return builder;
}
}
We're adding couple of methods for single operation so that you can chain it with HomeViewModelBuilder
or Task<HomeViewModelBuilder>
. Otherwise you'll not be able to call builder.WithCarousel()
Then use it like
private static void Main()
{
HomeViewModelBuilder builder = new HomeViewModelBuilder();
var task = builder
.WithCarousel()
.WithFeaturedItems(3);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With