BsonDocument to dynamic Expression or in-memory caching of results - c#

I am trying to write a proxy class for IMongoCollection so that I can use an in-memory cache for some of the method implementations. The issue, however, is that almost all the filters are of type FilterDefinition<T> which means we can call Render on them to get a BsonDocument. I am wondering if there is a way to convert the filter BsonDocument to dynamic Expression so that I can run it against my in-memory List<T>. Or maybe there is a better approach to do in-memory caching which I am not aware of. Thank you.
Update:
I was tempted to write a solution as #simon-mourier suggested but the problem with this hacky solution is that C# mongo driver returns IAsyncCursor<T> for find operations which is basically a stream of BsonDocuments and after each read it is pointing to the last index and disposes itself. And there is no way to reset the stream to its initial position. Which means the following code works the first time but after that, we get an exception of the cursor is at the end of stream and already disposed.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using DAL.Extensions;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
namespace DAL.Proxies
{
public static class MongoCollectionProxy
{
private static readonly Dictionary<Type, object> _instances = new Dictionary<Type, object>();
public static IMongoCollection<T> New<T>(IMongoCollection<T> proxy)
{
return ((IMongoCollection<T>)_instances.AddOrUpdate(typeof(T), () => new MongoCollectionBaseProxyImpl<T>(proxy)));
}
}
public class MongoCollectionBaseProxyImpl<T> : MongoCollectionBase<T>
{
private readonly IMongoCollection<T> _proxy;
private readonly ConcurrentDictionary<string, object> _cache = new ConcurrentDictionary<string, object>();
public MongoCollectionBaseProxyImpl(IMongoCollection<T> proxy)
{
_proxy = proxy;
}
public override Task<IAsyncCursor<TResult>> AggregateAsync<TResult>(PipelineDefinition<T, TResult> pipeline,
AggregateOptions options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return _proxy.AggregateAsync(pipeline, options, cancellationToken);
}
public override Task<BulkWriteResult<T>> BulkWriteAsync(IEnumerable<WriteModel<T>> requests,
BulkWriteOptions options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return _proxy.BulkWriteAsync(requests, options, cancellationToken);
}
[Obsolete("Use CountDocumentsAsync or EstimatedDocumentCountAsync instead.")]
public override Task<long> CountAsync(FilterDefinition<T> filter, CountOptions options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return _proxy.CountAsync(filter, options, cancellationToken);
}
public override Task<IAsyncCursor<TField>> DistinctAsync<TField>(FieldDefinition<T, TField> field,
FilterDefinition<T> filter, DistinctOptions options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return _proxy.DistinctAsync(field, filter, options, cancellationToken);
}
public override async Task<IAsyncCursor<TProjection>> FindAsync<TProjection>(FilterDefinition<T> filter,
FindOptions<T, TProjection> options = null,
CancellationToken cancellationToken = new CancellationToken())
{
// ReSharper disable once SpecifyACultureInStringConversionExplicitly
return await CacheResult(filter.Render().ToString(), () => _proxy.FindAsync(filter, options, cancellationToken));
}
public override async Task<TProjection> FindOneAndDeleteAsync<TProjection>(FilterDefinition<T> filter,
FindOneAndDeleteOptions<T, TProjection> options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return await InvalidateCache(_proxy.FindOneAndDeleteAsync(filter, options, cancellationToken));
}
public override async Task<TProjection> FindOneAndReplaceAsync<TProjection>(FilterDefinition<T> filter,
T replacement,
FindOneAndReplaceOptions<T, TProjection> options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return await InvalidateCache(_proxy.FindOneAndReplaceAsync(filter, replacement, options,
cancellationToken));
}
public override async Task<TProjection> FindOneAndUpdateAsync<TProjection>(FilterDefinition<T> filter,
UpdateDefinition<T> update,
FindOneAndUpdateOptions<T, TProjection> options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return await InvalidateCache(_proxy.FindOneAndUpdateAsync(filter, update, options, cancellationToken));
}
public override Task<IAsyncCursor<TResult>> MapReduceAsync<TResult>(BsonJavaScript map, BsonJavaScript reduce,
MapReduceOptions<T, TResult> options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return _proxy.MapReduceAsync(map, reduce, options, cancellationToken);
}
public override IFilteredMongoCollection<TDerivedDocument> OfType<TDerivedDocument>()
{
return _proxy.OfType<TDerivedDocument>();
}
public override IMongoCollection<T> WithReadPreference(ReadPreference readPreference)
{
return _proxy.WithReadPreference(readPreference);
}
public override IMongoCollection<T> WithWriteConcern(WriteConcern writeConcern)
{
return _proxy.WithWriteConcern(writeConcern);
}
public override CollectionNamespace CollectionNamespace => _proxy.CollectionNamespace;
public override IMongoDatabase Database => _proxy.Database;
public override IBsonSerializer<T> DocumentSerializer => _proxy.DocumentSerializer;
public override IMongoIndexManager<T> Indexes => _proxy.Indexes;
public override MongoCollectionSettings Settings => _proxy.Settings;
private async Task<TResult> CacheResult<TResult>(string key, Func<Task<TResult>> result)
{
return _cache.ContainsKey(key) ? (TResult) _cache[key] : (TResult) _cache.AddOrUpdate(key, await result());
}
private TResult InvalidateCache<TResult>(TResult result)
{
_cache.Clear();
return result;
}
}
}

if your only concern is creating a decorator class which applies some sort of caching strategy in order to avoid some database access I think that you should try a simpler approach to the problem.
I'm not saying that trying to write a decorator for the interface IMongoCollection<T> is wrong per se, I'm just saying that it's not the simplest solution to your problem.
A better approach could be, instead, shrinking your focus to the specific needs of your application. In the following paragraph I'll try to explain my point of view.
Let's suppose that your application must frequently access a User collection and that the user data don't change very often, so that they are good candidates for a simple caching strategy. At that point you can decide to define an abstraction like IUserRepository and to shape that abstraction for your application needs. Consider, for instance, this interface definition:
public interface IUserRepository
{
User GetById(Guid userId);
ReadOnlyCollection<User> GetAll();
}
At this point you will write a concrete implementation which uses MongoDB as a persistence layer:
public class MongoUserRepository: IUserRepository
{
private readonly IMongoCollection<User> userCollection;
public MongoUserRepository(IMongoCollection<User> userCollection)
{
this.userCollection = userCollection ?? throw new ArgumentNullException(nameof(userCollection));
}
// interface members implementation omitted for simplicity
}
Then, you should define a decorator for IUserRepository implementing the caching aspect, as follows:
public class UserRepositoryCachingDecorator: IUserRepository
{
private readonly IUserRepository decoratee;
private readonly IMemoryCache cache;
public UserRepositoryCachingDecorator(IUserRepository decoratee, IMemoryCache cache)
{
this.decoratee = decoratee ?? throw new ArgumentNullException(nameof(decoratee));
this.cache = cache ?? throw new ArgumentNullException(nameof(cache));
}
// interface members implementation omitted for simplicity
}
In my opinion this is a better approach, because it's much simpler than trying to write a general purpose proxy over IMongoCollection<T> and it only requires you to focus on the specific needs of your application.

Related

How to get rid of code duplication for Minimal API's

I am trying to learn to effectively use Minimal API's. How can I reduce duplicate codes, how to create base Endpoint class and do the most job there?
Especially, how to use Mediatr from a static reference and not put in every methods parameter?
My endpoint:
public static class ArticleEndpoints
{
public static void MapArticleEndpoints(this WebApplication app)
{
var articles = app.MapGroup("Articles");
articles.MapPost(nameof(CreateAsync), CreateAsync)
.AddEndpointFilter<ValidationFilter<CreateArticleCommand>>()
.WithName(nameof(CreateAsync))
.Produces(StatusCodes.Status201Created, typeof(Guid))
.ProducesValidationProblem()
.WithOpenApi();
articles.MapPut(nameof(AddTagToArticleAsync), AddTagToArticleAsync)
.AddEndpointFilter<ValidationFilter<AddTagToArticleCommand>>()
.WithName(nameof(AddTagToArticleAsync))
.Produces(StatusCodes.Status200OK, typeof(ArticleGetByIdDto))
.ProducesValidationProblem()
.WithOpenApi();
articles.MapGet(nameof(GetAllAsync), GetAllAsync)
.WithName(nameof(GetAllAsync))
.Produces(StatusCodes.Status200OK, typeof(IReadOnlyCollection<ArticleGetAllDto>))
.WithOpenApi();
articles.MapGet(nameof(GetByIdAsync), GetByIdAsync)
.WithName(nameof(GetByIdAsync))
.Produces(StatusCodes.Status200OK, typeof(TagGetByIdDto))
.WithOpenApi();
}
public static async Task<IResult> CreateAsync([FromBody] CreateArticleCommand command, IMediator mediator, CancellationToken cancellationToken)
{
var id = await mediator.Send(command, cancellationToken);
return Results.Created(nameof(CreateAsync), new { id });
}
public static async Task<IResult> AddTagToArticleAsync([FromBody] AddTagToArticleCommand command, IMediator mediator, CancellationToken cancellationToken)
{
var result = await mediator.Send(command, cancellationToken);
return Results.Ok(result);
}
public static async Task<IResult> GetAllAsync(IMediator mediator, CancellationToken cancellationToken)
{
var result = await mediator.Send(new GetAllArticlesQuery(), cancellationToken);
return Results.Ok(result);
}
public static async Task<IResult> GetByIdAsync([FromBody] GetArticleByIdIncludeTagsQuery query, IMediator mediator, CancellationToken cancellationToken)
{
var result = await mediator.Send(query, cancellationToken);
return Results.Ok(result);
}
}

Fluent validation divide business validation from auth validaton

I'm using ASP, CQRS + MediatR and fluent validation. I want to implement user role validation, but I don't want to mix it with business logic validation. Do you have any idea how to implement this?
I mean a specific validator must be executed for a specific request.
Something tells me the solution lies in IEnumerable< IValidator>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators) => _validators = validators;
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToArray();
if (failures.Any())
{
var errors = failures
.Select(x => new Error(x.ErrorMessage, x.ErrorCode))
.ToArray();
throw new ValidationException(errors);
}
}
return await next();
}
}
I see your concern, I also found myself in this situation. I wanted to separate my validators from handlers while also keeping them in the domain/business project. Also I didn't want to throw exceptions just to handle bad request or any other custom business exception.
You have the right idea by
I mean a specific validator must be executed for a specific request
For this, you need to set up a mediator pipeline, so for every Command you can find the appropriate the appropriate validator, validate and decide whether to execute the command or return a failed result.
First, create an interface(although not necessary but it is how I did it) of ICommand like this:
public interface ICommand<TResponse>: IRequest<TResponse>
{
}
And, ICommandHandler like:
public interface ICommandHandler<in TCommand, TResponse>: IRequestHandler<TCommand, TResponse>
where TCommand : ICommand<TResponse>
{
}
This way we can only apply validation to commands. Instead of iheriting IRequest<MyOutputDTO> and IRequestHandler<MyCommand, MyOutputDTO> you inherit from ICommand and ICommandHandler.
Now create a ValidationBehaviour for the mediator as we agreed before.
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : class, ICommand<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators) => _validators = validators;
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (!_validators.Any())
return await next();
var validationContext = new ValidationContext<TRequest>(request);
var errors = (await Task.WhenAll(_validators
.Select(async x => await x.ValidateAsync(validationContext))))
.SelectMany(x => x.Errors)
.Where(x => x != null)
.Select(x => x.CustomState)
.Cast<TResponse>();
//TResponse should be of type Result<T>
if (errors.Any())
return errors.First();
try
{
return await next();
}
catch(Exception e)
{
//most likely internal server error
//better retain error as an inner exception for debugging
//but also return that an error occurred
return Result<TResponse>.Failure(new InternalServerException(e));
}
}
}
This code simply, excepts all the validators in the constructor, because you register all your validator from assembly for your DI container to inject them.
It waits for all validations to validate async(because my validations mostly require calls to db itself such as getting user roles etc).
Then check for errors and return the error(here I have created a DTO to wrap my error and value to get consistent results).
If there were no errors simply let the handler do it's work return await next();
Now you have to register this pipeline behavior and all the validators.
I use autofac so I can do it easily by
builder
.RegisterAssemblyTypes(_assemblies.ToArray())
.AsClosedTypesOf(typeof(IValidator<>))
.AsImplementedInterfaces();
var mediatrOpenTypes = new[]
{
typeof(IRequestHandler<,>),
typeof(IRequestExceptionHandler<,,>),
typeof(IRequestExceptionAction<,>),
typeof(INotificationHandler<>),
typeof(IPipelineBehavior<,>)
};
foreach (var mediatrOpenType in mediatrOpenTypes)
{
builder
.RegisterAssemblyTypes(_assemblies.ToArray())
.AsClosedTypesOf(mediatrOpenType)
.AsImplementedInterfaces();
}
If you use Microsoft DI, you can:
services.AddMediatR(typeof(Application.AssemblyReference).Assembly);
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
services.AddValidatorsFromAssembly(typeof(Application.AssemblyReference).Assembly); //to add validators
Example usage:
My generic DTO Wrapper
public class Result<T>: IResult<T>
{
public Result(T? value, bool isSuccess, Exception? error)
{
IsSuccess = isSuccess;
Value = value;
Error = error;
}
public bool IsSuccess { get; set; }
public T? Value { get; set; }
public Exception? Error { get; set; }
public static Result<T> Success(T value) => new (value, true, null);
public static Result<T> Failure(Exception error) => new (default, false, error);
}
A sample Command:
public record CreateNewRecordCommand(int UserId, string record) : ICommand<Result<bool>>;
Validator for it:
public class CreateNewRecordCommandValidator : AbstractValidator<CreateNewRecordCommand>
{
public CreateNewVoucherCommandValidator(DbContext _context, IMediator mediator) //will be injected by out DI container
{
RuleFor(x => x.record)
.NotEmpty()
.WithState(x => Result<bool>.Failure(new Exception("Empty record")));
//.WithName("record") if your validation a property in array or something and can't find appropriate property name
RuleFor(x => x.UserId)
.MustAsync(async(id, cToken) =>
{
//var roles = await mediator.send(new GetUserRolesQuery(id, cToken));
//var roles = (await context.Set<User>.FirstAsync(user => user.id == id)).roles
//return roles.Contains(MyRolesEnum.CanCreateRecordRole);
}
)
.WithState(x => Result<bool>.Failure(new MyCustomForbiddenRequestException(id)))
}
}
This way you always get a result object, you can check if error is null or !IsSuccess and then create a custom HandleResult(result) method in your Controller base which can switch on the exception to return BadReuqestObjectResult(result) or ForbiddenObjectResult(result).
If you prefer to throw, catch and handle the exceptions in the pipeline or you wan't non-async implementation, read this https://code-maze.com/cqrs-mediatr-fluentvalidation/
This way all your validations are very far from your handler while maintaining consistent results.
I think that your initial approach its right. When you say that you want to keep the auth validations apart from the other business validation, do you mean like returning a http error like 403 and 401 right?
If thats the case try marking the auth validations with and interface to identify they, and do not run all the validations at once. Search first in the collection for a validation with that interface, and if it fails send a custom exception that you can identity in a IActionFilter to set the wanted result. This code does not do that exactly but you can make an idea.
public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
{
private ISystemLogger _logger;
public HttpResponseExceptionFilter()
{
}
public int Order { get; } = int.MaxValue - 10;
public void OnActionExecuting(ActionExecutingContext context) { }
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception is PipelineValidationException exception)
{
context.Result = new ObjectResult(new Response(false, exception.ValidationErrors.FirstOrDefault()?.ErrorMessage ?? I18n.UnknownError));
context.ExceptionHandled = true;
}
else if (context.Exception != null)
{
_logger ??= (ISystemLogger)context.HttpContext.RequestServices.GetService(typeof(ISystemLogger));
_logger?.LogException(this, context.Exception, methodName: context.HttpContext.Request.Method);
context.Result = new ObjectResult(new Response(false, I18n.UnknownError));
context.ExceptionHandled = true;
}
}
}

DbContext Gets Disposed Upon Using async Task method

I am currently using an async Task method to implement the IAuthenticationFilter interface. Upon successful login, I'll try to access an API that has this attribute and it will work fine. However once I go back and access the API again the exception will be thrown.
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
var token = context.Request.Headers.Authorization.Parameter;
var principal = await AuthenticateToken(token)
// Other code here ...
}
protected Task<IPrincipal> AuthenticateToken(string token)
{
var secretKey = _authenticationBusiness.GetSecretKey(); // error triggers here.
if (principal == null)
context.ErrorResult = new AuthenticationFailureResult("Invalid token", request);
else
context.Principal = principal;
}
//AuthenticationBusiness.cs
public string GetSecretKey()
{
using (_unitOfWork)
{
var token = _unitOfWork.Tokens.GetToken();
return token.SecretKey ?? string.Empty;
}
}
//Dependency Injection using Unity
container.RegisterType<IUnitOfWork, UnitOfWork>(new HierarchicalLifetimeManager());
container.RegisterType<IContext, Context>(new HierarchicalLifetimeManager());
//UnitOfWork.cs
private readonly IContext _context;
public UnitOfWork(IContext context, IJWTRepository tokens)
{
_context = context;
Tokens = tokens;
}
public IJWTRepository Tokens { get; private set; }
public void Dispose()
{
_context.Dispose();
}
//Context.cs
public class Context : DbContext, IContext
{
public new void SaveChanges()
{
base.SaveChanges();
}
public new void Dispose()
{
base.Dispose();
}
}
//JWTRepository.cs
public class JWTRepository : Repository<JsonWebToken>, IJWTRepository
{
public JWTRepository(Context context) : base(context) { }
public JsonWebToken GetToken()
{
return Context.Tokens
.OrderBy(jwt => jwt.Id)
.Take(1)
.SingleOrDefault();
}
private Context Context => _context as Context;
}
If I try to remove this attribute and access the API multiple times nothing wrong happens so I am assuming that this has something to do with the fact that the attribute has asynchronous methods?
When the lifetime of an IDisposable object is limited to a single
method, you should declare and instantiate it in the using statement.
The using statement calls the Dispose method on the object in the
correct way, and (when you use it as shown earlier) it also causes the
object itself to go out of scope as soon as Dispose is called. Within
the using block, the object is read-only and cannot be modified or
reassigned.
using Statement (C# Reference)
In your code, the problem is that you are wrapping GetSecretkey() into using() which will dispose _unitOfWork and when you will try to access it again it will show an error.
Hope this code works for you.
//AuthenticationBusiness.cs
public string GetSecretKey()
{
var token = _unitOfWork.Tokens.GetToken();
return token.SecretKey ?? string.Empty;
}
The problem in in your function AuthenticateToken. When using async-await, make sure you await every Task before returning, if the Task Disposes items. See what is the purpose of return await. The first answer focuses on disposable object
I assume you omitted parts of method AuthenticateToken, because I don't say the return value.
Solution: declare the method async, and await for the Task before returning
async Task<IPrincipal> AuthenticateToken(string token)
{
var secretKey = _authenticationBusiness.GetSecretKey();
...
// Somewhere there is a Task involved,
Task<IPrincipal> myTask = ...
// instead of return myTask:
return await myTask;
}

Creating a simple Async Workflow Activity

I'm trying to get in to workflow foundation but apparently i can't seem to get even the most basic implementation of an async activity working.
Could anyone point me in the right direction with this activity I have put together in order to make an async OData request using HttpClient ...
Firstly I created a base type extending from AsyncCodeActivity ...
public abstract class ODataActivity<TResult> : AsyncCodeActivity<TResult>, IDisposable
{
protected HttpClient Api =
new HttpClient(
new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }
)
{ BaseAddress = new Uri(new Config().ApiRoot) };
bool disposed = false;
public void Dispose()
{
Dispose(disposed);
}
public virtual void Dispose(bool disposed)
{
if (!disposed)
{
Api.Dispose();
Api = null;
}
}
}
Next I inherit that to provide my implementation ...
public class ODataFetchActivity<TResult> : ODataActivity<TResult>
{
public string Query { get; set; }
protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
var task = Api.GetAsync(Query)
.ContinueWith(t => t.Result.Content.ReadAsAsync<TResult>())
.ContinueWith(t => callback(t));
context.UserState = task;
return task;
}
protected override TResult EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
var response = ((Task<TResult>)result).Result;
context.SetValue(Result, response);
return response;
}
}
... the idea being that this activity can do only get requests, i could then implement a post, put and delete to get full crud in the same manner on top of my base type above.
The problem comes when i add this to a workflow and try to execute the flow using a re-hosted designer in a new wpf app resulting in the following exception ...
Edit:
So I did a little bit more tinkering and have something that appears to not complain but i'm not convinced this is a "good" way to handle this as Task implements IAsyncResult directly and it feels like i'm jumping through a bunch of hoops I perhaps don't need to.
public class ODataFetchActivity<TResult> : ODataActivity<TResult>
{
public string Query { get; set; }
Func<TResult> work;
protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
work = () => Api.Get<TResult>(Query).Result;
context.UserState = work;
return work.BeginInvoke(callback, state);
}
protected override TResult EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
TResult response = work.EndInvoke(result);
Result.Set(context, response);
return response;
}
}
This appears to compile and run but i can't help but feel like there's a cleaner way to handle this.
Hmm apparently this works fine ...
public class ODataFetchActivity<TResult> : ODataActivity<TResult>
{
public string Query { get; set; }
Func<TResult> work;
protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
work = () => Api.Get<TResult>(Query).Result;
context.UserState = work;
return work.BeginInvoke(callback, state);
}
protected override TResult EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
TResult response = work.EndInvoke(result);
Result.Set(context, response);
return response;
}
}
I was getting some odd behavior from the designer re-hosting where it would run the previous version until a save made (no idea why)

Checking that CancellationTokenSource.Cancel() was invoked with Moq

I have a conditional statement which should looks as follows:
//...
if(_view.VerifyData != true)
{
//...
}
else
{
_view.PermanentCancellation.Cancel();
}
where PermanentCancellation is of type CancellationTokenSource.
Im wondering how i should set this up in my mock of _view. All attempts thus far have failed :( and i cant find an example on google.
Any pointers would be appreciated.
Because CancellationTokenSource.Cancel is not virtual you cannot mock it with moq.
You have two options:
Create a wrapper interface:
public interface ICancellationTokenSource
{
void Cancel();
}
and an implementation which delegates to the wrapped CancellationTokenSource
public class CancellationTokenSourceWrapper : ICancellationTokenSource
{
private readonly CancellationTokenSource source;
public CancellationTokenSourceWrapper(CancellationTokenSource source)
{
this.source = source;
}
public void Cancel()
{
source.Cancel();
}
}
And use the ICancellationTokenSource as PermanentCancellation then you can create an Mock<ICancellationTokenSource> in your tests:
// arrange
var mockCancellationTokenSource = new Mock<ICancellationTokenSource>();
viewMock.SetupGet(m => m.PermanentCancellation)
.Returns(mockCancellationTokenSource.Object)
// act
// do something
// assert
mockCancellationTokenSource.Verify(m => m.Cancel());
And use the CancellationTokenSourceWrapper in your production code.
Or use a mocking framework which supports mocking non virtual members like:
Microsoft Fakes
Typemock isolator (commercial)
JustMock (commercial)
I went a step further and made a factory to create a CancellationTokenManager class that implements the interface. This was because my method has to take CancellationToken and I wanted granular control over .IsCancellationRequested():
My CancellationTokenManagerFactory:
public interface ICancellationTokenManagerFactory
{
ICancellationTokenManager CreateManager(CancellationToken token);
}
public class CancellationTokenManagerFactory : ICancellationTokenManagerFactory
{
public ICancellationTokenManager CreateManager(CancellationToken token)
{
return new CancellationTokenManager(token);
}
}
and the manager:
public interface ICancellationTokenManager
{
bool IsCancellationRequested { get; }
CancellationToken CancellationToken { get; }
}
public class CancellationTokenManager : ICancellationTokenManager
{
private readonly CancellationToken _token;
public CancellationTokenManager(CancellationToken token)
{
_token = token;
}
public bool IsCancellationRequested
{
get
{
return _token.IsCancellationRequested;
}
}
public CancellationToken CancellationToken => _token;
}
Then in a class utilizing:
public class MyService
{
private readonly ICancellationTokenManagerFactory _factory = factory;
public MyService(ICancellationTokenManagerFactory factory)
{
_factory = factory;
}
public void StartAsync(CancellationToken token)
{
manager = _factory.CreateManager(token);
//check if cancelled
if (!manager.IsCancellationRequested())
}
// do some work
}
}
}
Now if I check cancellation is requested more than once i can mock with different responses each time. Additionally, any interfaces like IHostService can still be utilized because CancellationToken is passed in although it doesn't necessarily matter what is in that token.

Categories