Category Archives: Development

gRPC code-first for Blazor WebAssembly front-end

gRPC is a phenomenon of our time. This modern and performance-efficient protocol is rapidly spreading, and today we will show how to use it for communication between the Blazor WebAssembly front-end and the ASP.NET Core backend (host):

  1. We will efficiently use the possibilities of sharing code between the server and client part. We will use the code-first arrangement and put the “contract” (interface for the called service and data object definitions) into the assembly shared by both the server and client parts of the solution.
  2. To overcome browser limitations, we will use the gRPC-Web extension.

We will show the entire implementation on a simple example – we will use the default Blazor WebAssembly App template from Visual Studio (ASP.NET Core hosted, version of the .NET7 template) and we will convert the prepared Fetch data example, which uses the REST API in this template, to a gRPC-Web call using code-first.

Let’s do this, it’s just a few steps:

1. MyBlazorSolution.Server – Preparing ASP.NET Core host

First, we prepare the server-side infrastructure for gRPC. We will go directly to the version with the gRPC-Web extension with code-first support and install NuGet packages

We register support services in dependency-injection in Startup.cs:

builder.Services.AddCodeFirstGrpc(config => { config.ResponseCompressionLevel = System.IO.Compression.CompressionLevel.Optimal; });

We add gRPC middleware somewhere between UseRouting() and endpoint definition (before MapXy() methods):

app.UseGrpcWeb(new GrpcWebOptions() { DefaultEnabled = true });

2. MyBlazorSolution.Shared – Service contract definition (code-first)

Now we define in the form of an interface what our service will look like. We will then use the interface on the server side (we will create its implementation) and on the client side (we will generate a gRPC client that will implement the interface and we will directly use it in our code via dependency injection).
Add to the project a NuGet package that allows us to decorate the interface with necessary attributes

Find the example WeatherForecast.cs file from the project template. It contains the definition of the return data message, which the sample REST API now returns to us. We will convert this class into the following form:

[DataContract]
public class WeatherForecast
{
    [DataMember(Order = 1)]
    public DateTime Date { get; set; }

    [DataMember(Order = 2)]
    public int TemperatureC { get; set; }

    [DataMember(Order = 3)]
    public string? Summary { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
  • We added the [DataContract] attribute to mark the class we will use as the gRPC data message.
  • We added [DataMember(Order = ...)] attributes that mark the elements to be transmitted via gRPC (others are ignored, here TemperatureF is calculated and recalculated from other data on the client anytime). Each element needs to be set Order, which defines the fixed layout for the used protobuf serialization.
  • We replaced the original DateOnly type with DateTime. We have to stick to types supported by used protobuf serialization.

Next, we need to create an interface that will describe the whole service:

[ServiceContract]
public interface IWeatherForecastFacade
{
    Task<List<WeatherForecast>> GetForecastAsync(CancellationToken cancellationToken = default);
}
  • The [ServiceContract] attribute tells us the applicability for gRPC (can be used later for automatic registrations).
  • By the nature of network communication, the entire interface should be asynchronous.
  • We can use the optional CancellationToken, which can convey a signal of premature termination of communication by the client (or disconnection).

3. MyBlazorSolution.Server – Implementing the gRPC service

Now we need to implement the prepared interface on the server side (we will use slightly modified code from the sample WeatherForecastController, which you can now delete):

public class WeatherForecastFacade : IWeatherForecastFacade
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public Task<List<WeatherForecast>> GetForecastAsync(CancellationToken cancellationToken = default)
    {
        return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Today.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .ToList());
    }
}

Now we have to add the gRPC service in Startup.cs:

app.MapGrpcService<WeatherForecastFacade>();

4. MyBlazorSolution.Client – gRPC client in Blazor WebAssembly

Now all that is left is to use the service in the Blazor WebAssembly front-end. The entire definition is available in the form of the IWeatherForecastFacade interface with its WeatherForecast data class.

We will add the necessary NuGet packages to the project:

Register the gRPC-Web infrastructure and the client (in factory form) in Program.cs:

builder.Services.AddTransient<GrpcWebHandler>(provider => new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));

builder.Services.AddCodeFirstGrpcClient<IWeatherForecastFacade>((provider, options) => =>
    {
        var navigationManager = provider.GetRequiredService<NavigationManager>();
        var backendUrl = navigationManager.BaseUri;

        options.Address = new Uri(backendUrl);
    })
    .ConfigurePrimaryHttpMessageHandler<GrpcWebHandler>();

Well, now we can use IWeatherForecastFacade anywhere in the front-end project by having the service injected using dependency injection. So, for example, we’ll modify FetchData.razor to use our new gRPC service instead of the original REST API:

@inject IWeatherForecastFacade WeatherForecastFacade

...

@code {
    private List<WeatherForecast>? forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await WeatherForecastFacade.GetForecastAsync();
    }
}

Done. The project should now be executable and the Fetch data page will now communicate via gRPC-Web.

You can check your solution against the sample repository

Other extensions and more advanced techniques

The gRPC service can, of course, accept input. In this case, use one input parameter for the incoming message – a data class created in the same way we prepared the WeatherForecast output. (Usually these classes are referred to as Data Transfer Object and thus given the suffix Dto. The implementation is usually as a C# record.)

If we have authentication and authorization in our project, then we can use the [Authorize] attribute on the implementing class/method, just as we would on a controller/action.

We can apply arbitrary techniques to the published gRPC endpoint like any other mapped server endpoint (rate limiting, caching, …).

gRPC has support for interceptors which can be used to further improve gRPC communication

  • pass exceptions from server to client (basic support is built-in, but you may want to enrich it with specific handling of custom scenarios),
  • passing the required culture from client to server (what language the front-end is switched to),

In a more advanced variant of the layout, you can also provide automatic registration of interface and data contracts without having to decorate them with [ServiceContract], [DataContract] and [DataMember(Order = ...)] attributes. All this and much more can be found ready in:

Both open-source with MIT license, free of charge.

Blazor WASM: Unsupported connection to AAD using the standard auth-library (Microsoft.AspNetCore.Components.WebAssembly.Authentication)

If you (like me) were hoping to “easily” create a Blazor application that connects to a generic OIDC identity-provider using the Microsoft.AspNetCore.Components.WebAssembly.Authentication library, while also supporting connections to Azure Active Directory (AAD) this way, you’ll be disappointed.

The idea is simple, AAD does support OIDC and therefore now why shouldn’t a generic OIDC client be able to connect to AAD as well. Unfortunately the hurdles are more than you would like to overcome.

While the Microsoft.Authentication.WebAssembly.Msal library for AAD is an extension of the underlying Microsoft.AspNetCore.Components.WebAssembly.Authentication library, it does a lot more than just boilerplate configuration of the underlying generic OIDC.

The basic difference can be found, for example, in the “interop” part with the underlying oidc-client JavaScript module.

  • AuthenticationService.ts in Microsoft.AspNetCore.Components.WebAssembly.Authentication,
  • AuthenticationService.ts in Microsoft.Authentication.WebAssembly.Msal.
    This is manifested for example when retrieving access-tokens. The MSAL version “fixes” the AAD specificity that when querying a token endpoint does not always return an access-token with all required scopes (the details are for a broader discussion, but e.g. you don’t get a token that has User.Read scope in it along with the custom-scope of your API, etc.).

The base library uses the “caching” of tokens in the oidc-client UserManager and relies on the assumption “If token-endpoint returned an access-token, then that token has all the required scopes in it.” (i.e., it stores the required scopes for the retrieved token, and then returns the already retrieved token the next time the same scopes are requested).

The “fixed” MSAL library knows this shortcoming of the underlying oidc-client and knows that while it claims to have a token for some set of scopes, the access-token may not actually have those scopes. Therefore, it “turns off” caching at this level and always gets the token again.

Announcing HAVIT Blazor 1.4.3 – Free Bootstrap 5 components for ASP.NET Blazor

Didn’t have a chance to announce our component bundle on this KnowledgeBase yet…

Now we released version 1.4.3. What’s new?

  • HxInputDateHxInputDateRange and HxCalendar have new MinDate and MaxDate parameters to allow customization of selectable dates in calendars (also added to Defaults to be able to customize the application-wide defaults)
  • HxInputDate and HxInputDateRange have new CalendarDateCustomizationProvider which allows further customization (enabled/disabled, CssClass) of individual dates in dropdown calendars
  • ⚠️ HxCalendar parameters MinYear and MaxYear replaced with the new MinDate and MaxDate parameters
  • HxCalendar has new simplified month/year navigation
  • HxInputDate fixed validation icon position if CalendarIcon is used
  • HxInputFileCore has new HxInputFileCore.Defaults.MaxFileSize application-wide default to be able to limit maximum file size,
  • HxInputFile has new HxInputFile.Defaults.InputSize application-wide default to be able to set form component size
  • HxSidebar fixed ability to scroll on mobile if viewport is overlapped
  • HxSidebar has new CSS variable for customising background-colour
  • Updated to Bootstrap 5.1.3 and Bootstrap Icons 1.6

What is HAVIT Blazor?

HAVIT Blazor is a free open-source (MIT) component bundle build on top of Bootstrap 5. It not only covers all Bootstrap 5 components but also brings a few enterprise-level ones (e.g. HxGrid, HxInputDate, HxAutosuggest, HxInputTags) and special ones (e.g. HxGoogleTagManager).

Interactive documentation & demos – https://havit.blazor.eu

Source code – https://github.com/havit/Havit.Blazor

Forms

Buttons & Indicators

Data & Grid

Layout & Typography

Navigation

Modals & Interactions

Special

Enterprise-application template [OPTIONAL]

Beside the components there is a ready to run enterprise level application template which includes gRPC code-first communication, full layered architecture stack (Model, DataLayer, Services, …) and much more.

Blazor WASM error: Could not load settings from ‘_configuration/BlazorClient’

Make sure your startup project is set to Web.Server (ASP.NET Host).

This kind of error appears when you try to start the Web.Client directly.

blazor.webassembly.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Could not load settings from '_configuration/Havit.Bety2.Web.Client'
      Error: Could not load settings from '_configuration/Havit.Bety2.Web.Client'
          at Function.createUserManager (http://localhost:59799/_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js:1:5893)
          at async Function.initializeCore (http://localhost:59799/_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js:1:5027)
Microsoft.JSInterop.JSException: Could not load settings from '_configuration/Havit.Bety2.Web.Client'
Error: Could not load settings from '_configuration/Havit.Bety2.Web.Client'

HxInputFile – Blazor InputFile extension with direct (native) upload and progress indication

Starting with .NET 5 there is a InputFile component in Blazor which allows you to access content of the corresponding file(s) in form of a Stream (IBrowserFile.OpenReadStream).

If you run Blazor on server, the InputFile implementation sends the file content from browser to server through a SignalR connection almost at native speed (approx. 30% slower). In case of Blazor WebAssembly, you have to transfer the file to server on your own.

Due to unmarshalled interop Blazor is able to copy the file to WASM memory extremely quickly (approx. 100MB/sec). You can process the file on client-side (resize image etc.), but if you just want to upload the file, the cost of WASM is relatively high (in comparison to direct HTTP upload) and not suitable for larger files:

Direct (native) upload

To get over the limits of InputFile component in WASM, we can upload the files to server directly from JavaScript using XMLHttpRequest:

var data = new FormData();
data.append('file', file, file.name);

var request = new XMLHttpRequest();
request.open('POST', uploadEndpointUrl, true);
request.send(data);

(Unlike XMLHttpRequest the new fetch() API does not support progress indication.)

Progress indicator

XMLHttpRequest gives you nice progress indication by using the onprogress event:

request.upload.onprogress = function (e) {
    // e.loaded - bytes already uploaded
    // e.total - total upload size (slightly bigger than the file size)
};

HxInputFile / HxInputFileCore

Let’s put this all together and create a new component. We will call it HxInputFileCore (+ the HxInputFile which is a ready-made Bootstrap derivative).

The most important portions follow:

public partial class HxInputFileCore : InputFile, IAsyncDisposable
{
	[Parameter] public string UploadUrl { get; set; }
	[Parameter] public EventCallback<UploadProgressEventArgs> OnProgress { get; set; }
	[Parameter] public EventCallback<FileUploadedEventArgs> OnFileUploaded { get; set; }
	[Parameter] public EventCallback<UploadCompletedEventArgs> OnUploadCompleted { get; set; }
	[Parameter] public bool Multiple { get; set; }
	[Parameter] public string Id { get; set; } = "hx" + Guid.NewGuid().ToString("N");

	[Inject] protected IJSRuntime JSRuntime { get; set; }

	private DotNetObjectReference<HxInputFileCore> dotnetObjectReference;
	private IJSObjectReference jsModule;

	public HxInputFileCore()
	{
		dotnetObjectReference = DotNetObjectReference.Create(this);
	}

	protected override void OnParametersSet()
	{
		base.OnParametersSet();

		// TODO Temporary hack as base implementation of InputFile does not expose ElementReference (vNext: https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web/src/Forms/InputFile.cs)
		AdditionalAttributes ??= new Dictionary<string, object>();
		AdditionalAttributes["id"] = this.Id;
		AdditionalAttributes["multiple"] = this.Multiple;
	}

	public async Task StartUploadAsync(string accessToken = null)
	{
		jsModule ??= await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./_content/Havit.Blazor.Components.Web/hxinputfilecore.js");
		await jsModule.InvokeVoidAsync("upload", Id, dotnetObjectReference, this.UploadUrl, accessToken);
	}

	[JSInvokable("HxInputFileCore_HandleUploadProgress")]
	public async Task HandleUploadProgress(int fileIndex, string fileName, long loaded, long total)
	{
		var uploadProgress = new UploadProgressEventArgs() { /*...*/	};
		await OnProgress.InvokeAsync(uploadProgress);
	}

	[JSInvokable("HxInputFileCore_HandleFileUploaded")]
	public async Task HandleFileUploaded(int fileIndex, string fileName, long fileSize, string fileType, long fileLastModified, int responseStatus, string responseText)
	{
		var fileUploaded = new FileUploadedEventArgs() { /* ... */ };
		await OnFileUploaded.InvokeAsync(fileUploaded);
	}

	[JSInvokable("HxInputFileCore_HandleUploadCompleted")]
	public async Task HandleUploadCompleted(int fileCount, long totalSize)
	{
		var uploadCompleted = new UploadCompletedEventArgs() { /* ... */
		};
		await OnUploadCompleted.InvokeAsync(uploadCompleted);
	}

	public async ValueTask DisposeAsync()
	{
		// ...
	}
}

…and the supportive JavaScript is:

export function upload(inputElementId, hxInputFileDotnetObjectReference, uploadEndpointUrl, accessToken) {
	var inputElement = document.getElementById(inputElementId);
	var dotnetReference = hxInputFileDotnetObjectReference;
	var files = inputElement.files;
	var totalSize = 0;
	var uploadedCounter = 0;

	for (var i = 0; i < files.length; i++) {
		(function (curr) {
			var index = curr;
			var file = files[curr];
			totalSize = totalSize + file.size;

			var data = new FormData();
			data.append('file', file, file.name);

			var request = new XMLHttpRequest();
			request.open('POST', uploadEndpointUrl, true);

			if (accessToken) {
				request.setRequestHeader('Authorization', 'Bearer ' + accessToken);
			}

			request.upload.onprogress = function (e) {
				dotnetReference.invokeMethodAsync('HxInputFileCore_HandleUploadProgress', index, file.name, e.loaded, e.total);
			};
			request.onreadystatechange = function () {
				if (request.readyState === 4) {
					dotnetReference.invokeMethodAsync('HxInputFileCore_HandleFileUploaded', index, file.name, file.size, file.type, file.lastModified, request.status, request.responseText);
				};

				uploadedCounter++;
				if (uploadedCounter === files.length) {
					dotnetReference.invokeMethodAsync('HxInputFileCore_HandleUploadCompleted', files.length, totalSize);
				}
			}

			request.send(data);
		}(i));
	}
}

The component is part of open-sourced library HAVIT Blazor published on GitHub.

Usage

<HxInputFile @ref="hxInputFileComponent" Label="HxInputFile" UploadUrl="/file-upload-streamed/" OnProgress="HandleProgress" OnFileUploaded="HandleFileUploaded" OnUploadCompleted="HandleUploadCompleted" Multiple="true" />

<HxButton Text="Upload" OnClick="HandleUploadClick" />

@code
{
	private HxInputFile hxInputFileComponent;

	private async Task HandleUploadClick()
	{
		files.Clear();

		string accessToken = null;
		
		var accessTokenResult = await ... // use IAccessTokenProvider
		await hxInputFileComponent.StartUploadAsync(accessToken);
	}

	private Task HandleProgress(UploadProgressEventArgs progress)
	{
		// indicate progress here
	}

	private Task HandleFileUploaded(FileUploadedEventArgs fileUploaded)
	{
		// individual file uploaded
	}

	private Task HandleUploadCompleted(UploadCompletedEventArgs uploadCompleted)
	{
		// all files uploaded
	}
}

TODOs

The presented component if not feature complete. There is some more work to do:

  • Maximum file size limit
  • Limit number of files being uploaded in parallel
  • Better error-handling
  • …?

Links

See also

SQL LocalDB: Upgrade to 2019 (15.0.2000)

For me it was quite confusing to find the 2019 version of LocalDB and it is not a streamline process to upgrade your local default instance.

The easiest way to upgrade your LocalDB instance to 2019 is:

  1. Download the LocalDB 2019 installer by using the SQL Server Express installer.
    https://go.microsoft.com/fwlink/?linkid=866658
    1. Run the installer and select “Download Media”.
    2. Select “LocalDB” + click Download.
  2. Before running the SqlLocalDB.msi installer, delete your current MSSQLLocalDB instance:
sqllocaldb stop MSSQLLocalDB
sqllocaldb delete MSSQLLocalDB
  1. Run the new SqlLocalDB.msi (2019) installer. It will create a new MSSQLLocalDB instance.
  2. RESTART YOUR PC! (Otherwise MS SQL Management Studio will still tell you you have an old version running, etc. etc.)
  3. Now you can re-attach your original databases one by one using SQL Server Management Studio (RClick + Attach…)
  4. Done.

Blazor WebAssembly with gRPC-Web code-first approach

Do you like WCF-like approach and need to cover communication in between ASP.NET Core service and Blazor WebAssembly client? Use code-first with gRPC-Web! You can try the it right now by following a few simple steps (GitHub repo):

1. Blazor.Server – Prepare the ASP.NET Core host

Add NuGet packages:

  1. Grpc.AspNetCore.Web (prerelease)
  2. protobuf-net.Grpc.AspNetCore

Register CodeFirstGrpc() and GrpcWeb() services in Startup.cs ConfigureServices() method:

services.AddCodeFirstGrpc(config => { config.ResponseCompressionLevel = System.IO.Compression.CompressionLevel.Optimal; });

Add GrpcWeb middleware in between UseRouting() and UseEndpoints():

app.UseGrpcWeb(new GrpcWebOptions() { DefaultEnabled = true });

2. Blazor.Shared – Define the service contract (code-first)

Add System.ServiceModel.Primitives NuGet package.

Define the interface of your service:

[ServiceContract]
public interface IMyService
{
	Task DoSomething(MyServiceRequest request);

}

[DataContract]
public class MyServiceResult
{
	[DataMember(Order = 1)]
	public string NewText { get; set; }

	[DataMember(Order = 2)]
	public int NewValue { get; set; }
}

[DataContract]
public class MyServiceRequest
{
	[DataMember(Order = 1)]
	public string Text { get; set; }

	[DataMember(Order = 2)]
	public int Value { get; set; }
}

3. Blazor.Server – Implement and publish the service

Implement your service:

public class MyService : IMyService
{
	public Task DoSomething(MyServiceRequest request)
	{
		return Task.FromResult(new MyServiceResult()
		{
			NewText = request.Text + " from server",
			NewValue = request.Value + 1
		});
	}
}

Publish the service in Startup.cs:

app.UseEndpoints(endpoints =>
{
	endpoints.MapGrpcService();
	// ...
}

4. Blazor.Client (Blazor Web Assembly) – consume the service

Add NuGet packages:

  1. Grpc.Net.Client
  2. Grpc.Net.Client.Web (prerelease)
  3. protobuf-net.Grpc

Consume the service in your razor file:

var handler = new Grpc.Net.Client.Web.GrpcWebHandler(Grpc.Net.Client.Web.GrpcWebMode.GrpcWeb, new HttpClientHandler());
using (var channel = Grpc.Net.Client.GrpcChannel.ForAddress("https://localhost:44383/", new Grpc.Net.Client.GrpcChannelOptions() { HttpClient = new HttpClient(handler) }))
{
	var testFacade = channel.CreateGrpcService();
	this.result = await testFacade.DoSomething(request);
}

(You can move the plumbing to ConfigureServices() and use pure dependency injection in your razor files.)

References

  1. Steve Sanderson: Using gRPC-Web with Blazor WebAssembly
  2. Use gRPC in browser apps | Microsoft Docs
  3. protobuf-net.Grpc – Getting Started

Blazor Component Lifecycle Diagram

Blazor Component Lifecycle Diagram

Get familiar with basic Blazor (Razor) Component Lifestyle Methods:

  • SetParametersAsync
  • OnInitialized, OnInitializedAsync
  • OnParametersSet, OnParametersSetAsync
  • event handlers, event callbacks
  • BuildRenderTree
  • OnAfterPreRender, OnAfterPreRenderAsync
  • IDisposable.Dispose