9/16/2019 Admin

Blazor Error Handling OwningComponentBase and EF Core Tracking

The version of the book An Introduction to Building Applications with Blazor had a major change from Preview version 8 to Preview version 9 with the introduction of OwningComponentBase.

Using OwningComponentBase ensures that the service and related services that share its scope are disposed with the component. Otherwise a service will live for the life of user’s the connection to the application, which may be problematic if users stay connected for a long time.

To fully understand why this is important, we can explore what would happen when you don’t use OwningComponentBase. In addition, we will explore this issue in the larger context of handling errors in your Blazor applications and special code you need to implement when using Entity Framework Core.

Removing OwningComponentBase


We start with code from the article Creating A Step-By-Step End-To-End Database Server-Side Blazor Application (you can download the code from the downloads page on this site).


Open the project in Visual Studio 2019 Preview (or higher) and open the FetchData.razor page.

To remove OwningComponentBase, remove the line:

@inherits OwningComponentBase<WeatherForecastService>

And replace with:

@inject WeatherForecastService DataService

Replace all instances of @Service with DataService. For example replace:

forecasts = await @Service.GetForecastAsync(user.Identity.Name);


forecasts = await DataService.GetForecastAsync(user.Identity.Name);

Creating Errors


Select View then SQL Server Object Explorer.


Connect to the database that the project is connected to (see the article: Creating A Step-By-Step End-To-End Database Server-Side Blazor Application for help with this), right-click on the database, and select New Query.


In the query window, enter:

CREATE UNIQUE INDEX AK_WeatherForecast_TemperatureC       ON WeatherForecast (TemperatureC);    GO

…and press the execute button.

This creates a unique index that does not allow duplicates values for the TemperatureC field.


Close the window.


Run the project.


Create an Account, log in, and navigate to the Fetch data page.

Click the Add New Forecast button to create a Forecast.

Now, try to create a Forecast that has the same Celsius value as a previously saved Forecast.

The application will then hang.


We can hit the F12 key and open the web browser debug window and see there is an error.

Enabling Detailed Errors


To see a better error, we can close the web browser and stop the application, then open the Startup.cs file.

Then change the following line:



services.AddServerSideBlazor()                 .AddCircuitOptions(options => { options.DetailedErrors = true; });


When we run the application and enter a duplicate record, we get a much more informative error message.

Display Errors Using Try Catch


Add a new class called DTOUpdateResult.cs using the following code:

namespace EndToEndDB.Data.EndToEnd {     public class DTOUpdateResult     {         public DTOUpdateResult()         {             HasError = false;             ErrorMessage = "";         }         public bool HasError { get; set; }         public string ErrorMessage { get; set; }     } }

This creates a class that will be returned by the Create and Update methods to return any possible errors.


Open WeatherForecastService.cs and change the following methods as indicated (to return the new DTOUpdateResult class):


								public Task<DTOUpdateResult>             CreateForecastAsync(WeatherForecast objWeatherForecast)         {             DTOUpdateResult objDTOUpdateResult = new DTOUpdateResult();             try             {                 _context.WeatherForecast.Add(objWeatherForecast);                 _context.SaveChanges();             }             catch (System.Exception ex)             {                 objDTOUpdateResult.HasError = true;                 objDTOUpdateResult.ErrorMessage = ex.GetBaseException().Message;             }             return Task.FromResult(objDTOUpdateResult);         }


								public Task<DTOUpdateResult>              UpdateForecastAsync(WeatherForecast objWeatherForecast)         {             DTOUpdateResult objDTOUpdateResult = new DTOUpdateResult();             try             {                 var ExistingWeatherForecast =                         _context.WeatherForecast                         .Where(x => x.Id == objWeatherForecast.Id)                         .FirstOrDefault();                 if (ExistingWeatherForecast != null)                 {                     ExistingWeatherForecast.Date =                         objWeatherForecast.Date;                     ExistingWeatherForecast.Summary =                         objWeatherForecast.Summary;                     ExistingWeatherForecast.TemperatureC =                         objWeatherForecast.TemperatureC;                     ExistingWeatherForecast.TemperatureF =                         objWeatherForecast.TemperatureF;                     _context.SaveChanges();                 }                 else                 {                     objDTOUpdateResult.HasError = true;                     objDTOUpdateResult.ErrorMessage = "Record not found";                 }             }             catch (System.Exception ex)             {                 objDTOUpdateResult.HasError = true;                 objDTOUpdateResult.ErrorMessage = ex.GetBaseException().Message;             }             return Task.FromResult(objDTOUpdateResult);         }


Open FetchData.razor.

Change the SaveForecast method to the following (to display any possible errors from the DTOUpdateResult class):

								string ErrorMessage = "";     async Task SaveForecast()     {         DTOUpdateResult objDTOUpdateResult = new DTOUpdateResult();         ErrorMessage = "";         // Close the Popup         ShowPopup = false;         // Get the current user         var user = (await authenticationStateTask).User;         // A new forecast will have the Id set to 0         if (objWeatherForecast.Id == 0)         {             // Create new forecast             WeatherForecast objNewWeatherForecast = new WeatherForecast();             objNewWeatherForecast.Date = System.DateTime.Now;             objNewWeatherForecast.Summary = objWeatherForecast.Summary;             objNewWeatherForecast.TemperatureC =             Convert.ToInt32(objWeatherForecast.TemperatureC);             objNewWeatherForecast.TemperatureF =             Convert.ToInt32(objWeatherForecast.TemperatureF);             objNewWeatherForecast.UserName = user.Identity.Name;             // Save the result             objDTOUpdateResult =             DataService.CreateForecastAsync(objNewWeatherForecast).Result;         }         else         {             // This is an update             objDTOUpdateResult =             DataService.UpdateForecastAsync(objWeatherForecast).Result;         }         if(objDTOUpdateResult.HasError)         {             ErrorMessage = objDTOUpdateResult.ErrorMessage;         }         // Get the forecasts for the current user         forecasts =         await DataService.GetForecastAsync(user.Identity.Name);     }

Finally add this code to the top of the page to display any error messages:



Now when we run the application, and enter a duplicate Celsius value, the error message is displayed and the application does not hang.


However, we can navigate to the counter page…


… navigate back to the Fetch data page and enter a new Forecast


…enter a new Forecast that is not a duplicate…


The update will not save!

The reason is that Entity Framework Core is still stuck on the original error. Navigating to the counter page and back demonstrates that EF Core, contained inside the WeatherForecastService, remains active no matter what page we go to in the application

We will address this situation in two parts:

  1. Implement OwningComponentBase so the WeatherForecastService and EF Core do not remain active when we navigate to a different page in the application
  2. Clear ChangeTracker in EF Core when there are errors

Use OwningComponentBase


Open FetchData.razor and change:

@inject WeatherForecastService DataService


@inherits OwningComponentBase<WeatherForecastService>

Replace all instances of DataService with @Service. For example replace:

forecasts = await DataService.GetForecastAsync(user.Identity.Name);


forecasts = await @Service.GetForecastAsync(user.Identity.Name);


Now we can enter a duplicate value


Navigate to a new page…


… return and add a new Forecast that is not a duplicate…


… and it will allow us to save.


However, if we get an error from a duplicate value, we cannot enter a new value, that is not a duplicate, unless we navigate away from the page and come back.

We need to clear the previous error in EF Core whenever there is an error.

Clear ChangeTracker in EF Tracking


Open WeatherForecastService.cs and add the following using statement to the top of the class:

using Microsoft.EntityFrameworkCore;

Next, add the following method:

								public void DetachAllEntities()         {             var changedEntriesCopy = _context.ChangeTracker.Entries()                 .Where(e => e.State == EntityState.Added ||                             e.State == EntityState.Modified ||                             e.State == EntityState.Deleted)                 .ToList();             foreach (var entry in changedEntriesCopy)                 entry.State = EntityState.Detached;         }

Finally alter the catch block in the Create and Update methods to the following:

								catch (System.Exception ex)             {                 DetachAllEntities();                 objDTOUpdateResult.HasError = true;                 objDTOUpdateResult.ErrorMessage = ex.GetBaseException().Message;             }

Now we can enter a record that has an error, and then immediately enter a valid record, without the need to go to a new page and come back.

What If You Have Multiple Services?

See: An Introduction to OwningComponentBase

@inherits OwningComponentBase     WeatherForecastService _WeatherForecastService;     protected override async Task OnInitializedAsync()     {         // Get the current user         var user = (await authenticationStateTask).User;         _WeatherForecastService = (WeatherForecastService)ScopedServices.GetService(typeof(WeatherForecastService));         forecasts = await _WeatherForecastService.GetForecastAsync(user.Identity.Name);             }


An Introduction to OwningComponentBase

Utility base component classes to manage a DI scope

The Entity Framework Core ChangeTracker

How do I clear tracked entities in entity framework


The project is available on the Downloads page on this site.

You must have Visual Studio 2019 (or higher) installed to run the code.

An error has occurred. This application may no longer respond until reloaded. Reload 🗙