How do i run a Windows service in Azure Service Fabric? - windows-services

I have a Windows service for test purposes that i want to migrate to Service Fabric. The service does nothing more than writing to a txt-file on my drive when it starts and when it stops. It works fine when i manually start and stop the service after installing it on my machine. Can i achieve the same result on service fabric or does the implementation be different?
I have created a guest executable with the service and deployed it to a local cluster following this guide.

First of all, I don't like this answer. After playing with it, I'm convinced the best way is to just port the code to a service fabric app. I would love to see a better "bolt-on" solution, but I haven't found any others. Every answer I've seen says "just run the exe as a Guest Executable", but a Windows Service exe doesn't "just run". It needs to be ran as a Windows Service which calls the OnStart entry point of the Service class (which inherits from ServiceBase).
The code below will allow your Windows Service to run in Service Fabric, but Service Fabric seems to report WARNINGS! So it's FAR from perfect.
It shouldn't require any changes to your OnStart or OnStop methods, however it does require some basic plumbing to work. This is also helpful if you wish to debug your windows services, as it allows you to pass in a /console command line argument and have it run in a console window.
First, either create your own ServiceBase class, or simply paste this code into your Service class (by default it's called Service1.cs in a C# Windows Service project):
// Expose public method to call the protected OnStart method
public void StartConsole(string[] args)
{
// Plumbing...
// Allocate a console, otherwise we can't properly terminate the console to call OnStop
AllocConsole();
// Yuck, better way?
StaticInstance = this;
// Handle CTRL+C, CTRL+BREAK, etc (call OnStop)
SetConsoleCtrlHandler(new HandlerRoutine(ConsoleCtrlCheck), true);
// Start service code
this.OnStart(args);
}
// Expose public method to call protected OnStop method
public void StopConsole()
{
this.OnStop();
}
public static Service1 StaticInstance;
private static bool ConsoleCtrlCheck(CtrlTypes ctrlType)
{
switch (ctrlType)
{
case CtrlTypes.CTRL_C_EVENT:
case CtrlTypes.CTRL_BREAK_EVENT:
case CtrlTypes.CTRL_CLOSE_EVENT:
case CtrlTypes.CTRL_LOGOFF_EVENT:
case CtrlTypes.CTRL_SHUTDOWN_EVENT:
StaticInstance.StopConsole();
return false;
}
return true;
}
[DllImport("kernel32.dll")]
private static extern bool AllocConsole();
[DllImport("Kernel32")]
public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);
public delegate bool HandlerRoutine(CtrlTypes CtrlType);
public enum CtrlTypes
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
Now change your Main method in Program.cs to look like this:
static void Main(string[] args)
{
var service = new Service1();
if (args.Length > 0 && args.Any(x => x.Equals("/console", StringComparison.OrdinalIgnoreCase)))
{
service.StartConsole(args);
}
else
{
ServiceBase.Run(
new ServiceBase[]
{
service
});
}
}
You may need to rename 'Service1' to whatever your service class is called.
When calling it through Service Fabric, make sure it's passing in the /console argument in ServiceManifest.xml:
<CodePackage Name="Code" Version="1.0.0">
<EntryPoint>
<ExeHost>
<Program>WindowsService1.exe</Program>
<Arguments>/console</Arguments>
<WorkingFolder>Work</WorkingFolder>
</ExeHost>
</EntryPoint>
</CodePackage>
If you wish to use this as a debuggable Windows Service, you can also set your 'Command line arguments' to /console under the Project settings > Debug tab.
EDIT:
A better option is to use TopShelf. This will work without warnings in Service Fabric, however it does require some code refactoring as it becomes a Console project instead of a Windows Service project.

Related

Running init script on oracle test container with system privileges

I am struggling with org.testcontainers:oracle-xe:1.14.3.
I am trying to run a test intended to verify schema creation and migration, however I'm getting stuck at the InitScript, when trying to initialize the users for the test with the users 'sys as sysdba'.
#Before
public void setUp() {
oracleContainer = new OracleContainer("oracleinanutshell/oracle-xe-11g")
.withUsername("sys as sysdba")
.withInitScript("oracle-initscript.sql");
oracleContainer.start();
}
The above seems to be able to connect, but execution of the init script fails with a
ORA-01109: database not open
Using the 'system' user in the above does not provide the InitScript connection with sysdba privileges, but result in an open database.
I'm looking for a solution that will allow me to initialize multiple users prior to a test. This initialization has grants that requires sysdba privileges. The test, in which some SQL scripts are executed, requires that both users are created in the database and can connect to the database.
In my case I'm using
oracleContainer = new OracleContainer("gvenzl/oracle-xe:18.4.0-slim")
.withUsername("test")
.withPassword("test")
.addEnv("ORACLE_PASSWORD", "s") // Sys password is required
.withCopyFileToContainer(MountableFile.forHostPath("oracle-initscript.sql"), "/container-entrypoint-initdb.d/init.sql")
gvenzl/oracle-xe is the default image used by the org.testcontainers.oracle-xe library.
The documentation for this image describes how to call initialization SQL on DB start and it works great.
Hard to say what is the issue but here are some tricks:
maybe "sys as sysdba" is not valid in your code, documentation is not clear about the usage
maybe withLogConsumer can provide some clues what's wrong
I recommend the image gvenzl/oracle-xe,
in some cases withInitScript may not work properly.
it is useful to test the init script on the container started manually
I finished on end with this approach:
as sys admin created two different schema/user)
#SpringBootTest(classes = Main.class)
#Import(DbConfiguration.class)
#Testcontainers
public class ServiceIntegrationTest {
#Container
public static final OracleContainer oracleContainer =
new OracleContainer("gvenzl/oracle-xe:21-slim-faststart");
}
import static com.integrationtests.local_test.service.IntegrationTest.oracleContainer;
#TestConfiguration
public class DbConfiguration {
static final String DEFAULT_SYS_USER = "sys as sysdba";
private static final String ENTITY_MANAGER_FACTORY = "entityManagerFactory";
#Bean
public DataSource getDataSource() {
DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("oracle.jdbc.OracleDriver");
dataSourceBuilder.url(oracleContainer.getJdbcUrl());
dataSourceBuilder.username(DEFAULT_SYS_USER);
dataSourceBuilder.password(oracleContainer.getPassword());
return dataSourceBuilder.build();
}
Also in application.yaml put scripts
spring:
datasource:
initialization-mode: always
schema:
- classpath:/sql/init_schemas/USER_ONE.sql
- classpath:/sql/init_schemas/USER_TWOT.sql

Hangfire job on Console/Web App solution?

I'm new to Hangfire and I'm trying to understand how this works.
So I have a MVC 5 application and a Console application in the same solution. The console application is a simple one that just updates some data on the database (originally planned to use Windows Task Scheduler).
Where exactly do I install Hangfire? In the Web app or the console? Or should I convert the console into a class on the Web app?
If I understand it correctly, the console in your solution is acting like an "pseudo" HangFire, since like you said it does some database operations overtime and you plan to execute it using the Task Scheduler.
HangFire Overview
HangFire was design to do exactly what you want with your console app, but with a lot more of power and functionalities, so you avoid all the overhead of creating all that by yourself.
HangFire Instalation
HangFire is installed commonly alongside with ASP.NET Applications, but if you carefully read the docs, you will surprisingly find this:
Hangfire project consists of a couple of NuGet packages available on
NuGet Gallery site. Here is the list of basic packages you should know
about:
Hangfire – bootstrapper package that is intended to be installed only
for ASP.NET applications that uses SQL Server as a job storage. It
simply references to Hangfire.Core, Hangfire.SqlServer and
Microsoft.Owin.Host.SystemWeb packages.
Hangfire.Core – basic package
that contains all core components of Hangfire. It can be used in any
project type, including ASP.NET application, Windows Service, Console,
any OWIN-compatible web application, Azure Worker Role, etc.
As you can see, HangFire can be used in any type of project including console applications but you will need to manage and add all the libraries depending on what kind of job storage you will use. See more here:
Once HangFire is Installed you can configure it to use the dashboard, which is an interface where you can find all the information about your background jobs. In the company I work, we used HangFire several times with recurring jobs mostly to import users, synchronize information across applications and perform operations that would be costly to run during business hours, and the Dashboard proved to be very useful when we wanted to know if a certain job was running or not. It also uses CRON to schedule the operations.
A sample of we are using right now is:
Startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
//Get the connection string of the HangFire database
GlobalConfiguration.Configuration.UseSqlServerStorage(connection);
//Start HangFire Server and enable the Dashboard
app.UseHangfireDashboard();
app.UseHangfireServer();
//Start HangFire Recurring Jobs
HangfireServices.Instance.StartSendDetails();
HangfireServices.Instance.StartDeleteDetails();
}
}
HangfireServices.cs
public class HangfireServices
{
//.. dependency injection and other definitions
//ID of the Recurring JOBS
public static string SEND_SERVICE = "Send";
public static string DELETE_SERVICE = "Delete";
public void StartSend()
{
RecurringJob.AddOrUpdate(SEND_SERVICE, () =>
Business.Send(), //this is my class that does the actual process
HangFireConfiguration.Instance.SendCron.Record); //this is a simple class that reads an configuration CRON file
}
public void StartDeleteDetails()
{
RecurringJob.AddOrUpdate(DELETE_SERVICE, () =>
Business.SendDelete(), //this is my class that does the actual process
HangFireConfiguration.Instance.DeleteCron.Record); //this is a simple class that reads an configuration CRON file
}
}
HangFireConfiguration.cs
public sealed class HangFireConfiguration : ConfigurationSection
{
private static HangFireConfiguration _instance;
public static HangFireConfiguration Instance
{
get { return _instance ?? (_instance = (HangFireConfiguration)WebConfigurationManager.GetSection("hangfire")); }
}
[ConfigurationProperty("send_cron", IsRequired = true)]
public CronElements SendCron
{
get { return (CronElements)base["send_cron"]; }
set { base["send_cron"] = value; }
}
[ConfigurationProperty("delete_cron", IsRequired = true)]
public CronElements DeleteCron
{
get { return (CronElements)base["delete_cron"]; }
set { base["delete_cron"] = value; }
}
}
hangfire.config
<hangfire>
<send_cron record="0,15,30,45 * * * *"></send_cron>
<delete_cron record="0,15,30,45 * * * *"></delete_cron>
</hangfire>
The CRON expression above will run at 0,15,30,45 minutes every hour every day.
Web.config
<configSections>
<!-- Points to the HangFireConfiguration class -->
<section name="hangfire" type="MyProject.Configuration.HangFireConfiguration" />
</configSections>
<!-- Points to the .config file -->
<hangfire configSource="Configs\hangfire.config" />
Conclusion
Given the scenario you described, I would probably install HangFire in your ASP.NET MVC application and remove the console application, simple because it is one project less to worry about. Even though you can install it on a console application I would rather not follow that path because if you hit a brick wall (and you'll hit, trust me), chances are you'll find help mostly for cases where it was installed in ASP.NET applications.
No need of any more console application to update the database. You can use hangfire in your MVC application itself.
http://docs.hangfire.io/en/latest/configuration/index.html
After adding the hangfire configuration, you can make use of normal MVC method to do the console operations like updating the DB.
Based on your requirement you can use
BackgroundJob.Enqueue --> Immediate update to DB
BackgroundJob.Schedule --> Delayed update to DB
RecurringJob.AddOrUpdate --> Recurring update to DB like windows service.
Below is an example,
public class MyController : Controller
{
public void MyMVCMethod(int Id)
{
BackgroundJob.Enqueue(() => UpdateDB(Id));
}
public void UpdateDB(Id)
{
// Code to update the Database.
}
}

How do I stop TopShelf creating multiple log files using log4net?

One of my TopShelf-hosted Windows services is creating duplicate log files that look like this:
myapp.20140729.log
myapp.20140729.log.20140729.log
A similar problem has been described on StackOverflow before - the solutions in 10639682 didn't work for me, but 579688 suggests it could be caused by initialising the logging system twice.
I'm using the TopShelf Log4Net plugin, but I'm also doing my own log4net logging and need access to the ILog instance before the service has actually started, so my code looks like this:
public static void Main(string[] args) {
XmlConfigurator.Configure();
var log = LogManager.GetLogger(typeof(MyService));
var container = new Container();
RegisterComponents(container, log);
log.InfoFormat("Starting MyService");
RunService(container);
}
public static void RunService(Container container) {
HostFactory.Run(x => {
x.Service<PortalAdaptor>(s => {
s.ConstructUsing(name => container.Resolve<MyService>());
s.WhenStarted(f => f.Start());
s.WhenStopped(f => f.Stop());
});
x.RunAsLocalService();
x.SetDescription("My Service");
x.SetDisplayName("My Service");
x.SetServiceName("MyService");
x.UseLog4Net();
});
}
I've done a little digging, and it appears that calling x.UseLog4Net() in my RunService method is actually running XmlConfigurator.Configure() again - see Log4NetLogWriterFactory, line 62 in the TopShelf code.
So...
Am I correct in thinking that calling XmlConfigurator.Configure() twice could be the cause of my duplicate log file issues?
If so - how can I either inject an existing config into TopShelf, OR get the TopShelf initialization to run before I start my service so I can start logging before the service starts?
Call Hostfactory.New instead of HostFactory.Run to get a reference to the host, do your logging, then call the Run() method on the host:
var host = HostFactory.New(configureService);
var log = LogManager.GetLogger(typeof(MyService));
log.InfoFormat("Starting MyService");
host.Run();
An alternative approache would be that once you have done your initial logging, call LogManager.ResetConfiguration to clear the configuration and allow TopShelf to reload it:
Resets all values contained in the repository instance to their
defaults. This removes all appenders from all loggers, sets the level
of all non-root loggers to null, sets their additivity flag to true
and sets the level of the root logger to Debug. Moreover, message
disabling is set to its default "off" value.

Resolving a type without registering first - prism 4 and Untiy

First of all I would like to remark I am new with the concept of prism, DI and containers. I am looking on one of the code samples provided with the Prism Library:
The code simply injects a view with the "Hello World" string (in a TextBlock element) to a region in the shell.
When the application starts-up, it creates a new BootStrapper instance, which creates and initializes the shell:
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<Shell>();
}
protected override void InitializeShell()
{
base.InitializeShell();
Application.Current.RootVisual = (UIElement)this.Shell;
}
protected override void ConfigureModuleCatalog()
{
base.ConfigureModuleCatalog();
ModuleCatalog moduleCatalog = (ModuleCatalog)this.ModuleCatalog;
moduleCatalog.AddModule(typeof(HelloWorldModule.HelloWorldModule));
}
}
My question refers to the method CreateShell(). I couldnt find nowhere in the supplied code (including not in a configuration file or any xaml file...) where do they register the type Shell, and even if it was registered - the supplies Shell class doesnt implement any interface... what is the meaning of resolving a specific type?
the Shell implementation:
public partial class Shell : UserControl
{
public Shell()
{
InitializeComponent();
}
}
This looks like a magic to me, so I tried to create my own type (MyType) and resolve it the same way:
Container.Resolve<MyType>();
By setting a breakepoint inside MyType constructor, I saw that it DID resolved MyType. Can somebody please explain to me how does it work?
These couple of threads should answer your question:
http://compositewpf.codeplex.com/Thread/View.aspx?ThreadId=230051
Does unity just make clasess with out needing anything registered?
Additionally, if you are eager to get more detail into how Unity can do this, simple download Unity 2.0 and open the source code that is provided with the installer.
I hope this helps.
Thanks,
Damian
You do not need to register a type you want to resolve. You need to register the dependencies of a type, that you want to resolve. In this case, the Shell doesn't need any dependencies, so you can resolve it simply. But for an example (not really), if your shell getting an interface IService as a parameter, then you must register IService, before you resolve Shell.
Otherwise you will get Dependency Resolution Failed Exception. In Prism 4.1 it will be swallowed silently due to TryResolve.

running code in remote process CLR runtime through ICLRRuntimeHost and ExecuteInDefaultAppDomain()

I tried to combine the examples at coding the wheel and profiler attach. Everything seems to go fine, except, when I try to enumerate running assemblies in the remote processes' default appdomain, I don't get the right list. public class remoteFoo {
public static int sTest(String message) {
AppDomain currentDomain = AppDomain.CurrentDomain;
Assembly[] assems = currentDomain.GetAssemblies();
MessageBox.Show("List of assemblies loaded in current appdomain:");
foreach (Assembly assem in assems)
MessageBox.Show(assem.ToString()); //remoteFoo gets listed, but not hostBar.
return 3;
}
}remoteFoo gets listed, but not hostBar. Basically I want to use introspection to run code in the remote process's appdomain. But it seems that I don't have the right appdomain...
Here is a link to the code I use: main.cpp

Resources