Search code examples
javascriptc#razorstatic-methodsblazor

Static method invocation using JSInterop/Blazor


(#)firstpost

I'm using JSInterop and Blazor/RazorPages to invoke a method when an item on a table is clicked. The Interop specification only allows static methods to be used. Well, technically instance methods can also be used, but the docs for that are really confusing. I'm almost positive the instance method invocation won't work without the Blazor '@onclick' tag-helper -- which I can't use in this example.

This is what works:

public class Dashboard : PageModel

private readonly DeviceService _deviceService;
public Dashboard (DeviceService deviceService)
{
     _deviceService = deviceService;
}

//I want to use the above instance of deviceService, for a JSInvokable method

[JSInvokable]
public static async Task<List<string>> DeviceDetails(string deviceName)
{
    List < string > details = new List<string>();
    Uri location = new Uri("http://superapi.com/api/devices");
    HttpClient client = new HttpClient();
    client.BaseAddress = location;

    DeviceService dev = new DeviceService(client);

    Device details = await dev.GetDeviceByName(deviceName);

    return details;
}
//This <script> is what the client gets in their browser when they visit the page.
function () {
            var table = $('#<TableName>').DataTable();
            var d = table.row(this).data();
            DotNet.invokeMethodAsync("<namespace>", "DeviceDetails", d[0])
                .then(data => {
                    document.getElementById("Property1").innerHTML = data[0];
                    document.getElementById("Property2").innerHTML = data[1];
                    document.getElementById("Property3").innerHTML = data[2];
                    document.getElementById("Property4").innerHTML = data[3];
                    document.getElementById("Property5").innerHTML = data[4];
                });
        });

The MS docs for invokeMethodAsync state that it only works for static methods which won't allow me to access the instance of DeviceService that I have declared in the PageModel. It forces me to create a rather ugly new instance with 'new' everything.

I think the solution might involve using interfaces or dependency injection, does anyone have a suggestion?


Solution

  • I discovered an answer to this by researching dependency injection. I registered a string from my appsetting.json using:

    IConfigurationSection baseAPI = Configuration.GetSection("BaseAddress");
    services.Configure<ApiBaseAddress>(baseAPI);
    

    Then on the page I injected:

    @inject IOptions<ApiBaseAddress> BaseAddress
    

    Now we can send this BaseAddress to the static method in a parameter:

    DotNet.invokeMethodAsync("<namespace>", "DeviceDetails", d[0], "@BaseAddress.Value.Uri")
    

    Which allows us to access during URI creation:

    Uri location = new Uri(baseUri + "/api/worker");
    

    This isn't totally perfect though - I really would have wanted to not create a service from scratch, but at least I don't need to change multiple pieces of code during deployment.