IHostedService - background task locks app - asp.net-core

I want to run task in different thread which will synchornize data, but it locks whole server (asp net core). i have no idea where i did something wrong.
I tried with tasks, and then with thread, but it always lock my app.
public interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
public class ScopedProcessingService : IScopedProcessingService
{
private int executionCount = 0;
private readonly ISynchronizationBackgroundService _syncService;
public ScopedProcessingService(ISynchronizationBackgroundService syncService)
{
_syncService= syncService;
}
public Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
_syncService.Synchronize();
}
catch (Exception ex)
{
Log.Error($"SYNCHRONIZATION ERROR {ex.Message} {ex.InnerException?.Message}");
}
Thread.Sleep(5000);
}
return Task.CompletedTask;
}
}
and the consumer
public class ConsumeScopedServiceHostedService : BackgroundService
{
public IServiceProvider Services { get; }
public ConsumeScopedServiceHostedService(IServiceProvider services)
{
Services = services;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
Thread t = new Thread(() =>
{
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
scopedProcessingService.DoWork(stoppingToken);
}
});
t.Start();
return Task.CompletedTask;
}
}
and in the end registration:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
services.AddScoped<ISynchronizationBackgroundService, SynchronizationBackgroundService>();
Would be grateful for any advice.

Related

Read ASP.NET Core logs per scope/operation

Let's say I have several ASP.NET BackgroundServices and each is logging to its own scope/operation (OP1 and OP2).
public class MyBackgroundService1 : BackgroundService
{
private readonly ILogger<MyBackgroundService1> _logger;
public MyBackgroundService1(ILogger<MyBackgroundService1> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var activity = new Activity("OP1");
activity.Start();
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Hello from MyBackgroundService1");
await Task.Delay(5000, stoppingToken);
}
}
}
public class MyBackgroundService2 : BackgroundService
{
private readonly ILogger<MyBackgroundService2> _logger;
public MyBackgroundService2(ILogger<MyBackgroundService2> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var activity = new Activity("OP2");
activity.Start();
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Hello from MyBackgroundService2");
await Task.Delay(1000, stoppingToken);
}
}
}
Now I would like to use Blazor and want to display a table per operation with all corresponding logs.
Example output
OP1 Logs:
Hello from MyBackgroundService1
Hello from MyBackgroundService1
OP2 Logs:
Hello from MyBackgroundService2
Hello from MyBackgroundService2
How would I do that?
For this purpose, you need to create a log provider that stores the information in the database and then retrieves the information from the log table.
First, create a class to store logs in the database as follows:
public class DBLog
{
public int DBLogId { get; set; }
public string? LogLevel { get; set; }
public string? EventName { get; set; }
public string? Message { get; set; }
public string? StackTrace { get; set; }
public DateTime CreatedDate { get; set; }=DateTime.Now;
}
Now, We need to create a custom DBLogger. The DBLogger class inherits from the ILogger interface and has three methods, the most important of which is the Log method, which is actually called every time the Logger is called in the program. To read more about the other two methods, you can refer here.
public class DBLogger:ILogger
{
private readonly LogLevel _minLevel;
private readonly DbLoggerProvider _loggerProvider;
private readonly string _categoryName;
public DBLogger(
DbLoggerProvider loggerProvider,
string categoryName
)
{
_loggerProvider= loggerProvider ?? throw new ArgumentNullException(nameof(loggerProvider));
_categoryName= categoryName;
}
public IDisposable BeginScope<TState>(TState state)
{
return new NoopDisposable();
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel >= _minLevel;
}
public void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception exception,
Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
if (formatter == null)
{
throw new ArgumentNullException(nameof(formatter));
}
var message = formatter(state, exception);
if (exception != null)
{
message = $"{message}{Environment.NewLine}{exception}";
}
if (string.IsNullOrEmpty(message))
{
return;
}
var dblLogItem = new DBLog()
{
EventName = eventId.Name,
LogLevel = logLevel.ToString(),
Message = $"{_categoryName}{Environment.NewLine}{message}",
StackTrace=exception?.StackTrace
};
_loggerProvider.AddLogItem(dblLogItem);
}
private class NoopDisposable : IDisposable
{
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
}
}
}
Now we need to create a custom log provider so that an instance of the above custom database logger (DBLogger) can be created.
public class DbLoggerProvider : ILoggerProvider
{
private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly IList<DBLog> _currentBatch = new List<DBLog>();
private readonly TimeSpan _interval = TimeSpan.FromSeconds(2);
private readonly BlockingCollection<DBLog> _messageQueue = new(new ConcurrentQueue<DBLog>());
private readonly Task _outputTask;
private readonly IServiceProvider _serviceProvider;
private bool _isDisposed;
public DbLoggerProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
_outputTask = Task.Run(ProcessLogQueue);
}
public ILogger CreateLogger(string categoryName)
{
return new DBLogger(this, categoryName);
}
private async Task ProcessLogQueue()
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
while (_messageQueue.TryTake(out var message))
{
try
{
_currentBatch.Add(message);
}
catch
{
//cancellation token canceled or CompleteAdding called
}
}
await SaveLogItemsAsync(_currentBatch, _cancellationTokenSource.Token);
_currentBatch.Clear();
await Task.Delay(_interval, _cancellationTokenSource.Token);
}
}
internal void AddLogItem(DBLog appLogItem)
{
if (!_messageQueue.IsAddingCompleted)
{
_messageQueue.Add(appLogItem, _cancellationTokenSource.Token);
}
}
private async Task SaveLogItemsAsync(IList<DBLog> items, CancellationToken cancellationToken)
{
try
{
if (!items.Any())
{
return;
}
// We need a separate context for the logger to call its SaveChanges several times,
// without using the current request's context and changing its internal state.
var scopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
var scopedProvider = scope.ServiceProvider;
using (var newDbContext = scopedProvider.GetRequiredService<ApplicationDbContext>())
{
foreach (var item in items)
{
var addedEntry = newDbContext.DbLogs.Add(item);
}
await newDbContext.SaveChangesAsync(cancellationToken);
// ...
}
}
}
catch
{
// don't throw exceptions from logger
}
}
[SuppressMessage("Microsoft.Usage", "CA1031:catch a more specific allowed exception type, or rethrow the exception",
Justification = "don't throw exceptions from logger")]
private void Stop()
{
_cancellationTokenSource.Cancel();
_messageQueue.CompleteAdding();
try
{
_outputTask.Wait(_interval);
}
catch
{
// don't throw exceptions from logger
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
try
{
if (disposing)
{
Stop();
_messageQueue.Dispose();
_cancellationTokenSource.Dispose();
}
}
finally
{
_isDisposed = true;
}
}
}
}
In the end, it is enough to call this custom log provider (DbLoggerProvider) in the Startup.cs or Program.cs class.
var serviceProvider = app.ApplicationServices.CreateScope().ServiceProvider;
loggerFactory.AddProvider(new DbLoggerProvider(serviceProvider));
From now on, every time we call the _logger.LogInformation("");, the log information will also be stored in the database.
Note: Because the number of calls to record logs in the database may be high, a concurrent queue is used to store logs.
If you like, you can refer to my repository that implements the same method.
In order to log the areas separately(scope/operation), you can create several different DBLoggers to store the information in different tables.

SignalR: Websocket closed with error: InternalServerError

On the front side when the user adds a new mp3. In addition to writing this to the database, I also need to transfer that mp3 to the client side. But my client side is Worker Service. I need to transfer this mp3 to that Worker Service via SignalR.
My SignalR server project codes :
Startup hub endpoint :
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action}/{id?}");
endpoints.MapHub<TrackHub>("/hubs/track");
});
Hub :
public class TrackHub : Hub
{
public static Dictionary<int, string> ActiveUsers = new Dictionary<int, string>();
public TrackHub()
{
}
public async Task SendMessage(string message)
{
var connectionId = Context.ConnectionId;
await Clients.Others.SendAsync("ReceiveMessage", message + connectionId);
}
public override async Task OnConnectedAsync()
{
await Clients.All.SendAsync("ActiveUsers");
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await Clients.All.SendAsync("ActiveUsers");
}
}
HubContext
public class TrackBusiness
{
private readonly IHubContext<TrackHub> _hubContext;
public TrackBusiness(IHubContext<TrackHub> hubContext)
{
_hubContext = hubContext;
}
public Task SendMessage()
{
return _hubContext.Clients.All.SendAsync("Receive");
}
}
And my client side - worker service project :
public class SocketService : ISocketService
{
private HubConnection? _connection;
private readonly IConfiguration _configuration;
private readonly ILogger<SocketService> _logger;
public event OnNewTrackAddedEventHandler NewTrackAdded;
public event OnDeviceDeletedEventHandler DeviceDeleted;
public SocketService(IConfiguration configuration, ILogger<SocketService> logger)
{
_configuration = configuration;
_logger = logger;
}
public async Task Start()
{
_connection = new HubConnectionBuilder()
.WithUrl(_configuration.GetValue<string>("SocketUrl"))
.WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.Zero, TimeSpan.FromSeconds(10) })
.Build();
_connection.Reconnected += connectionId =>
{
if (_connection.State == HubConnectionState.Connected)
{
_logger.LogInformation("Socket reconnected");
}
if (_connection.State == HubConnectionState.Reconnecting)
{
_logger.LogInformation("Socket try to reconnect");
}
return Task.CompletedTask;
};
_connection.On<string, byte[]>("SendMessage", (imei, track) =>
{
NewTrackAdded.Invoke(imei, track);
});
_connection.On<string>("DeviceDeleted", (imei) =>
{
DeviceDeleted.Invoke(imei);
});
try
{
await _connection.StartAsync();
_logger.LogInformation("Socket started");
}
catch (Exception e)
{
_logger.LogWarning("Socket can't connect : {0}", e.Message);
}
}
}
My appSettings :
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ApiUrl": "localhost",
"SocketUrl": "http://localhost:29082/hubs/track"
}
But I run into this error when I start the project. Can you help with the problem?

Blazor Server Page Unit Testing - How to mock secondary signalR client connection used in Blazor Server Page

I am a new user to bUnit and have managed to get some tests running for NavMenu to grasp the basic concepts.
However, a different Blazor page makes a request to a secondary signalR hub for communication of workflow state.
How to mock signalR connection?
https://github.com/dotnet/aspnetcore/issues/14924
Server Page that uses an additional signalR connection to communicate workflow state
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var hubUrl = NavigationManager.BaseUri.TrimEnd('/') + "/motionhub";
try
{
Logger.LogInformation("Index.razor page is performing initial render, connecting to secondary signalR hub");
hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.ConfigureLogging(logging =>
{
logging.AddConsole();
logging.AddFilter("Microsoft.AspNetCore.SignalR", LogLevel.Information);
})
.AddJsonProtocol(options =>
{
options.PayloadSerializerOptions = JsonConvertersFactory.CreateDefaultJsonConverters(LoggerMotionDetection, LoggerMotionInfo, LoggerJsonVisitor);
})
.Build();
hubConnection.On<MotionDetection>("ReceiveMotionDetection", ReceiveMessage);
hubConnection.Closed += CloseHandler;
Logger.LogInformation("Starting HubConnection");
await hubConnection.StartAsync();
Logger.LogInformation("Index Razor Page initialised, listening on signalR hub => " + hubUrl.ToString());
}
catch (Exception e)
{
Logger.LogError(e, "Encountered exception => " + e);
}
}
}
Stub Unit Test Class
public class IndexTest : TestContext, IDisposable
{
private MotionDetectionRepository repo;
public IndexTest()
{
var mock = new Mock<IMotionDetectionSerializer<MotionDetection>>();
repo = new MotionDetectionRepository(mock.Object, new NullLogger<MotionDetectionRepository>());
Services.AddScoped<ILogger<MotionDetectionRepository>, NullLogger<MotionDetectionRepository>>();
Services.AddScoped<ILogger<MotionInfoConverter>, NullLogger<MotionInfoConverter>>();
Services.AddScoped<ILogger<MotionDetectionConverter>, NullLogger<MotionDetectionConverter>>();
Services.AddScoped<ILogger<JsonVisitor>, NullLogger<JsonVisitor>>();
Services.AddScoped<ILogger<WebApp.Pages.Index>, NullLogger<WebApp.Pages.Index>>();
Services.AddScoped<IMotionDetectionRepository>(sp => repo);
Services.AddScoped<MockNavigationManager>();
Services.AddSignalR();
}
[Fact]
public void Test()
{
var cut = RenderComponent<WebApp.Pages.Index>();
Console.WriteLine($"{cut.Markup}");
}
}
I use these brokers.
public interface IHubConnectionsBroker
{
HubConnection CreateHubConnection(string endpoint, bool autoReconnect);
}
public interface IChatHubBroker
{
event EventHandler<ChatMessage> OnReceiveMessage;
event EventHandler<Exception> OnConnectionClosed;
event EventHandler<Exception> OnReconnecting;
event EventHandler<string> OnReconnected;
bool IsConnected { get; }
string ConnectionId { get; }
TimeSpan HandshakeTimeout { get; set; }
TimeSpan KeepAliveInterval { get; set; }
TimeSpan ServerTimeout { get; set; }
ChatHubBrokerState ChatHubBrokerState { get; }
ValueTask StartAsync();
ValueTask StopAsync();
ValueTask SendAsync(ChatMessage message);
ValueTask DisposeAsync();
}

Can a SignalR Hub receive events from clients? And if so, how?

I have a signalR hub that needs to be able to receive an event from a client and then notify all other clients connected to the hub.
Is that possible?
I want my 'hub' application to be able to receive messages and send them. I can only figure out how to do the sending of messages. Here is what I have now:
Application 1-- Hub
Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSignalR().AddHubOptions<EventsHub>(options =>
{
options.HandshakeTimeout = TimeSpan.FromMinutes(5);
options.EnableDetailedErrors = true;
});
services.AddTransient(typeof(BusinessLogic.EventsBusinessLogic));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseSignalR((configure) =>
{
configure.MapHub<EventsHub>("/hubs/events", (options) =>
{
});
});
}
Set Up of the Hub in Application 1
public class EventsHub : Hub
{
public EventsHub()
{
}
public override Task OnConnectedAsync()
{
if (UserHandler.ConnectedIds.Count == 0)
{
//Do something on connect
}
UserHandler.ConnectedIds.Add(Context.ConnectionId);
Console.WriteLine("Connection:");
return base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
//Do something on Disconnect
}
public static class UserHandler
{
public static HashSet<string> ConnectedIds = new HashSet<string>();
}
}
BusinessLogic:
public class EventsBusinessLogic
{
private readonly IHubContext<EventsHub> _eventsHub;
public EventsBusinessLogic(IHubContext<EventsHub> eventsHub)
{
_eventsHub = eventsHub;
}
public async Task<Task> EventReceivedNotification(ProjectMoonEventLog eventInformation)
{
try
{
await _eventsHub.Clients.All.SendAsync("NewEvent", SomeObject);
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
}
In the second application, that listens for events or messages from the hub:
Startup.cs
private static void ConfigureAppServices(IServiceCollection services, string Orale, string Sql)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddOptions();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
//set up of singletons and transients
services.AddHostedService<Events.EventingHubClient>();
}
The ClientHub to connect to application 1:
public class EventingHubClient : IHostedService
{
private HubConnection _connection;
public EventingHubClient()
{
_connection = new HubConnectionBuilder()
.WithUrl("http://localhost:61520/hubs/events")
.Build();
_connection.On<Event>("NewEvent",
data => _ = EventReceivedNotification(data));
}
public async Task<Task> EventReceivedNotification(Event eventInformation)
{
try
{
//Do something when the event happens
return Task.CompletedTask;
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// Loop is here to wait until the server is running
while (true)
{
try
{
await _connection.StartAsync(cancellationToken);
Console.WriteLine("Connected");
break;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
await Task.Delay(100);
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _connection.DisposeAsync();
}
}
This works, but now I want application 2 to be able to send a message to application 1? So I need a similar piece of code as in the EventsBusinessLogic class in application2 to send messages to application 1.
I hope this is clear enough? Is this the purpose of SignalR?
Please refer to signalR documentation signalR documentation for .net client
I guess in your Hub method like this
public async Task SendTransaction(Transaction data)
{
await Clients.All.SendAsync("TransactionReceived", data);
}
Then add methods in client side
in constructor add
connection.On<Transaction>("TransactionReceived", (data) =>
{
this.Dispatcher.Invoke(() =>
{
var transactionData = data;
});
});
and then SendTransaction expected on server
private async void SendTransaction(Transaction data)
{
try
{
await connection.InvokeAsync("SendTransaction", data);
}
catch (Exception ex)
{
//
throw
}
}

How to correctly handle errors in ASP.NET Core middleware?

I've created a HandlerMiddleware-class, which executes a handler for a specific URL.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace FailTest
{
public interface IHttpHandler
{
bool IsReusable { get; }
void ProcessRequest(HttpContext context);
}
public abstract class HandlerMiddleware<T>
where T : IHttpHandler
{
private readonly RequestDelegate _next;
public HandlerMiddleware()
{ }
public HandlerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await SyncInvoke(context);
}
catch (System.Exception ex)
{
System.Console.WriteLine(ex.Message);
throw;
}
}
public Task SyncInvoke(HttpContext context)
{
try
{
// IHttpHandler handler = (IHttpHandler)this;
T handler = System.Activator.CreateInstance<T>();
handler.ProcessRequest(context);
}
catch (System.Exception ex)
{
System.Console.WriteLine(ex.Message);
throw;
}
return Task.CompletedTask;
}
} // End Abstract Class HandlerMiddleware
}
Now I have a class which implements a handler, e.g.
[HandlerPath("/treedata", "GET,POST")]
public class SomeFailingTask
: HandlerMiddleware<SomeFailingTask>, IHttpHandler
{
public SomeFailingTask() : this(null)
{ }
public SomeFailingTask(RequestDelegate next) : base(next)
{ }
bool IHttpHandler.IsReusable
{
get { throw new System.NotImplementedException(); }
}
void IHttpHandler.ProcessRequest(HttpContext context)
{
// Do something in DB and return result as JSON
// e.g. SQL with invalid syntax, or a missing parameter
}
}
It works fine as long as there are no errors in ProcessRequest.
But when an exception is thrown in ProcessRequest, it finishes the request with HTTP 200 OK.
What am I doing wrong ?
I get the exception, in both Invoke and SyncInvoke, but the HTTP request always finished as if everything was fine...