In: Genel


Blazor’da her şeyi yalnızca .NET ile yapamazsınız. Bir JS lib kullanmak veya tarayıcıdan bilgi almak istiyorsanız, kullanmanız gerekir. JSInterop. 2 sistem veya alt sistem arasında iletişim kurmanız gerektiğinde, genellikle diğer taraftan bir şeyin referansını “saklamanız” gerekir. Örneğin, bir “ödeme oluştur” API’sini çağırdığınızda, size bir “ödeme kimliği” gönderme olasılığı çok yüksektir, böylece bu kimlikle diğer API’yi arayabilir ve “geri ödeme” veya “iptal” gibi işlemler yapabilirsiniz. Bellek içi uygulama için, ID ile elle yapmak biraz zahmetlidir, çünkü:

  • diğer tarafla paylaştığınız nesnenin bellek içi haritasını tutmanız gerekir
  • bu nesne asla GC tarafından bellekten serbest bırakılmayacak
  • her türlü etkileşim için bunu yapmak zorundasın

Çerçeve, .NET örneğini js ile nasıl paylaşır?

Geliştirici deneyiminin daha iyi olması için bu tür şeyleri çerçeve/altyapı düzeyinde ele almak daha iyidir. Blazor’da zaten bunun gibi bir mekanizma vardır: JS’ye bir .NET nesnesinin referansını gönderebilirsiniz, böylece açıklamalı herhangi bir yöntemi çağırabilirsiniz. [JSInvokable] bu nesne üzerinde. Resmi belgelerdenbunu şöyle yaparsın:

Jilet dosyası:

@inject IJSRuntime _jsRuntime
@implements IDisposable

<button type="button" class="btn btn-primary" @onclick="TriggerNetInstanceMethod">
    Trigger .NET instance method HelloHelper.SayHello
</button>

@code {
    private DotNetObjectReference<HelloHelper> _objRef;

    public async Task TriggerNetInstanceMethod()
    {
        _objRef = DotNetObjectReference.Create(new HelloHelper("Rémi"));
        var test = await  _jsRuntime.InvokeAsync<string>("exampleJsFunctions.sayHello", _objRef);
    }
    public void Dispose()
    {
        _objRef?.Dispose();
    }
    public class HelloHelper
    {
        public HelloHelper(string name)
        {
            Name = name;
        }

        public string Name { get; set; }

        [JSInvokable]
        public string SayHello() => $"Hello, {Name}!";
    }
}

JS dosyası

window.exampleJsFunctions = {
  sayHello: function (dotnetHelper) {
    return dotnetHelper.invokeMethodAsync('SayHello')
      .then(r => console.log(r));
  }
};

Bu kod ile “test” değişkeni “Merhaba, Rémi” olacaktır. O nasıl çalışır ?

  • DotNetObjectReferans.Create, verilen HelloHelper değeriyle bir DotNetObjectReference örneğini oluşturun.
  • bu JSRuntime (“TrackObjectReference” yöntemi) bir parametrenin bir DotNetObjectReference olduğunu algılar ve zaten orada değilse dahili bir sözlükte depolar, ardından bir kimliği artırır ve bu kimliği bunun gibi bir json nesnesine sarılmış javascript’e gönderir

“1”, dahili sözlükteki nesne kimliğidir.

  • üzerinde gördük son blog yazısı JSInterop, .NET çalışma zamanından alınan değeri değiştirmek için json reviver’ı nasıl kullanır? Burada, json değerini böyle bir DotnetObject örneğine değiştirmek için aynı mekanizmayı kullanır.
const dotNetObjectRefKey = '__dotNetObject';
  attachReviver(function reviveDotNetObject(key: any, value: any) {
    if (value && typeof value === 'object' && value.hasOwnProperty(dotNetObjectRefKey)) {
      return new DotNetObject(value.__dotNetObject);
    }

    // Unrecognized - let another reviver handle it
    return value;
  });
  • Daha sonra JS yöntemi bir DotnetObject örneğini aldığında ve “invokeMethodAsync” Blazor js kitaplığı tarafından tanımlanan “DotNetCallDispache” adlı bir JS arayüzünde “invokeDotNetFromJS” adlı bir yöntemi çağırır. burada.
  • Bu yöntem 2. argüman olarak 2 şeyi kabul eder: bir derleme adı VEYA bir nesne kimliği (__dotNetObject alanının değeri) ve diğer argümanlar yöntem adı, çağrı kimliği (zaman uyumsuz çağrıları yönetmek için) ve yöntem parametreleridir.
  • Daha sonra “ yardımı ilebind_static_method” yüklenen derlemelere göz atan mono WASM çalışma zamanından, C# statik yöntemi “MonoWebAssemblyJSRuntime.BeginInvokeDotNet” için bir başvuru bulur ve onu çağırır (yöntem kendini çağırır). gerçekten karmaşıkWebAssembly içindeki .NET çalışma zamanından bellekle derleme adı veya nesne kimliği, yöntem adı ve yöntem parametreleriyle oynamaktan oluşur.
  • Bu yöntem daha sonra parametrenin bir rakam olup olmadığını kontrol eder, eğer bir rakam ise, o zaman bir js nesnesine bir referanstır (eğer değilse, o zaman bir derleme adıdır). Neden böyle bir hack yapıyorlar ki kendine soracaksın? Monowasm ve blazor arasında 4 parametreye kadar birlikte çalışma sınırı var, bu yüzden 1 yuvaya 2 farklı bilgi göndermeyi seçtiler. Her js birlikte çalışma çağrısı bu sarmalayıcıdan geçtiğinden, bu sınır kodunuz için geçerli değildir.
  • Sonra yansıma ile çağrılan C# sınıfı DotNetGönderici parametrelerle iyi yöntemi çağırır.

Bir geliştirici deneyiminden kaynaklanan tek sorun, örneğinize ilişkin statik bir referansın etrafta tutulduğu için GC yokmuş gibi nesne elden çıkarmayı biraz düşünmeniz gerektiğidir. Bir DotNetObjectReference’ı elden çıkarmak için, üzerinde Dispose çağırmanız gerekir.

Bu blog yazısı için JSInterop açıklamasında neden bu kadar ileri gittiğimi bilmiyorum, ama umarım birisinin nasıl çalıştığını biraz daha anlamasına yardımcı olur. Şu an neredeyiz ? Bu çalışmadan, js nesne referansını .NET’e nasıl göndermem gerektiğini belirleyebilirim:

  • Bir yöntem sonucunu bir haritada saklayın
  • Kimliği etrafta tutmak için bir C# sınıfı oluşturun
  • Bu serileştirilmiş C# sınıfı js birlikte çalışmasına gönderildiğinde, onu ilgili JS nesnesine değiştirin
  • JS tarafında referansı temizlemek için bir yol sağlayın

Bir yöntem sonucunu bir haritada saklayın

Yapılacak ilk şey, aynı şeyi js tarafında inşa etmektir. ilk önce kullanmayı düşündüm Zayıf Harita ancak anahtar, zayıf bir referans tutmak istediğimiz nesne olduğu için nasıl yararlı olabileceğini gerçekten anlamıyorum. Bu yüzden basit bir javascript nesnesi kullanıyorum. İşte nesneyi saklamak için benim yöntemim:

var jsObjectRefs = {};
var jsObjectRefId = 0;
const jsRefKey = '__jsObjectRefId';
function storeObjectRef(obj) {
    var id = jsObjectRefId++;
    jsObjectRefs[id] = obj;
    var jsRef = {};
    jsRef[jsRefKey] = id;
    return jsRef;
}   

İşte benim örnek js yöntemim onu ​​çağırıyor

 function openWindow() {
      return storeObjectRef(window.open("/", "_blank"));
  }

Ve JSInterop çağrısı

 public class JsRuntimeObjectRef
  {
      [JsonPropertyName("__jsObjectRefId")]
      public int JsObjectRefId { get; set; }
  }
  private JsRuntimeObjectRef _windowRef;
  private async Task OpenWindow()
  {
      _windowRef = await jsRuntime.InvokeAsync<JsRuntimeObjectRef>("openWindow");
  }

Sınıfı basitleştirdim: Blazor’da özellik dahilidir ve bunu kullanıcılara gizlerken seri hale getirmek için özel bir JsonConverter kullanırlar.

Bir yöntem çağrısında referansı kullanma

Şimdi bu açılan pencereyi kapatmam gerekiyor, işte JS yöntemi

function closeWindow(window) {
    window.close();
}

Ve işte C# birlikte çalışma çağrısı

private async Task CloseWindow()
{
    await jsRuntime.InvokeVoidAsync("closeWindow", _windowRef);
}

Merak ediyor olabilirsiniz: Bir JsRuntimeObjectRef, JS tarafında nasıl bir pencere nesnesi olur? Bir canlandırıcı ile! İşte tanımı:

DotNet.attachReviver(function (key, value) {
    if (value &&
        typeof value === 'object' &&
        value.hasOwnProperty(jsRefKey) &&
        typeof value[jsRefKey] === 'number') {

        var id = value[jsRefKey];
        if (!(id in jsObjectRefs)) {
            throw new Error("This JS object reference does not exists : " + id);
        }
        const instance = jsObjectRefs[id];
        return instance;
    } else {
        return value;
    }
});

Bu canlandırıcı, JSInterop aracılığıyla JS’ye gönderilen her serileştirilmiş nesne için çağrılır (nesne grafiğinin derinliklerinde bile, böylece JsRuntimeObjectRef özellikleriyle diziler veya karmaşık nesneler gönderebilirsiniz).

Tüm çalışma kodunu bulabilirsiniz burada.

JS çalışma zamanı belleğinden tutulan referansı temizleme.

Bunu böyle bırakırsak, jsObjectRefs, sonsuza kadar kötü olan ve kullanıcı deneyiminizi etkileyebilecek olan js nesnesine bir referans tutacaktır (UX evet). jsObjectRefs’deki nesne referansını kaldırmak için biraz DotNetObjectReference ile benzer bir şey yapacağız ve JsRuntimeObjectRef içinde IAsyncDisposable’ı şöyle uygulayacağız.


public class JsRuntimeObjectRef : IAsyncDisposable
  {
      internal IJSRuntime JSRuntime { get; set; }

      public JsRuntimeObjectRef()
      {
      }

      [JsonPropertyName("__jsObjectRefId")]
      public int JsObjectRefId { get; set; }

      public async ValueTask DisposeAsync()
      {
          await JSRuntime.InvokeVoidAsync("browserInterop.removeObjectRef", JsObjectRefId);
      }
  }
  private JsRuntimeObjectRef _windowRef;
  private async Task OpenWindow()
  {
      _windowRef = await jsRuntime.InvokeAsync<JsRuntimeObjectRef>("openWindow");
      _windowRef.JSRuntime = jsRuntime;
  }

  private async Task CloseWindow()
  {
      await jsRuntime.InvokeVoidAsync("closeWindow", _windowRef);
      await _windowRef.DisposeAsync();
  }

Her yeni JsRuntimeObjectRef oluşturma işleminden sonra JSRuntime’ı ayarlamanız gerektiğinden, bunu bir uzantı yöntemine sarmak daha iyi bir fikir olabilir.

İşte JS yöntemi

function cleanObjectRef(id) {
    delete jsObjectRefs[jsObjectRefId];
}

Şimdi, açılan pencereyi kapattığımızda, söz konusu pencereye yapılan referans kaldırılacak ve tarayıcı bunu GC’ye çevirebilecek (eğer öyle hissediyorsa).

TarayıcıInterop

Son blog yazımda kütüphanemden bahsetmiştim. TarayıcıInterop geliştiricinin JSInterop kullanması gerektiğinde hayatını kolaylaştıran bir kütüphanedir. Bu kütüphane, bu blog yazısında bahsettiğim birçok şeyi kullanıyor çünkü pencere nesnesinin referansını tutmam gerekiyor. Hayatımı kolaylaştırmak için kullanabileceğiniz bir dizi yardımcı yöntem oluşturdum:

IJSRuntime jsRuntime;
// this will get a reference to the js window object that you can use later, it works like ElementReference ofr DotNetRef : you can add it to any method parameter and it 
// will be changed in the corresponding js object 
var windowObjectRef = await jsRuntime.GetInstancePropertyAsync<JsRuntimeObjectRef>("window");
// get the value of window.performance.timeOrigin
var time = await jsRuntime.GetInstancePropertyAsync<decimal>(windowObjectRef, "performance.timeOrigin");
// set the value of the property window.history.scrollRestoration
await jsRuntime.SetInstancePropertyAsync(windowObjectRef, "history.scrollRestoration", "auto");
//get a reference to window.parent
var parentRef = await jsRuntime.GetInstancePropertyRefAsync(windowObjectRef, "parent");
// call the method window.console.clear with window.console as scope
await jsRuntime.InvokeInstanceMethodAsync(windowObjectRef, "console.clear");
// call the method window.history.key(1) with window.history as scope
await jsRuntime.InvokeInstanceMethodAsync<string>(windowObjectRef, "history.key",1 );
//will listen for the event until DisposeAsync is called on the result
var listener = await jSRuntime.AddEventListener(windowObjectRef, "navigator.connection", "change", () => Console.WriteLine("navigator connection change"));
//stop listening to the event, you can also use "await using()" notation
await listener.DisposeAsync();
//will return true if window.navigator.registerProtocolHandler property exists
await jsRuntime.HasProperty(windowObjectRef, "navigator.registerProtocolHandler")

Pek çok yöntem var, yakında bununla ilgili blog pst oluşturacağım, ancak yine de kullanabilir ve bana geri bildirim/hata raporları gönderebilirsiniz.

Çözüm

ASPNET Core’daki (HttpClient…) kancaların eksikliği hakkında kafamda çok şey var ama canlandırıcı, belgelenmemiş olsa da, burada gerçekten harika.

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ı"]