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();