C#Blazor WASM | Firestore:使用Google.Cloud.Firestore.FirestoreDb.CreateAsync

时间:2020-04-01 17:26:44

标签: firebase google-cloud-firestore blazor blazor-client-side

尝试在C#(Blazor WASM)中创建FirebaseDb对象后,将其部署到Firebase后会抛出“阻止的混合内容”错误。 有没有办法强迫它使用HTTPS?

错误:

阻止加载混合的活动内容“ http://169.254.169.254/” dotnet.3.2.0-preview3.20168.1.js:1:163131 爆击:Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer [100] blazor.webassembly.js:1:36074 未处理的异常呈现组件:TypeError:尝试获取资源时出现NetworkError。 blazor.webassembly.js:1:36074 WebAssembly.JSException:尝试获取资源时发生TypeError:NetworkError。 blazor.webassembly.js:1:36074 在WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler.doFetch(System.Threading.Tasks.TaskCompletionSource 1[TResult] tcs, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x257c7e0 + 0x00988> in <filename unknown>:0 blazor.webassembly.js:1:36074 at WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x255e648 + 0x00184> in <filename unknown>:0 blazor.webassembly.js:1:36074 at System.Net.Http.HttpClient.FinishSendAsyncBuffered (System.Threading.Tasks.Task 1 [TResult] sendTask,System.Net.Http.HttpRequestMessage请求,System.Threading.CancellationTokenSource cts,System.Boolean disposeCts)<0x256c970 + 0x00278>在:0 blazor.webassembly.js:1:36074 在Google.Apis.Auth.OAuth2.ComputeCredential.IsRunningOnComputeEngineNoCache()<0x24f5570 + 0x0018c>在:0 blazor.webassembly.js:1:36074 在Google.Apis.Auth.OAuth2.DefaultCredentialProvider.CreateDefaultCredentialAsync()<0x24e22f0 + 0x0020e>中的:0 blazor.webassembly.js:1:36074 在:0 blazor.webassembly.js:1:36074中的Google.Api.Gax.Grpc.ChannelPool.CreateChannelCredentialsUncached()<0x24cf210 + 0x000d8> 在Google.Api.Gax.Grpc.ChannelPool.GetChannelAsync(Google.Api.Gax.Grpc.ServiceEndpoint端点,System.Collections.Generic.IEnumerable`1 [T] channelOptions)<0x246fb30 + 0x000f4>在:0 blazor.webassembly中。 js:1:36074 在Google.Cloud.Firestore.V1.FirestoreClient.CreateAsync(Google.Api.Gax.Grpc.ServiceEndpoint端点,Google.Cloud.Firestore.V1.FirestoreSettings设置)<0x246e908 + 0x000ec>在:0 blazor.webassembly.js:1中:36074 在Google.Cloud.Firestore.FirestoreDb.CreateAsync(System.String projectId,Google.Cloud.Firestore.V1.FirestoreClient客户端)<0x2449d00 + 0x001d0>在:0 blazor.webassembly.js:1:36074 在blog.Pages.Index.OnInitializedAsync()<0x2434eb8 + 0x000c8>中:0 blazor.webassembly.js:1:36074 在:0 blazor.webassembly.js:1:36074中的Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()<0x2330b40 + 0x0014c>中 在Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(System.Threading.Tasks.Task taskToHandle)中的<0x247d5b8 + 0x000c2>在:0 blazor.webassembly.js:1:36074

代码:

    protected override async Task OnInitializedAsync()
    {
        string projectId = "my-poject-id";
        FirestoreDb db = await FirestoreDb.CreateAsync(projectId);
    }

2 个答案:

答案 0 :(得分:1)

目前,您需要使用 JS-Interop:https://docs.microsoft.com/en-us/aspnet/core/blazor/call-javascript-from-dotnet

附言正如@swimburger 很好地指出的那样,服务器端 C# 不起作用;如果为 firebase 添加新的客户端 C# 库,这可能会改变。

答案 1 :(得分:0)

TL; DR
Firestore库不应在浏览器上下文中运行。它被开发用于服务器端或可能在管理工具中使用。该库将允许您在Firestore中进行所有操作,并将凭据传送给客户会带来安全风险。


根据您的堆栈跟踪信息,我假设您正在使用Google.Cloud.Firestore库。 不幸的是,该库被设计为“ server client library”。服务器端要使用的客户端库。
这些“服务器客户端库”的设计与“ mobile/web client libraries”不同。
移动/网络库将使用Firebase的身份验证(用户名/密码,Facebook等),并且安全模型将应用于该用户上下文。 服务器库实质上使您可以访问所有内容。

现在,您遇到的问题是Blazor不支持该SDK的原因。 首先,SDK依赖的GOOGLE_APPLICATION_CREDENTIALS环境变量在Blazor应用程序中不可用。 如果可用,则无法从浏览器沙箱中访问该文件。

您可以使用FirestoreDbBuilder并手动设置JsonCredentialsProjectId来规避这两个问题:

var builder = new FirestoreDbBuilder();
builder.JsonCredentials = "{\"type\": \"service_account\", \"project_id\": \"\", \"private_key_id\": \"\", \"private_key\": \"\", \"client_email\": \"\", \"client_id\": \"\", \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\", \"token_uri\": \"https://oauth2.googleapis.com/token\", \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\", \"client_x509_cert_url\": \"\" }";
builder.ProjectId = "";
FirestoreDb db = builder.Build();

这将导致下一个问题,所使用的GRPC库不支持Blazor / .NET WASM,从而导致此堆栈跟踪:

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Unsupported platform.
System.InvalidOperationException: Unsupported platform.
  at Grpc.Core.Internal.NativeExtension.GetNativeLibraryFilename () [0x0003f] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:231 
  at Grpc.Core.Internal.NativeExtension.LoadUnmanagedLibrary () [0x0000a] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:93 
  at Grpc.Core.Internal.NativeExtension.LoadNativeMethods () [0x0001a] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:120 
  at Grpc.Core.Internal.NativeExtension..ctor () [0x00006] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:40 
  at Grpc.Core.Internal.NativeExtension.Get () [0x00022] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:65 
  at Grpc.Core.Internal.NativeMethods.Get () [0x00000] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeMethods.cs:49 
  at Grpc.Core.GrpcEnvironment.GrpcNativeInit () [0x00016] in T:\src\github\grpc\src\csharp\Grpc.Core\GrpcEnvironment.cs:373 
  at Grpc.Core.GrpcEnvironment..ctor () [0x0001e] in T:\src\github\grpc\src\csharp\Grpc.Core\GrpcEnvironment.cs:302 
  at Grpc.Core.GrpcEnvironment.AddRef () [0x00028] in T:\src\github\grpc\src\csharp\Grpc.Core\GrpcEnvironment.cs:78 
  at Grpc.Core.Channel..ctor (System.String target, Grpc.Core.ChannelCredentials credentials, System.Collections.Generic.IEnumerable`1[T] options) [0x00041] in T:\src\github\grpc\src\csharp\Grpc.Core\Channel.cs:70 
  at Google.Api.Gax.Grpc.GrpcCore.GrpcCoreAdapter.CreateChannelImpl (System.String endpoint, Grpc.Core.ChannelCredentials credentials, Google.Api.Gax.Grpc.GrpcChannelOptions options) [0x00000] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc.GrpcCore\GrpcCoreAdapter.cs:34 
  at Google.Api.Gax.Grpc.GrpcAdapter.CreateChannel (System.String endpoint, Grpc.Core.ChannelCredentials credentials, Google.Api.Gax.Grpc.GrpcChannelOptions options) [0x00024] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc\GrpcAdapter.cs:29 
  at Google.Api.Gax.Grpc.ClientBuilderBase`1[TClient].CreateChannel (System.String endpoint, Grpc.Core.ChannelCredentials credentials) [0x00000] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc\ClientBuilderBase.cs:423 
  at Google.Api.Gax.Grpc.ClientBuilderBase`1[TClient].CreateCallInvokerAsync (System.Threading.CancellationToken cancellationToken) [0x00145] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc\ClientBuilderBase.cs:313 
  at Google.Cloud.Firestore.V1.FirestoreClientBuilder.BuildAsyncImpl (System.Threading.CancellationToken cancellationToken) [0x00033] in /_/apis/Google.Cloud.Firestore.V1/Google.Cloud.Firestore.V1/FirestoreClient.g.cs:285 
  at Google.Cloud.Firestore.FirestoreDbBuilder.BuildAsync (System.Threading.CancellationToken cancellationToken) [0x0015a] in /_/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/FirestoreDbBuilder.cs:119 
  at BlazorFirestore.Pages.FetchData.OnInitializedAsync () [0x00061] in C:\Users\niels\source\repos\BlazorFirestore\Pages\FetchData.razor:12 
  at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync () <0x2ff3be8 + 0x0013a> in <filename unknown>:0 

这可以通过使用Grpc.Net.Client.Web来解决,using System; using Grpc.Core; using Google.Api.Gax.Grpc; using Grpc.Net.Client; using System.Net.Http; using Grpc.Net.Client.Web; // you'll need these packages // Google.Apis.Auth, Google.Cloud.Firestore, Grpc.Net.Client, Grpc.Net.Client.Web namespace BlazorFirestore { // most of the code was borrowed from https://github.com/googleapis/gax-dotnet/blob/master/Google.Api.Gax.Grpc.GrpcNetClient/GrpcNetClientAdapter.cs public sealed class GrpcWebAdapter : GrpcAdapter { // this HttpClient using the GrpcWebHandler and mode is crucial to get Grpc to work for Firestore private static HttpClient httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler())); // Note: this is "Default" rather than "Instance" as we expect to have other factory methods later, e.g. accepting // an HTTP client factory. /// <summary> /// Returns the default instance of this class. /// </summary> public static GrpcWebAdapter Default { get; } = new GrpcWebAdapter(); private GrpcWebAdapter() { } /// <inheritdoc /> protected override ChannelBase CreateChannelImpl(string endpoint, ChannelCredentials credentials, Google.Api.Gax.Grpc.GrpcChannelOptions options) { var grpcNetClientOptions = ConvertOptions(credentials, options); var address = ConvertEndpoint(endpoint); return GrpcChannel.ForAddress(address, grpcNetClientOptions); } // Internal for testing internal static global::Grpc.Net.Client.GrpcChannelOptions ConvertOptions(ChannelCredentials credentials, Google.Api.Gax.Grpc.GrpcChannelOptions options) { // If service config resolution is explicitly enabled, throw - we can't support that, // and users may be depending on it. if (options.EnableServiceConfigResolution == true) { throw new ArgumentException($"{nameof(options.EnableServiceConfigResolution)} is not currently supported in {nameof(GrpcWebAdapter)}"); } if (options.CustomOptions.Count > 0) { throw new ArgumentException($"Custom options are not currently supported in {nameof(GrpcWebAdapter)}"); } // Options we ignore: // - PrimaryUserAgent // - KeepAliveTime return new global::Grpc.Net.Client.GrpcChannelOptions { Credentials = credentials, MaxReceiveMessageSize = options.MaxReceiveMessageSize, MaxSendMessageSize = options.MaxSendMessageSize, // pass the GrpcWeb version of the httpclient HttpClient = httpClient }; } // Internal for testing internal static string ConvertEndpoint(string endpoint) => // Note that we assume HTTPS for any bare address; this feels like a reasonable assumption for now. endpoint.StartsWith("http:", StringComparison.Ordinal) || endpoint.StartsWith("https:", StringComparison.Ordinal) ? endpoint : $"https://{endpoint}"; } } 是Blazor内部可运行的.NET的Grpc实现。 创建以下类(受Google's Source启发):

GrpcWebAdapter.Default

现在将FirestoreDbBuilder.GrpcAdapter设置为var builder = new FirestoreDbBuilder(); builder.JsonCredentials = "{\"type\": \"service_account\", \"project_id\": \"\", \"private_key_id\": \"\", \"private_key\": \"\", \"client_email\": \"\", \"client_id\": \"\", \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\", \"token_uri\": \"https://oauth2.googleapis.com/token\", \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\", \"client_x509_cert_url\": \"\" }"; builder.ProjectId = ""; builder.GrpcAdapter = GrpcWebAdapter.Default; FirestoreDb db = builder.Build(); DocumentReference docRef = db.Collection("users").Document("alovelace"); Dictionary<string, object> user = new Dictionary<string, object> { { "First", "Ada" }, { "Last", "Lovelace" }, { "Born", 1815 } }; await docRef.SetAsync(user);

Access to fetch at 'https://firestore.googleapis.com/google.firestore.v1.Firestore/Commit' from origin 'https://localhost:5001' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

dotnet.3.2.0.js:1 POST https://firestore.googleapis.com/google.firestore.v1.Firestore/Commit net::ERR_FAILED

blazor.webassembly.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Status(StatusCode="Internal", Detail="Error starting gRPC call. JSException: TypeError: Failed to fetch", DebugException="WebAssembly.JSException: TypeError: Failed to fetch
        at System.Net.Http.WebAssemblyHttpHandler.doFetch (System.Threading.Tasks.TaskCompletionSource`1[TResult] tcs, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x3c61b60 + 0x00a30> in <filename unknown>:0 
        at System.Net.Http.WebAssemblyHttpHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x3c24040 + 0x00174> in <filename unknown>:0 
        at Grpc.Net.Client.Web.GrpcWebHandler.SendAsyncCore (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x00092] in /_/src/Grpc.Net.Client.Web/GrpcWebHandler.cs:137 
        at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered (System.Threading.Tasks.Task`1[TResult] sendTask, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationTokenSource cts, System.Boolean disposeCts) <0x3d9ae50 + 0x00134> in <filename unknown>:0 
        at Grpc.Net.Client.Internal.GrpcCall`2[TRequest,TResponse].RunCall (System.Net.Http.HttpRequestMessage request, System.Nullable`1[T] timeout) [0x0020c] in /_/src/Grpc.Net.Client/Internal/GrpcCall.cs:477 ")
Grpc.Core.RpcException: Status(StatusCode="Internal", Detail="Error starting gRPC call. JSException: TypeError: Failed to fetch", DebugException="WebAssembly.JSException: TypeError: Failed to fetch
  at System.Net.Http.WebAssemblyHttpHandler.doFetch (System.Threading.Tasks.TaskCompletionSource`1[TResult] tcs, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x3c61b60 + 0x00a30> in <filename unknown>:0 
  at System.Net.Http.WebAssemblyHttpHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x3c24040 + 0x00174> in <filename unknown>:0 
  at Grpc.Net.Client.Web.GrpcWebHandler.SendAsyncCore (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x00092] in /_/src/Grpc.Net.Client.Web/GrpcWebHandler.cs:137 
  at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered (System.Threading.Tasks.Task`1[TResult] sendTask, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationTokenSource cts, System.Boolean disposeCts) <0x3d9ae50 + 0x00134> in <filename unknown>:0 
  at Grpc.Net.Client.Internal.GrpcCall`2[TRequest,TResponse].RunCall (System.Net.Http.HttpRequestMessage request, System.Nullable`1[T] timeout) [0x0020c] in /_/src/Grpc.Net.Client/Internal/GrpcCall.cs:477 ")
  at Google.Api.Gax.Grpc.ApiCallRetryExtensions+<>c__DisplayClass0_0`2[TRequest,TResponse].<WithRetry>b__0 (TRequest request, Google.Api.Gax.Grpc.CallSettings callSettings) [0x00051] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc\ApiCallRetryExtensions.cs:27 
  at Google.Cloud.Firestore.WriteBatch.CommitAsync (Google.Protobuf.ByteString transactionId, System.Threading.CancellationToken cancellationToken) [0x000b5] in /_/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/WriteBatch.cs:231 
  at Google.Cloud.Firestore.DocumentReference.SetAsync (System.Object documentData, Google.Cloud.Firestore.SetOptions options, System.Threading.CancellationToken cancellationToken) [0x0004b] in /_/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/DocumentReference.cs:181 
  at BlazorFirestore.Pages.FetchData.OnInitializedAsync () [0x00166] in C:\Users\niels\source\repos\BlazorFirestore\Pages\FetchData.razor:21 
  at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync () <0x2ff3be8 + 0x0013a> in <filename unknown>:0 
  at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask (System.Threading.Tasks.Task taskToHandle) <0x3905a50 + 0x000b6> in <filename unknown>:0 

我们距离更近了一步,但是现在我们遇到了CORS安全问题,因为这种与Firestore的通信方式并非旨在通过浏览器运行,并且无法配置与此SDK结合使用的CORS。 这些是导致的错误:

 .\chrome.exe  --disable-web-security

当您使用Grpc.Net.Client.Web在浏览器中明确禁用CORS时,结果将是成功的身份验证,但实际的API将返回404。
为什么我会返回404?我可以用Fiddler嗅探 window.addEventListener("load", function () { //checking is the user is connected to the internet and show content respectively if (navigator.onLine) { // NASA API fetch( `https://api.nasa.gov/planetary/apod?api_key=MY_KEY` ) .then((response) => response.json()) .then((data) => { document.body.style.backgroundImage = `url("${data.url}")`; }) .catch((err) => console.log("Something's wrong with NASA API")); } //when the user is offline else { /*THE CODE GOES HERE*/ } 版本,但是默认实现不会被Fiddler拦截。 网络版本似乎使用了不支持的协议/传输或其他某种协议。希望有人可以解决下一个问题。

结论 :. net的Firestore库不应在浏览器沙箱中使用,也不被支持。