Up until now, in this series of articles, we have learned how to call JavaScript code from C#, how to call C# code from JavaScript, and also how to extract a JS code and use it from the Razor class library. But all the time, we had to write some JavaScript code to make the functionality works. That said, in this article, we are going to learn more about wrapping JavaScript Libraries with C# so that our library users can consume JS libraries by writing only C# code.

For our example, we are going to use the Toastr JavaScript library for showing non-blocking notifications. We are going to learn how to register it, call it, and pass configuration parameters, all from C#. Once the implementation is done, we are going to be able to use this JS library by only writing C# code.

To download the source code for this article, visit the Wrapping JavaScript Libraries with C# repository

If you want to see complete navigation for this series, you can visit our Blazor WebAssembly Page and find all of the articles from this series and many other articles as well.

We are going to divide this article into the following sections:

So, let’s get going.

Preparing the Razor Class Library with Toastr Files

Let’s start with a project from a previous article, and let’s add another class library project named BlazorWasm.Toastr:

Razor Class Library Project for Toastr

After the creation, let’s remove all the files from the wwwroot folder, and also ExampleJsInterop.cs and Component1.razor files.

To include the Toastr JavaScript library in our Razor class library, we have to add some .js and .css files in the project. You can find these files in the source code repository under the resources folder. Just copy all of them (two .js files and a single .css file) and paste them inside the wwwroot folder in the BlazorWasm.Toastr project:

Wrapping JavaScript Libraries with C# - Toastr Support Files

Now, let’s reference the BlazorWasm.Toastr project from our main project, and let’s import these files inside the index.html file:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>BlazorWasmJSInteropExamples</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />
    <link href="BlazorWasmJSInteropExamples.styles.css" rel="stylesheet" />
    <link href="_content/BlazorWASM.Toastr/toastrStyles.css" rel="stylesheet" />
</head>

<body>
    <div id="app">Loading...</div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
    <script src="scripts/jsExamples2.js"></script>
    <script src="_content/JSInteropExamples.OnlineStatusIndicator/onlineStatusIndicator.js"></script>
    <script src="_content/BlazorWASM.Toastr/toastrJQuery.js"></script>
    <script src="_content/BlazorWASM.Toastr/toastr.js"></script>
</body>

</html>

As you can see, we are doing the same thing as we did with the onlineStatusIndicator.js file in a previous article.

Creating a New Component and Calling a First Toastr Function

Let’s start by creating WrappingJsLibraryInDotNet.razor and WrappingJsLibraryInDotNet.razor.cs files in the Pages folder.

For now, we are just going to add a route to the .razor file:

@page "/wrappjsindotnet"

<h3>WrappingJsLibraryInDotNet</h3>

Now let’s modify the Index.razor file to link this new component:

@page "/"

<h3>
    Use the following links to explore different examples 
    of using a JS code with .NET in Blazor WebAssembly Project:
</h3>

<ul>
    <li>
        <a href="/jsindotnet">
            How to call JavaScript code from .NET
        </a>
    </li>
    <li>
        <a href="/dotnetinjs">
            How to call .NET code from JavaScript
        </a>
    </li>
    <li>
        <a href="/wrappjsindotnet">
            Wrapping JS Library in C#
        </a>
    </li>
</ul>

Excellent.

If we start our application, we are going to see three links on the Home page. Clicking the last one will get us to the newly created component.

Now, in the BlazorWasm.Toastr project, under the wwwroot folder, we are going to create a new toastrFunctions.js file:

window.toastrFunctions = {
    showToastrInfo: function () {
        toastr.info("This is an info message from the Toastr notification.");
    }
}

Here, we globally expose the toastrFunctions object and create a single showToastrInfo function inside it. In this function, we just call the toastr object and call the info function to show the info notification with the provided message.

Okay, let’s get back to the index.html file and import this Javascript file:

<script src="_content/BlazorWASM.Toastr/toastrFunctions.js"></script>

Good. Let’s proceed to the service registration actions.

Creating Toastr Service

In the BlazorWasm.Toastr project, we are going to create a new Services folder and inside it a new ToastrService file:

public class ToastrService
{
    private IJSRuntime _jsRuntime;

    public ToastrService(IJSRuntime jSRuntime)
    {
        _jsRuntime = jSRuntime;
    }

    public async Task ShowInfoMessage()
    {
        await _jsRuntime.InvokeVoidAsync("toastrFunctions.showToastrInfo");
    }
}

This is a familiar code if you have followed our entire series. We just use the IJSRuntime to call the showToastrInfo function from the javascript file. Of course, since we are creating a service class, we have to inject the IJSRuntime with constructor injection.

After this, let’s create a new ServiceCollectionExtension class under the same folder:

public static class ServiceCollectionExtension
{
    public static IServiceCollection AddBlazorToastr(this IServiceCollection services)
        => services.AddScoped<ToastrService>();
}

We just create an extension on the IServiceCollection interface and register our ToastrService as a scoped service.

Then, let’s open the Program.cs class and call this extension method:

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add<App>("#app");

        builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
        builder.Services.AddBlazorToastr();

        await builder.Build().RunAsync();
    }
}

That’s all it takes.

At this moment, we have the Toastr library wrapped inside a C# service, and a library user may use only C# code to work with it. Let’s see how.

Calling Toastr Functions in C#

Let’s start by modifying the WrappingJsLibraryInDotNet.razor.cs file:

public partial class WrappingJsLibraryInDotNet
{
    [Inject]
    public ToastrService ToastrService { get; set; }

    private async Task ShowToastrInfo()
    {
        await ToastrService.ShowInfoMessage();
    }
}

We inject the ToastrService service and call the ShowInfoMessage method inside the ShowToastrInfo method.

Now we have to modify the WrappingJsLibraryInDotNet.razor file:

<div class="row">
    <div class="col-md-4">
        <h4>
            Calling Toastr function
        </h4>
    </div>
    <div class="col-md-3">
        <button type="button" class="btn btn-info" @onclick="ShowToastrInfo">Show Toastr Info</button>
    </div>
</div>

Great.

As soon as we start our app and navigate to the required component, we can click the Show Toastr Info button:

Wrapping JavaScript Libraries with C# - Toastr Info Notification

Excellent.

It works perfectly.

But we are far from over.

Passing Parameters to the JavaScript Function

Right now, we are just calling the showToastrInfo function that accepts no parameters at all. But, we can pass the message to the function and also many other options to configure our Toastr notification.

To do that, we have to modify the showToastrInfo function:

window.toastrFunctions = {
    showToastrInfo: function (message, options) {
        toastr.options = options;
        toastr.info(message);
    }
}

At this point, our function accepts parameters for the message and for additional options, and inside it, we set the options and use the message to show it in the notification.

Then, we have to modify the ShowInfoMessage method inside the TostrService class:

public async Task ShowInfoMessage(string message, object options)
{
    await _jsRuntime.InvokeVoidAsync("toastrFunctions.showToastrInfo", message, options);
}

This method accepts two parameters as well – the message of type string and the options of type object – and pass them to the showToastrInfo function.

Lastly, we have to modify the ShowToastrInfo method from our component’s class:

private async Task ShowToastrInfo()
{
    var message = "This is a message sent from the C# method";
    var options = new 
    { 
        CloseButton = true, 
        HideDuration = 300,
        HideMethod = "slideUp",
        ShowMethod = "slideDown",
        PositionClass = "toast-bottom-right"
    };

    await ToastrService.ShowInfoMessage(message, options);
}

This is the place where we create the message and options variables, and send them as arguments to the ToastrService.

Good.

Now, let’s start the app, navigate to the component, and press the button:

Wrapping JavaScript Libraries with C# - Toastr Info Notification with Options

And there we go. We can see our notification on the bottom-right side, with the close button and also with some slideUp/slideDown animations while opening/closing.

Awesome. But we can make it even better.

Enabling Strongly-Typed Parameters For the JavaScript Library

Even though our solution looks good for now and we use only C# to consume and use the JS library, we can make it even better. We can make our options parameter strongly typed, thus providing IntelliSense for all kinds of options for the library users.

So, let’s start by creating a new Enumerations folder in the BlazorWasm.Toastr project, and add a new ToastrShowMethod enumeration inside:

public enum ToastrShowMethod
{
    [Description("fadeIn")] FadeIn,
    [Description("slideDown")] SlideDown
}

As you can see, for each option, we add the [Description] attribute with the value that the Toastr library expects from. We want to use enumerations because it provides some nice IntelliSense and we reduce the possibility to send an option with a wrong value. The [Description] attribute resides in the System.ComponentModel namespace.

Now, let’s create two more enumerations.

The ToastrHideMethod enumeration:

public enum ToastrHideMethod
{
    [Description("fadeOut")] FadeOut,
    [Description("slideUp")] SlideUp
}

And another one for the position:

public enum ToastrPosition
{
    [Description("toast-top-left")] TopLeft,
    [Description("toast-top-right")] TopRight,
    [Description("toast-bottom-left")] BottomLeft,
    [Description("toast-bottom-right")] BottomRight
}

That’s it. Of course, there are many other options for the Toastr library, but these will be enough for now.

Adding a Custom Converter

Since System.Text.Json can’t convert the Description attribute out of the box, we have to provide a custom converter to help with the process.

That said, let’s create a new CustomConverters folder in the BlazorWasm.Toastr project and a new CustomEnumDescriptionConverter class inside that folder:

public class CustomEnumDescriptionConverter<T> : JsonConverter<T> where T : Enum
{
    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        throw new NotSupportedException();
    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        var fieldInfo = value.GetType().GetField(value.ToString());

        var description = (DescriptionAttribute)fieldInfo.GetCustomAttribute(typeof(DescriptionAttribute), false);

        writer.WriteStringValue(description?.Description);
    }
}

Since we are going to use this converter for all three enumerations, we make it generic and restrict it to the Enum type. This class must inherit from the JsonConverter<T> abstract class and implement both abstract methods – Read and Write. Because we are not supporting the read operation for our converter, we just throw a NotSupportedException for the Read method.

In the Write method, we use the GetType method to extract the type of the T parameter, and the GetField method to extract the field with a specified name of type FieldInfo. Then, we use the reflection with the GetCustomAttribute method to extract the attribute of the provided type and cast it into that type – in this case, the DescritpionAttribute type. Finally, we use the WriteStringValue method to write a value of the Description attribute. We use the null conditional operator (?) to prevent an error if the description is null, or in other words, if the Description attribute is not provided.

Using the Strongly-Typed Options

Now, let’s create one more class in the BlazorWasm.Toastr project:

public class ToastrOptions
{
    [JsonConverter(typeof(CustomEnumDescriptionConverter<ToastrPosition>))]
    [JsonPropertyName("positionClass")]
    public ToastrPosition Position { get; set; }

    [JsonConverter(typeof(CustomEnumDescriptionConverter<ToastrHideMethod>))]
    [JsonPropertyName("hideMethod")]
    public ToastrHideMethod HideMethod { get; set; }

    [JsonConverter(typeof(CustomEnumDescriptionConverter<ToastrShowMethod>))]
    [JsonPropertyName("showMethod")]
    public ToastrShowMethod ShowMethod { get; set; }

    [JsonPropertyName("closeButton")]
    public bool CloseButton { get; set; }
    
    [JsonPropertyName("hideDuration")]
    public int HideDuration { get; set; }
}

Here, we provide different options for the Toastr library and for each of them use the JsonPropertyName attribute to specify the correct option name that Toastr library uses. For example, there is no Position option in the library but the positionClass option. Also, you can see that we apply our custom converter for each enumeration option.

With this in place, we can modify the ShowToastrInfo method and use the strongly-typed options:

private async Task ShowToastrInfo()
{
    var message = "This is a message sent from the C# method";
    var options = new ToastrOptions
    { 
        CloseButton = true, 
        HideDuration = 300,
        HideMethod = ToastrHideMethod.SlideUp,
        ShowMethod = ToastrShowMethod.SlideDown,
        Position = ToastrPosition.BottomRight
    };

    await ToastrService.ShowInfoMessage(message, options);
}

Excellent.

Now if you repeat our last test, you will get the same result. But this time, the users of our library have a complete C# experience while using the JS library.

Conclusion

In this article, we have learned:

  • How to wrap the JavaScript Toastr library inside the Razor class library project
  • The way to use the Toastr library with additional options
  • How to create strongly-typed options for the JavaScript library

So, with all these in our minds, we can convert any JS library for our C# users without them knowing anything about the JS code.

We hope you enjoyed this article, and we see you in the other one.