Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mixing .NET Framework and .NET Core #2400

Open
terryaney opened this issue May 6, 2024 · 5 comments
Open

Mixing .NET Framework and .NET Core #2400

terryaney opened this issue May 6, 2024 · 5 comments

Comments

@terryaney
Copy link

I have an abnormal (temporary) environment. My original implementation was a Windows Service hosting Hangfire as the job processing host, written in .NET Framework.

Then, I had .NET Framework sites/apis that used something like:

var hangfireId = Hangfire.BackgroundJob.Enqueue(
      () => new JobInvoker().Invoke(
             jobName,
             inputPackage,
             null,
             HF.JobCancellationToken.Null )
);

To perform a fire/forget job.

We are slowly migrating everything to .NET Core, but need to be in 'mixed' environment until migration is complete. I was hoping to upgrade only an API to .NET Core and have it leverage the existing .NET Framework Windows Service.

In my .NET Core Api project, I referenced my .NET Framework assembly containing JobInvoker and use the same line as above. My intention was for it to simply schedule the job into the Hangfire SQL db and my .NET Framework Windows Service would pick it up and process it. But after Enqueue call, the job fails with this exception:

{
"FailedAt": "2024-05-06T20:49:03.0362835Z",
"ExceptionType": "System.IO.FileNotFoundException",
"ExceptionMessage": "Could not load file or assembly 'System.Private.Xml.Linq, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. The system cannot find the file specified.",
"ExceptionDetails": "
System.IO.FileNotFoundException: Could not load file or assembly 'System.Private.Xml.Linq, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. The system cannot find the file specified.
      File name: 'System.Private.Xml.Linq, PublicKeyToken=cc7b13ffcd2ddd51'
       at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
       at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
       at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
       at System.Reflection.Assembly.Load(AssemblyName assemblyRef)
       at Hangfire.Common.TypeHelper.AssemblyResolver(String assemblyString)
       at System.Collections.Concurrent.ConcurrentDictionary2.GetOrAdd(TKey key, Func2 valueFactory)
       at Hangfire.Common.TypeHelper.CachedAssemblyResolver(AssemblyName assemblyName)
       at System.TypeNameParser.ResolveAssembly(String asmName, Func2 assemblyResolver, Boolean throwOnError, StackCrawlMark& stackMark)        at System.TypeNameParser.ConstructType(Func2 assemblyResolver, Func4 typeResolver, Boolean throwOnError, Boolean ignoreCase, StackCrawlMark& stackMark)        at System.TypeNameParser.GetType(String typeName, Func2 assemblyResolver, Func4 typeResolver, Boolean throwOnError, Boolean ignoreCase, StackCrawlMark& stackMark)        at System.Type.GetType(String typeName, Func2 assemblyResolver, Func4 typeResolver, Boolean throwOnError)        at Hangfire.Common.TypeHelper.DefaultTypeResolver(String typeName)        at System.Linq.Enumerable.WhereSelectArrayIterator2.MoveNext()
       at System.Linq.Buffer1..ctor(IEnumerable1 source)
       at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
       at Hangfire.Storage.InvocationData.DeserializeJob()
      
      === Pre-bind state information ===
      LOG: DisplayName = System.Private.Xml.Linq, PublicKeyToken=cc7b13ffcd2ddd51
       (Partial)
      WRN: Partial binding information was supplied for an assembly:
      WRN: Assembly Name: System.Private.Xml.Linq, PublicKeyToken=cc7b13ffcd2ddd51 | Domain ID: 1
      WRN: A partial bind occurs when only part of the assembly display name is provided.
      WRN: This might result in the binder loading an incorrect assembly.
      WRN: It is recommended to provide a fully specified textual identity for the assembly,
      WRN: that consists of the simple name, version, culture, and public key token.
      WRN: See whitepaper http://go.microsoft.com/fwlink/?LinkId=109270 for more information and common solutions to this issue.
      LOG: Appbase = file:///C:/BTR/Evolution/Services/BTR.Evolution.Service/bin/Debug/
      LOG: Initial PrivatePath = NULL
      Calling assembly : Hangfire.Core, Version=1.7.30.0, Culture=neutral, PublicKeyToken=null.
      ===
      LOG: This bind starts in default load context.
      LOG: Using application configuration file: C:\BTR\Evolution\Services\BTR.Evolution.Service\bin\Debug\BTR.Evolution.Service.exe.Config
      LOG: Using host configuration file:
      LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
      LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
      LOG: The same bind was seen before, and was failed with hr = 0x80070002."
}

It seems like it is trying to resolve .NET Core LINQ to Xml objects? I could be wrong, but the namespace of 'System.Private.Xml.Linq' was not something I was used to seeing in .NET Framework.

Is there a way to do what I'm trying to do? Or a workaround I can perform for this non-standard setup I'm stuck in for a while?

@odinserj
Copy link
Member

odinserj commented May 7, 2024

A lot of logic was added to ensure smooth co-existence of .NET Framework and .NET Core servers at the same time, to be able to create and consume jobs from each other. However, I don't remember anything regarding Linq, since these types aren't something usually used as background job parameters.

How does your background job method look like, can you send its full signature, including parameter types you are using to reproduce the behavior?

@terryaney
Copy link
Author

This Hangfire implementation/framework of ours was written 10 years ago maybe, lol. Looking at my JobInvoker method and trying to figure out why some of the things done are being done, but here is the signature:

public void Invoke( string jobName, XElement inputPackage, PerformContext performContext, IJobCancellationToken cancellationToken )

I'm not in a position to be able to modify that. I could maybe add a string inputPackage overload if that would help?

@odinserj
Copy link
Member

odinserj commented May 9, 2024

I'm not in a position to be able to modify that. I could maybe add a string inputPackage overload if that would help?

Perhaps this would be the best solution as for now, since contents of an XElement is string, but I will check the binding anyway.

@terryaney
Copy link
Author

FYI...probably totally going against what I am supposed to do but hopefully temporary. Having weird issue with integration testing with my mix of Core and Framework. With all the problems I'm having, until I can I migrate to .NET Core completely I think I'm going to just directly inject the row into the Hangfire Sql DB then let the HF server of mine pick up the job. How crazy am I? lol

@terryaney
Copy link
Author

Here's what I ended up doing as a work around until I properly move everything to latest/greatest .NET Core/HangFire

		var qb = cn.QueryBuilder( $@"
			set xact_abort on; 
			set nocount on; 
			declare @jobId bigint;
			declare @stateId bigint;
			begin tran;

			insert into HangFire.Job (InvocationData, Arguments, CreatedAt, ExpireAt) 
			values ({invocationData:nvarchar(max)}, {arguments:nvarchar(max)}, {now}, DATEADD(day, 1, {now}));

			select @jobId = scope_identity(); 

			insert into HangFire.JobParameter (JobId, Name, Value) 
			values (@jobId, 'CurrentCulture', '""en-US""'), (@jobId, 'CurrentUICulture', '""en-US""');

			insert into HangFire.State (JobId, Name, CreatedAt, Data) 
			values (@jobId, 'Enqueued', {now}, {state:nvarchar(max)})

			select @stateId = scope_identity(); 

			UPDATE HangFire.Job 
			SET StateId = @stateId, StateName = 'Enqueued'
			WHERE Id = @jobId

			INSERT INTO HangFire.JobQueue (JobId, Queue) VALUES (@jobId, 'default' )

			commit tran;

			SELECT @jobId
		" );

		var hangfireId = await qb.QueryFirstAsync<int>( cancellationToken: c );

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants