The extensions methods shown here can be applied in general in Entity Framework Core. In Azure Cosmos DB, if you use the FindAsync method for example, you will not load the references of the item automatically.
Instead, you must explicitly go via Entry method and then to LoadAsync on each Reference.
A Reference is also called a navigational property in EF.
Let's first consider this code finding some Trip data item (data is stored in json format in Azure Cosmos DB since it is schema less and non-relational db or a 'document DB') and related data inside the
Driver, Address.
The POCO for Trip looks like this:
Note that we probably want to check that trip1 object is not null here. We want to get the relations also here. This is not automatically loaded in Azure Cosmos DB ! We can load the relations or navigational properties using the following extensions methods listed below. If you use the method accepting an EntityEntry, you must use the Entry method first, like shown in the other method.
publicstaticclassEntityEntryExtensions
{
publicstaticasync Task LoadAllReferences<T>(this EntityEntry<T> entry) where T : class
{
foreach (var reference in entry.References)
{
await reference.LoadAsync();
}
}
}
publicstaticclassEntityExtensions
{
publicstaticasync Task LoadEntityWithAllReferences<T>(this DbContext dbContext, T dataItem) where T : class
{
if (dataItem == null)
{
return;
}
var entity = dbContext.Entry(dataItem!);
foreach (var reference in entity.References)
{
await reference.LoadAsync();
}
}
}
I have added two screenshots here, first before calling the method LoadEntityWithAllReferences, and afterwards.
Before:
After:
As we can see from the screen shots, the references has been loaded and now you avoid to manually have to load one and one reference property / navigation property.
Note - about some of the sample code - it came from a Pluralsight Course. I have looked more into the extension methods here myself.
/*
This demo application accompanies Pluralsight course 'Using EF Core 6 with Azure Cosmos DB',
by Jurgen Kevelaers. See https://pluralsight.pxf.io/efcore6-cosmos.
MIT License
Copyright (c) 2022 Jurgen Kevelaers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
CancellationToken are used to signal that an asynchronous task should cancelled, at some given point in the code from here on a cancellation has been signaled and downstream.
Methods downstream are methods and sub methods / sub routines. We can pass a cancellation token into for example Entity Framework Core to cancel a heavy database I/O process.
Cancelling a method this way can be invoked on many different ways.
Examples of ways to cancel a cancellation tokens
1. By user interface actions. Like hitting a Cancel button in the UI. For example, the REST client Insomnia allows you to do this.
2. Other means of stopping a task. Inside a browser you can stop requests by reloading a window / tab. For example - If you use Swagger API in a browser, you can refresh the Swagger web page in a tab to indicate a cancellation is desired.
The suggested way to programatically cancelling a cancellation token in code is to throw a OperationCanceledException in your code. If you have the Cancellation Token Source - the CTS - you can cancel the cancellation token as you like. Most often you do not have the CTS.
You can always throw an OperationCanceledException.
The source code can then listen to such a cancellation if you call ThrowIfCancellationIsRequested on the cancellation token.
Another way to cancel a cancellation token is to create a linked cancellation token and cancellation the cancellation token source you created for it. This is an alternative way that directly updates the cancellation of a token to be cancelled downstream in case you have some logic further upstream that still should be called instead of directly throwing an OperationCancelledException.
Is it a good approach to programatically create a new cancellation token and overwrite it or should you instead just throw an OperationCancelledException ? And why not just stick to the same object ? I am overwriting the token here using ref keyword, since CancellationToken is a struct object.
This makes it harder to overwite, since structs are copied by value into methods, such as an extension method. However if this is a good idea or not - I include the code here for completeness.
Defining an extension method on cancellation tokens which can cancel them
So there we have it, we can either use an approach like in this article, creating a temporary new cancellation token source and then created a linked cancellation token from the original cancellation token, overwriting it, and at the same time cancel it, possible by supplying a condition to decide
if we want to cancel the cancellation token or not. Or we could just throw an OperationCanceledException.
In the example code above I finally make the EF code communicating with the database and supply the cancelation token into a ToListAsync method here. This makes our code cancellable, in case we for example hit big data in the database that is slow and user wants to cancel.
Using an extension method that is cancellation cancellation tokens - downstream code making use of same cancellation token passed into sub method
First off, we need to add some Nuget package references, such as adding a capability of using local storage in a convenient way in the Blazor WASM app. The project file of the sample app has this setup :
Note the use of the property setting :
BlazorWebAssemblyLoadAllGlobalizationData
This is required to add localization to your Blazor WASM app ! Also note that we use Blazored.LocalStorage to write and access local storage.
Let's look at the Program.cs file next how we set up the app.
Program.cs
using Blazored.LocalStorage;
using HelloBlazorLocalization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
string[] supportedCultures = new[] { "no", "en" };
options
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures)
.SetDefaultCulture("no");
});
builder.Services.AddLocalization(options =>
options.ResourcesPath = "Resources");
builder.Services.AddBlazoredLocalStorage();
await builder.Services.BuildServiceProvider().SetDefaultCultureAsync();
await builder.Build().RunAsync();
An extension method is added to ServiceProvider to load up selected culture from local storage. It also inspects the query string set, if any, since language picker component presented later on will reload the Blazor WASM app after selecting language.
WebAssemblyHostExtensions.cs
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.WebUtilities;
using System.Globalization;
namespaceHelloBlazorLocalization
{
publicstaticclassWebAssemblyHostExtensions
{
publicasyncstatic Task SetDefaultCultureAsync(this ServiceProvider serviceProvider)
{
var navigationManager = serviceProvider.GetService<NavigationManager>();
var uri = navigationManager!.ToAbsoluteUri(navigationManager.Uri);
var queryStrings = QueryHelpers.ParseQuery(uri.Query);
var localStorage = serviceProvider.GetRequiredService<ILocalStorageService>();
if (queryStrings.TryGetValue("culture", outvar selectedCulture))
{
await localStorage.SetItemAsStringAsync("culture", selectedCulture);
}
var cultureString = await localStorage.GetItemAsync<string>("culture");
CultureInfo cultureInfo;
if (!string.IsNullOrWhiteSpace(cultureString))
{
cultureInfo = new CultureInfo(cultureString);
}
else
{
cultureInfo = new CultureInfo("en-US");
}
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
}
}
}
Now, let's look at the Index.razor file where we repeat some of the code in the extension method shown above.
Index.razor
@page "/"
@using System.Globalization;
@inject NavigationManager NavigationManager
@inject Blazored.LocalStorage.ILocalStorageService LocalStorage
@inject IStringLocalizer<SharedResources> Localizer
<PageTitle>@Localizer["Home"]</PageTitle>
<h1>@Localizer["Home"]</h1>
@Localizer["HomeDescription"]
<SurveyPrompt Title="How is Blazor working for you?" />
@code {
protectedoverrideasync Task OnParametersSetAsync()
{
var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
var queryStrings = QueryHelpers.ParseQuery(uri.Query);
if (queryStrings.TryGetValue("culture", outvar selectedCulture))
{
await LocalStorage.SetItemAsStringAsync("culture", selectedCulture);
}
else
{
selectedCulture = await LocalStorage.GetItemAsStringAsync("culture");
}
if (!string.IsNullOrWhiteSpace(selectedCulture))
{
var cultureInfo = new CultureInfo(selectedCulture);
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
}
}
}
To localize strings, we first do an inject of the IStringLocalizer as shown in the razor file. We also set the resource key when we fetch the localized text (Value). This is set up in the SharedResource files.
This is done in the sample app in three files.
An empty class called SharedResources at the root level
Two resources files (.resx) called SharedResources.en.resx and SharedResources.no.resx
You can have multiple resource file in Blazor WASM. Note that we in Program.cs set up the ResourcesPath to the sub folder Resources, where we put the .resx files. See the sample app for details (clone the Github repo).
Next up, let's look at the LanguagePicker.razor file that will show a language picker. The sample app got flag icons for all flags of countries so check out the folder flag-icons under wwwroot folder in the sample app.
Note that Blazor WASM app should refresh entirely after choosing another language. Also note that you should set up multiple languages in your browser to get the expected results.
You should have the supported languages set up in Blazor WASM, however it might still work to get the localization done if the language settings are not set up to include the specified languages. But if you do not see the expected results, check the language settings in your browser.
And as can be seen, we use local storage to persist our selected language. The selected language is displayed with the green button to indicate selected. When the Blazor WASM reloads, the selected language is fetched from local storage. This can be seen in Application => Local Storage in F12 Developer Tools in Chrome for example, when running the app.
Blazor WASM supports a reduced set of localization functionality, compared to Blazor server side apps.
A limited set of ASP.NET Core's localization features are supported:
✔️Supported: IStringLocalizer and IStringLocalizer are supported in Blazor apps.
❌Not supported: IHtmlLocalizer, IViewLocalizer, and Data Annotations localization are ASP.NET Core MVC features and not supported in Blazor apps.