27

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/

0

5 Answers 5

51

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();

    }
Sign up to request clarification or add additional context in comments.

1 Comment

This is not intuitive at all. Good find :)
17

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( ) )

2 Comments

But HttpClientHandler is created manually here? Will it be managed by the framework (pooling, time to live etc)?
@Ssss It has the exact scope of MyDelegatingHandler, so it is kept alive and garbage collected along with that. If that was added with .ConfigurePrimaryMessageHandler(...) and default PooledConnectionIdleTimeout is used, then both will live roughly 2 minutes.
3

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

Comments

2

.Net 8 and higher using primary constructor, this will look like as below

public class AccessTokenDelegatingHandler()
    : DelegatingHandler
{

 protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
      
            InnerHandler ??= new HttpClientHandler();

Comments

0

Ran into this building integration tests for a versioned API with .NET Aspire.

Inside of my TestingFixture:

    var opt = new ApiVersionHandler(new QueryStringApiVersionWriter(), new ApiVersion(version))
    {
        // Add this in after the constructor args!
        InnerHandler = new HttpClientHandler()
    };

    var endpoint = _app!.GetEndpoint(applicationName);

    var _testingClient = new HttpClient(opt)
    {
        BaseAddress = endpoint
    };

Then I can create my integration tests inside of:

SendApiTests(TestingFixture fixture) : IClassFixture<TestingFixture>

and make use of the HttpClient like so:

var client = await fixture.CreateHttpClient(version);

As an aside if you're abstracting the HttpClient instantiation to your IClassFixture<T> make sure you use IAsyncLifetime like so:

public sealed class TestingFixture : IAsyncLifetime
{
    // Rest of fixture setup above, along with InitialiseAsync()


    public async Task DisposeAsync()
    {
        if( _testingClient is not null)
            _testingClient.Dispose();
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.