Ich bin auf diesen alten Thread gestoßen. Das Problem, das ich hatte, war, dass ich weiß, einen statischen HttpClient zu verwenden, aber mein Token braucht alle 59 Minuten zu aktualisieren.
Ich hätte also HttpClientFactory verwenden können, aber da eines meiner Projekte noch in .NET 4.8 war, habe ich eine Klasse erstellt, die von HttpClient geerbt hat, sodass ich in allen Projekten ähnlichen Code habe. Um das Token abrufen zu können, ist ein Geheimnis erforderlich (ich verwende identityserver4).
Ich setze das dann als Singleton in DI (ich verwende hier Ninject):
Bind<MyHttpClient>().ToMethod(c =>
{
var accessKey = ConfigurationManager.AppSettings["AccessKey"];
var client = new MyHttpClient(accessKey)
{
BaseAddress = new Uri(MyUrls.MyApiBaseUrl)
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
return client;
}).InSingletonScope();
Dann die Klasse selbst - benannt nach der API, auf die sie zugreifen soll:
public class MyHttpClient : BaseHttpClient
{
private static readonly HttpClient _authHttpClient = new HttpClient();
private string _secret;
public MyHttpClient(string secret)
{
_secret = secret;
}
/// <summary>
/// Add the token to each and every request, cached for 1 minute less than the token's lifetime
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var cacheSeconds = 3600 - 60; // Default of 59 minutes
var token = CacheHelper<string>.Get("MyToken", cacheSeconds * 60, () =>
{
var authorityUrl = MyUrls.AuthServerUrl;
// discover endpoints from metadata
DiscoveryDocumentResponse disco;
disco = _authHttpClient.GetDiscoveryDocumentAsync(authorityUrl).Result;
if (disco.IsError)
{
throw new Exception("Error getting discovery document: " + disco.Error);
}
// request token
var tokenResponse = _authHttpClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "myapp",
ClientSecret = _secret,
Scope = "myapi"
}).Result;
if (tokenResponse.IsError)
{
throw new Exception("Error getting token: " + tokenResponse.Error);
}
if (tokenResponse.ExpiresIn < cacheSeconds + 60)
{
throw new Exception($"Token expires in {tokenResponse.ExpiresIn}s, which is less than {cacheSeconds + 60}");
}
if (tokenResponse.ExpiresIn > cacheSeconds + 60)
{
Log.Warn().Message($"Token expiry in {tokenResponse.ExpiresIn}s, which is greater than {cacheSeconds}").Write();
}
return tokenResponse.AccessToken;
});
// THIS IS THE BIT - Assign this inside a SendAsync override and you are done!
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return base.SendAsync(request, cancellationToken);
}
}
Schließlich, nur der Vollständigkeit halber, sieht meine CacheHelper-Klasse wie folgt aus:
public static class CacheHelper<T>
{
private static readonly object _locker = new object();
public static T Get(string cacheName, int cacheTimeoutSeconds, Func<T> func)
{
var obj = MemoryCache.Default.Get(cacheName, null);
if (obj != null) return (T)obj;
lock (_locker)
{
obj = MemoryCache.Default.Get(cacheName, null);
if (obj == null)
{
obj = func();
var cip = new CacheItemPolicy
{
AbsoluteExpiration = new DateTimeOffset(DateTime.UtcNow.AddSeconds(cacheTimeoutSeconds))
};
MemoryCache.Default.Set(cacheName, obj, cip);
}
}
return (T)obj;
}
}