How to inject strongly typed signalR hub in a class [ASP CORE 2.2] - asp.net-core

I want to inject my strongly typed hub in a service, but I don't like certain thing in the example shown by Microsoft - https://learn.microsoft.com/en-us/aspnet/core/signalr/hubcontext?view=aspnetcore-2.2 (Inject a strongly-typed HubContext)
public class ChatController : Controller
{
public IHubContext<ChatHub, IChatClient> _strongChatHubContext { get; }
public ChatController(IHubContext<ChatHub, IChatClient> chatHubContext)
{
_strongChatHubContext = chatHubContext;
}
public async Task SendMessage(string message)
{
await _strongChatHubContext.Clients.All.ReceiveMessage(message);
}
}
In this example ChatHub is coupled to ChatController.
So I want to inject the hub itself
defined with generic interface parameter and no concrete implementation of it will be defined in my service.
This is sample code
public interface IReportProcessingClient
{
Task SendReportInfo(ReportProgressModel report);
}
public class ReportProcessingHub : Hub<IReportProcessingClient>
{
public async Task SendMessage(ReportProgressModel report)
{
await Clients.All.SendReportInfo(report);
}
}
public class ReportInfoHostedService : IHostedService, IDisposable
{
private readonly Hub<IReportProcessingClient> _hub;
private readonly IReportGenerationProgressService _reportService;
public ReportInfoHostedService(Hub<IReportProcessingClient> hub, IReportGenerationProgressService reportService)
{
_hub = hub;
_reportService = reportService;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_reportService.SubscribeForChange(async x =>
{
await _hub.Clients.All.SendReportInfo(x);
});
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public void Dispose()
{
}
}
This approach obviously will need additional registration of the hub in Startup.cs as it is not called by the context api provided by Microsoft.
services.AddSingleton<Hub<IReportProcessingClient>, ReportProcessingHub>();
app.UseSignalR(route => {
route.MapHub<ReportProcessingHub>("/reportProcessingHub");
});
All done and working until the hub is trying to send messages to Clients. Then I get the exception
_hub.Clients.All threw an exception of System.NullReferenceException: 'Object reference not set to an instance of an object.'
So to summerize:
1. Is this the right way to inject strongly typed hubs and what am I doing wrong(e.g. wrong registration of the hub in services, wrong usage of app.UseSingleR)?
2. If not, what is the correct way?
NOTE:
I know there is a lot easier way injecting IHubContext<Hub<IReportProcessingClient>>, but this is not a solution for me, because I have to call the hub method name passed as string parameter.

I want to ... and no concrete implementation of it will be defined in my service
If you don't want to expose a concrete hub implementation, you should at least expose a base class or interface. However, since a hub should inherit from the Hub class, we can't use an interface here. So let's create a public base hub ReportProcessingHubBase as well as an internal concrete ReportProcessingHub:
public abstract class ReportProcessingHubBase : Hub<IReportProcessingClient>
{
public abstract Task SendMessage(ReportProgressModel report);
}
// the concrete hub will NOT be exposed
internal class ReportProcessingHub : ReportProcessingHubBase
{
public override async Task SendMessage(ReportProgressModel report)
{
await Clients.All.SendReportInfo(report);
}
}
Make sure you've registered the two related service:
services.AddScoped<ReportProcessingHubBase, ReportProcessingHub>();
services.AddHostedService<ReportInfoHostedService>();
Make sure you're mapping the Base Hub (MOST IMPORTANT!):
endpoints.MapHub<ReportProcessingHubBase>("/report");
Finally, you can get the base hub by injecting IHubContext<ReportProcessingHubBase,IReportProcessingClient> :
public class ReportInfoHostedService : IHostedService, IDisposable
{
private readonly IHubContext<ReportProcessingHubBase,IReportProcessingClient> _hub;
public ReportInfoHostedService(IHubContext<ReportProcessingHubBase,IReportProcessingClient> hub)
{
_hub = hub;
}
...
}
Now, you can invoking the hub method in a strongly-typed way.

Related

How to send a message outside the controller?

I need to send information to the connected clients outside the HUB.
Here my class :
public static class Notification{
public static void SendMessage(){
//... Do some stuff
MyHub.Clients.All.SendAsync("sendInfo");
}
}
How to instantiate HUB?
As far as I know, you could use IHubContext service to send the service message outside the hub.
If you have register the service inside the ConfigureServices in startup.cs, then you could access an instance of IHubContext via dependency injection.
services.AddSignalR();
E.g Inject an instance of IHubContext in a controller.
public class HomeController : Controller
{
private readonly IHubContext<NotificationHub> _hubContext;
public HomeController(IHubContext<NotificationHub> hubContext)
{
_hubContext = hubContext;
}
}
Now, with access to an instance of IHubContext, you can call hub methods as if you were in the hub itself.
public async Task<IActionResult> Index()
{
await _hubContext.Clients.All.SendAsync("Notify", $"Home page loaded at: {DateTime.Now}");
return View();
}
More details ,you could refer to this article.
You do as #Brando Zhang posted above or just inject the Hub in to your controller or manager like:
Controller
private IHubContext<YourHub, IYourHub> YourHub
{
get
{
return this.HttpContext.RequestServices.GetRequiredService<IHubContext<YourHub, IYourHub>>();
}
}
Other
private IHubContext<YourHub, IYourHub> YourHub
{
get
{
return this.serviceProvider.GetRequiredService<IHubContext<YourHub, IYourHub>>();
}
}
PS: It is recomended to inject the HubContext and not the hub directly.

Autofac - Registering instance type that takes interface as parameter

I am very new to Autofac and not able to understand the syntax for registration. I have following calsses/interfaces :
//Interface
public interface ISender
{
void Send();
}
//implementations
public class Post : ISender
{
public void Send()
{
//Post implementation
}
}
public class Email : ISender
{
public void Send()
{
//Email implementation
}
}
And a class that calls these implementations
public class Consumer
{
ISender Sender;
public Consumer(ISender sender)
{
Sender = sender
}
public void Act()
{
Sender.Send();
}
}
Now, which implementation to call is to be decided in a controller, so I tried using IIndex from this page like:
public calss PostController
{
Consumer ConsumerObject;
public PostController(IIndex<string, Consumer> consumer)
{
ConsumerObject = consumer["post"];
}
}
public class EmailController
{
Consumer ConsumerObject;
public PostController(IIndex<string, Consumer> consumer)
{
ConsumerObject = consumer["email"];
}
}
Firstly, is it correct or doable? Now the problem is I don't understand how to register in Autofac. So, How can we register Consumer and ISender in Autofac ?? Please suggest if there is any better/alternative way.
The way of registering components in Autofac is described widely in the documentation here. And how to use keyed services is described in the documentation you linked.
Basically you have to create ContainerBuilder, register all your components and build the container itself, based on the project type you have.
In your situation you need to use the following registrations:
var builder = new ContainerBuilder();
builder.RegisterType<Post>().Keyed<ISender>("post");
builder.RegisterType<Email>().Keyed<ISender>("email");
builder.RegisterType<Consumer>();
If you use ASP.NET WebApi (I assume that based on the fact you are using Controllers), you need to register your controllers
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
or for MVC
builder.RegisterControllers(typeof(MvcApplication).Assembly);
Now, there are various way to pick the Consumer with proper ISender implementation (I assume you want to pick proper implementation in controller).
One would be to inject IIndex<string, ISender> to Consumer class and pass the proper key string to it as well:
public class Consumer
{
ISender Sender;
public Consumer(string type, IIndex<string, ISender> sender)
{
Sender = sender[type];
}
public void Act()
{
Sender.Send();
}
}
Then, in controller you could use Func injection:
public class PostController
{
Consumer ConsumerObject;
public PostController(Func<string, Consumer> consumer)
{
ConsumerObject = consumer("post");
}
}
public class EmailController
{
Consumer ConsumerObject;
public EmailController(Func<string, Consumer> consumer)
{
ConsumerObject = consumer("email");
}
}
Another could be registering Consumer twice with Register method and resolving ISender in registration time.

Running WCF service method during start of Windows Service

I have got WCF service running as Windows service and I need to run a method of the WCF Service when Windows Service is starting. Is it possible in any way?
[ServiceContract]
public interface IWebMonitorServiceLibrary
{
[OperationContract]
void TestMethod();
}
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class WebMonitorServiceLibrary : IWebMonitorServiceLibrary
{
#region properties
#endregion
#region events
#endregion
public WebMonitorServiceLibrary()
{
Initialization();
}
private void Initialization()
{
/////////
}
public void TestMethod()
{
//////////
}
}
You don't explain why you want this initialization code to run, but given you almost never want to use a single-instance WCF service, the proper way would be to use dependency injection (see How do I pass values to the constructor on my wcf service?).
Create an object in which you store the things you want to initialize, which you initialize on your Windows Service start:
public class SomeSettingsYouWantToInitialize
{
public string SomeSetting { get; set; }
}
public class WindowsServiceInstance : ServiceBase
{
protected override void OnStart(string[] args)
{
InitializeWcfService();
}
private void InitializeWcfService()
{
var settings = new SomeSettingsYouWantToInitialize
{
SomeSetting = "Foo"
};
_yourDependencyContainer.Register<SomeSettingsYouWantToInitialize>(settings);
}
}
Then (using whatever dependency injection framework you use), inject that into your service's constructor:
public class WebMonitorServiceLibrary
{
public WebMonitorServiceLibrary(SomeSettingsYouWantToInitialize settings)
{
// do stuff with settings
}
}
Generally, no. This is because by default (and following best practice) you will have configured your service to run per-call (or per session), which means there can be multiple instances of your actual service running in your service host.
Therefore, any requirement for you to be able to return an instance of the service from the service host will involve some nasty plumbing code and is not advised.
Specifically, however, there are two approaches you could use to do what you want.
The first involves running your service in InstanceContextMode.Single - this means there will be a single service instance which will handle all requests. If you do this then you can simply create the service instance and then pass it into the servicehost when you start the windows service:
var service = new MyService();
var host = new ServiceHost(service);
You then have access to the service instance and can call the operation directly.
service.MyOperation("something");
The second thing you can do for when you don't want to run a singleton service you can make your service implementation just a wrapper around a static instance of a shared class that actually process the requests. As an example:
public class MyService : IMyService
{
private static IMyService instance = new MySharedServiceClass();
public static IMyService Instance
{
get { return instance ; }
}
public bool MyOperation(string something)
{
return instance.MyOperation(something);
}
}
Then you can call the method on the class like this:
var host = new ServiceHost(typeof(MyService));
var instance = MyService.Instance;
instance.MyOperation("something");
I would still avoid doing this if at all possible. Think to yourself why do you even want this method called on startup? Surely it would be better to have this code directly in the windows service if it's something that needs to be run on startup?

WCF with Sharp architecture - The needed dependency of type could not be located with the ServiceLocator

I'm working with an application which uses wcf and sharp architecture, I'm trying to create a service to write to the database. Here is my service: (Sicaf.Core.Services.Wcf)
[ServiceContract]
public interface IFacturaWcfService : ICloseableAndAbortable
{
[OperationContract]
string ConsultarValorMatricula(string xmlData);
}
[ServiceBehavior, AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class FacturaWcfService : IFacturaWcfService
{
private readonly IFacturaBusiness facturaBusiness;
public FacturaWcfService(IFacturaBusiness facturaBusiness)
{
this.facturaBusiness = facturaBusiness;
}
public string ConsultarValorMatricula()
{
return facturaBusiness.GetFactura();
}
public void Abort() { }
public void Close() { }
}
In the ComponentRegistrar.cs: (Sicaf.Core.Services.WebServices)
private static void AddWcfServicesTo(IWindsorContainer container)
{
// Since the TerritoriesService.svc must be associated with a concrete class,
// we must register the concrete implementation here as the service
container.AddComponent("facturaWcfService", typeof(FacturaWcfService));
}
I created a client but I get this exception:
The needed dependency of type FacturaWcfService could not be located with the ServiceLocator. You'll need to register it with the Common Service Locator (CSL) via your IoC's CSL adapter.
I've finally found my mistake.
Before:
private static void AddCustomRepositoriesTo(IWindsorContainer container)
{
// Register Data Layer Services
container.Register(
AllTypes.Pick()
.FromAssemblyNamed("Sicaf.Core.Services.Data")
.WithService.FirstNonGenericCoreInterface("Sicaf.Core.Services.Services.Data"));
}
After:
private static void AddCustomRepositoriesTo(IWindsorContainer container)
{
// Register Data Layer Services
container.Register(
AllTypes.Pick()
.FromAssemblyNamed("Sicaf.Core.Services.Data")
.WithService.FirstNonGenericCoreInterface("Sicaf.Core.Services.Services.Data"));
container.Register(
AllTypes.Pick()
.FromAssemblyNamed("SismatV2.Data")
.WithService.FirstNonGenericCoreInterface("SismatV2.Services.Data"));
}

RhinoMocks Testing callback method

I have a service proxy class that makes asyn call to service operation. I use a callback method to pass results back to my view model.
Doing functional testing of view model, I can mock service proxy to ensure methods are called on the proxy, but how can I ensure that callback method is called as well?
With RhinoMocks I can test that events are handled and event raise events on the mocked object, but how can I test callbacks?
ViewModel:
public class MyViewModel
{
public void GetDataAsync()
{
// Use DI framework to get the object
IMyServiceClient myServiceClient = IoC.Resolve<IMyServiceClient>();
myServiceClient.GetData(GetDataAsyncCallback);
}
private void GetDataAsyncCallback(Entity entity, ServiceError error)
{
// do something here...
}
}
ServiceProxy:
public class MyService : ClientBase<IMyService>, IMyServiceClient
{
// Constructor
public NertiAdminServiceClient(string endpointConfigurationName, string remoteAddress)
:
base(endpointConfigurationName, remoteAddress)
{
}
// IMyServiceClient member.
public void GetData(Action<Entity, ServiceError> callback)
{
Channel.BeginGetData(EndGetData, callback);
}
private void EndGetData(IAsyncResult result)
{
Action<Entity, ServiceError> callback =
result.AsyncState as Action<Entity, ServiceError>;
ServiceError error;
Entity results = Channel.EndGetData(out error, result);
if (callback != null)
callback(results, error);
}
}
Thanks
Played around with this a bit and I think I may have what you're looking for. First, I'll display the MSTest code I did to verify this:
[TestClass]
public class UnitTest3
{
private delegate void MakeCallbackDelegate(Action<Entity, ServiceError> callback);
[TestMethod]
public void CallbackIntoViewModel()
{
var service = MockRepository.GenerateStub<IMyServiceClient>();
var model = new MyViewModel(service);
service.Stub(s => s.GetData(null)).Do(
new MakeCallbackDelegate(c => model.GetDataCallback(new Entity(), new ServiceError())));
model.GetDataAsync(null);
}
}
public class MyViewModel
{
private readonly IMyServiceClient client;
public MyViewModel(IMyServiceClient client)
{
this.client = client;
}
public virtual void GetDataAsync(Action<Entity, ServiceError> callback)
{
this.client.GetData(callback);
}
internal void GetDataCallback(Entity entity, ServiceError serviceError)
{
}
}
public interface IMyServiceClient
{
void GetData(Action<Entity, ServiceError> callback);
}
public class Entity
{
}
public class ServiceError
{
}
You'll notice a few things:
I made your callback internal. You'll need to use the InternalsVisisbleTo() attribute so your ViewModel assembly exposes internals to your unit tests (I'm not crazy about this, but it happens in rare cases like this).
I use Rhino.Mocks "Do" to execute the callback whenever the GetData is called. It's not using the callback supplied, but this is really more of an integration test. I assume you've got a ViewModel unit test to make sure that the real callback passed in to GetData is executed at the appropriate time.
Obviously, you'll want to create mock/stub Entity and ServiceError objects instead of just new'ing up like I did.