I have an Aspect class that uses the MethodInterception class. When I create a throw like below in this class, ExceptionMiddleware does not handle, why?
I am using Castle.DynamicProxy for MethodInterception. Since Castle.DynamicProxy has a working problem in async methods, I solved my problem by using the extension 'Castle.Core.AsyncInterceptor'. But now the problem is that the throw exception from an async interceptor is not handled by the exception middleware. If I don't define the onBefore method as async, the exception middleware works, but I run await method in onbefore, so it's necessary.
public class ExceptionMiddleware
{
private RequestDelegate _next;
public ExceptionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (Exception e)
{
await HandleExceptionAsync(httpContext, e);
}
}
private Task HandleExceptionAsync(HttpContext httpContext, Exception e)
{
httpContext.Response.ContentType = "application/json";
httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
string message = "Internal Server Error";
if (e.GetType() == typeof(UnauthorizedAccessException))
{
message = e.Message;
httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
return httpContext.Response.WriteAsync(new ErrorDetails
{
StatusCode = httpContext.Response.StatusCode,
Message = message
}.ToString());
}
}
SecuredScopeAspect
public class SecuredOperation : MethodInterception
{
private string[] _roles;
private IHttpContextAccessor _httpcontextAccessor;
public SecuredOperation()
{
_httpcontextAccessor = ServiceTool.ServiceProvider.GetService<IHttpContextAccessor>();
}
public SecuredOperation(string roles)
{
_roles = roles.Split(',');
_httpcontextAccessor = ServiceTool.ServiceProvider.GetService<IHttpContextAccessor>();
}
protected override async void OnBefore(IInvocation invocation)
{
if (_httpcontextAccessor.HttpContext.User.Identity.IsAuthenticated)
{
if (_roles != null)
{
//some await proccess
const result = await _fga.check();
if(result != null) return;
throw new MemberAccessException(AuthMessages.AuthorizationForbidden);
}
return;
}
throw new UnauthorizedAccessException(AuthMessages.AuthorizationDenied);
}
}
MethodInterception.cs
public abstract class MethodInterception : MethodInterceptionBaseAttribute
{
protected virtual void OnBefore(IInvocation invocation)
{
}
protected virtual void OnAfter(IInvocation invocation)
{
}
protected virtual void OnException(IInvocation invocation, System.Exception e)
{
}
protected virtual void OnSuccess(IInvocation invocation)
{
}
public override void InterceptSynchronous(IInvocation invocation)
{
var isSuccess = true;
OnBefore(invocation);
try
{
invocation.Proceed();
}
catch (Exception e)
{
isSuccess = false;
OnException(invocation, e);
throw;
}
finally
{
if (isSuccess)
{
OnSuccess(invocation);
}
}
OnAfter(invocation);
}
public override void InterceptAsynchronous(IInvocation invocation)
{
invocation.ReturnValue = InternalInterceptAsynchronous(invocation);
}
protected async Task InternalInterceptAsynchronous(IInvocation invocation)
{
var isSuccess = true;
OnBefore(invocation);
try
{
invocation.Proceed();
var task = (Task)invocation.ReturnValue;
await task;
}
catch (Exception e)
{
isSuccess = false;
OnException(invocation, e);
throw;
}
finally
{
if (isSuccess)
{
OnSuccess(invocation);
}
}
OnAfter(invocation);
}
public override void InterceptAsynchronous<TResult>(IInvocation invocation)
{
invocation.ReturnValue = InternalInterceptAsynchronous<TResult>(invocation);
}
protected async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation)
{
TResult result;
var isSuccess = true;
OnBefore(invocation);
try
{
invocation.Proceed();
var task = (Task<TResult>)invocation.ReturnValue;
result = await task;
}
catch (Exception e)
{
isSuccess = false;
OnException(invocation, e);
throw;
}
finally
{
if (isSuccess)
{
OnSuccess(invocation);
}
}
OnAfter(invocation);
return result;
}
}
}
This is your problem: async void. void is not a natural return type for asynchronous code, and it has very surprising exception handling semantics in particular.
So, OnBefore and friends have to become async Task instead.
Then, this causes another problem: how to intercept synchronous calls? Blocking on asynchronous code is possible in ASP.NET Core (it doesn't cause deadlocks), but it's not an ideal solution because it wastes threads. [Links are to my blog].
Welcome to the complexities of asynchronous-and-synchronous interception. It's not a pretty world.
Your options are:
Only intercept asynchronous methods. I don't know how DynamicProxy works in this case; the last time I used it they didn't support intercepting asynchronous methods with asynchronous pre-work at all. So it may be that it just ignores synchronous methods, which leaves you with a rather nasty pit-of-failure. Alternatively, you could always fail synchronous methods, so at least you would have some indication it's being used incorrectly (even if that indication is at runtime in production...)
Make all On* methods asynchronous (return Task instead of void), and just block on them for the synchronous interceptions.
Force/allow all your derived interception types to support both synchronous and asynchronous implementations.
IMO (2) would be cleanest in terms of code maintainability. (2) would look like:
public abstract class MethodInterception : MethodInterceptionBaseAttribute
{
protected virtual Task OnBefore(IInvocation invocation) => Task.CompletedTask;
protected virtual Task OnAfter(IInvocation invocation) => Task.CompletedTask;
protected virtual Task OnException(IInvocation invocation, System.Exception e) => Task.CompletedTask;
protected virtual Task OnSuccess(IInvocation invocation) => Task.CompletedTask;
public override void InterceptSynchronous(IInvocation invocation)
{
var isSuccess = true;
OnBefore(invocation).GetAwaiter().GetResult();
try
{
invocation.Proceed();
}
catch (Exception e)
{
isSuccess = false;
OnException(invocation, e).GetAwaiter().GetResult();
throw;
}
finally
{
if (isSuccess)
{
OnSuccess(invocation).GetAwaiter().GetResult();
}
}
OnAfter(invocation).GetAwaiter().GetResult();
}
public override void InterceptAsynchronous(IInvocation invocation)
{
invocation.ReturnValue = InternalInterceptAsynchronous(invocation);
}
protected async Task InternalInterceptAsynchronous(IInvocation invocation)
{
var isSuccess = true;
await OnBefore(invocation);
try
{
invocation.Proceed();
var task = (Task)invocation.ReturnValue;
await task;
}
catch (Exception e)
{
isSuccess = false;
await OnException(invocation, e);
throw;
}
finally
{
if (isSuccess)
{
await OnSuccess(invocation);
}
}
await OnAfter(invocation);
}
... // similarly for <TResult>
}
public class SecuredOperation : MethodInterception
{
private string[] _roles;
private IHttpContextAccessor _httpcontextAccessor;
protected override async Task OnBefore(IInvocation invocation)
{
if (_httpcontextAccessor.HttpContext.User.Identity.IsAuthenticated)
{
if (_roles != null)
{
const result = await _fga.CheckAsync();
if (result != null) return;
throw new MemberAccessException(AuthMessages.AuthorizationForbidden);
}
return;
}
throw new UnauthorizedAccessException(AuthMessages.AuthorizationDenied);
}
}
An example of (3) using the boolean argument pattern may look like this:
public abstract class MethodInterception : MethodInterceptionBaseAttribute
{
protected virtual Task OnBefore(IInvocation invocation, bool sync) => Task.CompletedTask;
protected virtual Task OnAfter(IInvocation invocation, bool sync) => Task.CompletedTask;
protected virtual Task OnException(IInvocation invocation, System.Exception e, bool sync) => Task.CompletedTask;
protected virtual Task OnSuccess(IInvocation invocation, bool sync) => Task.CompletedTask;
public override void InterceptSynchronous(IInvocation invocation)
{
var isSuccess = true;
OnBefore(invocation, sync: true).GetAwaiter().GetResult();
try
{
invocation.Proceed();
}
catch (Exception e)
{
isSuccess = false;
OnException(invocation, e, sync: true).GetAwaiter().GetResult();
throw;
}
finally
{
if (isSuccess)
{
OnSuccess(invocation, sync: true).GetAwaiter().GetResult();
}
}
OnAfter(invocation, sync: true).GetAwaiter().GetResult();
}
public override void InterceptAsynchronous(IInvocation invocation)
{
invocation.ReturnValue = InternalInterceptAsynchronous(invocation);
}
protected async Task InternalInterceptAsynchronous(IInvocation invocation)
{
var isSuccess = true;
await OnBefore(invocation, sync: false);
try
{
invocation.Proceed();
var task = (Task)invocation.ReturnValue;
await task;
}
catch (Exception e)
{
isSuccess = false;
await OnException(invocation, e, sync: false);
throw;
}
finally
{
if (isSuccess)
{
await OnSuccess(invocation, sync: false);
}
}
await OnAfter(invocation, async: false);
}
... // similarly for <TResult>
}
public class SecuredOperation : MethodInterception
{
private string[] _roles;
private IHttpContextAccessor _httpcontextAccessor;
protected override async Task OnBefore(IInvocation invocation, bool sync)
{
if (_httpcontextAccessor.HttpContext.User.Identity.IsAuthenticated)
{
if (_roles != null)
{
#if (_fga supports synchronous calls)
const result = sync ? _fga.Check() : await _fga.CheckAsync();
#else
const result = sync ? _fga.CheckAsync().GetAwaiter().GetResult() : await _fga.CheckAsync();
#endif
if (result != null) return;
throw new MemberAccessException(AuthMessages.AuthorizationForbidden);
}
return;
}
throw new UnauthorizedAccessException(AuthMessages.AuthorizationDenied);
}
}
Related
I have a class which creates a Task which runs during its whole lifetime, that is, until Dispose() is called on it:
In the constructor I call:
_worker = Task.Run(() => ProcessQueueAsync(_workerCancellation.Token), _workerCancellation.Token);
The way I currently do it (which I am also not sure is the right way) is cancelling the CancellationToken, and waiting on the task.
public void Dispose()
{
if (_isDisposed)
{
return;
}
_workerCancellation.Cancel();
_worker.GetAwaiter().GetResult();
_isDisposed = true;
}
When I do the same in the AsyncDispose method like so:
public async ValueTask DisposeAsync()
{
await _worker;
}
I get this warning
How do I correctly dispose of such a worker? Thanks!
As requested, here is the full code of what I am trying to do:
public sealed class ActiveObjectWrapper<T, TS> : IAsyncDisposable
{
private bool _isDisposed = false;
private const int DefaultQueueCapacity = 1024;
private readonly Task _worker;
private readonly CancellationTokenSource _workerCancellation;
private readonly Channel<(T, TaskCompletionSource<TS>)> _taskQueue;
private readonly Func<T, TS> _onReceive;
public ActiveObjectWrapper(Func<T, TS> onReceive, int? queueCapacity = null)
{
_onReceive = onReceive;
_taskQueue = Channel.CreateBounded<(T, TaskCompletionSource<TS>)>(queueCapacity ?? DefaultQueueCapacity);
_workerCancellation = new CancellationTokenSource();
_worker = Task.Run(() => ProcessQueueAsync(_workerCancellation.Token), _workerCancellation.Token);
}
private async Task ProcessQueueAsync(CancellationToken cancellationToken)
{
await foreach (var (value, taskCompletionSource) in _taskQueue.Reader.ReadAllAsync(cancellationToken))
{
try
{
var result = _onReceive(value); // todo: do I need to propagate the cancellation token?
taskCompletionSource.SetResult(result);
}
catch (Exception exception)
{
taskCompletionSource.SetException(exception);
}
}
}
public async Task<TS> EnqueueAsync(T value)
{
// see: https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
var completionSource = new TaskCompletionSource<TS>(TaskCreationOptions.RunContinuationsAsynchronously);
await _taskQueue.Writer.WriteAsync((value, completionSource));
return await completionSource.Task;
}
public async ValueTask DisposeAsync()
{
if (_isDisposed)
{
return;
}
_taskQueue.Writer.Complete();
_workerCancellation.Cancel();
await _worker;
_isDisposed = true;
}
}
This is the pattern that I use when implementing both IDisposabe and IDisposeableAsync. It isn't strictly compliant with the .Net recommendations. I found that implementing DisposeAsyncCore() was unnecessary as my classes are sealed.
public void Dispose()
{
Dispose(disposing: true);
//GC.SuppressFinalize(this);
Worker.GetAwaiter().GetResult();
}
public void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
if (disposing)
{
lock (isDisposing)
{
if (isDisposed)
{
return;
}
Cts.Cancel();
Cts.Dispose();
isDisposed = true;
}
}
}
public async ValueTask DisposeAsync()
{
Dispose(disposing: true);
//GC.SuppressFinalize(this);
await Worker;
}
This looks like an attempt to build a TransformBlock<TIn,TOut> on top of Channels. Using a Channel properly wouldn't generate such a warning. There's no reason to use a single processing task, or store it in a field.
Using Blocks
First, the equivalent code using a TransformBlock<Tin,TOut> would be:
var block=new TransformBlock<TIn,TOut>(msgIn=>func(msgIn));
foreach(....)
{
block.Post(someMessage);
}
To stop it, block.Complete() would be enough. Any pending messages would still be processed. A TransformBlock is meant to forward its output to other blocks, eg an ActionBlock or BufferBlock.
var finalBlock=new ActionBlock<TOut>(msgOut=>Console.WriteLine(msgOut));
block.LinkTo(finalBlock,new DataflowLinkOptions{PropagateCompletion = true});
...
block.Complete();
await finalBlock.Completion;
Using Channels
Doing something similar with channels doesn't need explicit classes, or Enqueue/Dequeue methods. Channels are build with separate ChannelReader, ChannelWriter interfaces to make it easier to control ownership, concurrency and completion.
A similar pipeline using channels would require only some methods:
ChannelReader<string> FolderToChannel(string path,CancellationToken token=default)
{
Channel<int> channel=Channel.CreateUnbounded();
var writer=channel.Writer;
_ = Task.Run(async ()=>{
foreach(var path in Directory.EnumerateFiles(path))
{
await _writer.SendAsync(path);
if (token.CancellationRequested)
{
return;
}
}
},token).ContinueWith(t=>_writer.TryComplete(t.Exception));
return channel;
}
This produces a reader that can be passed to a processing method. One that could generate another reader with the results:
static ChannelReader<MyClass> ParseFile(this ChannelReader<string> reader,CancellationToken token)
{
Channel<int> channel=Channel.CreateUnbounded();
var writer=channel.Writer;
_ = Task.Run(async ()=>{
await foreach(var path in reader.ReadAllAsync(token))
{
var json= await File.ReadAllTextAsync(path);
var dto= JsonSerializer.DeserializeObject<MyClass>(json);
await _writer.SendAsync(dto);
}
},token).ContineWith(t=>writer.TryComplete(t.Exception);
return channel;
}
And a final step that only consumes a channel:
static async Task LogIt(this ChannelReader<MyClass> reader,CancellationToken token)
{
await Task.Run(async ()=>{
await foreach(var dto in reader.ReadAllAsync(token))
{
Console.WriteLine(dto);
}
},token);
}
The three steps can be combined very easily:
var cts=new CancellationTokenSource();
var complete=FolderToChannel(somePath,cts.Token)
.ParseFile(cts.Token)
.LogIt(cts.Token);
await complete;
By encapsulating the channel itself and the processing in a method there's no ambiguity about who owns the channel, who is responsible for completion or cancellation
I create the BackgroundService:
public class CustomService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
//...
}
}
and I added to the project:
public class Startup
{
//...
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<CustomService>();
//...
}
//...
}
How can I find the CustomService from another class?
How to start it again?
Create an interface just for the call to StartAsync:
public interface ICustomServiceStarter
{
Task StartAsync(CancellationToken token = default);
}
public class CustomService : BackgroundService, ICustomServiceStarter
{
//...
Task ICustomServiceStarter.StartAsync(CancellationToken token = default) => base.StartAsync(token);
//...
}
Register the interface as a singleton:
public class Startup
{
//...
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddSingleton<ICustomServiceStarter, CustomService>();
}
//...
}
and inject ICustomServiceStarter when needed:
public class MyServiceControllerr : Controller
{
ICustomServiceStarter _starter;
public MyServiceController(ICustomServiceStarter starter)
{
_starter = starter;
}
[HttpPost]
public async Task<IActionResult> Start()
{
await _starter.StartAsync();
return Ok();
}
}
When it comes to controller's action, using "await BackgroundService.StartAsync" is the wrong way for long-running tasks.
For instance, the main ussue could be request's timeout depended on proxy settings.
Here is an example how to make your BackgroundService restartable.
BackgroundService implementation:
public class MyBackgroundService: BackgroundService
{
private volatile bool _isFinished = false;
private SemaphoreSlim _semaphore = new SemaphoreSlim(0,1);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_isFinished = false;
// DoSomeWork
_isFinished = true;
await WaitToRestartTask(stoppingToken);
}
private async Task WaitToRestartTask(CancellationToken stoppingToken)
{
// wait until _semaphore.Release()
await _semaphore.WaitAsync(stoppingToken);
// run again
await base.StartAsync(stoppingToken);
}
public void RestartTask()
{
if (!_isFinished)
throw new InvalidOperationException("Background service is still working");
// enter from _semaphore.WaitAsync
_semaphore.Release();
}
}
Controller's action (for instance):
public async Task<IActionResult> Restart()
{
var myBackgroundService= _serviceProvider.GetServices<IHostedService>()
.OfType<MyBackgroundService>()
.First();
try
{
myBackgroundService.RestartTask();
return Ok($"MyBackgroundService was restarted");
}
catch (InvalidOperationException exception)
{
return BadRequest(exception.Message);
}
}
For some part of my system I need to add retry logic for reading from the database. I have a number of repositories with async and sync read methods that I can't change. I found a simple solution - interception of all read methods with AsyncInterceptor and add retry read policy with Polly when database exception caught. Polly retries reading with some intervals.
Interceptor code:
public class RetriableReadAsyncInterceptor : IAsyncInterceptor
{
public void InterceptSynchronous(IInvocation invocation)
{
invocation.ReturnValue = InternalInterceptSync(invocation);
}
public void InterceptAsynchronous(IInvocation invocation)
{
throw new NotImplementedException();
}
public void InterceptAsynchronous<TResult>(IInvocation invocation)
{
invocation.ReturnValue = InternalInterceptAsync<TResult>(invocation);
}
private IEnumerable<TimeSpan> RetryIntervals =>
new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(15)
};
private object InternalInterceptSync(IInvocation invocation)
{
return Policy
.Handle<DatabaseException>()
.WaitAndRetry(RetryIntervals, (exception, timeSpan) =>
{
Console.WriteLine($"Exception {timeSpan}");
})
.Execute(() =>
{
invocation.Proceed();
return invocation.ReturnValue;
});
}
private async Task<TResult> InternalInterceptAsync<TResult>(IInvocation invocation)
{
return await Policy
.Handle<DatabaseException>()
.WaitAndRetryAsync(RetryIntervals, (exception, timeSpan) =>
{
Console.WriteLine($"Exception {timeSpan}");
})
.ExecuteAsync(async () =>
{
invocation.Proceed();
var task = (Task<TResult>)invocation.ReturnValue;
return await task;
});
}
}
Repository code:
public class Repository : IRepository
{
private int _exceptionsCoutner;
public Entity GetById(int id)
{
if (_exceptionsCoutner <= 2)
{
_exceptionsCoutner++;
throw new DatabaseException();
}
//read from db
return new Entity {Id = id};
}
public async Task<Entity> GetByIdAsync(int id)
{
if (_exceptionsCoutner <= 2)
{
_exceptionsCoutner++;
throw new DatabaseException();
}
//read from db
return await Task.FromResult(new Entity { Id = id });
}
}
Sync version of GetById works as expected (retries with intervals):
Exception 00:00:01
Exception 00:00:05
Exception 00:00:10
Async version of GetById retries but not waits for time interval elapsed:
Exception 00:00:01
Exception 00:00:01
Exception 00:00:01
I can't understand where is the problem. If you have any thoughts - please share.
Full example can be found here.
It was a kind of 'chicken and egg' problem which can be solved now with newer version of Castle.Core (I tried version 4.4.0) leveraging the invocation.CaptureProceedInfo method:
private Task<TResult> InternalInterceptAsync<TResult>(IInvocation invocation)
{
var capture = invocation.CaptureProceedInfo();
return Policy
.Handle<DatabaseException>()
.WaitAndRetryAsync(RetryIntervals, (exception, timeSpan) =>
{
Console.WriteLine($"Exception {timeSpan}");
})
.ExecuteAsync(async () =>
{
capture.Invoke();
var task = (Task<TResult>)invocation.ReturnValue;
return await task;
});
}
OK, here is my naive implementation of retry:
public class Retry
{
public static async Task<TResult> DoAsync<TResult, TException>(
Func<Task<TResult>> action,
TimeSpan retryInterval,
int maxAttemptCount = 3)
where TException : Exception
{
TException exception = null;
var startDateTime = DateTime.UtcNow;
for (var attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
return await action().ConfigureAwait(false);
}
catch (TException ex)
{
exception = ex;
Console.WriteLine($"Exception {DateTime.UtcNow - startDateTime}");
await Task.Delay(retryInterval); //doesnt work
//Thread.Sleep(retryInterval); works!
}
}
throw exception;
}
}
And interceptor:
private async Task<TResult> InternalInterceptAsync<TResult>(IInvocation invocation)
{
return await Retry.DoAsync<TResult, DatabaseException>(async () =>
{
invocation.Proceed();
var task = (Task<TResult>) invocation.ReturnValue;
return await task.ConfigureAwait(false);
},
TimeSpan.FromSeconds(3),
4);
}
Implementation with blocking Tread.Sleep works well, but with Task.Delay dont.
I'm currently trying to test the following code in an application that makes use of the Microsoft Bot Framework.
public async Task ResumeAfterCalculation_v2FormDialog(IDialogContext context, IAwaitable<Calculation_v2Form> result)
{
try
{
var extractedCalculationForm = await result;
//Removed additional code
}
catch (FormCanceledException ex)
{
var reply = "You have canceled the operation.";
await _chat.PostAsync(context, reply);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
context.Done<object>(null);
}
}
When a user types 'quit' to the bot the 'await result' code throws a FormCanceledException and the code quits the form.
When creating a test I implemented a class to mock the IAwaitable:
public class TaskAwaiterHelper<T> : IAwaiter<T>, IAwaitable<T>
{
public Task<T> Task { get; }
public TaskAwaiterHelper(T obj)
{
this.Task = System.Threading.Tasks.Task.FromResult(obj);
}
public TaskAwaiterHelper(Task<T> task)
{
this.Task = task;
}
public bool IsCompleted { get { return Task.IsCompleted; } }
public void OnCompleted(Action action)
{
SynchronizationContext context = SynchronizationContext.Current;
TaskScheduler scheduler = context == null ? TaskScheduler.Current
: TaskScheduler.FromCurrentSynchronizationContext();
Task.ContinueWith(ignored => action(), scheduler);
}
public T GetResult()
{
return Task.Result;
}
public IAwaiter<T> GetAwaiter()
{
return this;
}
}
I then created the following test:
[Fact]
public async Task ResumeAfterCalculation_v2FormDialog_WasCancelled_ThenCallsDone()
{
//Arrange
var chat = new Mock<IChatHelper>();
var calculationApi = new Mock<ICalculationApi>();
var dialogContextMock = new Mock<IDialogContext>();
var rootLuisDialog = new RootLuisDialog(chat.Object, calculationApi.Object);
var taskAwaiter = new TaskAwaiterHelper<Calculation_v2Form>(new Task<Calculation_v2Form>(() =>
{
throw new FormCanceledException("Error created for test test", null);
}));
taskAwaiter.Task.Start();
//Act
await rootLuisDialog.ResumeAfterCalculation_v2FormDialog(dialogContextMock.Object, taskAwaiter);
//Assert
chat.Verify(c => c.PostAsync(dialogContextMock.Object, "You have canceled the operation."), Times.Once());
dialogContextMock.Verify(t => t.Done<object>(null), Times.Once());
}
Now whatever I try to do I the exception that's being thrown in the IAwaitable is being wrapped in an AggregateException, so we always end up in the catch (Exception ex) instead of the desired catch (FormCanceledException ex)
Is there a way to make a Task throw a specific Exception instead of an AggregateException (I mean there should be as the bot framework itself seems to be able to do it).
I just found the answer, I basically created a new class:
public class ExceptionThrower : IAwaitable<Calculation_v2Form>
{
public IAwaiter<Calculation_v2Form> GetAwaiter()
{
throw new FormCanceledException("Error created for test test", null);
}
}
And just provided this to the method:
var exceptionThrower = new ExceptionThrower();
await rootLuisDialog.ResumeAfterCalculation_v2FormDialog(dialogContextMock.Object, exceptionThrower);
I wonder what I am doing wrong in the following implementation.
I cannot able to see loading dialog, even to opening the ClassroomViewModel takes few seconds.
public IMvxCommand ClassroomSelectedCommand => new MvxAsyncCommand<ClassroomViewModel>(ClassroomSelected);
private async Task ClassroomSelected(Model obj)
{
using (UserDialogs.Instance.Loading("Loading..."))
{
try
{
ShowViewModel<ClassroomViewModel>(new { Id = obj.Id });
}
catch (Exception ex)
{
}
}
}
You are using async APIs, use an MvxAsynCommand
private IMvxAsynCommand _classroomSelectedCommand;
public IMvxAsynCommand ClassroomSelectedCommand => _classroomSelectedCommand ?? (_classroomSelectedCommand = new MvxAsyncCommand<ClassroomViewModel>(ClassroomSelectedAsync));
private async Task ClassroomSelectedAsync(Model obj)
{
using (UserDialogs.Instance.Loading("Loading..."))
{
await Task.Delay(300);
try
{
ShowViewModel<ClassroomViewModel>(new { Id = obj.Id });
}
catch (Exception ex)
{
}
}
}