Host a SignalR Hub in a .NET Core 3.1 Console - asp.net-core

I migrating an old .NET library hosted by an exe from .NET Framework 4.6 to .NET Core 3.1. A part of the assembly is based on a stand alone SignalR hub implemented like this.
//-----------------------------------
// Startup SignalR Server
//-----------------------------------
m_oSignalRServer = WebApp.Start( C_AppSettings.ServerURL );
I understood that the host must be initiated with IHostBuilder and Host.CreateDefaultBuilder but I really don understand how to configure it. And especially, how to I specify the bindings and hub names.
Sample code or books are welcome.
learn.microsoft.com
public static IHostBuilder CreateHostBuilder( string [ ] args ) =>
Host.CreateDefaultBuilder( args ).ConfigureServices( ( hostContext, services ) =>
{
services.AddSignalR( ( hubOptions =>
{
hubOptions.EnableDetailedErrors = true;
hubOptions.KeepAliveInterval = TimeSpan.FromMinutes( 1 );
} ));
} );
Thanks in advance!

this is what I do and it works fine for me with a .net core 3.1 console app.
open up your .csproj and add the following to it:
<ItemGroup>
<FrameworkReference Include="Microsoft.aspNetCore.App" />
</ItemGroup>
then add the following package via nuget package manager:
Microsoft.AspNetCore.SignalR
this is my basic program.cs:
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace GameServer
{
internal class Program
{
private static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
private static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args).UseStartup<Startup>();
}
}
and a basic Startup.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace GameServer
{
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) { Configuration = configuration; }
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
}
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ChatHub>("/chat");
});
}
}
}
a lot simpler imo hope it helps :)

I trying to explain my problem in more details and hope someone know how to solve the issue.
Microsoft recommend to use Host.CreateDefaultBuilder instead of WebHost.CreateDefaultBuilder as I have understood. Host.CreateDefaultBuilder reads configuration from json files. The problem is that I don't understand how to connect the call services.AddSignalR() to my Hub.
In my old .NET 4.5 version it was easier from my point of view.
The server was started with this code
IDisposable oSignalRServer = WebApp.Start( "http://localhost:3211" );
And the hub was referenced with
ConnectionManager.GetHubContext<C_IOHub>()
Hub definition
[HubName( "IOHub" )]
public class C_IOHub : Hub
But with .NET Core I'm lost how to build this as a standalone server. All examples I have found describe how to attach the Hub to an existing MVC project.
I have a Startup.cs with the following code:
public static void Main( string [ ] args )
{
CreateHostBuilder( args ).Build().Run();
}
public static IHostBuilder CreateHostBuilder( string [ ] args ) =>
Host.CreateDefaultBuilder( args )
.ConfigureServices( ( hostContext, services ) =>
{
services.AddSignalR();
} );
I need the following information
How do I create a standalone Hub in .NET Core?
How do I obtain a reference to the Hub context?

Follows a full example to create and use a hub in the .NET Core 3.1 app:
First read the configuration from appsettings.json
"Azure": {
"SignalR": {
"ClientTimeoutInterval": 3600,
"HandshakeTimeout": 30,
"KeepAliveInterval": 15,
"EnableDetailedErrors": true,
"MaximumReceiveMessageSize": 32000,
"StreamBufferCapacity": 10,
"SupportedProtocols": [ "WebSockets", "ServerSentEvents" ],
"ServerConnectionCount": 1
}
}
Then read the configuration on the startup
private AzureConfiguration azureConfiguration;
Add in to configuration method
services.Configure<AzureConfiguration>(this.Configuration.GetSection(Azure)).AddOptionsSnapshot<Azure>();
Note: you can resolve the configuration like this this.azureConfiguration = provider.GetRequiredService<AzureConfiguration>();.
On the startup, configure method:
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<YourHub>(this.azureConfiguration.SignalR.Endpoint)
});
On the configure services method:
services.AddSignalR(hubOptions =>
{
hubOptions.ClientTimeoutInterval = TimeSpan.FromSeconds(this.azureConfiguration.SignalR.ClientTimeoutInterval);
hubOptions.HandshakeTimeout = TimeSpan.FromSeconds(this.azureConfiguration.SignalR.HandshakeTimeout);
hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(this.azureConfiguration.SignalR.KeepAliveInterval);
hubOptions.EnableDetailedErrors = this.azureConfiguration.SignalR.EnableDetailedErrors;
hubOptions.MaximumReceiveMessageSize = this.azureConfiguration.SignalR.MaximumReceiveMessageSize;
hubOptions.StreamBufferCapacity = this.azureConfiguration.SignalR.StreamBufferCapacity;
});
So your configuration on the startup is done, now just go create your hub.
After the hub is created, you can inject it using DI in to the controllers, workers, etc... like:
private IHubContext<YourHub, IYourHub> YourHub
{
get
{
return this.serviceProvider.GetRequiredService<IHubContext<YourHub, IYourHub>>();
}
}
PS: You should configure your CORS before adding the hub methods.
services.AddCors(options =>
{
options.AddPolicy(CorsPolicy, builder => builder.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.SetIsOriginAllowed((host) => true));
});

Related

ASP.NET core start dependency injection at when app is started

I am injecting one of my services as last item in the ConfigureServices method:
services.AddSingleton<IBot>(_ => new De.Impl.Bot(Configuration));
I am running the app in docker container so whenever container is restarted I need to invoke my controller so I can get my service running. How can I get it running in the beginning of the configuration part?
As for .NET Core 3.1 and .NET 5.0
First, you can write your business code as an extension:
using Microsoft.Extensions.Hosting;
public static IHost DoSomething(this IHost host)
{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var yourService = services.GetService<YourService>();
yourService.DoSomething();
}
return host;
}
So if you want to invoke that method every time your application starts, simply call:
// .NET 5.0 Style
public static void Main(string[] args)
{
CreateHostBuilder(args)
.Build()
.DoSomething()
.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
}
As for .NET 6.0
It's gonna be easier. Simply access var yourService = app.Services.GetRequiredService<YourService> in the Program.cs.
// .NET 6.0 style, in Program.cs, before app.Run();
var yourService = app.Services.GetRequiredService<YourService>();
yourService.DoSomething();
app.Run();

Unable to resolve service for type 'Domain.IBlobModelCache' while attempting to activate 'Domain.EntityMaterializerSource'

Although the error message seems obvious I believe it is different from other questions. As you can see in my Startup I have registered IBlobModelCache service.
public class Startup
{
...
public virtual void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<DataContext>(options =>
{
options
.ReplaceService<IEntityMaterializerSource, Domain.EntityMaterializerSource>();
.UseSqlServer(
Configuration.GetConnectionString("Default"),
opt => opt.MigrationsAssembly("API")
);
});
services
.AddAuthentication(ConfigureAthentication)
.AddJwtBearer(ConfigureJwtBearer);
services
.AddLogging(ConfigureLogging)
.AddTransient<IBlobPersisterFactory, BlobPersisterFactory>()
.AddTransient<IBlobDbContextSavingHandler, BlobDbContextSavingHandler>()
.AddTransient<IBlobDbContextModelCreator, BlobDbContextModelCreator>()
.AddSingleton<IBlobModelCache, BlobModelCache>() // It is here
.AddOptions<DiskBlobStorageSettings>().Configure<IConfiguration>((settings, config) => config.Bind("BlobStorage", settings));
}
...
}
And here the constructor of my services so you can see that there is not any loop in them.
class EntityMaterializerSource : Base.EntityMaterializerSource
{
public EntityMaterializerSource(
[NotNull] EntityMaterializerSourceDependencies dependencies,
IBlobModelCache blobModelCache
)
: base(dependencies)
{
BlobModelCache = blobModelCache;
OnMaterializedMethod = typeof(EntityMaterializerSource)
.GetMethod(nameof(OnMaterialized), BindingFlags.Instance | BindingFlags.NonPublic);
}
}
class BlobModelCache : IBlobModelCache
{
// Has no constructor
}
The BlobModelCache is resolved in other services successfully and the error only happens when EF needs to materialize entities.
All I need is to be notified when an entity is materialized from DB and I could not find any solution but extending EntityMaterializerSource. I am using EF Core 3.1.8 and ASP.Net Core 3.1.

Hosting Web API in .Net Core Worker Service - cannot reference IWebHostEnvironment

I'm creating a .NET Core Worker Service, and want to expose ASP.Net Core Web APIs for the service. I'm using .NET Core 3.0.
Initially, my plan was to replace IHostBuilder with IWebHostBuilder and add a Startup class just like a regular Asp.Net Core web app (this is probably an oversimplification, though).
My plan was to simply try to replace
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
with
public static IWebHostBuilder CreateHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<Worker>();
}).UseStartup<Startup>();
}
which may not work at all, but at least it's a starting point...
What's blocking me from trying this approach is that I cannot implement my Startup class because IWebHostEnvironment is unavailable.
Here's my
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<UserSecretsId>dotnet-WorkerServices-0E977A2C-F0C8-49E7-B00A-5EB01B99FBEB</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.0.0" />
</ItemGroup>
As far as I know, IWebHostEnvironment should be in the Microsoft.Extensions.Hosting.Abstractions nuget package, but referencing it does not seem to work.
Does anyone have any ideas?
-Thanks!
It is much easier to add a service to an API project. That's just one extra line.
If you can't or don't want to start over, change your project manually. Forget about IWebHostBuilder, it is now obsolete or deprecated or something.
I could post some fragments here but much easier: create a temporary API project, copy over the Program and Startup classes (but keep your namespaces) and then, inside Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<Worker>(); // the worker will run
services.AddControllers();
}
I researched a lot and I did this issue using some new methods which were not well-formed and lack performance, and I tried to find a correct way of doing this problem, finally, I solved it and I've shared my experiences in this post. You can use the following steps which are working 100%, and also, you can clone the Worker-Service Web-API template from my GitHub profile.
Actually, you needn't use IWebHostBuilder. The following steps are enough to self-host a WebAPI in a .net core worker service and host worker service in windows services:
Create a simple console application.
Install packages "Microsoft.AspNetCore.App" and "Microsoft.Extensions.Hosting.WindowsServices" using NuGet in your console application.
Create an empty file named Worker.cs and put the following code inside it:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace WorkerServiceWebAppTemplate
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
}
}
The above class is your worker service main file which is inherited from BackgroundService, personally, I inherit IHostedService most of the time.
Create another file named Startup.cs and put the following lines of codes:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
namespace WorkerServiceWebAppTemplate
{
public class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseHsts();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
}
}
The Startup file is your web server file and it starts a host and has a simple GET API (/) returns a simple message, you can extend it.
Finally, you've to start your worker service and your host at the same time. change your Program.cs like the following codes:
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace WorkerServiceWebAppTemplate
{
class Program
{
static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
private static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostBuilderContext, services) =>
{
services.AddHostedService<Worker>();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
}
The following lines in your Program.cs file do the magic:
.UseWindowsService()
.ConfigureServices((hostBuilderContext, services) =>
{
services.AddHostedService<Worker>();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});

Error when calling UseSwagger in Azure Web API

I have VS2015 and .Net Core Web API project created.
I'm following example in http://www.technicalblogs.sentientmindz.com/2017/04/09/enabling-swagger-support-to-the-web-api/
I have installed Swashbuckle.AspNetCore and next trying to code, but getting errors when using UseSwagger. Please advise me.
/* Startup.cs */
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Owin;
using Owin;
using Swashbuckle.AspNetCore.Swagger;
using Microsoft.Extensions.DependencyInjection;
[assembly: OwinStartup(typeof(TestApi.Startup))]
namespace TestApi
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
/*use swagger added by me*/
app.UseSwagger(); /*ERROR:iAppBuilder does not contain definition for UseSwagger…*/
app.UseSwaggerUI(c =>. /*ERROR :iAppBuilder does not contain definition for UseSwaggerUI…*/
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Accounts API V1");
});
}
//Add framework services by me
public void ConfigureServices(IServiceCollection services) {
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "AcccountsAPI", Version = "v1" });
});
}
}
}
I'm assuming from your code that you're using .Net Core v1.1, this is how I've done it:
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
config.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "WebAPI");
c.IncludeXmlComments(GetXmlCommentsPath());
c.ResolveConflictingActions(x => x.First());
}).EnableSwaggerUi();
}
protected static string GetXmlCommentsPath()
{
return System.String.Format($#"{0}\bin\MyApi.XML",
System.AppDomain.CurrentDomain.BaseDirectory);
}

EF7 Beta 4 : AddEntityFramework dont accept argument (Configuration)

I am going through this example:
http://stephenwalther.com/archive/2015/01/17/asp-net-5-and-angularjs-part-4-using-entity-framework-7
and i am struggling with this code part:
using Microsoft.AspNet.Builder;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Data.Entity;
using creaservo.com.Models;
using Microsoft.Framework.ConfigurationModel;
using Microsoft.AspNet.Hosting;
namespace creaservo.com
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
// Setup configuration sources.
Configuration = new Configuration()
.AddJsonFile("config.json")
.AddEnvironmentVariables();
}
public IConfiguration Configuration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Register Entity Framework
services.AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<MoviesAppContext>();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
}
}
}
The Problem is with
// Register Entity Framework
services.AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<MoviesAppContext>();
where i get a build error:
Error CS1501 No overload for method 'AddEntityFramework' takes 1 arguments
I saw in a lot of other examples the same use of a argument for Configuration.
No idea, what's wrong....
It looks like the tutorial you're following is using an older version of the EF7 framework. EntityFramework 7 beta 4 no longer accepts any parameters to AddEntityFramework. It looks like beta 5 is still on this same track, too.
I believe what you're looking for is this:
// Register Entity Framework
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<MoviesAppContext>(options =>
{
options.UseSqlServer(Configuration.Get("Data:DefaultConnection:ConnectionString"));
});
This simplifies the structure that you need in the configuration file, because the MoviesAppContext only needs the connection string, not the EntityFramework and Data elements.