DEV Community

Cover image for VS2022 and Blazor Review: 11/25/2021
John Peters
John Peters

Posted on • Updated on

VS2022 and Blazor Review: 11/25/2021

Today we look at VS2022 which contains .NET 6.0 and Blazor. It was released to GA on November 18th, 2022.

Disclaimer: I am by no means a huge fan of Microsoft's past leadership. They've demonstrated multiple times in the past that they will "Throw their own adopters under the bus" by quitting work and not admitting it, or starting something new to give three ways to do the same things. Just think Silverlight, WPF, UWP, Entity Framework (I still miss the designer) and oh yes, the worst; Internet Explorer. They spent over 25 years in the desert repeatedly telling us of the promised land we never reached.

VS2022 Community Edition

The VS2022 install was excellent, the installer downloaded all the content in the background and then performed the install without any issue.

My laptop had VS2017, VS2019 and even a VS2020-preview version already installed. Upon removing these, VS2022 was unaffected.

VS2022 comes with .NET 6.0 but I also had two or three other versions at the time and left them alone.

On the surface VS2022 seems to be fast, and has a new look.

Nice job MSFT.

Blazor

This was my first dive into Blazor; however, I've worked with MVC, Angular, React, ASP.NET Core 3.2 and Razor in my past. I'm thinking, I should be able to get this with little effort. Indeed, there was little effort creating and understanding a start up project.

My project goal was to get Dev.To Articles, using Blazor with both a Blazor client and server.

The Layout of a Blazor Server App

I did notice that unlike MVC or Web Api projects, there's not a clear distinction of front-end and back end. No explicit back-end routing or "special" folders. The data folder held all the .cs files. This must be the "back-end".

Image description

The Service

This code was the HTTP Get to Dev.To for the articles.

using Newtonsoft.Json;
namespace BlazorApp2.Data
{
    public class DevToService
    {

        public async Task<ArticleModel[]> GetArticles()
        {

            var cli = new HttpClient();
            var req = new HttpRequestMessage(HttpMethod.Get, "https://dev.to/api/articles?username =Juan & per_page = 2000");
            var resp = await cli.SendAsync(req);
            resp.EnsureSuccessStatusCode();
            var result = await resp.Content.ReadAsStringAsync();
            ArticleModel[] sorted = ParseAndSort(result);
            return sorted;

        }

        private ArticleModel[] ParseAndSort(string result)
        {
            var articles = JsonConvert.DeserializeObject<ArticleModel[]>(result);
            articles = OrderByDate(articles);
            return articles;
        }

        internal ArticleModel[] OrderByDate(ArticleModel[] articles)
        {
            return articles.OrderByDescending(item => DateTime.Parse(item.created_at)).ToArray();
        }
        internal ArticleModel[] OrderByTitle(ArticleModel[] articles)
        {
            return articles.OrderBy(item => item.title).ToArray();
        }
        internal ArticleModel[] OrderByDesc(ArticleModel[] articles)
        {
            if (articles != null)
            {
                return articles.OrderBy(item => item.description).ToArray();
            }
            return null;
        }
        internal ArticleModel[] OrderByURL(ArticleModel[] articles)
        {
            return articles.OrderBy(item => item.url).ToArray();
        }


    }
}
Enter fullscreen mode Exit fullscreen mode

I started to use the System.Text.Json class for parsing to see if anything new was there. Nope, it's still the same as before, and I still don't like it. I switched to Netwonsoft.JSON instead and created this model.

The Model

using Newtonsoft.Json;
using System.ComponentModel.DataAnnotations;

namespace BlazorApp2.Data
{
    public class ArticleModel
    {
        [JsonProperty(PropertyName = "type_of")]
        public string type_of;
        [JsonProperty(PropertyName = "id")]
        public string id;
        [JsonProperty(PropertyName = "title")]
        public string title;
        [JsonProperty(PropertyName = "description")]
        public string description;
        [JsonProperty(PropertyName = "readable_publish_date")]
        public string readable_publish_date;
        [JsonProperty(PropertyName = "slug")]
        public string slug;
        [JsonProperty(PropertyName = "path")]
        public string path;
        [JsonProperty(PropertyName = "url")]
        public string url;
        [JsonProperty(PropertyName = "comments_count")]
        public string comments_count;
        [JsonProperty(PropertyName = "positive_reactions_count")]
        public string positive_reactions_count;
        [JsonProperty(PropertyName = "social_image")]
        public string social_image;
        [JsonProperty(PropertyName = "canonical_url")]
        public string canonical_url;
        private string _date = "";

        [JsonProperty(PropertyName = "created_at")]

        public string created_at
        {
            get { return _date; }
            set
            {

                var cvt = DateTime.Parse(value);
                var test = cvt.ToLocalTime().ToString("G");
                _date = test;
            }
        }

        [JsonProperty(PropertyName = "edited_at")]

        public string edited_at;
        [JsonProperty(PropertyName = "crossposted_at")]
        public string crossposted_at;
        [JsonProperty(PropertyName = "published_at")]
        public string published_at;
        [JsonProperty(PropertyName = "last_comment_at")]
        public string last_comment_at;
        [JsonProperty(PropertyName = "reading_time_minutes")]
        public string reading_time_minutes;
        [JsonProperty(PropertyName = "tag_list")]
        public string[] tag_list;
        [JsonProperty(PropertyName = "tags")]
        public string tags;
        [JsonProperty(PropertyName = "user")]
        public user user;

    }
    public class user
    {
        public string name;
        public string username;
        public string twitter_username;
        public string github_username;
        public string website_url;
        public string profile_image;
        public string profile_image_90;
    }
Enter fullscreen mode Exit fullscreen mode

Blazor Client

I didn't realize that running just a Blazor WASM client presented the exact same limits as a Web Page does. For example, there's no way to issue a Cross-Domian Get Request from just the client. For that there must be a Blazor Client and Server to work.

Forget about setting a Debugger.Break() statement too as it doesn't work when using just the client. Instead, you'll need to read up on 5 pages of alternative methods to set a break point. I had no time for that.

Blazor Server

After learning of the limitations of a Blazor Client only project, it was time to create the Blazor Client and Server project.

Image description

Much of the configuration looks exactly like MVC or Web API configuration. Making it familiar territory for any ASP.NET (MVC or Web API) work of the past.

By using the Blazor Server, the cross-domain requests were allowed with just this configuration below. But unlike (MVC and Web API projects), no explicit CORs configuration was needed, which seemed a bit odd to me.

using BlazorApp2.Data;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddSingleton<DevToService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();
Enter fullscreen mode Exit fullscreen mode

Blazor Pages Using the Service

Actually there are no Blazor pages they are Razor pages as shown below. Note the injection of the service (to get the articles)

@page "/devto"
@using BlazorApp2.Data
@inject DevToService DevToService

<PageTitle>devto</PageTitle>

<h1>devto</h1>

<div>
    <h3>Articles</h3>
    <div>
        <button @onclick="@OnOrderByDate" >Order by Date</button>
        <button @onclick="@OnOrderByTitle">Order by Title</button>
        <button @onclick="@OnOrderByDescription">Order by Desc.</button>
        <button @onclick="@OnOrderByURL">Order by URL</button>
    </div>
     @if (@articles == null)
    {
      <div> Getting Data.... </div>
    }
    @if (@articles != null)
    {
        <table>
            <thead>
                <tr>
                    <th>Created</th>
                    <th>Title</th>
                    <th>Description</th>
                    <th>URL</th>
                </tr>

            </thead>

            @foreach (var item in @articles)
            {
                <tr>
                    <td>@item.created_at</td>
                    <td>@item.title</td>
                    <td>@item.description</td>
                    <td><a href='@item.url'>@item.url</a></td>

                </tr>
            }


    </table>
    }  
</div>

@code {
    private string response = "Not Yet";
    private ArticleModel[] articles;

    protected override async Task OnInitializedAsync() => articles = await DevToService.GetArticles();
    protected void OnOrderByDescription(MouseEventArgs args)
    {
        articles = DevToService.OrderByDesc(articles);        
    }
    protected void OnOrderByTitle(MouseEventArgs args)
    {
        articles = DevToService.OrderByTitle(articles); 
    }
    protected void OnOrderByDate(MouseEventArgs args)
    {
        articles = DevToService.OrderByDate(articles);
    }
    protected void OnOrderByURL(MouseEventArgs args)
    {
        articles = DevToService.OrderByURL(articles);
    }
}
Enter fullscreen mode Exit fullscreen mode

This markup is similar to PHP; which, is still a very popular web-based framework. Just simply embed C# (in the HTML) with the special '@' syntax and you're off.

Note the similarity to React's JSX syntax, in that the code and the HTML are both in the same file.

Result

Image description

Hot Reload?

One may think by reading the release notes; that Hot-Reload is a new thing that revolutionizes development. Didn't they get the memo on this 20 years ago?

I found it to work sometimes and not others. I guess it depends on the change being made. All service level changes require an app restart if that service is a singleton. Mostly however, I was not impressed with the "Hot Reload" hype.

Impressions

Blazor for new comers appears to be a diamond in the rough. The more I dug into it, the better it got. So much so, that I feel as if an epiphany had happened.

I kept thinking things like:

  • You mean I am free of NPM now? (except for very select packages)
  • I really only need to focus on CSS as C# can provide all the content and layout using only sematic HTML5?
  • I can deliver totally secure applications?
  • That WASM generates .exe files which act similar to a desktop application?
  • I am able to be fully vested in true WASM based Web Components?
  • I can write C# code where we have been using Typescript or Javascript for so long.
  • I can compile to any of today's popular Operating Systems?
  • Wow!

One interesting point was with JSON Dates coming in as UTC serialized strings, running the conversion in C# was simple as shown here:

internal ArticleModel[] OrderByDate(ArticleModel[] articles)
        {
            return articles.OrderByDescending(item => DateTime.Parse(item.created_at)).ToArray();
        }
Enter fullscreen mode Exit fullscreen mode

Try that conversion in Javascript!

Intellisense

For the back-end service part, intellisense is good; however, the client side intellisense (when using the "@") is bad. It's useless; which, leaves you guessing at method names and properties.

Summary

WASM is cool and the page response times are excellent (once it all loads). Did it have something to do with WASM just being faster? I can't say; but, compared to doing the exact same thing in Angular it seemed faster to me. Nice Job MSFT!

Blazor is a niche framework with tomorrow in mind (WASM). .NET already has a huge lead with support for Generic collections and the fantastic System.Linq framework as well as Entity Framework support for SQL back ends. It fully supports Asynchronous programming which means all back-end Tasks are CPU agnostic (always choosing the lowest used) CPU for new Tasks. This means Speed!

Throw in Newtsonsoft.JSON; and, the elements for a potential industry disrupter are here.

The only thing holding back Blazor is widespread adoption in the industry and the lack of Open Source Blazor components.

I've changed my mind on Blazor and feel it's a solid player, too bad it took them 15 years to get here.

Publishing the App

I was totally surprised by the ease of which I could publish the application (to a local folder).

It was small and lighting fast, without the need to do any IIS setup. Just click on the .exe file and it runs.

Image description

Now that is ultra-cool.

Discussion (0)