4
votes

I am at a complete loss here. I have a Blazor WASM Hosted running .net5 which has been deployed to Azure App Services. When there was no Database there was no problem deploying to Azure. It loaded and ran as expected. I have since installed Identity and a DBContext. Everything builds locally and runs properly with a local SQL instance.

On Azure, I have created a new SQLServer as well as a SQL database. On the SQL Database Firewall Settings i have "Allow Azure Services and resources to access this server" as well as a Rule for my client IP (not the IP for the Azure App).

For the App Service in Configuration i have a ConnectionString named DefaultConnection (same as in appsettings.json) with the same connection string as the SQLDatabase provides, source AppConfig, Type SqlAzure

I am publishing to Azure using the VS2019 Publish on the Server project (the startup project). I chose Target to be Azure -> Azure App Sevices (Windows) and my instance name. Configuration is Release, Target Framework net5.0, DeploymentMode Framework-dependent, Target runtime Portable.

The Service Dependencies is set to AzureSqlDatabase which uses the ConnectionName DefaultConnection, Username and Password are the Admin UserPassword setup for the SQL Server created on Azure and the SaveConnectionStringValue is Azure App Settings. (This auto populates the App Services Configuration ConnectionString described above.

When I click publish I see in the output that all publishes correctly:

Publish Succeeded.
Web App was published successfully http://bbqfriend.azurewebsites.net/
========== Build: 1 succeeded, 0 failed, 6 up-to-date, 0 skipped ==========
========== Publish: 1 succeeded, 0 failed, 0 skipped ==========
Installation of Web App Site extension Microsoft.AspNetCore.AzureAppServices.SiteExtension is in progress...
Restarting the Web App...
Successfully installed Web App extension Microsoft.AspNetCore.AzureAppServices.SiteExtension
Successfully restarted Web App.

However when the page launches, It shows a 500 Error.

If I go back to the Publish and Edit the settings - Database - DefaultConnection and Check the Use this Connection string at runtime selecting the connectionstring configured in the ServiceDependencies as well as the EntityFrameworkMigrations DataContext Apply This Migrations on publish. When I publish that profile it will do the migrations as well as the Seeds I have defined within the DataContext OnModelCreating override

protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            #region Identity Seed
            modelBuilder.ApplyConfiguration(new ApplicationUserConfiguration());
            modelBuilder.ApplyConfiguration(new IdentityRoleConfiguration());
            modelBuilder.ApplyConfiguration(new IdentityUserRoleConfiguration());
            #endregion

            //modelBuilder.ApplyConfiguration(new CountryConfiguration());

            
        }

So I know the connection string is correct and there is a Database with the proper model and seeded data. Why am I getting a 500?!?

Here is my appsettings.json in the Server project

{
    "ConnectionStrings": {
        "DefaultConnection": "Server=.;Database={DatabaseName};Trusted_Connection=True;MultipleActiveResultSets=true"
    },
    "IdentityServer": {
        "Clients": {
            "XXXX.Client": {
                "Profile": "IdentityServerSPA"
            }
        }
    },
    "Serilog": {
        "Using": [ "Serilog.Sinks.MSSqlServer" ],
        "MinimumLevel": {
            "Default": "Information",
            "Override": {
                "Microsoft": "Warning",
                "Microsoft.AspNetCore": "Warning",
                "Microsoft.AspNetCore.Authorization.DefaultAuthorizationService": "Warning",
                "Microsoft.EntityFrameworkCore": "Warning",
                "System": "Warning",
                "System.Net.Http.HttpClient*": "Warning",
                "IdentityServer4": "Warning",
                "Serilog.AspNetCore": "Warning"
            }
        },
        "WriteTo": [
            {
                "Name": "MSSqlServer",
                "Args": {
                    "connectionString": "DefaultConnection",
                    "sinkOptionsSection": {
                        "tableName": "Logs"
                    },
                    "columnOptionsSection": {
                        "additionalColumns": [
                            {
                                "ColumnName": "InstanceId"
                            },
                            {
                                "ColumnName": "Origin"
                            },
                            {
                                "ColumnName": "SourceContext"
                            },
                            {
                                "ColumnName": "UserId"
                            },
                            {
                                "ColumnName": "Username"
                            }
                        ],
                        "excludeAdditionalProperties": true
                    }
                }
            }
        ]
    },
    "AllowedHosts": "*"
}

Here is the Startup.cs for the Server project

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            //Register the Datacontext and Connection String
            services.AddDbContext<DataContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));

            services.AddDatabaseDeveloperPageExceptionFilter();

            //Sets up the default Asp.net core Identity Screens - Use Identity Scaffolding to override defaults
            services.AddDefaultIdentity<ApplicationUser>( options =>
                    {
                        options.SignIn.RequireConfirmedAccount = true;
                        options.Password.RequireDigit = true;
                        options.Password.RequireLowercase = true;
                        options.Password.RequireUppercase = true;
                        options.Password.RequiredUniqueChars = 0;
                        options.Password.RequireNonAlphanumeric = false;
                        options.Password.RequiredLength = 8;
                        options.User.RequireUniqueEmail = true;
                    })
                .AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<DataContext>();

            //Associates the User to Context with Identity
            services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, DataContext>( options =>
            {
                options.IdentityResources["openid"].UserClaims.Add(JwtClaimTypes.Role);
                options.ApiResources.Single().UserClaims.Add(JwtClaimTypes.Role);
            });
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove(JwtClaimTypes.Role);

            //Adds authentication handler
            services.AddAuthentication().AddIdentityServerJwt();

            //Register Repositories for Dependency Injection
            services.AddScoped<ICountryRepository, CountryRepository>();

            services.AddControllersWithViews();
            services.AddRazorPages();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DataContext dataContext)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseMigrationsEndPoint();
                app.UseWebAssemblyDebugging();
            }
            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();
            }

            //AutoMigrates data
            //dataContext.Database.Migrate();

            app.UseHttpsRedirection();
            app.UseBlazorFrameworkFiles();
            app.UseStaticFiles();

            app.UseSerilogIngestion();
            app.UseSerilogRequestLogging();

            app.UseRouting();

            app.UseIdentityServer();
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapControllers();
                endpoints.MapFallbackToFile("index.html");
            });
        }
    }

Here is the Program.cs for the Server project

public class Program
    {
        public static void Main(string[] args)
        {
            var configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .Build();

            Log.Logger = new LoggerConfiguration()
                .ReadFrom.Configuration(configuration)
                .Enrich.WithProperty("InstanceId", Guid.NewGuid())
                .Enrich.WithProperty("Origin", "Server")
                .CreateLogger();

            try
            {
                Log.Information("Starting up");
                CreateHostBuilder(args).Build().Run();
            }
            catch (Exception ex)
            {
                Log.Fatal(ex, "Application start-up failed");
            }
            finally
            {
                Log.CloseAndFlush();
            }

        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseSerilog()
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }

Here is the Program.cs for the Client project

public static async Task Main(string[] args)
        {
            //Serilog 
            var levelSwitch = new LoggingLevelSwitch();
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.ControlledBy(levelSwitch)
                .Enrich.WithProperty("InstanceId", Guid.NewGuid())
                .Enrich.FromLogContext()
                .WriteTo.BrowserHttp(controlLevelSwitch: levelSwitch)
                .CreateLogger();            

            Log.ForContext<Program>().Information("Client has started");

            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("#app");

            builder.Services.AddLogging(logging =>
           {
               logging.ClearProviders();
               logging.AddSerilog(dispose: true);
           });

            builder.Services.AddHttpClient("XXX.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
                .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

            // Supply HttpClient instances that include access tokens when making requests to the server project
            builder.Services.AddTransient(sp => 
                sp.GetRequiredService<IHttpClientFactory>()
                .CreateClient("XXXX.ServerAPI"));

            builder.Services.AddApiAuthorization()
                .AddAccountClaimsPrincipalFactory<RolesClaimsPrincipalFactory>();

            //Register Services
            var baseAddress = new Uri($"{builder.HostEnvironment.BaseAddress}api/");
            void RegisterTypedClient<TClient, TImplementation>(Uri apiBaseUrl)
                where TClient : class where TImplementation : class, TClient
            {
                builder.Services.AddHttpClient<TClient, TImplementation>(client => client.BaseAddress = apiBaseUrl)
                    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
            }

            RegisterTypedClient<ICountryService, CountryService>(baseAddress);


            await builder.Build().RunAsync();
        }

I do have Serilog configured and it looks to be working as well. Here are the error messages I am seeing during the Server Launch

System.InvalidOperationException: Startup assembly Microsoft.ApplicationInsights.StartupBootstrapper failed to execute. See the inner exception for more details.
 ---> System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.ApplicationInsights.StartupBootstrapper, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'Microsoft.ApplicationInsights.StartupBootstrapper, Culture=neutral, PublicKeyToken=null'
   at System.Reflection.RuntimeAssembly.InternalLoad(ObjectHandleOnStack assemblyName, ObjectHandleOnStack requestingAssembly, StackCrawlMarkHandle stackMark, Boolean throwOnFileNotFound, ObjectHandleOnStack assemblyLoadContext, ObjectHandleOnStack retAssembly)
   at System.Reflection.RuntimeAssembly.InternalLoad(AssemblyName assemblyName, RuntimeAssembly requestingAssembly, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, AssemblyLoadContext assemblyLoadContext)
   at System.Reflection.Assembly.Load(AssemblyName assemblyRef)
   at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.ExecuteHostingStartups()
   --- End of inner exception stack trace ---

and

 System.InvalidOperationException: Startup assembly DiagnosticServices.HostingStartup failed to execute. See the inner exception for more details.
 ---> System.IO.FileNotFoundException: Could not load file or assembly 'DiagnosticServices.HostingStartup, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'DiagnosticServices.HostingStartup, Culture=neutral, PublicKeyToken=null'
   at System.Reflection.RuntimeAssembly.InternalLoad(ObjectHandleOnStack assemblyName, ObjectHandleOnStack requestingAssembly, StackCrawlMarkHandle stackMark, Boolean throwOnFileNotFound, ObjectHandleOnStack assemblyLoadContext, ObjectHandleOnStack retAssembly)
   at System.Reflection.RuntimeAssembly.InternalLoad(AssemblyName assemblyName, RuntimeAssembly requestingAssembly, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, AssemblyLoadContext assemblyLoadContext)
   at System.Reflection.Assembly.Load(AssemblyName assemblyRef)
   at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.ExecuteHostingStartups()
   --- End of inner exception stack trace ---

UPDATE I am able to replicate the above error messages and they are logged into the Database via Serilog.

enter image description here

So we can see the "Starting up" from the Server Program.cs Main method (above) and the next entry is from the EntityFramework Model Validation. Then comes the errors. I can see the Namespace as Microsoft.AspNetCore.Hosting.Diagnostics as the source of the exception.

  1. I attempted to add a Nuget reference but that did nothing

  2. I attempted to add a reference to added services.AddApplicationInsightsTelemetry(); to the Server Startup.cs ConfigureServices and the ApplicationInsights InstrumentationKey to the appsettings.json (which already existed within Azure as a Variable) but that did nothing

  3. I added a reference to and added

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseSerilog()
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>().UseAzureAppServices();
                });

as per a workaround found here https://github.com/dotnet/extensions/issues/2566 which did not help

Update Day 2

Adding more information as I am still getting the same exceptions. Im curious if this is a compatibility issue between versions. My application is .net5 and running on Azure using .net5 early access.

Here are my Nuget packages for the Server Project

<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="5.0.1" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="5.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="5.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="5.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.1">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="5.0.1" />
    <PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
    <PackageReference Include="Serilog.AspNetCore.Ingestion" Version="1.0.0-dev-00012" />
    <PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
    <PackageReference Include="Serilog.Sinks.MSSqlServer" Version="5.6.0" />
  </ItemGroup>

Here are the Nuget Packages for the Client project

<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="5.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.1" />
    <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
    <PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
    <PackageReference Include="Serilog.Sinks.BrowserHttp" Version="1.0.0-dev-00012" />
    <PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
  </ItemGroup>

I have tried removing Serilog from both the Client and Server projects. I still received a 500.

With Serilog Removed I tried using .UseAzureAppServices() from https://github.com/dotnet/extensions/issues/2566 with no luck as well.

I did notice an additional error messsage

2021-01-06 19:00:38.322 +00:00 [Error] Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware: An unhandled exception has occurred while executing the request.
System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.Extensions.DependencyInjection.IdentityServerBuilderConfigurationExtensions.<>c.<AddSigningCredentials>b__10_2(IServiceProvider sp)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at ... (removed for post size) 

I have been able to isolate this to becoming an issue when I deployed my site as a Blazor WebAssembly Hosted solution. I was able to get a version of my site which was upgraded to .net5 PRE switching from Blazor WebAssembly to Blazor WebAssembly Hosted. The .net5 version was able to be deployed to Azure without issues. When deploying the WebAssembly Hosted version is when I got 500 Errors. So this has to do with deploying a Blazor WebAssembly Hosted solution to Azure.

I also experimented by creating an out of the box Blazor WebAssembly Hosted solution without Authentication and deployed it to Azure. This works with no issues. HOWEVER, when I create an out of the box Blazor WebAssembly Hosted WITH Authentication (individual user accounts stored in app) and deployed it to Azure it fails with 500!

1
I actually was able to replicate the Errors listed above. I believe that is my issue. - JoeyD
You can acess the logs via the Azure Portal, iirc WebApp > Dev Tools > Advanced. - Henk Holterman
Henk i went to the Advanced as described and in the Debug Console the eventlog.xml i have a 6700 line file. I dont know what to make of it honestly. This is far above my knowledge range - JoeyD
Scroll to the bottom and decode the exception that is there. XML is supposed to be "human readable" (-: - Henk Holterman
FYI, I have the same issue - Matt G

1 Answers

0
votes

The easiest way to make it work:

Add/set the env variable ASPNETCORE_ENVIRONMENT to Development

enter image description here

...and your Hosted Blazor WASM with Identity will finally work in Azure App Service


If you don't want the easy way above, do these instead:

  1. Generate a self-signed certificated by following this article:

(in the Generating a Self-Signed Certificate section) https://gavilan.blog/2020/08/18/blazor-using-a-self-signed-certificate-for-identityserver4-in-azure-app-service/

  1. Remember the password you used for the generated certificate.
  2. Place the certificate in your project (e.g. in the server project)
  3. Append these in the appsettings.json file:

enter image description here

  1. Publish the app once again.