diff --git a/src/Foundation/Features/Hangfire/ExampleRecurringJob.cs b/src/Foundation/Features/Hangfire/ExampleRecurringJob.cs new file mode 100644 index 000000000..880ec9bb7 --- /dev/null +++ b/src/Foundation/Features/Hangfire/ExampleRecurringJob.cs @@ -0,0 +1,34 @@ +using Hangfire.Console; +using Hangfire.Server; +using System.Threading; + +namespace Foundation.Features.Hangfire; + +public class ExampleRecurringJob +{ + private readonly IContentTypeRepository _contentTypeRepository; + + + public ExampleRecurringJob(IContentTypeRepository contentTypeRepository) + { + _contentTypeRepository = contentTypeRepository; + } + + public void Execute(PerformContext context) + { + context.WriteLine("Hello, world!"); + Thread.Sleep(TimeSpan.FromSeconds(1)); + + context.SetTextColor(ConsoleTextColor.Red); + context.WriteLine("Error! Just joking :)"); + Thread.Sleep(TimeSpan.FromSeconds(0.2)); + context.ResetTextColor(); + + var bar = context.WriteProgressBar(); + foreach (var contentType in _contentTypeRepository.List().WithProgress(bar)) + { + context.WriteLine(contentType.Name); + Thread.Sleep(TimeSpan.FromSeconds(0.3)); + } + } +} diff --git a/src/Foundation/Features/Hangfire/HangfireAuthorizationFilter.cs b/src/Foundation/Features/Hangfire/HangfireAuthorizationFilter.cs new file mode 100644 index 000000000..0aedec305 --- /dev/null +++ b/src/Foundation/Features/Hangfire/HangfireAuthorizationFilter.cs @@ -0,0 +1,12 @@ +using Hangfire.Annotations; +using Hangfire.Dashboard; + +namespace Foundation.Features.Hangfire; + +public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter +{ + public bool Authorize([NotNull] DashboardContext context) + { + return EPiServer.Security.PrincipalInfo.CurrentPrincipal.IsInRole("CmsAdmins"); + } +} \ No newline at end of file diff --git a/src/Foundation/Features/Hangfire/HangfireCmsController.cs b/src/Foundation/Features/Hangfire/HangfireCmsController.cs new file mode 100644 index 000000000..45e36492f --- /dev/null +++ b/src/Foundation/Features/Hangfire/HangfireCmsController.cs @@ -0,0 +1,12 @@ +namespace Foundation.Features.Hangfire; + +[Authorize(Roles = "CmsAdmin,WebAdmins,Administrators")] +[Route("[controller]")] +public class HangfireCmsController : Controller +{ + [Route("[action]")] + public ActionResult Index() + { + return View(); + } +} \ No newline at end of file diff --git a/src/Foundation/Features/Hangfire/HangfireMenuProvider.cs b/src/Foundation/Features/Hangfire/HangfireMenuProvider.cs new file mode 100644 index 000000000..ae4c34a3f --- /dev/null +++ b/src/Foundation/Features/Hangfire/HangfireMenuProvider.cs @@ -0,0 +1,23 @@ +using EPiServer.Authorization; + +namespace Foundation.Features.Hangfire; + +[MenuProvider] +public class HangfireMenuProvider: IMenuProvider +{ + public IEnumerable GetMenuItems() + { + var hangFireMenuItem = new UrlMenuItem("Hangfire", MenuPaths.Global + "/cms" + "/cmsMenuItem", + "/HangfireCms/index") + { + IsAvailable = request => EPiServer.Security.PrincipalInfo.CurrentPrincipal.IsInRole("CmsAdmins"), + AuthorizationPolicy = CmsPolicyNames.CmsAdmin, + SortIndex = SortIndex.First + 25 + }; + + return new MenuItem[] + { + hangFireMenuItem + }; + } +} \ No newline at end of file diff --git a/src/Foundation/Features/Hangfire/Index.cshtml b/src/Foundation/Features/Hangfire/Index.cshtml new file mode 100644 index 000000000..8bda39236 --- /dev/null +++ b/src/Foundation/Features/Hangfire/Index.cshtml @@ -0,0 +1,39 @@ +@using EPiServer.Framework.Web.Resources +@using EPiServer.Shell.Navigation + +@{ + Layout = string.Empty; +} + + + + + + Hangfire Dashboard + @ClientResources.RenderResources("ShellCore") + @ClientResources.RenderResources("ShellCoreLightTheme") + + + + + + @Html.CreatePlatformNavigationMenu() +
+ +
+ + + \ No newline at end of file diff --git a/src/Foundation/Foundation.csproj b/src/Foundation/Foundation.csproj index 2ea82037e..7274fc1df 100644 --- a/src/Foundation/Foundation.csproj +++ b/src/Foundation/Foundation.csproj @@ -81,6 +81,10 @@ + + + + diff --git a/src/Foundation/Startup.cs b/src/Foundation/Startup.cs index 361611e4a..70c570c52 100644 --- a/src/Foundation/Startup.cs +++ b/src/Foundation/Startup.cs @@ -15,6 +15,7 @@ using EPiServer.ServiceApi; using EPiServer.Shell.Modules; using Foundation.Features.Checkout.Payments; +using Foundation.Features.Hangfire; using Foundation.Infrastructure.Cms.ModelBinders; using Foundation.Infrastructure.Cms.Users; using Foundation.Infrastructure.Display; @@ -24,6 +25,8 @@ using Geta.Optimizely.Categories.Configuration; using Geta.Optimizely.Categories.Find.Infrastructure.Initialization; using Geta.Optimizely.Categories.Infrastructure.Initialization; +using Hangfire; +using Hangfire.Console; using Mediachase.Commerce.Anonymous; using Mediachase.Commerce.Orders; using Microsoft.AspNetCore.Builder; @@ -253,6 +256,17 @@ public void ConfigureServices(IServiceCollection services) // Adds the DAM selector button services.AddDamSelectButton(); + + // Add Hangfire services. + services.AddHangfire(configuration => configuration + .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) + .UseSimpleAssemblyNameTypeSerializer() + .UseRecommendedSerializerSettings() + .UseSqlServerStorage(_configuration.GetConnectionString("EcfSqlConnection")) + .UseConsole()); + + // Add the processing server as IHostedService + services.AddHangfireServer(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -280,7 +294,21 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) endpoints.MapControllers(); endpoints.MapRazorPages(); endpoints.MapContent(); + endpoints.MapHangfireDashboard(); }); + + var dashboardOptions = new DashboardOptions + { + Authorization = new[] + { + new HangfireAuthorizationFilter() + }, + AppPath = null + }; + // Order of middlewares is important! Add it after Authentication and Authorization in order to have a user in the context. + app.UseHangfireDashboard("/episerver/backoffice/Plugins/hangfire", dashboardOptions); + + RecurringJob.AddOrUpdate(nameof(ExampleRecurringJob) + "_Id", x => x.Execute(null), Cron.Daily); } } }