本地主机上的 React Native 和 C# WebAPI 之间的获取命令返回网络请求失败错误

Posted

技术标签:

【中文标题】本地主机上的 React Native 和 C# WebAPI 之间的获取命令返回网络请求失败错误【英文标题】:Fetch Command Between React Native and C# WebAPI on Localhost Returns Network Request Failed error 【发布时间】:2021-10-29 14:52:36 【问题描述】:

我在 androidios 中的 React Native 中遇到问题,我正在尝试运行 fetch 命令以指向在我的 localhost 上运行的 C# WebAPI 端点。我不断收到以下错误:

[TypeError: Network request failed]

我注意到,如果我将 fetch 命令指向外部源(请参阅下面代码中的注释),fetch 就可以正常工作。但是对于localhost 连接,我根本无法让这个东西工作。

这是我在 React Native 中登录页面的代码:

import "react-native-gesture-handler";
import  StatusBar  from "expo-status-bar";
import React,  useState, Component  from "react";
import 
  StyleSheet,
  Text,
  TextInput,
  View,
  Button,
  Dimensions,
  TouchableOpacity,
 from "react-native";
import  NavigationContainer  from "@react-navigation/native";
import  createStackNavigator  from "@react-navigation/stack";
import NetInfo from "@react-native-community/netinfo";
import  getUniqueId  from "react-native-device-info";
import  API_URL, PRIMARY_COLOR, QUATERNARY_COLOR  from "../env.json";
import * as Linking from "expo-linking";

var width = Dimensions.get("window").width - 20;

const LogInTheUser = (emailAddress, password) => 
  console.log(`$API_URL/Login`);
  let userInfo = fetch(`https://localhost:44371/api/Login`, 
    //"https://devapi.flouriish.io/api/Login", 
    method: "POST",
    cache: "no-cache",
    mode: "cors",
    headers: 
      "Content-Type": "application/json",
    ,
    body: JSON.stringify(
      emailAddress: emailAddress,
      password: password,
      ipAddress: ipAddress,
      deviceUUID: deviceUUID,
    ),
  )
    .then((response) => response.json())
    .then((responseData) => 
      console.log(responseData);
      return responseData;
    )
    .catch((error) => console.warn(error));
;

const deviceUUID = getUniqueId();
let ipAddress = "127.0.0.1";
NetInfo.fetch().then((state) => 
  ipAddress = state.details.ipAddress;
);

export default class Login extends Component 
  state = 
    emailAddress: "",
    password: "",
  ;
  static navigationOptions = 
    title: "ResetPassword",
  ;

  render() 
    return (
      <View style=styles.login>
        <Text style=styles.label>Email Address</Text>
        <TextInput
          style=styles.input
          placeholder="Email Address"
          placeholderTextColor=PRIMARY_COLOR
          onChangeText=(value) => this.setState( emailAddress: value )
          value=this.state.emailAddress
        />
        <Text style=styles.label>Password</Text>
        <TextInput
          style=styles.input
          placeholder="Password"
          placeholderTextColor=PRIMARY_COLOR
          onChangeText=(value) => this.setState( password: value )
          value=this.state.password
          secureTextEntry=true
        />
        <Text style=styles.label>IP Address: ipAddress</Text>
        <Text style=styles.label>Device UUID: deviceUUID</Text>
        <Button
          title="Login"
          color=PRIMARY_COLOR
          onPress=() =>
            LogInTheUser(this.state.emailAddress, this.state.password)
          
        />
        <TouchableOpacity
          style=styles.label
          onPress=() => this.props.navigation.navigate("ForgotPassword")
        >
          <Text style= color: PRIMARY_COLOR >Forgot Password?</Text>
        </TouchableOpacity>

        <StatusBar style="auto" />
      </View>
    );
  


const styles = StyleSheet.create(
  login: 
    flex: 1,
    backgroundColor: QUATERNARY_COLOR,
    alignItems: "center",
  ,
  label: 
    marginTop: 10,
    color: PRIMARY_COLOR,
    marginBottom: 10,
  ,
  input: 
    height: 40,
    width: width,
    margin: 12,
    borderWidth: 1,
    borderColor: PRIMARY_COLOR,
    color: PRIMARY_COLOR,
  ,
);

这是 React Native 应用程序的 AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.akmaziofrontend">
  <uses-permission android:name="android.permission.INTERNET"/>
  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
  <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
  <uses-permission android:name="android.permission.VIBRATE"/>
  <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  <application android:name=".MainApplication"
               android:label="@string/app_name"
               android:icon="@mipmap/ic_launcher"
               android:roundIcon="@mipmap/ic_launcher_round"
               android:allowBackup="false"
               android:theme="@style/AppTheme"
               android:usesCleartextTraffic="true">
    <meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@akmazio/akmazio-frontend"/>
    <meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="41.0.0"/>
    <meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
    <meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
    <meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
    <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
        android:launchMode="singleTask"
        android:windowSoftInputMode="adjustResize"
        android:theme="@style/Theme.App.SplashScreen">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
      <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name = "android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme = "flouriish" />
      </intent-filter>
    </activity>
    <activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
  </application>
</manifest>

这是在https://localhost:44371/api/Login 找到的 API 端点的代码:

using akmazio_api.Classes;
using akmazio_api.Contexts;
using akmazio_api.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Security;
using System.Text;
using System.Threading.Tasks;

namespace akmazio_api.Services

    public class LoginService : BaseService
    
        public LoginService(IConfiguration configuration, DatabaseContext context)
            : base(configuration, context)  

        public User Login(Login login)
        
            var user = Context.Users
                .Include(x => x.UserRole)
                .Include(x => x.UserType)
                .Include(x => x.Business)
                .Include(x => x.Business.Address)
                .Include(x => x.Address)
                .Single(x => x.EmailAddress == login.EmailAddress && x.IsVerified);
            var computedHash = Cryptography.ComputeHash(login.Password, user.Salt);

            if (!user.PasswordHash.Equals(computedHash))
            
                throw new SecurityException("User login credentials are not valid");
            
            else
            
                var userLoginTokens = Context.UserLoginTokens
                    .Include(x => x.User)
                    .Where(x => x.User.UserId == user.UserId &&
                        x.IPAddress == login.IPAddress && x.DeviceUUID == login.DeviceUUID);
                
                if (userLoginTokens.Any())
                
                    var token = userLoginTokens.First().Token;
                    user.Token = token;
                    return user.GetScrubbedUser();
                
                else
                
                    var userLoginToken = new UserLoginToken()
                    
                        User = user,
                        Token = Guid.NewGuid().ToString(),
                        IPAddress = login.IPAddress,
                        DeviceUUID = login.DeviceUUID
                    ;
                    Context.UserLoginTokens.Add(userLoginToken);
                    Context.SaveChanges();
                    user.Token = userLoginToken.Token;
                    return user.GetScrubbedUser();
                
            
        
    

这是启用 CORS 的 Startup.cs 文件:

using akmazio_api.Contexts;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace akmazio_api

    public class Startup
    
        public Startup(IWebHostEnvironment env)
        
            var builder = new ConfigurationBuilder();
            if (!env.IsProduction())
            
                builder
                    .SetBasePath(env.ContentRootPath)
                    .AddJsonFile($"appsettings.env.EnvironmentName.json", optional: false, reloadOnChange: true)
                    .AddEnvironmentVariables();
            
            else
            
                builder
                    .SetBasePath(env.ContentRootPath)
                    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                    .AddEnvironmentVariables();
            
            Configuration = builder.Build();
        

        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.AddSingleton(Configuration);
            services.AddControllers();
            services.AddSwaggerDocument(settings =>
            
                settings.Version = "v1";
                settings.Title = "Akmazio Flouriish API";
            );
            services.AddCors(o => o.AddPolicy("DevPolicy", builder =>
            
                builder.AllowAnyOrigin()
                       .AllowAnyMethod()
                       .AllowAnyHeader();
            ));
            services.AddCors(o => o.AddPolicy("ProdPolicy", builder =>
            
                builder.WithOrigins("https://flouriish.io")
                       .AllowAnyMethod()
                       .AllowAnyHeader();
            ));
            services.AddDbContext<DatabaseContext>(option =>
                option.UseSqlServer(Configuration["ConnectionStrings:DatabaseConnection"]));
            services.AddDatabaseDeveloperPageExceptionFilter();
        

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        
            if (env.IsDevelopment() || env.IsEnvironment("Local"))
            
                app.UseOpenApi();
                app.UseSwaggerUi3();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "akmazio_api v1"));
                app.UseExceptionHandler("/error-local-development");
            
            else
            
                app.UseExceptionHandler("/error");
            

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();
            app.UseAuthentication();

            if (env.IsDevelopment() || env.IsEnvironment("Local"))
            
                app.UseCors("DevPolicy");
            
            else if (env.IsProduction())
            
                app.UseCors("ProdPolicy");
            

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

            app.UseForwardedHeaders(new ForwardedHeadersOptions
            
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
            );
        
    

这里有人知道如何解决这个问题吗?正如您所见,我尝试设置 CORS 修复以及添加明文流量,但没有任何效果。任何建议表示赞赏。

【问题讨论】:

【参考方案1】:

您可能还需要在 LoginService 之上添加 CORS 策略属性。

[EnableCors("<YOUR_CORS_POLICY_NAME_GOES_HERE>")]
public class LoginService : BaseService
 ... 

【讨论】:

即使 EnableCors 属性位于 LoginService 类上方,我仍然遇到 CORS 问题 看起来这是一个问题,Android 设备试图连接到它自己的本地主机环境,而不是主机的本地主机。关于如何在 C# WebAPI 和 React Native 中解决此问题的任何想法?【参考方案2】:

localhost 只能在您的 Windows 应用程序中运行,而不是在您的操作系统中运行 如果您使用模拟器或真实设备进行测试和调试,您最常通过 IP 地址运行 Visual Studio iis Express Web 服务器

    请午餐 Visual Studio 以管理员身份运行 转到您的 Visual Studio 项目文件夹并找到 .vs 隐藏文件夹,其中是 sln 文件。 找到applicationhost.config文件,用编辑器打开 在sites标签中找到子标签site并设置bindingInformation *:xxxx:

绑定协议="http" bindingInformation="*:4732:"

例如 4732=>xxxx 是我的项目。

运行asp应用程序,您可以通过设置在局域网、无线局域网中的任何本地ip访问iis express web服务器

【讨论】:

以上是关于本地主机上的 React Native 和 C# WebAPI 之间的获取命令返回网络请求失败错误的主要内容,如果未能解决你的问题,请参考以下文章

本地主机上的连接被拒绝

使用 NextJS + Express 在本地主机上进行 HTTPS

如何在 expo 中禁用 https-only fetch 请求 react native/node js

在本地主机上运行 docker 容器不起作用

react-native出现编译模块找不到

react-native出现编译模块找不到