I'm implementing a number of classes in C# that have async methods. However, each of these methods needs to implement some boilerplate code that is common to all. Therefore I've implemented the common code in an async method of a base class; the async methods on the derived classes then call this base class method.
Here's the base class, containing the method that does the common stuff (GetResultAsync
).
namespace Redacted
{
abstract class RepositoryBase : IRepository
{
protected RepositoryBase(IRepositoryConfiguration configuration)
{
Configuration = configuration;
}
protected IRepositoryConfiguration Configuration { get; private set; }
protected FleetContext CreateDatabaseContext()
{
return new FleetContext(Configuration.DatabaseConnectionString);
}
protected async Task<IApiResult<TValue>> GetResultAsync<TValue>(
IApiCallerContext context,
Func<FleetContext, CancellationToken, Task<TValue>> getTask)
{
using (new SynchronizationContextChange())
using (FleetContext db = CreateDatabaseContext())
{
bool isSystemOnline = await db.IsSystemOnlineAsync();
if (!isSystemOnline)
{
return ImmutableApiResult<TValue>.Offline;
}
bool isValidSession = await db.IsValidSessionAsync(context.Caller);
if (!isValidSession)
{
return ImmutableApiResult<TValue>.Blocked;
}
Task<TValue> task = getTask(db, context.CancellationToken);
TValue value = await task;
return ImmutableApiResult.Create<TValue>(value);
}
}
}
}
Basically, I have an Entity Framework database context (FleetContext
) that exists for the duration of the call. I then perform some common DB-related tasks by calling custom async methods that have been added to the context class. Depending on the result of these calls, I may exit the method early: in such cases, the IApiResult
object returned by the method is different than when the method runs through to the end normally.
Here's an example of a derived class that contains a method which calls the base class:
namespace Redacted
{
sealed class CompanyRepository : RepositoryBase, ICompanyRepository
{
public CompanyRepository(IRepositoryConfiguration configuration)
: base(configuration)
{
}
public Task<IApiResult<ICompanyIdentity>> GetCompanyIdentityAsync(int companyId, IApiCallerContext context)
{
return GetResultAsync<ICompanyIdentity>(
context,
async (db, cancellationToken) =>
{
Company company = await db.Companies.FindAsync(cancellationToken, companyId);
return ImmutableCompanyIdentity.Create(company);
}
);
}
}
}
As you can see I'm using an asynchronous anonymous function to return the Task
object required by the GetResultAsync
method.
This all works but seems somewhat inside-out to me, in that the API method (GetCompanyIdentityAsync
) passes a function to the base class, rather than the more typical approach of the derived class calling the base method as part of its normal processing.
Am I worrying unnecessarily, or are there obvious alternate ways of calling the boilerplate code from the derived classes?
GetResultAsync
method should be in a separate helper class? If so, what would be the reasoning behind that? (These are genuine questions, I'm not trying to pick an argument) \$\endgroup\$