With AspNetCore.SignalR (1.0.0 preview1-final) and AspNetCore.All (2.0.6), how can I invoke a method on a hub in server code that is not directly in a Controller and is in a class that cannot be made via Dependency Injection?
Most examples assume the server code is in a Controller and should 'ask' for the hub via an injectable parameter in a class that will created by DI.
I want to be able to call the hub's method from server code at any time, in code that is not injected. The old SignalR had a GlobalHost that enabled this approach. Basically, I need the hub to be a global singleton.
Now, everything seems to be dependent on using Dependency Injection, which is introducing a dependency that I don't want!
I've seen this request voiced in a number of places, but haven't found a working solution.
Edit
To be more clear, all I need is to be able to later access the hubs that I've registered in the Configure routine of the Startup class:
app.UseSignalR(routes =>
{
routes.MapHub<PublicHubCore>("/public");
routes.MapHub<AnalyzeHubCore>("/analyze");
routes.MapHub<ImportHubCore>("/import");
routes.MapHub<MainHubCore>("/main");
routes.MapHub<FrontDeskHubCore>("/frontdesk");
routes.MapHub<RollCallHubCore>("/rollcall");
// etc.
// etc.
});
If I register them like this:
services.AddSingleton<IPublicHub, PublicHubCore>();
it doesn't work, since I get back an uninitiated Hub.
No It's not possible. See "official" answer from david fowler https://github.com/aspnet/SignalR/issues/1831#issuecomment-378285819
How to inject your hubContext:
Best solution is to inject your hubcontext like IHubContext<TheHubWhichYouNeedThere> hubcontext
into the constructor.
See for more details:
Call SignalR Core Hub method from Controller
Thanks to those who helped with this. Here's what I've ended up on for now...
In my project, I can call something like this from anywhere:
Startup.GetService<IMyHubHelper>().SendOutAlert(2);
To make this work, I have these extra lines in Startup.cs to give me easy access to the dependency injection service provider (unrelated to SignalR):
public static IServiceProvider ServiceProvider { get; private set; }
public static T GetService<T>() { return ServiceProvider.GetRequiredService<T>(); }
public void Configure(IServiceProvider serviceProvider){
ServiceProvider = serviceProvider;
}
The normal SignalR setup calls for:
public void Configure(IApplicationBuilder app){
// merge with existing Configure routine
app.UseSignalR(routes =>
{
routes.MapHub<MyHub>("/myHub");
});
}
I don't want all my code to have to invoke the raw SignalR methods directly so I make a helper class for each. I register that helper in the DI container:
public void ConfigureServices(IServiceCollection services){
services.AddSingleton<IMyHubHelper, MyHubHelper>();
}
Here's how I made the MyHub set of classes:
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
public class MyHub : Hub { }
public interface IMyHubHelper
{
void SendOutAlert(int alertNumber);
}
public class MyHubHelper : IMyHubHelper
{
public IHubContext<MyHub> HubContext { get; }
public MyHubHelper(IHubContext<MyHub> hubContext)
{
HubContext = hubContext;
}
public void SendOutAlert(int alertNumber)
{
// do anything you want to do here, this is just an example
var msg = Startup.GetService<IAlertGenerator>(alertNumber)
HubContext.Clients.All.SendAsync("serverAlert", alertNumber, msg);
}
}
This is a nice solution. In .NET Core 2.1 the service provider is disposed and you get cannot access disposed object. The fix is to create a scope:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider.CreateScope().ServiceProvider;
Related
In my Minimal API, I use and integrate with Kofax TotalAgility WCF endpoints. I wanted to implement this integration properly, so I added a remote assembly and added the WCF contract in it along with the service interface and implementation:
Service Interface:
public interface IKofaxService
{
public Task<string> CreateJob(long letterId);
public Task ActionHandler(PortalActionRequest request);
}
Service implementation:
public class KofaxService : IKofaxService
{
private readonly ILogger<KofaxService> logger;
private readonly KofaxSetup config;
private readonly KtaJob.IJobService jobService;
private readonly KtaActivity.IActivityService activityService;
public KofaxService(ILogger<KofaxService> inLogger, KofaxSetup inConfig)
{
logger = inLogger;
// Here is the problem: THe constructor's parameter should be IOptions<Kofaxsetup> instead of just KofaxSetup and this below line will become:
// config = inConfig.Value;
config = inConfig;
//WCF Generated Stuff within this remote assembly
jobService = new KtaJob.JobServiceClient(GetBinding(), GetEndpointAddress(config.KtaUrlApiJob));
activityService = new KtaActivity.ActivityServiceClient(GetBinding(), GetEndpointAddress(config.KtaUrlApiActivity));
}
public async Task<string> CreateJob(long letterId)
{
...
}
public async Task ActionHandler(PortalActionRequest request)
{
...
}
}
In order to have a Servces.AddKofaxTotalAgility() like fluent API, I added the extension method like so (in the remote assembly):
Service extension method:
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddKofaxTotalAgility(this IServiceCollection services)
{
services.AddScoped<IKofaxService, KofaxService>();
return services;
}
}
Also in the remote assembly, I have a class representing the setting object from appSetting's section:
Config class:
public class KofaxSetup
{
public string KtaUrlApiActivity { get; set; } = string.Empty;
public string KtaUrlApiJob { get; set; } = string.Empty;
public string SessionId { get; set; } = string.Empty;
public string ProcessId { get; set; } = string.Empty;
}
Back in the Minimal API project, I added a reference to the remote assembly and also have the settings in appSettings.json file:
appSettings.json:
{
...
"KofaxSetup": {
"KtaUrlApiActivity": "https://kofax.somewhere.com/TotalAgility/Services/SDK/ActivityService.svc",
"KtaUrlApiJob": "https://kofax.somewhere.com/TotalAgility/Services/SDK/JobService.svc",
"SessionId": "7DB87F70018D4770BF6114B1C9BA6041",
"ProcessId": "66EC6EED5D024E7AB0013D60F7A04A1A"
},
...
}
Lastly, modifications to Program.cs are as follows:
Minimal API Program.cs
var builder = WebApplication.CreateBuilder(args);
...
// Trigger KofaxSetting object from AppSetting's section
builder.Services.Configure<KofaxSetup>(builder.Configuration.GetSection(nameof(KofaxSetup)));
...
// Add the service to the DI
builder.Services.AddKofaxTotalAgility();
...
All of this just results in this exception at startup:
Exception # var app = builder.Build();
System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: DACRL.Integrations.Kofax.IKofaxService Lifetime: Scoped ImplementationType: DACRL.Integrations.Kofax.KofaxService': Unable to resolve service for type 'DACRL.Integrations.Kofax.Configs.KofaxSetup' while attempting to activate 'DACRL.Integrations.Kofax.KofaxService'.) (Error while validating the service descriptor 'ServiceType: DACRL.Application.Core.Services.ILetterService Lifetime: Transient ImplementationType: DACRL.Api.Services.LetterService': Unable to resolve service for type 'DACRL.Integrations.Kofax.Configs.KofaxSetup' while attempting to activate 'DACRL.Integrations.Kofax.KofaxService'.) (Error while validating the service descriptor 'ServiceType: DACRL.Application.Core.Services.ILetterService Lifetime: Transient ImplementationType: DACRL.Api.Services.LetterService': Unable to resolve service for type 'DACRL.Integrations.Kofax.Configs.KofaxSetup' while attempting to activate 'DACRL.Integrations.Kofax.KofaxService'.)'
1/2:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: DACRL.Integrations.Kofax.IKofaxService Lifetime: Scoped ImplementationType: DACRL.Integrations.Kofax.KofaxService': Unable to resolve service for type 'DACRL.Integrations.Kofax.Configs.KofaxSetup' while attempting to activate 'DACRL.Integrations.Kofax.KofaxService'.
2/2:
InvalidOperationException: Unable to resolve service for type 'DACRL.Integrations.Kofax.Configs.KofaxSetup' while attempting to activate 'DACRL.Integrations.Kofax.KofaxService'.
Note that the ILetterService is working properly, and this is the service that internally attempts to receive the IKofaxService from DI in its parameter. I'm thinking the error has something to do with the object KofaxSetup
Is there a best practice that I'm missing here? Am I supposed to have a parameter-less constructor somewhere? Is the Logger<KofaxService> injection within the service's implementation not valid?
I actually sorted the issue out but didn't want to waste a well-written question.
The problem was fact, the KofaxSetup class. I was receiving it as its type directly in the Service's constructor. I had to use IOptions<KofaxSetup> instead to solve the issue.
I'm working on a solution that interacts with Redis, using the servicestack.net library.
I have a class that inherits from ServiceStack.AppHostBase and asks me for an override of the Configure method. This method has as a parameter a Funq.Container that I see is an implementation of IServiceProvider, IResolver and IContainer, and none of these interfaces have the AddHttpClient method that is provided by the IServiceCollection. Method I need to be able to inject the IHttpClientFactory. Any idea how to solve my problem?
To do it in ASP.NET (not .NET Core), the quick way would be to:
install Microsoft.Extensions.DependencyInjection package and call .AppHttpClient() extension
Build the Service Provider you would normally see in .NET Core
Get the instance of IHttpClientFactory from the Service Provider
Register the instance of IHttpClientFactory with Funq.Container again
using Microsoft.Extensions.DependencyInjection;
public class AppHost : AppHostBase
{
public override void Configure(Container container)
{
...
RegisterHttpClientFactory(container);
}
private container RegisterHttpClientFactory(Container container)
{
var services = new ServiceCollection()
.AddHttpClient();
// You can kind of inspect services returned.
// You can see this extension registers lot of other things too beside
// IHttpClientFactory.
// Also you can see the lifetime of IHttpClientFactory is Singleton.
var serviceProvider = services.BuildServiceProvider();
container.AddSingleton(serviceProvider.GetService<IHttpClientFactory>());
return container;
}
}
If you happen to use Unity Adaptor
Unity has a package to give you an extension as well to build the Service Provider directly into the Unity Container:
using Microsoft.Extensions.DependencyInjection;
using Unity;
using Unity.Microsoft.DependencyInjection;
public static class UnityConfig
{
public static void RegisterTypes(IUnityContainer container)
{
...
container.RegisterServices();
container.RegisterHttpClientFactory();
}
private static IUnityContainer RegisterHttpClientFactory(
this IUnityContainer unityContainer)
{
new ServiceCollection()
.AddHttpClient()
.BuildServiceProvider(unityContainer);
return unityContainer;
}
}
This is the interface definition of IServiceCollection from IServiceCollection.cs:
public interface IServiceCollection : IList<ServiceDescriptor>
{
}
AddHttpClient is just an extension method from Microsoft.Extensions.DependencyInjection that wraps adding a number of additional dependencies to ASP.NET Core IOC.
So you should continue to register it on ASP.NET Core IOC, i.e:
public class Startup : ModularStartup
{
public new void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseServiceStack(new AppHost
{
AppSettings = new NetCoreAppSettings(Configuration)
});
}
}
As any dependencies registered .NET Core Startup are also available to ServiceStack.
During migration of an ASPNetCore 1.1 Project to ASPNetCore 2.0, we stumbled upon a Problem with the Cookie-AuthN and its SessionStore.
ASP.NET Core 1 allowed us to do something like that:
public void ConfigureServices(...) {
Services.AddDistributedSqlServerCache(...);
Services.AddSingleton<DistributedCookieSessionStore>(); /// SQL based store
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory) {
var cookieOptions = app.ApplicationServices.GetRequiredService<IOptions<CookieAuthenticationOptions>>().Value;
cookieOptions.SessionStore = app.ApplicationServices.GetRequiredService<DistributedCookieSessionStore>();
app.UseCookieAuthentication(cookieOptions);
}
Messy, but doing its Job.
Now with ASP.NET Core 2 app.UseAuthentication() does not have a signature allowing to modify the options, and I am not able to use DI, to get a hold of the session store.
After long search I came accross this discussion https://github.com/aspnet/Security/issues/1338 where they mentioned IPostConfigureOptions interface. I put that together and this works for me:
1) Implement interface IPostConfigureOptions<CookieAuthenticationOptions>
public class PostConfigureCookieAuthenticationOptions : IPostConfigureOptions<CookieAuthenticationOptions>
{
private readonly ITicketStore _ticketStore;
public PostConfigureCookieAuthenticationOptions(ITicketStore ticketStore)
{
_ticketStore = ticketStore;
}
public void PostConfigure(string name, CookieAuthenticationOptions options)
{
options.SessionStore = _ticketStore;
}
}
2) Register this implementation to the container in Startup.ConfigureServices method
services.AddSingleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>();
I have an OWIN pipeline using Nancy:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseNancy();
}
}
The UseNancy() is actually a call to my own custom extension method defined in this gist: https://gist.github.com/TheFastCat/0b7635d9e5795b44e72e
This code is executed both as an Azure Website or an Azure Cloud Service. Based on the context it is executing within I want to use a particular favicon, loaded as an embedded resource from a separate assembly. I do this by specifying separate NancyBootstrappers (each loading the proper favicon for its context).
Is there a more elegant solution to determining the runtime application that is executing the OWIN pipeline? Currently I check app.Properties["host.AppName"] ; however while the Website's app name matches it's assembly configuration, the CloudService app is the name of the Owin startup assembly.class. (see gist). It's cloogey.
Is there a more elegant/simple solution for specifying a custom favicon within Nancy for each of my web applications than creating separate bootstrappers and doing runtime application context checks?
I solved this problem with the help of others on the https://jabbr.net/#/rooms/owin and https://jabbr.net/#/rooms/nancyfx chat boards
Yes. You can contextually check the OWIN host properties:
if (app.Properties.ContainsKey("System.Net.HttpListener"))
{
// self hosted application context
}
2.) Yes.
namespace ClassLib
{
public class Startup()
{
public Startup(byte[] favIcon) { ... }
public void Configuration(IAppBuilder app) { ... }
}
}
[assembly: OwinStartup(typeof(WebHost.Startup))]
namespace WebHost
{
public class Startup()
{
public voic Configuration(IAppBuilder app)
{
new ClassLib.Startup(webhostFavIcon).Configuration(app);
}
}
}
namespace SelfHost
{
private class Program()
{
public void Main(string[] args)
{
using(WebApp.Start(app => new ClassLib.Startup(selfHostFavIcon).Configuration(app))
{}
}
}
}
I wrote about this topic in another question.
However, I've since refactored my code to get rid of configuration access, thus allowing the specs to pass. Or so I thought. They run fine from within Visual Studio using TestDriven.Net. However, when I run them during rake using the mspec.exe tool, they still fail with a serialization exception. So I've created a completely self-contained example that does basically nothing except setup fake security credentials on the thread. This test passes just fine in TD.Net, but blows up in mspec.exe. Does anybody have any suggestions?
Update: I've discovered a work-around. After researching the issue, it seems the cause is that the assembly containing my principal object is not in the same folder as the mspec.exe. When mspec creates a new AppDomain to run my specs, that new AppDomain has to load the assembly with the principal object in order to deserialize it. That assembly is not in the same folder as the mspec EXE, so it fails. If I copied my assembly into the same folder as mspec, it works fine.
What I still don't understand is why ReSharper and TD.Net can run the test just fine? Do they not use mspec.exe to actually run the tests?
using System;
using System.Security.Principal;
using System.Threading;
using Machine.Specifications;
namespace MSpecTest
{
[Subject(typeof(MyViewModel))]
public class When_security_credentials_are_faked
{
static MyViewModel SUT;
Establish context = SetupFakeSecurityCredentials;
Because of = () =>
SUT = new MyViewModel();
It should_be_initialized = () =>
SUT.Initialized.ShouldBeTrue();
static void SetupFakeSecurityCredentials()
{
Thread.CurrentPrincipal = CreatePrincipal(CreateIdentity());
}
static MyIdentity CreateIdentity()
{
return new MyIdentity(Environment.UserName, "None", true);
}
static MyPrincipal CreatePrincipal(MyIdentity identity)
{
return new MyPrincipal(identity);
}
}
public class MyViewModel
{
public MyViewModel()
{
Initialized = true;
}
public bool Initialized { get; set; }
}
[Serializable]
public class MyPrincipal : IPrincipal
{
private readonly MyIdentity _identity;
public MyPrincipal(MyIdentity identity)
{
_identity = identity;
}
public bool IsInRole(string role)
{
return true;
}
public IIdentity Identity
{
get { return _identity; }
}
}
[Serializable]
public class MyIdentity : IIdentity
{
private readonly string _name;
private readonly string _authenticationType;
private readonly bool _isAuthenticated;
public MyIdentity(string name, string authenticationType, bool isAuthenticated)
{
_name = name;
_isAuthenticated = isAuthenticated;
_authenticationType = authenticationType;
}
public string Name
{
get { return _name; }
}
public string AuthenticationType
{
get { return _authenticationType; }
}
public bool IsAuthenticated
{
get { return _isAuthenticated; }
}
}
}
Dan,
thank you for providing a reproduction.
First off, the console runner works differently than the TestDriven.NET and ReSharper runners. Basically, the console runner has to perform a lot more setup work in that it creates a new AppDomain (plus configuration) for every assembly that is run. This is required to load the .dll.config file for your spec assembly.
Per spec assembly, two AppDomains are created:
The first AppDomain (Console) is created
implicitly when mspec.exe is
executed,
a second AppDomain is created by mspec.exe for the assembly containing the specs (Spec).
Both AppDomains communicate with each other through .NET Remoting: For example, when a spec is executed in the Spec AppDomain, it notifies the Console AppDomain of that fact. When Console receives the notification it acts accordingly by writing the spec information to the console.
This communiciation between Spec and Console is realized transparently through .NET Remoting. One property of .NET Remoting is that some properties of the calling AppDomain (Spec) are automatically included when sending notifications to the target AppDomain (Console). Thread.CurrentPrincipal is such a property. You can read more about that here: http://sontek.vox.com/library/post/re-iprincipal-iidentity-ihttpmodule-serializable.html
The context you provide will run in the Spec AppDomain. You set Thread.CurrentPrincipal in the Because. After Because ran, a notification will be issued to the Console AppDomain. The notification will include your custom MyPrincipal that the receiving Console AppDomain tries to deserialize. It cannot do that since it doesn't know about your spec assembly (as it is not included in its private bin path).
This is why you had to put your spec assembly in the same folder as mspec.exe.
There are two possible workarounds:
Derive MyPrincipal and MyIdentity from MarshalByRefObject so that they can take part in cross-AppDomain communication through a proxy (instead of being serialized)
Set Thread.CurrentPrincipal transiently in the Because
(Text is required for formatting to work -- please ignore)
Because of = () =>
{
var previousPrincipal = Thread.CurrentPrincipal;
try
{
Thread.CurrentPrincipal = new MyPrincipal(...);
SUT = new MyViewModel();
}
finally
{
Thread.CurrentPrincipal = previousPrincipal;
}
}
ReSharper, for example, handles all the communication work for us. MSpec's ReSharper Runner can hook into the existing infrastructure (that, AFAIK, does not use .NET Remoting).