DotNetT2 Light_1200x303

One of the merits of ASP.NET Core is how it manages to provide a number of built-in solutions for different use cases without getting in the way.

In this blog post we take a look at some of these built-in solutions in ASP.NET Core (MVC, Razor Pages, Blazor), what each one is best suited for, and how we could use them to build a fictional supercar.fans app.

Background

ASP.NET MVC was a Ruby on Rails inspired, open-source web framework for .NET, released in 2009.1 (Fun fact: Scott Guthrie wrote the first few hundred lines of this framework while on a flight.2) In 2014, the ASP.NET team began rewriting ASP.NET MVC to make it more cloud-friendly, and in the process ended up making it cross-platform.3 4 About two years later, in 2016, these were released as ASP.NET Core 1.0,5 along with the cross-platform .NET Core 1.0.6

Today, ASP.NET Core is at 3.1 and targets .NET Core 3.1 by default. By targeting .NET Core 3.1, all APIs available in .NET Core 3.1 are made available to ASP.NET Core 3.1 users.

The ASP.NET Core Framework

ASP.NET Core is a web application framework that lays the groundwork for creating web apps in .NET Core. It provides out-of-the-box support for dependency injection, logging, configuration, etc., provides complete control of the request pipeline through its middleware API, and a lot more.

A very basic Hello World application in ASP.NET Core could look like:

public static class Program
{
    public static void Main() =>
        WebHost.CreateDefaultBuilder() // create a webhost builder
            .Configure(app =>
            {
                app.UseRouting();
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapGet("/", async context => 
                        await context.Response.WriteAsync("Hello World"));
                });
            })
            .Build()           // use the builder to build the webhost
            .Run();            // run the webhost
}

It creates a WebHost, and Configures it to use the routing middleware, and then specifies what to do when a HTTP GET request to / is received.

ASP.NET Core also provides three main higher level frameworks:

  1. MVC
  2. Razor Pages
  3. Blazor

Each of these address different use-cases. And since they are added to the application as middleware, they can easily co-exist in the same application allowing us to use the right tool for the job.

ASP.NET Core MVC

This is very similar to the old ASP.NET MVC in concept.

We have a bunch of controller actions (C# methods) that accept and process HTTP requests, and return Razor views.

Here’s a Hello World example:

// Controllers/HelloWorldController.cs

public class HelloWorldController : Controller
{
    [HttpGet("/")]
    public IActionResult Hello() => View();
}
<!-- Views/Hello.cshtml -->

<p>Hello World!</p>

We can also use MVC without Razor views for APIs. The following example returns a JSON object instead:

// Controllers/HelloWorldController.cs

[ApiController]
public class HelloWorldController : ControllerBase
{
    public IActionResult Hello() => 
        Ok(new { Message = "Hello World" });
}

Writing Web APIs using MVC is a joy. For web pages though, MVC can feel like ceremony despite the flexibility it offers.

If you’re trying to create a Web API, whether for public consumption or for your client applications, MVC is the perfect tool for the job.

ASP.NET Core Razor Pages

This follows a page model, similar to PHP in a sense. You have a bunch of Razor (.cshtml) files for the actual web pages, and optional C# (.cs) files for backend logic (like processing a form’s input).

A Hello World Razor page could look like:

<!-- Pages/Index.cshtml -->
@page

<p>Hello, world!</p>

No need for controller-actions, like in MVC. Let’s say we needed to query the database to display a list of items. We could do something like:

<!-- Pages/Items.cshtml -->
@page
@inject IItemRepository db
@{
    var items = await db.GetItems();
}

<ol>
@foreach(var item in items)
{
    <li>@item.Title</li>
}
</ol>

The major advantage of Razor Pages over MVC is how developer-friendly it is when it comes to making web pages. Let’s say you need to add a new page with a contact form to your app. With Razor Pages, you simply add a single Contact.cshtml file for the markup, and a Contact.cshtml.cs file to process the submitted form.

The only caveat is when the Razor page needs to make an Ajax request (say for validating a field on the client side). In this case it’s easiest to host the validation API with MVC.

Other Supported Technologies

gRPC Remote Procedure Calls (gRPC)

gRPC is a language-agnostic, high-performance Remote Procedure Call (RPC) framework, particularly useful in microservice environments. Microsoft Docs has a page describing scenarios when gRPC makes sense. ASP.NET Core has built-in support for gRPC applications.

SignalR

SignalR provides an abstraction over a bunch of transport protocols, including WebSockets for .NET apps to send real-time notifications to the browser. SignalR can be used to implement features like dashboard notifications where the server needs to initiate an operation on the client.

ASP.NET Core Worker Services

This is like a naive console application that runs on the server. It can be used as a background service (e.g. to process long-running jobs), or to perform little tasks on the server that run to completion (e.g. to seed test data). Workers are meant to be deployed independent of an ASP.NET application, but if you want it to be part of the ASP.NET application you can register the respective IHostedServices with the DI.

Blazor

From the website:

Blazor lets you build interactive web UIs using C# instead of JavaScript. Blazor apps are composed of reusable web UI components implemented using C#, HTML, and CSS. Both client and server code is written in C#, allowing you to share code and libraries.

ASP.NET Core has two implementations for Blazor: one that runs on the server but can seamlessly talk to the client browser, and the other that runs on the browser like any other SPA.

All Blazor implementations follow the same component model—the different implementations just make it possible to render the components in different ways. More on that in a bit.

Blazor Server

Blazor Server components execute on the server, and use a SignalR connection to inject DOM updates to the client browser.

Something that makes Blazor stand out from traditional SPAs is how it manages to bridge the client and the server in a way that hasn’t been possible until now. For instance, a Blazor Component that needs to display some data can fetch the data from the database on the server, and display it on the client browser—something that Vue or React components were not meant to do.

The following code will first show “Loading items…”, and then replace that div with a list of items after Items is populated from the database:

@page "/"
@inject IItemRepository db

@code {
    private IReadOnlyList<Item>? Items { get; set; }
}

@if(Items is null)
{
    <div><p>Loading items...</p></div>
}
else
{
    <ol>
    @foreach (var item in Items)
    {
        <li>@item.Name</li>
    }
    </ol>
}

@code {
    protected override async Task OnInitializedAsync()
    {
        Items = await db.GetItems();
    }
}
Blazor WebAssembly

Blazor WebAssembly allows you to run .NET code in the browser using the open WebAssembly standard. This was recently released after spending a long time in preview, and is finally production-ready.

Unlike Blazor Server, this is completely separate from the Server, and will need a Web API backend like any other SPA would. If the API is written in C#, it provides a great opportunity for code re-use particularly with respect to the request/response ViewModels and validation logic.

Following is the WebAssembly version of the previous example:

// The MVC action that returns the items from the server
[HttpGet("/items")]
public async Task<IActionResult> Get() =>
    Ok(await db.GetItems());
@page "/"
@inject HttpClient http

@code {
    private IReadOnlyList<Item>? Items { get; set; }
}

@if(Items is null)
{
    <div><p>Loading items...</p></div>
}
else
{
    <ol>
    @foreach (var item in Items)
    {
        <li>@item.Name</li>
    }
    </ol>
}

@code {
    protected override async Task OnInitializedAsync()
    {
        Items = await http.GetFromJsonAsync<List<Item>>("/items");
    }
}

We needed to expose a GET: /items API using MVC and make an HTTP GET request to it from the client (Blazor app). But we could reuse the Item class without needing to have a copy in the server and in the client app. In return for the little extra effort compared to Blazor Server, we get to run C# natively on the browser.

While this might not completely replace JavaScript in the browser, it does replace SPA frameworks like Angular, Vue.js, etc. Blazor WebAssembly apps—like any other SPA app—can be deployed to a static web server. Everything on the client also makes it possible to create Progressive Web Apps with full offline support.

The Blazor Component Model

At the core, what makes Blazor powerful is its component model. You build the application based on a common model, and the renderer will handle the specifics. This explains why the Blazor Server and the Blazor WebAssembly code samples above look so similar, despite the fact that they work differently.

Take a look at the snippet below that uses Telerik UI for Blazor’s grid-view. This too will work on every Blazor Platform Telerik supports.

@page "/"
@inject IProductService products

<TelerikGrid Data=@GridData EditMode="@GridEditMode.Inline" Height="500px"
             Pageable="true" PageSize="15"
             OnCreate="@CreateHandler" OnDelete="@DeleteHandler" OnUpdate="@UpdateHandler">
  <GridToolBar>
    <GridCommandButton Command="Add" Icon="add">Add Product</GridCommandButton>
  </GridToolBar>
  <GridColumns>
    <GridColumn Field=@nameof(Product.ProductName) Title="Product Name" />
    <GridColumn Field=@nameof(Product.UnitPrice) Title="Unit Price">
      <Template>
        @(String.Format("{0:C2}", (context as Product).UnitPrice))
      </Template>
    </GridColumn>
    <GridColumn Field=@nameof(Product.UnitsInStock) Title="Units In Stock" />
    <GridCommandColumn>
      <GridCommandButton Command="Edit" Icon="edit">Edit</GridCommandButton>
      <GridCommandButton Command="Delete" Icon="delete">Delete</GridCommandButton>
      <GridCommandButton Command="Save" Icon="save" ShowInEdit="true">Update</GridCommandButton>
      <GridCommandButton Command="Cancel" Icon="cancel" ShowInEdit="true">Cancel</GridCommandButton>
    </GridCommandColumn>
  </GridColumns>
</TelerikGrid>

@code {
    public List<Product> GridData { get; set; }

    protected override void OnInitialized()
    {
      GridData = products.GetProducts();
    }

    private void CreateHandler(GridCommandEventArgs args)
    {
        // create product here
    }

    private void DeleteHandler(GridCommandEventArgs args)
    {
        // delete product here
    }

    private void UpdateHandler(GridCommandEventArgs args)
    {
        // update product here
    }
}

The component model also means that the Blazor team can build additional renderers for different scenarios, without us having to learn a new framework for each of them. One that’s particularly worth mentioning is Mobile Blazor Bindings. It’s currently in experimental preview, but you can still try it out.

The supercar.fans App

In this section, we’ll look at a fictional app, and how we could go about building them using ASP.NET Core. In order to stay on topic, we won’t delve into other concerns like persistence, caching, etc. or non-ASP.NET-specific technologies like serverless functions.

https://supercar.fans is a site for diehard supercar fans. It aggregates supercar related news from various sources using their RSS feeds, and lets you browse them in one place. The site content reviewers work hard behind the scenes to make sure you only see the most reliable news, leaks, and rumours.

Based on the copy above, we can deduce three different aspects of the app that could possibly be approached differently:

  1. RSS syndication
  2. The content-reviewer’s interface
  3. The end-user-facing public website

supercar.fans block diagram

RSS Syndication

We need a background job that checks a bunch of RSS feeds for new content, at a predefined interval—say once every hour. We can do this by using an ASP.NET Core Worker.

And we could start off by simply hosting the worker within our ASP.NET Core app directly. It can be refactored out when the need to scale-out arises.

Content Reviewer’s Interface

This kind of admin interface generally has a limited number of users compared to the public website—which means the usage is predictable, and so scale is seldom a problem to be solved. And if you were to look at the app from a business perspective, this is also one of those things that should ideally be low maintenance, while also being functional and reliable.

From what we’ve seen with ASP.NET Core, Blazor Server would be the perfect candidate for something like this. Quick to develop, and easy to maintain.

The Public Website

There are two ways one could go about this:

  1. Razor Pages: Razor pages would mean you drop a bunch of Razor (.cshtml) files, and you’re good to go. Again, low maintenance. I’d pick this if first load time is important.
  2. Blazor WebAssembly + MVC: We use Razor components (.razor) that run on the browser. And like we saw earlier in the post, this can be low maintenance too since we get to reuse the ViewModels used by the API Controllers.

If you noticed, the decision here doesn’t have to do with Razor Pages or Blazor WebAssembly per se, but with whether we’d like the “views” to be run on the server (Razor Pages) or on the client (Blazor WebAssembly).

The Future of .NET

The original .NET Framework 1.0 was released in February 2002. In 2016, .NET Core came into existence. This created a sort of division between the two frameworks, because though they were similar they weren’t the same. Since the initial release, the .NET team migrated about 120,000 APIs from the original .NET Framework, and came up with the .NET Standard to enable better code sharing.

Moving forward, instead of having a separate .NET Full Framework & a separate .NET Core, .NET Core 3.1’s next release will be the new .NET 5. Users of the Full .NET Framework are encouraged (but not required) to migrate to .NET 5. And next year, with the release of .NET 6, we’ll have Mono merging with it too.7 8

As far as web applications are concerned, stay away from the Full Framework’s web technologies (Web Forms, the old MVC, Web APIs, etc.) and you’ll be fine. ASP.NET Core is continuously improving, and the kind of innovation we’re seeing in the Blazor space is quite something.

Besides, C# as a language is ever-evolving too, and I can’t wait for November to use C# 9’s new features!

Closing

In this article we’ve seen a bunch of ASP.NET Core based technologies, what each of them is meant for, and how they could be used to build an application.

If you are new to .NET or learning to code, the Getting Started Tutorials at Microsoft Docs (here’s an example) is probably the best place to start.


Footnotes

  1. ASP.NET MVC 1.0—Scott Guthrie, 2019 · Blog Post ↩︎

  2. Professional ASP.NET MVC 1.0, 1st ed, p.170—Rob Conery, Scott Hanselman, Phil Haack, Scott Guthrie, 2009 · Amazon ↩︎

  3. Introducing ASP.NET vNext—Scott Hanselman, 2014 · Blog Post ↩︎

  4. ASP.NET vNext—David Fowler, 2014 · Blog Post ↩︎

  5. Announcing ASP.NET Core 1.0—Jeffrey Fritz, 2016 · ASP.NET Blog ↩︎

  6. Announcing .NET Core 1.0—Richard Lander 2016 · .NET Blog ↩︎

  7. .NET Core is the Future of .NET—Scott Hunter, May 2019 · .NET Blog ↩︎

  8. Announcing .NET 5 Preview 4 and our journey to one .NET—Scott Hunter, May 2020 · .NET Blog ↩︎


Gildan Raphael
About the Author

Galdin Raphael

Galdin Raphael is an independent software developer. When not writing code, he's probably reading a book, playing the piano or studying music theory. Follow him on 
twitter: @gldraphael

Related Posts

Comments

Comments are disabled in preview mode.