Blazor Server App crashes if DBContext is used

Jim G 0 Reputation points
2023-03-31T17:31:54.6466667+00:00

I have a Blazor Server App that runs fine in the VS Web Server but crashes hard when deployed to an IIS 10 Web Server running on a Windows Server 2022 Machine.

I have reviewed my use of Entity Framework DBContexts and all are created and disposed very quickly following the Unit of Work pattern. I have verified this by tracking the lifetime of the contexts - none are around for even 1 second - and confirmed that all created contexts are being disposed.

If I remove the references to code that invokes a DB Context (Via a DBContextFactory and wrapped in a Using Statement) then the server will die and will only show 'service unavailable' until I restart the Web Server or, in some cases, the entire machine.

I have an entire application that I cannot publish because it will not stay up for long, and I have been trying for a week to figure out why. The most I get out of the Event Log is a vague reference to an ISAPI process that is unhealthy. I have Try/Catch blocks around pretty much everything and am not seeing any reported errors of consequence.

How do I figure out what is causing my app to take down the Web Server when it accesses the SQL Server via EF?

Here is my DBFactory:

using BTOnlineBlazor.App_Code;
using BTOnlineBlazor.Services;
using System.Configuration;
using Microsoft.EntityFrameworkCore;
using System.Diagnostics;

namespace BTOnlineBlazor.Data
{
    public class BtDbContextFactory : IDbContextFactory<BtDbContext>
    {        
        private static readonly Lazy<BtDbContextFactory> lazy = new Lazy<BtDbContextFactory>(() =>  new BtDbContextFactory());
        private readonly ILogger<BtDbContextFactory> mLogger;

        private Dictionary<int, ContextInfo> mContextState = new Dictionary<int,ContextInfo>();

        private double mMaxLifeSpan = 0;
        public static BtDbContextFactory Instance
        {
            get
            {               
                return lazy.Value;
            }
        }

        public Dictionary<int, ContextInfo> ContextState { get => mContextState; set => mContextState = value; }

       Instance.CreateErrorReporter();
        //private IDbContextFactory<BtDbContext> mContextFactory = null!;
        private DbContextOptions<BtDbContext> dbOptions = null!;
        private string? _dbConnectionString = string.Empty;
        private int mHitCount = 0;

        private int mOpened = 0;
        private int mDisposed = 0;
        public BtDbContextFactory() 
        {

            try
            {
                ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
                {
                    builder.AddConsole();
                    builder.AddDebug();
                    builder.AddEventLog();
                });

                mLogger = loggerFactory.CreateLogger<BtDbContextFactory>();

                var builder = WebApplication.CreateBuilder();

                _dbConnectionString = builder?.Configuration.GetConnectionString("production");

                if (Debugger.IsAttached || Environment.MachineName.ToUpper().Equals("GECKOSERVER"))
                    _dbConnectionString = builder?.Configuration.GetConnectionString("development");

                System.Console.WriteLine(_dbConnectionString);
                var optionsBuilder = new DbContextOptionsBuilder<BtDbContext>();
                optionsBuilder.UseSqlServer(_dbConnectionString,
                providerOptions => providerOptions.EnableRetryOnFailure());
                
                dbOptions = optionsBuilder.Options;

                //mContextFactory = (IDbContextFactory<BtDbContext>)Instance;
            }
            catch (Exception ex)
            {
                //errReport.LogErr(ex);
            }

        }

        
        //{
        //    //if(mContextFactory == null)
        //    //{
        //    //    mContextFactory = (IDbContextFactory<BtDbContext>)Instance;
        //    //}
        //    BtDbContext context = ((IDbContextFactory<BtDbContext>)Instance).CreateDbContext();
        //    return context;
        //}

        public BtDbContext CreateContext()
        {
            mHitCount++;
            //mLogger.LogInformation("Context Count:{0}", mHitCount.ToString());
            BtDbContext context = new BtDbContext(dbOptions);//_dbConnectionString
            context.ContextId = mHitCount;
            ContextInfo info = new ContextInfo(mHitCount, DateTime.Now, 0, "");
            mContextState.Add(mHitCount, info);
            context.ContextDisposed += Context_ContextDisposed;
            mOpened++;
            return context;
        }

        private void Context_ContextDisposed(object? sender, EventArgs e)
        {
            BtDbContext context = (BtDbContext) sender;
            DateTime creationTime = mContextState[context.ContextId].ClosedTime;
            mContextState[context.ContextId].ClosedTime = DateTime.Now;
            var lifetime = (mContextState[context.ContextId].ClosedTime - creationTime).TotalMilliseconds;
            mContextState[context.ContextId].LifeSpan = lifetime;
            if (lifetime > mMaxLifeSpan)
                mMaxLifeSpan = lifetime;
            mDisposed++;
            mLogger.LogInformation(new EventId(1111, "DbContextDisposed"), "Max Lifespan: {0}, Opened: {1}, Disposed: {2}", mMaxLifeSpan, mOpened, mDisposed);
        }

        public BtDbContext CreateDbContext()
        {
            throw new NotImplementedException();
        }      
    }

    public class ContextInfo
    {
        public string Name { get; set; }
        public int Id { get; set; }

        public DateTime ClosedTime { get; set; }

        public double LifeSpan { get; set; }

        public ContextInfo(int id, DateTime closedTime, double lifeTime, string name)
        {
            Name = name;
            Id = id;
            ClosedTime = closedTime;
            LifeSpan = lifeTime;
        }        
    }
}

Here is my Program.cs:

using BTOnlineBlazor.Areas.Identity;
using BTOnlineBlazor.Data;
using BTOnlineBlazor.App_Code;
//using BTOnlineBlazor.Shared;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System.Diagnostics;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.AspNetCore.Http.Connections;




var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

var connectionString = builder.Configuration.GetConnectionString("production");

if (Debugger.IsAttached || Environment.MachineName.ToUpper().Equals("GECKOSERVER"))
    connectionString = builder.Configuration.GetConnectionString("development");

builder.Services.AddDbContext<BtDbContext>(options =>
    options.UseSqlServer(connectionString,
    providerOptions => providerOptions.EnableRetryOnFailure()),
    optionsLifetime: ServiceLifetime.Transient);


builder.Services.AddDbContextFactory<BtDbContext>(options => options.UseSqlServer(connectionString));


builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<BtDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
//builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddHttpClient();
builder.Services.RegisterApplicationServices();
builder.Services.AddMvc().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.MaxDepth = 256;
    options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
    options.JsonSerializerOptions.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull;
    options.JsonSerializerOptions.DefaultBufferSize = 200000000;
});

builder.Services.AddServerSideBlazor().AddHubOptions(options =>
{
    // maximum message size of 2MB
    options.MaximumReceiveMessageSize = 2000000;
});

builder.Services.AddHttpContextAccessor();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}




RewriteOptions urlOptions = new RewriteOptions().AddRewrite(@"^(.*).ashx$", "api/$1", true);

urlOptions.AddRewrite(@"^(.*).inf$", "api/ComputerInfo", true);

urlOptions.AddRewrite(@"AmazonLAPconsent.aspx", "AmazonLAPconsent", false);

urlOptions.AddRewrite(@"AccountReview.aspx", "AccountReview", false);

urlOptions.AddRewrite(@"^(.*).aspx$", "api/$1", true);

app.UseRewriter(urlOptions);


app.UseStaticFiles();


app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

app.MapControllers();

app.MapBlazorHub(configureOptions: options =>
{
    options.Transports = HttpTransportType.WebSockets | HttpTransportType.LongPolling;
});

app.MapFallbackToPage("/_Host");

// setup app's root folders
AppDomain.CurrentDomain.SetData("ContentRootPath", app.Environment.ContentRootPath);
AppDomain.CurrentDomain.SetData("WebRootPath", app.Environment.WebRootPath);

app.Run();

Entity Framework Core
Entity Framework Core
A lightweight, extensible, open-source, and cross-platform version of the Entity Framework data access technology.
697 questions
ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,152 questions
Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,383 questions
{count} votes

1 answer

Sort by: Most helpful
  1. VasimTamboli 4,410 Reputation points
    2023-05-07T19:31:16.9233333+00:00

    It seems that there is an issue with your Blazor Server App when deployed to an IIS 10 Web Server running on a Windows Server 2022 machine. Your application runs fine on the VS Web Server, but it crashes when deployed to IIS.

    Based on the information you provided, it seems that the cause of the issue might be related to accessing the SQL Server via Entity Framework.

    Here are some things you can try to figure out what is causing your app to take down the Web Server when it accesses the SQL Server via EF:

    Check the Event Viewer for more detailed error messages. The Event Viewer should give you more information about the error that is causing the crash.

    Check the IIS logs for any errors. The IIS logs should give you more information about the requests that are causing the crash.

    Check the SQL Server logs for any errors. The SQL Server logs should give you more information about the queries that are causing the crash.

    Use a profiler to see what is happening inside the application. A profiler will allow you to see what is happening inside the application, which should give you a better understanding of what is causing the crash.

    Simplify the application and remove any unnecessary code to see if the problem goes away. Sometimes, a problem can be caused by code that is not directly related to the problem, so simplifying the application can help narrow down the issue.

    Check the configuration of IIS and SQL Server to make sure they are set up correctly. It's possible that the problem is related to the configuration of IIS or SQL Server.

    Make sure that you are using the correct version of Entity Framework. It's possible that the version of Entity Framework you are using is not compatible with the version of .NET you are using or with IIS.

    Make sure that you are handling exceptions correctly in your code. It's possible that an unhandled exception is causing the crash.

    I hope these suggestions help you figure out what is causing the crash in your Blazor Server App. If you have any more questions, feel free to ask!

    0 comments No comments