CINXE.COM
Web Development - C# in the Browser with Blazor | Microsoft Learn
<!DOCTYPE html><html class="hasSidebar hasPageActions hasBreadcrumb conceptual has-default-focus theme-light" lang="en-us" dir="ltr" data-authenticated="false" data-auth-status-determined="false" data-target="docs" x-ms-format-detection="none"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta property="og:title" content="Web Development - C# in the Browser with Blazor" /> <meta property="og:type" content="website" /> <meta property="og:url" content="https://learn.microsoft.com/en-us/archive/msdn-magazine/2018/september/web-development-csharp-in-the-browser-with-blazor" /><meta property="og:image" content="https://learn.microsoft.com/en-us/media/open-graph-image.png" /> <meta property="og:image:alt" content="Microsoft Learn" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:site" content="@MicrosoftLearn" /> <meta name="color-scheme" content="light dark"><meta name="author" content="kexugit" /> <meta name="breadcrumb_path" content="/archive/msdn-magazine/breadcrumb/toc.json" /> <meta name="depot_name" content="MSDN.msdn-magazine-archive" /> <meta name="document_id" content="cc9339c3-17a6-a6b8-c9f2-bb8b58dcc8bf" /> <meta name="document_version_independent_id" content="cc9339c3-17a6-a6b8-c9f2-bb8b58dcc8bf" /> <meta name="feedback_system" content="None" /> <meta name="git_commit_id" content="65cb603265929c7971672beaa81fcca59365ddea" /> <meta name="gitcommit" content="https://docs-archive.visualstudio.com/DefaultCollection/docs-archive-project/_git/msdn-magazine-archive-pr/commit/65cb603265929c7971672beaa81fcca59365ddea?path=/msdn-magazine-archive/2018/september/web-development-csharp-in-the-browser-with-blazor.md&_a=contents" /> <meta name="is_archived" content="true" /> <meta name="locale" content="en-us" /> <meta name="ms.author" content="Archiveddocs" /> <meta name="ms.date" content="01/04/2019" /> <meta name="ms.prod" content="msdn-magazine" /> <meta name="ms.topic" content="Archived" /> <meta name="ms:assetid" content="81825a08-8d3a-45f0-99db-eb5222343a20" /> <meta name="ms:contentKeyID" content="74533908" /> <meta name="ms:mtpsurl" content="https://msdn.microsoft.com/en-us/library/Mt829752(v=MSDN.10)" /> <meta name="mtps_version" content="v=MSDN.10" /> <meta name="original_content_git_url" content="https://docs-archive.visualstudio.com/DefaultCollection/docs-archive-project/_git/msdn-magazine-archive-pr?path=/msdn-magazine-archive/2018/september/web-development-csharp-in-the-browser-with-blazor.md&version=GBlive&_a=contents" /> <meta name="page_type" content="conceptual" /> <meta name="ROBOTS" content="INDEX,FOLLOW" /> <meta name="schema" content="Conceptual" /> <meta name="site_name" content="Docs" /> <meta name="toc_rel" content="toc.json" /> <meta name="TOCTitle" content="Web Development - C# in the Browser with Blazor" /> <meta name="uhfHeaderId" content="MSDocsHeader-Archive" /> <meta name="updated_at" content="2022-01-21 03:47 AM" /> <meta name="word_count" content="2494" /> <meta name="persistent_id" content="836b8c70-3a3a-1d25-9ae1-8531195fe1ee" /> <meta name="github_feedback_content_git_url" content="https://docs-archive.visualstudio.com/DefaultCollection/docs-archive-project/_git/msdn-magazine-archive-pr?path=/msdn-magazine-archive/2018/september/web-development-csharp-in-the-browser-with-blazor.md&version=GBlive&_a=contents" /><link href="https://learn.microsoft.com/en-us/archive/msdn-magazine/2018/september/web-development-csharp-in-the-browser-with-blazor" rel="canonical"><title>Web Development - C# in the Browser with Blazor | Microsoft Learn</title><link rel="stylesheet" href="/static/assets/0.4.028726178/styles/site-ltr.css"> <script id="msdocs-script"> var msDocs = {environment: { supportLevel: 'production', accessLevel: 'online', reviewFeatures: false, systemContent: true, azurePortalHostname: 'portal.azure.com', legacyHosting: false, siteName: 'learn', },data: { timeOrigin: Date.now(), contentLocale: 'en-us', contentDir: 'ltr', userLocale: 'en-us', userDir: 'ltr', pageTemplate: 'Conceptual', brand: '', context: {}, hasBinaryRating: false, feedbackHelpLinkType:'', feedbackHelpLinkUrl:'', standardFeedback: false, showFeedbackReport: false, enableTutorialFeedback: false, feedbackSystem: 'None', feedbackGitHubRepo: '', feedbackProductUrl: '',extendBreadcrumb: true,isEditDisplayable: false, hideViewSource: false, hasPageActions: true, hasPrintButton: true, hasBookmark: true, hasShare: true, isPermissioned: false, isPrivateUnauthorized: false,hasRecommendations: true,}, functions:{} }; </script><script src="https://wcpstatic.microsoft.com/mscc/lib/v2/wcp-consent.js"></script> <script src="https://js.monitor.azure.com/scripts/c/ms.jsll-4.min.js"></script><script src="/static/assets/0.4.028726178/global/deprecation.js"></script><script src="/static/assets/0.4.028726178/scripts/en-us/index-docs.js"></script></head> <body lang="en-us" dir="ltr"> <div class="header-holder has-default-focus"> <a href="#main" style="z-index: 1070" class="outline-color-text visually-hidden-until-focused position-fixed inner-focus focus-visible top-0 left-0 right-0 padding-xs text-align-center has-body-background" tabindex="1">Skip to main content</a><div hidden id="cookie-consent-holder" data-test-id="cookie-consent-container"></div> <div id="unsupported-browser" style=" background-color: white; color: black; padding: 16px; border-bottom: 1px solid grey;" hidden > <div style="max-width: 800px; margin: 0 auto;"> <p style="font-size: 24px">This browser is no longer supported.</p> <p style="font-size: 16px; margin-top: 16px;">Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.</p> <div style="margin-top: 12px;"> <a href="https://go.microsoft.com/fwlink/p/?LinkID=2092881 " style=" background-color: #0078d4; border: 1px solid #0078d4; color: white; padding: 6px 12px; border-radius: 2px; display: inline-block; ">Download Microsoft Edge</a> <a href="https://learn.microsoft.com/en-us/lifecycle/faq/internet-explorer-microsoft-edge" style=" background-color: white; padding: 6px 12px; border: 1px solid #505050; color: #171717; border-radius: 2px; display: inline-block; ">More info about Internet Explorer and Microsoft Edge</a> </div> </div> </div> <!-- liquid-tag banners global --> <!-- site header --> <header id="ms--site-header" data-test-id="site-header-wrapper" role="banner" itemscope="itemscope" itemtype="http://schema.org/Organization"> <div id="ms--mobile-nav" class="site-header display-none-tablet padding-inline-none gap-none" data-bi-name="mobile-header" data-test-id="mobile-header"></div> <div id="ms--primary-nav" class="site-header display-none display-flex-tablet" data-bi-name="L1-header" data-test-id="primary-header"></div> <div id="ms--secondary-nav" class="site-header display-none display-flex-tablet" data-bi-name="L2-header" data-test-id="secondary-header"></div> </header><div id="content-header" class="content-header uhf-container has-padding has-default-focus border-bottom-none" data-bi-name="content-header"> <div class="content-header-controls margin-xxs margin-inline-sm-tablet"> <button type="button" class="contents-button button button-sm margin-right-xxs" data-bi-name="contents-expand" aria-haspopup="true" data-contents-button> <span class="icon"><span class="docon docon-menu" aria-hidden="true"></span></span> <span class="contents-expand-title">Table of contents</span> </button> <button type="button" class="ap-collapse-behavior ap-expanded button button-sm" data-bi-name="ap-collapse" aria-controls="action-panel"> <span class="icon"><span class="docon docon-exit-mode" aria-hidden="true"></span></span> <span>Exit focus mode</span> </button> </div> </div><div id="disclaimer-holder" class="has-overflow-hidden has-default-focus"> <!-- liquid-tag banners sectional --> </div> </div> <div class="mainContainer uhf-container has-default-focus" data-bi-name="body"> <div class="columns has-large-gaps is-gapless-mobile "><div id="left-container" class="left-container is-hidden-mobile column is-one-third-tablet is-one-quarter-desktop"> <nav id="affixed-left-container" class="margin-top-sm-tablet position-sticky display-flex flex-direction-column" aria-label="Primary"></nav> </div><!-- .primary-holder --> <section class="primary-holder column is-two-thirds-tablet is-three-quarters-desktop"> <!--div.columns --> <div class="columns is-gapless-mobile has-large-gaps "><div id="main-column" class="column is-full is-8-desktop"> <main id="main" class="" role="main" data-bi-name="content" lang="en-us" dir="ltr"><!-- article-header --> <div id="article-header" class="background-color-body margin-top-sm-tablet margin-bottom-xs display-none-print"> <div class="display-flex align-items-center "><details id="article-header-breadcrumbs-overflow-popover" class="popover" data-for="article-header-breadcrumbs"> <summary class="button button-clear button-primary button-sm inner-focus" aria-label="All breadcrumbs"> <span class="icon"> <span class="docon docon-more"></span> </span> </summary> <div id="article-header-breadcrumbs-overflow" class="popover-content padding-none"> </div> </details> <bread-crumbs id="article-header-breadcrumbs" data-test-id="article-header-breadcrumbs" class="overflow-hidden flex-grow-1 margin-right-sm margin-right-md-tablet margin-right-lg-desktop margin-left-negative-xxs padding-left-xxs"></bread-crumbs><div id="article-header-page-actions" class="opacity-none margin-left-auto display-flex flex-wrap-no-wrap align-items-stretch"><a id="lang-link-tablet" class="button button-primary button-clear button-sm display-none display-inline-flex-tablet" title="Read in English" data-bi-name="language-toggle" data-read-in-link hidden> <span class="icon margin-none" aria-hidden="true" data-read-in-link-icon> <span class="docon docon-locale-globe"></span> </span> <span class="is-visually-hidden" data-read-in-link-text>Read in English</span> </a><button type="button" class="collection button button-clear button-sm button-primary display-none display-inline-flex-tablet" data-list-type="collection" data-bi-name="collection" title="Add to collection"> <span class="icon margin-none" aria-hidden="true"> <span class="docon docon-circle-addition"></span> </span> <span class="collection-status is-visually-hidden">Save</span> </button> <details class="popover popover-right" id="article-header-page-actions-overflow"> <summary class="justify-content-flex-start button button-clear button-sm button-primary" aria-label="More actions" title="More actions"> <span class="icon" aria-hidden="true"> <span class="docon docon-more-vertical"></span> </span> </summary> <div class="popover-content padding-xs"><button data-page-action-item="overflow-mobile" type="button" class="justify-content-flex-start button-block button-sm has-inner-focus button button-clear display-none-tablet" data-bi-name="contents-expand" data-contents-button data-popover-close> <span class="icon"> <span class="docon docon-editor-list-bullet" aria-hidden="true"></span> </span><span class="contents-expand-title">Table of contents</span></button><a id="lang-link-overflow" class="justify-content-flex-start button-sm has-inner-focus button button-clear button-block display-none-tablet" title="Read in English" data-bi-name="language-toggle" data-page-action-item="overflow-mobile" data-check-hidden="true" data-read-in-link hidden > <span class="icon" aria-hidden="true" data-read-in-link-icon> <span class="docon docon-locale-globe"></span> </span> <span data-read-in-link-text>Read in English</span> </a><button type="button" class="collection justify-content-flex-start button button-clear button-sm has-inner-focus button-block display-none-tablet" data-list-type="collection" data-bi-name="collection" title="Save" data-page-action-item="overflow-mobile" data-check-hidden="true" data-popover-close> <span class="icon" aria-hidden="true"> <span class="docon docon-circle-addition"></span> </span> <span class="collection-status">Save</span> </button> <button type="button" class="collection justify-content-flex-start button button-clear button-sm has-inner-focus button-block display-none-tablet" data-list-type="plan" data-bi-name="plan" title="Add to Plan" data-page-action-item="overflow-mobile" data-check-hidden="true" data-popover-close hidden> <span class="icon" aria-hidden="true"> <span class="docon docon-circle-addition"></span> </span> <span class="plan-status">Add to Plan</span> </button><div aria-hidden="true" class="margin-none" data-page-action-item="overflow-all"></div> <hr class="display-none-tablet margin-bottom-xxs margin-top-xxs" /> <h4 class="font-size-sm padding-left-xxs">Share via</h4> <a class="button button-clear button-sm button-block has-inner-focus text-decoration-none justify-content-flex-start share-facebook" data-bi-name="facebook" data-page-action-item="overflow-all"> <span class="icon" aria-hidden="true"> <span class="docon docon-facebook-share font-size-md color-primary"></span> </span> <span class="margin-left-xxs">Facebook</span> </a> <a class="button button-clear button-sm has-inner-focus button-block text-decoration-none justify-content-flex-start share-twitter" data-bi-name="twitter" data-page-action-item="overflow-all"> <span class="icon" aria-hidden="true"> <span class="docon docon-xlogo-share font-size-xxs"></span> </span> <span class="margin-left-xxs">x.com</span> </a> <a class="button button-clear button-sm has-inner-focus button-block text-decoration-none justify-content-flex-start share-linkedin" data-bi-name="linkedin" data-page-action-item="overflow-all"> <span class="icon" aria-hidden="true"> <span class="docon docon-linked-in-logo font-size-sm color-primary"></span> </span> <span class="margin-left-xxs">LinkedIn</span> </a> <a class="button button-clear button-sm button-block has-inner-focus text-decoration-none justify-content-flex-start margin-bottom-xxs share-email" data-bi-name="email" data-page-action-item="overflow-all"> <span class="icon" aria-hidden="true"> <span class="docon docon-mail-message font-size-sm color-primary"></span> </span> <span class="margin-left-xxs">Email</span> </a><hr /> <button class="button button-block button-clear button-sm justify-content-flex-start has-inner-focus margin-top-xxs" title="Print" type="button" aria-label="Print" data-bi-name="print" data-page-action-item="overflow-all" data-popover-close data-print-page data-check-hidden="true"> <span class="icon" aria-hidden="true"> <span class="docon docon-print font-size-sm color-primary"></span> </span> <span class="margin-left-xxs">Print</span> </button> </div> </details> </div></div> </div> <!-- end article-header --><div> <button type="button" class="border contents-button button button-clear button-sm is-hidden-tablet has-inner-focus" data-bi-name="contents-expand" data-contents-button hidden> <span class="icon"> <span class="docon docon-editor-list-bullet" aria-hidden="true"></span> </span><span class="contents-expand-title">Table of contents</span></button> </div><!-- end mobile-contents button --> <div class="content "><div class="display-flex justify-content-space-between align-items-center flex-wrap-wrap page-metadata-container"> <div class="margin-right-xxs"> <ul class="metadata page-metadata" data-bi-name="page info" lang="en-us" dir="ltr"><li>Article</li><li> <time class="is-invisible" data-article-date aria-label="Article review date" datetime="2019-01-04T00:00:00.000Z" data-article-date-source="ms.date">01/04/2019</time> </li></ul> </div></div><nav id="center-doc-outline" class="doc-outline is-hidden-desktop display-none-print margin-bottom-sm" data-bi-name="intopic toc" aria-label="In this article"> <h2 id="ms--in-this-article" class="title is-6 margin-block-xs">In this article</h2> </nav><!-- <content> --><hr> <p>September 2018</p> <p>Volume 33 Number 9</p> <h3 id="web-development">[Web Development]</h3> <h1 id="c-in-the-browser-with-blazor">C# in the Browser with Blazor</h1> <p>By <a href="%5Carchive%5Cmsdn-magazine%5Cauthors%5CJonathan_Miller" data-linktype="relative-path">Jonathan Miller</a></p> <p>Blazor is the new Microsoft experimental framework that brings C# into any browser without a plug-in. It holds the promise of modern single-page applications, combined with the ability to use C# and its vast base-class library. Blazor takes C# development to a new level. It’s the final piece necessary to make the language a full-stack development tool. It will have all the power of the popular JavaScript frameworks, but based on the familiar languages, APIs and tooling of the Microsoft .NET Framework.</p> <p>If you come from a traditional Microsoft background and are familiar with ASP.NET Web Forms or Model-View-Controller (MVC), getting up to speed on Blazor is surprisingly easy, especially when compared to the mountain a Microsoft developer must climb to gain the equivalent knowledge in a JavaScript framework such as Angular or React.</p> <p>It’s important to understand that Blazor runs completely inside the browser. Once a Blazor app is compiled, it’s essentially a set of files that gets loaded into the browser and runs. Unlike traditional ASP.NET applications, there’s no need for anything special on the back end to serve it. A Blazor site can be served by any Web server on any platform. As for clients, any browser that supports the WebAssembly standard supports Blazor. That includes all the major browsers shipping today.</p> <p>When running in the browser, an application isn’t really all that useful without access to external data and services. Just like standard JavaScript single-page applications, Blazor apps access Web services using HTTP REST APIs. Those APIs can be created using Microsoft tools, such as Web API, or any technology that can present an HTTP REST endpoint. In this article, I’m going to demonstrate the ability of a Blazor app to call freely available Web services on the Web.</p> <h2 id="getting-set-up">Getting Set Up</h2> <p>The first step is getting the current build of Blazor installed. As of this writing, Blazor is still an unsupported experimental framework. That means you shouldn’t use it in production. The Blazor install is very low-impact, but you may not want to install it on your everyday work machine. Consider installing it on another machine or a virtual environment. The main requirements are Visual Studio 2017 with ASP.NET and Web development workload installed, the .NET Core SDK, and the Blazor Language extension. Please review the “Get Started” steps on blazor.net. The Blazor team is advancing quite rapidly, and sometimes you need specific versions of Visual Studio or .NET Core to use the current experimental version.</p> <h2 id="creating-a-new-blazor-app">Creating a New Blazor App</h2> <p>I’m going to start by creating the sample Blazor application and then modify it to call some Web services. First, let’s create a new ASP.NET Core Web Application in Visual Studio.</p> <p>Next, choose a Blazor application and click OK. If you don’t see Blazor in the list of choices, you may be missing the Blazor Language Services Extension.</p> <p>The default Blazor application that’s created is far from an empty canvas. It includes a basic Web site that utilizes Bootstrap. There are a few sample pages that make it easy to get up and running and start experimenting with Blazor right away. The Fetch data tab displays some dummy weather data from a JSON file embedded into the default application.</p> <h2 id="how-is-this-even-possible">How Is This Even Possible?</h2> <p>The concept of C# running in a browser has been a dream since the inception of .NET Silverlight. It worked very well for line-of-business applications, but the fact that it required a plug-in and the emerging iOS model didn’t allow browser plug-ins severely limited the future of Silverlight.</p> <p>The magic that makes this all possible is a new standard called WebAssembly (WASM). WASM is a binary format that can be loaded and run directly in the browser and is currently supported by all the major browsers. The Mono team is working hard on a version of the .NET runtime that runs in WASM. Blazor itself is a framework that builds on top of that Mono runtime for WASM. When the project is compiled, it’s compiled to a .NET assembly that gets loaded and executed by the Common Language Runtime (CLR) running inside the browser. It’s important to understand that the .NET code running in the browser is running in the same JavaScript security sandbox. See Dino Esposito’s Cutting Edge column in this issue for more on this topic.</p> <h2 id="getting-a-real-weather-forecast">Getting a Real Weather Forecast</h2> <p>The forecast is fake data loaded from a file embedded in the project. I’m going to replace the entire Fetch data page with real data from real Web services. First, I need to replace the HTML with something simple that prompts the user for a ZIP code. Inside the FetchData.cshtml, I replace the HTML code with the following:</p> <pre><code><h1>Weather Forecast</h1> <div class="input-group col-md-3"> <input type="text" class="form-control" placeholder="Zip code" bind="@zip" maxlength="5" /> <div class="input-group-append"> <button class="btn btn-secondary" type="button" onclick="@GetWeather">Get Weather</button> </div> </div> <br /><span style="color:red">@errorMessage</span> </code></pre> <p>Notice the Razor syntax embedded in the script. The @ sign signals code and variables. The input tag captures the ZIP code and binds it to a variable called zip. The button tag has its onclick method bound to <span class="no-loc" dir="ltr" lang="en-us">@GetWeather</span>, which calls the GetWeather method in C# (not JavaScript). There’s also a little <span class="no-loc" dir="ltr" lang="en-us">@errorMessage</span> that can be used if the user enters an invalid ZIP. These variables and methods are defined in the same FetchData.cshtml inside the <span class="no-loc" dir="ltr" lang="en-us">@functions</span> block:</p> <pre><code class="lang-csharp">@functions { String zip = String.Empty; String errorMessage = String.Empty; private async Task GetWeather() { } } </code></pre> <p>Running the application now gives the user the ability to enter the ZIP code and click the Get Weather button. The GetWeather method is empty, so nothing happens. In the next section, I’ll add the code that will call the Web services and retrieve the current weather conditions.</p> <h2 id="storing-the-data-returned-from-the-web-service">Storing the Data Returned from the Web Service</h2> <p>Next, I need some local variables in my page to store the data returned from the Web service calls. The Razor pages will bind to these variables so the data can be displayed. These variables get added to the <span class="no-loc" dir="ltr" lang="en-us">@functions</span> block in the page, like so:</p> <pre><code class="lang-csharp">Models.CurrentConditions currentcondition; Models.Alert alerts; Models.ZipLookup ziplookup; String imgurl = ""; </code></pre> <h2 id="adding-the-zip-code-web-service">Adding the ZIP Code Web Service</h2> <p>Once the user clicks the Get Weather button, the ZIP code in the input box must be validated. The first public Web service is from zippopotam.us. When the <a href="https://api.zippopotam.us/US/%5C" data-linktype="external">https://api.zippopotam.us/US/\</a><zip> API URL is called, it returns information about the specified ZIP code. The information needed from this Web service is the name of the city and state. This data will be displayed to the user in the forecast, and the state abbreviation will be used in subsequent Web service calls. The Web service code should look familiar because it uses the familiar HttpClient class.</p> <p><strong>Calling the ZIP Lookup Web Service</strong> The script in <strong>Figure 1</strong> downloads the ZIP code info from the API and places it into a local variable called ziplookup. I can use this variable in my Razor code to display the city name. The API will return an exception if the ZIP is invalid. If that happens, an error message is displayed.</p> <p>Figure 1 Getting the ZIP Code Information</p> <pre><code class="lang-csharp">try { errorMessage = ""; var zipresultStr = await Http.GetStringAsync($"https://api.zippopotam.us/US/{zip}"); zipresultStr = zipresultStr.Replace("place name", "city").Replace( "state abbreviation", "stateabbr"); ziplookup = JsonUtil.Deserialize<Models.ZipLookup>(zipresultStr); } catch { errorMessage = "Invalid zip code"; return; } </code></pre> <p><strong>Deserializing the ZIP Lookup Data</strong> In the previous code snippet, I’m retrieving data from the Web service and deserializing it into a Models.ZipLookup class. This is a class I’ve created to match the schema of the JSON data being returned:</p> <pre><code class="lang-csharp">public class ZipLookup { public Place[] places { get; set; } } public class Place { public String city { get; set; } public String stateabbr { get; set; } } </code></pre> <p>Much more data is returned, but I’ve only created properties and classes for the data I want to use. The current implementation has issues with dealing with spaces in the JSON field names. As a temporary workaround, I’m using String.Replace to remove the spaces.</p> <p><strong>Displaying the City and State</strong> Now that the data has been downloaded and deserialized, I can display it in the Web page. The following code block displays the city and state abbreviation in the page:</p> <pre><code><h1> @ziplookup.places[0].city, @ziplookup.places[0].stateabbr<br /> </h1> </code></pre> <h2 id="adding-the-weather-conditions-web-service">Adding the Weather Conditions Web Service</h2> <p>The next Web service will retrieve the current conditions for the ZIP code from the <a href="https://openweathermap.org/" data-linktype="external">openweathermap.org</a> Web service. You’ll need to create an account in order to receive a special key that’s used when calling the Web service.</p> <p><strong>Calling the Current Conditions Web Service</strong> The call to get the current conditions works much like the previous Web service call. The exception here is the apikey parameter in the call. The openweathermap.org service requires a key to authenticate the caller:</p> <pre><code class="lang-csharp">currentcondition = await Http.GetJsonAsync<Models.CurrentConditions>( $"https://api.openweathermap.org/data/2.5/ weather?zip={zip},us&appid=<apikey>"); imgurl = $"https://openweathermap.org/img/w/{currentcondition.weather[0].icon}.png"; </code></pre> <p>The result of the current conditions call is stored in a local variable called currentcondition. That result also passes the name of an icon to be displayed that corresponds to the current conditions. The name of the image is encoded into the imgurl variable so it can be displayed in the Web page.</p> <p><strong>Deserializing Current Conditions Data</strong> Once again, the raw JSON data needs to be deserialized into a class so it can be used, as shown in <strong>Figure 2</strong>. The class definition looks a little odd, but it’s designed to match the schema of the JSON data being returned from the Web service. There’s a lot more data being returned than what’s shown here. Only the properties that are needed have to be implemented.</p> <p>Figure 2 Deserializing the Raw JSON Data</p> <pre><code class="lang-csharp">public class CurrentConditions { public CurrentConditions() { } public List<Weather> weather { get; set; } public Main main { get; set; } public String name { get; set; } } public class Weather { public String main { get; set; } public String description { get; set; } public String icon { get; set; } } public class Main { public decimal temp { get; set; } public decimal temp_min { get; set; } public decimal temp_max { get; set; } } </code></pre> <p><strong>Converting the Temperatures</strong> The temperatures returned from the Web service are in kelvins, so the values need to be converted to degrees Fahrenheit and rounded. The current temperature will be rounded to the nearest tenth of a degree. The high and low temperatures will be rounded to the nearest whole degree.</p> <pre><code class="lang-csharp">private decimal ConvertKtoF(decimal kelvin, int decimals) { return Math.Round(kelvin * 9 / 5 - 459.67M, decimals); } </code></pre> <p><strong>Displaying the Current Conditions</strong> The Razor script in <strong>Figure 3</strong> will now be updated to include the current temperature, the high and low, a description of the current conditions, and an icon that corresponds to the current conditions. Notice how the temperatures are being passed to the ConvertKtoF function created earlier.</p> <p>Figure 3 Updating the Razor Script</p> <pre><code><h1> @ziplookup.places[0].city, @ziplookup.places[0].stateabbr<br /> @ConvertKtoF(currentcondition.main.temp, 1) &#176;F </h1> <h2> @currentcondition.weather[0].main <img src="@imgurl" style="display:inline" /> </h2> <h3> <span style="display:inline;color:red">HI @ConvertKtoF(currentcondition.main.temp_max, 0) &#176;F</span> / <span style="color:blue">LO @ConvertKtoF( currentcondition.main.temp_min, 0) &#176;F </span><br /> </h3> </code></pre> <h2 id="adding-national-weather-service-alerts">Adding National Weather Service Alerts</h2> <p>The final Web service will retrieve alerts from the National Weather Service (NWS) for the state the ZIP code is in.</p> <p><strong>Calling the NWS Web Service</strong> The following block of code retrieves all the current severe weather alerts from the NWS:</p> <pre><code class="lang-csharp">alerts = await Http.GetJsonAsync<Models.Alert>( $"https://api.weather.gov/alerts/active/area/{ziplookup.places[0].stateabbr}"); </code></pre> <p>The state abbreviation retrieved from the ziplookup Web service is used to filter the alerts to the same state.</p> <p><strong>Deserializing NWS Alerts Data</strong> The JSON data needs to be deserialized into a set of classes that aligns with the NWS JSON schema. The code snippet in <strong>Figure 4</strong> holds the alerts returned from the NWS Web service.</p> <p>Figure 4 Deserializing the JSON Data</p> <pre><code class="lang-csharp">public class Alert { public String type { get; set; } public String title { get; set; } public Feature[] features { get; set; } } public class Feature { public String type { get; set; } public PropertyInfo properties { get; set; } } public class PropertyInfo { public String headline { get; set; } public String description { get; set; } public DateTime effective { get; set; } public DateTime expires { get; set; } } </code></pre> <p><strong>Displaying the Severe Weather Alerts</strong> Now that the alerts have been retrieved, the code in <strong>Figure 5</strong> is added to the Razor view to display them. It creates a table to display the alerts and uses <span class="no-loc" dir="ltr" lang="en-us">@foreach</span> to loop through and display each alert. The effective date, headline, and description are displayed for each alert. If there are no alerts for the state, the table will be empty.</p> <p>Figure 5 Displaying Weather Alerts</p> <pre><code><table class="table"> <thead> <tr> <th>Date</th> <th>Alert</th> </tr> </thead> <tbody> @foreach (var alert in alerts.features) { <tr> <td>@alert.properties.effective.ToString("MM/dd/yyyy hh:mmt")</td> <td> <span style="font-weight:600">@alert.properties.headline</span><br /> <span>@alert.properties.description</span> </td> </tr> } </tbody> </table> </code></pre> <h2 id="putting-it-all-together">Putting It All Together</h2> <p>The application is now complete, as shown in <strong>Figure 6</strong>. It runs entirely in the browser and calls three external Web services to display the current weather conditions and alerts.</p> <p><img src="images/mt829752.0918_miller_figure10_hires(en-us,msdn.10).png" alt="The Finished Application" title="The Finished Application" data-linktype="relative-path"><br> <strong>Figure 6 The Finished Application</strong></p> <h2 id="publishing-the-blazor-application">Publishing the Blazor Application</h2> <p>Publishing a Blazor application is just as easy as publishing any other ASP.NET application. Doing this from Visual Studio will compile the application code and generate a complete Web application with all of the final HTML, CSS, scripts and Blazor binaries needed. A good starting place is to use the Publish to Folder option. All of the files of the application get put there. If you look inside the Dist folder, you’ll find a standard index.htm page and an _framework folder. The _framework folder contains all of the compiled assemblies, as well as the Blazor and Mono runtime components.</p> <h2 id="next-steps">Next Steps</h2> <p>Blazor is still an experimental framework, though the team is moving forward at a very fast rate of release. The Blazor roadmap details a full framework with routing, components, layouts and more. The Blazor project is being developed in the open on GitHub. Visit the blazor.net page to keep up-to-date and start experimenting with the current bits.</p> <hr> <p><strong>Jonthan Miller</strong> <em>is a senior architect. He’s been developing products on the Microsoft stack for a decade and programming on .NET since its inception. Waldman is a full-stack product developer with expertise in front-end technologies (Windows Forms, Windows Presentation Foundation, Silverlight, ASP.NET, AngularJS/Bootstrap), middleware (Windows services, Web API), and back ends (SQL server, Azure).</em></p> <p>Thanks to the following technical experts who reviewed this article: Dino Esposito (BaxEnergy), Daniel Roth (Microsoft)</p> <hr> <p><a href="https://social.msdn.microsoft.com/forums/en-us/8d860811-8266-4172-9953-66593ff944f4/web-development-c-in-the-browser-with-blazor?forum=msdnmagazine" data-linktype="external">Discuss this article in the MSDN Magazine forum</a></p> </div><div id="ms--inline-notifications" class="margin-block-xs" data-bi-name="inline-notification"></div><div id="assertive-live-region" role="alert" aria-live="assertive" class="visually-hidden" aria-relevant="additions" aria-atomic="true"></div> <div id="polite-live-region" role="status" aria-live="polite" class="visually-hidden" aria-relevant="additions" aria-atomic="true"></div> <!-- </content> --> </main><!-- recommendations section --><!-- end recommendations section --> <!-- feedback section --><!-- end feedback section --> <!-- feedback report section --><!-- end feedback report section --><aside id="ms--additional-resources-mobile" aria-label="Additional resources" class="display-none-desktop display-none-print" > <hr class="hr" hidden /> <h2 id="ms--additional-resources-mobile-heading" class="title is-3" hidden>Additional resources</h2> <section id="right-rail-recommendations-mobile" data-bi-name="recommendations" hidden></section> <section id="right-rail-training-mobile" data-bi-name="learning-resources-card" hidden></section> <section id="right-rail-events-mobile" data-bi-name="events-card" hidden></section> <section id="right-rail-qna-mobile" data-bi-name="qna-link-card" hidden></section> </aside><div class="border-top is-visible-interactive has-default-focus margin-top-sm "><footer id="footer-interactive" data-bi-name="footer" class="footer-layout"><div class="display-flex gap-xs flex-wrap-wrap is-full-height padding-right-lg-desktop"><a data-mscc-ic="false" class="locale-selector-link button button-sm button-clear flex-shrink-0" href="#" data-bi-name="select-locale"> <span class="icon" aria-hidden="true"> <span class="docon docon-world"></span> </span> <span class="local-selector-link-text"></span></a><div class="ccpa-privacy-link" data-ccpa-privacy-link hidden> <a href="https://aka.ms/yourcaliforniaprivacychoices" class="button button-sm button-clear flex-shrink-0" data-mscc-ic="false" data-bi-name="your-privacy-choices" > <svg role="img" aria-label="California Consumer Privacy Act (CCPA) Opt-Out Icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 14" xml:space="preserve" height="16" width="43" focusable="false" > <title>California Consumer Privacy Act (CCPA) Opt-Out Icon</title> <path d="M7.4 12.8h6.8l3.1-11.6H7.4C4.2 1.2 1.6 3.8 1.6 7s2.6 5.8 5.8 5.8z" style="fill-rule:evenodd;clip-rule:evenodd;fill:#fff"></path> <path d="M22.6 0H7.4c-3.9 0-7 3.1-7 7s3.1 7 7 7h15.2c3.9 0 7-3.1 7-7s-3.2-7-7-7zm-21 7c0-3.2 2.6-5.8 5.8-5.8h9.9l-3.1 11.6H7.4c-3.2 0-5.8-2.6-5.8-5.8z" style="fill-rule:evenodd;clip-rule:evenodd;fill:#06f"></path> <path d="M24.6 4c.2.2.2.6 0 .8L22.5 7l2.2 2.2c.2.2.2.6 0 .8-.2.2-.6.2-.8 0l-2.2-2.2-2.2 2.2c-.2.2-.6.2-.8 0-.2-.2-.2-.6 0-.8L20.8 7l-2.2-2.2c-.2-.2-.2-.6 0-.8.2-.2.6-.2.8 0l2.2 2.2L23.8 4c.2-.2.6-.2.8 0z" style="fill:#fff"></path> <path d="M12.7 4.1c.2.2.3.6.1.8L8.6 9.8c-.1.1-.2.2-.3.2-.2.1-.5.1-.7-.1L5.4 7.7c-.2-.2-.2-.6 0-.8.2-.2.6-.2.8 0L8 8.6l3.8-4.5c.2-.2.6-.2.9 0z" style="fill:#06f"></path> </svg> <span>Your Privacy Choices</span> </a> </div> <div class="flex-shrink-0"> <div class="dropdown has-caret-up"> <button class="dropdown-trigger button button-clear button-sm has-inner-focus theme-dropdown-trigger" aria-controls="theme-menu-interactive" aria-expanded="false" title="Theme" data-bi-name="theme"> <span class="icon"> <span class="docon docon-sun" aria-hidden="true"></span> </span> <span>Theme</span> <span class="icon expanded-indicator" aria-hidden="true"> <span class="docon docon-chevron-down-light"></span> </span> </button> <div class="dropdown-menu" id="theme-menu-interactive" role="menu"> <ul class="theme-selector padding-xxs" role="none"> <li class="theme display-block" role="menuitem"> <button class="button button-clear button-sm theme-control button-block justify-content-flex-start" data-theme-to="light"> <span class="theme-light margin-right-xxs"> <span class="theme-selector-icon border display-inline-block has-body-background" aria-hidden="true"> <svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 14"> <rect width="22" height="14" class="has-fill-body-background" /> <rect x="5" y="5" width="12" height="4" class="has-fill-secondary" /> <rect x="5" y="2" width="2" height="1" class="has-fill-secondary" /> <rect x="8" y="2" width="2" height="1" class="has-fill-secondary" /> <rect x="11" y="2" width="3" height="1" class="has-fill-secondary" /> <rect x="1" y="1" width="2" height="2" class="has-fill-secondary" /> <rect x="5" y="10" width="7" height="2" rx="0.3" class="has-fill-primary" /> <rect x="19" y="1" width="2" height="2" rx="1" class="has-fill-secondary" /> </svg> </span> </span> <span>Light</span> </button> </li> <li class="theme display-block" role="menuitem"> <button class="button button-clear button-sm theme-control button-block justify-content-flex-start" data-theme-to="dark"> <span class="theme-dark margin-right-xxs"> <span class="border theme-selector-icon display-inline-block has-body-background" aria-hidden="true"> <svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 14"> <rect width="22" height="14" class="has-fill-body-background" /> <rect x="5" y="5" width="12" height="4" class="has-fill-secondary" /> <rect x="5" y="2" width="2" height="1" class="has-fill-secondary" /> <rect x="8" y="2" width="2" height="1" class="has-fill-secondary" /> <rect x="11" y="2" width="3" height="1" class="has-fill-secondary" /> <rect x="1" y="1" width="2" height="2" class="has-fill-secondary" /> <rect x="5" y="10" width="7" height="2" rx="0.3" class="has-fill-primary" /> <rect x="19" y="1" width="2" height="2" rx="1" class="has-fill-secondary" /> </svg> </span> </span> <span>Dark</span> </button> </li> <li class="theme display-block" role="menuitem"> <button class="button button-clear button-sm theme-control button-block justify-content-flex-start" data-theme-to="high-contrast"> <span class="theme-high-contrast margin-right-xxs"> <span class="border theme-selector-icon display-inline-block has-body-background" aria-hidden="true"> <svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 14"> <rect width="22" height="14" class="has-fill-body-background" /> <rect x="5" y="5" width="12" height="4" class="has-fill-secondary" /> <rect x="5" y="2" width="2" height="1" class="has-fill-secondary" /> <rect x="8" y="2" width="2" height="1" class="has-fill-secondary" /> <rect x="11" y="2" width="3" height="1" class="has-fill-secondary" /> <rect x="1" y="1" width="2" height="2" class="has-fill-secondary" /> <rect x="5" y="10" width="7" height="2" rx="0.3" class="has-fill-primary" /> <rect x="19" y="1" width="2" height="2" rx="1" class="has-fill-secondary" /> </svg> </span> </span> <span>High contrast</span> </button> </li> </ul> </div> </div> </div> </div> <ul class="links" data-bi-name="footerlinks"> <li class="manage-cookies-holder" hidden></li><li><a class="external-link-indicator" data-mscc-ic="false" href="/en-us/previous-versions/" data-bi-name="archivelink">Previous Versions</a></li> <li><a class="external-link-indicator" data-mscc-ic="false" href="https://techcommunity.microsoft.com/t5/microsoft-learn-blog/bg-p/MicrosoftLearnBlog" data-bi-name="bloglink">Blog</a></li> <li><a class="external-link-indicator" data-mscc-ic="false" href="/en-us/contribute/" data-bi-name="contributorGuide">Contribute</a></li><li><a class="external-link-indicator" data-mscc-ic="false" href="https://go.microsoft.com/fwlink/?LinkId=521839" data-bi-name="privacy">Privacy</a></li><li><a class="external-link-indicator" data-mscc-ic="false" href="/en-us/legal/termsofuse" data-bi-name="termsofuse">Terms of Use</a></li><li><a class="external-link-indicator" data-mscc-ic="false" href="https://www.microsoft.com/legal/intellectualproperty/Trademarks/" data-bi-name="trademarks">Trademarks</a></li><li>© Microsoft 2024</li> </ul> </footer></div></div><div id="ms--additional-resources" class="right-container column is-4-desktop display-none display-block-desktop" data-bi-name="pageactions" role="complementary" aria-label="Additional resources" > <div id="affixed-right-container" class="margin-top-sm-tablet" data-bi-name="right-column"> <h2 id="ms--additional-resources-heading" class="title is-6 margin-top-md" hidden>Additional resources</h2> <section id="right-rail-events" data-bi-name="events-card" hidden></section> <section id="right-rail-training" data-bi-name="learning-resources-card" hidden></section> <section id="right-rail-recommendations" data-bi-name="recommendations" hidden></section> <nav id="side-doc-outline" class="doc-outline" data-bi-name="intopic toc" aria-label="In this article"> <h3>In this article</h3> </nav> <section id="right-rail-qna" class="margin-top-xxs" data-bi-name="qna-link-card" hidden></section> </div> </div></div> <!--end of div.columns --> </section> <!--end of .primary-holder --> <!-- interactive container --> <aside id="interactive-container" class="interactive-container is-visible-interactive column has-body-background-dark "> </aside> <!-- end of interactive container --> </div> </div> <!--end of .mainContainer --> <section class="border-top has-default-focus is-hidden-interactive margin-top-sm "><footer id="footer" data-bi-name="footer" class="footer-layout uhf-container has-padding" role="contentinfo"><div class="display-flex gap-xs flex-wrap-wrap is-full-height padding-right-lg-desktop"><a data-mscc-ic="false" class="locale-selector-link button button-sm button-clear flex-shrink-0" href="#" data-bi-name="select-locale"> <span class="icon" aria-hidden="true"> <span class="docon docon-world"></span> </span> <span class="local-selector-link-text"></span></a><div class="ccpa-privacy-link" data-ccpa-privacy-link hidden> <a href="https://aka.ms/yourcaliforniaprivacychoices" class="button button-sm button-clear flex-shrink-0" data-mscc-ic="false" data-bi-name="your-privacy-choices" > <svg role="img" aria-label="California Consumer Privacy Act (CCPA) Opt-Out Icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 14" xml:space="preserve" height="16" width="43" focusable="false" > <title>California Consumer Privacy Act (CCPA) Opt-Out Icon</title> <path d="M7.4 12.8h6.8l3.1-11.6H7.4C4.2 1.2 1.6 3.8 1.6 7s2.6 5.8 5.8 5.8z" style="fill-rule:evenodd;clip-rule:evenodd;fill:#fff"></path> <path d="M22.6 0H7.4c-3.9 0-7 3.1-7 7s3.1 7 7 7h15.2c3.9 0 7-3.1 7-7s-3.2-7-7-7zm-21 7c0-3.2 2.6-5.8 5.8-5.8h9.9l-3.1 11.6H7.4c-3.2 0-5.8-2.6-5.8-5.8z" style="fill-rule:evenodd;clip-rule:evenodd;fill:#06f"></path> <path d="M24.6 4c.2.2.2.6 0 .8L22.5 7l2.2 2.2c.2.2.2.6 0 .8-.2.2-.6.2-.8 0l-2.2-2.2-2.2 2.2c-.2.2-.6.2-.8 0-.2-.2-.2-.6 0-.8L20.8 7l-2.2-2.2c-.2-.2-.2-.6 0-.8.2-.2.6-.2.8 0l2.2 2.2L23.8 4c.2-.2.6-.2.8 0z" style="fill:#fff"></path> <path d="M12.7 4.1c.2.2.3.6.1.8L8.6 9.8c-.1.1-.2.2-.3.2-.2.1-.5.1-.7-.1L5.4 7.7c-.2-.2-.2-.6 0-.8.2-.2.6-.2.8 0L8 8.6l3.8-4.5c.2-.2.6-.2.9 0z" style="fill:#06f"></path> </svg> <span>Your Privacy Choices</span> </a> </div> <div class="flex-shrink-0"> <div class="dropdown has-caret-up"> <button class="dropdown-trigger button button-clear button-sm has-inner-focus theme-dropdown-trigger" aria-controls="theme-menu" aria-expanded="false" title="Theme" data-bi-name="theme"> <span class="icon"> <span class="docon docon-sun" aria-hidden="true"></span> </span> <span>Theme</span> <span class="icon expanded-indicator" aria-hidden="true"> <span class="docon docon-chevron-down-light"></span> </span> </button> <div class="dropdown-menu" id="theme-menu" role="menu"> <ul class="theme-selector padding-xxs" role="none"> <li class="theme display-block" role="menuitem"> <button class="button button-clear button-sm theme-control button-block justify-content-flex-start" data-theme-to="light"> <span class="theme-light margin-right-xxs"> <span class="theme-selector-icon border display-inline-block has-body-background" aria-hidden="true"> <svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 14"> <rect width="22" height="14" class="has-fill-body-background" /> <rect x="5" y="5" width="12" height="4" class="has-fill-secondary" /> <rect x="5" y="2" width="2" height="1" class="has-fill-secondary" /> <rect x="8" y="2" width="2" height="1" class="has-fill-secondary" /> <rect x="11" y="2" width="3" height="1" class="has-fill-secondary" /> <rect x="1" y="1" width="2" height="2" class="has-fill-secondary" /> <rect x="5" y="10" width="7" height="2" rx="0.3" class="has-fill-primary" /> <rect x="19" y="1" width="2" height="2" rx="1" class="has-fill-secondary" /> </svg> </span> </span> <span>Light</span> </button> </li> <li class="theme display-block" role="menuitem"> <button class="button button-clear button-sm theme-control button-block justify-content-flex-start" data-theme-to="dark"> <span class="theme-dark margin-right-xxs"> <span class="border theme-selector-icon display-inline-block has-body-background" aria-hidden="true"> <svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 14"> <rect width="22" height="14" class="has-fill-body-background" /> <rect x="5" y="5" width="12" height="4" class="has-fill-secondary" /> <rect x="5" y="2" width="2" height="1" class="has-fill-secondary" /> <rect x="8" y="2" width="2" height="1" class="has-fill-secondary" /> <rect x="11" y="2" width="3" height="1" class="has-fill-secondary" /> <rect x="1" y="1" width="2" height="2" class="has-fill-secondary" /> <rect x="5" y="10" width="7" height="2" rx="0.3" class="has-fill-primary" /> <rect x="19" y="1" width="2" height="2" rx="1" class="has-fill-secondary" /> </svg> </span> </span> <span>Dark</span> </button> </li> <li class="theme display-block" role="menuitem"> <button class="button button-clear button-sm theme-control button-block justify-content-flex-start" data-theme-to="high-contrast"> <span class="theme-high-contrast margin-right-xxs"> <span class="border theme-selector-icon display-inline-block has-body-background" aria-hidden="true"> <svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 14"> <rect width="22" height="14" class="has-fill-body-background" /> <rect x="5" y="5" width="12" height="4" class="has-fill-secondary" /> <rect x="5" y="2" width="2" height="1" class="has-fill-secondary" /> <rect x="8" y="2" width="2" height="1" class="has-fill-secondary" /> <rect x="11" y="2" width="3" height="1" class="has-fill-secondary" /> <rect x="1" y="1" width="2" height="2" class="has-fill-secondary" /> <rect x="5" y="10" width="7" height="2" rx="0.3" class="has-fill-primary" /> <rect x="19" y="1" width="2" height="2" rx="1" class="has-fill-secondary" /> </svg> </span> </span> <span>High contrast</span> </button> </li> </ul> </div> </div> </div> </div> <ul class="links" data-bi-name="footerlinks"> <li class="manage-cookies-holder" hidden></li><li><a class="external-link-indicator" data-mscc-ic="false" href="/en-us/previous-versions/" data-bi-name="archivelink">Previous Versions</a></li> <li><a class="external-link-indicator" data-mscc-ic="false" href="https://techcommunity.microsoft.com/t5/microsoft-learn-blog/bg-p/MicrosoftLearnBlog" data-bi-name="bloglink">Blog</a></li> <li><a class="external-link-indicator" data-mscc-ic="false" href="/en-us/contribute/" data-bi-name="contributorGuide">Contribute</a></li><li><a class="external-link-indicator" data-mscc-ic="false" href="https://go.microsoft.com/fwlink/?LinkId=521839" data-bi-name="privacy">Privacy</a></li><li><a class="external-link-indicator" data-mscc-ic="false" href="/en-us/legal/termsofuse" data-bi-name="termsofuse">Terms of Use</a></li><li><a class="external-link-indicator" data-mscc-ic="false" href="https://www.microsoft.com/legal/intellectualproperty/Trademarks/" data-bi-name="trademarks">Trademarks</a></li><li>© Microsoft 2024</li> </ul> </footer> </section> <div id="action-panel" role="region" aria-label="Action Panel" class="action-panel has-default-focus" tabindex="-1"></div> </body> </html>