Question

I've setup a Quartz as a windows service (using AdoJobStore if it matters) and have managed to get a ASP.NET site to communication with it via remoting to add/schedule a custom IJob that lives in my own project Company.Project.ServiceLayer.

This took me a while but all works OK on my dev machine provided the Company.Project.ServiceLayer.dll exists in both the windows service folder and the websites bin folder. However in the production environment there is a requirement to have the Company.Project.ServiceLayer.dll added to the GAC. I've tried to simulate this on my dev machine, removing the Company.Project.ServiceLayer.dll from the services folder and adding it into the GAC using gacutil. Unforunately now when the service starts up and tries to instanciate my IJob instance it is unable to load the assembly from the GAC and throws an exception "Could not load file or assembly" (full details below).

I'm guessing Quartz uses reflection along with the DB value in the [QRTZ_JOB_DETAILS].[JOB_CLASS_NAME] field to try and load the class? I could be totally wrong but since this value ("Company.Product.ServiceLayer.SchedulerJobs.QuintilesEasyJob, Company.Product.ServiceLayer") is only a partial description of the class (its missing the version and public key token) doesn't that mean that .NET won't look in the GAC when it does the reflection coz only strong named dll's can live there? Is that the reason my GAC setup is now failing? Could a Quartz windows service ever work with dll's in the GAC?

Any assistance is greatly appreciated.


2012-10-30 11:20:20,203 [4560] ERROR Topshelf.Model.ServiceCoordinator.OnServiceFault(:0) - Fault on quartz.server: Topshelf.Exceptions.ServiceControlException
Service Start Exception: quartz.server (IQuartzServer)
   at Topshelf.Builders.LocalServiceBuilder`1.StartService(T service)
   at Topshelf.Model.LocalServiceController`1.CallAction[TBefore,TComplete](String text, Action`1 callback, Func`1 before, Func`1 complete)
HelpLink:

Quartz.SchedulerConfigException
Failure occured during job recovery.
   at Quartz.Impl.AdoJobStore.JobStoreSupport.SchedulerStarted() in c:\Work\OpenSource\quartznet\src\Quartz\Impl\AdoJobStore\JobStoreSupport.cs:line 591
   at Quartz.Core.QuartzScheduler.Start() in c:\Work\OpenSource\quartznet\src\Quartz\Core\QuartzScheduler.cs:line 440
   at Quartz.Server.QuartzServer.Start()
   at Topshelf.Builders.LocalServiceBuilder`1.StartService(T service)
HelpLink:

Quartz.JobPersistenceException
Couldn't store trigger 'default.remotelyAddedTrigger' for 'default.remotelyAddedJob' job: Could not load file or assembly 'Company.Product.ServiceLayer' or one of its dependencies. The system cannot find the file specified.
   at Quartz.Impl.AdoJobStore.JobStoreSupport.StoreTrigger(ConnectionAndTransactionHolder conn, IOperableTrigger newTrigger, IJobDetail job, Boolean replaceExisting, String state, Boolean forceState, Boolean recovering) in c:\Work\OpenSource\quartznet\src\Quartz\Impl\AdoJobStore\JobStoreSupport.cs:line 1064
   at Quartz.Impl.AdoJobStore.JobStoreSupport.DoUpdateOfMisfiredTrigger(ConnectionAndTransactionHolder conn, IOperableTrigger trig, Boolean forceState, String newStateIfNotComplete, Boolean recovering) in c:\Work\OpenSource\quartznet\src\Quartz\Impl\AdoJobStore\JobStoreSupport.cs:line 876
   at Quartz.Impl.AdoJobStore.JobStoreSupport.RecoverMisfiredJobs(ConnectionAndTransactionHolder conn, Boolean recovering) in c:\Work\OpenSource\quartznet\src\Quartz\Impl\AdoJobStore\JobStoreSupport.cs:line 814
   at Quartz.Impl.AdoJobStore.JobStoreSupport.RecoverJobs(ConnectionAndTransactionHolder conn) in c:\Work\OpenSource\quartznet\src\Quartz\Impl\AdoJobStore\JobStoreSupport.cs:line 760
   at Quartz.Impl.AdoJobStore.JobStoreSupport.<>c__DisplayClass74.<ExecuteInNonManagedTXLock>b__73(ConnectionAndTransactionHolder conn) in c:\Work\OpenSource\quartznet\src\Quartz\Impl\AdoJobStore\JobStoreSupport.cs:line 3411
   at Quartz.Impl.AdoJobStore.JobStoreSupport.ExecuteInNonManagedTXLock(String lockName, Func`2 txCallback) in c:\Work\OpenSource\quartznet\src\Quartz\Impl\AdoJobStore\JobStoreSupport.cs:line 3481
   at Quartz.Impl.AdoJobStore.JobStoreSupport.SchedulerStarted() in c:\Work\OpenSource\quartznet\src\Quartz\Impl\AdoJobStore\JobStoreSupport.cs:line 581
HelpLink:

System.IO.FileNotFoundException
Could not load file or assembly 'Company.Product.ServiceLayer' or one of its dependencies. The system cannot find the file specified.
   at System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMarkHandle stackMark, IntPtr pPrivHostBinder, Boolean loadTypeFromPartialName, ObjectHandleOnStack type)
   at System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean loadTypeFromPartialName)
   at System.Type.GetType(String typeName, Boolean throwOnError)
   at Quartz.Simpl.SimpleTypeLoadHelper.LoadType(String name) in c:\Work\OpenSource\quartznet\src\Quartz\Simpl\SimpleTypeLoadHelper.cs:line 51
   at Quartz.Impl.AdoJobStore.StdAdoDelegate.SelectJobDetail(ConnectionAndTransactionHolder conn, JobKey jobKey, ITypeLoadHelper loadHelper) in c:\Work\OpenSource\quartznet\src\Quartz\Impl\AdoJobStore\StdAdoDelegate.cs:line 788
   at Quartz.Impl.AdoJobStore.JobStoreSupport.StoreTrigger(ConnectionAndTransactionHolder conn, IOperableTrigger newTrigger, IJobDetail job, Boolean replaceExisting, String state, Boolean forceState, Boolean recovering) in c:\Work\OpenSource\quartznet\src\Quartz\Impl\AdoJobStore\JobStoreSupport.cs:line 1041
Was it helpful?

Solution 2

I finally sussed this one out myself. I was able to get the service to look in the GAC by adding a partial name to full/strong name mapping in the app.config like so:

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <qualifyAssembly partialName="Company.Product.ServiceLayer" fullName="Company.Product.ServiceLayer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=125c3f237e3350e4"/>
    </assemblyBinding>
  </runtime>

OTHER TIPS

There's an extension point available on StdAdoDelegate for this, method GetStorableJobTypeName can be overridden to include version and strong name information. Currently there is no configuration switch to change behavior so you need to extend SqlServerDelegate (if using SQL Server).

So new version in your derived class would be something along the lines:

protected override string GetStorableJobTypeName(Type jobType)
{
    return jobType.AssemblyQualifiedName;
}

By default Quartz only uses assembly qualified name with only type and assembly name to lessen versioning problems. Be aware of them though, when version info is included in type you need to manually fix database data or create assembly version redirect if dll version number changes.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top