{"id":42440,"date":"2022-09-26T09:00:13","date_gmt":"2022-09-26T16:00:13","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=42440"},"modified":"2024-12-13T15:11:04","modified_gmt":"2024-12-13T23:11:04","slug":"use-net-7-from-any-javascript-app-in-net-7","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/use-net-7-from-any-javascript-app-in-net-7\/","title":{"rendered":"Use .NET from any JavaScript app in .NET 7"},"content":{"rendered":"<p>.NET 7 provides improved support for running .NET on WebAssembly in JavaScript-based apps, including a rich JavaScript interop mechanism. The WebAssembly support in .NET 7 is the basis for Blazor WebAssembly apps but can be used independently of Blazor too. Existing JavaScript apps can use the expanded WebAssembly support in .NET 7 to reuse .NET libraries from JavaScript or to build completely novel .NET-based apps and frameworks. Blazor WebAssembly apps can also use the new JavaScript interop mechanism to optimize interactions with JavaScript and the web platform. In this post, we&#8217;ll take a look at the new JavaScript interop support in .NET 7 and use it to build the classic TodoMVC sample app. We&#8217;ll also look at using the new JavaScript interop app from a Blazor WebAssembly app.<\/p>\n<h2>TL;DR<\/h2>\n<p>Fork the samples:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc\">https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/pavelsavara\/blazor-wasm-hands-pose\">https:\/\/github.com\/pavelsavara\/blazor-wasm-hands-pose<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/maraf\/dotnet-wasm-react\">https:\/\/github.com\/maraf\/dotnet-wasm-react<\/a><\/li>\n<\/ul>\n<p>The new JavaScript interop is controlled by attributes from the <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.runtime.interopservices.javascript?view=net-7.0\">System.Runtime.InteropServices.JavaScript<\/a> namespace.<\/p>\n<p>Live demos:<\/p>\n<ul>\n<li><a href=\"https:\/\/pavelsavara.github.io\/dotnet-wasm-todo-mvc\">https:\/\/pavelsavara.github.io\/dotnet-wasm-todo-mvc<\/a><\/li>\n<li><a href=\"https:\/\/pavelsavara.github.io\/blazor-wasm-hands-pose\">https:\/\/pavelsavara.github.io\/blazor-wasm-hands-pose<\/a><\/li>\n<li><a href=\"https:\/\/maraf.github.io\/dotnet-wasm-react\/\">https:\/\/maraf.github.io\/dotnet-wasm-react\/<\/a><\/li>\n<\/ul>\n<h2>TodoMVC<\/h2>\n<p>TodoMVC is great <a href=\"https:\/\/todomvc.com\/\">community project<\/a> which helps JavaScript developers compare the features of various UI frameworks.<\/p>\n<p>To show how the new JS interop support in .NET 7 works, let&#8217;s create a C# port of TodoMVC based on the <a href=\"https:\/\/github.com\/tastejs\/todomvc\/tree\/gh-pages\/examples\/vanilla-es6\">vanilla-es6<\/a> version. We won&#8217;t try to convert all the UI logic, just the app logic.<\/p>\n<h2>How-to<\/h2>\n<p><strong>This post only shows interesting snippets, not the whole code.<\/strong>\nTo follow along, please copy &amp; paste from the sample <a href=\"https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc\">repo on github<\/a>.<\/p>\n<p>To get started, install the <a href=\"https:\/\/dotnet.microsoft.com\/download\/dotnet\/7.0\">.NET 7 RC1 SDK<\/a> (or later) and run the following commands:<\/p>\n<pre><code class=\"language-.sh\">dotnet workload install wasm-tools\r\ndotnet workload install wasm-experimental\r\ndotnet new wasmbrowser<\/code><\/pre>\n<p>The <code>wasm-experimental<\/code> workload contains experimental project templates for getting started with .NET on WebAssembly in a browser app (WebAssembly Browser App) or in a Node.js based console app (WebAssembly Console App). Here we&#8217;re using the browser-based template. The developer experience for these project templates is still a work in progress, but the APIs used in them are fully supported in .NET 7.<\/p>\n<p>Open the folder in your favorite code editor and change the <strong>Program.cs<\/strong> to match <a href=\"https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc\/blob\/main\/Program.cs\">Program.cs<\/a> from the sample.<\/p>\n<pre><code class=\"language-.cs\">using System;\r\nusing System.Runtime.InteropServices.JavaScript;\r\nusing System.Threading.Tasks;\r\n\r\nnamespace TodoMVC\r\n{\r\n    public partial class MainJS\r\n    {\r\n        static Controller? controller;\r\n\r\n        public static async Task Main()\r\n        {\r\n            if (!OperatingSystem.IsBrowser())\r\n            {\r\n                throw new PlatformNotSupportedException(\"This demo is expected to run on browser platform\");\r\n            }\r\n\r\n            await JSHost.ImportAsync(\"todoMVC\/store.js\", \".\/store.js\");\r\n            await JSHost.ImportAsync(\"todoMVC\/view.js\", \".\/view.js\");\r\n\r\n            var store = new Store();\r\n            var view = new View(new Template());\r\n            controller = new Controller(store, view);\r\n            Console.WriteLine(\"Ready!\");\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<p>Add <a href=\"https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc\/blob\/main\/Item.cs\">Item.cs<\/a>, <a href=\"https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc\/blob\/main\/Controller.cs\">Controller.cs<\/a>, <a href=\"https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc\/blob\/main\/Template.cs\">Template.cs<\/a> which are C# ports of the ES6 sample code. Also add <a href=\"https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc\/blob\/main\/helpers.js\">helpers.js<\/a> as it is.<\/p>\n<h3>Store<\/h3>\n<p><a href=\"https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc\/blob\/main\/Store.cs\">Store.cs<\/a> is also almost the same as the ES6 version, except the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Window\/localStorage\">localStorage API<\/a> is wrapped by <a href=\"https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc\/blob\/main\/store.js\">store.js<\/a><\/p>\n<pre><code class=\"language-.js\">export function setLocalStorage(todosJson) {\r\n  window.localStorage.setItem('dotnet-wasm-todomvc', todosJson);\r\n}\r\n\r\nexport function getLocalStorage() {\r\n  return window.localStorage.getItem('dotnet-wasm-todomvc') || '[]';\r\n};<\/code><\/pre>\n<p>Bind these JavaScript functions to C# code using <code>JSImportAttribute<\/code> in <code>Store.cs<\/code><\/p>\n<pre><code class=\"language-.cs\">static partial class Interop\r\n{\r\n    [JSImport(\"setLocalStorage\", \"todoMVC\/store.js\")]\r\n    internal static partial void _setLocalStorage(string json);\r\n\r\n    [JSImport(\"getLocalStorage\", \"todoMVC\/store.js\")]\r\n    internal static partial string _getLocalStorage();\r\n}<\/code><\/pre>\n<p>The first parameter of the attribute <code>\"setLocalStorage\"<\/code> is a name of a JS function.\nThe function is exported from the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Guide\/Modules\">ES6 module<\/a> named <code>\"todoMVC\/store.js\"<\/code>.\nThe module name needs to be unique for all libraries in the application. The prefix <code>todoMVC\/<\/code> solves that.\nIt also maps to the name used in <code>JSHost.ImportAsync<\/code> in the <code>Main.cs<\/code>.<\/p>\n<h3>View<\/h3>\n<p>The last and most complex part is the View. Most of the real implementation is left in JavaScript in <a href=\"https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc\/blob\/main\/view.js\">view.js<\/a>.\nUnlike with Blazor, the new <code>wasmbrowser<\/code> template doesn&#8217;t include any UI framework, so we&#8217;ll leave all the UI logic in JavaScript.<\/p>\n<pre><code class=\"language-.js\">export function removeItem(id) {\r\n  const elem = qs(`[data-id=\"${id}\"]`);\r\n  if (elem) {\r\n    $todoList.removeChild(elem);\r\n  }\r\n}\r\n\r\nexport function bindAddItem(handler) {\r\n  $on($newTodo, 'change', ({ target }) =&gt; {\r\n    const title = target.value.trim();\r\n    if (title) {\r\n      handler(title);\r\n    }\r\n  });\r\n}<\/code><\/pre>\n<p><a href=\"https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc\/blob\/main\/View.cs\">View.cs<\/a> binds the JS functions and makes them callable from C#. A couple of these imported functions are shown below, but there are more in the full sample.<\/p>\n<pre><code class=\"language-.cs\">public static partial class Interop\r\n{\r\n    [JSImport(\"removeItem\", \"todoMVC\/view.js\")]\r\n    public static partial void removeItem([JSMarshalAs&lt;JSType.Number&gt;] long id);\r\n\r\n    [JSImport(\"bindAddItem\", \"todoMVC\/view.js\")]\r\n    public static partial void bindAddItem(\r\n        [JSMarshalAs&lt;JSType.Function&lt;JSType.String&gt;&gt;] Action&lt;string&gt; handler);\r\n}<\/code><\/pre>\n<p>The <code>removeItem<\/code> is passing <code>Int64<\/code> as an argument. The marshaller is configured to translate it as <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Number\"><code>JSType.Number<\/code><\/a> which can only represent a 52-bit integer.\nThe alternative would be to marshal it as <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/BigInt\"><code>JSType.BigInt<\/code><\/a> which is bit more expensive.\nSince the runtime doesn&#8217;t want to guess which conversion is preferred in the use case, the developer needs to annotate the method parameter with <code>[JSMarshalAs&lt;T&gt;]<\/code>.<\/p>\n<p>In C# 11, it&#8217;s now possible to <a href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/whats-new\/csharp-11#generic-attributes\">use generic instances as custom attributes<\/a>, such as <code>JSMarshalAsAttribute&lt;T&gt;<\/code> in the above code. Enable the preview language version with <code>&lt;LangVersion&gt;preview&lt;\/LangVersion&gt;<\/code> in the project file.<\/p>\n<p>The <code>bindAddItem<\/code> is even more interesting. It&#8217;s passing a strongly typed callback delegate <code>handler<\/code>.\nAn explicit definition for the marshaller also needs to be there.\nIt&#8217;s a <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Function\"><code>Function<\/code><\/a> with a first argument of type <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/String\"><code>String<\/code><\/a>.\nThis callback is registered by <code>$on<\/code> helper with the DOM <code>change<\/code> event for the <code>&lt;input class=\"new-todo\"\/&gt;<\/code> when we call <code>bindAddItem<\/code> function.<\/p>\n<h3>Main<\/h3>\n<p>Copy the contents of <a href=\"https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc\/blob\/main\/index.html\">index.html<\/a> from the sample. The interesting part is<\/p>\n<pre><code class=\"language-.html\">&lt;script type='module' src=\".\/main.js\"\/&gt;<\/code><\/pre>\n<p>It&#8217;s loading <code>main.js<\/code> which will import <code>.\/dotnet.js<\/code> and start the .NET runtime.<\/p>\n<p>Copy the contents of <a href=\"https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc\/blob\/main\/main.js\">main.js<\/a>. The interesting part is<\/p>\n<pre><code class=\"language-.js\">import { dotnet } from '.\/dotnet.js'\r\n\r\nawait dotnet.run();<\/code><\/pre>\n<p>Edit the project file and add<\/p>\n<pre><code class=\"language-.diff\">&lt;Project Sdk=\"Microsoft.NET.Sdk\"&gt;\r\n    &lt;PropertyGroup&gt;\r\n        &lt;TargetFramework&gt;net7.0&lt;\/TargetFramework&gt;\r\n        &lt;WasmMainJSPath&gt;main.js&lt;\/WasmMainJSPath&gt;\r\n        &lt;OutputType&gt;Exe&lt;\/OutputType&gt;\r\n        &lt;Nullable&gt;enable&lt;\/Nullable&gt;\r\n        &lt;AllowUnsafeBlocks&gt;true&lt;\/AllowUnsafeBlocks&gt;\r\n+        &lt;LangVersion&gt;preview&lt;\/LangVersion&gt;\r\n    &lt;\/PropertyGroup&gt;\r\n\r\n    &lt;ItemGroup&gt;\r\n        &lt;WasmExtraFilesToDeploy Include=\"index.html\" \/&gt;\r\n-        &lt;WasmExtraFilesToDeploy Include=\"main.js\" \/&gt; \r\n+        &lt;WasmExtraFilesToDeploy Include=\"*.js\" \/&gt;\r\n+        &lt;WasmExtraFilesToDeploy Include=\"*.css\" \/&gt;\r\n    &lt;\/ItemGroup&gt;\r\n&lt;\/Project&gt;<\/code><\/pre>\n<h3>JSExport<\/h3>\n<p>The code above demonstrates how to pass callback from C# to JavaScript so that events fired by the browser can be handled by managed code. There is also another possibility: static methods annotated with <code>JSExportAttribute<\/code> that are callable from JavaScript.<\/p>\n<pre><code class=\"language-.cs\">namespace TodoMVC\r\n{\r\n    public partial class MainJS\r\n    {\r\n        [JSExport]\r\n        public static void OnHashchange(string url)\r\n        {\r\n            controller?.SetView(url);\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<p>The <a href=\"https:\/\/github.com\/pavelsavara\/dotnet-wasm-todo-mvc\/blob\/main\/main.js\">main.js<\/a> uses <code>getAssemblyExports<\/code> JavaScript API to get all the exports of the assembly.<\/p>\n<pre><code class=\"language-.js\">const exports = await getAssemblyExports(getConfig().mainAssemblyName);\r\nexports.TodoMVC.MainJS.OnHashchange(document.location.hash);<\/code><\/pre>\n<p>The code uses <code>getConfig().mainAssemblyName<\/code> so that <code>\"TodoMVC.dll\"<\/code> doesn&#8217;t have to be hard-coded.\nThe <code>exports<\/code> object contains the same namespace <code>TodoMVC<\/code>, the same class <code>MainJS<\/code> and the same method <code>OnHashchange<\/code> as the C# code.\nThe parameters of the call will be marshalled using the same rules as for <code>[JSImport]<\/code>.\nMarshaling in both directions is governed by the C# method signature and the <code>[JSMarshalAs&lt;T&gt;]<\/code> annotations on the parameters and return values.<\/p>\n<h3>Run the app<\/h3>\n<p>Start the app with the following command (*) :<\/p>\n<pre><code class=\"language-.sh\">dotnet run<\/code><\/pre>\n<p>* This is unfortunately broken on Windows for RC1 and it should be <a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/75295\">fixed in RC2<\/a><\/p>\n<p>For RC1 on Windows you could run following commands instead with similar results:<\/p>\n<pre><code class=\"language-.sh\">dotnet tool update dotnet-serve --global\r\ndotnet serve --directory bin\\Debug\\net7.0\\browser-wasm\\AppBundle<\/code><\/pre>\n<p>You should see output similar to this. You can click on one of the URLs and test the application in your browser.<\/p>\n<pre><code class=\"language-.sh\">WasmAppHost --runtime-config C:\\Dev\\dotnet-wasm-todo-mvc\\bin\\Debug\\net7.0\\browser-wasm\\AppBundle\\TodoMVC.runtimeconfig.json\r\nApp url: http:\/\/127.0.0.1:9000\/index.html\r\nApp url: https:\/\/127.0.0.1:58139\/index.html<\/code><\/pre>\n<p>You can see a live demo of the completed app at <a href=\"https:\/\/pavelsavara.github.io\/dotnet-wasm-todo-mvc\">https:\/\/pavelsavara.github.io\/dotnet-wasm-todo-mvc<\/a>.\n<a href=\"https:\/\/pavelsavara.github.io\/dotnet-wasm-todo-mvc\"><img decoding=\"async\" class=\"aligncenter\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2022\/09\/todomvcnet7rc1.png\" alt=\"image\" width=\"534\" height=\"452\" \/><\/a><\/p>\n<h3>Optimize the app<\/h3>\n<p>It&#8217;s possible to preload the files <code>dotnet.js<\/code> and <code>mono-config.json<\/code> which are the early dependencies for the runtime to begin starting.<\/p>\n<p>It&#8217;s also possible to prefetch the largest binary files.\nIn a real production application you should measure the impact of these optimizations.\nDepending on the expected bandwidth and the latency of the target device&#8217;s connectivity, strike a good balance of what to prefetch.<\/p>\n<pre><code class=\"language-.html\">&lt;head&gt;\r\n  &lt;script type='module' src=\".\/dotnet.js\"\/&gt;\r\n  &lt;link rel=\"preload\" href=\".\/mono-config.json\" as=\"fetch\" crossorigin=\"anonymous\"&gt;\r\n  &lt;link rel=\"prefetch\" href=\".\/dotnet.wasm\" as=\"fetch\" crossorigin=\"anonymous\"&gt;\r\n  &lt;link rel=\"prefetch\" href=\".\/icudt.dat\" as=\"fetch\" crossorigin=\"anonymous\"&gt;\r\n  &lt;link rel=\"prefetch\" href=\".\/managed\/System.Private.CoreLib.dll\" as=\"fetch\" crossorigin=\"anonymous\"&gt;\r\n&lt;\/head&gt;<\/code><\/pre>\n<p>In the project file enable ahead-of-time (AOT) compilation to improve speed.\nEnable trimming of unused code to reduce download size.<\/p>\n<pre><code class=\"language-.diff\">&lt;Project Sdk=\"Microsoft.NET.Sdk\"&gt;\r\n  &lt;PropertyGroup&gt;\r\n        &lt;TargetFramework&gt;net7.0&lt;\/TargetFramework&gt;\r\n        &lt;WasmMainJSPath&gt;main.js&lt;\/WasmMainJSPath&gt;\r\n        &lt;OutputType&gt;Exe&lt;\/OutputType&gt;\r\n        &lt;Nullable&gt;enable&lt;\/Nullable&gt;\r\n        &lt;AllowUnsafeBlocks&gt;true&lt;\/AllowUnsafeBlocks&gt;\r\n+        &lt;PublishTrimmed&gt;true&lt;\/PublishTrimmed&gt;\r\n+        &lt;TrimMode&gt;full&lt;\/TrimMode&gt;\r\n+        &lt;RunAOTCompilation&gt;true&lt;\/RunAOTCompilation&gt;\r\n    &lt;\/PropertyGroup&gt;\r\n&lt;\/Project&gt;<\/code><\/pre>\n<p>Please note that while AOT code runs much faster than interpreted IL, it increases size of the binary download.<\/p>\n<p>When trimming unused code, the components which are used dynamically (for example via reflection) need to be protected from trimming.<code class=\"language-.cs\"><\/code><\/p>\n<p>In order to publish with AOT compilation, install the <code>wasm-tools<\/code> workload and then publish the app.<\/p>\n<pre><code class=\"language-.sh\">dotnet workload install wasm-tools\r\ndotnet publish -c Release<\/code><\/pre>\n<p>We can use the <code>dotnet-serve<\/code> tool to host the published app locally. Using the correct MIME type for <code>.wasm<\/code> allows the browser to use streaming instantiation of the WebAssembly module.<\/p>\n<pre><code class=\"language-.sh\">dotnet tool install --global dotnet-serve\r\ndotnet serve --mime .wasm=application\/wasm --mime .js=text\/javascript --mime .json=application\/json --directory bin\\Release\\net7.0\\browser-wasm\\AppBundle\\<\/code><\/pre>\n<p>Compressing the binary assets is also good idea but it&#8217;s out of the scope of this article.<\/p>\n<h3>Interop performance<\/h3>\n<p>We test the performance of the new JS interop model by running a set of microbenchmarks regularly.\nFor example, this graph shows 10000 calls to a trivial JavaScript method using the C# signature <code>[JSImport] int Echo(int value)<\/code>.\nCurrently we measure various build configurations and browsers on a small ODROID-N2+.\n<img decoding=\"async\" class=\"aligncenter\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2022\/09\/interopperf.png\" alt=\"image\" \/><\/p>\n<p>Marshalling of primitive numbers and <code>IntPtr<\/code> is fast.\nNote in the graph the difference AOT can make.\nMarshalling <code>string<\/code> requires us to allocate memory and copy the bits, so it&#8217;s slower.\nMarshaling <code>Span&lt;byte&gt;<\/code> is cheap but the <code>MemoryView<\/code> on JavaScript side is valid only for the duration of the call.\nMarshalling a proxy of <code>object<\/code> involves an object allocation, <code>GCHandle<\/code>, and two GCs.\nMarshaling <code>Task<\/code>, <code>Exception<\/code> and also <code>ArraySegment&lt;byte&gt;<\/code> is similar as they also allocate a <code>GCHandle<\/code>.<\/p>\n<h2>Blazor WebAssembly<\/h2>\n<p>The new interop with <code>[JSImport]<\/code> and <code>[JSExport]<\/code> is also available in Blazor WebAssembly apps.\nIt&#8217;s useful when you need to integrate with the browser on the client side only.\nWhen you need to call your JavaScript also from the server side or in a native hybrid app, please use the existing <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/microsoft.jsinterop.ijsruntime?view=aspnetcore-7.0\">IJSRuntime<\/a> interface, which does JSON serialization and a remote dispatch.<\/p>\n<p>For a simple sample, please have a look at the <a href=\"https:\/\/github.com\/pavelsavara\/blazor-wasm-hands-pose\">hands demo<\/a> of 3rd party video processing JavaScript library integrated into Blazor WASM.<\/p>\n<p>You can see a live demo of the app is at <a href=\"https:\/\/pavelsavara.github.io\/blazor-wasm-hands-pose\">https:\/\/pavelsavara.github.io\/blazor-wasm-hands-pose<\/a>\n<a href=\"https:\/\/pavelsavara.github.io\/blazor-wasm-hands-pose\"><img decoding=\"async\" class=\"aligncenter\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2022\/09\/blazorhands.png\" alt=\"image\" width=\"582\" height=\"478\" \/><\/a><\/p>\n<h2>Legacy interop<\/h2>\n<p>Prior to .NET 7, to perform low-level JavaScript interop in Blazor WebAssembly apps you may have used the undocumented APIs grouped in the <code>MONO<\/code> and <code>BINDING<\/code> JavaScript namespaces.\nThose APIs are still there in .NET 7 for backward compatibility reason, but please consider them deprecated.\nThey expose the user to raw pointers to managed objects and are not protected from GC and WASM memory resize.\nIn Blazor the <code>IJSUnmarshalledRuntime<\/code> interface has similar downsides and is now also deprecated.\nThe new interop with <code>[JSImport]<\/code> and <code>[JSExport]<\/code> should be a faster and safer replacement.<\/p>\n<p>If your use case can&#8217;t be implemented by the new API or if you found a bug, please let us know by <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/new\/choose\">creating a new issue<\/a> on GitHub.<\/p>\n<h2>Conclusion<\/h2>\n<p>We hope that these new features will allow developers to create better integration between the JavaScript ecosystem and .NET. With these new features .NET developers can now wrap and use existing JavaScript libraries within existing frameworks like Blazor or Uno, or use them directly, like in this demo.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Port of the famous TodoMVC to .NET on WASM. Showcase of the JavaScript interop and running .NET in the browser, with or without Blazor.<\/p>\n","protected":false},"author":1820,"featured_media":42443,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,7251,756],"tags":[7675,7674,7372,7673],"class_list":["post-42440","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-blazor","category-csharp","tag-browser","tag-interop","tag-javascript","tag-wasm"],"acf":[],"blog_post_summary":"<p>Port of the famous TodoMVC to .NET on WASM. Showcase of the JavaScript interop and running .NET in the browser, with or without Blazor.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/42440","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/1820"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=42440"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/42440\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/42443"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=42440"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=42440"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=42440"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}