Application Builder Map does not work for middlewares - c#

I am encountering the following problem:
I have a ASP NET Core Application where I am using the following routes:
status, message, http.The first 2 accept a websocket request.
The problem is that the AppBuilder.Map in the pipeline does not work and it always sends me to the first route for all requests.
Program
class Program
{
static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args)=>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseKestrel()
.UseSockets()
.UseUrls($"http://0.0.0.0:{Constants.SERVER_PORT}/")
.Build();
}
Startup
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<StatusService>();//(x => new StatusService());
services.AddTransient<RegisterService>();
services.AddTransient<MessagingService>();
services.AddCors();
}
public void Configure(IApplicationBuilder builder)
{
builder.UseCors((p) => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials());
builder.UseWebSockets();
builder.Map("/status", app =>
{
builder.UseMiddleware<StatusWare>();
});
builder.Map("/http", app =>
{
builder.UseMiddleware<HTTPWare>();
});
builder.Map("/message", app =>
{
builder.UseMiddleware<MessageWare>();
});
}
}
The Middlewares all use their specific service which I will not post since the Invoke method of the other two middlewares does not get invoked.
Middlewares
Status
class StatusWare
{
StatusService handler;
public StatusWare(StatusService _handler,RequestDelegate del)
{
this.handler = _handler;
this.next = del;
}
RequestDelegate next;
public async Task Invoke(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await this.next(context);
return;
}
await this.handler.AddClientAsync(context.WebSockets);
}
}
Message
class MessageWare
{
private MessagingService messageService;
private RequestDelegate next;
public MessageWare(MessagingService serv,RequestDelegate del)
{
this.messageService = serv;
this.next = del;
}
public async Task Invoke(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await next(context);
}
await this.messageService.AcceptClientAsync(context.WebSockets);
}
}
HTTP
class HTTPWare
{
RequestDelegate next;
RegisterService registerService;
public HTTPWare(RequestDelegate _del,RegisterService service)
{
this.next = _del;
this.registerService = service;
}
public async Task Invoke(HttpContext context)
{
}
}
As you can see the middlewares are almost identical (I did not write anything in HttpWare since its Invoke method does not get called either.
So my question is .why despite using AppBuilder.Map all requests go into the first middleware StatusWare?
Could it be because of the way the specific services are added in ConfigureServices?

Configure is calling the UseMiddleware on the original builder and not on the delegate argument. In this case app
Update the Map calls to use the middleware on the builder delegate argument.
public void Configure(IApplicationBuilder builder) {
builder.UseCors((p) => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials());
builder.UseWebSockets();
builder.Map("/status", app => {
app.UseMiddleware<StatusWare>();
});
builder.Map("/http", app => {
app.UseMiddleware<HTTPWare>();
});
builder.Map("/message", app => {
app.UseMiddleware<MessageWare>();
});
}
Reference ASP.NET Core Middleware

Related

GRPC service instantiated per call

I've created a GRPC service host under .NET core 3.1 (using Grpc.AspNetCore v2.30 from https://github.com/grpc/grpc-dotnet). By putting a breakpoint in the "ProxyService" constructor, I can see that the class is instantiated per call - every time a GRPC call is coming from a client, the breakpoint is hit. How do I configure it to always use the same ProxyService instance?
These are the program and Startup classes:
class Program
{
const int _port = 23456;
static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
Console.WriteLine("started - press any key to quit...");
Console.ReadKey();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
options.ConfigureEndpointDefaults(o =>
{
o.Protocols = HttpProtocols.Http2;
});
options.ListenAnyIP(_port);
});
webBuilder.UseStartup<Startup>();
});
}
public class ProxyService : StreamingApi.Protos.StreamingApi.StreamingApiBase
{
public ProxyService()
{
// gets here with every client call
}
public override Task<UpdateResponse> Update(UpdateRequest request, ServerCallContext context)
{
return Task.FromResult(new UpdateResponse());
}
}
class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<ProxyService>();
});
}
}
First, let me guess why you want to do this:
You have some heavy-logic inside ProxyService like initialization of some sort;
You have static variables which you want to share between calls;
To resolve first case you should use either method itself:
public ProxyService(IFooBar foobar)
{
this.foobar = foobar;
}
public override Task<UpdateResponse> Update(UpdateRequest request, ServerCallContext context)
{
await this.foobar.InitializeAsync();
return Task.FromResult(new UpdateResponse());
}
Or some other trigger in your system, like for example "At service start":
public interface IFooBarInitilizer :IHostedService
{
}
public class FooBarInitilizer : IFooBarInitilizer
{
public async Task StartAsync(CancellationToken token){ await this.foobar.InitializeAsync(); }
}
//in your Configure
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddSingleton<IFooBarInitializer, FooBarInitializer>();
services.AddHostedService(x=> x.GetService<IFooBarInitializer>());
}
For the second case it is even easier, because you just can specify your shared resources through interface dpendency:
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddSingleton<IFooBarResource, FooBarResource>();
}
public class ProxyService : StreamingApi.Protos.StreamingApi.StreamingApiBase
{
public ProxyService(IFooBarResource myStaticResource)
{
this.myStaticResource = myStaticResource;
}
public override Task<UpdateResponse> Update(UpdateRequest request, ServerCallContext context)
{
var somethingGood = this.myStaticResource.GetMeSomethingGood();
return Task.FromResult(new UpdateResponse());
}
}
According to this page you can register your grpc service yourself as a singleton.
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddSingleton(new ProxyService());
}

Passing a Hub Context to a non Controller Class

I am trying to send a message to a client in the server using SignalR
I am trying to do that in a class that is not a Controller. I have made the Startup like so:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.Configure<ConfigurationModel>(Configuration.GetSection("configurationModel"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSignalR();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
app.UseSignalR(routes => { routes.MapHub<MoveViewHub>("/movehub"); });
}
}
In my Program, this one:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
This is in my Hub:
public class MoveViewHub : Hub
{
private async void ReceiveTagNumber(object sender, EventArgs e)
{
await Clients.All.SendAsync("ReceivedFromServer", sender.ToString());
}
public async Task MoveViewFromServer(float newX, float newY)
{
Console.WriteLine(#"Receive position from Server app: " + newX + "/" + newY);
await Clients.Others.SendAsync("ReceivedNewPosition", newX, newY);
//await Clients.All.SendAsync("ReceivedNewPosition", newX, newY);
}
public async Task WriteThisMessage(string message)
{
Console.WriteLine(message);
await Clients.Others.SendAsync("ReceivedStatus", "Message was received. Thank you.");
}
public override Task OnConnectedAsync()
{
Console.WriteLine("Client has connected");
RfidClass rfidClass = new RfidClass("THE HUB CONTEXT SHOULD BE HERE"); ====>> I NEED TO PASS MY HUBCONTEXT
rfidClass.sas();
RfidClass.SendTagNumber += ReceiveTagNumber;
System.Diagnostics.Process.Start(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Notepad++", #"notepad++.exe"));
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
Console.Write("Client has disconnected");
return base.OnDisconnectedAsync(exception);
}
}
This is the RfidClass:
private IHubContext<MoveViewHub> hubContext;
public RfidClass(IHubContext<MoveViewHub> hubContext)
{
this.hubContext = hubContext;
}
public void sas()
{
Start();
}
private void Start()
{
try
{
hubContext.Clients.Others.SendAsync("ReceivedFromServer", "You are connected");
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
How can I make it right?
You need to inject IServiceProvider into your Hub by .NET Core DI (like into standard Controller, injecting by constructor):
public class MoveViewHub : Hub
{
private readonly IServiceProvider provider
public MovieViewHub(IServiceProvider provider)
{
this.provider = provider
}
}
Then you can do something like this:
public override Task OnConnectedAsync()
{
Console.WriteLine("Client has connected");
// you need to inject service provider to your hub, then get hub context from
// registered services
using (var scope = this.provider.CreateScope())
{
// get instance of hub from service provider
var scopedServices = scope.ServiceProvider;
var hub = scopedServices.GetRequiredService<IHubContext<MoveViewHub>>
// pass hub to class constructor
RfidClass rfidClass = new RfidClass(hub)
rfidClass.sas();
RfidClass.SendTagNumber += ReceiveTagNumber;
}
System.Diagnostics.Process.Start(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Notepad++", #"notepad++.exe"));
return base.OnConnectedAsync();
}
EDIT:
If you just want to SignalR work, you dont need to work on Hub. Instead make service. In this service inject HubContext<> of your Hub:
// you need to make your own class and interface and inject hub context
public interface ISignalRService
{
Task SendMessageToAll(string message);
}
public class SignalRService : ISignalRService
{
private readonly IHubContext<YourHub> hubContext;
public SignalRService (IHubContext<NotificationHub> hubContext)
{
this.hubContext = hubContext;
}
public async Task SendMessageToAll(string message)
{
await this.hubContext.Clients.All.SendAsync("ReciveMessage", message);
}
}
Then register that service in your Startup class:
services.AddScoped<ISignalRService, SignalRService>();
After that you can call SignalRService wherever you want to like normal service from .NetCore DI container:
private readonly ISignalRService notificationService;
public SomeController(ISignalRService notificationService)
{
this.notificationService = notificationService;
}
[HttpGet]
public async Task<IActionResult> Send()
{
await this.notificationService.SendMessageToAll("message");
return Ok();
}
You dont need to make some work around like RfidClass.

Get Instance of HubContext for re-use anywhere At SelfHosted Asp.net core Application

I have got a self hosted asp.net core app working at console. I can send message from my c# windows forms clients. But i want to send message anywhere in my server class to clients. Not one time message. So I need hubcontext instance for re-use it.
I have used IHubContext implement but im getting NullReference exception when I use hub context.
This is my Hub.
public class UniveraHub : Microsoft.AspNetCore.SignalR.Hub
{
public string GetConnectionId()
{
return Context.ConnectionId;
}
public async Task SendMessage(string message)
{
await Clients.All.SendAsync("ReceiveMessage", message);
}
}
This is my Startup Class
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseSignalR(routes =>
{
routes.MapHub<UniveraHub>("/UniveraHub");
});
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddScoped<MyHubHelper>();
}
}
This is my HubHelper Class
public class MyHubHelper
{
private readonly IHubContext<UniveraHub> _hubContext;
public MyHubHelper(IHubContext<UniveraHub> hubContext)
{
_hubContext = hubContext;
}
public MyHubHelper()
{
}
public void SendOutAlert(string msg)
{
_hubContext.Clients.All.SendAsync("ReceivedMessage", msg);
}
}
This program.cs that I'm using my helper class for send message to clients
MyHubHelper helper = new MyHubHelper();
helper.SendOutAlert("Hi from server!");
In Program.cs you can use like below:
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args)
.Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var helper = services.GetRequiredService<MyHubHelper>();
helper.SendOutAlert("Hi from server!");
}
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}

How to invoke SignalR Clients.All.InvokeAsync() in places other than the Controller?

I'm able to access my IHubContext<MyHub> fine and dandy in my .NET Core WebAPI's Controller through DI in the constructor, but I want to access it elsewhere too.
Specifically, when I consume a message from RabbitMQ, sometimes I want to update clients through _myHubContext.Clients.All.InvokeAsync(), but I just can't figure out how to get it.
I'm also having issues finding documentation for doing this kind of thing outside of the controller.
Any help would be appreciated.
EDIT:
To add some detail, and where the cause of my problem may originate, I'm trying to access the IHubContext (and some of my own services registered in ConfigureServices) within my Startup class, specifically during IApplicationLifetime ApplicationStarted and ApplicationStopped which call a RabbitMQ consumer's methods to connect and disconnect.
I'm I correct in guessing that maybe I'm unable to access registered services in the Startup class? If so, how would I go about starting these services?
Update:
Moving services.AddSignalR() and some of the services that are called at startup one level up to the WebHost.ConfigureServices in Program.cs solved some of my problems, but of course there are more.
I wasn't getting any messages on my JS client when I received a message from RabbitMQ, but my client was connecting successfully. "Weird..." I thought. To get more info, I wired up a an GET action in my controller to sent some content through the SignalR Hub. Whenever I called that GET, it works... the IHubContext<MyHub>. I get the hubContext through the constructor in my RabbitMQ listener, just like I do with the controller.
The new question: are things injected differently in the controller than they are into services that I register myself at startup? How so, and how do I overcome that?
Some code to go with it...
Excerpt from Program.cs
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseIISIntegration()
.ConfigureServices(services => {
services.AddSignalR();
services.AddTransient<ISubscriber, Subscriber>();
services.AddTransient<IDataService, DataService>();
services.AddTransient<IHealthCheckProcessor, HealthCheckProcessor>();
services.AddTransient<INodeProcessor, NodeProcessor>();
})
.UseStartup<Startup>()
.Build();
From Startup.cs
public class Startup
{
public Startup(IConfiguration _configuration, ISubscriber _subscriber)
{
configuration = _configuration;
subscriber = _subscriber;
}
public IConfiguration configuration { get; }
public ISubscriber subscriber { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(builder => builder
// CORS stuff);
app.UseSignalR(routes =>
{
routes.MapHub<StatusHub>("Status");
});
app.UseMvc();
applicationLifetime.ApplicationStarted.Register(OnStartup);
applicationLifetime.ApplicationStopping.Register(OnShutdown);
}
private void OnStartup() {
// MessageBroker stuff
subscriber.Start(messageBroker);
}
private void OnShutdown() {
subscriber.Stop();
}
}
From Subscriber.cs
public class Subscriber : ISubscriber {
public static IConnection connection;
public static IModel channel;
public IHubContext<StatusHub> hubContext;
public static IHealthCheckProcessor healthCheckProcessor;
public static INodeProcessor nodeProcessor;
public Subscriber(IHubContext<StatusHub> _hubContext, INodeProcessor _nodeProcessor, IHealthCheckProcessor _healthCheckProcessor)
{
connection = new ConnectionFactory().CreateConnection();
channel = connection.CreateModel();
hubContext = _hubContext;
nodeProcessor = _nodeProcessor;
healthCheckProcessor = _healthCheckProcessor;
}
public void Start(MessageBroker messageBroker)
{
var factory = new ConnectionFactory() { HostName = messageBroker.URL }.CreateConnection();
foreach (Queue queue in messageBroker.Queues)
{
channel.QueueDeclare(
queue: queue.Name,
durable: queue.Durable,
exclusive: queue.Exclusive,
autoDelete: queue.AutoDelete,
arguments: null
);
EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
byte[] body = ea.Body;
string message = Encoding.UTF8.GetString(body);
RouteMessage(queue, message);
};
channel.BasicConsume(
queue: queue.Name,
autoAck: queue.AutoAck,
consumer: consumer
);
hubContext.Clients.All.InvokeAsync("Send", "It worked - from the subscriber");
}
}
public void RouteMessage(Queue queue, string message) {
if(queue.Name == "discovery") {
nodeProcessor.Process(message);
}
if(queue.Name == "health") {
healthCheckProcessor.Process(message);
}
}
public void Stop()
{
Console.WriteLine("Terminating connection to RabbitMQ instance.");
channel.Close(200, "Goodbye");
connection.Close();
}
}
From HealthCheckProcessor.cs
public class HealthCheckProcessor : IHealthCheckProcessor {
private IDataService dataService;
private IHubContext<StatusHub> hubContext;
public HealthCheckProcessor(IDataService _dataService, IHubContext<StatusHub> _hubContext)
{
dataService = _dataService;
hubContext = _hubContext;
}
public void Process(string message) {
HealthCheck health = JsonConvert.DeserializeObject<HealthCheck>(message);
Node node = dataService.GetSingle(health.NodeId);
node.Health = health;
dataService.Update(node);
Console.WriteLine("It's sending.");
hubContext.Clients.All.InvokeAsync("Send", "It worked - from the processor");
}
}
From the Controller:
[Route("api/[controller]")]
public class MyController: Controller
{
private IDataService _dataService;
private readonly IConfiguration configuration;
private static IHubContext<StatusHub> hubContext;
public NodesController(IConfiguration config, IDataService dataService, IHubContext<StatusHub> _hubContext)
{
_dataService = dataService;
configuration = config;
hubContext = _hubContext;
}
[HttpGet]
public string Get()
{
hubContext.Clients.All.InvokeAsync("Send", "Blarg!");
return "Well, I tried.";
}
}
You are trying to access services that are not available at the time you request them.
Configure is called after ConfigureServices specifically so that any services registered can be accessible.
public class Startup {
public Startup(IConfiguration _configuration) {
configuration = _configuration;
}
public IConfiguration configuration { get; }
public void ConfigureServices(IServiceCollection services) {
services.AddCors();
services.AddMvc();
services.AddSignalR();
services.AddTransient<ISubscriber, Subscriber>();
services.AddTransient<IDataService, DataService>();
services.AddTransient<IHealthCheckProcessor, HealthCheckProcessor>();
services.AddTransient<INodeProcessor, NodeProcessor>();
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
IApplicationLifetime applicationLifetime,
IServiceProvider sp
) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseCors(builder => builder
// CORS stuff);
app.UseMvc();
app.UseSignalR(routes => {
routes.MapHub<StatusHub>("Status");
});
//At this point all the necessary dependencies have been registered and configured
var subscriber = sp.GetService<ISubscriber>();
applicationLifetime.ApplicationStarted.Register(() => OnStartup(subscriber));
applicationLifetime.ApplicationStopping.Register(() => OnShutdown(subscriber));
}
private void OnStartup(ISubscriber subscriber) {
// MessageBroker stuff
subscriber.Start(messageBroker);
}
private void OnShutdown(ISubscriber subscriber) {
subscriber.Stop();
}
}
You should be able to now remove the convenience ConfigureServices when building the host.
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();

AspNetCore.SignalR : Cannot start a connection that is not in the Initial state

I have troubles to get my ASP.NET Core SignalR app working.
I have this server-side code :
public class PopcornHub : Hub
{
private int Users;
public async Task BroadcastNumberOfUsers(int nbUser)
{
await Clients.All.InvokeAsync("OnUserConnected", nbUser);
}
public override async Task OnConnectedAsync()
{
Users++;
await BroadcastNumberOfUsers(Users);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
Users--;
await BroadcastNumberOfUsers(Users);
await base.OnDisconnectedAsync(exception);
}
}
whose SignalR Hub service is configured as :
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSignalR();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
app.UseSignalR(routes =>
{
routes.MapHub<PopcornHub>("popcorn");
});
...
}
In my client-side (WPF app), I have a service :
public class PopcornHubService : IPopcornHubService
{
private readonly HubConnection _connection;
public PopcornHubService()
{
_connection = new HubConnectionBuilder()
.WithUrl($"{Utils.Constants.PopcornApi.Replace("/api", "/popcorn")}")
.Build();
_connection.On<int>("OnUserConnected", (message) =>
{
});
}
public async Task Start()
{
await _connection.StartAsync();
}
}
My issue is that, when I call Start() method, I get the exception "Cannot start a connection that is not in the Initial state".
The issue occurs either locally or in Azure.
The SignalR endpoint is fine but no connection can be established. What am I missing?
You seem to be trying to start a connection that has already been started. Also note that as of alpha2 the connection is not restartable - i.e. if it is stopped you cannot restart it - you need to create a new connection.
EDIT
In post alpha2 versions the connection will be restartable.

Categories