The Practical Client

Integrating JavaScript and C# in the Browser: Beyond the Basics with Blazor

In a previous column, I showed how to create a "Hello, World" application that integrated JavaScript functions and C# Blazor methods. However, my Blazor-world and JavaScript-world code didn't interact much (for example, I was only able to call static C# Blazor methods from my JavaScript code). In this column, I'm going to go beyond that.

To build this code, I used Visual Studio 2017 with all the latest updates applied and the latest version of Blazor (Blazor 0.5.0) installed. I created a new project using File > New > Project and selecting ASP.NET Core Web Application as my project type. In the subsequent dialog, I picked the Blazor template. I named my project BlazorToJS2 (that will matter later on).

One warning: Often, when I pressed F5 to start debugging, Visual Studio would hang up on the build step. To fix that, I would have to go to Visual Studio's Build menu, click the Cancel Build option at the end of the menu, wait a few seconds for the build to finish, and then press F5 again to restart my test. Everything then worked tickety-boo. Ending my debugging session by stopping the session from Visual Studio (rather than by closing the browser) seemed to reduce these occurrences. I have no explanation for this.

Did I mention that this Blazor technology is "experimental"?

Instantiating Blazor Classes
If you have a class in your Blazor code and want to call a method on an instantiated version of it from JavaScript code, you can. The problem is, however, that you can't instantiate ("new-up") a C# class from JavaScript code -- that has to be done from within your Blazor code. However, once you've instantiated the class you can return a reference to that class to your JavaScript code.

Before passing a .NET object to a JavaScript function, your C# code must wrap it in a DotNetObjectRef object. As an example, the following code in my project's Index.cshtml OnInitAsync method instantiates a .NET Customer object and passes it into a DotNetObjectRef object's constructor (I used the OnInitAsync method because it executes automatically when Index.cshtml loads). The code then passes the wrapped Customer object to a JavaScript function called SayHelloJS using the InvokeAsync method I described in my earlier column (the version of the SayHelloJS function I used here doesn't return anything -- the data type in the InvokeAsync method is simply a placeholder):

protected override async Task OnInitAsync()
{
  Customer cust = new Customer("Peter","Vogel");
  DotNetObjectRef wrappedCust = new DotNetObjectRef(cust);
  await JSRuntime.Current.InvokeAsync<object>("SayHelloJS", wrappedCust);
}

The C# Customer class I'm passing is relatively uninteresting except for its GetFullName method, which is decorated with the JSInvokable attribute. That attribute makes it possible to call the method from JavaScript code:

public class Customer
{
  public string FirstName { get; set; }
  public string LastName { get; set; }

  public Customer(string FirstName, string LastName)
  {
    this.FirstName = FirstName;
    this.LastName = LastName;
  }

  [JSInvokable]
  public string GetFullName()
  {
    return FirstName + " " + LastName;
  }
}

Using C# Classes in JavaScript
The JavaScript function I called from my Blazor method is in my project's Index.html file under the project's wwwroot node. The function accepts the DotNetObjectRef that's wrapping the Customer object. The function then calls the DotNetObjectRef's invokeMethod method, passing the name of the method on the Customer object to be called. It then catches the result in a variable called fullName and displays it:

<script>

function SayHelloJS(cust) 
{
  var fullName = cust.invokeMethod("GetFullName");
  alert (fullName);
}
</script>

You only need to use the DotNetObjectRef object if you intend, in JavaScript, to call a method in your C# object. If all you want to pass between your Blazor and JavaScript worlds is data, then your best solution is probably to convert objects into JSON and pass the resulting strings around and skip using DotNetObjectRef.

Passing Multiple Parameters
In all these examples I've been passing only a single parameter to my functions and methods. However, it is possible to pass multiple parameters.

When passing parameters from C# to JavaScript, you must put the parameters in an array. In my C# Blazor code, for example, I can pass multiple parameters to the JavaScript function with code like this:

JSRuntime.Current.InvokeAsync<object>("SayHelloJS", new object[] { wrappedCust, "Peter"});

My sample code here is made more complicated because my parameters are two very different types (a DotNetObjectRef and a string). If my types were, for example, both strings, I could have used an implicitly typed array like this:

JSRuntime.Current.InvokeAsync<object>("SayHelloJS", new[] { "Vogel", "Peter"});

To call a C# Blazor method from JavaScript code, you can use either the invokeMethod or the InvokeMethodAsync functions. With either of them, thanks to the flexibility of JavaScript, you can pass as many parameters as you want. This example, using invokeMethodAsync, passes two parameters (fullName and salutation) to a static C# method called SayHelloCS in the BlazorAndJS2 namespace:

DotNet.invokeMethodAsync("BlazorAndJS2", "SayHelloCS", fullName, salutation)
                .then(data => alert(completeName), reason => alert(reason));

Returning Results
In JavaScript, to catch the result of C# method called through invokeMethodAsync, the best choice is to use the then function as I did in my previous example.

However, if you use the invokeMethod function you can just catch the return value of the C# method, like this:

var completeName = DotNet.invokeMethod("BlazorAndJS2", "SayHelloCS", fullName, salutation);

The C# method you're calling doesn't need to return a Task object even if called through invokeMethodAsync. The static C# method I've been using, for example, simply returns a string:

[JSInvokable]
public static string SayHelloCS(string name, string salutation)
{
  return "Hello, " + salutation + " " + name;
}

In Blazor, to catch the result of a JavaScript function called through the InvokeAsync method, you just have to use the await keyword to grab the result. In this code my SayHelloJS method is returning a string value so I've set the datatype for the InvokeAsync method to match the return data type:

var name = await JSRuntime.Current.InvokeAsync<string>("SayHelloJS", wrappedCust);

Again, no special datatype is required in your JavaScript code as your return value.

At this moment in history (September, 2018) this is how Blazor can leverage your JavaScript assets and how JavaScript can pass off work to Blazor. We're still on the path to the first RTM version, though, so much may change. You've been warned.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

Subscribe on YouTube