SignalR: Unable to call Hub method from Controller - c#

Environment:
Visual Studio 2017 Community with latest updates
Target Framework: .NET Core 2.1 (latest version)
SignalR Core
Running on IIS Express on Windows 10 (dev environment)
TL;DR: Injecting IHubContext<> into Controller ctor so Action method can send message to clients doesn't seem to be working.
Long version:
I have a basic ASP.NET Core test application working and .NET clients are able to connect and send/receive messages. So my Hub and Clients appear to be working fine.
I'm now trying to add a controller to the same VS project that the SignalrR Hub is in so that external actors can send messages via a REST API endpoint.
To do this I've tried using DI to inject IHubContext<> into the ctor of my controller as follows:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : Controller
{
private IHubContext<OrgHub> _hubContext;
public ValuesController(IHubContext<OrgHub> hubContext)
{
_hubContext = hubContext;
}
//...
}
This appears to be successfully injecting the right IHubContext because when I debug the private members I see the number of connections = 1 when I have 1 .NET client connected.
Now the trouble: within an action method I try to use the _hubContext to call a hub method... but nothing happens. The debugger passes by the line of code and no breakpoints within my Hub are hit. Nothing happens at all. Note that when .NET clients send messages (via the SignalR .NET client) the breakpoints on my Hub are indeed hit. Its just the _hubContext in my Controller/action method that doesn't seem to be working.
Here is what I'm doing in an action method:
// GET api/values
[HttpGet]
public async Task<ActionResult<IEnumerable<string>>> GetAsync()
{
//Try to call "SendMessage" on the hub:
await _hubContext.Clients.All.SendAsync("SendMessage", "SomeUserName", "SomeMessage");
//...
return new string[] { "bla", "bla" };
}
And here is the corresponding Hub method:
public class OrgHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
//...
}
If it helps, here is the edited version of Startup.cs:
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.AddSignalR();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSignalR(routes =>
{
routes.MapHub<OrgHub>("/rpc");
});
app.UseMvc();
}
}
So, any ideas or suggestions on where to go from here? Clearly there must be something I'm overlooking...
thanks!

That's not how this works. When you call SendAsync that message is going out to the client. You don't call methods on your hub, via SendAsync. Nothing is happening, because the clients are literally getting sent a message that should invoke something listening for "SendMessage" client-side, which presumably is not something you've register your clients to listen for. If the goal is to hit "ReceiveMessage" client-side, then you should be doing SendAsync("ReceiveMessage", ...) in your controller.

Related

MVC API not hitting controller

I'm moving some of internal projects from NET Core 2.0 to 3.0, and having trouble with getting the controller to execute after middleware has finished. Honestly I'm a bit frustrated as a similar approach used to work with NET Core 2.0.
I've uploaded my test project to GitHub:
https://github.com/wonea/MVC-API-Routing-Test
The test project details three middleware stages; SecurityMiddleware, UserValidatorMiddleware, WebSocketMiddleware. So upon booting the API you can set breakpoints on each of the individual stages and they will be hit in the correct order. However upon passing the HTTPContext on the final middleware stage it returns but does not hit the controller.
In Startup.cs I configure my services:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddApplicationInsightsTelemetry(_configuration);
// memory cache
services.AddMemoryCache();
services.AddMvc()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.IgnoreNullValues = true;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
// CORS
var corsBuilder = new CorsPolicyBuilder();
services.AddCors(builder =>
{
corsBuilder.AllowAnyHeader();
corsBuilder.AllowAnyMethod();
corsBuilder.AllowAnyOrigin();
corsBuilder.WithOrigins("*");
corsBuilder.AllowCredentials();
});
}
I've detailed the request pipeline, setting up WebSockets, performing the middleware, and then triggeringmy controller as an endpoint.
public void Configure(IApplicationBuilder app, IHostApplicationLifetime appLifetime)
{
appLifetime.ApplicationStarted.Register(OnStarted);
appLifetime.ApplicationStopping.Register(OnStopping);
appLifetime.ApplicationStopped.Register(OnStopped);
Console.CancelKeyPress += (sender, eventArgs) =>
{
appLifetime.StopApplication();
// Don't terminate the process immediately, wait for the Main thread to exit gracefully.
eventArgs.Cancel = true;
};
app.UseRouting();
app.UseSecurityMiddleware();
app.UseUserValidation();
// websockets
var webSocketOptions = new WebSocketOptions
{
KeepAliveInterval = TimeSpan.FromSeconds(120)
};
app.UseWebSockets(webSocketOptions);
app.UseWebSocketMiddleware();
// put last so header configs like CORS or Cookies etc can fire
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
I'm only ever going to need one controller, so don't need any fancy config.
I see your controller is setup like
[Route("api/[controller]")]
public class MainController : Controller
{
[HttpGet]
public string Get()
Note the docs say
Replace [controller] with the name of the controller, which by convention is the controller class name minus the "Controller" suffix.
For example, I have a "Work" controller that looks like
[Route("api/Work")]
[ApiController]
public class WorkController : ControllerBase
{
(it also has the ApiController attribute).
This would then be accessed at httpx://localhost/api/Work
You can then specify other endpoints on the api with the parameter to the HttpGet or HttpPost attribute.
[HttpGet("Test")]
public async Task<ActionResult<string>> Test()
{
Would be httpx://localhost/api/Work/Test
Figured it out, my routing was at fault.
My launch settings were point directly to the controllers location
Then my controller was duplicating the routing. So the main controller was being resolved on.
localhost:51234/api/main/api/main
I've flattened the main controller's routing now

SignalR Core, not getting response from server when client is connected

I am working on a SignalR Clinet-Server connection. My server is WebApi Core 2.1 and my client is WPF .NET Framework 4.7.2.
On the client side I have a singleton hub service with one Instance to recive messages from server:
using System.Collections.ObjectModel;
using Microsoft.AspNetCore.SignalR.Client;
public class HubService
{
//singleton
public static HubService Instance { get; } = new HubService();
public ObservableCollection<string> Notifications { get; set; }
public async void Initialize()
{
this.Notifications = new ObservableCollection<string>();
var hubConnection = new HubConnectionBuilder()
.WithUrl(UrlBuilder.BuildEndpoint("Notifications"))
.Build();
hubConnection.On<string>("ReciveServerUpdate", update =>
{
//todo
});
await hubConnection.StartAsync();
}
}
i initialize it as singleton:
public MainWindowViewModel()
{
HubService.Instance.Initialize();
}
While I'm debugging, on MainWindowViewModel im hitting that HubService.
On Server side its look like this.
Hub:
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
public class NotificationsHub : Hub
{
public async Task GetUpdateForServer(string call)
{
await this.Clients.Caller.SendAsync("ReciveServerUpdate", call);
}
}
Im trigering send message in this way in my controller's methods:
[HttpPost]
public async Task<IActionResult> PostTask([FromBody] Task task)
{
if (!this.ModelState.IsValid)
{
return this.BadRequest(this.ModelState);
}
this.taskService.Add(task);
//here im calling sending message. When im debugging
//i see one connection from my WPF with unique ConnectionId
await this.notificationsHub.Clients.All.SendAsync("ReciveServerUpdate", "New Task in database!");
return this.Ok(task);
}
As I wrote before, while I'm debugging my WebApi, in Clients I have exactly one connection from my WPF. When I turn off WPF, connection count = 0 so connections works perfectly.
But when I call SendAsync(), I'm not reciving any information in WPF in hubConnection.On. Funny thing, yesterday it works perfectly.
So, is my thinking about making HubService as static singleton is right? If its, why i cant recive messages from WebApi by SignalR when my WPF is connected to it?
I asked something similiar yesterday but i found a solution for it. Yesterday, my methods works, i could hit hubConnection.On when i get any message from WebApi. My question from yestarday.
EDIT
Injection of HUb to controller:
private readonly ITaskService taskService;
private readonly IHubContext<NotificationsHub> notificationsHub;
public TaskController(ITaskService taskService, IHubContext<NotificationsHub> notificationsHub)
{
this.taskService = taskService;
this.notificationsHub = notificationsHub;
}
And Startup.cs only SignalR things (i deleted other things not related to signal):
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddSignalR();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSignalR(routes => routes.MapHub<NotificationsHub>("/Notifications"));
}
EDIT2
Here is connection that i can get it, when my client WPF will register his connection:
I tried your code with all kinds of clients (wpf/console/even with a browser), it always works fine for me. The hubConnection.On<string>("ReciveServerUpdate", update => {//todo}); always be invoked when I send a request to PostTask.
I'm not sure why (sometimes) it doesn't work for you somehow . However, when SignalR client has connected to server but gets no message from server, there're possible two reasons:
Your PostTask([FromBody] Task task) action method is not executed. Let's say this is an ApiController method, if the browser posts a request with a Content-Type of application/www-x-form-urlencoded by accident, the invocation of Clients.All.SendAsync(..., ...); won't be executed at all.
The handler of SigalR client (hubConnection.On<>(method,handler)) must has exactly the same argument list as the invocation in order to receive messages. We must be very careful when dealing with this.
Finally, it's better to add a reference to Microsoft.Extensions.Logging.Console
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.*" />
so that we could enable logging to troubleshoot :
var hubConnection = new HubConnectionBuilder()
.WithUrl(UrlBuilder.BuildEndpoint("Notifications"))
.ConfigureLogging(logging =>{
logging.AddConsole(); // enable logging
})
.Build();

Policy based Authorization not working in asp.net core

I'm trying to get Policy based Authorization working in .net core 2.1 based on this article: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.1
However I cannot seem to get it to fire.
In the below example, I've commented out the context.suceeed line, so I would have thought my api call to my usercontroller would fail.
However my API call is being accepted.
What am I doing wrong?
This is my startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IAuthorizationHandler, VerifyAuthCookieHandler>();
services.AddAuthorization(options =>
{
options.AddPolicy("VerifyAuthCookie", policy =>
policy.Requirements.Add(new VerifyAuthCookieRequirement()));
});
services.AddMvcCore().AddJsonFormatters();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc();
}
}
Here is my handler
public class VerifyAuthCookieHandler : AuthorizationHandler<VerifyAuthCookieRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
VerifyAuthCookieRequirement requirement)
{
//context.Succeed(requirement);
return Task.CompletedTask;
}
}
And here is my requirement:
public class VerifyAuthCookieRequirement : IAuthorizationRequirement
{
public VerifyAuthCookieRequirement()
{
}
}
And finally, my controller:
[Route("api/[controller]")]
[Authorize(Policy = "VerifyAuthCookie")]
public class UserController : Controller
{
}
If I add code in HandleRequirementAsync and set a breakpoint then it's not being hit when I debug, so my Handler doesn't appear to be called at all.
You should call app.UseAuthentication(); before the app.UseMvc(); in the Configure method of the Startup class. This will add the ASP.NET Core authentication middleware to the request pipeline.
Since you are using services.AddMvcCore() we'll need to configure the authorization services manually, something services.AddMvc() does for you automatically. We should add .AddAuthorization() to the IMvcCoreBuilder.
This will add the default Authentication- and Authorization services and a PolicyEvaluator.
If you're interested in the exact services that will be registered in your DI container you should follow this link.
I had similar issue, I fix it by :
services.AddMvcCore().AddAuthorization().AddJsonFormatters();

Middleware class not called on api requests

I've created a basic webAPI project (blank web project with webAPI checked) and added the owin nuget packages to the project.
Microsoft.AspNet.WebApi.Owin
Microsoft.Owin.Host.SystemWeb
Owin
I've then created a Logging class, and hooked it up via startup
using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>;
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
Debug.WriteLine("Startup Called");
var config = new HttpConfiguration();
WebApiConfig.Register(config);
appBuilder.UseWebApi(config);
appBuilder.Use(typeof(LoggingMiddleware));
}
}
public class LoggingMiddleware
{
private AppFunc Next { get; set; }
public LoggingMiddleware(AppFunc next)
{
Next = next;
}
public async Task Invoke(IDictionary<string, object> environment)
{
Debug.WriteLine("Begin Request");
await Next.Invoke(environment);
Debug.WriteLine("End Request");
}
}
When I run the project, and the default page opens, I see the Begin/End requests called (twice, as it happens, not sure why that is).
However, if I try to call an /api route (such as `/api/ping/'), the request completes successfully, but I do not see the Begin/End request states in the log.
What am I missing with this?
Owin executes the middleware items in the order that they are registered, ending at the call to the controller (appBuilder.UseWebApi(config)) which does not appear to call next.Invoke(). Given that the code in the question has the Logging Middleware class registered after the UseWebApi call, this causes it to never be called for API requests.
Changing the code to:
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
//.....
//This must be registered first
appBuilder.Use(typeof(LoggingMiddleware));
//Register this last
appBuilder.UseWebApi(config);
}
}
resolves the issue.

Why does my MVC website hang on startup when using Nimbus with Azure Service Bus

I have been using MassTransit in my application and I have decided to see how Nimbus differs.
I am trying to write a proof of concept system, using Azure Service Bus and Nimbus with Autofac.
So far I have successfully written a Console Application which is configured to use Nimbus using Azure Service Bus and Autofac. I have followed all the examples that I can find online, in particular the PizzaSample from the Nimbus Samples.
This console application successfully starts up, configures the Bus, connects to Azure Service Bus and creates me a new Queue as expected in my Azure Service Bus. The application then begins to publish "Heartbeat" events, which seem to show that this application works fine.
Next I move onto a simple off the shelf, Visual Studio Templated MVC Application with no authentication.
I have again added Autofac, Nimbus, and Nimbus.Autofac and configured as per the above PizzaSample Web application. When I start the application, as expected Global.asax.cs is invoked, which configures Autofac and configures my Bus. When the Autofac container is built, my Bus is AutoActivated and the Instance of my Bus is Started. I can also see in Azure Service Bus that a queue for my web application has also been created successfully. However, this is where my application now hangs.
Upon further debugging, I seem to be able to hit my default controller and action (Home/Index) and I can step completely through all the code in this controller right upto and passed my "return View()". So all appears normal. However the view is never rendered and the browser sits at "Loading" until I kill "IIS Express".
If I amend my Web Applications Nimbus configuration to take out the Instance.Start(), and run the application without starting the bus, the application runs fine as expected, but obviously without the bus being started so I can't send/publish any messages.
Web Application Code
Global.asax.cs
public class MvcApplication : HttpApplication
{
protected void Application_Start()
{
ContainerConfig.Configure();
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
protected void Application_End()
{
ContainerConfig.TearDown();
}
}
ContainerConfig.cs
public static class ContainerConfig
{
private static IContainer _container;
public static void Configure()
{
var builder = new ContainerBuilder();
builder.RegisterModule<NimbusModule>();
builder.RegisterControllers(Assembly.GetExecutingAssembly());
_container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(_container));
}
public static void TearDown()
{
_container.Dispose();
}
}
NimbusModule.cs
public class NimbusModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
var connectionString = ConfigurationManager.ConnectionStrings["AzureServiceBus"].ConnectionString;
// You'll want a logger. There's a ConsoleLogger and a NullLogger if you really don't care. You can roll your
// own by implementing the ILogger interface if you want to hook it to an existing logging implementation.
builder.RegisterType<ConsoleLogger>()
.AsImplementedInterfaces()
.SingleInstance();
var messageContactAssembly = typeof(MyTestEvent).Assembly;
var handlerTypesProvider = new AssemblyScanningTypeProvider(messageContactAssembly);
builder.RegisterNimbus(handlerTypesProvider);
builder.Register(componentContext => new BusBuilder()
.Configure()
.WithConnectionString(connectionString)
.WithNames("Nimbus.Web", Environment.MachineName)
.WithTypesFrom(handlerTypesProvider)
.WithAutofacDefaults(componentContext)
.Build())
.As<IBus>()
.AutoActivate()
.OnActivated(c => c.Instance.Start())
.SingleInstance();
}
}
MyTestEvent.cs (From an independent referenced contract assembly)
public class MyTestEvent : IBusEvent
{
public MyTestEvent(Guid id)
{
Id= id;
}
public Guid Id { get; private set; }
}
HomeController.cs
public class HomeController : Controller
{
private readonly IBus _bus;
public HomeController(IBus bus)
{
_bus = bus;
}
public ActionResult Index()
{
_bus.Publish(new MyTestEvent(Guid.NewGuid()));
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
I obviously have some configuration wrong somewhere but I have spent hours looking at this and bashing my head against a wall. The closest that I can get to is thinking its something to do with the bus being started Async and perhaps somewhere the application is waiting for the Bus? But this is just a huge assumption and we all know assumptions are evil.
Has anyone seen this happen before and can offer any advice?

Categories

Resources