【问题标题】:HttpClientFactory BadRequest?HttpClientFactory BadRequest?
【发布时间】:2018-06-24 11:28:40
【问题描述】:

我需要使用HttpClientFactory 连接到外部api。 Startup.cs 中的代码是这样的

public void SetUpHttpClients(IServiceCollection services)
{
        var loginEndpoint = Path.Combine(baseApi, "api/authentication);
        var fileExists = File.Exists(certificatePath);

        if (!fileExists)
            throw new ArgumentException(certificatePath);

        var certificate = new X509Certificate2(certificatePath, certPwd);

        services.AddHttpClient("TestClient", client =>
        {
        client.BaseAddress = new Uri(baseApi);
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(accept));
        client.DefaultRequestHeaders.Add("ApiKey", apiKey);

        var body = new { Username = username, Password = password };
        var jsonBody = JsonConvert.SerializeObject(body);
        var content = new StringContent(jsonBody, Encoding.UTF8, contentType);

        var loginResponse = client.PostAsync(loginEndpoint, content).Result;

        }).ConfigurePrimaryHttpMessageHandler(() =>
        {
            var cookieContainer = new CookieContainer();
            var handler = new HttpClientHandler
            {
                CookieContainer = cookieContainer
            };
            handler.ClientCertificates.Add(certificate);
            return handler;
}); 

我已经用 HttpClientFactory 在 MessageController 中添加了这段代码,

public class MessagesController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;

    public ValuesController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

   [HttpGet]
    public IActionResult GetMessages()
    {
        var client = _httpClientFactory.CreateClient("TestClient");
        var result = client.GetStringAsync("/api/messages");
        return Ok(result);
    }
}

但是当我尝试连接并从HttpClientFactory 获取消息时,我收到此错误:

加载资源失败:net::ERR_CONNECTION_RESET, 404 - BadRequest

【问题讨论】:

  • 快速提问,为什么要在完成出厂配置之前提出请求?该 Post 将失败,因为您仍在配置客户端时尝试使用它。
  • 你能说明你实际需要在哪里使用客户端吗?在控制器或服务类中?这里的假设是SetUpHttpClientsConfigureServices 中被调用。
  • 是的,首先我想在 ConfigureServices 中使用,然后从控制器调用。
  • 好吧,这感觉就像XY problem。澄清你真正想做的是什么,而不仅仅是你试图做的事情。这样可以得到更准确的答案。

标签: c# asp.net-core dotnet-httpclient httpclientfactory


【解决方案1】:

您尝试在完成出厂配置之前发出请求,该 Post 将失败,因为您尝试使用客户端同时仍在配置它。

这里的假设是SetUpHttpClientsConfigureServices 中被调用。

public void ConfigureServices(IServiceCollection services) {
    //...

    SetUpHttpClients(services);

    //...

    services.AddMvc();
}

public void SetUpHttpClients(IServiceCollection services) {
    var basePath = Directory.GetCurrentDirectory();
    var certificatePath = Path.Combine(basePath, certPath);
    var fileExists = File.Exists(certificatePath);

    if (!fileExists)
        throw new ArgumentException(certificatePath);

    var certificate = new X509Certificate2(certificatePath, certPwd);

    //Adding a named client
    services.AddHttpClient("TestClient", client => {
        client.BaseAddress = new Uri(baseApi);
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(accept));
        client.DefaultRequestHeaders.Add("ApiKey", apiKey);
    })
    .ConfigurePrimaryHttpMessageHandler(() => {
        var cookieContainer = new CookieContainer();
        var handler = new HttpClientHandler {
            CookieContainer = cookieContainer
        };
        handler.ClientCertificates.Add(certificate);
        return handler;
    });
}

配置完成后,工厂可以根据需要注入到控制器或服务类中。

以下示例显示了使用工厂作为对控制器的依赖项。

[Route("api/[controller]")]
public class ValuesController : Controller {
    private readonly IHttpClientFactory factory;

    public MyController(IHttpClientFactory factory) {
        this.factory = factory;
    }

    [HttpGet]
    public async Task<IActionResult> Get() {
        var client = factory.CreateClient("TestClient");
        var result = client.GetStringAsync("api/messages");
        return Ok(result);
    }
}

在上面,工厂用于创建命名客户端的实例。它将具有启动期间设置的所有配置,其中包括带有证书的客户端处理程序。

为了能够在启动期间使用客户端(我不建议这样做),您首先需要将服务集合构建到服务提供者中。

var serviceProvider = services.BuildServiceProvider();
var factory = serviceProvider.GetService<IHttpClientFactory>();
var client = factory.CreateClient("TestClient");

var body = new { Username = username, Password = password };
var jsonBody = JsonConvert.SerializeObject(body);
var content = new StringContent(jsonBody, Encoding.UTF8, contentType);

var loginResponse = client.PostAsync("api/authentication", content).Result;

//...do something with the response

但是,这可能会产生无法预料的结果,因为在启动过程的这个阶段,服务集合不会完全填充。

【讨论】:

  • 我已经用代码编辑了我的第一篇文章。我该如何解决这个问题,但我不必像您在第一个示例中那样在控制器的方法中添加凭据数据。谢谢。我的启动配置文件有什么问题?
  • 你喜欢什么并建议我。?我想设置正确的方法,第一次使用 HttpClientFactory
  • @enzodev 我们遇到了沟通问题,因此无法理解实际问题。 api/authenticationapi/messages 有什么关系。可能是因为我们没有看到相同的解释。尽我所能用您提供的信息为您提供帮助。
  • 我需要从端点 api/messages 获取消息,但前提是正确的授权/登录。我有单独的登录端点(api/授权),如果登录响应 StatusCodeOK 我需要从同一个请求中调用 api/messages 端点。我有 MessageController 和方法 GetMessages()。
【解决方案2】:

我已经解决了这个问题。 SetUpHttpClient 方法看这个:

public void SetUpHttpClients(IServiceCollection services)
{
    var basePath = Directory.GetCurrentDirectory();
    var certificatePath = Path.Combine(basePath, CertPath);

    var fileExists = File.Exists(certificatePath);
    if (!fileExists)
        throw new ArgumentException(certificatePath);

    var certificate = new X509Certificate2(certificatePath, CertPwd);

    services.AddHttpClient("TestClient", client =>
    {
        client.BaseAddress = new Uri(BaseApi);
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(Accept));
        client.DefaultRequestHeaders.Add("ApiKey", ApiKey);

    }).ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ClientCertificates.Add(certificate);
        return handler;
    });
}

我添加到服务和从控制器调用的登录和消息的其余代码。

MessageClient.cs

    public MessageClient(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;

        _client = _httpClientFactory.CreateClient("TestClient");

        var body = new { UserName = Username, UserPassword = Password };
        var jsonBody = JsonConvert.SerializeObject(body);
        var content = new StringContent(jsonBody, Encoding.UTF8, ContentType);

        var loginEndpoint = Path.Combine(BaseApi, "api/authentication");

        _responseMessage = _client.PostAsync(loginEndpoint, content).Result;
    }

    public async Task<string> ReadMessages()
    {
        try
        {
            string messages = "";

            if (_responseMessage.IsSuccessStatusCode)
            {
                var messagesEndpoint = Path.Combine(BaseApi, "api/messages/");
                var cookieContainer = new CookieContainer();
                var handler = new HttpClientHandler { CookieContainer = cookieContainer };
                var cookiesToSet = GetCookiesToSet(_responseMessage.Headers, BaseApi);

                foreach (var cookie in cookiesToSet)
                {
                    handler.CookieContainer.Add(cookie);
                }
                var messageResponse = await _client.GetAsync(messagesEndpoint);
                messages = await messageResponse.Content.ReadAsStringAsync();
            }
            return messages;
        }
        catch (Exception e)
        {
            throw e.InnerException;
        }
    }

MessageContoller.cs

[HttpGet]
public async Task<ActionResult> GetMessages()
{
  var result = await _messageClient.ReadMessages();
  return Ok(result);
}

谢谢@Nkosi。

【讨论】:

  • 一些建议。不要混合 async await 和 .Result。它可能会导致死锁。使ReadMessages 一直异步。
猜你喜欢
  • 1970-01-01
  • 2021-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多