Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a DelegatingHandler in HttpClient class from windows forms - Inner handler has not been set

First off, the error message I am receiving is as follows: The inner handler has not been set

I'm writing a custom message handler to handle authentication cookie timeouts to my API. For example, if my code base makes a call to the API and in turn receives a 401 then it should retry the login url to get an updated cookie. I planned to accomplish this as follows:

public class CkApiMessageHandler : DelegatingHandler
{
    private readonly string _email;
    private readonly string _password;
    private const string _loginPath = "Account/Login";

    public CkApiMessageHandler(string email, string password)
    {
        _email = email;
        _password = password;

        //InnerHandler = new HttpControllerDispatcher(null);
    }

    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {

        var response = await base.SendAsync(request, cancellationToken);

        if(response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
        {
            Logging.Logger.LogInformation("Authentication cookie timed out");
            var baseAddress = request.RequestUri.Host;
            var loginPath = baseAddress + _loginPath;

            var originalRequest = request;

            var loginRequest = new HttpRequestMessage(new HttpMethod("POST"), loginPath);
            var dict = new Dictionary<string, string>();
            dict.Add("Email", _email);
            dict.Add("Password", _password);

            var form = new FormUrlEncodedContent(dict);
            loginRequest.Content = form;
            loginRequest.Headers.Clear();

            foreach(var header in originalRequest.Headers)
            {
                loginRequest.Headers.Add(header.Key, header.Value);
            }

            var loginResponse = await base.SendAsync(loginRequest, cancellationToken);

            if (loginResponse.IsSuccessStatusCode)
            {
                Logging.Logger.LogInformation("Successfully logged back in");
                return await base.SendAsync(originalRequest, cancellationToken);
            }
            else
            {
                Logging.Logger.LogException(new Exception("Failed to log back in"), "Could not log back in");
            }
        }

        return response;
    }


}  

I am converting an old service that used to access the database directly, to a service that accesses a web api and I'm trying to do it without having to change any of the consumers of this class.. Hence the weird handler. Here is an example method from the service class.

 public void UpdateClientInstallDatabaseAndServiceData(string dbAcronym, string clientVersion, string clientEnginePort, Guid installationId, string sqlserver)
    {
        var dict = new Dictionary<string, string>();
        dict.Add("dbAcronym", dbAcronym);
        dict.Add("clientVersion", clientVersion);
        dict.Add("clientEnginePort", clientEnginePort);
        dict.Add("desktopClientId", installationId.ToString());
        dict.Add("sqlServerIp", sqlserver);

        var form = new FormUrlEncodedContent(dict);

        _client.PostAsync(_apiPath + "/UpdateClientInstallDatabaseAndServiceData", form);
    }  

So if the above code fails with a 401, the service will auto log back in and retry the original code without the consumer of the service having to check requests and relog back in. The consumer should not know that it is dealing with a web api.

My problem is that my message handler is expecting an InnerHandler to be set which requires an instance of HttpConfiguration class. When I take a look at the specs here, it appears to be some type of class used to setup a web api service. This is fine, except that this code is not being executed in an api.. It is being executed on a windows forms app. The delegating handler is being used within an HttpClient class like so...

_client = new HttpClient(new CKEnterprise.Common.CkApiMessageHandler(email, password));  

So my question is : How can I get this DelegatingHandler to do it's job outside of the context of a web api project?

Making an update. Looks like I may be able to just use HttpClientHandler class https://blogs.msdn.microsoft.com/henrikn/2012/08/07/httpclient-httpclienthandler-and-webrequesthandler-explained/

like image 763
Adrian Avatar asked Apr 29 '17 23:04

Adrian


People also ask

How to add handler to HttpClient?

Adding Message Handlers to the Client PipelineHttpClient client = HttpClientFactory. Create(new Handler1(), new Handler2(), new Handler3()); Message handlers are called in the order that you pass them into the Create method. Because handlers are nested, the response message travels in the other direction.

What is a delegatinghandler?

Typically, a series of message handlers are chained together. The first handler receives an HTTP request, does some processing, and gives the request to the next handler. At some point, the response is created and goes back up the chain. This pattern is called a delegating handler.

What is ConfigurePrimaryHttpMessageHandler?

ConfigurePrimaryHttpMessageHandler(IHttpClientBuilder, Func<IServiceProvider,HttpMessageHandler>) Adds a delegate that will be used to configure the primary HttpMessageHandler for a named HttpClient.

What is HttpClientHandler C#?

The HttpClient class uses a message handler to process the requests on the client side. The default handler provided by the dot net framework is HttpClientHandler. This HTTP Client Message Handler sends the request over the network and also gets the response from the server.


3 Answers

I should have realized this sooner but it makes sense to perhaps set the inner handler to the default handler that HttpClient uses. So inside your child class of DelegatingHandler you should set your inner handler to the default handler used by HttpClient like so:

    public CkApiMessageHandler(string email, string password, Guid moduleId)
    {
        _email = email;
        _password = password;
        _moduleId = moduleId;
        InnerHandler = new HttpClientHandler();

    }
like image 69
Adrian Avatar answered Oct 09 '22 20:10

Adrian


Adrian answer is correct. You have to set an inner handler for your custom handler, but there is a better way to set the inner handler.

public MyDelegatingHandler( ) : base( new HttpClientHandler( ) )
like image 14
Lukáš Kmoch Avatar answered Oct 09 '22 21:10

Lukáš Kmoch


Or you can use the HttpClientFactory

_client  = HttpClientFactory.Create(new CKEnterprise.Common.CkApiMessageHandler(email, password))

https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/httpclient-message-handlers#adding-message-handlers-to-the-client-pipeline

like image 3
Raj Rao Avatar answered Oct 09 '22 21:10

Raj Rao