SaveChangesOnDbContextAsync has never been called - asp.net-core

ABP has added an overridable function called SaveChangesOnDbContextAsync that allow us to intercept any insert/update/delete operation as in this link: https://github.com/abpframework/abp/issues/4659
I tried to override SaveChangesOnDbContextAsync in a class that inherits AbpDbContext like the following but it has never been called whenever I update the data:
public override async Task<int> SaveChangesOnDbContextAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
var result = await base.SaveChangesAsync();
return result;
}

That method is provided for your application code to bypass the logic in AbpDbContext.SaveChangesAsync(). ABP does not call it.
This is what the requestor meant:
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
try
{
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
catch (AbpDbConcurrencyException)
{
// Handle it
// ...
// Directly call Microsoft.EntityFrameworkCore.DbContext's SaveChangesAsync(bool, CancellationToken) to not repeatedly
// create audit logs, update concurrencystamps, or trigger entity events etc. as AbpDbContext's version would do
return await base.SaveChangesOnDbContextAsync(acceptAllChangesOnSuccess, cancellationToken);
}
}
You probably just want to override SaveChangesAsync instead.

Related

Can I pass CancellationToken as parameter in AspNetCore WebAPI

As far as I know if I use services.AddControllers() or services.AddMvc()extension in my Startup.cs "MVC will automatically bind any CancellationToken parameters in an action method.
I have the following TestController and TestService as Transient service.
According to this informations, when the auto-binded CancellationToken IsCancellationRequested will the tokens that I have passed as parameters be also canceled?
public class TestController : ControllerBase
{
private readonly ITestService _testService;
public TestController(ITestService testService)
{
_testService = testService;
}
[HttpGet, ActionName("Get")]
public async Task<IActionResult> GetAsync(CancellationToken cancellationToken)
{
await _testService.GetAsync(cancellationToken);
return Ok();
}
}
public class TestService : ITestService
{
public async Task GetAsync(CancellationToken cancellationToken)
{
//I also send the cancellationToken as a parameter to external API calls
}
}
when the auto-binded CancellationToken IsCancellationRequested will
the tokens that I have passed as parameters be also canceled?
By injecting a CancellationToken into your action method, which will be automatically bound to the HttpContext.RequestAborted token for the request. After the request is cancelled by the user refreshing the browser or click the "stop" button, the original request is aborted with a TaskCancelledException which propagates back through the MVC filter pipeline, and back up the middleware pipeline. Then, You could check the value of IsCancellationRequested and exit the action gracefully. In this scenario, there is no need to transfer the CancellationToken as parameters.
If you want to Cancel an Async task, since, CancellationTokens are lightweight objects that are created by a CancellationTokenSource. When a CancellationTokenSource is cancelled, it notifies all the consumers of the CancellationToken. So, you could call the Cancel() method to cancel the task.
Check the following sample:
var cts = new CancellationTokenSource();
//cts.CancelAfter(5000);//Request Cancel after 5 seconds
_logger.LogInformation("New Test start");
var newTask = Task.Factory.StartNew(state =>
{
int i = 1;
var token = (System.Threading.CancellationToken)state;
while (true)
{
Console.WriteLine(i);
i++;
if (i == 10)
{
cts.Cancel(); //call the Cancel method to cancel.
}
_logger.LogInformation("thread running " + DateTime.Now.ToString());
Thread.Sleep(1000);
token.ThrowIfCancellationRequested();
}
}, cts.Token, cts.Token);
try
{
newTask.Wait(10000);
}
catch
{
Console.WriteLine("Catch:" + newTask.Status);
}
_logger.LogInformation("Test end");
More detail information about using CancellationToken, please check the following articles:
Using CancellationTokens in ASP.NET Core MVC controllers

How to set custom DelegatingHandler to all HttpClients automatically?

I would like to use the LoggingHttpClientHandler for all the clients in the application. I found the only usable way via named/typed client
startup.cs
services.AddTransient<LoggingHttpClientHandler>();
services.AddHttpClient("clientWithLogging").AddHttpMessageHandler<LoggingHttpClientHandler>();
and then in each service I have to use var client = _clientFactory.CreateClient("clientWithLogging") which is kinda uncomfortable.
LoggingHttpClientHandler.cs
public class LoggingHttpClientHandler : DelegatingHandler
{
public LoggingHttpClientHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Content != null)
{
Logging.Log.Info(await request.Content.ReadAsStringAsync());
}
return await base.SendAsync(request, cancellationToken);
}
}
Is there a way how to do it without naming each client?
If it is just about the name, there is an extension method CreateClient that takes no arguments and uses the default name, which is string.Empty.
So you might be fine by registering the service using string.Empty as name, e.g.:
services.AddHttpClient(string.Empty).AddHttpMessageHandler<LoggingHttpClientHandler>();
and instantiate clients using:
var client = _clientFactory.CreateClient();
The problem was with resolving of params in useless constructor of LoggingHttpClientHandler.
So I removed the constructor.

Running multiple backend services using iHostedService

Currently my web API is able to run on a schedule and trigger another end point in order to sync data. The services that needs to be called are stored in a yml file. I have managed to get it working for one service to run a schedule. What I want is to be able to save multiple endpoints with schedules of their own and for them to be scheduled and executed at the right time.
Here is the code that I have now
I have done this using iHostedService interface.
This is the HostService class that implements iHostedService
public abstract class HostedService : IHostedService
{
private Task _executingTask;
private CancellationTokenSource _cts;
public Task StartAsync(CancellationToken cancellationToken)
{
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_executingTask = ExecuteAsync(_cts.Token);
// If the task is completed then return it, otherwise it's running
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
// Signal cancel
_cts.Cancel();
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
cancellationToken.ThrowIfCancellationRequested();
}
// cancel
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
}
I am then extending this class and implementing what needs to be done in the ExecuteAsync as follows
public class DataRefreshService : HostedService
{
private readonly DataFetchService _dataFetchService;
public DataRefreshService(DataFetchService randomStringProvider)
{
_dataFetchService = randomStringProvider;
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
await _dataFetchService.UpdateData(cancellationToken);
TimeSpan span = _dataFetchService.GetNextTrigger();
await Task.Delay(span, cancellationToken);
}
} catch (Exception)
{
await StopAsync(cancellationToken);
throw new Exception("Error trigger Sync service");
}
}
}
This is what I have added to the Startup.cs file
services.AddSingleton<DataFetchService>();
services.AddSingleton<IHostedService, DataRefreshService>();
You could try
services.AddHostedService<DataRefreshService>;
You could also try in making the DataRefreshService inherit from
Microsoft.Extensions.Hosting.BackgroundService
You can read more about that here

Access IUrlHelper inside DelegatingHandler

Following a migration to ASP.Net core, the following handler does not work. I cannot see how to get access to IUrlHelper from the HttpRequestMessage as was previously possible, and can't find a package with relevant extension methods.
The handler is added using config.MessageHandlers.Add(new LinkDecoratorHandler());
Can anyone help?
public class LinkDecoratorHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage
request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
var response = task.Result;
if (!(response.Content is ObjectContent))
{
return response;
}
var entity = (response.Content as ObjectContent).Value as ILinkedEntity;
var enumeration = (response.Content as ObjectContent).Value as IEnumerable<ILinkedEntity>;
if (entity != null || enumeration != null)
{
//no longer available
var helper = request.GetUrlHelper();
//blah
}
return response;
});
}
}
Thanks in advance
If your LinkDecoratorHandler is instantiated via dependency injection then you could inject an instance of IActionContextAccessor to get the current ActionContext. From there, you can create your own UrlHelper instance.

WCF async pattern Begin/End with tasks block the GUI of the client

I have an async method in a WCF service, and a client that consume this method. When I call the method, the GUI of the client is blocked until the async method finish. My code is the following:
SERVICE
public IAsyncResult BeginAsyncMethod(CustomClass paramCustomClass, AsyncCallback callback, object state)
{
Task<bool> task = Task<bool>.Factory.StartNew(p => slowMethod(paramCustomClass, state);
return task.ContinueWith(res => callback(task));
}
public bool EndAsyncMethod(IAsyncResult paramResult)
{
return ((Task<bool>)paramResult).Result;
}
slowMethod is a dummy method with a for from i = 0 to 1000000000.
CLIENT
private void callAsyncMethod()
{
Task<bool> task = Task<bool>.Factory.FromAsync(_proxy.Proxy.BeginAsyncMethod, _proxy.Proxy.EndAsyncMethod, CustomClass, null);
bool hasFinished= task.Result;
}
In the client, I have a button that calls the callAsyncMethod, and when I click the button, the GUI gets bloked while the slowMethod is running.
If I am not wrong, the async methods return the control to the caller to avoid the program was blocked, but in my case this does not occur. have I misunderstood something? I am using in the wrong way the async methods?
Thanks.
Daimroc.
Even though the methods are asynchronous, you're waiting for the result to return, effectively blocking the calling thread (UI). When you access the Result property of the Task object, if the task hasn't been completed, it will block until the result is available.
You should call the asynchronous method asynchronously. If you're using .NET 4.5, you can use the await keyword:
private async void callAsyncMethod()
{
Task<bool> task = Task<bool>.Factory.FromAsync(_proxy.Proxy.BeginAsyncMethod, _proxy.Proxy.EndAsyncMethod, CustomClass, null);
bool hasFinished = await task;
}
Otherwise you'll really need to make the call asynchronous
private void callAsyncMethod() {
_proxy.Proxy.BeginAsyncMethod(CustomClass, null, MyCallback, _proxy);
}
private void MyCallback(IAsyncResult asyncResult) {
bool hasFinished = _proxy.Proxy.EndAsyncMethod(asyncResult);
}