Ends in:
2 DAYS
17 HRS
21 MIN
15 SEC
Ends in:
2 D
17 H
21 M
15 S
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:

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<AppUrlOptions>(options =>
    {
        options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
        options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"].Split(','));
    });
}

private void ConfigureBundles()
{
    Configure<AbpBundlingOptions>(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<SaasHostBlazorOptions>(options =>
    {
        options.EnableTenantImpersonation = true;
    });
    context.Services.Configure<AbpIdentityProBlazorOptions>(options =>
    {
        options.EnableUserImpersonation = true;
    });
    context.Services.Configure<AbpAccountOptions>(options =>
    {
        options.TenantAdminUserName = "admin";
        options.ImpersonationTenantPermission = SaasHostPermissions.Tenants.Impersonation;
        options.ImpersonationUserPermission = IdentityPermissions.Users.Impersonation;
    });
}

private void ConfigureVirtualFileSystem(IWebHostEnvironment hostingEnvironment)
{
    if (hostingEnvironment.IsDevelopment())
    {
        Configure<AbpVirtualFileSystemOptions>(options =>
        {
            options.FileSets.ReplaceEmbeddedByPhysical<PolDomainSharedModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pol.Domain.Shared"));
            options.FileSets.ReplaceEmbeddedByPhysical<PolDomainModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pol.Domain"));
            options.FileSets.ReplaceEmbeddedByPhysical<PolApplicationContractsModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pol.Application.Contracts"));
            options.FileSets.ReplaceEmbeddedByPhysical<PolApplicationModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pol.Application"));
            options.FileSets.ReplaceEmbeddedByPhysical<PolBlazorModule>(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<GoogleOptions, GoogleHandler>(
            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<MicrosoftAccountOptions, MicrosoftAccountHandler>(
            MicrosoftAccountDefaults.AuthenticationScheme,
            options =>
            {
                options.WithProperty(x => x.ClientId);
                options.WithProperty(x => x.ClientSecret, isSecret: true);
            }
        )
        .AddTwitter(TwitterDefaults.AuthenticationScheme, options => options.RetrieveUserDetails = true)
        .WithDynamicOptions<TwitterOptions, TwitterHandler>(
            TwitterDefaults.AuthenticationScheme,
            options =>
            {
                options.WithProperty(x => x.ConsumerKey);
                options.WithProperty(x => x.ConsumerSecret, isSecret: true);
            }
        );
}

private void ConfigureLocalizationServices()
{
    Configure<AbpLocalizationOptions>(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<AbpNavigationOptions>(options =>
    {
        options.MenuContributors.Add(new PolMenuContributor());
    });
}

private void ConfigureLeptonTheme()
{
    Configure<Volo.Abp.AspNetCore.Components.Web.LeptonTheme.LeptonThemeOptions>(options =>
    {
        options.FooterComponent = typeof(MainFooterComponent);
    });
}

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

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

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

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:


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:

  • 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.

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

Made with ❤️ on ABP v9.1.0-preview. Updated on December 02, 2024, 12:35