This article covers
- Implementing custom module validation in an ASP.NET Core MVC web application
- Using Emscripten to create a WebAssembly module that can be used outside the browser
- Using WebAssembly modules in your C# code
Imagine you’re working for a company who’s about to build a web application for expense reimbursements. Before they do, they task you with building a prototype to evaluate several things including if WebAssembly can be leveraged both on the client in JavaScript and on the server in C#.
For the prototype, you’ll create an ASP.NET Core MVC web application. The MVC application has good built-in validation but the final product will need some additional custom validation. For the prototype you’ll keep the validation simple because you just want to know if it’s possible to use WebAssembly in both the browser and in the C# code.
The following image shows the two web pages that you need to build for this prototype:
For this prototype, you’re going to implement WebAssembly validation in the JavaScript for the Amount field and in C# for the Description field.
Validation is performed in the browser primarily so that a web application is more responsive to the user’s actions. For example, it’s frustrating to a user when they submit a form and wait for a response only to find out that there was something wrong with what they entered. By validating as much as you can in the browser, the user gets immediate feedback. This also helps you because your server isn’t doing as much work which allows it to handle more clients at once.
Although validating in the browser has several advantages, you still need to validate the data when it reaches the server because there are ways to bypass the client-side checks.
As shown below, the steps to build this prototype are:
- Create an ASP.NET Core MVC web application and then create the following
- Custom attributes for the validation of the model’s Amount and Description properties
- A model for the expense entries
- The Expense Entries and the Create Expense views
- Add a C++ project to the solution whose code will be compiled into a WebAssembly module
- Adjust the JavaScript code to validate the Amount field using the WebAssembly module
- Adjust the C# code to validate the Description field using the WebAssembly module
As shown in the following image, your first step is to create the ASP.NET Core MVC web application.
1. Create the web application
To create the web application itself, open Visual Studio 2019 and choose Create a new project
- Under C#, choose the ASP.NET Core Web Application and click Next.
- Give the project a name. I called mine WebAssemblyInCSharp but you can use a different name if you wish. Click Create.
- Select the Web Application (Model-View-Controller) template and then press Create.
Now that you have your web application, you’ll create the custom attributes for the Amount and Description properties that will be part of the Expense model that you’ll create in a moment.
a. Create the custom attributes
Right-click on an empty area of the Solution Explorer and choose Add, New Folder from the context menu. Name the folder Attributes.
Right-click on the Attributes folder and choose Add, Class… from the context menu. Give the class the name WasmAmountAttribute.
In your WasmAmountAttribute.cs file, replace the generated code with the following code for the Amount field’s attribute:
using Microsoft.AspNetCore.Mvc.DataAnnotations; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.Localization; using System; using System.ComponentModel.DataAnnotations; namespace WebAssemblyInCSharp.Attributes { [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class WasmAmountAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { // Server-side validation goes here return ValidationResult.Success; } } public class WasmAmountAttributeAdapter : AttributeAdapterBase<WasmAmountAttribute> { public WasmAmountAttributeAdapter(WasmAmountAttribute attribute, IStringLocalizer stringLocalizer) : base(attribute, stringLocalizer) { } public override void AddValidation(ClientModelValidationContext context) { MergeAttribute(context.Attributes, "data-val", "true"); MergeAttribute(context.Attributes, "data-val-wasm-amount", GetErrorMessage(context)); } public override string GetErrorMessage(ModelValidationContextBase validationContext) { return GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName()); } } }
Right-click on the Attributes folder again and choose Add, Class… from the context menu. Give the class the name WasmDescriptionAttribute and then replace the generated code with the following code:
using Microsoft.AspNetCore.Mvc.DataAnnotations; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.Localization; using System; using System.ComponentModel.DataAnnotations; namespace WebAssemblyInCSharp.Attributes { [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class WasmDescriptionAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { // Server-side validation goes here return ValidationResult.Success; } } public class WasmDescriptionAttributeAdapter : AttributeAdapterBase<WasmDescriptionAttribute> { public WasmDescriptionAttributeAdapter(WasmDescriptionAttribute attribute, IStringLocalizer stringLocalizer) : base(attribute, stringLocalizer) { } public override void AddValidation(ClientModelValidationContext context) { MergeAttribute(context.Attributes, "data-val", "true"); MergeAttribute(context.Attributes, "data-val-wasm-description", GetErrorMessage(context)); } public override string GetErrorMessage(ModelValidationContextBase validationContext) { return GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName()); } } }
You’ll come back to the WasmDescriptionAttribute class later in this article to add the WebAssembly validation logic.
Right-click on the Attributes folder one last time and choose Add, Class… from the context menu. Give the class the name CustomValidatiomAttributeAdapterProvider and then replace the generated code with the following code:
using Microsoft.AspNetCore.Mvc.DataAnnotations; using Microsoft.Extensions.Localization; using System.ComponentModel.DataAnnotations; namespace WebAssemblyInCSharp.Attributes { public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider { IValidationAttributeAdapterProvider baseProvider = new ValidationAttributeAdapterProvider(); public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer) { if (attribute is WasmAmountAttribute) { return new WasmAmountAttributeAdapter(attribute as WasmAmountAttribute, stringLocalizer); } else if (attribute is WasmDescriptionAttribute) { return new WasmDescriptionAttributeAdapter(attribute as WasmDescriptionAttribute, stringLocalizer); } return baseProvider.GetAttributeAdapter(attribute, stringLocalizer); } } }
The last step required for the new custom validation attributes to be recognized by C# is to add the CustomValidationAttributeAdapterProvider as a dependency.
Open the Startup.cs file and add the following line of code to the ConfigureServices method:
services.AddSingleton<IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
You’ll also need to include the following using statements at the top of the Startup.cs file:
using Microsoft.AspNetCore.Mvc.DataAnnotations; using WebAssemblyInCSharp.Attributes;
With the custom validation attributes defined, you can now create the expense model.
b. Create the expense model
In the Solution Explorer, right-click on the Models folder and choose Add, Class… from the context menu. Give the class the name Expense.
The model will have four properties: ID, Date, Amount, and Description. You’ll only be validating the Amount and Description properties for this article.
The Date property won’t display the time portion and will be made nullable so that it gets ignored by the validation.
The Amount property will be displayed formatted as a currency and will also be nullable so that your custom validation handles the check to see if a value has been specified. You’ll decorate this property with your new WasmAmount attribute.
You’ll decorate the Description property with your new WasmDescription attribute.
Replace the code that was generated in your Expense.cs file with the following code:
using System; using System.ComponentModel.DataAnnotations; using WebAssemblyInCSharp.Attributes; namespace WebAssemblyInCSharp.Models { public class Expense { public long ID { get; set; } // Only display the date itself (no time) [DisplayFormat(DataFormatString = "{0:d}")] public DateTime? Date { get; set; } // nullable so that validation is ignored for this // Display the value as a currency (e.g. $497.32) [DisplayFormat(DataFormatString = "{0:C}")] [WasmAmount(ErrorMessage = "The {0} must be greater than 0.")] public double? Amount { get; set; } // nullable so that the required field validation isn't triggered [WasmDescription(ErrorMessage = "The {0} must be provided.")] public string Description { get; set; } } }
With the expense model now created, you can create the expense entries and creation views.
c. Create the Expense views
Rather than creating a new view for the expense entries, you’ll adjust the auto-generated Home view.
In the Solution Explorer, expand the Views folder and then expand the Home folder. Open the Index.cshtml file.
The view will accept an IEnumberable of the Expense model and will loop through the list of expenses adding each as a row to a table.
Replace the contents of the Index.cshtml file with the following:
@model IEnumerable<Expense> @{ ViewData["Title"] = "Expense Entries"; } <h2>Expense Entries</h2> <p> <a asp-action="Create">Create New</a> </p> <table class="table"> <thead> <tr> <th>Date</th> <th>Amount</th> <th>Description</th> <th></th> </tr> </thead> <tbody> @foreach (Expense expense in Model) { <tr> <td>@Html.DisplayFor(modelItem => expense.Date)</td> <td>@Html.DisplayFor(modelItem => @expense.Amount)</td> <td>@Html.DisplayFor(modelItem => @expense.Description)</td> <td> <a asp-action="Edit" asp-route-id="@expense.ID">Edit</a> | <a asp-action="Delete" asp-route-id="@expense.ID">Delete</a> </td> </tr> } </tbody> </table>
Next, you’ll create the Create Expense page that will be displayed when the user clicks the Create New link at the top of the Expense Entries page.
In the Solution Explorer, right-click on the Home folder and choose Add, View from the context menu. Choose the Razor View – Empty template and click Add. Give the new view the name: Create.cshtml
On this page, you’ll only display the controls for the expense properties that you want to validate: Amount and Description. Because you’re creating a new expense record, the page will be displayed with the fields empty.
You’ll come back and add some script tags to the end of this page later in this article. For now, however, replace the auto-generated content in the Create.cshtml file with the following:
@model Expense @{ ViewData["Title"] = "Create"; } <h1>Create</h1> <h4>Expense</h4> <hr /> <div class="row"> <div class="col-md-4"> <form id="formCreate" asp-action="Create"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group"> <label asp-for="Amount" class="control-label"></label> <input asp-for="Amount" class="form-control" /> <span asp-validation-for="Amount" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Description" class="control-label"></label> <input asp-for="Description" class="form-control" /> <span asp-validation-for="Description" class="text-danger"></span> </div> <div class="form-group"> <input type="submit" value="Create" class="btn btn-primary" /> </div> </form> </div> </div>
If you tried to run this web application right now, you’d receive an object reference not set to an instance of an object error because you’re not yet passing down a list of expenses for the Expense Entries view to display.
To fix this, in the Solution Explorer, expand the Controllers folder and open the HomeController.cs file.
Being a prototype to test validation using WebAssembly, you’ll simulate having received data from a database by returning a Generic List of Expense objects.
Replace the content of the Index method with the code in the following snippet:
List<Expense> expenses = new List<Expense>() { new Expense { ID = 3254, Date = DateTime.Now.AddDays(-3), Amount = 497.32, Description = "3 nights at the hotel" }, new Expense { ID = 3255, Date = DateTime.Now.AddDays(-3), Amount = 54.75, Description = "Meals not covered by the conference" } }; return View(expenses);
While you’re in this file, you’ll add the Create method for the GET request that displays the Create Expense view when the user clicks on the Create New link on the Expense Entries form.
You’ll also add the POST method for when the user clicks the Create button to have the new expense entry saved.
Add the code in the following snippet to your HomeController.cs file after the Index method:
public IActionResult Create() { return View(); } [HttpPost] public IActionResult Create([Bind("Amount,Description")] Expense expense) { if (ModelState.IsValid) { // The data is ok. Save to the database. } return View(expense); }
This is optional and purely for aesthetic purposes, but I went into the Views, Shared folder in the Solution Explorer and opened the _Layout.cshtml file. In the _Layout.cshtml file, I changed all occurrences of the WebAssemblyInCSharp project name to ACME Expenses. I also removed the Privacy items.
If you ran this code now, you should see the Expense Entries view with the two expenses. Clicking the Create New link should bring you to the Create Expense view but clicking on the Create button won’t do anything yet because you haven’t implemented the validation checks.
With the web application now created and ready for the validation code, your next step as shown in the following image, is to create the C++ project that will be compiled into WebAssembly.
2. Create the C++ project
Right-click the Solution item in the Solution Explorer and choose Add, New Project… from the context menu.
Choose the C++ Console App and press the Next button. Give the project the name: Validation
To compile the C++ code to WebAssembly, you’re going to use the Emscripten toolkit that I’ll talk about in a moment. When you build the solution, the Visual Studio compiler will build the C++ files too, so you’ll want it to ignore Emscripten specific code. To do that you’ll wrap the Emscripten specific code in the __EMSCRIPTEN__ conditional compilation symbol.
Also, by default, a C++ compiler will adjust function names which makes it difficult to call a function if it’s no longer the original name you gave it. To prevent the C++ compiler from renaming the functions, you’ll place the functions within an extern “C” block.
When you created the C++ project, Visual Studio created a Valiadation.cpp file at the same time. Open the Validation.cpp file and replace the contents of the file with the following code:
#include <cstdio> #ifdef __EMSCRIPTEN__ #include <emscripten.h> #endif #ifdef __cplusplus extern "C" { // So that the C++ compiler doesn't adjust your function names #endif
The first function that you need to define is the IsAmountValid function. This function will receive a double parameter for the amount. If the amount is greater than 0.00, the function will return 1 to indicate that the amount is valid. If the amount is 0.00 or less, the function will return 0 indicating that the amount is invalid.
Add the code in the following snippet after the #endif of the extern “C” opening bracket in your Validation.cpp file:
#ifdef __EMSCRIPTEN__ EMSCRIPTEN_KEEPALIVE #endif int IsAmountValid(double amount) { return (amount > 0.00 ? 1 : 0); // 1 for true, 0 for false }
WebAssembly only supports four value types natively: 32-bit integers, 64-bit integers, 32-bit floats, and 64-bit floats. C++ pointers are represented as 32-bit integers. All other values need to be represented in memory.
To pass a string to a WebAssembly module, like you will need to when you check to see if the description is valid, you need to allocate a section of the module’s memory of the proper size for the string and then copy the string in. You then pass the module a pointer to that memory location.
To allocate a section of memory, you’ll define a function called CreateBuffer that creates a new character array pointer of the size requested.
Once you’re done with a section of memory, you need to release it so that it can be used by other code. To release the memory, you’ll define a FreeBuffer function that accepts a pointer to release.
Add the code in the following snippet after the IsAmountValid function in your Validation.cpp file:
#ifdef __EMSCRIPTEN__ EMSCRIPTEN_KEEPALIVE #endif char* CreateBuffer(int size) { return new char[size]; } #ifdef __EMSCRIPTEN__ EMSCRIPTEN_KEEPALIVE #endif void FreeBuffer(char* buffer) { delete[] buffer; }
Next, you’ll create the IsDescriptionValid function that will receive a pointer to the string in the module’s memory. If the string wasn’t specified (NULL) or is an empty string (starts with a null terminator) then the description isn’t considered valid. If a string was provided, then the description is valid.
Add the code in the following snippet after the FreeBuffer function in your Validation.cpp file:
#ifdef __EMSCRIPTEN__ EMSCRIPTEN_KEEPALIVE #endif int IsDescriptionValid(const char* description) { if ((description == NULL) || (description[0] == '\0')) { return 0; } return 1; }
The Visual Studio compiler will throw a compilation error if it doesn’t find a main function so you’ll need to include that as well. The main function is useful for the WebAssembly module as well because Emscripten sets it up as the main entry point for when the module is instantiated. You can use it to run code or just initialize variables or state in anticipation of a function call.
Add the code in the following snippet after your IsDescriptionValid function in your Validation.cpp file:
int main() { return 0; }
The last thing you need to do is add the closing curly brace for the extern “C” block. Add the code in the following snippet after your main function in your Validation.cpp file:
#ifdef __cplusplus } #endif
Now that you have your C++ code written, it’s time to set up Emscripten so that you can compile it into a WebAssembly module.
Compiling the code into a WebAssembly module
The Emscripten version used for this article is 1.39.20. If you don’t already have Emscripten installed on your machine, you can download it from the following web page by clicking on the green Code button and then clicking Download ZIP: https://github.com/emscripten-core/emscripten
The installation instructions for Emscripten can be found here: https://emscripten.org/docs/getting_started/downloads.html
There are several ways that WebAssembly modules can be created using Emscripten. By default, when Emscripten generates the WebAssembly file it also generates a JavaScript file that contains a number of helper methods as well as the code needed to load and instantiate the WebAssembly module for your web page.
Emscripten also allows you to compile the WebAssembly module as stand-alone where it only builds the module itself (.wasm) and doesn’t generate a JavaScript file. While you could use this version of the module in JavaScript as well, it’s really intended for use with runtimes that support the WebAssembly System Interface (WASI) and you would need to provide additional functions that the module is expecting as a result.
INFO: WASI is a standard interface for making calls to the underlying system in a secure way. Like with traditional WebAssembly modules, with WASI, the modules are secure by default. They only have access to what you pass them. For more details about WASI and the motivation behind it, I highly recommend reading Lin Clark’s article: https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/
For this solution, you’ll have Emscripten build the WebAssembly module twice. Once as a stand-alone module that your C# code will use and once as a normal module for use by your JavaScript code.
To configure the project to have Emscripten build the C++ code into a WebAssembly module, right-click on the root of the Validation project in the Solution Explorer and choose Properties from the context menu.
Expand the Build Events item and click on the Post-Build Event item.
Click into the textbox to the right of the Command Line label and paste the following to have the WebAssembly files compiled and placed in the web application’s wwwroot\js\ folder:
call emcc Validation.cpp -O3 -o "$(SolutionDir)wwwroot\js\ValidationServer.wasm" call emcc Validation.cpp -O3 -o "$(SolutionDir)wwwroot\js\ValidationClient.js"
Now that you have your WebAssembly files, your next step as shown in the following image, is to add the JavaScript to your web application so that the amount field is validated by your WebAssembly module.
3. JavaScript validation using a WebAssembly module
In the Solution Explorer, expand wwwroot in your web application project. Right-click on the js folder and choose Add, New Item… from the context menu. Choose JavaScript file and give it the name: home_create.js
In this file, you’re going to create a function for JQuery’s validator called wasm-amount. The function will return false if a non-number has been entered into the amount textbox. If a number was entered, the function will call the WebAssembly module’s IsAmountValid function passing in the value.
Add the code in the following snippet to your home_create.js file:
(function ($) { const $jQval = $.validator; $jQval.addMethod("wasm-amount", function (value, element, params) { // Convert the string to a float. If it's not a number (character or empty string) then indicate the // value isn't valid. const number = parseFloat(value); if (isNaN(number)) { return false; } // Return if the number is valid return Module._IsAmountValid(number); }); const adapters = $jQval.unobtrusive.adapters; adapters.addBool("wasm-amount"); })(jQuery);
As you may have noticed with the previous code snippet, there’s no wasm-description function defined. This was done intentionally so that you can see the server error if the description isn’t specified.
This also showcases another reason why you’d want validation on the client and on the server. If something were to happen where the frontend developer would forget to include a function, or perhaps misspelled something, then you could unknowingly be allowing bad data into your database if you didn’t also verify things on the server.
Open your Create.cshtml file and scroll to the bottom. After the HTML, you’re going to add an @section for Scripts.
The first script that you’ll include is the Emscripten generated JavaScript file (ValidationClient.js) that will handle downloading and instantiating the WebAssembly module for you. Next, you’ll call the Html.RenderPartialAsync function to load in the jQuery validation JavaScript files. Finally, you’ll include a link for the home_create.js file that you just created.
Add the following code to the bottom of your Create.cshtml file:
@section Scripts { <script src="~/js/ValidationClient.js"></script> @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } <script src="~/js/home_create.js"></script> }
With your JavaScript now validating using the WebAssembly module, your next step as shown in the following image, is to adjust the C# code to also use the module to validate the data.
4. C# validation using a WebAssembly module
Earlier, in the Compiling the code into a WebAssembly module section, I talked about the WebAssembly System Interface that’s being designed to provide a standard way to run WebAssembly modules outside the browser safely.
There are several runtimes that have been created to allow WebAssembly modules to run outside the browser. In this article you’re going to use the Wasmtime runtime but there are others available. If you’re interested, the following web page maintains a curated list WebAssembly runtimes: https://github.com/appcypher/awesome-wasm-runtimes
To use the .NET Wasmtime runtime, you need to add the Wasmtime package to your solution. To do this, click on the Tools, NuGet Package Manager, Package Manager Console menu. At the command line paste the following:
dotnet add package Wasmtime --version 0.19.0-preview1
Open your WasmDescriptionAttribute.cs file and navigate to the IsValid function.
I chose to show you the description validation rather than the amount’s validation because strings are more involved than numbers with WebAssembly. That’s because WebAssembly supports 32-bit and 64-bit integers and floats natively but things like strings need to be represented in the module’s memory.
I’ll show you the full function in a moment but will step through it line by line first.
The first steps to interacting with a WebAssembly module using Wasmtime is the same for all function calls. First you need to create an Engine instance that represents the Wasmtime runtime. Then you create a Host object instance passing in the engine instance as shown in the following snippet:
using Engine engine = new Engine(); using Host host = new Host(engine);
Because WASI is still a work in progress, a module might have been built according to an older version of the specification. At the time of this article’s writing, Emscripten’s stand-alone module is built based on the wasi_snapshot_preview1 specification.
If you don’t specify a version of the specification, Wasmtime will default to an older version and you’ll receive the following error:
'unknown import: `wasi_snapshot_preview1::proc_exit` has not been defined'
To tell Wasmtime to use wasi_snapshot_preview1, you pass the string to the DefineWasi function of the Host instance as shown in the following snippet:
host.DefineWasi("wasi_snapshot_preview1");
Next, you need to create a Module object instance that represents the WebAssembly module. Here, you’ll specify the module’s physical location to load but you can also pass in the bytes, a stream, or even a WebAssembly Text Format version of a module.
As shown in the following snippet, you’ll load the stand-alone version of the module:
using Module module = Module.FromFile(engine, "wwwroot\\js\\ValidationServer.wasm");
Now that everything’s defined, you create an instance of the module as shown in the following snippet:
using dynamic instance = host.Instantiate(module);
With the module instance, you now have access to everything the module exported. If wanted to call the IsAmountValid function for example, you could do so at this point the following: int result = instance.IsAmountValid(1.00);
Because the IsDescriptionValid function expects a char* parameter (a string), and because WebAssembly doesn’t natively support strings, you’ll need to place a copy of the description into the module’s memory.
Before you do that, however, you need to make sure that nothing else writes to the address space where the string will be placed until you’re done with the string. To prevent writes, you need to allocate the space needed using a C++ function like malloc or by using the new keyword.
In the C++ that you wrote, you defined a CreateBuffer function that accepts a size and it then allocates the memory needed returning a pointer to the start of that address space. All that your code needs to do is call the function passing in the number of bytes in the description string as shown in the following snippet:
Int32 pointer = instance.CreateBuffer(System.Text.Encoding.UTF8.GetByteCount(description));
Once you have a section of the module’s memory set aside for the string, you need to copy the string into the memory as shown in the following snippet:
var memory = instance.Externs.Memories[0]; memory.WriteString(pointer, description);
With the string now in the module’s memory, you can call the module’s IsDescriptionValid function passing it the pointer to the string’s location as shown in the following snippet:
int result = instance.IsDescriptionValid(pointer);
An important step to remember is that, once you’re done with the memory, you need to release it or you’ll end up with a memory leak. You free the memory by calling the FreeBuffer function that you defined in the module and passing it the pointer as shown in the following snippet:
instance.FreeBuffer(pointer);
The full code for the IsValid function is shown in the following code snippet. Replace the contents of your IsValid function, in your WasmDescriptionAttribute class, with the following code:
string description = (value == null ? "": (string)value); using Engine engine = new Engine(); using Host host = new Host(engine); // This line is required. Without it, you'll receive the following error: // 'unknown import: `wasi_snapshot_preview1::proc_exit` has not been defined' host.DefineWasi("wasi_snapshot_preview1"); using Module module = Module.FromFile(engine, "wwwroot\\js\\ValidationServer.wasm"); using dynamic instance = host.Instantiate(module); Int32 pointer = 0; ValidationResult response = ValidationResult.Success; try { pointer = instance.CreateBuffer(System.Text.Encoding.UTF8.GetByteCount(description)); var memory = instance.Externs.Memories[0]; memory.WriteString(pointer, description); if (instance.IsDescriptionValid(pointer) == 0) { response = new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } } finally { instance.FreeBuffer(pointer); } return response;
You’ll also need to include the following using statement at the top of the WasmDescriptionAttribute.cs file:
using Wasmtime;
Now that the web application has been created and all the components are in place, it’s time to give it a test.
Verify the results
Rebuild the solution to make sure the WebAssembly files are created and then run it to see your Expense Entries page:
Click the Create New link to view your Create Expense page:
If you enter 0 (zero) in the Amount field and then click away, you’ll see your custom JavaScript validation function catch that the amount is not greater than zero:
Adjust the Amount to be 1 (one) but leave the Description field empty and then press the Create button.
Because you didn’t implement a JavaScript validation function for the Description field, a postback will happen where the form’s data will be sent to the server and your server-side validation will catch the issue. When the page reloads, you’ll see the validation error about the Description field:
Summary
As you learned in this article, it’s possible to implement custom model validation in an ASP.NET Core MVC web application for both the JavaScript and C# code.
Emscripten can be instructed to build a stand-alone WebAssembly module that doesn’t include the generated JavaScript file. The stand-alone module can then be used in runtimes that support the WebAssembly System Interface (WASI).
With the help of a WebAssembly runtime like Wasmtime you can use your WebAssembly module in C#!
About Uno Platform
For those new to Uno Platform – it enables for creation of pixel-perfect, single-source C# and XAML apps which run natively on Windows, iOS, Android, macOS, Linux and WebAssembly. Uno Platform is Open Source (Apache 2.0) and available on GitHub. To learn more about Uno Platform, see how it works, or create a small sample app.
Guest blog post by Gerard Gallant, the author of the book “WebAssembly in Action” and a Senior software developer / architect @dovico