How to inject SignalR Hub Class (not hubcontext) into Controller - signalr

public class ComputerHub : Hub
{
private readonly DbContext _db;
public ComputerHub(DbContext db)
{
_db = db;
}
public Task OpenLock(string connectionId)
{
return Clients.Client(connectionId).SendAsync("OpenLock");
}
...
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSignalR();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
....
app.UseSignalR(routes =>
{
routes.MapHub<ComputerHub>("/computerhub");
});
....
}
I want to reach the OpenLock method in a controller. How should I add to ServiceCollection the computerhub in the startup.cs.

You don't seem to understand how this works. To simply answer your question, to inject the class directly, it simply needs to be registered with the service collection, like any other dependency:
services.AddScoped<ComputerHub>();
However, that's not going to do what you want it to. The class itself doesn't do anything. It's the hub context that bestows it with its powers. If you simply inject an instance of the class, without the hub context, then things like Clients (which the method you want to utilize uses) won't be set and won't have any of the functionality they need to actually do anything useful.

Related

.net 6.0 Integration tests

I have a problem that I can`t create HttpClient for integration tests.
I have ready carefully this article:
https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0
Microsoft.AspNetCore.Mvc.Testing installed
<Project Sdk="Microsoft.NET.Sdk.Web"> in csproj
CustomWebApplicationFactory:
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
Environment.SetEnvironmentVariable("HANGFIRE_DASHBOARD_USERNAME", "test");
Environment.SetEnvironmentVariable("HANGFIRE_DASHBOARD_PASSWORD", "test");
Environment.SetEnvironmentVariable("ASPNETCORE_URLS", "https://+:1229");
base.ConfigureWebHost(builder);
}
}
ControllerIntegrationTests:
public class ControllerIntegrationTests : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient client;
private readonly CustomWebApplicationFactory<Startup> factory;
public ControllerIntegrationTests(CustomWebApplicationFactory<Startup> factory)
{
this.factory = factory;
client = this.factory.CreateClient();
}
[Fact]
public async Task TestGetEndpointReturnSuccess()
{
// Arrange
// Act
var response = await client.GetAsync("/Information");
// Assert
response.EnsureSuccessStatusCode();
}
}
Nothing special in my code, just a simple example. As a result in Output I have :
Hosting environment:Development
Now listening on : https://[::]:1229
Application started. Press Ctrl+C to shut down.
It seems like it started the main application. And I cant move to Act in test, because its hanging in CreateClient(). So I can`t finish my test. Whats wrong?
You forget in your CustomWebApplicationFactory to override the CreateHostBuilder.
protected override IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder().ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<TStartup>();
});
}
Try to add this method in your Factory class using the TStartup.

How do I inject a DBContext into a service in Startup?

I have a service that I want to use in a .Net core Blazor app.
The service uses a context that I want to pass to it.
public class MyService
{
public AddDbContextContext { get; set; }
public MyService(AddDbContext mycontext)
{
Context = mycontext;
}
}
In Startup, I create the context that I want the service to use:
public void ConfigureServices(IServiceCollection services)
{
//configure the context
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("HIDDEN"));
//how do I pass the context?
services.AddSingleton<MyService>();
}
How do I pass the context to service?
How do I pass the context to service?
You don't pass the context to the service. It is automatically done when the DI container creates your service.
Since your DBContext is named ApplicationDbContext, your service should look like this:
public class MyService
{
private ApplicationDbContext _context;
public MyService(ApplicationDbContext context)
{
_context = context;
}
}
Note: You can only use DBContext in Blazor Server App
Note: I've only answered your question here. But it's very likely you'll have issues with the DBContext, depending on what you do in your service. You should consult the documents how to use the DBContext in Blazor, particularly, why and how you should use AddDbContextFactory

.NET Core, SignalR Hub's constructor IHubCallerClients is NULL

I'm trying to implement .NET Core 2.2/SignalR 1.1.0.
In startup:
public void ConfigureServices(IServiceCollection services)
services.AddSignalR();
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub");
});
It works smoothly when I apply a one-to-one example.
But I need an architectural change.
My example:
public class ChatHub : Hub
{
ResponseHandler ResponseHandler { get; set; }
public ChatHub()
{
IHubCallerClients hubCallerClients = this.Clients;
ResponseHandler = new ResponseHandler(hubCallerClients);
}
public async Task SendMessage(string user, string message)
{
IHubCallerClients hubCallerClients = this.Clients;
await ResponseHandler.R();
}
}
If I tried to get this.Clients in the constructor it is coming with null data. But if I try to take it in the method, it comes full as expected.
I should get IHubCallerClients in the contructor so that I can forward it to another Response context.
Thanks advance!
OK. I solved the problem by
public class RequestHandler : Hub
{
ResponseHandler ResponseHandler { get; set; }
public RequestHandler(IHubContext<RequestHandler> hubContext)
{
ResponseHandler = new ResponseHandler(hubContext);
}
public async Task SendMessage(string user, string message)
{
await ResponseHandler.R();
}
}
Due to the nature of .net core, context comes to constructor as dependency.
"services.AddSignalR();" we're sure to add it to Scope.
"IHubContext hubContext" In this way, we can collect the contructured object.

How to inject IHubContext in Azure Mobile Services

To get an IHubContext from outside the hub I use:
public class EventSender
{
private readonly IHubContext context;
public EventSender(ApiServices services)
{
context = services.GetRealtime<MyHub>();
}
public void Send(string message)
{
context.Clients.All.Send(message);
}
}
Where services is an ApiServices instance that gets injected into the calling class.
What if I want to inject the IHubContext itself? How do I do that?
I tried to register the IHubContext instance in WebApiConfig.cs like this:
var configBuilder = new ConfigBuilder(options, (httpConfig, autofac) =>
{
autofac.RegisterType<Logger>().As<ILogger>().SingleInstance();
autofac.RegisterInstance(??).As<IHubContext>(); <-- ????
....
But this has 2 problems:
I don't have access to the ApiServices instance (and WebApiConfig class is static so I can't inject it there).
What type do I register it as? IHubContext seems too general.
If you can inject ApiServices, you can have access while trying to register your dependencies.
Can you try something like this?
autofac.Register(c =>
{
var services = c.Resolve<ApiServices>();
return services.GetRealtime<MyHub>();
}.As<IHubContext>();
And in your constructor:
public EventSender(IHubContext context)
{
this.context = context;
}

Getting current hub in SignalR

Is there a good way to call methods in SignalR hub from a controller ?
Right now I have this:
public class StatsHub : Hub
{
private static readonly Lazy<StatsHub> instance = new Lazy<StatsHub>(() => new StatsHub());
public static StatsHub Instance { get { return instance.Value; } }
public StatsHub()
{
if (this.Clients == null)
{
var hubContext = SignalR.GlobalHost.ConnectionManager.GetHubContext<StatsHub>();
this.Clients = hubContext.Clients;
this.Groups = hubContext.Groups;
}
}
// methods here...
}
so in my controller actions I can just say, for example
StatsHub.Instance.SendMessage("blah");
and it's almost good, except that hubContext doesn't have Caller or Context properties of Hub - which are nice to have.
Hopefully, there's a better way to do this ?
If you want to broadcast over a hub from outside of the hub, you need GlobalHost.ConnectionManager.GetHubContext<MyHub>() to get ahold of the hub context. You can then use this context to broadcast via the .Clients property.
As indicated in your sample code you already get ahold of the hub context, but doing so inside the hub just doesn't feel right in my opinion. If you're only using the logic in SendMessage() from your controller actions, I'd move the code right into the controller action and use the hub context obtained via GetHubContext<T>() from there.
Please note that the Caller or Context property will always be null in this scenario, because SignalR wasn't involved when making a request to the server and therefore cannot provide the properties.
Found a DefaultHubManager, which is what I need, I think.
DefaultHubManager hd = new DefaultHubManager(GlobalHost.DependencyResolver);
var hub = hd.ResolveHub("AdminHub") as AdminHub;
hub.SendMessage("woohoo");
Works. If there's an even better/preferred way - please share.
As per the latest documentation, IHubContext can be injected by dependency injection.
documentation : https://learn.microsoft.com/en-us/aspnet/core/signalr/hubcontext?view=aspnetcore-6.0
In service you could do
public class NotificationService : INotificationService
{
private readonly IHubContext<NotificationHub> _hubContext;
public NotificationService(IHubContext<NotificationHub> hubContext)
{
_hubContext = hubContext;
}
}
In controller
public class HomeController : Controller
{
private readonly IHubContext<NotificationHub> _hubContext;
public HomeController(IHubContext<NotificationHub> hubContext)
{
_hubContext = hubContext;
}
}
Once you have HubContext you could send message to group/client etc.
public async Task SendMessage()
{
return await _hubContext.Clients.All.SendAsync("Notify", $"Hello world");
}

Resources