Should RedisMqServer/RedisMqHost be configured once per application? - c#

I have a web app and a background service that processes messages from Redis. However, I'm unsure as to whether or not the web application's RedisMqServer should be configured as a singleton (I'm using Ninject as my IoC container). Each request that comes is will need to send messages to the background service (one-way), but I'm not sure it it should be instantiated per-request or per-application.
I was thinking that the container would be configured like this:
var clientManager = new PooledRedisClientManager();
var mqHost = new RedisMqHost(clientManager);
Bind<IMessageProducer>()
.ToMethod(_ => mqHost.MessageFactory.CreateMessageProducer())
.InRequestScope();
Or maybe the RedisMqHost/RedisMqServer isn't necessary when the messages are one-way? Therefore, reducing the configuration to:
var clientManager = new PooledRedisClientManager();
Bind<IMessageProducer>()
.ToMethod(_ => new RedisMessageProducer(clientManager))
.InRequestScope();

You don't actually need to register the IMessageService if your services don't need access to the host directly. But if you do end up using it, then Yes it should be registered as a singleton.
The only thing that needs to be registered is IMessageFactory. In this case RequestScope is the same as Ninject's default TransientScope since if it's being used, it'll only ever be resolved once per request, in your Service class.
The IMessageFactory is used in the base Service to lazily load a IMessageProducer so you can publish a message in your services with:
base.MessageProducer.Publish(new RequestDto());
Note: You're using RedisMqHost in code which process all messages on a single background thread. Changing to use RedisMqServer will create a background thread for each message type, allowing processing of different messages to happen in parallel.

Related

EF and Multiple Instances of IHostedService

I have multiple IHostedService (BackgroundService) instances registered in my DI container.
services.AddHostedService<ResourceMatchingMessageListeningService>();
services.AddHostedService<ResourceMatchingMessageListeningService2>();
services.AddHostedService<ResourceMatchingMessageListeningService3>();
services.AddHostedService<ResourceMatchingMessageListeningService4>();
services.AddHostedService<ResourceMatchingMessageListeningService5>();
Each service listens to a different Azure Service Bus queue. When receiving a message it writes to an database through EF Core. If I run a single service it works fine, as soon as I run 2 or more I get the error:
A second operation started on this context before a previous operation
completed. This is usually caused by different threads using the same
instance of DbContext.
I'm assuming this error comes up because EF Core is not thread safe and the hosted services all run in different threads.
I tired to fix this by setting EF's service lifetime to be transient), but I still get the same error.
services.AddDbContext<MyDataContext>(options =>
options.UseSqlServer(connectionString, sqlServerOptions =>
sqlServerOptions.CommandTimeout(120)),
ServiceLifetime.Transient, ServiceLifetime.Transient);
Any suggestion on a potential solution here?
You'll need to Create a new service scope for each hosted service, then request your DbContext from that scope.
using var scope = services.CreateScope();
var context = scope.ServiceProvider.GetService<MyDataContext>();
You might want to create a service scope for each message you process. Perhaps even a separate scoped service to handle the processing of each message.
Unfortunately the implementation then starts to look like a service locator anti-pattern.

How to do DI for non-request bound code in ASP.NET Core?

We're all being taught to use Dependency-Injection for coding in ASP.NET Core applications, but all of the examples I've seen so far that related to the retrieval of services via DI relate to situations where the method that has the service reference injected is strictly bound to a specific HTTP request (HttpContext) (e.g. MVC controllers, Routing delegates).
Service location is warned against as an anti-pattern, but I'm not sure on how to obtain a proper service (e.g. DbContext) reference via DI in code that is not bound a specific HTTP request, e.g. code that has to respond to messages arriving over a websocket.
Although the websocket itself is set-up initially with a specific HTTP request, messages will get responses over potentially a long lifetime of the websocket (as long as the user web session lasts). The server should not reserve/waste a DbContext/DB connection over this entire lifetime (this would result in exhaustion quickly), but rather obtain a DB connection temporarily when a message arrives and requires a response; discarding the DbContext/connection immediately afterwards - while the original HTTP request that set-up the websocket in the very beginning of the user-session technically is still there.
I haven't been able to find anything else but using:
httpContext.RequestServices.GetService(typeof(<MyNeededDbContext>)
This way I use the initial httpContext (obtained via DI when the websocket was set up), but at multiple times after that whenever a websocket message needs a response I can request a transient service object (a DbContext in this example), that may be recycled or pooled after the message response is complete, but while the original httpContext is very much still alive.
Anyone aware of a better approach?
You can create a new service scope to manage the lifetime of services yourself;
IServiceProvider provider = ...;
using (var scope = provider.CreateScope())
{
var context = scope.ServiceProvider.GetService<MyNeededDbContext>();
...
}

Periodically reload credentials on SearchServiceClient and child ISearchIndexClient singletons

In our web app dependency injection, we configure the ISearchIndexClient instances returned by .NET Azure Search SDK's SearchServiceClient.Indexes.GetClient(...) as a singletons.
We did this because of the answer https://stackoverflow.com/a/43502662 mentions that these classes share a single HTTP client. To avoid port exhaustion we want a single shared HttpClient.
The problem with this, however, is that this means we need to restart the web app to cause the new query key (secret) to be reloaded from KeyVault. Consider a secret rotation flow: we need to restart/redeploy our web app after putting the new secret in KeyVault but before invalidating the old secret.
Is there a factory or another pattern the Azure Search .NET SDK recommends for periodically getting a new SearchServiceClient or ISearchIndexClient? I want a singleton most of the time for performance reasons but would like a new instance every couple of hours (for example).
Our code looks something like this right now. It uses Autofac but I think it gets the point across:
containerBuilder
.Register(c =>
{
var options = c.Resolve<IOptionsSnapshot<AzureSearchConfiguration>>();
return new SearchServiceClient(
options.Value.SearchServiceName,
new SearchCredentials(options.Value.SearchServiceApiKey));
});
containerBuilder
.Register(c =>
{
var serviceClient = c.Resolve<SearchServiceClient>();
var options = c.Resolve<IOptionsSnapshot<AzureSearchConfiguration>>();
return serviceClient.Indexes.GetClient(options.Value.SearchIndexName);
})
.SingleInstance();
I can build a time-based factory that spits out a new SearchServiceClient or ISearchIndexClient from time to time but I was hoping that something already existed.
Implementing your own factory/pool for SearchIndexClient instances is the way to go. Unfortunately this kind of functionality isn't included in the SDK, so you'll have to roll your own.

Why is my Winforms-hosted WCF service single threaded?

I have a WCF service that I'm using to replace an old ASP.NET web service. The service appears to be working fine but it is unable to handle simultaneous requests for some reason. My implementation of the service has the following properties:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class HHService : IHHService
My host declaration looks like this:
baseAddress = new Uri("http://0.0.0.0:8888/HandHeld/");
host = new ServiceHost(typeof(HHService), baseAddress);
ServiceMetadataBehavior behavior;
behavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (behavior == null)
{
behavior = new ServiceMetadataBehavior();
behavior.HttpGetEnabled = true;
behavior.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
host.Description.Behaviors.Add(behavior);
}
host.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName,MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
host.AddServiceEndpoint(typeof(IHHService), new BasicHttpBinding(), "HHService.asmx");
HHService.LogMessage += new EventHandler<HHService.LogMessageEventArgs>(HHService_LogMessage);
host.Open();
The service runs and returns correct results, but if two clients try to make a call at the same time one client will block until the other is finished rather than the calls executing together. I'm not using any configuration files. I'm trying to do everything programmatically. Do i have something setup incorrectly that's causing this behavior? I've run other services using the NetTCPBinding without this problem.
EDIT:
In response to John Saunders:
I'm not familiar with any ASP.NET compatibility mode. I'm not using any session state the service is stateless it just processes requests. Aside from the implementation of the actual methods everything else I've done is in the code listed here.
Possible Solution:
I was calling the host.Open() function from the form_load event of the main form. I moved the call to a separate thread. All this thread did was call host.Open() but now the service appears to be behaving as I would expect.
If your instance context mode is PerCall, then your server is always single-threaded, since by definition, every call gets a new server instance.
This works okay in a IIS environment, where IIS can spin up several server instances to handle n concurrent callers, one each as a single-threaded server for each incoming request.
You mention in one of your comments your hosting your WCF inside a forms app - this might be a design decision you need to reconsider - this is not really optimal, since the Winforms app cannot easily handle multiple callers and spin up several instances of the service code.
Marc
Is there a lock somewhere in your service function?
Are you using ASP.NET compatibility mode? Session state?
My next question would be: what makes you think it's single-threaded? How did you determine that, and what test do you use to prove that you have not solved the problem? Could be a false positive.
This is answered in another question:
[ServiceBehavior(UseSynchronizationContext = false)]
WCF in Winforms app - is it always single-threaded?

WCF: Using Duplex for notifications across multiple instances of the same WCF service

What is the best possible way to share a single instance of a WCF Service (the SoapClient class) across multiple instances of an application (WPF)?
I need to do this because I need to enable duplex communications with callbacks, so i need to "register the application" to the the service so that other users using the application will get notified whenever a new user logs in.
Btw the below is striked out because I have confirmed that for the notifications to work, the registrants need to register to the same wcf service instance...thus now I need a way to share this instance
I am currently developing an application and I need some way to inform the users that are currently using the application whenever someone logs in the application.
I have tried using the WCF Duplex thing, but and I can't get it to work...and I think the reason behind it is because notifications and subscriptions need to occur to the same instance of the WCF Service.
But since this application will be deployed on multiple users' pcs, I cannot share only one instance of this wcf service eh? (or can I ?)
Is there a way to share a common instance of a wcf service (the SoapClient) for all the users? Or am I going about this the wrong way?
Currently I'm accessing the WCF Service through a class library via a public property that sends a new isntance of the wcf service every time it is accessed, and I think that that is the reason on why the notifications are not working on multiple instances of the application.
The following are the methods (in the class library) that the client application (a wpf app) uses to gain access to the service methods:
public static MusicRepo_DBAccess_ServiceClient GetService(object instanceContext)
{
return new MusicRepo_DBAccess_ServiceClient(new InstanceContext(instanceContext), dualBinding, endpointAddress);
}
public static MusicRepo_DBAccess_ServiceClient GetService()
{
return new MusicRepo_DBAccess_ServiceClient(new InstanceContext(new WSGateway()), dualBinding, endpointAddress);
}
In the main application window, I am then getting a new instance from the above overloaded method passing in this as the instanceContext parameter and the Open it to wait for the notifications but I am never notified when another user logs in from another instance of the application.
This is how I am notifying the registrars (excerpt) in the service login method:
if (!_callbackList.Contains(newUser))
{
_callbackList.Add(newUser);
}
_callbackList.ForEach(c => c.NotifyNewUserOnline(loggedInUser));
The solution was simple. All I needed was to change InstanceContextMode to Single:
[ServiceBehavior(
InstanceContextMode = InstanceContextMode.Single)]

Categories