【发布时间】: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