Tag Archives: Castle Windsor

Quartz.NET, Castle Windsor – LifeStyle Per Job (Scoped)

Pokud používáte Quartz.NET (známá knihovna pro job-scheduling) s Castle Windsor (IoC/DI container), možná jste narazili na potřebu registrovat komponentu s životním cyklem „per job“ (LifestylePerJob). Na internetu najdete několik možností, které mají jedno společné – nefungují:

  • LifestylePerThread – neresetuje thread mezi joby, komponenta se použije pro více jobů
  • BoundTo<Job>() – nepodporuje typed factories, pokud si z nich vyžádáte službu v jobu
  • BoundTo<object>() – nepodporuje typed factories, pokud si z nich vyžádáte službu v jobu
  • Quartz.IJobFactory + LifestyleScoped – nepodporuje typed factories, pokud si z nich vyžádáte službu v jobu (scope odstartovaný v JobFactory se nezpropaguje do jobu!)

Naštěstí existuje „jednoduché“ řešení. Můžete použít LifestyleScoped, ale je potřeba scope začít/ukončit uvnitř jobu.

Pokud použijete samotný job jenom jako plumbing class kde práce samotná je zapouzdřena do samostatné služby (a měli byste to tak mít tak jako tak), pak stačí do jobu nainjectovat factory k takové službě a kernel pro ovládání scope:

public class MyJob : IJob
{
    private readonly IServiceFactory&lt;myservice&gt; myServiceFactory;
    private readonly IKernel kernel;

    public MyJob(IServiceFactory&lt;myservice&gt; myServiceFactory, IKernel kernel)
    {
        this.myServiceFactory = myServiceFactory;
        this.kernel = kernel;
    }

    public void Execute()
    {
        using (var scope = kernel.BeginScope())
        {
            // use your way to work with factories ;-)
            using (var myService = myServiceFactory.Create())
            {
                myService.DoWork();
            }
            // BTW: we have an extension method
            myServiceFactory.ExecuteAction(service =&amp;gt; service.DoWork());
        }
    }
}

…třída MyService class má všechny dependencies, které potřebuje a při použítí přes factory tím získáte nový pseudo-resolution-root. Svádí to samozřejmě použít rovnou kernel.Resolve+Release, ale nedělejte to… ;-)

References

Castle Windsor: container.Install(FromAssembly.This()) – Občas nefunguje? Pozor na inlining!

Dneska nás tu zabavil problém s chováním Castle Windsor.

Symptom

Na jednom z projektů Castle Windsor „občas“ ignoroval registrace a odmítal resolvovat komponenty, které měly být regulérně zaregistrovány pomocí installerů. Při vývoji a ladění z Visual Studia na vývojářských strojích vše fungovalo, po continous integration deploymentu na STAGE aplikace nejela. Padala na chybu:

System.ApplicationException: Error in resolving dependency XY. ---&gt;
 Castle.MicroKernel.ComponentNotFoundException: No component for supporting the service XY was found

Celé se to chovalo velmi nedeterministicky. Při re-deploymentu stejného buildu aplikace pomocí XCOPY se tato rozběhla, po iisreset zase nešla, atp.

Analysis

Vzhledem k tomu, že aplikace se chovala jinak na vývojářských strojích (fungovala v DEBUG i RELEASE) a jinak na STAGE, bylo potřeba zvolit trochu agresivnější metody ladění. Pokus o remote-debugging vypadal zdlouhavě, analýza memory dumpu ukázala, že ve WindsorContaineru příslušné registrace opravdu chybí.

Zkoumání registrací/installerů nakonec ukázalo, kde je problém.

Cause

Uspořádání solution bylo jednoduché. Webová aplikace, v Global.asax:Application_Start se vytvoří container a následně se na něm zavolá extension-metoda ConfigureForWebApplication(), která samotná je však v samostatné class-library WindsorInstallers, kde jsou umístěné všechny installery a extension-metoda si je volá přes Install(FromAssembly.This()):

public static void ConfigureForWebApplication(this IWindsorContainer container)
{
	container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel));
	container.AddFacility();
	container.Install(FromAssembly.This());
}

Kritickým a zároveň podezření-hodným místem je právě řádek s installem. Rychlý náhled do zdrojáků FromAssembly.This() a konzultace s MSDN ohledně Assembly.GetCallingAssembly potvrzuje podezření na inlining a zmatečné určení assembly s installery. Metoda ConfigureForWebApplication() byla inlinována a GetCallingAssembly() tak vracelo jako assembly tu, v které je Global.asax:Application_Start().

Action

Náprava je naštěstí jednoduchá, metodu ConfigureForWebApplication() je potřeba označit atributem MethodIml pro compiler tak, aby inlining neproběhl:

[MethodImpl(MethodImplOptions.NoInlining)]
public static void ConfigureForWebApplication(this IWindsorContainer container)
{
	container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel));

	container.AddFacility();

	container.Install(FromAssembly.This());
}

Alternativně lze samozřejmě zdrojovou assembly určit jiným determinističtějším způsobem (FromAssembly.jinak()).