Open Closed

Hangfire Dashboard configured but receiving 404 page not found #4021


User avatar
0
dev1_premierpoint created

Check the docs before asking a question: https://docs.abp.io/en/commercial/latest/
Check the samples, to see the basic tasks: https://docs.abp.io/en/commercial/latest/samples/index
The exact solution to your question may have been answered before, please use the search on the homepage.

If you're creating a bug/problem report, please include followings:

  • ABP Framework version: v5.3.1

  • UI type: Blazor

  • DB provider: EF Core

  • Tiered (MVC) or Identity Server Separated (Angular): no

  • Exception message and stack trace:

  • Steps to reproduce the issue:"

When I run my application in dev and try to browse to the Hangfire dashboard I get a 404 page not found:

image.png

Here is how I have Hangfire configured in my Blazor project's module:

using System;
using System.IO;
using System.Threading.Tasks;
using Blazorise.Bootstrap5;
using Blazorise.Icons.FontAwesome;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
using Microsoft.AspNetCore.Authentication.Twitter;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Pol.Blazor.Menus;
using Pol.EntityFrameworkCore;
using Pol.Localization;
using Pol.MultiTenancy;
using Microsoft.OpenApi.Models;
using Pol.Blazor.Components.Layout;
using Volo.Abp;
using Volo.Abp.Account.Pro.Admin.Blazor.Server;
using Volo.Abp.Account.Pro.Public.Blazor.Server;
using Volo.Abp.Account.Public.Web;
using Volo.Abp.Account.Public.Web.ExternalProviders;
using Volo.Abp.Account.Web;
using Volo.Abp.Account.Public.Web.Impersonation;
using Volo.Abp.AspNetCore.Authentication.JwtBearer;
using Volo.Abp.AspNetCore.Components.Server.LeptonTheme;
using Volo.Abp.AspNetCore.Components.Server.LeptonTheme.Bundling;
using Volo.Abp.AspNetCore.Components.Web.Theming.Routing;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared;
using Volo.Abp.AspNetCore.Serilog;
using Volo.Abp.AuditLogging.Blazor.Server;
using Volo.Abp.Autofac;
using Volo.Abp.AutoMapper;
using Volo.Abp.Gdpr.Blazor.Server;
using Volo.Abp.Identity;
using Volo.Abp.Identity.Pro.Blazor;
using Volo.Abp.Identity.Pro.Blazor.Server;
using Volo.Abp.IdentityServer.Blazor.Server;
using Volo.Abp.LanguageManagement.Blazor.Server;
using Volo.Abp.LeptonTheme.Management.Blazor.Server;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.Swashbuckle;
using Volo.Abp.TextTemplateManagement.Blazor.Server;
using Volo.Abp.UI.Navigation;
using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.VirtualFileSystem;
using Volo.Saas.Host;
using Volo.Saas.Host.Blazor;
using Volo.Saas.Host.Blazor.Server;
using Volo.Abp.BackgroundJobs.Hangfire;
using Hangfire;

namespace Pol.Blazor;

[DependsOn(
typeof(PolApplicationModule),
typeof(PolEntityFrameworkCoreModule),
typeof(PolHttpApiModule),
typeof(AbpAspNetCoreMvcUiLeptonThemeModule),
typeof(AbpAutofacModule),
typeof(AbpSwashbuckleModule),
typeof(AbpAccountPublicWebImpersonationModule),
typeof(AbpAspNetCoreSerilogModule),
typeof(AbpAspNetCoreComponentsServerLeptonThemeModule),
typeof(AbpAccountPublicWebIdentityServerModule),
typeof(AbpAccountPublicBlazorServerModule),
typeof(AbpAccountAdminBlazorServerModule),
typeof(AbpAuditLoggingBlazorServerModule),
typeof(AbpIdentityProBlazorServerModule),
typeof(LeptonThemeManagementBlazorServerModule),
typeof(AbpIdentityServerBlazorServerModule),
typeof(LanguageManagementBlazorServerModule),
typeof(SaasHostBlazorServerModule),
typeof(TextTemplateManagementBlazorServerModule),
typeof(AbpGdprBlazorServerModule),
typeof(AbpBackgroundJobsHangfireModule)
)]

public class PolBlazorModule : AbpModule

{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.PreConfigure(options =>
{
options.AddAssemblyResource(
typeof(PolResource),
typeof(PolDomainModule).Assembly,
typeof(PolDomainSharedModule).Assembly,
typeof(PolApplicationModule).Assembly,
typeof(PolApplicationContractsModule).Assembly,
typeof(PolBlazorModule).Assembly
);
});
}

public override void ConfigureServices(ServiceConfigurationContext context)
{
    var hostingEnvironment = context.Services.GetHostingEnvironment();
    var configuration = context.Services.GetConfiguration();

    ConfigureUrls(configuration);
    ConfigureBundles();
    ConfigureAuthentication(context, configuration);
    ConfigureImpersonation(context, configuration);
    ConfigureAutoMapper();
    ConfigureVirtualFileSystem(hostingEnvironment);
    ConfigureLocalizationServices();
    ConfigureSwaggerServices(context.Services);
    ConfigureExternalProviders(context, configuration);
    ConfigureAutoApiControllers();
    ConfigureBlazorise(context);
    ConfigureRouter(context);
    ConfigureMenu(context);
    ConfigureLeptonTheme();
    ConfigureHangfire(context, configuration);
}

private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
{
    context.Services.AddHangfire(config =>
    {
        config.UseSqlServerStorage(configuration.GetConnectionString("Hangfire"));
    });
}


private void ConfigureUrls(IConfiguration configuration)
{
    Configure(options =>
    {
        options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
        options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"].Split(','));
    });
}

private void ConfigureBundles()
{
    Configure(options =>
    {
        // MVC UI
        options.StyleBundles.Configure(
            LeptonThemeBundles.Styles.Global,
            bundle =>
            {
                bundle.AddFiles("/global-styles.css");
            }
        );

        // Blazor UI
        options.StyleBundles.Configure(
            BlazorLeptonThemeBundles.Styles.Global,
            bundle =>
            {
                bundle.AddFiles("/blazor-global-styles.css");
                //You can remove the following line if you don't use Blazor CSS isolation for components
                bundle.AddFiles("/Pol.Blazor.styles.css");
            }
        );
    });
}

private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
    context.Services.AddAuthentication()
        .AddJwtBearer(options =>
        {
            options.Authority = configuration["AuthServer:Authority"];
            options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
            options.Audience = "Pol";
        });
}

private void ConfigureImpersonation(ServiceConfigurationContext context, IConfiguration configuration)
{
    context.Services.Configure(options =>
    {
        options.EnableTenantImpersonation = true;
    });
    context.Services.Configure(options =>
    {
        options.EnableUserImpersonation = true;
    });
    context.Services.Configure(options =>
    {
        options.TenantAdminUserName = "admin";
        options.ImpersonationTenantPermission = SaasHostPermissions.Tenants.Impersonation;
        options.ImpersonationUserPermission = IdentityPermissions.Users.Impersonation;
    });
}

private void ConfigureVirtualFileSystem(IWebHostEnvironment hostingEnvironment)
{
    if (hostingEnvironment.IsDevelopment())
    {
        Configure(options =>
        {
            options.FileSets.ReplaceEmbeddedByPhysical(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pol.Domain.Shared"));
            options.FileSets.ReplaceEmbeddedByPhysical(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pol.Domain"));
            options.FileSets.ReplaceEmbeddedByPhysical(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pol.Application.Contracts"));
            options.FileSets.ReplaceEmbeddedByPhysical(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pol.Application"));
            options.FileSets.ReplaceEmbeddedByPhysical(hostingEnvironment.ContentRootPath);
        });
    }
}

private void ConfigureSwaggerServices(IServiceCollection services)
{
    services.AddAbpSwaggerGen(
        options =>
        {
            options.SwaggerDoc("v1", new OpenApiInfo { Title = "Pol API", Version = "v1" });
            options.DocInclusionPredicate((docName, description) => true);
            options.CustomSchemaIds(type => type.FullName);
        }
    );
}

private void ConfigureExternalProviders(ServiceConfigurationContext context, IConfiguration configuration)
{
    context.Services.AddAuthentication()
        .AddGoogle(GoogleDefaults.AuthenticationScheme, _ => {})
        .WithDynamicOptions(
            GoogleDefaults.AuthenticationScheme,
            options =>
            {
                options.WithProperty(x => x.ClientId);
                options.WithProperty(x => x.ClientSecret, isSecret: true);
            }
        )
        .AddMicrosoftAccount(MicrosoftAccountDefaults.AuthenticationScheme, options =>
        {
            //Personal Microsoft accounts as an example.
            options.AuthorizationEndpoint = "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize";
            options.TokenEndpoint = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
        })
        .WithDynamicOptions(
            MicrosoftAccountDefaults.AuthenticationScheme,
            options =>
            {
                options.WithProperty(x => x.ClientId);
                options.WithProperty(x => x.ClientSecret, isSecret: true);
            }
        )
        .AddTwitter(TwitterDefaults.AuthenticationScheme, options => options.RetrieveUserDetails = true)
        .WithDynamicOptions(
            TwitterDefaults.AuthenticationScheme,
            options =>
            {
                options.WithProperty(x => x.ConsumerKey);
                options.WithProperty(x => x.ConsumerSecret, isSecret: true);
            }
        );
}

private void ConfigureLocalizationServices()
{
    Configure(options =>
    {
        options.Languages.Add(new LanguageInfo("ar", "ar", "العربية", "ae"));
        options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština"));
        options.Languages.Add(new LanguageInfo("en", "en", "English"));
        options.Languages.Add(new LanguageInfo("en-GB", "en-GB", "English (UK)"));
        options.Languages.Add(new LanguageInfo("hu", "hu", "Magyar"));
        options.Languages.Add(new LanguageInfo("fi", "fi", "Finnish", "fi"));
        options.Languages.Add(new LanguageInfo("fr", "fr", "Français", "fr"));
        options.Languages.Add(new LanguageInfo("hi", "hi", "Hindi", "in"));
        options.Languages.Add(new LanguageInfo("it", "it", "Italiano", "it"));
        options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português"));
        options.Languages.Add(new LanguageInfo("ru", "ru", "Русский", "ru"));
        options.Languages.Add(new LanguageInfo("sk", "sk", "Slovak", "sk"));
        options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe"));
        options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文"));
        options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "繁體中文"));
        options.Languages.Add(new LanguageInfo("de-DE", "de-DE", "Deutsch", "de"));
        options.Languages.Add(new LanguageInfo("es", "es", "Español"));
    });
}


private void ConfigureBlazorise(ServiceConfigurationContext context)
{
    context.Services
        .AddBootstrap5Providers()
        .AddFontAwesomeIcons();
}

private void ConfigureMenu(ServiceConfigurationContext context)
{
    Configure(options =>
    {
        options.MenuContributors.Add(new PolMenuContributor());
    });
}

private void ConfigureLeptonTheme()
{
    Configure(options =>
    {
        options.FooterComponent = typeof(MainFooterComponent);
    });
}

private void ConfigureRouter(ServiceConfigurationContext context)
{
    Configure(options =>
    {
        options.AppAssembly = typeof(PolBlazorModule).Assembly;
    });
}

private void ConfigureAutoApiControllers()
{
    Configure(options =>
    {
        options.ConventionalControllers.Create(typeof(PolApplicationModule).Assembly);
    });
}

private void ConfigureAutoMapper()
{
    Configure(options =>
    {
        options.AddMaps();
    });
}

public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
    var env = context.GetEnvironment();
    var app = context.GetApplicationBuilder();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseAbpRequestLocalization();

    if (!env.IsDevelopment())
    {
        app.UseErrorPage();
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseCorrelationId();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseAuthentication();
    app.UseJwtTokenMiddleware();

    if (MultiTenancyConsts.IsEnabled)
    {
        app.UseMultiTenancy();
    }

    app.UseUnitOfWork();
    app.UseIdentityServer();
    app.UseAuthorization();
    app.UseSwagger();
    app.UseAbpSwaggerUI(options =>
    {
        options.SwaggerEndpoint("/swagger/v1/swagger.json", "Pol API");
    });
    app.UseAuditing();
    app.UseAbpSerilogEnrichers();
    app.UseConfiguredEndpoints();
    app.UseHangfireDashboard("/hangfire");
}

}

Also, this is what shows up in the application logs:

2022-11-08_17-57-36.png


4 Answer(s)
  • User Avatar
    0
    EngincanV created
    Support Team .NET Developer

    Hi, thanks for the detailed information. I'll check and write you back asap.

  • User Avatar
    0
    EngincanV created
    Support Team .NET Developer

    Hi, please add the app.UseHangfireDashboard("/hangfire"); middleware before the app.UseConfiguredEndpoints(); middleware:

    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        //...
        app.UseHangfireDashboard("/hangfire"); //should defined before UseConfiguredEndpoints()
        app.UseConfiguredEndpoints();
    }
    

    Then, you should be able to see the /hangfire page:

    image.png

  • User Avatar
    0
    dev1_premierpoint created

    Thanks - that solved the problem.

    I had been following this page of documentation:

    https://docs.abp.io/en/abp/latest/Background-Jobs-Hangfire

    I'd say it should be updated to add this requirement.

    Jeff

  • User Avatar
    0
    EngincanV created
    Support Team .NET Developer

    Hi Jeff, thanks for the confirmation. I've updated the doc.

    image.png

    I close the question since your problem is resolved.
    Best regards.

Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
Do you need assistance from an ABP expert?
Schedule a Meeting
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v9.2.0-preview. Updated on March 19, 2025, 10:09