I am new to SignalR and building a live chat by using .NET 5 and angular 13. While establishing the connection it is giving this error-- Connection closed with an error. NullReferenceException
After multiple debagging I found out what is the root cause of the problem. When I inject any dependency to my message hub. NullReferenceException error is happened. without dependency the connection is established properly.
here is my MessageHub.cs code (with dependency)
public class MessageHub : Hub
{
private readonly IMessageRepository _messageRepository;
private readonly IUserRepository _userRepository;
public MessageHub(IMessageRepository messageRepository, IUserRepository userRepository)
{
_messageRepository = messageRepository;
_userRepository = userRepository;
}
public override async Task OnConnectedAsync()
{
var httpContext = Context.GetHttpContext();
var otherUser = httpContext.Request.Query["user"].ToString();
var groupName = GetGroupName(Context.User.GetUsername(), otherUser);
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await base.OnDisconnectedAsync(exception);
}
}
Startup.cs (ConfigureServices)
services.AddSignalR(e=>
{
e.EnableDetailedErrors = true;
});
Startup.cs (Configure)
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<MessageHub>("hubs/message");
});
I extremely need this dependency to complete my functionality. I got stack with this. If I remove the Dependency it works fine. I tried to find some solution from various sources but didn't work for me. if anyone help me. I will be really appreciate for this.
After doing some study, a solution I applied that worked for me. We Can Retrieve service object. so in startup.cs what I did given below
Startup.cs (ConfigureServices)
//we can retrieve service object
public static IServiceProvider serviceProvider;
services.AddSignalR()
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IMessageRepository, MessageRepository>();
In my MessageHub.cs instead of using Dependency Injection I Retrieve Required Service and define it in the filed like below
MessageHub.cs
public class MessageHub : Hub
{
private readonly IMessageRepository _messageRepository;
private readonly IUserRepository _userRepository;
public MessageHub()
{
_messageRepository = (IMessageRepository)Startup.serviceProvider.GetRequiredService(typeof(IMessageRepository));
_userRepository = (IUserRepository)Startup.serviceProvider.GetRequiredService(typeof(IUserRepository));
}
// other code....
}
It worked for me. Now I can implement my necessary method from IRepository and SignalR also working properly.
Related
I'm currently working on an ASP.NET Core Web Application.
I have a MQTT Server, which is connected to a service (IHostedService) and this service references a SignalR Hub.
So if there is a new message comming from the MQTT Server, it is forwarded to the hub and therefore to the client.
This works fine. But now I would like to add a button to send MQTT messages back to the MQTT server.
To do so, I added a function in the hub, which es called by the button via SignalR.
So far so good but when adding the service now to the constructor of the hub it fails, when I open the web app (not during startup), with the following message:
fail: Microsoft.AspNetCore.SignalR.HubConnectionHandler[1]
Error when dispatching 'OnConnectedAsync' on hub.
System.InvalidOperationException: Unable to resolve service for type 'websiteApp.HostedServices.UserPromptService' while attempting to activate 'websiteApp.Hubs.UserPromptHub'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
at lambda_method(Closure , IServiceProvider , Object[] )
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubActivator'1.Create()
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher'1.OnConnectedAsync(HubConnectionContext connection)
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher'1.OnConnectedAsync(HubConnectionContext connection)
at Microsoft.AspNetCore.SignalR.HubConnectionHandler'1.RunHubAsync(HubConnectionContext connection)
The service declaration looks like this:
public class UserPromptService : IHostedService, IDisposable
{
public UserPromptService(ILogger<UserPromptService> logger, IConfiguration config, UserPromptContext userPromptContext, IHubContext<UserPromptHub> userPromptHub)
{
}
}
And my hub looks like this:
public class UserPromptHub : Hub<IUserPromptHub>
{
public UserPromptHub(UserPromptService service) // everything works until I add the service here
{
service.ToString(); // just for testing
}
}
And they are configured in the Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ...
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapHub<Hubs.UserPromptHub>("/userPromptHub");
});
}
As well as in the Program.cs:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
// ...
.ConfigureServices(services =>
{
services.AddSingleton<websiteApp.DataContext.UserPromptContext>();
services.AddHostedService<HostedServices.UserPromptService>();
});
Could you please help me to fix the problem?
One option to solve your problem would be to restructure your code a little bit.So instead of your UserPromptService be responsable for the MQTT connection you create a seperate class for that.
The following is only sudo code
You could create a new class
public class MQTTConnection
{
private readonly _yourMQTTServerConnection;
public MQTTConnection()
{
_yourMQTTServerConnection = new ServerConnection(connectionstring etc);
}
public Task<Message> GetMessage()
{
return _yourMQTTServerConnection.GetMessageAsync();
}
public Task SendMessage(Message message)
{
return _yourMQTTServerConnection.SendMessageAsync(message);
}
}
So your Hub look something like this
public class UserPromptHub : Hub<IUserPromptHub>
{
private readonly MQTTConnection _connection;
public UserPromptHub(MQTTConnection connection)
{
_connection = connection
}
public async Task MessageYouReceiveFromTheUser(object object)
{
// your business logic
await _connection.SendMessage(message);
}
public Task MessageYouSendToTheClient(object object)
{
await Clients.All.MessageYouSendToTheClient(message);
}
}
And your UserPromptService looks somehting like that
public class UserPromptService : IHostedService, IDisposable
{
public UserPromptService(ILogger<UserPromptService> logger,
IConfiguration config,
UserPromptContext userPromptContext,
IHubContext<UserPromptHub> userPromptHub,
MQTTConnection connection)
{
// map arguments to private fields
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while(yourAppIsUpAndRunning)
{
var message = await _connection.GetMessage()
// process the message
await _hub.MessageYouSendToTheClient(yourMessage)
}
}
}
I hope my approach is understandable, if not I can add more details.
I am making a web API using ASP.NET Core and now I am having a problem with quartz scheduled jobs. The jobs I have will access my services to update the database. After some researches, I figured how to do the dependency injection so that my jobs can access the services, here is how I overrode the job factory:
public class AspNetCoreJobFactory : SimpleJobFactory
{
IServiceProvider _provider;
public AspNetCoreJobFactory(IServiceProvider provider)
{
_provider = provider;
}
public override IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
try
{
return (IJob)this._provider.GetService(bundle.JobDetail.JobType);
}
catch(Exception e)
{
throw new SchedulerException(string.Format("Problem while instantiating job '{0}' from the AspNet Core IOC.", bundle.JobDetail.Key), e);
}
}
}
and I added this line on my startup configure:
_quartzScheduler.JobFactory = new AspNetCoreJobFactory(app.ApplicationServices);
Lastly I added those two lines on my ConfigureServices method:
services.AddSingleton<IUserService, UserService>();
services.AddTransient<BatchJobCheckContract>();
right now I am getting this exception when trying to execute the job, it seems like it's because my service uses the DbContext, how can I solve this?
Cannot consume scoped service 'RHP.data.RHPDbContext' from singleton
'RHP.data.IServices.Administration.IUserService'.
After playing around with Quartz (version 3.2.3), it looks like you do not have to write your own JobFactory to use Microsoft DI. (See ASP.NET Core Integration and Microsoft DI Integration):
Add the Quartz.AspNetCore nuget package and you can scoped services like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IScopedService, ScopedService>();
// Job has scoped dependencies, so it must be scoped as well
services.AddScoped<Job>();
services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionScopedJobFactory();
var jobKey = new JobKey("job");
q.AddJob<Job>(jobKey);
q.AddTrigger(t => /* ... */));
});
services.AddQuartzServer(opts => opts.WaitForJobsToComplete = true);
}
However, if you cannot use the current version of Quartz.AspNetCore, you could still
use IServiceProvider as dependency in your Job class and resolve services there:
public class Job : IJob
{
private readonly IServiceProvider _serviceProvider;
public Job(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public Task Execute(IJobExecutionContext context)
{
using var scope = _serviceProvider.CreateScope();
var scopedService = scope.ServiceProvider.GetRequiredService<IScopedService>();
// ...
}
}
Like this the Job class controls the lifetime of scoped services.
I've previously had a similar problem with background tasks, you might need to create a scope.
I've adapted this code and applied it to your use case.
public class AspNetCoreJobFactory : SimpleJobFactory
{
IServiceProvider _provider;
public AspNetCoreJobFactory(IServiceProvider provider)
{
_provider = provider;
}
public override IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
try
{
using(var serviceScope = _provider.CreateScope())
{
var services = serviceScope.ServiceProvider.
return (IJob)services.GetService(bundle.JobDetail.JobType);
}
}
catch(Exception e)
{
throw new SchedulerException(string.Format("Problem while instantiating job '{0}' from the AspNet Core IOC.", bundle.JobDetail.Key), e);
}
}
}
I have two services.
services.AddControllers();
and
services.AddSignalR()
.AddHubOptions<OutputMessages>(options =>
{
options.EnableDetailedErrors = true;
});
services.AddScoped<IOutputMessages, OutputMessages>();
second services is also confured:
app.UseEndpoints(endpoints =>
endpoints.MapHub<OutputMessages>("/OutputMessages", options =>
{
options.Transports =
HttpTransportType.WebSockets |
HttpTransportType.LongPolling;
});
I have interface for my SignalR service:
public interface ISignalRHandler
{
public void RestartProcessor(Guid containerId, string userId, string message);
....
}
and I inherits this interface and SignalR interface in my SignalR service realization
public class OutputMessages : Hub, IOutputMessages
{
public OutputMessages(IHubContext<OutputHub> hubContext, ILogger<OutputMessages> logger, ApplicationDbContext dbContext)
{
_hubContext = hubContext;
_db = dbContext;
_logger = logger;
}
public void SendUserMessage(string discordId, Guid containerId, string message)
....
}
I try to inject my SignalR service to controller service:
public class ApplicationAPIController : ControllerBase
{
public ApplicationAPIController(ILogger<ApplicationAPIController> logger, ApplicationDbContext dbContext, IConfiguration Configuration, CoreObjectDumper.CoreObjectDumper dump, OutputMessages _outputMessages)
{
But receive error message
Unable to resolve service for type 'SignalR.OutputMessages' while attempting to activate 'ApplicationAPIController'.
How is possible to solver this problem?
You don't need to add the OutputMessages as transient because you already doing that when you map your hub and add/use signalr.
app.UseSignalR(routes =>
{
routes.MapHub<OutputMessages>("/OutputMessages");
});
Then when you injecting to controller, it is recommended to inject the HubContext, and not the hub itself.
Example:
private IHubContext<NotificationsHub, INotificationsHub> NotificationsHub
{
get
{
return this.serviceProvider.GetRequiredService<IHubContext<NotificationsHub, INotificationsHub>>();
}
}
or in your case:
public ApplicationAPIController(ILogger<ApplicationAPIController> logger, ApplicationDbContext dbContext, IConfiguration Configuration, CoreObjectDumper.CoreObjectDumper dump, IHubContext<OutputMessages, IOutputMessages> _outputMessages)
You generally shouldn't resolve the Hub out of DI. If you need to share code between your Hub and some other component, I'd suggest using either IHubContext or putting the shared code in a separate DI service instead.
I have a SignalR Core hub which has a dependency on a service. That service itself has it's own dependencies and one of them requires access to the current ClaimsPrincipal.
I know, that I can access the ClaimsPrincipal inside the hub using the Context.User property and pass it as a parameter to the service, which can also pass it as a parameter and so on. But I really don't like to pollute the service API by passing this kind of ambient info as a parameter.
I've tried to use the IHttpContextAccessor as described in: https://learn.microsoft.com/en-us/aspnet/core/migration/claimsprincipal-current?view=aspnetcore-2.2
This seems to be working with a simple SignalR setup, but it isn't working with the Azure SignalR service, which will be our production setup.
Is there a reliable way how to get the ClaimsPrincipal outside of the hub that will work for both a simple local setup and Azure SignalR service?
In the current SignalR version (1.1.0) there is no support for this. I've created a feature request: https://github.com/dotnet/aspnetcore/issues/18657 but it was rejected. Eventually, I've ended up doing it like this:
services.AddSingleton(typeof(HubDispatcher<>), typeof(HttpContextSettingHubDispatcher<>));
public class HttpContextSettingHubDispatcher<THub> : DefaultHubDispatcher<THub> where THub : Hub
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HttpContextSettingHubDispatcher(IServiceScopeFactory serviceScopeFactory, IHubContext<THub> hubContext,
IOptions<HubOptions<THub>> hubOptions, IOptions<HubOptions> globalHubOptions,
ILogger<DefaultHubDispatcher<THub>> logger, IHttpContextAccessor httpContextAccessor) :
base(serviceScopeFactory, hubContext, hubOptions, globalHubOptions, logger)
{
_httpContextAccessor = httpContextAccessor;
}
public override async Task OnConnectedAsync(HubConnectionContext connection)
{
await InvokeWithContext(connection, () => base.OnConnectedAsync(connection));
}
public override async Task OnDisconnectedAsync(HubConnectionContext connection, Exception exception)
{
await InvokeWithContext(connection, () => base.OnDisconnectedAsync(connection, exception));
}
public override async Task DispatchMessageAsync(HubConnectionContext connection, HubMessage hubMessage)
{
switch (hubMessage)
{
case InvocationMessage _:
case StreamInvocationMessage _:
await InvokeWithContext(connection, () => base.DispatchMessageAsync(connection, hubMessage));
break;
default:
await base.DispatchMessageAsync(connection, hubMessage);
break;
}
}
private async Task InvokeWithContext(HubConnectionContext connection, Func<Task> action)
{
var cleanup = false;
if (_httpContextAccessor.HttpContext == null)
{
_httpContextAccessor.HttpContext = connection.GetHttpContext();
cleanup = true;
}
await action();
if (cleanup)
{
_httpContextAccessor.HttpContext = null;
}
}
}
I've recently upgraded to the new version of the excellent SignalR library, and moved all my Dependency Injection from StructureMap to Ninject, as Ninject seemed to be better supported.
I've got the dependency injection working fine for Server-side notifications using the "Broadcasting over a Hub from outside of a Hub" described here: https://github.com/SignalR/SignalR/wiki/Hubs.
The problem I'm getting is that all SignalR messages originating from the Javascript hub don't seem to be triggering the dependency injection.
I'm also using MVC4 WebAPI which also takes some shoe-horning to get dependency injection working.
Here's my Hub:
public class PresenceHub : Hub, IPresenceHub
{
private readonly IUserRepository _userRepository;
private readonly IFormsAuthenticationProvider _formsAuthenticationProvider;
public PresenceHub(IFormsAuthenticationProvider formsAuthenticationProvider, IUserRepository userRepository)
{
_userRepository = userRepository;
_formsAuthenticationProvider = formsAuthenticationProvider;
}
public void PresenceChange(string presence)
{
var user = _userRepository.FindById(_formsAuthenticationProvider.GetUserId());
var rosterEntry = Mapper.Map<User, RosterEntryDto>(user);
rosterEntry.Presence = presence;
Clients.updatePresence(rosterEntry);
}
}
Here's my Ninject Bootstrapper:
Namespace SLx.Web.App_Start
{
using System;
using System.Web;
using Microsoft.Web.Infrastructure.DynamicModuleHelper;
using Ninject;
using Ninject.Web.Common;
public static class NinjectWebCommon
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper();
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
bootstrapper.Initialize(CreateKernel);
}
public static void Stop()
{
bootstrapper.ShutDown();
}
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
// SignalR Ninject Resolver
GlobalHost.DependencyResolver = new SignalR.Ninject.NinjectDependencyResolver(kernel);
// WebApi Ninject Resolver
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
RegisterServices(kernel);
return kernel;
}
private static void RegisterServices(IKernel kernel)
{
}
}
I'm notifying clients on the serverside via a PresenceProxy defined as follows:
public class PresenceHubProxy : IPresenceHubProxy
{
private readonly IHubContext _hubContext;
public PresenceHubProxy()
{
_hubContext = GlobalHost.ConnectionManager.GetHubContext<PresenceHub>();
}
public void NotifyLogin(RosterEntryDto user)
{
_hubContext.Clients.updatePresence(user);
}
public void NotifyLogout(RosterEntryDto user)
{
_hubContext.Clients.updatePresence(user);
}
}
The Proxy works fine, injected into Controllers or their dependencies, and can send messages to the clients.
When the clients try to call SignalR via Javascript I get the following error:
No parameterless constructor defined for this object.
It looks like Ninject is not being invoked because the dependencies are not being injected into the constructor. What do I need to do to get Dependency Injection working for Javascript calls too?
Update --
Following advice from DFowler, I've replaced the Resolver in PostApplicationStart. Debugging I can see in the Immediate Window that SignalR.GlobalHost.Resolver is of type NinjectDependencyResolver but its still not working I get the same error - no paramaterless constructor.
I've then removed the NinjectDependencyResolver NuGet Library and added the source file to my solution and am using that for debugging purposes. Debugging on GetService and GetServices shows that neither method is ever called in NinjectDependencyResolver.
Any Ideas?
Problem was I hadn't called RouteTable.Routes.MapHubs:
GlobalHost.DependencyResolver = new SignalRNinjectResolver(NinjectWebCommon.Kernel);
RouteTable.Routes.MapHubs(new SignalRNinjectResolver(NinjectWebCommon.Kernel));
From the docs https://github.com/SignalR/SignalR/wiki/Extensibility:
NOTE: DO NOT override the global resolver in PreApplicationStart, it will not work, or it'll work only sometimes. Do it in PostApplicationStart (using WebActivator) or in Global.asax.