当 API 关闭时,Xamarin 表单依赖注入 HttpClient 超时不起作用

Posted

技术标签:

【中文标题】当 API 关闭时,Xamarin 表单依赖注入 HttpClient 超时不起作用【英文标题】:Xamarin Forms Dependeny Injected HttpClient Timeout not working when API is down 【发布时间】:2021-10-03 16:36:15 【问题描述】:

经过一些研究,我了解到为每个请求创建新的 HttpClients 对 HttpClient 的使用并不好。使用依赖注入是最好的解决方案之一。所以,我已经在我的 Xamarin Forms App 中实现了 DI,但我遇到了一个问题。我将客户端服务作为 Singleton 并将 httpclient 注入其中。如果 API 正在运行,则 httpclient 超时属性有效。不幸的是,当 API 未运行时,超时不起作用。即使我将 http 超时设置为 10 秒,也需要两分钟以上才会发生异常。我预计在设置的超时后会发生异常,并且会出现类似这样的消息,“无法建立连接,因为目标机器主动拒绝了它......”,但是直到两分钟以上才再次发生任何事情。但是,如果我创建一个新的 HttpClient,一切都会按预期工作。我已经在 Startup 文件和 Service 构造函数中初始化了 timeout 属性,但没有区别。当 API 未运行时,httpclient 的超时不起作用。我真的很感激这方面的任何帮助。请参阅下面我用作参考的文章的链接。如果您有任何问题,请告诉我。

提前谢谢你!!!

Xamarin Forms Dependency Injection Article

编辑:

Startup.cs

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using Xamarin.Essentials;

namespace HttpClientTimeoutTest

  public class Startup
  
    public static IServiceProvider ServiceProvider  get; set; 

    public static void Init()
    
      var host = new HostBuilder()
                .ConfigureHostConfiguration(c =>
                
                  c.AddCommandLine(new string[]  $"ContentRoot=FileSystem.AppDataDirectory" );
                )
                .ConfigureServices((c, x) => ConfigureServices(c, x))
                .Build();

      //Save our service provider so we can use it later.
      ServiceProvider = host.Services;
    

    static void ConfigureServices(HostBuilderContext ctx, IServiceCollection services)
    
      services.AddHttpClient("", client =>
      
        client.Timeout = TimeSpan.FromSeconds(10);
      );
      services.AddSingleton<IMyAppService, MyAppService>();
      services.AddTransient<MainViewModel>();
    
  

MainViewModel.cs

using System;
using System.Windows.Input;
using Xamarin.Forms;

namespace HttpClientTimeoutTest

  public class MainViewModel
  
    public ICommand CallAPICommand
    
      get
      
        return new Command(async () =>
        
          try
          
            var races = await _appService.CallApiAsync();
          
          catch (Exception ex)
          
          
        );
      
    

    IMyAppService _appService;
    public MainViewModel(IMyAppService appService)
    
      _appService = appService;
    
  

MyAppService.cs

using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace HttpClientTimeoutTest

  public class MyAppService : IMyAppService
  
    private readonly HttpClient httpClient;

    public MyAppService(IHttpClientFactory httpClientFactory)
    //public MyAppService(HttpClient client)
    
      httpClient = httpClientFactory.CreateClient();
      //httpClient = client;
    

    public async Task<string> CallApiAsync()
    
      //HttpClient client = new HttpClient();
      var response = await httpClient.GetAsync("YOUR API CALL HERE");

      if (response.IsSuccessStatusCode)
      
        return await response.Content.ReadAsStringAsync();
      

      return null;
    
  

IMyAppService.cs

using System.Threading.Tasks;

namespace HttpClientTimeoutTest

  public interface IMyAppService
  
    Task<string> CallApiAsync();
  

MainPage.xaml.cs

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace HttpClientTimeoutTest

  public partial class MainPage : ContentPage
  
    public MainPage()
    
      InitializeComponent();
      BindingContext = Startup.ServiceProvider.GetService<MainViewModel>();
    
  

MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="HttpClientTimeoutTest.MainPage">

  <StackLayout>
    <Frame BackgroundColor="#2196F3" Padding="24" CornerRadius="0">
      <Label Text="Welcome to Xamarin.Forms!" HorizontalTextAlignment="Center" TextColor="White" FontSize="36"/>
    </Frame>
    <Label Text="Start developing now" FontSize="Title" Padding="30,10,30,10"/>
    <Label Text="Make changes to your XAML file and save to see your UI update in the running app with XAML Hot Reload. Give it a try!" FontSize="16" Padding="30,0,30,0"/>
    <Label FontSize="16" Padding="30,24,30,0">
      <Label.FormattedText>
        <FormattedString>
          <FormattedString.Spans>
            <Span Text="Learn more at "/>
            <Span Text="https://aka.ms/xamarin-quickstart" FontAttributes="Bold"/>
          </FormattedString.Spans>
        </FormattedString>
      </Label.FormattedText>
    </Label>
    <Button Command="Binding CallAPICommand" Text="Call API"/>
  </StackLayout>

</ContentPage>

WebAPI HttpClientTimeoutTestController

using Microsoft.AspNetCore.Mvc;
using System.Threading;

namespace WebAPI.Controllers

  [ApiController]
  [Route("api/[controller]")]
  public class HttpClientTimeoutTestController : ControllerBase
  
    public HttpClientTimeoutTestController()
    
    

    [HttpGet]
    public IActionResult Get()
    
      //Thread.Sleep(60000);
      return Ok(true);
    
  

【问题讨论】:

请在您的问题正文中发布格式正确的相关代码。链接到异地回购违反了 SO 准则。请阅读How to Ask 获取指导。 我很抱歉。我已经用重现我的问题所需的重要代码修改了我的帖子。请让我知道我是否还有其他遗漏。谢谢。 ASP.NET 核心主机构建器的东西在这里绝对是矫枉过正。您可以使用 Xamarin.Forms 依赖服务注册单例。您可以使用静态类创建一个单例,您可以将您的 httpclient 放在 app.cs 文件中(它本身就是一个单例)。我个人为我的 xamarin 应用程序(Prism)使用 DI 容器,并将我的客户注册为单例,并且没有您描述的问题。我会想象主机构建器正在做一些时髦的事情,因为它不是为这个应用程序设计的。 哇。矫枉过正是轻描淡写。 :) 我对 XF 的内置 DependencyService 了解不多。谢谢你的参考。我所要做的就是使用 DependencyService 在 App.xaml.cs 文件中将我的服务注册为单例。 【参考方案1】:

这是我的解决方案。所以我从我的项目中删除了 Startup.cs 文件,并简单地将我的服务注册为单例:

App.xaml.cs

using System;
using System.Net.Http;
using Xamarin.Forms;

namespace HttpClientTimeoutTest

  public partial class App : Application
  
    public HttpClient Client  get; set; 
    public App()
    
      InitializeComponent();
      Client = new HttpClient();
      Client.Timeout = TimeSpan.FromSeconds(10);
      var appService = new MyAppService(Client);
      DependencyService.RegisterSingleton<IMyAppService>(appService);
      MainPage = new MainPage();
    

    protected override void OnStart()
    
    

    protected override void OnSleep()
    
    

    protected override void OnResume()
    
    
  

然后,我将 Singleton 的值分配给要注入到 ViewModel 中的变量,如下所示:

MainPage.xaml.cs

using Xamarin.Forms;

namespace HttpClientTimeoutTest

  public partial class MainPage : ContentPage
  
    public MainPage()
    
      InitializeComponent();
      var service = DependencyService.Get<IMyAppService>();
      BindingContext = new MainViewModel(service);
    
  

我已经对此进行了测试和重新测试,即使 API 未运行,我的超时也能正常工作。我也在我的“真实”应用程序中实现了这一点,一切都在那里工作。包括正在注入的 httpclient 上的标头标记。如果这可以进一步简化,请告诉我,但到目前为止一切都很好。再次感谢@Axemasta

【讨论】:

以上是关于当 API 关闭时,Xamarin 表单依赖注入 HttpClient 超时不起作用的主要内容,如果未能解决你的问题,请参考以下文章

在xamarin表单中处理api请求的一个地方处理异常

使用 Prism 在 Xamarin Forms 的后台服务中实现依赖注入

使用 Xamarin 跨平台打开开始表单并关闭所有其他表单

xamarin 形式的跨平台移动应用程序(iOS、Android)中的依赖注入

将 xamarin 表单与 IServiceProvider 一起使用

当连接是移动数据 xamarin 表单时,无法在服务器上发出 Put 和 Post 请求