.NET Core, SignalR Hub's constructor IHubCallerClients is NULL - signalr

I'm trying to implement .NET Core 2.2/SignalR 1.1.0.
In startup:
public void ConfigureServices(IServiceCollection services)
services.AddSignalR();
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub");
});
It works smoothly when I apply a one-to-one example.
But I need an architectural change.
My example:
public class ChatHub : Hub
{
ResponseHandler ResponseHandler { get; set; }
public ChatHub()
{
IHubCallerClients hubCallerClients = this.Clients;
ResponseHandler = new ResponseHandler(hubCallerClients);
}
public async Task SendMessage(string user, string message)
{
IHubCallerClients hubCallerClients = this.Clients;
await ResponseHandler.R();
}
}
If I tried to get this.Clients in the constructor it is coming with null data. But if I try to take it in the method, it comes full as expected.
I should get IHubCallerClients in the contructor so that I can forward it to another Response context.
Thanks advance!

OK. I solved the problem by
public class RequestHandler : Hub
{
ResponseHandler ResponseHandler { get; set; }
public RequestHandler(IHubContext<RequestHandler> hubContext)
{
ResponseHandler = new ResponseHandler(hubContext);
}
public async Task SendMessage(string user, string message)
{
await ResponseHandler.R();
}
}
Due to the nature of .net core, context comes to constructor as dependency.
"services.AddSignalR();" we're sure to add it to Scope.
"IHubContext hubContext" In this way, we can collect the contructured object.

Related

Eager loading doesn't work with BackgroundService

I'm trying to load related entities in a console application that runs a BackgroundService,
but it doesn't load related entities, I have had this problem for hours now and I just noticed it happens only in the BackgroundService, tried the same DbContext in a web application by injecting the DbContext class in the Index page model, without a problem.
Here's the code from the console application:
the Background Service:
public class MyService : BackgroundService
{
private readonly MyDbContext _context;
public MyService(MyDbContext context)
{
_context = context;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
//Jobs always empty!
var theBatch = _context.Batches.Include(x => x.Jobs).FirstOrDefault();
}
}
the program file:
class Program
{
static async Task Main(string[] args)
{
using IHost host = CreateHostBuilder(args).Build();
await host.RunAsync();
}
static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
services.AddLogging()
.AddDbContext<MyDbContext>(options => options
.UseSqlServer(context.Configuration.GetConnectionString("MyConnection")))
.AddHostedService<MyService>()
.BuildServiceProvider();
});
}
I'm using .NET 5 and EF Core 5.0.12
What you have should work as written. So double-check your configuration. As #Nkosi points out you should use shorter scopes, but putting one scope in your ExecuteAsync isn't enough, as the Task it returns has the same lifetime as the BackgroundService instance.
Anyway here's a complete repro that works fine in .NET 6.
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
class Program
{
static async Task Main(string[] args)
{
using IHost host = CreateHostBuilder(args).Build();
await host.RunAsync();
}
static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
services.AddLogging()
.AddDbContext<MyDbContext>(options => options
.UseSqlServer(context.Configuration.GetConnectionString("MyConnection")))
.AddHostedService<MyService>()
.BuildServiceProvider();
});
}
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{ }
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public DbSet<Batch> Batches { get; set; }
public DbSet<Job> Jobs{ get; set; }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
}
public class Job
{
public int Id { get; set; }
}
public class Batch
{
public int Id { get; set; }
public virtual ICollection<Job> Jobs { get; set; } = new HashSet<Job>();
}
public class MyService : BackgroundService
{
private readonly MyDbContext _context;
public MyService(MyDbContext context)
{
_context = context;
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var batch = new Batch();
batch.Jobs.Add(new Job() );
context.Batches.Add(batch);
context.SaveChanges();
context.ChangeTracker.Clear();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var theBatch = await _context.Batches.Include(x => x.Jobs).FirstOrDefaultAsync();
Console.WriteLine(theBatch.Jobs.Count);
}
}
OK, I'm writing this while I'm really angry for the hours and the effort spent on this problem, I don't know who to blame, Microsoft or the person who installed EF 6 in .NET core project. Microsoft made it difficult to find out the source of the problem.
I was using Include from System.Data.Entity namespace not Microsoft.EntityFrameworkCore
I figured it out by a mere coincidence, I used FirstOrDefaultAsync without a specific reason, and got this weird exception:
'The provider for the source IQueryable doesn't implement
IDbAsyncQueryProvider. Only providers that implement
IDbAsyncQueryProvider can be used for Entity Framework asynchronous
operations.
When I looked at it, I found that I was using the wrong namespace for FirstOrDefaultAsync (System.Data.Entity instead of Microsoft.EntityFrameworkCore)
when I changed the namespace, everything worked!

Controller cannot reach Controller in other project because of constructor ASP:NET Core

I'm new to ASP.NET Core and I'm trying to solve this problem for a week now.
I have a solution with two projects.
And when I start the porject the browser just says:
InvalidOperationException: Unable to resolve service for type 'TSM_Programm.Data.TSMContext' while attempting to activate 'TSM_Programm.Controllers.ResourcesController'.
The first part of the solution is my API-Layer that passes data to a user (currently via postman).
The second project is my Data Access Layer.
This Layer contains several Controllers, all of them using the same constructor, which is the following:
public TSMContext _context;
public ResourcesController(TSMContext context)
{
_context = context;
}
The TSMContext Class is the following:
namespace TSM_Programm.Data
{
public class TSMContext : DbContext
{
public TSMContext(DbContextOptions<TSMContext> options)
: base(options)
{
}
public DbSet<Resource> Resources { get; set; }
public DbSet<Parameter> Parameters { get; set; }
public DbSet<ResourceToParameter> ResourceToParameters { get; set; }
public DbSet<Reservation> Reservations { get; set; }
}
So far so god, but when I am trying to start the program the controllerof the API-Layer does not seem to be able to handle the constructor.
This is my API-Conrtoller:
namespace TSM_API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class APIController : ControllerBase //Base Class without View Support
{
//Troublemaker
public ResourcesController _resources;
public ParametersController _parameters;
public ReservationsController _reservations;
public APIController(ResourcesController resources, ParametersController parameters, ReservationsController reservations)
{
_resources = resources;
_parameters = parameters;
_reservations = reservations;
}
//Function to check if controller works
//GET: api/API
[HttpGet]
public IEnumerable<string> Get()
{
// ResourcesController controller = new ResourcesController();
return new string[] { "value1", "value2" };
}
The API-Controller was not able to use its own constructors, that's why I changed the Startup.cs.
namespace TSM_API
{
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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMvc().AddApplicationPart(typeof(ResourcesController).Assembly).AddControllersAsServices();
services.AddMvc().AddApplicationPart(typeof(ParametersController).Assembly).AddControllersAsServices();
services.AddMvc().AddApplicationPart(typeof(ReservationsController).Assembly).AddControllersAsServices();
services.AddMvc().AddApplicationPart(typeof(TSMContext).Assembly).AddControllersAsServices();
}
I'm simply out of ideas on how to solve the problem, since I can't add the TSMContext class a service.
Any idea how to solve it?
Thank you.
I see you have not registered your dbcontext as a dependency injection. Your issue might be due to ResourceController trying to access _context as a DI but it is not registered. To use the context as a dependency injection, register it in the startup.cs as following.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TSMContext>(options => options.UseSqlServer(Configuration.GetConnectionString("YOUR_CONNECTION_STRING")));
//If you have any services that should be used as DI, then they also must be registered as like this
services.AddScoped<Interface, Class>(); //Interface refer to the service interface while class is the actual service you will use.
}

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.

EasyNetQ (AMQP) Single Application Connection In ASP.NET?

Community:
I'm struggling to figure out how to create a single AMQP connection that lives with my ASP.NET application lifecycle in ASP.NET using .NET Core 2.1. After researching, I've found lots of references to using a single AMQP connection for the whole application as they are expensive and slow to create and I was headed down the road of creating the connection using DI but it appears my approach is flawed, I can't seem to identify which interface I need to add as a singleton...
public void ConfigureServices(IServiceCollection services)
{
var sqlConnectionStringBuilder = new SqlConnectionStringBuilder(Configuration.GetConnectionString("DefaultConnection"));
var envSQL = Environment.GetEnvironmentVariable("ASPNETCORE_SQL_SERVER");
if (envSQL != null)
sqlConnectionStringBuilder.DataSource = envSQL;
services.AddSingleton<IMessageBusService, MessageBusService>();
services.AddSingleton<EasyNetQ.IAdvancedBus, RabbitAdvancedBus>();
services.AddSingleton<EasyNetQ.IConnectionFactory, ConnectionFactoryWrapper>();
services.AddMvc();
}
Adding the above interfaces works but I get an error about ConnectionConfiguration service not being locatable. Is this the right direction or is there a more proper way to create a single application once EasyNetQ connection in ASP.NET core?
You can use AutoSubcriber in .net core
and use the sample code here.
add connection to appsettings.json
"MessageBroker": {
"ConnectionString": "host=localhost"
}
then add IBus in ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IBus>(RabbitHutch.CreateBus(Configuration["MessageBroker:ConnectionString"]));
services.AddSingleton(RabbitHutch.CreateBus(Configuration["MessageBroker:ConnectionString"]));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
add class AppBuilderExtension and use extension method for auto subscriber
public static class AppBuilderExtension
{
public static IApplicationBuilder UseSubscribe(this IApplicationBuilder appBuilder, string subscriptionIdPrefix, Assembly assembly)
{
var services = appBuilder.ApplicationServices.CreateScope().ServiceProvider;
var lifeTime = services.GetService<IApplicationLifetime>();
var Bus = services.GetService<IBus>();
lifeTime.ApplicationStarted.Register(() =>
{
var subscriber = new AutoSubscriber(Bus, subscriptionIdPrefix);
subscriber.Subscribe(assembly);
subscriber.SubscribeAsync(assembly);
});
lifeTime.ApplicationStopped.Register(() => Bus.Dispose());
return appBuilder;
}
}
add UseSubscribe in Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseSubscribe("ClientMessageService", Assembly.GetExecutingAssembly());
app.UseHttpsRedirection();
app.UseMvc();
}
then create Producers controller
[Route("api/[controller]")]
[ApiController]
public class ProducersController : ControllerBase
{
private readonly IBus _bus;
public ProducersController(IBus bus)
{
_bus = bus;
}
[HttpGet]
[Route("Send")]
public JsonResult Send()
{
_bus.Publish(new TextMessage { Text = "Send Message from the Producer" });
return new JsonResult("");
}
}
then create Consumers controller
[Route("api/[controller]")]
[ApiController]
public class ConsumersController : ControllerBase, IConsume<TextMessage>
{
[HttpGet]
[Route("Receive")]
public JsonResult Receive()
{
using (var bus = RabbitHutch.CreateBus("host=localhost"))
{
bus.Subscribe<TextMessage>("test", HandleTextMessage);
}
return new JsonResult("");
}
private static void HandleTextMessage(TextMessage textMessage)
{
var item = textMessage.Text;
}
public void Consume(TextMessage message)
{
// code receive message
}
}

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(...);
}
}

Resources