Getting Hub Context for ASPNet.Core Signal-R (.NET Core 2.1) RC - asp.net-core

I'm using ASP.NET Core 2.1 RC1.
I'm also using Signal-R for it (found here):
https://learn.microsoft.com/en-us/aspnet/core/signalr/javascript-client?view=aspnetcore-2.1
I'm creating a .NET Core console application that's hosting Kestrel and using Signal-R. I've pretty much set it up exactly as the getting started documentation states for setting up the Startup.
This all works great. I'm able to connect to the it, get my HTML with signal-R script in it, receive messages I crafted with Clients.All.SendAsync. Works great.
BUT
I want to be able to send a message to clients, from outside the Hub. Where some event happens in my application, and a message is sent to clients. In full .NET, I'd use the GlobalHost and get the context. In ALL my searches on Stack Overflow, they reference something that no longer works, or used within an REST API controller that's passed in the IHubContext.
I have an event listener in my program.cs, and when the event is triggered, I'd love to be able to send a message to my UserInterfaceHub.
So -- how do I get the hub context in Program.CS - so I can send messages to it (call the SwitchUI method) from within an event delegate I have in Program.CS?
StartUp.cs
public void ConfigureServices(IServiceCollection services) {
services.Configure<CookiePolicyOptions>(options => {
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc();
services.AddCors(options => options.AddPolicy("CorsPolicy",
builder => {builder.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin().AllowCredentials();}));
services.AddSignalR();
var provider = services.BuildServiceProvider();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseCors("CorsPolicy");
app.UseSignalR(routes => {routes.MapHub<UserInterfaceHub>("/uihub");});
app.UseMvc();
//app.Run(async (context) =>{await context.Response.WriteAsync("Active");});
}
Program.CS
CreateWebHostBuilder(args)
.UseKestrel()
.UseUrls("http://0.0.0.0:" + appProperties.HostPort.ToString().Trim())
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.Build()
.Start();
UserInterfaceHub.cs
namespace InterfaceModule.Hubs {
public class UserInterfaceHub : Hub {
public async Task SwitchUI(string message) {
await Clients.All.SendAsync("ReceiveEvent", message);
}
public override async Task OnConnectedAsync() {
//await SwitchUI("HOWDY NEW PERSON!");
await base.OnConnectedAsync();
}
}
}
edit adding clarity.
In Program.CS, I have this event delegate:
//now that we're ready, start listening.
await
deviceClient.SetInputMessageHandlerAsync(ModuleProperties.InputName, OnReceiveEvent, deviceClient);
Console.WriteLine("INIT: Event Message Input handler created: [{0}]", ModuleProperties.InputName);
which is this:
static async Task<MessageResponse> OnReceiveEvent(Message message, object userContext) {
//HOW DO I REACH THE HUB FROM HERE SO I CAN SEND A MESSAGE TO THE LISTENERS?
}

I encountered a similar situation and here's how to resolve it:
In your Service layer, create an interface called something like ISendHubMessage. Have a method called Send() that takes parameters that you're wanting to send via SignalR. Create a class within the same file called SendHubMessage that implements the interface. Have it just do a return.
In your top-level project (where your Startup.cs file is located) create another class called SendHubMessage that implements that same ISendHubMessage interface from your Service layer. Within this SendHubMessage, you can use DI to get at the hub as explained above. This method will do the actual logic of sending via SignalR.
In your Startup ConfigureServices() method, add the following line:
services.AddTransient<"Service".ISendHubMessage, "TopLevel".SendHubMessage>();
(where "Service" is the namespace to your Service-level project and "TopLevel" in the namespace to your top-level project).
What you're doing with this line is saying "Whenever an object requests the ISendHubMessage dependency from the Service layer, supply it with the SendHubMessage class defined in my top-level project".
Finally, in all the places in code outside of your top-level project that you're wanting to send messages through your hub, inject that ISendHubMessage dependency in the constructor. You can then refer to it in the class methods and when you call Send(), it will call the Send() method defined in your SendHubMessage class in your top-level project.

This line of code:
app.UseSignalR(routes => {routes.MapHub<UserInterfaceHub>("/uihub");});
will register your hub with the DI container. Then to get access to it, you either use constructor injection to inject in the IHubContext<UserInterfaceHub> (this works for example in a Web Controller) or access it directly from the DI container by doing the following:
var hub = app.ApplicationServices.GetRequiredService<IHubContext<UserInterfaceHub>>();
(for example if executed in the startup.cs Configure method)
If you don't have access to the app.ApplicationServices which is basically an IServiceProvider at the location you need to access the hub, then you will need to either 1) get that class to work with dependency injection to inject in the IHubContext<UserInterfaceHub> or IServiceProvider 2) Setup a static Services global var via Configure so that you can have access to one of them globally, or find some other way to access the DI container (aka IServiceProvider) to get your hub via the above line of code.
Once you have your hub, then sending the message to the registered clients is a simple as calling the method on your hub.
await hub.Clients.All.SendAsync("ReceiveEvent", message);

You're question is a little unclear, but I'm assuming you mean you want to replace the following with something that can send a message through your hub:
app.Run(async (context) =>{await context.Response.WriteAsync("Active");});
Since this is in your Configure method, you can simply add IServiceCollection services to your Configure methods params. Then, you can do:
var hub = services.GetRequiredService<IHubContext<MyHub>>();
However, I'm not sure that this will actually do anything useful ultimately. At startup, you'd logically have no clients with subscriptions yet. As a result, sending a message through your hub at this point, would essentially go nowhere. By the time a user actually hits your site and gets connected to your hub, this part of your application has already run, and won't be hit again.

Related

Register dependent services on every request

I am working in Multi-tenant solution primarily there are 2 type of applications
WebAPI
Console app to process message from queue
I have implemented dependency injection to inject all services. I have crated TenantContext class where I am resolving tenant information from HTTP header and it's working fine for API, but console application getting tenant information with every message (tenant info is part of queue message) so I am calling dependency injection register method on every incoming message which is not correct, do you have any suggestion/solution here?
The way I am resolving ITenantContext in API
services.AddScoped<ITenantContext>(serviceProvider =>
{
//Get Tenant from JWT token
if (string.IsNullOrWhiteSpace(tenantId))
{
//1. Get HttpAccessor and processor settings
var httpContextAccessor =
serviceProvider.GetRequiredService<IHttpContextAccessor>();
//2. Get tenant information (temporary code, we will get token from JWT)
tenantId = httpContextAccessor?.HttpContext?.Request.Headers["tenant"]
.FirstOrDefault();
if (string.IsNullOrWhiteSpace(tenantId))
//throw bad request for api
throw new Exception($"Request header tenant is missing");
}
var tenantSettings =
serviceProvider.GetRequiredService<IOptionsMonitor<TenantSettings>>();
return new TenantContext(tenantId, tenantSettings );
});
Create two different ITenantContext implementations. One for your Web API, and one for your Console application.
Your Web API implementation than might look as follows:
public class WebApiTenantContext : ITenantContext
{
private readonly IHttpContextAccessor accessor;
private readonly IOptionsMonitor<TenantSettings> settings;
public WebApiTenantContext(
IHttpContextAccessor accessor,
IOptionsMonitor<TenantSettings> settings)
{
// Notice how the dependencies are not used in this ctor; this is a best
// practice. For more information about this, see Mark's blog:
// https://blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple/
this.accessor = accessor;
this.settings = settings;
}
// This property searches for the header each time its called. If needed,
// it can be optimized by using some caching, e.g. using Lazy<string>.
public string TenantId =>
this.accessor.HttpContext?.Request.Headers["tenant"].FirstOrDefault()
?? throw new Exception($"Request header tenant is missing");
}
Notice that this implementation might be a bit naive for your purposes, but hopefully you'll get the idea.
This class can be registered in the Composition Root of the Web API project as follows:
services.AddScoped<ITenantContext, WebApiTenantContext>();
Because the WebApiTenantContext has all its dependencies defined in the constructor, you can do a simple mapping between the ITenantContext abstraction and the WebApiTenantContext implementation.
For the Console application, however, you need a very different approach. The WebApiTenantContext, as shown above, is currently stateless. It is able to pull in the required data (i.e. TenantId) from its dependencies. This probably won't work for your Console application. In that case, you will likely need to manually wrap the execution of each message from the queue in a IServiceScope and initialize the ConsoleTenantContext at the beginning of that request. In that case, the ConsoleTenantContext would look merely as follows:
public class ConsoleTenantContext : ITentantContext
{
public string TenantId { get; set; }
}
Somewhere in the Console application's Composition Root, you will have to pull messages from the queue (logic that you likely already have), and that's the point where you do something as follows:
var envelope = PullInFromQueue();
using (var scope = this.serviceProvider.CreateScope())
{
// Initialize the tenant context
var context = scope.ServiceProvider.GetRequiredService<ConsoleTenantContext>();
content.TenantId = envelope.TenantId;
// Forward the call to the message handler
var handler = scope.ServiceProvider.GetRequiredService<IMessageHandler>();
handler.Handle(envelope.Message);
}
The Console application's Composition Root will how have the following registrations:
services.AddScoped<ConsoleTenantContext>();
services.AddScoped<ITenentContext>(
c => c.GetRequiredServices<ConsoleTenantContext>());
With the registrations above, you register the ConsoleTenantContext as scoped. This is needed, because the previous message infrastructure needs to pull in ConsoleTenantContext explicitly to configure it. But the rest of the application will depend instead on ITenantContext, which is why it needs to be registered as well. That registration just forwards itself to the registered ConsoleTenantContext to ensure that both registrations lead to the same instance within a single scope. This wouldn't work when there would be two instances.
Note that you could use the same approach for Web API as demonstrated here for the Console application, but in practice it's harder to intervene in the request lifecycle of Web API compared to doing that with your Console application, where you are in full control. That's why using an ITenantContext implementation that is itself responsible of retrieving the right values is in this case an easier solution for a Web API, compared to the ITenantContext that is initialized from the outside.
What you saw here was a demonstration of different composition models that you can use while configuring your application. I wrote extensively about this in my series on DI Composition Models on my blog.

ASP.NET Core 3 - Is it possible to add a SignalR service in Configure method when dependencies required for a factory method are available?

I am learning ASP.NET core and struggling trying to inject some ILogger instances required as parameters within a factory method used within my AddJsonProtocol of the AddSignalR method. See code below.
The JsonConvertersFactory factory method creates the JsonConverters for serialization/deserialization in signalR. It is used within the .AddJsonProtocol anonymous method when adding SignalR service.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddHostedService<MqttKafkaBridge>();
services.AddHostedService<ConsumerService>();
services.AddLogging();
// how can I retrieve ILogger<MotionDetection>, ILogger<MotionInfo>, ILogger<JsonVisitor>?
services.AddSignalR(o => o.EnableDetailedErrors = true)
.AddJsonProtocol(options =>
{
options.PayloadSerializerOptions = JsonConvertersFactory.CreateDefaultJsonConverters(LoggerMotionDetection, LoggerMotionInfo, LoggerJsonVisitor);
});
services.AddCustomConfiguration(services);
}
I could build a temporary service provider and add logger dependencies so that the SignalR Json protocol could be configued. However I am not sure if this is considered an anti-pattern....
var sp = services.BuildServiceProvider();
var loggerMD = sp.GetService<ILogger<MotionDetection>>();
var loggerMI = sp.GetService<ILogger<MotionInfo>>();
var loggerJV = sp.GetService<ILogger<JsonVisitor>>();
When I use BuildServiceProvider I receive the following warning message:
Startup.cs(53,22): warning ASP0000: Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.
Is it possible to add a service in Configure method, i.e. add the SignalR service in Configure when ILogger instances can be resolved?
OR
Is it possible to inject the SignalRService in Configure method and update it's Json protocol configuration?
Update
Tried injecting ISignalRServerBuilder in Configurebut receive an exception that could not find an ISignalRServerBuilder instance in container registry (Autofac).
Any ideas?
public void Configure(IApplicationBuilder app, ISignalRServerBuilder builder, IWebHostEnvironment env)
{
// this tries to build the options from the service container but
// the builder is null
builder.AddJsonProtocol(op =>
{
var lMD = app.ApplicationServices.GetService<ILogger<MotionDetectionConverter>>();
var lMI = app.ApplicationServices.GetService<ILogger<MotionInfoConverter>>();
var lJV = app.ApplicationServices.GetService<ILogger<JsonVisitor>>();
op.PayloadSerializerOptions = JsonConvertersFactory.CreateDefaultJsonConverters(lMD, lMI, lJV);
});
...
}

GetRequiredService from within Configure

I'm trying to access one of my services from within the Configure call within Startup.cs in aspnet core. I'm doing the following however I get the following error "No service for type 'UserService' has been registered." Now I know it is registered because I can use it in a controller so I'm just doing something wrong when it comes to using it here. Please can someone point me in the right direction. I'm happy with taking a different approach to setting up Tus if there's a better way of achieving what I want.
var userService = app.ApplicationServices.GetRequiredService<UserService>();
userService.UpdateProfileImage(file.Id);
The below is where I'm wanting to use
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
... Other stuff here...
app.InitializeSimpleInjector(container, Configuration);
container.Verify();
app.UseTus(httpContext =>
{
var restaurantEndpoint = "/restaurant/images";
var userEndpoint = "/account/images";
var endPoint = "/blank/images";
if (httpContext.Request.Path.StartsWithSegments(new PathString(restaurantEndpoint)))
{
endPoint = restaurantEndpoint;
}
if (httpContext.Request.Path.StartsWithSegments(new PathString(userEndpoint)))
{
endPoint = userEndpoint;
}
return new BranchTusConfiguration
{
Store = new TusDiskStore(#"C:\tusfiles\"),
UrlPath = endPoint,
Events = new Events
{
OnBeforeCreateAsync = ctx =>
{
return Task.CompletedTask;
},
OnCreateCompleteAsync = ctx =>
{
return Task.CompletedTask;
},
OnFileCompleteAsync = async ctx =>
{
var file = await ( (ITusReadableStore)ctx.Store ).GetFileAsync(ctx.FileId, ctx.CancellationToken);
var userService = app.ApplicationServices.GetRequiredService<UserService>();
userService.UpdateProfileImage(file.Id);
}
}
};
});
... More stuff here...
};
My end goal is to move this to an IApplicationBuilder extension to clean up my startup.cs but that shouldn't affect anything if it's working from within startup.cs
Edit: Add to show the registration of the userService. There is a whole lot of other stuff being registered and cross wired in the InitializeSimpleInjector method which I've left out. can add it all if need be..
public static void InitializeSimpleInjector(this IApplicationBuilder app, Container container, IConfigurationRoot configuration)
{
// Add application presentation components:
container.RegisterMvcControllers(app);
container.RegisterMvcViewComponents(app);
container.Register<UserService>(Lifestyle.Scoped);
container.CrossWire<IServiceProvider>(app);
container.Register<IServiceCollection, ServiceCollection>(Lifestyle.Scoped);
}
Please read the Simple Injector integration page for ASP.NET Core very closely, as Simple Injector integrates very differently with ASP.NET Core as Microsoft documented how DI Containers should integrate. The Simple Injector documentation states:
Please note that when integrating Simple Injector in ASP.NET Core, you do not replace ASP.NET’s built-in container, as advised by the Microsoft documentation. The practice with Simple Injector is to use Simple Injector to build up object graphs of your application components and let the built-in container build framework and third-party components
What this means is that, since the built-in container is still in place, resolving components using app.ApplicationServices.GetRequiredService<T>()—while they are registered in Simple Injector—will not work. In that case you are asking the built-in container and it doesn't know about the existence of those registrations.
Instead, you should resolve your type(s) using Simple Injector:
container.GetInstance<UserService>()

Injecting a service singleton into actor (Akka.NET) in ASP.NET Core

I am trying to inject a singleton of a service into an actor (Akka.NET) with ASP.NET Core's built-in DI container.
I have done the following in ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
// ..
// Register singleton of service
services.AddSingleton<IMyService, MyService>();
// Build service provider
var provider = services.BuildServiceProvider();
// Create actor system
var system = ActorSystem.Create("MyActorSystem");
// Inject service singleton into actor
directory.MyActorRef
= system.ActorOf(MyActor.Props(provider.GetService<IMyService>()), "myactor");
}
The issue is that the instance of MyService in the actor is different from the instance that is injected into the rest of the application - i.e. it is not a singleton.
What am I doing wrong and is there a better way of doing this?
That's because you create a separate IoC container inside your ConfigureServices
// Build service provider
var provider = services.BuildServiceProvider();
This line will create a new service provider (IoC container). When you resolve services from it, they are effectively singletons (since its not resolved from a scoped provider).
You shouldn't ever call .BuildServiceProvider() inside your ConfigureServices method, except when using 3rd party container and create it (i.e. when using Autofac).
Anyways, if you for some reason have to create the provider inside of ConfigureServices you need to change signature of ConfigureServices to
// Return value from void to IServiceProvider
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var provider = services.BuildServiceProvider();
// don't call services.AddXxx(..) after this point! The container is already created and its registrations can't be changed
...
return provider;
}
This will make ASP.NET Core use this container instead of creating its own one and passing that to Configure Method.
While this may solve you immediate problem, its not very clean to do that kind of resolving inside ConfigureServices and you should use the docs (or ask a separate question) on how to correctly use DI with Akka.NET (Sorry not familiar with it, I'm Microsoft Orleans user :)).
A slightly better (still not fully correct since it works around the idea of DI) way would be to delay the instantiation of the actor until Configure method is called.
public void ConfigureServices(IServiceCollection services)
{
// ..
// Register singleton of service
services.AddSingleton<IMyService, MyService>();
}
public void Configure(IApplicationBuilder app)
{
// Create actor system
var system = ActorSystem.Create("MyActorSystem");
// Inject service singleton into actor
directory.MyActorRef
= system.ActorOf(MyActor.Props(app.ApplicationServices.GetService<IMyService>()), "myactor");
}
or
public void ConfigureServices(IServiceCollection services)
{
// ..
// Register singleton of service
services.AddSingleton<IMyService, MyService>();
}
// inject it in Configure
public void Configure(IApplicationBuilder app, IMyService myService)
{
// Create actor system
var system = ActorSystem.Create("MyActorSystem");
// Inject service singleton into actor
directory.MyActorRef
= system.ActorOf(MyActor.Props(myService), "myactor");
}
This will initialize and resolve your services in Configure.
Remarks regarding singletons, scopes and actors
P.S. keep in mind, you can't resolve scoped services from app.ApplicationServices or the service provider, it will throw an exception. This may become an issue when you want to use DbContext which, by default is registered as scoped service.
You can also register it as scoped with an override to AddDbContext, but be aware of "memory leaks", as the number of tracked objects grows, so will the memory consumption (and big number of tracked entities (>=10k) will decrease your tracker related operations significantly).
And with DbContext in mind, also keep in mind that EF and EF Core are not thread-safe, and can't be accessed by threads (or run multiple asynchronous operations, i.e. starting 5 queries w/o awaiting and then using await Task.WaitAll(...)).
While an actor is guaranteed to only be accessed by a single thread at a single time, the services aren't if you scope them.
How well this works depends on the Task Scheduler implementation used by Akka.NET (again, not familiar with it's internals - i.e. Orleans abstracts persistence behind storage providers).

How create a middleware with api endpoints in .NET Core

I have created the web application with the web api. The application contains some Controllers for example TodoController:
namespace TodoApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
private readonly TodoContext _context;
public TodoController(TodoContext context)
{
_context = context;
}
[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
return _context.TodoItems.ToList();
}
}
}
If I create the GET request - /api/todo - I get the list of Todos from database.
I have a list of controllers and api endpoints like above.
I would like distribute this api to another application ideally like middleware - my idea is register in Startup.cs like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddTodoApi();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseTodoApi();
}
This will be awesome use case for my api but I don't know how this controllers api endpoints rewrite like middleware and return same JSON data same approache like using classic Controllers.
How can I write the middleware in .NET Core for creating API endpoints?
Instead of the separate middleware, you may configure the MVC middleware to discovery controllers from another assembly:
// using System.Reflection;
public void ConfigureServices(IServiceCollection services)
{
...
services
.AddMvc()
.AddApplicationPart(typeof(TodoController).GetTypeInfo().Assembly);
Controllers are part of MVC middleware, they are not a separate part of request pipeline (but this is what middlewares are). When you register the custom middleware, it by default invokes on each request and you have HttpContext context as an input parameter to work with/edit
Request/Response data. But ASP.NET Core provides Map* extensions that are used as a convention for branching the pipeline.
Map branches the request pipeline based on matches of the given request path. If the request path starts with the given path, the branch is executed.
Example:
private static void HandleMapTodo(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("/api/todo was handled");
});
}
public void Configure(IApplicationBuilder app)
{
app.Map("/api/todo", HandleMapTodo);
}
Note, that as middleware knows nothing about MVC middleware, you have only access to "raw" request and do not have features like model binding or MVC action filters.
Because it looks like the perfect microservices approach (similar than what my team is doing right now) I'd create a client assembly that can consume your API, the one that contains your TodoController, if you define a contract, and interface, for that API you can register it in your other assembly as it was a midleware and also you could mock that behaviour in your unit tests.
So, as I said, you could inject your client in ConfigureServices method, you can create:
public static IServiceCollection AddTodoRestClient(this IServiceCollection services)
{
services.AddSingleton<ITodoRestClient, TodoRestClient>();
return services;
}
Also consider that you will need to provide the enpoint so, it might looks like:
public static IServiceCollection AddConfiguredTodoClient(this IServiceCollection services, string todoEndpoint)
{
AddTodoClient(services);
ITodoRestClient todoRestClient = services.BuildServiceProvider().GetService<ITodoRestClient>();
// Imagine you have a configure method...
todoRestClient.Configure(services, todoEndpoint);
return services;
}
You can create those methods in a TodoRestClientInjector class and use them in Configure method on your startup.
I hope it helps
--- MORE DETAILS TO ANSWER COMMENTS ---
For me TodoClient is a Rest client library that implements calls to the ToDo API, (I've edited previous code to be TodoRestClient) methos like, i.e., CreateTodoItem(TodoDto todoItem) which implementation would call to the TodoController.Post([FromBody] item) or GetTodos() which wuold call TodoController.Get() and so on and so forth....
Regarding the enpoints... This approach implies to have (at least) two different applications (.NET Core apps), on the one hand the ASP NET Core app that has your TodoController and on the other hand a console application or another ASP NET Core API on which startup class you'll do the inyection adn the Rest client (the Todo Rest client) configuration ...
In a microservices approach using docker, in a dev environment, you'll use docker-compose-yml, but in a traditional approach you'll use concrete ports to define the endpoints...
So, imagine that you have in the second service a controller that need to use TodoController, to achieve so I'll use the above aproach and the "SecondController" would look like:
public class SecondController : Controller
{
private readonly SecondContext _context;
private readonly TodoRestClient _todoRestClient;
public TodoController(SecondContext context, ITodoRestClient todoRestClient)
{
_context = context;
_todoRestClient= todoRestClient;
}
// Whatever logic in this second controller... but the usage would be like:
_todoRestClient.GetTodos()
}
Just few final hints: it's key to minimize calls between services because it increases latency, and more and more if this happens on cascade. Also consider Docker usage, looks challenging but it is quite easy to start and, indeed, is thought to be used in scenarios that the one you presented and solutions like mine.
Again, I hope it helps.
Juan