Reputation: 865
Goal
Ok, so my aim here is to have a windows service run alongside my aspnetboilerplate web application. The reason for the windows service is that it will run the Hangfire background jobs, from a completely separate virtual server.
Configuration
This is the template i used to get things going
FYI, everything is working from from the web application.
Here is the Service program file (i am using Topshelf but i don't think that matters).
HostFactory.Run(x =>
{
x.Service<PortalService>(sc =>
{
sc.ConstructUsing(() => new PortalService());
sc.WhenStarted((tc, hostControl) => tc.Start(hostControl));
sc.WhenStopped((tc, hostControl) => tc.Stop(hostControl));
});
x.UseLog4Net();
x.RunAsLocalSystem();
x.SetServiceName(PortalService.ServiceName);
x.SetDisplayName(PortalService.ServiceDisplayName);
x.SetDescription(PortalService.ServiceDescription);
x.StartAutomatically();
});
Here is the Service Module
[DependsOn(typeof(PortalCoreModule), typeof(PortalEntityFrameworkModule), typeof(PortalApplicationModule), typeof(AbpHangfireModule))]
public class PortalServiceModule : AbpModule
{
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(PortalServiceModule).GetAssembly());
}
public override void PreInitialize()
{
Configuration.BackgroundJobs.UseHangfire(config =>
{
config.GlobalConfiguration.UseSqlServerStorage(PortalConsts.ConnectionStringName, new SqlServerStorageOptions
{
PrepareSchemaIfNecessary = true
});
config.Server = new BackgroundJobServer(new BackgroundJobServerOptions
{
Queues = new[] {BackgroundJobQueueNames.Critical, BackgroundJobQueueNames.Autotask, BackgroundJobQueueNames.Default}
});
config.GlobalConfiguration.UseConsole();
});
GlobalJobFilters.Filters.Add(new DisableMultipleQueuedItemsFilter());
}
}
And then here is my Service code
public class PortalService : ServiceControl
{
public const string ServiceName = "PortalWorker";
public const string ServiceDisplayName = "Portal Worker";
public const string ServiceDescription = "Process the background jobs for the Portal.";
private AbpBootstrapper _bootstrapper;
public bool Start(HostControl hostControl)
{
_bootstrapper = AbpBootstrapper.Create<PortalServiceModule>();
_bootstrapper.IocManager
.IocContainer
.AddFacility<LoggingFacility>(f => f.UseAbpLog4Net().WithConfig("log4net.config"));
_bootstrapper.Initialize();
return true;
}
public bool Stop(HostControl hostControl)
{
_bootstrapper.Dispose();
return true;
}
}
Yet every time i run the service, i get the following exception
Castle.MicroKernel.ComponentNotFoundException: 'No component for supporting the service Portal.BackupMonitoring.IBackupMonitoringManager was found'
However, if i was to run the Web application, it works fine, so to me its not an issue with IBackupMonitoringManager. It must be something to do with the Service not being able to register against the DI.
I have had a look in this repo for examples https://github.com/aspnetboilerplate/aspnetboilerplate-samples
But the examples are either massively out of date, or i cannot see an example of a console application with a dot net core web application.
Any help on where to get started with this would be greatly appreciated.
Here is the new exception when a job tries to execute.
Castle.MicroKernel.Handlers.HandlerException: 'Can't create component 'Portal.Authorization.Users.UserManager' as it has dependencies to be satisfied.
'Portal.Authorization.Users.UserManager' is waiting for the following dependencies: - Service 'Portal.Authorization.Roles.RoleManager' which was registered but is also waiting for dependencies. 'Portal.Authorization.Roles.RoleManager' is waiting for the following dependencies: - Service 'System.Collections.Generic.IEnumerable
1[[Microsoft.AspNetCore.Identity.IRoleValidator
1[[Portal.Authorization.Roles.Role, Portal.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Microsoft.Extensions.Identity.Core, Version=2.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]]' which was not registered. - Service 'Microsoft.AspNetCore.Identity.ILookupNormalizer' which was not registered. - Service 'Microsoft.AspNetCore.Identity.IdentityErrorDescriber' which was not registered. - Service 'Microsoft.Extensions.Logging.ILogger1[[Abp.Authorization.Roles.AbpRoleManager
2[[Portal.Authorization.Roles.Role, Portal.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[Portal.Authorization.Users.User, Portal.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Abp.ZeroCore, Version=3.2.4.0, Culture=neutral, PublicKeyToken=null]]' which was not registered. - Service 'Microsoft.Extensions.Options.IOptions1[[Microsoft.AspNetCore.Identity.IdentityOptions, Microsoft.Extensions.Identity.Core, Version=2.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]]' which was not registered. - Service 'Microsoft.AspNetCore.Identity.IPasswordHasher
1[[Portal.Authorization.Users.User, Portal.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' which was not registered. - Service 'System.Collections.Generic.IEnumerable1[[Microsoft.AspNetCore.Identity.IUserValidator
1[[Portal.Authorization.Users.User, Portal.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Microsoft.Extensions.Identity.Core, Version=2.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]]' which was not registered. - Service 'System.Collections.Generic.IEnumerable1[[Microsoft.AspNetCore.Identity.IPasswordValidator
1[[Portal.Authorization.Users.User, Portal.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Microsoft.Extensions.Identity.Core, Version=2.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]]' which was not registered. - Service 'Microsoft.AspNetCore.Identity.ILookupNormalizer' which was not registered. - Service 'Microsoft.AspNetCore.Identity.IdentityErrorDescriber' which was not registered. - Service 'System.IServiceProvider' which was not registered. - Service 'Microsoft.Extensions.Logging.ILogger1[[Microsoft.AspNetCore.Identity.UserManager
1[[Portal.Authorization.Users.User, Portal.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Microsoft.Extensions.Identity.Core, Version=2.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]]' which was not registered. '
Upvotes: 1
Views: 1429
Reputation: 865
I think i have got this now, so i'm going to post it as the answer, but let me know if there is anything wrong with it.
First of all i had to install Abp.AspNetCore package. This allowed access to the IServiceCollection.AddAbp()
extension method.
So my Program.cs
changed to this
static void Main(string[] args)
{
Clock.Provider = ClockProviders.Utc;
var services = new ServiceCollection();
IdentityRegistrar.Register(services);
services.AddAbp<PortalServiceModule>(options =>
{
//Configure Log4Net logging
options.IocManager.IocContainer.AddFacility<LoggingFacility>(
f => f.UseAbpLog4Net().WithConfig("log4net.config")
);
});
HostFactory.Run(x =>
{
x.Service<PortalService>(sc =>
{
sc.ConstructUsing(() => new PortalService());
sc.WhenStarted((tc, hostControl) => tc.Start(hostControl));
sc.WhenStopped((tc, hostControl) => tc.Stop(hostControl));
});
x.UseLog4Net();
x.RunAsLocalSystem();
x.SetServiceName(PortalService.ServiceName);
x.SetDisplayName(PortalService.ServiceDisplayName);
x.SetDescription(PortalService.ServiceDescription);
x.StartAutomatically();
});
}
I had some problems with the PortalServiceModule. The automapper configuration is already in the PortalApplicationModule so i may have to revisit this to remove the duplicate code. For the moment, this is what i got working.
[DependsOn(typeof(PortalCoreModule), typeof(PortalEntityFrameworkModule), typeof(PortalApplicationModule), typeof(AbpHangfireModule), typeof(AbpZeroCoreModule), typeof(AbpAutoMapperModule))]
public class PortalServiceModule : AbpModule
{
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(PortalServiceModule).GetAssembly());
Configuration.Modules.AbpAutoMapper().Configurators.Add(cfg =>
{
cfg.CreateMap<object, int?>().ConvertUsing<NullableIntTypeConverter>();
// Scan the assembly for classes which inherit from AutoMapper.Profile
cfg.AddProfiles(typeof(PortalApplicationModule).GetAssembly());
});
Configuration.BackgroundJobs.UseHangfire(config =>
{
config.GlobalConfiguration.UseSqlServerStorage(PortalConsts.ConnectionStringName, new SqlServerStorageOptions
{
PrepareSchemaIfNecessary = true
});
config.Server = new BackgroundJobServer(new BackgroundJobServerOptions
{
Queues = new[] { BackgroundJobQueueNames.Critical, BackgroundJobQueueNames.Autotask, BackgroundJobQueueNames.Default }
});
config.GlobalConfiguration.UseConsole();
});
GlobalJobFilters.Filters.Add(new DisableMultipleQueuedItemsFilter());
}
public override void PostInitialize()
{
HangfireJobsConfigurer.Configure();
}
}
Then my actual service code became this
public class PortalService : ServiceControl
{
public const string ServiceName = "PortalWorker";
public const string ServiceDisplayName = "Portal Worker";
public const string ServiceDescription = "Process the background jobs for the Portal.";
private AbpBootstrapper _bootstrapper;
public bool Start(HostControl hostControl)
{
_bootstrapper = AbpBootstrapper.Create<PortalServiceModule>();
_bootstrapper.Initialize();
return true;
}
public bool Stop(HostControl hostControl)
{
_bootstrapper.Dispose();
return true;
}
}
Now i can run the background jobs from the windows service, although i will try and reduce the dependencies and duplicate code.
Upvotes: 2