【问题标题】:Xamarin Connection Refused when connecting to Netcore 3.1 localhost rest api using Kestrel from mobile app使用移动应用程序中的 Kestrel 连接到 Netcore 3.1 localhost rest api 时 Xamarin 连接被拒绝
【发布时间】:2020-10-14 09:16:20
【问题描述】:

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

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

A.连接到 localhost 以从 Simulators 调试其余 api B. 如果可能,还希望在调试模式下从移动应用程序本身连接到 localhost kestrel

我正在使用 NetCore Kestrel 而不是 IIS 在 localhost 中托管其余的 api。

我可以在本地与 Postman 连接,并在 VS 中使用此 URL 进行调试:

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

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

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

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

REST API 项目

Program.cs 类:

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

启动类:

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 表单项目

在常量类(共享项目)中:

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 类:

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。

【问题讨论】:

  • 不要使用本地主机。关于 Xamarin 和 Web 服务的现有问题有数百个,它们都说同样的话
  • @Jason 好的,但是如何调试其余的 api?
  • 我没有说你不能使用本地服务器作为你的 API 的主机。只是不要使用 localhost,使用服务器的 FQDN 或 IP。
  • @Jason 哦,好的,Jason,会试一试,谢谢你的建议。
  • @Jason 结果相同,连接被拒绝.... iOS 模拟器也使用桌面本地主机,从我在文档中读到的内容来看,Android 是不同的。

标签: xamarin.forms localhost asp.net-core-webapi kestrel


【解决方案1】:

在您的 web 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.

然后像这样更改您的基本网址。

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

【讨论】:

    猜你喜欢
    • 2019-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-05
    • 1970-01-01
    • 2021-10-28
    相关资源
    最近更新 更多