How to inject IHubContext in Azure Mobile Services - signalr

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;
}

Related

How to use AutoMapper 9.0.0 in Asp.Net Web Api 2 without dependency injection?

I haven't been able to find any info where to put this code inside my project. Right now I am use using this in each action I need the mapper. Is there a better way to do this with out dependency injection?
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<Source, Dest>();
});
IMapper iMapper = config.CreateMapper();
var destList= iMapper.Map<Dest[]>(sourceList);
Dependency injection added a whole level of complexity to my legacy project that I just didn't want to deal with. 9.0 removed the api to call it staticly.
So I just reverse engineered what it was doing in 8.0 and wrote a wrapper for it.
public static class MapperWrapper
{
private const string InvalidOperationMessage = "Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.";
private const string AlreadyInitialized = "Mapper already initialized. You must call Initialize once per application domain/process.";
private static IConfigurationProvider _configuration;
private static IMapper _instance;
private static IConfigurationProvider Configuration
{
get => _configuration ?? throw new InvalidOperationException(InvalidOperationMessage);
set => _configuration = (_configuration == null) ? value : throw new InvalidOperationException(AlreadyInitialized);
}
public static IMapper Mapper
{
get => _instance ?? throw new InvalidOperationException(InvalidOperationMessage);
private set => _instance = value;
}
public static void Initialize(Action<IMapperConfigurationExpression> config)
{
Initialize(new MapperConfiguration(config));
}
public static void Initialize(MapperConfiguration config)
{
Configuration = config;
Mapper = Configuration.CreateMapper();
}
public static void AssertConfigurationIsValid() => Configuration.AssertConfigurationIsValid();
}
To initialize it have a configure method
public static class AutoMapperConfig
{
public static void Configure()
{
MapperWrapper.Initialize(cfg =>
{
cfg.CreateMap<Foo1, Foo2>();
});
MapperWrapper.AssertConfigurationIsValid();
}
}
And just call it in your startup
AutoMapperConfig.Configure();
To use it just Add MapperWrapper before your Mapper call. Can be called anywhere.
MapperWrapper.Mapper.Map<Foo2>(Foo1);

Controller constructor does not get called

Hello i am trying to understand why do my requests not enter my api route.They seem to reach the server but they wont fan out in the MVC.
The server is running on: http://localhost:9300
The route i am requesting is : http://localhost:9300/api/getusers
Program
public class Program {
public static void Main(string[] args) {
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) {
var builder = new WebHostBuilder();
builder.UseStartup<Startup>();
var url = Address.Default.ToUrl();
builder.UseKestrel().UseUrls(url);
return builder;
}
}
Startup
public class Startup {
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services) {
services.AddOptions();
services.AddMvc();
}
public IConfiguration Configuration;
public void Configure(IApplicationBuilder app) {
Debug.WriteLine("Entered server"); //enters successfully here
app.UseMvc(); //does not enter the controller
}
}
Controller
This is a simple controller with a GET method.The constructor is not invoked at all.Why would this happen?I know it when the server runs the first time ..it does a health check on its routes.
[ApiController]
class UserController : ControllerBase {
private static List<User> users = new List<User> {
new User{Id=0,Age=0,Name="Failed"},
new User{Id=12,Age=33,Name="Daniel"},
new User{Id=13,Age=33,Name="Marian"},
};
public UserController() {
Debug.WriteLine("Controller called"); //does not get called !
}
[HttpGet]
[Route("api/getusers")]
public async Task<HttpResponseMessage> GetUsers() {
await Task.Delay(1000);
return new HttpResponseMessage {
Content = new StringContent(users.ToJson()),
StatusCode = HttpStatusCode.OK
};
}
}
P.S Do i have to add anyything ? What am i missing i followed other implementations closely.
I've created the webapi project using dotnet new webapi.
I've managed to get to the url with the similar configuration by changing the access modifier of a similar controller. Try to add public keyword to the class UserController. So it should be public class UserController
I will provide more information about the configuration of the project if it is necessary and the step above does not help.

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

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.

Get HubContext from outside of Hub using Aspnetcore.signalr libray (not from Controller)

I am developing SignalR service using AspNetCore.SignalR.
Following is my Hub:
public class NotificationHub : Microsoft.AspNetCore.SignalR.Hub
{
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await base.OnDisconnectedAsync(exception);
}
}
Following is Another Context class that i have created to invoke Hub's method on client side:
public class NotificationHubContext
{
private readonly IHubContext<NotificationHub> _context;
public NotificationHubContext(IHubContext<NotificationHub> context)
{
_context = context;
}
public async Task Broadcast(string groupId, string eventName, object data)
{
await _context.Clients.Group(groupId).SendAsync(eventName, data);
}
}
I would like to Inject reference of NotificationContext class into my own IoC Container so I can just resolve it and call a BroadCast method on it and it should handle sending messages to clients.
I am using Service Bus to listen for messages from another part of the system, once I receive a message from Queue, I would like to notify Connected clients using HubContext from QueueHandler.
Assembly Info
var hubContext = app.ApplicationServices.GetService<IHubContext<Notification>>
That resolved my issue.
I design my class as below.
public class NotificationHub : Microsoft.AspNetCore.SignalR.Hub
{
public static IHubContext<NotificationHub> Current { get; set; }
}
In your Startup class
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
NotificationHub.Current = app.ApplicationServices.GetService<IHubContext<NotificationFromServerHub>>();
}
So, you can use as like this from anywhere.
public class MyBizClass
{
public void DoSomething()
{
NotificationHub.Current.MyMethod(...);
}
}

Use Hub methods from controller?

I am using SignalR 2 and I can not figure out how I can use my Hub methods e.g from inside a controller action.
I know I can do the following:
var hub = GlobalHost.ConnectionManager.GetHubContext<T>();
hub.Clients.All.clientSideMethod(param);
But that executes the method directly on the client side.
What if I have business logic inside my server side ClientSideMethod(param) method I want to call from my controller the same way as when it is called from the client side?
At the moment I use public static void ClientSideMethod(param) inside my hub and in that method I use the IHubContext from the ConnectionManager.
Is there no better was of doing this?
The following is not working (anymore in SignalR 2?):
var hubManager = new DefaultHubManager(GlobalHost.DependencyResolver);
instance = hubManager.ResolveHub(typeof(T).Name) as T;
instance.ClientSideMethod(param);
There I get a "Hub not created via Hub pipeline not supported" exception, when accessing the Clients.
It might work to create a "helper" class that implements your business rules and is called by both your Hub and your Controller:
public class MyHub : Hub
{
public void DoSomething()
{
var helper = new HubHelper(this);
helper.DoStuff("hub stuff");
}
}
public class MyController : Controller
{
public ActionResult Something()
{
var hub = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
var helper = new HubHelper(hub);
helper.DoStuff("controller stuff");
}
}
public class HubHelper
{
private IHubConnectionContext hub;
public HubHelper(IHubConnectionContext hub)
{
this.hub = hub;
}
public DoStuff(string param)
{
//business rules ...
hub.Clients.All.clientSideMethod(param);
}
}
As I did not find a "good solution" I am using #michael.rp's solution with some improvements:
I did create the following base class:
public abstract class Hub<T> : Hub where T : Hub
{
private static IHubContext hubContext;
/// <summary>Gets the hub context.</summary>
/// <value>The hub context.</value>
public static IHubContext HubContext
{
get
{
if (hubContext == null)
hubContext = GlobalHost.ConnectionManager.GetHubContext<T>();
return hubContext;
}
}
}
And then in the actual Hub (e.g. public class AdminHub : Hub<AdminHub>) I have (static) methods like the following:
/// <summary>Tells the clients that some item has changed.</summary>
public async Task ItemHasChangedFromClient()
{
await ItemHasChangedAsync().ConfigureAwait(false);
}
/// <summary>Tells the clients that some item has changed.</summary>
public static async Task ItemHasChangedAsync()
{
// my custom logic
await HubContext.Clients.All.itemHasChanged();
}

Resources