5
votes

Comment puis-je obtenir un rappel Javascript dans .Net Blazor?

Existe-t-il un moyen d'ajouter un rappel à Javascript et d'obtenir le résultat dans Blazor? En dehors des promesses de JS.

par exemple, disons que je veux charger un fichier

Code Javascript

    // read with javascript
    void ReadFileContent() {
        JsRuntime.InvokeAsync<object>("readFile", "file.txt", "resultCallbackMethod");
    }

    // output result callback to console
    void resultCallbackMethod(string text) {
        Console.Write(text);
    }

Puis-je avoir quelque chose comme ça dans Blazor C #

    // read file content and output result to console
    void GetFileContent() {
        JsRuntime.InvokeAsync<object>("readFile", "file.txt", (string text) => {
            Console.Write(text);
        });
    }

Ou peut-être quelque chose comme ça

window.readFile = function(filePath, callBack) {
    var reader = new FileReader();
    reader.onload = function (evt) {
        callBack(evt.target.result);
    };
    reader.readAsText(filePath);
}

Merci


2 commentaires

Je ne sais pas pour le rappel, mais ne pouvez-vous pas utiliser une promesse à la place?


je peux, mais j'ai ce groupe de codes avec rappel uniquement.


4 Réponses :


1
votes

Je crois que vous recherchez les informations sur la documentation ici: https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interop?view=aspnetcore-3.0#invoke-net-methods- from-javascript-functions

Il montre comment appeler le Razor.Net à partir de Javascript. La documentation contient plus d'informations, mais vous aurez essentiellement besoin de l'attribut [JSInvokable] sur la méthode dans razor et de l'appel via DotNet.invokeMethod en javascript.


1 commentaires

Merci, mais pas vraiment, juste un rappel direct. Quelque chose comme passer une fermeture de rappel de résultat à JsRuntime.InvokeAsync directement.



4
votes

MISE À JOUR 1:

Après avoir relu votre question, je pense que cela couvrirait votre deuxième exemple

Je pense que vous avez la possibilité d'implémenter une fonction proxy JS qui gère l'appel. Quelque chose comme ça:

MISE À JOUR 2:

Le code a été mis à jour avec une version fonctionnelle (mais pas profondément testée), vous pouvez également trouver un exemple fonctionnel sur blazorfiddle.com

CODE JAVASCRIPT

@page "/"

@inject IJSRuntime jsRuntime

<div>
    Select a text file:
    <input type="file" id="fileInput" @onchange="@ReadFileContent" />
</div>
<pre>
    @fileContent
</pre>

Welcome to your new app.

@code{

    private string fileContent { get; set; }

    public static object CreateDotNetObjectRefSyncObj = new object();

    public async Task ReadFileContent(UIChangeEventArgs ea)
    {
        // Fire & Forget: ConfigureAwait(false) is telling "I'm not expecting this call to return a thing"
        await jsRuntime.InvokeAsync<object>("readFileProxy", CreateDotNetObjectRef(this), "ReadFileCallback", ea.Value.ToString()).ConfigureAwait(false);
    }


    [JSInvokable] // This is required in order to JS be able to execute it
    public void ReadFileCallback(string response)
    {
        fileContent = response?.ToString();
        StateHasChanged();
    }

    // Hack to fix https://github.com/aspnet/AspNetCore/issues/11159    
    protected DotNetObjectRef<T> CreateDotNetObjectRef<T>(T value) where T : class
    {
        lock (CreateDotNetObjectRefSyncObj)
        {
            JSRuntime.SetCurrentJSRuntime(jsRuntime);
            return DotNetObjectRef.Create(value);
        }
    }

}

CODE C #

// Target Javascript function
window.readFile = function (filePath, callBack) {

    var fileInput = document.getElementById('fileInput');
    var file = fileInput.files[0];

    var reader = new FileReader();

    reader.onload = function (evt) {
        callBack(evt.target.result);
    };

    reader.readAsText(file);

}

// Proxy function
// blazorInstance: A reference to the actual C# class instance, required to invoke C# methods inside it
// blazorCallbackName: parameter that will get the name of the C# method used as callback
window.readFileProxy = (instance, callbackMethod, fileName) => {

    // Execute function that will do the actual job
    window.readFile(fileName, result => {
        // Invoke the C# callback method passing the result as parameter
        instance.invokeMethodAsync(callbackMethod, result);
    });

}


2 commentaires

Vous avez essayé bro mais cela n'a pas fonctionné, Buh je suis sûr qu'avec peu de modifications, cela devrait. Je cherche toujours quelque chose de plus intelligent. Peut-être avec un délégué ou d'autres moyens.


@ samtax01, j'ai mis à jour ma réponse: le pseudo-code a été supprimé et remplacé par du code de travail, un lien vers un exemple de travail a également été inclus.



0
votes

Merci pour ça @Henry Rodriguez. J'en ai créé quelque chose et je pensais que cela pourrait aussi être utile.

Notez que DotNetObjectRef.Create (this) fonctionne toujours correctement dans une autre méthode. Il est uniquement noté qu'il y a un problème avec les événements du cycle de vie Blazor dans l'aperçu6. https://github.com/aspnet/AspNetCore/issues/11159 .

Ceci est ma nouvelle implémentation.

// Proxy function that serves as middlemen
 window.callbackProxy =  function(dotNetInstance, callMethod, param, callbackMethod){
    // Execute function that will do the actual job
    window[callMethod](param, function(result){
          // Invoke the C# callback method passing the result as parameter
           return dotNetInstance.invokeMethodAsync(callbackMethod, result);
     });
     return true;
 };



// Then The Javascript function too

 window.readFile = function(filePath, callBack) {
    var reader = new FileReader();
    reader.onload = function (evt) {
        callBack(evt.target.result);
    };
    reader.readAsText(filePath);
}

Et dans blazor _Host.cshtml ou index.html, incluez le connecteur de proxy de rappel

<div>
    Load the file content
    <button @click="@ReadFileContent">Get File Content</button>
</div>

<pre>
    @fileContent
</pre>

Welcome to your new app.

@code{
string fileContent;

//The button onclick will call this.
void GetFileContent() {
     JsRuntime.InvokeAsync<object>("callbackProxy", DotNetObjectRef.Create(this), "readFile", "file.txt", "ReadFileCallback");
}


//and this is the ReadFileCallback

[JSInvokable] // This is required for callable function in JS
public void ReadFileCallback(string filedata) {
    fileContent = filedata;
    StateHasChanged();
}

Cela fonctionne parfaitement pour ce dont j'avais besoin et il est réutilisable.


0 commentaires

0
votes

En utilisant les astuces de cette page, j'ai proposé une version plus générique qui fonctionne avec presque toutes les fonctions basées sur le rappel.

Mise à jour:

Vous pouvez maintenant appeler n'importe quelle fonction dont le dernier argument est un rappel. Vous pouvez transmettre n'importe quel nombre d'arguments à la fonction et le rappel peut avoir n'importe quel nombre d'arguments renvoyés.

La fonction InvokeJS renvoie une instance de CallbackerResponse qui peut être utilisée pour obtenir la valeur typée de l'un des arguments de réponse. Voir des exemples et du code pour plus d'informations.

Basé sur le rappel OP (fileContents (string)):

Exemple 1 (C # Blazor avec await):

window._callbacker = function(callbackObjectInstance, callbackMethod, callbackId, cmd, args){
    var parts = cmd.split('.');
    var targetFunc = window;
    var parentObject = window;
    for(var i = 0; i < parts.length; i++){
        if (i == 0 && part == 'window') continue;
        var part = parts[i];
        parentObject = targetFunc;
        targetFunc = targetFunc[part];
    }
    args = JSON.parse(args);
    args.push(function(e, d){ 
        var args = [];
        for(var i in arguments) args.push(JSON.stringify(arguments[i]));
        callbackObjectInstance.invokeMethodAsync(callbackMethod, callbackId, args); 
    });
    targetFunc.apply(parentObject, args);
};

Exemple 2 (C # Blazor avec rappel):

using Microsoft.JSInterop;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Home.Services
{
    public class CallbackerResponse
    {
        public string[] arguments { get; private set; }
        public CallbackerResponse(string[] arguments)
        {
            this.arguments = arguments;
        }
        public T GetArg<T>(int i)
        {
            return JsonConvert.DeserializeObject<T>(arguments[i]);
        }
    }

    public class Callbacker
    {
        private IJSRuntime _js = null;
        private DotNetObjectReference<Callbacker> _this = null;
        private Dictionary<string, Action<string[]>> _callbacks = new Dictionary<string, Action<string[]>>();

        public Callbacker(IJSRuntime JSRuntime)
        {
            _js = JSRuntime;
            _this = DotNetObjectReference.Create(this);
        }

        [JSInvokable]
        public void _Callback(string callbackId, string[] arguments)
        {
            if (_callbacks.TryGetValue(callbackId, out Action<string[]> callback))
            {
                _callbacks.Remove(callbackId);
                callback(arguments);
            }
        }

        public Task<CallbackerResponse> InvokeJS(string cmd, params object[] args)
        {
            var t = new TaskCompletionSource<CallbackerResponse>();
            _InvokeJS((string[] arguments) => {
                t.TrySetResult(new CallbackerResponse(arguments));
            }, cmd, args);
            return t.Task;
        }

        public void InvokeJS(Action<CallbackerResponse> callback, string cmd, params object[] args)
        {
            _InvokeJS((string[] arguments) => {
                callback(new CallbackerResponse(arguments));
            }, cmd, args);
        }

        private void _InvokeJS(Action<string[]> callback, string cmd, object[] args)
        {
            string callbackId;
            do
            {
                callbackId = Guid.NewGuid().ToString();
            } while (_callbacks.ContainsKey(callbackId));
            _callbacks[callbackId] = callback;
            _js.InvokeVoidAsync("window._callbacker", _this, "_Callback", callbackId, cmd, JsonConvert.SerializeObject(args));
        }
    }
}

Basé sur un rappel commun (erreur (chaîne), données (objet)):

Exemple 3 (C # Blazor avec await):

[Inject]
public Callbacker Callbacker { get; set; }

Dans votre Blazor Program.cs Main, ajoutez un singleton (ou une portée si vous le souhaitez) Callbacker

builder.Services.AddSingleton<Services.Callbacker>();

Ajoutez le service Callbacker dans votre page Blazor. Exemple: MyPage.razor.cs

// To call a javascript function with the arguments (arg1, arg2, arg3, callback)
// and where the callback arguments are (err, data)
var response = await Callbacker.InvokeJS("window.myObject.myFunction", arg1, arg2, arg3);
// deserialize callback argument 0 into C# string
var err = response.GetArg<string>(0);
// deserialize callback argument 1 into C# object
var data = response.GetArg<MyObjectType>(1);

C #

Callbacker.InvokeJS((response) => { 
    var fileContents = response.GetArg<string>(0);
    // fileContents available here
}, "window.readFile", filename);

JS

var response = await Callbacker.InvokeJS("window.readFile", filename);
var fileContents = response.GetArg<string>(0);
// fileContents available here


0 commentaires