Blazor’da Js Interop’a Geri Arama Nasıl Gönderilir

In: Genel


Blazor istemci tarafı veya sunucu tarafı yalnızca CPU’ya bağlı hesaplamayı işleyebilir. Tarayıcı ile her etkileşim için JSInterop kullanmanız gerekir. Blazor’da bile ekip, XHR isteği veya DOM güncellemesi gibi tarayıcı API’sini kullanmak için JSInterop’u kullanır.

Şu anda JS Interop API’sinde yalnızca bir şey yapabilirsiniz: “pencere” nesnesine bağlı belirli bir yöntemi 3 tür parametre ile çağırın:

  • C# nesnesi System.Text.Json aracılığıyla serileştirildi ve JSON.parse aracılığıyla seri hale getirildi
  • Html öğesi (C# tarafında ElementReference)
  • Daha sonra “JSInvokable” ile açıklamalı herhangi bir yöntemi çağırabileceğiniz C# nesnesine başvuru

Peki ya bir geri arama yöntemini iletmek? window.navigator.connection.onchange gibi bir etkinliğe kaydolmak istiyorsanız bu yararlı olacaktır. Blazor olduğu gibi, bunu yapabilirsiniz, ancak biraz sıhhi tesisat yapmanız gerekir ve kullanmak istediğiniz her farklı geri arama için bunu yapmanız gerekir. Bu yazıda size bunu daha tekrar kullanılabilir bir şekilde nasıl yapacağınızı göstereceğim.

json canlandırıcı

Daha önce söylediğim gibi Blazor JSInterop, .net çalışma zamanı tarafından gönderilen bir JSON dizesinden bir nesne oluşturmak için JSON.parse’ı kullanır. ElementReference veya DotNetObjectReference bile JSON’da serileştirilir ve bu yönteme gönderilir, işte bunu 217 satırında yapan kod :

 function parseJsonWithRevivers(json: string): any {
    return json ? JSON.parse(json, (key, initialValue) => {
      // Invoke each reviver in order, passing the output from the previous reviver,
      // so that each one gets a chance to transform the value
      return jsonRevivers.reduce(
        (latestValue, reviver) => reviver(key, latestValue),
        initialValue
      );
    }) : null;
  }

JSON.parse json dizesinin her alanı ve her nesnesi için çağrılacak bir reviver parametresini kabul eder. Örneğin bu js kodu için

const json = '{"result":true, "inner": {"count":42}}';
const obj = JSON.parse(json,(k,v) => {console.log(k,v); return v;} );

Konsol bunu çıkaracak

“sonuç” doğru
“saymak” 42
“iç” Nesne { sayı: 42 }
”” Nesne { sonuç: doğru, iç: Nesne { sayı: 42 } }

Canlandırıcı, en iç içe geçmiş özellikten nesnenin köküne kadar çağrılır. Bununla, ASPNET Core ekibi, serileştirilmiş bir ElementReference öğesini gerçek DOM öğesine değiştirmek için bu reviver’ı üretti (dosya) :

export function applyCaptureIdToElement(element: Element, referenceCaptureId: string) {
  element.setAttribute(getCaptureIdAttributeName(referenceCaptureId), '');
}

function getElementByCaptureId(referenceCaptureId: string) {
  const selector = `[${getCaptureIdAttributeName(referenceCaptureId)}]`;
  return document.querySelector(selector);
}

function getCaptureIdAttributeName(referenceCaptureId: string) {
  return `_bl_${referenceCaptureId}`;
}

// Support receiving ElementRef instances as args in interop calls
const elementRefKey = '__internalId'; // Keep in sync with ElementRef.cs
DotNet.attachReviver((key, value) => {
  if (value && typeof value === 'object' && value.hasOwnProperty(elementRefKey) && typeof value[elementRefKey] === 'string') {
    return getElementByCaptureId(value[elementRefKey]);
  } else {
    return value;
  }
});

Blazor’da bir öğeye @ref eklediğinizde, HTML öğesine bir “_bl_NUMBER” kimliği ekler. Sayı, her başvuru veya GUID için artan bir statik int’den başlatılarak ElementReference yapısının içinde saklanır. Bu iş yapılır bu dosya :

    public readonly struct ElementReference
    {
        private static long _nextIdForWebAssemblyOnly = 1;

        public string Id { get; }

        public ElementReference(string id)
        {
            Id = id;
        }

        internal static ElementReference CreateWithUniqueId()
            => new ElementReference(CreateUniqueId());

        private static string CreateUniqueId()
        {
            if (PlatformInfo.IsWebAssembly)
            {
                var id = Interlocked.Increment(ref _nextIdForWebAssemblyOnly);
                return id.ToString(CultureInfo.InvariantCulture);
            }
            else
            {
                return Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture);
            }
        }
    }

Ayrıca, Id özelliğini ayarlayıcı olmadan tutmak için bu tür için bir JsonConverter oluşturdular.

Özel Reviver ve Func sarıcı

Şimdi ElementReference için nasıl yaptıklarını anlıyoruz, bunu Func için yapmayı deneyebiliriz. 2 şeye ihtiyacımız var:

  • Func’ıma bir referans tutacak AC# sarıcı ve onu çağıracak bir JSInvokable yöntemi
  • Gönderilen nesnenin bir Func sarmalayıcı olduğunu algılayan ve C# yöntemini çağıran bir JS canlandırıcı.

İşte benim C# sarmalayıcım:

public class CallBackInteropWrapper
{
    [JsonPropertyName("__isCallBackWrapper")]
    public string IsCallBackWrapper { get; set; } = "";

    private CallBackInteropWrapper()
    {

    }
    public static CallBackInteropWrapper Create<T>(Func<T, Task> callback)
    {
        var res = new CallBackInteropWrapper
        {
            CallbackRef = DotNetObjectReference.Create(new JSInteropActionWrapper<T>(callback))
        };
        return res;
    }

    public static CallBackInteropWrapper Create(Func<Task> callback)
    {
        var res = new CallBackInteropWrapper
        {
            CallbackRef = DotNetObjectReference.Create(new JSInteropActionWrapper(callback))
        };
        return res;
    }

    public object CallbackRef { get; set; }


    private class JSInteropActionWrapper
    {
        private readonly Func<Task> toDo;

        internal JSInteropActionWrapper(Func<Task> toDo)
        {
            this.toDo = toDo;
        }
        [JSInvokable]
        public async Task Invoke()
        {
            await toDo.Invoke();
        }
    }

    private class JSInteropActionWrapper<T>
    {
        private readonly Func<T, Task> toDo;

        internal JSInteropActionWrapper(Func<T, Task> toDo)
        {
            this.toDo = toDo;
        }
        [JSInvokable]
        public async Task Invoke(T arg1)
        {
            await toDo.Invoke(arg1);
        }
    }
}
  • Bunu 2 sarmalayıcı sınıfında yapmam gerekiyor: biri “bu bir func sarmalayıcıdır” bilgisini tutmak için ve diğeri JSInvokable yöntemini tutmak için.
  • 2 değişken oluşturdum: Biri Func’ın bir argümanı kabul ettiği ve diğeri olmadığı yerde, gerekirse, ele almak istediğim her Func türü için bir tane oluşturmam gerekecek
  • Geri aramanın eşzamansız olabilmesi için Func kullanıyorum. Async olmayan geri arama için de aşırı yük oluşturabilirim ama bu çok fazla gürültü olur.
  • Alanlarım gerçekten iyi olmayan alıcı ve ayarlayıcıya sahiptir, ancak System.Text.Json, kendi JsonConverter’ınızı oluşturmadığınız sürece bunları özel hale getirmenin kolay bir yolunu sağlamaz. Bunu bir lib’de düzeltmenin en iyi yolu, türü dahili olarak işaretlemek ve onu bir arayüz olarak göstermektir.

İşte js’deki canlandırıcı

DotNet.attachReviver(function (key, value) {
    if (value &&
        typeof value === 'object' &&
        value.hasOwnProperty("__isCallBackWrapper")) {

        var netObjectRef = value.callbackRef;

        return function () {            
            netObjectRef.invokeMethodAsync('Invoke', ...arguments);
        };
    } else {
        return value;
    }
});
  • DotNet.attachReviver, JSInterop js kitaplığının bir yöntemidir.
  • “…argümanlar”, tüm geri arama parametrelerini bir parametre dizisi yerine “Invoke” yöntem çağrısına ardışık argüman olarak göndereceğim anlamına gelir.

Bunu kullanmak için bu js işlevini ilan ediyorum

function testCallback(callback){
    if(confirm('are you sure ?')){
        callback("test");
    }
}

Ve .net tarafında böyle adlandırın

private string callBackResult
protected override async Task OnInitializedAsync()
{
    await jsRuntime.InvokeVoidAsync("testCallback", CallBackInteropWrapper.Create<string>(async s => {
        this.callBackResult = s;
        this.StateHasChanged();
        await Task.Completed;
    }));
}

TarayıcıInterop

JSInterop’suz Blazor biraz zor çünkü sık sık bazı tarayıcı API’lerini kullanmanız gerekiyor: yeni bir pencere açın, coğrafi konum belirlemeyi alın, pil seviyesini alın vb.TarayıcıInterop” bu, tarayıcı API’si ile ilgili tüm JS Interop çağrısını sarar. şuraya bir göz atabilirsin GitHub deposu ne demek istediğimi anlamak için.

Bu kitaplığın geliştirilmesi sırasında pencere olay işleme (“onclose” veya “connection.onchange” gibi) uygulamam gerekiyordu, bu yüzden daha önce açıklanan tekniği ve .net geliştiricisinin js yazmaktan olabildiğince kaçınmasına yardımcı olacak birkaç araç daha geliştirdim. mümkün (isterseniz herkes için bir kurşun yerim).

BrowserInterop daha önce tarif ettiğim sarmalayıcıyı sağlar, bunu şu şekilde kullanabilirsiniz (“Başlarken” paketini izledikten sonra):

var window = await jsRuntime.Window();
var eventListening = await window.OnMessage<string>(async (payload) => {
            onMessageEventPayload = payload;
            StateHasChanged();
            await Task.CompletedTask;
        });
  • Window(), diğer tüm BrowserInterop yöntemlerine giriş noktası olan bir BrowserInterop yöntemidir, ayrıca pencere nesnesi hakkında bilgi verir.
  • OnMessage, pencere nesnesindeki “message” olayı için bir olay işleyicisidir. Pencereler arası iletişim için kullanışlıdır (bununla ilgili bir blog yazısı gelecektir)
  • OnMessage, bir kez atıldığında dinlemeyi durduracak bir IAsyncDisposible döndürür, böylece bileşeniniz tıpkı C# olayında olduğu gibi atıldığında olayı dinlemeyi bırakabilirsiniz.
  • BrowserInterop sayesinde mesajı okuyabiliyorum. “Vanilla” JSInterop’ta boş bir nesneye sahip olursunuz, çünkü “message” olay yükündeki bilgiler JSON.stringify’a gönderildiğinde serileştirilmez, bununla ilgili daha fazla bilgi başka bir blog gönderisinde.

Çözüm

C# geliştirici rüyası (bir daha asla JS’ye dokunma) Blazor ile gerçek oluyor olsa da, tarayıcıyla konuşmak için yine de biraz tesisat yapmanız gerekiyor. Umalım ki gelecekte biraz daha kitaplık bu ihtiyacı ortadan kaldıracaktır veya belki bir gün Tarayıcı API’sini doğrudan WebAssembly ile kullanabileceğiz.

Bir cevap yazın

Ready to Grow Your Business?

We Serve our Clients’ Best Interests with the Best Marketing Solutions. Find out More

How Can We Help You?

Need to bounce off ideas for an upcoming project or digital campaign? Looking to transform your business with the implementation of full potential digital marketing?

For any career inquiries, please visit our careers page here.
[contact-form-7 404 "Bulunamadı"]