Xamarin Connection Refused when connecting to Netcore 3.1 localhost rest api 使用来自移动应用程序的 Kestrel

Xamarin Connection Refused when connecting to Netcore 3.1 localhost rest api using Kestrel from mobile app

我正在为 Xamarin 移动应用程序使用 MVVM Prism。

我创建了一个 NetCore 3.1 rest api 作为我的 Xamarin 移动应用程序的后端。我希望能够在本地调试它。 我愿意:

一个。连接到本地主机以从模拟器调试其余 api B. 如果可能的话,还想在调试模式下从移动应用程序本身连接到 localhost kestrel

我正在使用 NetCore Kestrel 而不是 IIS 在本地主机中托管其余 api。

我可以在本地连接 Postman 并在 VS 中使用此 URL:

进行调试

https://localhost:44349/api/Accounts/Register

尽管我确实遇到了第一个证书验证错误,但我关闭了 POSTMAN 中的证书验证以使其正常工作,但是当我尝试使用代码从移动设备连接到相同的 URL 时下面我得到这个错误。

SignInWithFacebookTapped函数调用ApiServices注册函数后LoginPageViewModel.cs触发错误:

我已经为此苦苦挣扎了整整 2 天,这是我的代码:

休息API项目

Program.cs class:

namespace MyAppNamespace
{
    public class Program
    {
        
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                 .ConfigureServices((context, services) =>
                 {
                     services.Configure<KestrelServerOptions>(
                         context.Configuration.GetSection("Kestrel"));
                 })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.ConfigureKestrel(serverOptions =>
                    {
                        // Set properties and call methods on options
                        serverOptions.Limits.MaxConcurrentConnections = 100;
                        serverOptions.Limits.MaxConcurrentUpgradedConnections = 100;
                        serverOptions.Limits.MaxRequestBodySize = 10 * 1024;
                        serverOptions.Limits.MinRequestBodyDataRate =
                            new MinDataRate(bytesPerSecond: 100,
                                gracePeriod: TimeSpan.FromSeconds(10));
                        serverOptions.Limits.MinResponseDataRate =
                            new MinDataRate(bytesPerSecond: 100,
                                gracePeriod: TimeSpan.FromSeconds(10));
                        serverOptions.Listen(IPAddress.Loopback, 5000);
                        serverOptions.Listen(IPAddress.Loopback, 5001,
                            listenOptions =>
                            {
                                listenOptions.UseHttps(",myapphosting.pfx",
                                    "mypassword");                                                 // I did not specify where these files are in the code, it seems to just know?
                            });
                        serverOptions.Limits.KeepAliveTimeout =
                            TimeSpan.FromMinutes(2);
                        serverOptions.Limits.RequestHeadersTimeout =
                            TimeSpan.FromMinutes(1);                 
                    })         
                    .UseStartup<Startup>();
                });
     }
}

启动class:

namespace MyAppNamespace
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddMvc(option => option.EnableEndpointRouting = false)
           .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
           .AddNewtonsoftJson(opt => opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
               .AddJwtBearer(options =>
               {
                   options.TokenValidationParameters = new TokenValidationParameters
                   {
                       ValidateIssuer = true,
                       ValidateAudience = true,
                       ValidateLifetime = true,
                       ValidateIssuerSigningKey = true,
                       ValidIssuer = Configuration["Tokens:Issuer"],
                       ValidAudience = Configuration["Tokens:Issuer"],
                       IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"])),
                       ClockSkew = TimeSpan.Zero,
                   };
               });
            services.AddDbContext<MyAppDbContext>(option => option.UseSqlServer("MyConnectionString"));
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, MyDbContext myDbContext)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //else
            //{
            //    app.UseHsts();
            //}

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            myDbContext.Database.EnsureCreated();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

launchSettings.Json :

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iis": {
      "applicationUrl": "http://localhost/MyWebApiName",
      "sslPort": 0
    },
    "iisExpress": {
      "applicationUrl": "http://localhost:52080",
      "sslPort": 44349
    }
  },
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "api/accounts/register",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "YWAW.WebApi": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "api/accounts/register",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "https://localhost:5001;http://localhost:5000"
    }
  }
}

我按照本教程设置了绕过证书安全检查并在 Android 和 iOS 平台上获取不安全的处理程序。 https://docs.microsoft.com/en-us/xamarin/cross-platform/deploy-test/connect-to-local-web-services

XAMARIN 表单项目

in 常量class(共享项目):

public static string BaseAddress =
    Device.RuntimePlatform == Device.Android ? "https://10.0.2.2:5001" : "https://localhost:5001";  // here I tried writing the iOS url as in Postman above too, same result
public static string RestUrl = $"{BaseAddress}/api/Accounts/Register";

App.Xaml.cs

public partial class App
    {
        /* 
         * The Xamarin Forms XAML Previewer in Visual Studio uses System.Activator.CreateInstance.
         * This imposes a limitation in which the App class must have a default constructor. 
         * App(IPlatformInitializer initializer = null) cannot be handled by the Activator.
         */
        public App() : this(null) { }

        public App(IPlatformInitializer initializer) : base(initializer) { }

        protected override async void OnInitialized()
        {
            InitializeComponent();

            await NavigationService.NavigateAsync("NavigationPage/HomePage");
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterSingleton<IAppInfo, AppInfoImplementation>();

            containerRegistry.RegisterForNavigation<NavigationPage>();

            containerRegistry.RegisterForNavigation<MainPage, MainPageViewModel>();
            containerRegistry.RegisterForNavigation<HomePage, HomePageViewModel>();
            containerRegistry.RegisterForNavigation<LoginPage, LoginPageViewModel>();
            containerRegistry.RegisterForNavigation<SignUpPage, SignUpPageViewModel>();
            containerRegistry.RegisterForNavigation<DefaultCustomActivityIndicatorPage, DefaultCustomActivityIndicatorPageViewModel>();
            containerRegistry.RegisterForNavigation<DiscoverDealsTabbedPage, DiscoverDealsTabbedPageViewModel>();

            containerRegistry.Register<ApiServices>(); //not sure if I actually should register this too
        }

        // add for app center analytics and crashes
        protected override void OnStart()
        {
            base.OnStart();

            AppCenter.Start("ios=secretkey" +
                  "uwp={Your UWP App secret here};" +
                  "android={Your Android App secret here}",
                  typeof(Analytics), typeof(Crashes));
        }
    }

LoginPageViewModel.cs

public class LoginPageViewModel : BindableBase
    {
        private IPageDialogService _dialogService { get; }
        private ApiServices _apiService { get; }

        public DelegateCommand SignInWithEmailTappedCmd { get; set; }

        public DelegateCommand SignInWithFacebookTappedCmd { get; set; }

        public DelegateCommand SignInWithGoogleTappedCmd { get; set; }

        private IFacebookClient _facebookService = CrossFacebookClient.Current;
        private IGoogleClientManager _googleService = CrossGoogleClient.Current;

        public INavigationService NavigationService { get; set; }
        private readonly ICustomActivityIndicatorPage _customActivityIndicator;

        private string _emailAddress;

        public string EmailAddress
        {
            get => _emailAddress;
            set => SetProperty(ref _emailAddress, value);
        }

        private string _password;

        public string Password
        {
            get => _password;
            set => SetProperty(ref _password, value);
        }

        public LoginPageViewModel(ICustomActivityIndicatorPage customActivityIndicator,
                                  IHttpClientHandlerService httpClientHandlerService,
                                  INavigationService navigationService,
                                  IPageDialogService dialogService)
        {
            _customActivityIndicator = customActivityIndicator;
            SignInWithEmailTappedCmd = new DelegateCommand(SignInWithEmailTapped);
            SignInWithFacebookTappedCmd = new DelegateCommand(async() => await SignInWithFacebookTapped());
            SignInWithGoogleTappedCmd = new DelegateCommand(async() => await SingInWithGoogleTapped());
            NavigationService = navigationService;
            _dialogService = dialogService;
            _apiService = new ApiServices(httpClientHandlerService);
        }


        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private async Task SignInWithFacebookTapped()
        {
            try
            {
                if (_facebookService.IsLoggedIn)
                    _facebookService.Logout();

                EventHandler<FBEventArgs<string>> userDataDelegate = null;

                _customActivityIndicator.InitActivityPage(new DefaultCustomActivityIndicatorPage());
                _customActivityIndicator.ShowActivityPage();

                userDataDelegate = async (object sender, FBEventArgs<string> e) =>
                {
                    switch (e.Status)
                    {
                        case FacebookActionStatus.Completed:
                            var facebookProfile = await Task.Run(() => JsonConvert.DeserializeObject<FacebookProfile>(e.Data));

                            // save the user to the db if doesn't exist
                            UserToRegister user = new UserToRegister
                            {
                                Email = facebookProfile.Email,
                                FirstName = facebookProfile.FirstName,
                                LastName = facebookProfile.LastName
                            };

                            // THIS IS WHERE I TRY TO ACCESS THE LOCALHOST REST API
                            **var registerOutcome = await _apiService.Register(user);**

                            await NavigationService.NavigateAsync("DiscoverDealsTabbedPage");
                            _customActivityIndicator.HideActivityPage();
                            break;
                        case FacebookActionStatus.Canceled:
                            _customActivityIndicator.HideActivityPage();
                            await _dialogService.DisplayAlertAsync("Facebook Auth", "Canceled", "Ok");
                            break;
                        case FacebookActionStatus.Error:
                            _customActivityIndicator.HideActivityPage();
                            await _dialogService.DisplayAlertAsync("Facebook Auth", "Error", "Ok");
                            break;
                        case FacebookActionStatus.Unauthorized:
                            _customActivityIndicator.HideActivityPage();
                            await _dialogService.DisplayAlertAsync("Facebook Auth", "Unauthorized", "Ok");
                            break;
                    }

                    _facebookService.OnUserData -= userDataDelegate;
                };

                _facebookService.OnUserData += userDataDelegate;

                string[] fbRequestFields = { "email", "first_name", "picture", "gender", "last_name" };
                string[] fbPermisions = { "email" };
                await _facebookService.RequestUserDataAsync(fbRequestFields, fbPermisions);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            finally
            {
                _customActivityIndicator.HideActivityPage();
            }
        }

    }

ApiServices class:

public class ApiServices
    {
        private HttpClient httpClient;
        private string apiRegisterURL;
        private readonly IHttpClientHandlerService _httpClientHandler;

        public ApiServices(IHttpClientHandlerService httpClientHandler)
        {
#if DEBUG
            _httpClientHandler = httpClientHandler;
            httpClient = new HttpClient(_httpClientHandler.GetInsecureHandler());
            apiRegisterURL = Constants.RestUrl;
#else
            httpClient = new HttpClient();
            apiRegisterURL = Constants.REGISTER_URL;
#endif    
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public async Task<RegisterOutcome> Register(UserToRegister user)
        {
            RegisterOutcome outcome = new RegisterOutcome();

            //var httpClient = new HttpClient();

            try
            {
                var json = JsonConvert.SerializeObject(user);
                var content = new StringContent(json, Encoding.UTF8, "application/json");

                outcome.ResponseMessage = await httpClient.PostAsync(apiRegisterURL, content);
            }
            catch (Exception ex)               // ERROR IS HERE CONNECTION REFUSED
            {
                outcome.ErrorMessage = ex.Message;
            }

            return outcome;
        }
    }

IOS 项目:

iOSHttpClientHandlerService.cs:

[assembly: Dependency(typeof(iOSHttpClientHandlerService))]
namespace YWAWMobileApp.iOS.Services
{
    public class iOSHttpClientHandlerService : IHttpClientHandlerService
    {
        public HttpClientHandler GetInsecureHandler()
        {
            HttpClientHandler handler = new HttpClientHandler();
            handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
            {
                if (cert.Issuer.Equals("CN=localhost"))
                    return true;
                return errors == System.Net.Security.SslPolicyErrors.None;
            };
            return handler;
        }
    }
}

AppDelegate.cs:

public class iOSInitializer : IPlatformInitializer
    {
        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            // Register any platform specific implementations
            containerRegistry.Register<ICustomActivityIndicatorPage, iOSCustomActivityIndicatorPage>();
            containerRegistry.Register<IHttpClientHandlerService, iOSHttpClientHandlerService>();
        }
    }

ANDROID 项目与 IOS.

相同

在您的网络 api 项目中,转到 Program.cs 文件并在 CreateWebHostBuilder 方法中添加它。

.UseUrls("https://*:5001") // Add your port number or url scheme (http or https) on which your apis running instead of 5001.

然后像这样改变你的基础url。

public static string BaseAddress = "https://{here write your local ip address}:5001";