Development Frameworks Made Easy with Blazor + Azure Functions

In the early days of web development (insert that lovely sound of dial-up internet that we all definitely do not miss here), most if not all of the intelligence in web apps was on the server side. The application logic ran on the server, and the client simply displayed the HTML pages the server delivered to it. But with the rise of the Single Page Application (SPA) approach, the focus has shifted from the server doing all the processing to most of it being done in the client’s browser.

JavaScript has long been the dominant programming language for browser-based SPA development. But that’s all beginning to change. 

Recently there has been a growing interest in using C# for web development. The latest version of ASP.NET Core reflects this, which uses the same C# code across all platforms. This new prominence of C# in web development is driving the emergence of new front-end frameworks that will allow developers to build and store their entire technology stack all within the .Net ecosystem. Pretty cool, right? 

One such framework that has most recently gotten a lot of buzz is Microsoft’s own Blazor. 

The following takes a deeper look into what Blazor is and how combining it with Azure Functions allows you to develop your front-end and back-end all in C#. 

Let’s dive right in, shall we?

What Is Blazor?

Blazor is the .Net equivalent of many of the Javascript frameworks such as React or Angular. With Blazor, you create your front-end application using HTML and CSS with your C# code all in one place. We already love where this is going. But there’s more! Blazor allows you to directly connect your HTML element events to C# methods and easily manage the state between parent and child components. Best of all, you can use a single development environment such as Visual Studio to build, debug, and test all in one place. And for those applications that require direct Javascript integration, Blazor supports JSInterop to allow callbacks between your C# code and your Javascript code. 

Okay, Blazor, we see you. We see you. 

Blazor Plus Azure Functions

Now that we know a little more about what Blazor is, let’s talk about what can happen when you combine it with Azure Functions. Blazor, being a front-end framework, can use any preferred back-end technology stack. However, when you combine Blazor with Azure Functions you have the added benefit of working with the same coding environment for both. 

A huge advantage can be found in your data modeling. You’ll be able to keep all your data models in a shared folder. Unlike React, your Blazor and Azure Functions models will always automatically be in sync, which is just another reason to say “Bye, Bye, Bye” to your other frameworks. 

So, What Does It Look Like?

Here’s a real-world example to better illustrate what the Blazor and Azure Functions model looks like. Below is a code snippet of a typical Blazor page. Note that Blazor pages have a .razor extension. Just as you include other packages in any .Net application, you can see the @using and @inject statements at the top of the page. Blazor apps also support dependency injection like other .Net applications.

After you set up your usings and injections, write your HTML layout. Below that is where your C# code is written within an @code section. In this particular example, we are using Radzen components. This is included by using NuGet to install the package.

@page "/login"

@using Api.Shared.Helpers

@using Api.Shared.Models

@using Radzen

@using Radzen.Blazor

@layout NoLayout

@inject HttpClient Http

@inject NavigationManager NavManager

@inject Blazored.SessionStorage.ISessionStorageService SessionStorage

@inject Blazored.LocalStorage.ILocalStorageService LocalStorage

@inject DialogService DialogService

@inject SpinnerService SpinnerService

<Spinner />

<style>

    .sk-rect {

      background-color: white !important;

    }

</style>

<div class="container vh-100 d-flex align-items-center">

  <div class="row w-100">

    <div class="col-8 d-flex flex-column justify-content-center">

      <h4><img src="/assets/eTrax.png" /></h4>

    </div>

    <div class="col-4 card p-5 login-card">

      <EditForm id="loginForm" Model="@login" OnValidSubmit="@HandleSubmit">

        <h2 class="mb-4">Sign In</h2>

        <label for="email">Email Address</label>

        <div class="input-group">

          <InputText id="email" class="w-100" @bind-Value="login.Email" />

        </div>

        <label for="password">Password</label>

        <div class="input-group">

          <InputText id="password" @onblur="onPassword" class="w-100" type="password" @bind-Value="login.Password" />

        </div>

        <label for="hospital">Hospital</label>

        <div class="input-group">

            <RadzenDropDown id="hospital" Placeholder=@hospitalsPLaceHolder TValue="int" Data=@hospitals @bind-value="login.HospitalID" TextProperty="HospitalName" ValueProperty="HospitalId" />

        </div>

        <div>

          <a href="/forgot-password" class="btn btn-link my-2 pl-0">

            Forgot password?

          </a>

        </div>

        <div>

            <RadzenButton ButtonType="ButtonType.Submit" Icon="login" ButtonStyle="ButtonStyle.Primary" Text="Sign In" />

        </div>

      </EditForm>

    </div>

  </div>

</div>

@code {

    private Api.Shared.Models.Login login = new Api.Shared.Models.Login();

    private HospitalList[] hospitals = new HospitalList[] { new HospitalList() { HospitalId = -1, HospitalName = "Username/Password Required"} };

    private string hospitalsPlaceHolder = "Please select a Hospital";

    private void InvalidLogin() =>

        DialogService.Open("Login Failed", ds =>

        @<div>

            <div class="row">

                <div class="col-md-12">

                    <RadzenLabel Text="Invalid credentials, please try again..." /><br/>

                    <RadzenButton Icon="check" ButtonStyle="ButtonStyle.Secondary" Text="Ok" Click="() => ds.Close(true)" />

                </div>

            </div>

    </div>

    , new DialogOptions() { ShowClose = false, Style = "min-height:auto;min-width:auto;width:auto" });

    private async Task HandleSubmit()

    {

        SpinnerService.Show();

        using var msg = await Http.PostAsJsonAsync<Api.Shared.Models.Login>("authentication/login", login, System.Threading.CancellationToken.None);

        SpinnerService.Hide();

        if (msg.IsSuccessStatusCode)

        {

            JWT result = await msg.Content.ReadFromJsonAsync<JWT>();

            if (!string.IsNullOrEmpty(result.Token))

            {

                await SessionStorage.SetItemAsStringAsync("email", result.Email);

                await SessionStorage.SetItemAsStringAsync("jwt", result.Token);

                await SessionStorage.SetItemAsStringAsync("name", result.Name);

                await SessionStorage.SetItemAsStringAsync("ForcePWChange", result.ForcePWChange.ToString());

                await SessionStorage.SetItemAsStringAsync("role", result.Role.Description());

                NavManager.NavigateTo("/");

            }

        }

        else

        {

            InvalidLogin();

        }

    }

}

As you can see, if you know C# and HTML/CSS you can quickly jump in and start writing your code. There is a minimal learning curve. To set up any dependency injection or environment-specific settings, you simply add that code to your Program.cs module (see the snippet below).

  public class Program

    {

        public static async Task Main(string[] args)

        {

#if DEBUG

            // Delay so debugger can attach

            await Task.Delay(3000);

#endif

            var builder = WebAssemblyHostBuilder.CreateDefault(args);

            builder.RootComponents.Add<App>("#app");

            builder.Services

                .AddScoped<IHttpService, HttpService>()

                .AddBlazoredSessionStorage()

                .AddBlazoredLocalStorage()

                .AddScoped<DialogService>()

                .AddScoped<NotificationService>()

                .AddScoped<TooltipService>()

                .AddScoped<SpinnerService>();

            string baseAddress = builder.Configuration.GetValue<string>("BaseUrl");

            if (string.IsNullOrEmpty(baseAddress))

                baseAddress = builder.Configuration.GetValue<string>("ProductionBaseUrl");

            builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(baseAddress) });

            await builder.Build().RunAsync();

        }

    }

Managing State

All web applications have some type of state that needs to be managed. In Blazor, this is handled by using the [CascadingParameter]. You wrap your child components with this, and any child can directly access the [CascadingParameter]. Blazor will then determine what needs to be re-rendered based on that state change. A key benefit to this is that you do not have to keep your state global to the whole application. You can keep your state at any level you want in your components, making it much easier to manage and debug. Be aware that in some instances, the state change may not be detected. If you hit this, add a StateHasChanged() call to force a re-render of your component. See the snippet below.

Parent Component:

<CascadingValue Value="this">

    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">

        <Found Context="routeData">

            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />

        </Found>

        <NotFound>

            <LayoutView Layout="@typeof(MainLayout)">

                <p>Sorry, there's nothing at this address.</p>

            </LayoutView>

        </NotFound>

    </Router>

</CascadingValue>

Child Component:

@code {

    [Parameter]

    public Employees Employee {get; set;}

    [Parameter]

    public bool isAdding { get; set; }

    [CascadingParameter]

    MainLayout main { get; set; }

With this code, the child component can now access all the public properties and methods of the parent component. There is no need for any third-party libraries to handle your state.

Azure Functions

If you are familiar with .Net web API and controllers, an Azure Function is designed in a similar way. A controller's methods are just individual Azure Functions. There are a set of attributes you need to add to the methods. When coding, you just use your familiar design patterns. You can also have as many functions as you want in your application. There is no requirement that you separate all your API methods. See the snippet below for a small example. Notice the additional attributes used for automatically generating Swagger documentation.

        [FunctionName("Login")]

        [OpenApiOperation(operationId: "Login", tags: new[] { "login" })]

        [OpenApiRequestBody(contentType: "application/json", bodyType: typeof(Login),

                            Required = true, Description = "Login")]

        [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(JWT), Description = "The OK response")]

        public async Task<IActionResult> Login(

            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "authentication/login")] HttpRequest req,

            ILogger log)

        {

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

Blazor Client Vs. Blazor Server

You may have read some articles about Blazor and noticed that there are two ways to implement a Blazor application. Based on your application's requirements, you will decide on which is best. The major difference between the two is that Blazor Client runs all your code on the client itself. In modern browsers, Blazor Client uses Web Assembly to run the code. This puts more of a load on the client and less on the server. However, it is very fast and efficient, and most modern devices can easily support this. One disadvantage is that the initial load may take a few seconds if you have a very large application. 

In most cases, you will never see a performance difference. Again, based on your application requirements, you may prefer to use Blazor Server instead. Blazor Server uses SignalR to send changes down to the client for rendering the page. All the code is run on the server, putting more of a load on the server. You also must have a connection to the server, so no offline application can run in Blazor Server. Also, it’s important to note that if you're running things on the server, the application can’t access client devices such as microphones and cameras directly. You will have to write Javascript code and use JSInterop.

Wrapping Things Up

Most industry experts agree that in terms of their capabilities, none of the major front-end .NET development frameworks stands head-and-shoulders above the rest as the best option for all situations. Your choice will ultimately come down to which one best suits your particular development environment and project goals. 

After reading this article, one option we encourage you to explore further is Blazor with Azure Functions. We highlighted the benefits of using this model and shared an example of what this might look like in real life. We hope that this information was valuable and insightful. If you are interested in adopting this framework, I highly suggest reading the Blazor and Azure Functions tutorials online to get even more detailed information on how to use both of these technologies. 

How Can Inventive Help You?

No matter which front-end framework you choose, you’ll need developers with the appropriate skills and experience to use it most effectively. That’s where Inventive can help.

We have successfully created applications for many clients using Blazor and would love to assist you in determining the best set of technologies for your requirements. Whether you are creating an application from scratch or modernizing an existing one, we are here to help! Contact us today, and let’s create something amazing together!