Core 3.1 application
Totally at loss here.
I have 2 clients (phone and tablet) which connect to a signalR hub where I register them each as a group:
public class OrderHub : Hub
{
public string GetConnectionId()
{
return Context.ConnectionId;
}
public async void RegisterDeviceOnGroup() { //for example purposes just using tablet
await Groups.AddToGroupAsync(Context.ConnectionId, 'tablet');
}
}
Then when I communicate between the 2, I call a method on a controller where the hub as been injected:
private readonly IHubContext<OrderHub> _hubContext;
public CustomerRepository(IHubContext<OrderHub> hubContext)
{
_hubContext = hubContext;
}
private async Task<bool> BroadcastOrder() { //Broadcast to tablet
await _hubContext.Clients.Group('tablet).SendAsync("Message");
}
This works fine for a few minutes and then stops. I can 't see anything in logs or any reason why it would.
Can the injected hub context lose groups?
I guess the problem is in sending parameters , naming ...
However, you can use Custom hub filters to view the communication between the client and the server
public class CustomFilter : IHubFilter
{
public async ValueTask<object> InvokeMethodAsync(
HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object>> next)
{
Console.WriteLine($"Calling hub method '{invocationContext.HubMethodName}'");
try
{
return await next(invocationContext);
}
catch (Exception ex)
{
Console.WriteLine($"Exception calling '{invocationContext.HubMethodName}': {ex}");
throw new HubException(ex.Message);
throw;
}
}
// Optional method
public Task OnConnectedAsync(HubLifetimeContext context, Func<HubLifetimeContext, Task> next)
{
return next(context);
}
// Optional method
public Task OnDisconnectedAsync(
HubLifetimeContext context, Exception exception, Func<HubLifetimeContext, Exception, Task> next)
{
return next(context, exception);
}
}
and add IServiceCollection
hubOptions.AddFilter<CustomFilter>();
you can see enter link description here
I hope that help
Related
Should catching exceptions be part of the business logic such as the Service layer, or should they be caught in the controllers' methods?
For example:
Controller UpdateUser method
[HttpPut]
[Route("{id}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public async Task<ActionResult<UserDto>> UpdateUserInfo(int id, UserDto userRequest)
{
try
{
var user = _userMapper.ConvertToEntity(userRequest);
var updatedUser = await _userService.UpdateAsync(user, id);
var result = _userMapper.ConvertToUserDto(updatedUser);
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError("Exception caught attempting to update user - Type: {ex}", ex.GetType());
_logger.LogError("Message: {ex}", ex.Message);
return StatusCode(500, ex.Message);
}
}
The Service Layer
public async Task<User> UpdateAsync(User user, int id)
{
await _repository.UpdateAsync(user, id);
return user;
}
So, should the exceptions be caught in the service layer or the controller? Or is it subjective?
It's dependent on the business of your application. maybe in your service you should use a try/catch block to adding a log or do anything when exception occurred. but usually I use a global try/catch in a middleware to get exception and send correct response to the client.
public class AdvancedExceptionHandler
{
private readonly RequestDelegate _next;
private readonly ILogger<AdvancedExceptionHandler> _logger;
private readonly IWebHostEnvironment _env;
public AdvancedExceptionHandler(RequestDelegate next, ILogger<AdvancedExceptionHandler> logger, IWebHostEnvironment env)
{
_next = next;
_logger = logger;
_env = env;
}
public async Task Invoke(HttpContext context)
{
string message = null;
HttpStatusCode httpStatusCode = HttpStatusCode.InternalServerError;
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex.Message, ex);
if (_env.IsDevelopment())
{
var dic = new Dictionary<string, string>
{
["StackTrace"] = ex.StackTrace,
["Exception"] = ex.Message
};
message = JsonConvert.SerializeObject(dic);
}
else
{
message = "an error has occurred";
}
await WriteToReponseAsync();
}
async Task WriteToReponseAsync()
{
if (context.Response.HasStarted)
throw new InvalidOperationException("The response has already started");
var exceptionResult = new ExceptionResult(message, (int)httpStatusCode);
var result = JsonConvert.SerializeObject(exceptionResult);
context.Response.StatusCode = (int)httpStatusCode;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(result);
}
}
}
ExceptionResutl class:
public class ExceptionResult
{
public ExceptionResult(string message, int statusCode)
{
this.Message = message;
this.StatusCode = statusCode;
}
public string Message { get; set; }
public int StatusCode { get; set; }
}
public static class ExceptionHandlerMiddlewareExtension
{
public static void UseAdvancedExceptionHandler(this IApplicationBuilder app)
{
app.UseMiddleware<AdvancedExceptionHandler>();
}
}
Then adding middleware in Configure method
public void Configure(IApplicationBuilder app)
{
app.UseAdvancedExceptionHandler();//<--NOTE THIS
}
I don't use try/catch block in controllers. (my opinion)
Catching exceptions in your controller will quickly start to violate some clean code principles, like DRY.
If I understand correctly, the example you have written is that you want to log some errors in case any exceptions are thrown in your code. This is reasonable, but if you begin to add more endpoints, you'll notice you have the same try/catch in all your controller methods. The best way to refactor this is to use a middleware that will catch the exception and map it to a response that you want.
Over time as you begin to update your application to have more features you may have a situation where multiple endpoints are throwing similar errors and you want it to be handled in a similar way. For example, in your example, if the user doesn't exist, the application (in your service layer) may throw an UserNotFoundException, and you may have some other endpoints which can throw the same error, too.
You could create another middleware to handle this or even extend your existing middleware.
One of the better approaches I've seen over the years is to use this library https://github.com/khellang/Middleware/tree/master/src/ProblemDetails to handle the boiler plate for you.
I'm calling UseExceptionHandler in order to handle errors. This works fine, but not for errors thrown by other (subsequently registered) middleware.
The middleware which exceptions I need to handle is TransactionMiddleware. What it does is to save any changes to the database after a successfully completed call to an action. To be clear - it doesn't just commit a transaction, but also runs all the insert:s/update:s etc. This might fail, for example due to database constraints. (There are also other reasons not to complete the transaction, but they are not included here. Just mentioning that to explain that making the database calls earlier and reducing the TransactionMiddleware to simply commiting won't do the trick.)
Is there a way to NOT start the response before this middleware has run its full course?
My Program.cs
var builder = WebApplication.CreateBuilder(args);
new Startup(builder.Configuration).ConfigureServices(builder.Services);
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
// await ExcludedCode()...
});
});
app.UseSwagger()
.UseSwaggerUI();
app.UseRouting()
.UseCors()
.UseAuthentication()
.UseAuthorization()
.UseMiddleware<LanguageMiddleWare>()
.UseMiddleware<TransactionMiddleWare>()
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
My (simplified) TransactionMiddleWare-class
public class TransactionMiddleWare
{
private readonly RequestDelegate next;
public TransactionMiddleWare(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context, IDataContext dataContext)
{
try
{
await next(context);
}
catch (PartialExecutionException)
{
this.Commit(context, dataContext);
throw;
}
this.Commit(context, dataContext);
}
private void Commit(HttpContext context, IDataContext dataContext)
{
if (this.ShouldTransactionCommited(context))
dataContext.SaveChanges();
else
throw new Exception("Exception example for clarity.");
}
private bool ShouldTransactionBeCommited(HttpContext context)
{
return true; // Actual code omitted for brevity.
}
}
Example of how my controllers return data (no special stuff):
[ApiController]
[Route("advertisment")]
public class AdvertismentController : ControllerBase
{
private readonly IAdvertismentService advertismentService;
private NLog.ILogger log;
public AdvertismentController(
IAdvertismentService advertismentService)
{
this.log = LogManager.GetCurrentClassLogger();
this.advertismentService = advertismentService;
}
[HttpPost]
public Result<Guid> Create([FromForm] CreateAdvertismentMultipartFormModel request)
{
var id = this.advertismentService.Create(request);
return new Result<Guid> { Data = id };
}
}
Here is what I ended up with. A change in the TransactionMiddleWare class:
public async Task Invoke(HttpContext context, IDataContext dataContext)
{
context.Response.OnStarting(state => {
this.Commit(context, dataContext);
return Task.CompletedTask;
}, context);
try
{
await next(context);
this.Commit(context, dataContext);
}
catch (PartialExecutionException)
{
this.Commit(context, dataContext);
throw;
}
}
That way it will be run and any exception will occur while it's still possible to modify the output and produce an error message.
Is there a way to access the request and response object in an azure middle ware.
Using a tutorial for a logging middleware I already got this far:
public class ExceptionLoggingMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
// Code before function execution here
await next(context);
// Code after function execution here
}
catch (Exception ex)
{
var log = context.GetLogger<ExceptionLoggingMiddleware>();
log.LogWarning(ex, string.Empty);
}
}
}
but I want to access the response and request object too. Like status code, body parameters, query parameters etc. Is this possible?
While there is no direct way to do this, but there is a workaround for accessing HttpRequestData (Not the best solution but it should work until there is a fix.):
public static class FunctionContextExtensions
{
public static HttpRequestData GetHttpRequestData(this FunctionContext functionContext)
{
try
{
KeyValuePair<Type, object> keyValuePair = functionContext.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
object functionBindingsFeature = keyValuePair.Value;
Type type = functionBindingsFeature.GetType();
var inputData = type.GetProperties().Single(p => p.Name == "InputData").GetValue(functionBindingsFeature) as IReadOnlyDictionary<string, object>;
return inputData?.Values.SingleOrDefault(o => o is HttpRequestData) as HttpRequestData;
}
catch
{
return null;
}
}
}
And you can use it like this:
public class CustomMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
HttpRequestData httpRequestData = context.GetHttpRequestData();
// do something with httpRequestData
await next(context);
}
}
Check out this for more details.
For Http Response, there is no workaround AFAIK. Further, check out GH Issue#530, that says that documentation for this will be added soon. This capability looks like a popular demand and expected to be fixed soon (at the time of writing this).
Environment
Windows 10 Professional
.NET Core Console Application
Code
I have an abstracted message receiver that looks like this. In this code the entity is the name of the Subscription (e.g. user).
public class AzureMessageReceiver : ITdlMessageReceiver
{
private readonly ServiceBusConnection serviceBusConnection;
private readonly ILogger<AzureMessageReceiver> logger;
public AzureMessageReceiver(ServiceBusConnection serviceBusConnection, ILogger<AzureMessageReceiver> logger)
{
this.serviceBusConnection = serviceBusConnection;
this.logger = logger;
}
public async Task<TdlMessage<T>> ReceiveAsync<T>(string topic, string entity) where T : class
{
try
{
var subscriptionPath = EntityNameHelper.FormatSubscriptionPath(topic, entity);
var messageReceiver = new MessageReceiver(serviceBusConnection, subscriptionPath, ReceiveMode.ReceiveAndDelete);
var message = await messageReceiver.ReceiveAsync();
if (message == null)
{
return null;
}
var messageString = Encoding.UTF8.GetString(message.Body);
return JsonConvert.DeserializeObject<TdlMessage<T>>(messageString);
}
catch (Exception ex)
{
logger.LogError(ex, "Error receiving Azure message.");
return null;
}
}
}
The injected ServiceBusConnection is constructed like this. NOTE: this same connection initialization works to write messages to the same Topic and Subscription.
services.AddSingleton(serviceProvider =>
new ServiceBusConnection(configuration[$"{DurableCommunicationKey}:AzureConnectionString"]));
UPDATE: here is the code that wraps the call to the receiver class and is the controller for receiving messages:
static async void Receive(ITdlMessageReceiver receiver, ILogger logger)
{
while (true)
{
var message = await receiver.ReceiveAsync<TdlMessage<object>>(topic, entity);
if (message != null)
{
logger.LogDebug($"Message received. Topic: {topic}. Action: {Enum.GetName(typeof(TopicActions), message.Action)}. Message: {JsonConvert.SerializeObject(message)}.");
}
Thread.Sleep(sleepTime);
}
}
Problem
Every time I execute this line var message = await messageReceiver.ReceiveAsync(); it just crashes the Console app. No Exception and nothing in Event Viewer.
What I've Tried
Using the Secondary Connection String from the ASB
Providing a timeout like messageReceiver.ReceiveAsync(TimeSpan.FromMinutes(1));
Changing the injected topic from just the name of the topic to the entire URL of the topic (e.g. https://{...}.servicebus.windows.net/{topicName})
Changing the ReceiveMode to PeekLock
Tacking on ConfigureAwait(false) to the ReceiveAsync call.
Changing the timeout to TimeSpan.Zero. NOTE: this does not crash the app but actually throws an Exception that gets logged.
async void should be converted to an async Task as well as you should be awaiting Task.Delay instead of invoking Thread.Sleep. If going async you need to go async all the way
static async Task Receive(ITdlMessageReceiver receiver, ILogger logger) {
while (true) {
var message = await receiver.ReceiveAsync<TdlMessage<object>>(topic, entity);
if (message != null) {
logger.LogDebug($"Message received. Topic: {topic}. Action: {Enum.GetName(typeof(TopicActions), message.Action)}. Message: {JsonConvert.SerializeObject(message)}.");
}
await Task.Delay(sleepTime);
}
}
Try making the code async all the way through, yes, but as a console application (single thread) you will be allowed to call Wait() on the Receive method in Main as it is not mixing calls that would cause problem with the async flow.
public static void Main(string[] args) {
//...
//...
//...
Receive(receiver, logger).Wait();
}
Reference Async/Await - Best Practices in Asynchronous Programming
Is there a way to find out the number of listeners (clients connected to a hub?)
I'm trying to run/start a task if at least one client is connected, otherwise do not start it:
[HubName("taskActionStatus")]
public class TaskActionStatus : Hub, IDisconnect
{
static CancellationTokenSource tokenSource;
public void GetTasksStatus(int? siteId)
{
tokenSource = new CancellationTokenSource();
CancellationToken ct = tokenSource.Token;
ITaskRepository taskRepository = UnityContainerSetup.Container.Resolve<ITaskRepository>();
// init task for checking task statuses
var tasksItem = new DownloadTaskItem();
taskRepository.GetTasksStatusAsync(siteId, tasksItem, ct);
// subscribe to event [ listener ]
tasksItem.Changed += new EventHandler<TaskEventArgs>(UpdateTasksStatus);
}
public void UpdateTasksStatus(object sender, TaskEventArgs e)
{
Clients.updateMessages(e.Tasks);
}
// when browsing away from page
public Task Disconnect()
{
try
{
tokenSource.Cancel();
}
catch (Exception)
{
//
}
return null;
}
}
thanks
There is no way to get this count from SignalR as such. You have to use the OnConnect() and OnDisconnect() methods on the Hub to keep the count yourself.
Simple example with a static class to hold the count:
public static class UserHandler
{
public static HashSet<string> ConnectedIds = new HashSet<string>();
}
public class MyHub : Hub
{
public override Task OnConnectedAsync()
{
UserHandler.ConnectedIds.Add(Context.ConnectionId);
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
UserHandler.ConnectedIds.Remove(Context.ConnectionId);
return base.OnDisconnectedAsync(exception);
}
}
You then get the count from UserHandler.ConnectedIds.Count.
For version 2.1.1< it should be:
public static class UserHandler
{
public static HashSet<string> ConnectedIds = new HashSet<string>();
}
public class MyHub : Hub
{
public override Task OnConnected()
{
UserHandler.ConnectedIds.Add(Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
UserHandler.ConnectedIds.Remove(Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
}
In SIgnalR(version 2.4.1) it is rather easy:
public int GetOnline()
{
return GlobalHost.DependencyResolver.Resolve<ITransportHeartbeat>().GetConnections().Count;
}
Just Invoke this method from client (:
Now you need:
public override Task OnConnectedAsync()
{
UserHandler.ConnectedIds.Add(Context.ConnectionId);
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
UserHandler.ConnectedIds.Remove(Context.ConnectionId);
return base.OnDisconnectedAsync(exception);
}
I would like to add that if you are using a serverless solution with Azure Functions and Azure SignalR Service there is the following resolved issue: https://github.com/Azure/azure-functions-signalrservice-extension/issues/54
It refers to this sample where you can use Event Grids to get the real-time count of connections, pretty sweet. https://github.com/aspnet/AzureSignalR-samples/tree/master/samples/EventGridIntegration
In my project which use Microsoft.AspNetCore.SignalR.Core version 1.1.0
I can debug and see the count with
((Microsoft.AspNetCore.SignalR.DefaultHubLifetimeManager<XXX.Push.SignalR.EventHub>)((Microsoft.AspNetCore.SignalR.Internal.AllClientProxy<XXX.Push.SignalR.EventHub>)((Microsoft.AspNetCore.SignalR.TypedClientBuilder.IEventImpl)((Microsoft.AspNetCore.SignalR.Internal.HubClients<XXX.Push.SignalR.EventHub, XXX.Push.SignalR.IEvent>)_hub.Clients).All)._proxy)._lifetimeManager)._connections.Count
It looks like this: