Вот мой файл MainActivity.cs:
Код: Выделить всё
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
}
Код: Выделить всё
[Application]
public class MainApplication : MauiApplication
{
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership)
{
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}
Код: Выделить всё
internal class PermissionManagingBlazorWebChromeClient : WebChromeClient, IActivityResultCallback
{
// This class implements a permission requesting workflow that matches workflow recommended
// by the official Android developer documentation.
// See: https://developer.android.com/training/permissions/requesting#workflow_for_requesting_permissions
// The current implementation supports location, camera, and microphone permissions. To add your own,
// update the s_rationalesByPermission dictionary to include your rationale for requiring the permission.
// If necessary, you may need to also update s_requiredPermissionsByWebkitResource to define how a specific
// Webkit resource maps to an Android permission.
// In a real app, you would probably use more convincing rationales tailored toward what your app does.
private const string CameraAccessRationale = "This app requires access to your camera. Please grant access to your camera when requested.";
private const string LocationAccessRationale = "This app requires access to your location. Please grant access to your precise location when requested.";
private const string MicrophoneAccessRationale = "This app requires access to your microphone. Please grant access to your microphone when requested.";
private static readonly Dictionary s_rationalesByPermission = new()
{
[Manifest.Permission.Camera] = CameraAccessRationale,
[Manifest.Permission.AccessFineLocation] = LocationAccessRationale,
[Manifest.Permission.RecordAudio] = MicrophoneAccessRationale,
// Add more rationales as you add more supported permissions.
};
private static readonly Dictionary s_requiredPermissionsByWebkitResource = new()
{
[PermissionRequest.ResourceVideoCapture] = new[] { Manifest.Permission.Camera },
[PermissionRequest.ResourceAudioCapture] = new[] { Manifest.Permission.ModifyAudioSettings, Manifest.Permission.RecordAudio },
// Add more Webkit resource -> Android permission mappings as needed.
};
private readonly WebChromeClient _blazorWebChromeClient;
private readonly ComponentActivity _activity;
private readonly ActivityResultLauncher _requestPermissionLauncher;
private Action? _pendingPermissionRequestCallback;
public PermissionManagingBlazorWebChromeClient(WebChromeClient blazorWebChromeClient, ComponentActivity activity)
{
_blazorWebChromeClient = blazorWebChromeClient;
_activity = activity;
_requestPermissionLauncher = _activity.RegisterForActivityResult(new ActivityResultContracts.RequestPermission(), this);
}
public override void OnCloseWindow(WebView? window)
{
_blazorWebChromeClient.OnCloseWindow(window);
_requestPermissionLauncher.Unregister();
}
public override void OnGeolocationPermissionsShowPrompt(string? origin, GeolocationPermissions.ICallback? callback)
{
ArgumentNullException.ThrowIfNull(callback, nameof(callback));
RequestPermission(Manifest.Permission.AccessFineLocation, isGranted => callback.Invoke(origin, isGranted, false));
}
public override void OnPermissionRequest(PermissionRequest? request)
{
ArgumentNullException.ThrowIfNull(request, nameof(request));
if (request.GetResources() is not { } requestedResources)
{
request.Deny();
return;
}
RequestAllResources(requestedResources, grantedResources =>
{
if (grantedResources.Count == 0)
{
request.Deny();
}
else
{
request.Grant(grantedResources.ToArray());
}
});
}
private void RequestAllResources(Memory requestedResources, Action callback)
{
if (requestedResources.Length == 0)
{
// No resources to request - invoke the callback with an empty list.
callback(new());
return;
}
var currentResource = requestedResources.Span[0];
var requiredPermissions = s_requiredPermissionsByWebkitResource.GetValueOrDefault(currentResource, Array.Empty());
RequestAllPermissions(requiredPermissions, isGranted =>
{
// Recurse with the remaining resources. If the first resource was granted, use a modified callback
// that adds the first resource to the granted resources list.
RequestAllResources(requestedResources[1..], !isGranted ? callback : grantedResources =>
{
grantedResources.Add(currentResource);
callback(grantedResources);
});
});
}
private void RequestAllPermissions(Memory requiredPermissions, Action callback)
{
if (requiredPermissions.Length == 0)
{
// No permissions left to request - success!
callback(true);
return;
}
RequestPermission(requiredPermissions.Span[0], isGranted =>
{
if (isGranted)
{
// Recurse with the remaining permissions.
RequestAllPermissions(requiredPermissions[1..], callback);
}
else
{
// The first required permission was not granted. Fail now and don't attempt to grant
// the remaining permissions.
callback(false);
}
});
}
private void RequestPermission(string permission, Action callback)
{
// This method implements the workflow described here:
// https://developer.android.com/training/permissions/requesting#workflow_for_requesting_permissions
if (ContextCompat.CheckSelfPermission(_activity, permission) == Permission.Granted)
{
callback.Invoke(true);
}
else if (_activity.ShouldShowRequestPermissionRationale(permission) && s_rationalesByPermission.TryGetValue(permission, out var rationale))
{
new AlertDialog.Builder(_activity)
.SetTitle("Enable app permissions")!
.SetMessage(rationale)!
.SetNegativeButton("No thanks", (_, _) => callback(false))!
.SetPositiveButton("Continue", (_, _) => LaunchPermissionRequestActivity(permission, callback))!
.Show();
}
else
{
LaunchPermissionRequestActivity(permission, callback);
}
}
private void LaunchPermissionRequestActivity(string permission, Action callback)
{
if (_pendingPermissionRequestCallback is not null)
{
throw new InvalidOperationException("Cannot perform multiple permission requests simultaneously.");
}
_pendingPermissionRequestCallback = callback;
_requestPermissionLauncher.Launch(permission);
}
void IActivityResultCallback.OnActivityResult(Java.Lang.Object isGranted)
{
var callback = _pendingPermissionRequestCallback;
_pendingPermissionRequestCallback = null;
callback?.Invoke((bool)isGranted);
}
#region Unremarkable overrides
// See: https://github.com/dotnet/maui/issues/6565
public override JniPeerMembers JniPeerMembers => _blazorWebChromeClient.JniPeerMembers;
public override Bitmap? DefaultVideoPoster => _blazorWebChromeClient.DefaultVideoPoster;
public override View? VideoLoadingProgressView => _blazorWebChromeClient.VideoLoadingProgressView;
public override void GetVisitedHistory(IValueCallback? callback)
=> _blazorWebChromeClient.GetVisitedHistory(callback);
public override bool OnConsoleMessage(ConsoleMessage? consoleMessage)
=> _blazorWebChromeClient.OnConsoleMessage(consoleMessage);
public override bool OnCreateWindow(WebView? view, bool isDialog, bool isUserGesture, Message? resultMsg)
=> _blazorWebChromeClient.OnCreateWindow(view, isDialog, isUserGesture, resultMsg);
public override void OnGeolocationPermissionsHidePrompt()
=> _blazorWebChromeClient.OnGeolocationPermissionsHidePrompt();
public override void OnHideCustomView()
=> _blazorWebChromeClient.OnHideCustomView();
public override bool OnJsAlert(WebView? view, string? url, string? message, JsResult? result)
=> _blazorWebChromeClient.OnJsAlert(view, url, message, result);
public override bool OnJsBeforeUnload(WebView? view, string? url, string? message, JsResult? result)
=> _blazorWebChromeClient.OnJsBeforeUnload(view, url, message, result);
public override bool OnJsConfirm(WebView? view, string? url, string? message, JsResult? result)
=> _blazorWebChromeClient.OnJsConfirm(view, url, message, result);
public override bool OnJsPrompt(WebView? view, string? url, string? message, string? defaultValue, JsPromptResult? result)
=> _blazorWebChromeClient.OnJsPrompt(view, url, message, defaultValue, result);
public override void OnPermissionRequestCanceled(PermissionRequest? request)
=> _blazorWebChromeClient.OnPermissionRequestCanceled(request);
public override void OnProgressChanged(WebView? view, int newProgress)
=> _blazorWebChromeClient.OnProgressChanged(view, newProgress);
public override void OnReceivedIcon(WebView? view, Bitmap? icon)
=> _blazorWebChromeClient.OnReceivedIcon(view, icon);
public override void OnReceivedTitle(WebView? view, string? title)
=> _blazorWebChromeClient.OnReceivedTitle(view, title);
public override void OnReceivedTouchIconUrl(WebView? view, string? url, bool precomposed)
=> _blazorWebChromeClient.OnReceivedTouchIconUrl(view, url, precomposed);
public override void OnRequestFocus(WebView? view)
=> _blazorWebChromeClient.OnRequestFocus(view);
public override void OnShowCustomView(View? view, ICustomViewCallback? callback)
=> _blazorWebChromeClient.OnShowCustomView(view, callback);
public override bool OnShowFileChooser(WebView? webView, IValueCallback? filePathCallback, FileChooserParams? fileChooserParams)
=> _blazorWebChromeClient.OnShowFileChooser(webView, filePathCallback, fileChooserParams);
#endregion
}
Код: Выделить всё
Код: Выделить всё
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
blazorWebView.BlazorWebViewInitializing += BlazorWebViewInitializing;
blazorWebView.BlazorWebViewInitialized += BlazorWebViewInitialized;
}
private partial void BlazorWebViewInitializing(object? sender, BlazorWebViewInitializingEventArgs e);
private partial void BlazorWebViewInitialized(object? sender, BlazorWebViewInitializedEventArgs e);
}
Код: Выделить всё
public partial class MainPage
{
// To manage Android permissions, update AndroidManifest.xml to include the permissions and
// features required by your app. You may have to perform additional configuration to enable
// use of those APIs from the WebView, as is done below. A custom WebChromeClient is needed
// to define what happens when the WebView requests a set of permissions. See
// PermissionManagingBlazorWebChromeClient.cs to explore the approach taken in this example.
private partial void BlazorWebViewInitializing(object? sender, BlazorWebViewInitializingEventArgs e)
{
}
private partial void BlazorWebViewInitialized(object? sender, BlazorWebViewInitializedEventArgs e)
{
if (e.WebView.Context?.GetActivity() is not ComponentActivity activity)
{
throw new InvalidOperationException($"The permission-managing WebChromeClient requires that the current activity be a '{nameof(ComponentActivity)}'.");
}
e.WebView.Settings.JavaScriptEnabled = true;
e.WebView.Settings.AllowContentAccess = true;
e.WebView.Settings.AllowFileAccess = true;
e.WebView.Settings.AllowFileAccessFromFileURLs = true;
e.WebView.Settings.AllowUniversalAccessFromFileURLs = true;
e.WebView.Settings.DomStorageEnabled = true;
e.WebView.Settings.DatabaseEnabled = true;
e.WebView.Settings.MediaPlaybackRequiresUserGesture = false;
e.WebView.Settings.SetGeolocationEnabled(true);
e.WebView.Settings.SetGeolocationDatabasePath(e.WebView.Context?.FilesDir?.Path);
e.WebView.SetWebChromeClient(new PermissionManagingBlazorWebChromeClient(e.WebView.WebChromeClient!, activity));
}
}
Код: Выделить всё
Код: Выделить всё
var api = app.MapGroup("/api");
api.MapGet("/documents", async ([FromServices] IMainDataService data, [FromServices] HttpContextAccessor context, [FromServices] UserManager userManager, int range, int selectedItemId) =>
{
var results = await data.GetAllDocumentsInRangeAsync(range, selectedItemId);
//context.HttpContext.Response.Headers.AccessControlAllowOrigin = "*";
return Results.Ok(results);
}).RequireAuthorization();
В проекте Shared у меня есть общая страница бритвы «Home.razor», из которой у меня есть фрагмент кода jQuery для получения некоторых данных из веб-проекта.
Это простой $.get следующим образом:
Код: Выделить всё
var requestData = [];
$.get(baseUrl + '/api/documents', {
"range": distance,
"selectedItemId": selectedItemId
})
.done(function (data) {
console.log("DOCUMENTS RECEIVED");
$.each(data, function (index, value) {
var reqData = {
name: value.name,
displayIndex: value.displayIndex,
itemId: value.id
};
requestData.push(reqData);
console.log("Added " + reqData.name + " to list of documents...");
});
});
Но когда я запускаю его из мобильного проекта (Android), я получаю странное поведение. Первоначально говорилось, что есть проблемы с CORS. Что я решил с помощью AddCors() в веб-проекте/Program.cs:
Код: Выделить всё
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
policy =>
{
policy.AllowAnyHeader();
policy.SetIsOriginAllowedToAllowWildcardSubdomains();
policy.AllowAnyMethod();
policy.AllowAnyOrigin();
policy.SetPreflightMaxAge(TimeSpan.FromSeconds(3600));
});
});
...
app.UseCors();
Код: Выделить всё
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewHandler: Debug: Intercepting request for https://localhost:7087/api/documents?range=10&selectedItemId=1.
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewHandler: Debug: Handling web request to URI 'https://localhost:7087/api/documents'.
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewHandler: Debug: Response content was not found for web request to URI 'https://localhost:7087/api/documents'.

Нет ни кода состояния HTTP, ни заголовков ответа.
В политике CORS я пытался указать Origins или использовать метод AllowAnyOrigin(), я получаю тот же результат.
Я использую VS2026, .NET 10 и jQuery 4.0.0 мин:
Код: Выделить всё