Blazor: .NET in the Browser, Part 2
Last time, we looked at the general structure of a Blazor app is. Now, we'll look at interoperability with JavaScript, binding details, and more.
Join the DZone community and get the full member experience.
Join For FreeWelcome back! If you missed Part 1, check it out here!
Things to Know When Using Blazor
We have seen how simple the general structure of a Blazor app is. Now, we are going to see a few things to know when using Blazor: interoperability with JavaScript, binding details, etc.
Special Attribute to Format Strings
In our example, we bind the input to a string field and then convert the string field to the proper DateTime value. This is the typical pattern that you are going to use because the input is generally a string, while our fields are not always so. However, just for the DateTime
type, there is a special attribute that you can use to bypass the need for an intermediate variable.
<input type="date" bind="@calendarDate.Date" format-value="yyyy-MM-dd" />
We could have used the special attribute format-value
to help Blazor automatically convert to and from the DateTime
value. At the moment, this attribute only works for DateTime
variables. It works both for display and when the value is parsed after the onchange
event.
Writing to Console Works
Writing to Console works as if you were using console.log
. If we added the following line to our ConvertDate
function.
Console.WriteLine("ConvertDate called");
You would see this text on the usual Console in the browser.
The only difference is that the string is prepended with a WASM:
.
You Can Inject Variables
Using the directive inject
you can inject variables/properties into a component.
@inject HttpClient Http
Blazor comes with a ready-to-inject service for HttpClient
. Since Blazor 0.5.0, you've been able to add your custom services as you would in a traditional ASP.NET app, by going to the ConfigureServices
function of the Startup
class.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Add any custom services here
}
public void Configure(IBlazorApplicationBuilder app)
{
app.AddComponent<App>("app");
}
}
You can also use these services to perform traditional constructor injections, like in any other ASP.NET software. You can look up the documentation for more information.
JavaScript Interoperability
To see how JavaScript interoperability works, let's create the new page Text.csthml
and add the link to navigation column in NavMenu.cshtml
. Then we create a folder js
inside www-root
, and inside this folder we add a file called site.js
.
The image shows our additions.
We obviously also need to add in a script
the JavaScript source in the main index.html
file.
Calling JavaScript From C#
Since Blazor 0.5.0, there is no need to register with Blazor the JavaScript function that we are going to call from C#. You can call any JavaScript function.
To call a registered JavaScript function we use the method InvokeAsync of JSRuntime.Current, included in the namespace Microsoft.JSInterop. The first parameter of the method is the name of the JavaScript function while the others are its arguments.The function identifier of the JavaScript function is relative to the global scope (window). For example, if you wish to call window.someFunction, the identifier is someFunction.
The generic parameter type is the return type of the function. You cannot use void or not return anything. However, there is a workaround: if you do not return anything, you can say that the function returns a string and everything works fine. Except, of course that you should not use the value returned by the invocation of the function.
We can go to the Text.cshtml
file and perform the call.
@page "/text"
@using Microsoft.AspNetCore.Blazor;
@using Microsoft.JSInterop;
<textarea id="main_text" class="form-control" rows="10" onchange='@repeat' ></textarea>
@functions {
void repeat(UIChangeEventArgs e)
{
JSRuntime.Current.InvokeAsync<string>(
"console.log",
e.Value);
}
}
We bind the onchange
attribute to our repeat
function. Notice that the signature of the repeat function contains a parameter of type UIChangeEventArgs
. We cannot define custom parameters but we can use some that are predefined for specific events. These parameters can be omitted if we do not need them. For the change events there is an argument of type UIChangeEventArgs
that contains the new value.
Passing References of Elements
Since Blazor 0.5.0 you can also pass references of HTML elements from C# to JavaScript functions. All you have to do is adding the attribute ref
to the HTML elements and then add an ElementRef
field with the value of the attribute in the functions
section.
<textarea ref="text"></textarea>
@functions {
ElementRef text;
}
In this example we create a reference text
for a textarea element. The ElementRef handle is completely opaque for Blazor, so you cannot do anything with itm except passing as-it-is to a JavaScript function.
@function {
JSRuntime.Current.InvokeAsync<string>(
"console.log",
text);
}
In JavaScript, the reference represents the whole element. So, from JavaScript you can access its individual attributes, like id or name.
However, from Blazor you cannot pass anything other than the plain reference. So, the following code is invalid.
Calling C# From JavaScript
You can also call C# methods from JavaScript using the functions Blazor.invokeDotNetMethod
or Blazor.invokeDotNetMethodAsync
. However, you cannot call all methods, only the ones that respect these conditions:
- Non-generic.
- Not overloaded.
- Concrete type method parameters (i.e.,
Example<string>
is valid,Example<T>
is not). - Parameters deserializable using JSON.
Let's see an example of how it works with a static method.
We create a new root C# file called Util.cs
.
namespace BlazorApp
{
public class Util
{
[JSInvokable]
public static string RememberTheTruth(string text)
{
int start = 0;
while(text.IndexOf("JavaScript", start) != -1)
{
start = text.IndexOf("JavaScript", start) + 10;
if((text.Length > start + 4 && text.Substring(start+2, 2) != "C#") ||
text.Length < start + 4)
text = text.Insert(start, " (C# is better)");
}
return text;
}
}
}
The class contain a static C# method decorate with [JSInvokable]
. The method checks a string and ensures that one basic truth of life is always remembered.
Then we edit the JavaScript file.
function check() {
document.getElementById("main_text").value =
DotNet.invokeMethod('BlazorApp','RememberTheTruth',
document.getElementById("main_text").value);
}
We can use the function DotNet.invokeMethod
to call a C# method (add Async if you are calling an async method):
- The first argument is the name of the assembly.
- The second one is the name of the function.
- The last argument is the argument for the C# method.
We assign the result of our C# function call to the textarea value.
Now, we just have to change onchange
attribute of the textarea in the Text.cshtml
file to call our JavaScript function.
<textarea id="main_text" class="form-control" rows="10" onchange='check();'></textarea>
The end result works nicely.
Calling a .NET Instance Method
Since Blazor 0.5.0 you can also call .NET instance method, although the procedure is a bit more convoluted.
As usual, you have to decorate the method with [JSInvokable]
.
public class Simple
{
public string Text { get; set; }
public Simple(string text)
{
Text = text;
}
[JSInvokable]
public string ToUpper()
{
return Text.ToUpper();
}
}
You then have to invoke the JavaScript function that is going to use that method (ToUpper
in our example) from C#, passing an object of the class to it.
@function {
void shout() {
JSRuntime.Current.InvokeAsync<object>(
"capitalize", // name of the JavaScript function
// object passed to the JavaScript function
new DotNetObjectRef(new Simple("strong opinion")));
}
}
The instance of the desired class is wrapped in another object of the type DotNetObjectRef
. In this example, we pass an object of type Simple
.
Finally, in JavaScript we are going to use invokeMethod
to call the method we wanted to call.
function capitalize(dotnetHelper) {
console.log(dotnetHelper.invokeMethod('ToUpper'));
}
As you can see it is not hard, but is still a bit complex.
Summary
We have created a small example program and shown the major issues that arise when merging the C# and JavaScript world. We have seen how Blazor allows to easily bring the mature ASP.NET and C# ecosystem to the client in the browser. The interoperability with JavaScript is also there, although it is still a bit cumbersome.
Blazor is a very promising platform to deliver C# software on the client. It is still at an experimental stage, so there are a few rough edges, but it already works well enough for simple projects. Even at this stage, it is still better than using WebAssembly with C++. If you like C#, or the .NET Platform, you should start studying the platform now, since it promises to be an awesome new addition to the .NET world.
Blazor is a great way to fullfill the promise of WebAssembly: you can now create and bring to the client every kind of powerful and complex software that is still on the desktop. And you can do that using your favorite language and the existing codebase. What is not to like?
One thing to remember: you can call any .NET library, but you will have always to use some C# because Razor uses C#. So, you can already use F# libraries, but you need C# in the Razor pages (i.e., everything the code that is in *.cshtml
files) to make it work with Blazor.
Published at DZone with permission of Federico Tomassetti, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments