Periodically reload credentials on SearchServiceClient and child ISearchIndexClient singletons - c#

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.

Related

Why do I have a .NET Core API performance issue using IHttpClientFactory when ramping up users?

I have a .NET Core WebAPI my React UI is calling to Authenticate Users. It calls a 3rd Party API for this. Everything works fine but we have started to do Performance testing on it and it is not scaling well when we ramp up the Users attempting to log on concurrently. (taking 30 secs)
The 3rd Party API we call are saying they are responding in milliseconds.
My API is hosted in Kubertnetes container on AWS. I have added AWS X-ray to the code to try and get further information though I am not really sure on how to interpret the results.
The code is quite straightforward - This is a snippet from MyAuthenticationProvider class (the constructor takes a metric collector (for AWS X-Ray and and securityProvider http client for making the call)
metricCollector.StartCollection("Stage 1");
HttpResponseMessage response = await securityProvider.SendAsync(requestMessage);
metricCollector.EndCollection();
The X-Ray image for the above code is:
Is X-Ray showing that it is indeed waiting 30+ Seconds for this API to return a response and I should reach out to that company for further investigation on there side even though they are telling me all traffic is getting responded too in milli-seconds.
Or could it be how I have defined the http client used in MyAuthProvider class in Startup.cs that is not scaling correctly when the concurrent users ramps up?
This is the code for that in Startup.cs
services.AddTransient<IMyAuthenticationProvider>(ctx =>
{
IHttpClientFactory clientFactory = ctx.GetRequiredService<IHttpClientFactory>();
return new MyAuthenticationProvider(
clientFactory.CreateClient("3RDPARTYAUTHCLIENT"),
ctx.GetService<IMetricCollector>());
});
Another thing I was thing to improve performance is introducing Redis to cache some of these responses as they are getting calling multiple times for different operations but the result will be the same
While you're only creating 1 named HttpClient, you've set the service lifetime of IMyAuthenticationProvider to transient.
This means that essentially you're losing out on most of the benefits of a single HttpClient by creating a new instance of IMyAuthenticationProvider every time something requests for one (which in the best-case scenario, will be synonymous with every client request but not to be mistaken with scoped services).
This can massively slow down your application & may be the cause of the badly performing scaling of the application.
You're trying to clearly use a single HttpClient, which would typically be static or wrapped as a non-static instance inside a singleton class. and is still a good solution for short-lived console applications etc. however in this case I'd allow IHttpClientFactory to resolve the client.
The primary goal of IHttpClientFactory in ASP.NET Core is to ensure that HttpClient instances are created appropriately (taking into account things like DNS changes which a single HttpClient instance won't take care of) while at the same time eliminating socket exhaustion.
Injected HttpClient instances by IHttpClientFactory have a transient lifetime (documentation is conflicting & mentions transient 2x & scoped 1x for some absurd reason) and so I'd set the lifetime of IMyAuthenticationProvider to scoped to allow it to be reused as much as possible.
Having a longer running singleton IHttpClientFactory, in this case, with an injected shorter-lived scoped HttpClient should not be done.
MSFT:
Do not resolve a scoped service from a singleton and be careful not to do so indirectly
While the injected HttpClient object is transient, using the IHttpClientFactory enables pooling of HttpMessageHandler objects that can and will be reused by multiple HttpClient instances.
Try:
services.AddHttpClient<IMyAuthenticationProvider, MyAuthenticationProvider>();
services.AddHttpClient<IMetricCollector, MetricCollector>();
...
services.AddScoped<IMyAuthenticationProvider, MyAuthenticationProvider>();
public class MyAuthenticationProvider : IMyAuthenticationProvider
{
private readonly HttpClient _httpClient;
public MyAuthenticationProvider(HttpClient httpClient)
{
_httpClient = httpClient;
}
...
}

How to reload AWS options at runtime

I am running an ASP.NET Core MVC app in a docker container, with an AWS credentials file. I have another service that is putting new keys into the file when the old ones expire, but these new keys don't seem to propagate through to my MVC app and my site crashes. I have seen that normally the solution to get strongly typed configuration to reload is to use IOptionsSnapshot, like:
services.AddDefaultAWSOptions(Configuration.GetAWSOptions())
.AddScoped(config => config.GetService<IOptionsSnapshot<AWSOptions>>().Value)
.AddAWSService<IAmazonS3>();
but this gives an exception:
System.InvalidOperationException: Cannot resolve scoped service 'Amazon.Extensions.NETCore.Setup.AWSOptions' from root provider.
Does anyone have a solution to getting ASP to reload the AWS credentials file? I'd like to continue using the AWS dependency injection extension if possible.
By default, AddAWSService registers the client factory in singleton scope, which means it's one and done for the life of the application. However, AddAWSService has a lifetime param you can utilize to customize this. Essentially, you need a shorter lifetime on the client, so that it will be recreated with the new settings. You can choose either "scoped" (request-scoped) or "transient" (new instance every time it's injected).
Obviously with "scoped", you'll get a connection with the updated settings every request. However, if you do any further operations on the same request after the settings have been changed, it will remain the old connection with the old settings (i.e. you'll still have the same issue, at least for the life of the request).
Using "transient" scope, you'll have have a client with the most updated settings, but you'll end up basically with a client for every use, which may not be ideal.

How do I use the ASP.NET v5 Configuration in a Console App (Packaged)?

The new configuration system in ASP.NET v6 is lovely for web apps but I'd like to do something similar for a Console app. Unfortunately, I couldn't quite figure out how to do so. The first part seems straightforward:
var config = new Configuration()
.AddJsonFile("config.json");
var services = new ServiceCollection();
services.Configure<AppSettings>(config.GetSubKey(nameof(AppSettings)));
Unfortunately, I haven't been able to figure out the balance of the needed code. I think it likely that an OptionsManager needs to be invoked so the hydrated AppSettings object can be returned via the OptionManager.Options property, but how one wires things up, I don't have clue.
Needless to say, any help in this regard would be much appreciated...

Should RedisMqServer/RedisMqHost be configured once per application?

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.

c# client calling java axis2 web service, object "resets"

I am very new to web service stuff so please be kind.
I have written a simple POJO class, and deployed it on an axis2 server:
public class Database {
private Project project;
public void login(){
project = new Project();
project.setDescription("Hello there");
project.setName("To me");
}
public Project getProject(){
return project;
}
}
I call the service from a c# client:
localhost.Database db = new WindowsFormsApplication1.localhost.Database();
db.login();
localhost.getProjectResponse pr = new WindowsFormsApplication1.localhost.getProjectResponse();
pr = db.getProject();
When I debug the response is null.
At the java end, when I call getProject, the project object is null.
What's happening?
How do I preserve the state of project between service calls?
For most toolkits, web services are stateless by default. I think axis is no different.
If you want to maintain state between calls then you will need to enable sessions. An example on how to maintain sessions in axis can be found at:
http://kickjava.com/src/test/session/TestSimpleSession.java.htm
On the .NET side you will need to assign a CookieContainer to your request to store the session identifier. See HOW TO: Use CookieContainer to Maintain a State in Web Services for more information.
I think your code would look something like this:
localhost.Database db = new WindowsFormsApplication1.localhost.Database();
// Assign the CookieContainer to the proxy class.
db.CookieContainer = new System.Net.CookieContainer();
db.login();
localhost.getProjectResponse pr = new WindowsFormsApplication1.localhost.getProjectResponse();
pr.CookieContainer = db.CookieContainer;
pr = db.getProject();
I think that should let you do what you want -- but I wouldn't recommend it.
Designing service interfaces is a bit different than designing object oriented interfaces. Service interfaces typically eschew the use of state and instead require the consumer to provide all of the relevant information in the request.
From Service-Oriented Architecture:
Services should be independent,
self-contained requests, which do not
require information or state from one
request to another when implemented.
I would definitely recommend reading that article and perhaps revisiting your design.
I'm not sure why #shivaspk left a comment instead of writing an answer, it is quite correct: web service calls (not just axis calls) are meant to be stateless, so although the project object gets created by
db.login();
when you call
db.getProject();
It is being called on a different instance of your Database class that was created by Axis to service the second call.
There is no really good answer to your question, except for you to rethink what you are trying to do. If you need some kind of authentication (via login), then that authentication needs to be part of every web service call.

Categories