I am trying to use SimpleInjector in a WPF Application (.NET Framework). We use it in exactly the same way in many of our Services but for some reason when I am attempting to implement the same logic in this WPF Application, the call to the HttpClient().GetAsync
is hanging. We think it is because for some reason the Task is not executing.
I am registering the objects from the OnStartUp
element of App.xaml.cs as below. Inside the SetupService
Constructor we call a SetupService URL (set in the SetupConfiguration Section of the App.Config) to get the SetupResponse
to use in the app.
It is ultimately hanging in the ServiceClient.GetAsync
method, I have tried to show the flow below:
All classes appear to have been injected correctly, and the ServiceClient
is populated in exactly the same way as the same point in one of our working services. We're at a loss as to what is happening, and how to fix this.
Finally, SetupService
is being injected in other Classes - so I would rather get it working like this, rather than remove the call from the SimpleInjector
mechanism.
Any help is very much appreciated.
public partial class App : Application
{
private static readonly Container _container = new Container();
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
RegisterDependencies();
_container.Verify();
}
private void RegisterDependencies()
{
var serviceConfigSection = ServiceConfigurationSection.Get();
_container.RegisterSingle<ILoggingProvider, LoggingProvider>();
_container.RegisterSingle<IServiceClient>(() => new ServiceClient(_container.GetInstance<ILoggingProvider>()));
_container.RegisterSingle<IConfigurationSection>(() => SetupConfigurationSection.Get());
_container.RegisterSingle<ISetupService, SetupService>();
}
}
public class SetupService: ISetupService
{
private static readonly Dictionary<string, string> AcceptType = new Dictionary<string, string>
{
{"Accept", "application/xml"}
};
private const string AuthenticationType = "Basic";
private readonly IServiceClient _serviceClient;
private readonly ILoggingProvider _logger;
private readonly IConfigurationSection _configuration;
public SetupService(IConfigurationSection configuration, IServiceClient serviceClient, ILoggingProvider logger)
{
_serviceClient = serviceClient;
_logger = logger;
_configuration = kmsConfiguration;
RefreshSetup();
}
public void RefreshSetup()
{
try
{
var token = BuildIdentityToken();
var authHeaderClear = string.Format("IDENTITY_TOKEN:{0}", token);
var authenticationHeaderValue =
new AuthenticationHeaderValue(AuthenticationType, Convert.ToBase64String(Encoding.ASCII.GetBytes(authHeaderClear)));
_serviceClient.Url = _configuration.Url;
var httpResponse = _serviceClient.GetAsync(string.Empty, authenticationHeaderValue, AcceptType).Result;
var responseString = httpResponse.Content.ReadAsStringAsync().Result;
_response = responseString.FromXML<SetupResponse>();
}
catch (Exception e)
{
throw
}
}
public class ServiceClient : IServiceClient
{
private const string ContentType = "application/json";
private string _userAgent;
private ILoggingProvider _logger;
public string Url { get; set; }
public string ProxyAddress { get; set; }
public int TimeoutForRequestAndResponseMs { get; set; }
public int HttpCode { get; private set; }
public ServiceClient(ILoggingProvider logger = null)
{
_logger = logger;
}
public async Task<HttpResponseMessage> GetAsync(string endpoint, AuthenticationHeaderValue authenticationHeaderValue = null, IDictionary<string, string> additionalData = null, IDictionary<string, string> additionalParams = null)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(Url);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(ContentType));
if (authenticationHeaderValue != null)
client.DefaultRequestHeaders.Authorization = authenticationHeaderValue;
ProcessHeader(client.DefaultRequestHeaders, additionalData);
var paramsQueryString = ProcessParams(additionalParams);
if (!string.IsNullOrEmpty(paramsQueryString))
endpoint = $"{endpoint}?{paramsQueryString}";
return await client.GetAsync(endpoint); **// HANGS ON THIS LINE!**
}
}
}
}
CodePudding user response:
If you block on asynchronous code from a UI thread, then you can expect deadlocks. I explain this fully on my blog. In this case, the cause of the deadlock is Result
. There's a couple of solutions.
The one I recommend is to rethink your user experience. Your UI shouldn't be blocking on an HTTP call to complete before it shows anything; instead, immediately (and synchronously) display a UI (i.e., some "loading..." screen), and then update that UI when the HTTP call completes.
The other is to block during startup. There's a few patterns for this. None of them work in all situations, but one that usually works is to wrap the asynchronous work in Task.Run
and then block on that, e.g., var httpResponse = Task.Run(() => _serviceClient.GetAsync(string.Empty, authenticationHeaderValue, AcceptType)).GetAwaiter().GetResult();
and similar for other blocking calls.
Blocking before showing a UI is generally considered a bad UX. App stores generally disallow it. So that's why I recommend changing the UX. You may find an approach like this helpful.
CodePudding user response:
Thanks for your Responses, I just wanted to sync the solution I've gone for. It was risky for me to change the code in SetupService to remove the .Result, even though this was probably the correct solution, as I did not want to affect the other working Services using the SetupService library already there.
I ended up moving the regsitrations off the UI Thread by embedding the SimpleInjector code in a Code library, Creating a Program.cs and Main() and setting that as my Entry point.
static class Program
{
public static readonly Container _container = new Container();
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static void Main(){
var app = new MyApp.App();
Register();
app.Run(_container.GetInstance<MainWindow>());
}
static void Register()
{
_container.Register<MainWindow>();
MySimpleInjector.Register(_container);
_container.Verify();
}
}
and then, in a Separate .dll project, MyApp.Common
public class ANYPayCryptographerSimpleInjector
{
private readonly Container _container;
public static void Register(Container container)
{
var injector = new MySimpleInjector(container);
}
private void RegisterDependencies()
{
var serviceConfigSection = ServiceConfigurationSection.Get();
_container.RegisterSingle<ILoggingProvider, LoggingProvider>();
_container.RegisterSingle<IServiceClient>(() => new ServiceClient(_container.GetInstance<ILoggingProvider>()));
_container.RegisterSingle<IConfigurationSection>(() => SetupConfigurationSection.Get());
_container.RegisterSingle<ISetupService, SetupService>();
}
}
I appreciate that this may not be the ideal solution - but it suits my purposes.
Again, thanks for your help and comments! Andrew.