{"id":24760,"date":"2021-04-08T13:35:14","date_gmt":"2021-04-08T20:35:14","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/aspnet\/?p=24760"},"modified":"2021-04-08T13:35:14","modified_gmt":"2021-04-08T20:35:14","slug":"asp-net-core-updates-in-net-6-preview-3","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/asp-net-core-updates-in-net-6-preview-3\/","title":{"rendered":"ASP.NET Core updates in .NET 6 Preview 3"},"content":{"rendered":"<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/announcing-net-6-preview-3\/\">.NET 6 Preview 3 is now available<\/a> and includes many great new improvements to ASP.NET Core.<\/p>\n<p>Here&#8217;s what&#8217;s new in this preview release:<\/p>\n<ul>\n<li>Smaller SignalR, Blazor Server, and MessagePack scripts<\/li>\n<li>Enable Redis profiling sessions<\/li>\n<li>HTTP\/3 endpoint TLS configuration<\/li>\n<li>Initial .NET Hot Reload support<\/li>\n<li>Razor compiler no longer produces a separate Views assembly<\/li>\n<li>Shadow-copying in IIS<\/li>\n<li>Vcpkg port for SignalR C++ client<\/li>\n<li>Reduced memory footprint for idle TLS connections<\/li>\n<li>Remove slabs from the <code>SlabMemoryPool<\/code><\/li>\n<li><code>BlazorWebView<\/code> controls for WPF &amp; Windows Forms<\/li>\n<\/ul>\n<h2>Get started<\/h2>\n<p>To get started with ASP.NET Core in .NET 6 Preview 3, <a href=\"https:\/\/dotnet.microsoft.com\/download\/dotnet\/6.0\">install the .NET 6 SDK<\/a>.<\/p>\n<p>If you&#8217;re on Windows using Visual Studio, we recommend <a href=\"http:\/\/visualstudio.com\/preview\">installing the latest preview of Visual Studio 2019 16.10<\/a>. If you&#8217;re on macOS, we recommend <a href=\"https:\/\/docs.microsoft.com\/visualstudio\/releasenotes\/vs2019-mac-preview-relnotes\">installing the latest preview of Visual Studio 2019 for Mac 8.10<\/a>.<\/p>\n<h2>Upgrade an existing project<\/h2>\n<p>To upgrade an existing ASP.NET Core app from .NET 6 Preview 2 to .NET 6 Preview 3:<\/p>\n<ul>\n<li>Update all Microsoft.AspNetCore.&#042; package references to <code>6.0.0-preview.3.*<\/code>.<\/li>\n<li>Update all Microsoft.Extensions.&#042; package references to <code>6.0.0-preview.3.*<\/code>.<\/li>\n<li>Update calls to <code>TryRedeemPersistedState<\/code> on <code>ComponentApplicationState<\/code> to call <code>TryTakePersistedState<\/code> instead.<\/li>\n<li>Update calls to <code>TryRedeemFromJson<\/code> on <code>ComponentApplicationState<\/code> to call <code>TryTakeAsJson<\/code> instead.<\/li>\n<\/ul>\n<p>See the full list of <a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/compatibility\/6.0#aspnet-core\">breaking changes<\/a> in ASP.NET Core for .NET 6.<\/p>\n<h2>Smaller SignalR, Blazor Server, and MessagePack scripts<\/h2>\n<p>Thanks to a community contribution from <a href=\"https:\/\/github.com\/benaadams\">Ben Adams<\/a> the SignalR, MessagePack, and Blazor Server scripts are now significantly smaller, enabling smaller downloads, less JavaScript parsing and compiling by the browser, and faster start-up.<\/p>\n<p>The download size reduction from this work is pretty phenomenal:<\/p>\n<table>\n<thead>\n<tr>\n<th>Library<\/th>\n<th>Before<\/th>\n<th>After<\/th>\n<th>% \ud83e\udc13<\/th>\n<th>.br<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>signalr.min.js<\/td>\n<td>130 KB<\/td>\n<td>39 KB<\/td>\n<td>70%<\/td>\n<td>10 KB<\/td>\n<\/tr>\n<tr>\n<td>blazor.server.js<\/td>\n<td>212 KB<\/td>\n<td>116 KB<\/td>\n<td>45%<\/td>\n<td>28 KB<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>You also now only need the <code>@microsoft\/signalr-protocol-msgpack<\/code> package for MessagePack; no need to include <code>msgpack5<\/code>. This means you only need an additional <code>29 KB<\/code> rather than the earlier <code>140 KB<\/code> to use MessagePack instead of JSON.<\/p>\n<p>Here&#8217;s how this size reduction was achieved:<\/p>\n<ul>\n<li>Updated TypeScript and dependencies to latest versions.<\/li>\n<li>Switched from <em>uglify-js<\/em> to <em>terser<\/em>, which is webpack&#8217;s default and supports newer JavaScript language features (like <code>class<\/code>).<\/li>\n<li>Marked the SignalR modules as <code>\"sideEffects\": false<\/code> so tree-shaking is more effective.<\/li>\n<li>Dropped the <code>\"es6-promise\/dist\/es6-promise.auto.js\"<\/code> polyfill.<\/li>\n<li>Changed TypeScript to output <code>es2019<\/code> rather than <code>es5<\/code> and dropped the &#8220;es2015.promise&#8221; and &#8220;es2015.iterable&#8221; polyfills.<\/li>\n<li>Moved to <code>msgpack5<\/code> from <code>@msgpack\/msgpack<\/code> as it requires less polyfills and is TypeScript &amp; module aware.<\/li>\n<\/ul>\n<p>You can find more details on these changes in Ben&#8217;s <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/30320\">pull request<\/a> on GitHub.<\/p>\n<h2>Enable Redis profiling sessions<\/h2>\n<p>We accepted a community contribution from <a href=\"https:\/\/github.com\/glucaci\">Gabriel Lucaci<\/a> to enable Redis profiling session with Microsoft.Extensions.Caching.StackExchangeRedis in this preview. For more details on Redis profiling, see the official <a href=\"https:\/\/stackexchange.github.io\/StackExchange.Redis\/Profiling_v2.html\">documentation<\/a>. The API can be used as follows:<\/p>\n<pre><code class=\"csharp\">services.AddStackExchangeRedisCache(options =&gt;\n{\n    options.ProfilingSession = () =&gt; new ProfilingSession();\n})\n<\/code><\/pre>\n<h2>HTTP\/3 endpoint TLS configuration<\/h2>\n<p>Work is beginning to ramp up on support for HTTP\/3 in .NET 6. HTTP\/3 brings a number of advantages over existing HTTP protocols, including faster connection setup, and improved performance on low-quality networks.<\/p>\n<p>New in this preview is the ability to configure TLS certificates on individual HTTP\/3 ports with <code>UseHttps<\/code>. This brings Kestrel&#8217;s HTTP\/3 endpoint configuration in-line with HTTP\/1.1 and HTTP\/2.<\/p>\n<pre><code class=\"csharp\">.ConfigureKestrel((context, options) =&gt;\n{\n    options.EnableAltSvc = true;\n    options.Listen(IPAddress.Any, 5001, listenOptions =&gt;\n    {\n        listenOptions.Protocols = HttpProtocols.Http3;\n        listenOptions.UseHttps(httpsOptions =&gt;\n        {\n            httpsOptions.ServerCertificate = LoadCertificate();\n        });\n    });\n})\n<\/code><\/pre>\n<h2>Initial .NET Hot Reload support<\/h2>\n<p>Early support for .NET Hot Reload is now available for ASP.NET Core &amp; Blazor projects using <code>dotnet watch<\/code>. .NET Hot Reload applies code changes to your running app without restarting it and without losing app state.<\/p>\n<p>To try out hot reload with an existing ASP.NET Core project based on .NET 6, add the <code>\"hotReloadProfile\": \"aspnetcore\"<\/code> property to your launch profile in <em>launchSettings.json<\/em>. For Blazor WebAssembly projects, use the <code>\"blazorwasm\"<\/code> hot reload profile.<\/p>\n<p>Run the project using <code>dotnet watch<\/code>. The output should indicate that hot reload is enabled:<\/p>\n<pre><code>watch : Hot reload enabled. For a list of supported edits, see https:\/\/aka.ms\/dotnet\/hot-reload. Press \"Ctrl + R\" to restart.\n<\/code><\/pre>\n<p>If at any point you want to force the app to rebuild and restart, you can do so by entering <kbd>Ctrl+R<\/kbd> at the console.<\/p>\n<p>You can now start making edits to your code. As you save code changes, applicable changes are automatically hot reloaded into the running app almost <em>instantaneously<\/em>. Any app state in the running app is preserved.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2021\/04\/HotReload6Preview3.gif\" alt=\".NET Hot Reload\" \/><\/p>\n<p>You can hot reload changes to your CSS files too without needing to refresh the browser:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2021\/04\/6dzDo6neYC.gif\" alt=\"CSS hot reload\" \/><\/p>\n<p>Some code changes aren&#8217;t supported with .NET Hot Reload. You can find a list of supported code edits in the <a href=\"https:\/\/docs.microsoft.com\/visualstudio\/debugger\/supported-code-changes-csharp\">docs<\/a>. With Blazor WebAssembly only method body replacement is currently supported. We&#8217;re working to expand the set of supported edits in .NET 6. When <code>dotnet watch<\/code> detects a change that cannot be applied using hot reload, it falls back to rebuilding and restarting the app.<\/p>\n<p>This is just the beginning of hot reload support in .NET 6. Hot reload support for desktop and mobile apps will be available soon in an upcoming preview as well as integration of hot reload in Visual Studio.<\/p>\n<h2>Razor compiler no longer produces a separate Views assembly<\/h2>\n<p>The Razor compiler previously utilized a two-step compilation process that produced a separate  Views assembly that contained the generated views and pages (.cshtml) defined in the application. The generated types were public and under the <code>AspNetCore<\/code> namespace.<\/p>\n<p>We&#8217;ve now updated the Razor compiler to build the views and pages types into the main project assembly. These types are now generated by default as <code>internal sealed<\/code> in the <code>AspNetCoreGeneratedDocument<\/code> namespace. This change improves build performance, enables single file deployment, and enables these types to participate in .NET Hot Reload.<\/p>\n<p>For additional details about this change, see the related <a href=\"https:\/\/github.com\/aspnet\/Announcements\/issues\/459\">announcement<\/a> issue on GitHub.<\/p>\n<h2>Shadow-copying in IIS<\/h2>\n<p>We&#8217;ve added a new feature in the ASP.NET Core Module for IIS to add support for shadow-copying your application assemblies. Currently .NET locks application binaries when running on Windows making it impossible to replace binaries when the application is still running. While our recommendation remains to use an <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/host-and-deploy\/app-offline\">app offline file<\/a>, we recognize there are certain scenarios (for example FTP deployments) where it isn&#8217;t possible to do so.<\/p>\n<p>In such scenarios, you can enable shadow copying by customizing the ASP.NET Core module handler settings. In most cases, ASP.NET Core application do not have a web.config checked into source control that you can modify (they are ordinarily generated by the SDK). You can add this sample <code>web.config<\/code> to get started.<\/p>\n<pre><code class=\"xml\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\n&lt;configuration&gt;\n  &lt;!-- To customize the asp.net core module uncomment and edit the following section. \n  For more info see https:\/\/go.microsoft.com\/fwlink\/?linkid=838655 --&gt;\n\n  &lt;system.webServer&gt;\n    &lt;handlers&gt;\n      &lt;remove name=\"aspNetCore\"\/&gt;\n      &lt;add name=\"aspNetCore\" path=\"*\" verb=\"*\" modules=\"AspNetCoreModulev2\" resourceType=\"Unspecified\"\/&gt;\n    &lt;\/handlers&gt;\n    &lt;aspNetCore processPath=\"%LAUNCHER_PATH%\" arguments=\"%LAUNCHER_ARGS%\" stdoutLogEnabled=\"false\" stdoutLogFile=\".\\logs\\stdout\"&gt;\n      &lt;handlerSettings&gt;\n        &lt;handlerSetting name=\"experimentalEnableShadowCopy\" value=\"true\" \/&gt;\n        &lt;handlerSetting name=\"shadowCopyDirectory\" value=\"..\/ShadowCopyDirectory\/\" \/&gt;\n        &lt;!-- Only enable handler logging if you encounter issues--&gt;\n        &lt;!--&lt;handlerSetting name=\"debugFile\" value=\".\\logs\\aspnetcore-debug.log\" \/&gt;--&gt;\n        &lt;!--&lt;handlerSetting name=\"debugLevel\" value=\"FILE,TRACE\" \/&gt;--&gt;\n      &lt;\/handlerSettings&gt;\n    &lt;\/aspNetCore&gt;\n  &lt;\/system.webServer&gt;\n&lt;\/configuration&gt;\n<\/code><\/pre>\n<p>You require a new version of ASP.NET Core module to try to this feature. On a self-hosted IIS server, this requires a new version of the hosting bundle.\nOn Azure App Services, you will be required to install a new ASP.NET Core runtime site extension<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2021\/04\/ASP.NET-Core-runtime-site-extension.png\" alt=\"ASP.NET Core runtime site extension\" \/><\/p>\n<h2>Vcpkg port for SignalR C++ client<\/h2>\n<p>Vcpkg is a cross-platform command-line package manager for C and C++ libraries. We&#8217;ve recently added a port to vcpkg to add CMake native support (works with MSBuild projects as well) for the SignalR C++ client.<\/p>\n<p>You can add the SignalR client to your CMake project with the following snippet (assuming you&#8217;ve already included the vcpkg toolchain file):<\/p>\n<pre><code class=\"cmake\">find_package(microsoft-signalr CONFIG REQUIRED)\nlink_libraries(microsoft-signalr::microsoft-signalr)\n<\/code><\/pre>\n<p>After this, the SignalR C++ client is ready to be #include&#8217;d and used in your project without any additional configuration. For a complete example of a C++ application that utilizes the SignalR C++ client, <a href=\"https:\/\/github.com\/halter73\/SignalR-Client-Cpp-Sample\">check out this repository<\/a>.<\/p>\n<h2>Reduced memory footprint for idle TLS connections<\/h2>\n<p>For long running TLS connections where data is only occasionally sent back and forth, we&#8217;ve significantly reduced the memory footprint of ASP.NET Core apps in .NET 6. This should help improve the scalability of scenarios such as WebSocket servers. This was possible due to numerous improvements in <code>System.IO.Pipelines<\/code>, <code>SslStream<\/code> and Kestrel. Let&#8217;s look at some of the improvements that have contributed to this scenario:<\/p>\n<h3>Reduce the size of <code>System.IO.Pipelines.Pipe<\/code><\/h3>\n<p>For every connection that we establish, we allocate two pipes in Kestrel: one from the transport layer to the application for the request and another from the application layer to the transport for the response. By shrinking the size of <a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/49270\"><code>System.IO.Pipelines.Pipe<\/code><\/a> from 368 bytes to 264 bytes (~28.2%), we&#8217;re saving 208 bytes per connection (104 bytes per Pipe).<\/p>\n<h3>Pool <code>SocketSender<\/code><\/h3>\n<p><code>SocketSender<\/code> objects (that subclass <code>SocketAsyncEventArgs<\/code>) are around 350 bytes at runtime. Instead of allocating a new <code>SocketSender<\/code> object per connection, <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/30771\">we can pool them since sends are usually very fast and we can reduce the per connection overhead<\/a>. Instead of paying 350 bytes per connection, we now only pay 350 bytes per <code>IOQueue<\/code>(one per queue to avoid contention). In our WebSocket server with 5000 idle connections we went from allocating ~1.75 MB (350 bytes * 5000) to now allocating just ~2.8kb (350 bytes * 8) for <code>SocketSender<\/code> objects.<\/p>\n<h3>Zero bytes reads with <code>SslStream<\/code><\/h3>\n<p>Bufferless reads are a technique we already employ in ASP.NET Core to avoid renting memory from the memory pool if there&#8217;s no data available on the socket. Prior to this change, our WebSocket server with 5000 idle connections required ~200 MB without TLS compared to ~800 MB with TLS. Some of these allocations (4k per connection) were from Kestrel having to hold on to an ArrayPool buffer while waiting for the reads on SslStream to complete. Given that these connections were idle, none of reads completed and return their buffers to the ArrayPool forcing the ArrayPool to allocate more memory. The remaining allocations were in <code>SslStream<\/code> itself: 4k buffer for TLS handshakes and 32k buffer for normal reads. In preview3, when the user performs a zero byte read on SslStream and it has no data available, <a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/49123\"><code>SslStream<\/code> will internally perform a zero-byte read on the underlying wrapped stream<\/a>. In the best case (idle connection), these changes result in a savings of 40 Kb per connection while still allowing the consumer (Kestrel) to be notified when data is available without holding on to any unused buffers.<\/p>\n<h3>Zero byte reads with <code>PipeReader<\/code><\/h3>\n<p>Once <code>SslStream<\/code> supported bufferless reads, we then added the option to perform zero byte reads to <code>StreamPipeReader<\/code>, the internal type that adapts a <code>Stream<\/code> into a <code>PipeReader<\/code>. In Kestrel, we use a <code>StreamPipeReader<\/code> to adapt the underlying <code>SslStream<\/code> into a <code>PipeReader<\/code> and it was necessary to expose these zero byte read semantics on the <code>PipeReader<\/code>.<\/p>\n<p>You can now create a <code>PipeReader<\/code> that supports zero bytes reads over any underlying <code>Stream<\/code> that supports zero byte read semantics (e.g,. <code>SslStream<\/code>, <code>NetworkStream<\/code>, etc) using the following API:<\/p>\n<pre><code class=\"csharp\">var reader = PipeReader.Create(stream, new StreamPipeReaderOptions(useZeroByteReads: true));\n<\/code><\/pre>\n<h2>Remove slabs from the <code>SlabMemoryPool<\/code><\/h2>\n<p>To reduce fragmentation of the heap, Kestrel employed a technique where it allocated slabs of memory of 128 KB as part of it&#8217;s memory pool. The slabs were then further divided into 4 KB blocks that were used by Kestrel internally. The slabs had to be larger than 85 KB to force allocation on the large object heap to try and prevent the GC from relocating this array. However, with the introduction of the new GC generation, Pinned Object Heap (POH), it no longer makes sense to allocate blocks on slab. In preview3, <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/30732\">we now directly allocate blocks on the POH<\/a>, reducing the complexity involved in managing our own memory pool. This change should make easier to perform future improvements such as making it easier to shrink the memory pool used by Kestrel.<\/p>\n<h2><code>BlazorWebView<\/code> controls for WPF &amp; Windows Forms<\/h2>\n<p>For .NET 6 we are adding support for building cross-platform hybrid desktop apps using .NET MAUI and Blazor. Hybrid apps are native apps that leverage web technologies for their functionality. For example, a hybrid app might use an embedded web view control to render web UI. This means you can write your app UI using web technologies like HTML &amp; CSS, while also leveraging native device capabilities. We will be introducing support for building hybrid apps with .NET MAUI and Blazor in an upcoming .NET 6 preview release.<\/p>\n<p>In this release, we&#8217;ve introduced <code>BlazorWebView<\/code> controls for WPF and Windows Forms apps that enable embedding Blazor functionality into existing Windows desktop apps based on .NET 6. Using Blazor and a hybrid approach you can start decoupling your UI investments from WPF &amp; Windows Forms. This is a great way to modernize existing desktop apps in a way that can be brought forward onto .NET MAUI or used on the web. You can use Blazor to modernize your existing Windows Forms and WPF apps while leveraging your existing .NET investments.<\/p>\n<p>To use the new <code>BlazorWebView<\/code> controls, you first need to make sure that you have <a href=\"https:\/\/developer.microsoft.com\/microsoft-edge\/webview2\/#download-section\">WebView2<\/a> installed.<\/p>\n<p>To add Blazor functionality to an existing Windows Forms app:<\/p>\n<ul>\n<li>Update the Windows Forms app to target .NET 6.<\/li>\n<li>Update the SDK used in the app&#8217;s project file to <code>Microsoft.NET.Sdk.Razor<\/code>.<\/li>\n<li>Add a package reference to <a href=\"https:\/\/nuget.org\/packages\/Microsoft.AspNetCore.Components.WebView.WindowsForms\">Microsoft.AspNetCore.Components.WebView.WindowsForms<\/a>.<\/li>\n<li>Add the following <em>wwwroot\/index.html<\/em> file to the project, replacing <code>{PROJECT NAME}<\/code> with the actual project name:<\/li>\n<\/ul>\n<pre><code class=\"html\">&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n\n&lt;head&gt;\n    &lt;meta charset=\"utf-8\" \/&gt;\n    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" \/&gt;\n    &lt;title&gt;Blazor app&lt;\/title&gt;\n    &lt;base href=\"\/\" \/&gt;\n    &lt;link href=\"{PROJECT NAME}.styles.css\" rel=\"stylesheet\" \/&gt;\n    &lt;link href=\"app.css\" rel=\"stylesheet\" \/&gt;\n&lt;\/head&gt;\n\n&lt;body&gt;\n    &lt;div id=\"app\"&gt;&lt;\/div&gt;\n\n    &lt;div id=\"blazor-error-ui\"&gt;\n        An unhandled error has occurred.\n        &lt;a href=\"\" class=\"reload\"&gt;Reload&lt;\/a&gt;\n        &lt;a class=\"dismiss\"&gt;\ud83d\uddd9&lt;\/a&gt;\n    &lt;\/div&gt;\n\n    &lt;script src=\"_framework\/blazor.webview.js\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/code><\/pre>\n<ul>\n<li>Add the following <em>app.css<\/em> file with some basic styles to the <em>wwwroot<\/em> folder:<\/li>\n<\/ul>\n<pre><code class=\"css\">html, body {\n    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;\n}\n\n.valid.modified:not([type=checkbox]) {\n    outline: 1px solid #26b050;\n}\n\n.invalid {\n    outline: 1px solid red;\n}\n\n.validation-message {\n    color: red;\n}\n\n#blazor-error-ui {\n    background: lightyellow;\n    bottom: 0;\n    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);\n    display: none;\n    left: 0;\n    padding: 0.6rem 1.25rem 0.7rem 1.25rem;\n    position: fixed;\n    width: 100%;\n    z-index: 1000;\n}\n\n    #blazor-error-ui .dismiss {\n        cursor: pointer;\n        position: absolute;\n        right: 0.75rem;\n        top: 0.5rem;\n    }\n<\/code><\/pre>\n<ul>\n<li>For all files in the <em>wwwroot<\/em> folder, set the <strong>Copy to Output Directory<\/strong> property to <strong>Copy if newer<\/strong>.<\/li>\n<li>Add a root Blazor component, <em>Counter.razor<\/em>, to the project:<\/li>\n<\/ul>\n<pre><code class=\"razor\">@using Microsoft.AspNetCore.Components.Web\n\n&lt;h1&gt;Counter&lt;\/h1&gt;\n\n&lt;p&gt;The current count is: @currentCount&lt;\/p&gt;\n&lt;button @onclick=\"IncrementCount\"&gt;Count&lt;\/button&gt;\n\n@code {\n    int currentCount = 0;\n\n    void IncrementCount()\n    {\n        currentCount++;\n    }\n}\n<\/code><\/pre>\n<ul>\n<li>Add a <code>BlazorWebView<\/code> control to the desired form to render the root Blazor component:<\/li>\n<\/ul>\n<pre><code class=\"csharp\">var serviceCollection = new ServiceCollection();\nserviceCollection.AddBlazorWebView();\nvar blazor = new BlazorWebView()\n{\n    Dock = DockStyle.Fill,\n    HostPage = \"wwwroot\/index.html\",\n    Services = serviceCollection.BuildServiceProvider(),\n};\nblazor.RootComponents.Add&lt;Counter&gt;(\"#app\");\nControls.Add(blazor);\n<\/code><\/pre>\n<ul>\n<li>Run the app to see your <code>BlazorWebView<\/code> in action:<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2021\/04\/Blazor-WinForms.png\" alt=\"Blazor Windows Forms\" \/><\/p>\n<p>To add Blazor functionality to an existing WPF app, follow the same steps listed above for Windows Forms apps, except:<\/p>\n<ul>\n<li>Substitute a package reference for <a href=\"https:\/\/nuget.org\/packages\/Microsoft.AspNetCore.Components.WebView.Wpf\">Microsoft.AspNetCore.Components.WebView.Wpf<\/a><\/li>\n<li>Add the <code>BlazorWebView<\/code> control in XAML:<\/li>\n<\/ul>\n<pre><code class=\"xaml\">&lt;Window x:Class=\"WpfApp1.MainWindow\"\n        xmlns=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\/presentation\"\n        xmlns:x=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\"\n        xmlns:d=\"http:\/\/schemas.microsoft.com\/expression\/blend\/2008\"\n        xmlns:mc=\"http:\/\/schemas.openxmlformats.org\/markup-compatibility\/2006\"\n        xmlns:local=\"clr-namespace:WpfApp1\"\n        xmlns:blazor=\"clr-namespace:Microsoft.AspNetCore.Components.WebView.Wpf;assembly=Microsoft.AspNetCore.Components.WebView.Wpf\"\n        mc:Ignorable=\"d\"\n        Title=\"MainWindow\" Height=\"450\" Width=\"800\"&gt;\n    &lt;Grid&gt;\n        &lt;blazor:BlazorWebView HostPage=\"wwwroot\/index.html\" Services=\"{StaticResource services}\"&gt;\n            &lt;blazor:BlazorWebView.RootComponents&gt;\n                &lt;blazor:RootComponent Selector=\"#app\" ComponentType=\"{x:Type local:Counter}\" \/&gt;\n            &lt;\/blazor:BlazorWebView.RootComponents&gt;\n        &lt;\/blazor:BlazorWebView&gt;\n    &lt;\/Grid&gt;\n&lt;\/Window&gt;\n<\/code><\/pre>\n<ul>\n<li>Set up the service provider as a static resource in the XAML code-behind file (such as <code>MainWindow.xaml.cs<\/code>):<\/li>\n<\/ul>\n<pre><code class=\"csharp\">var serviceCollection = new ServiceCollection();\nserviceCollection.AddBlazorWebView();\nResources.Add(\"services\", serviceCollection.BuildServiceProvider());\n<\/code><\/pre>\n<ul>\n<li>To workaround an issue with the WPF runtime build not finding Razor component types, add an empty partial class for the component in <em>Counter.razor.cs<\/em>:<\/li>\n<\/ul>\n<pre><code class=\"csharp\">public partial class Counter { }\n<\/code><\/pre>\n<ul>\n<li>Build and run your Blazor based WPF app:<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2021\/04\/Blazor-WPF.png\" alt=\"Blazor WPF\" \/><\/p>\n<h2>Give feedback<\/h2>\n<p>We hope you enjoy this preview release of ASP.NET Core in .NET 6. We&#8217;re eager to hear about your experiences with this release. Let us know what you think by filing issues on <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/issues\">GitHub<\/a>.<\/p>\n<p>Thanks for trying out ASP.NET Core!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>.NET 6 Preview 3 is now available! Check out all the improvements in ASP.NET Core in this update.<\/p>\n","protected":false},"author":417,"featured_media":21413,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[197,7509,7251],"tags":[],"class_list":["post-24760","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-aspnet","category-aspnetcore","category-blazor"],"acf":[],"blog_post_summary":"<p>.NET 6 Preview 3 is now available! Check out all the improvements in ASP.NET Core in this update.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/24760","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\/417"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=24760"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/24760\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/21413"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=24760"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=24760"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=24760"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}