Just added HangFire to my MVC project. I have a Service Layer which has an interface and then the implementation. I have using SimpleInjector for DI and my Controller has a Dependency on this interface as below:
So this service takes a list of car objects my user has uploaded and passes them off to my Service which is an abstraction of a 3rd Party External Web Service were I go and look up information on the cars and write response to DB
However - if I check on HangFire Dashboard after I run the Controller Method I am getting the following - has anyone else come across this and is there something I need to change to get this working correctly?
Failed An exception occured during job activation.
System.MissingMethodException
Cannot create an instance of an interface.
System.MissingMethodException: Cannot create an instance of an interface.
at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at System.Activator.CreateInstance(Type type)
at HangFire.JobActivator.ActivateJob(Type jobType)
at HangFire.Common.Job.Activate(JobActivator activator)
HangFire uses JobActivator class to create instances of your classes, in your example it is the FileImportController. Default JobActivator implementation can construct only classes with default constructors, and this is the default behavior for ASP.NET MVC’s dependency resolver either.
To be able to use non-default constructor, you should empower HF with the IoC container:
If you are using another IoC container, don’t worry, just implement its support by example, and use it in your code JobActivator.Current = new YourImplementation().
P.S. You can fix this, re-build the application and try the Retry button on a previously failed job.
Also be sure that you don’t use the request-related data (request, response, cookies, current user and so on) in your jobs, because it is not available there. You are able to pass the needed parameters through arguments if you need them.
Many thanks for quick response - I am already using SimpleInjector IOC in my project so I got the HangFire NuGet package for SimpleInjector - can you include an example of how I would use it in my Controller correctly? No I dont need to pass any Request related data - my car will contain list of Car Objects which is a POCO - containing string Name; string FuelType etc
Regarding to IoC container usage – you only need to install it (nuget package link is available in the repository read me file) and register it in the global.asax.cs file:
protected void Application_Start()
{
// ... some initialization logic
var container = /* I don't know how to initialize the SimpleInjector container */
// Register types in the container, but NOT IN REQUEST SCOPE!
JobActivator.Current = new SimpleInjectorJobActivator(container);
}
@devmondo, can you give an example of HangFire.SimpleInjector usage?
There is another issue with parameters - it is better to pass cars entity identifier, you can read more in the Are your methods ready to run in background? blog post to get some more details.
Failed An exception occured during arguments deserialization.
System.NotSupportedException
CollectionConverter cannot convert from System.String.
System.NotSupportedException: CollectionConverter cannot convert from System.String.
at System.ComponentModel.TypeConverter.GetConvertFromException(Object value)
at System.ComponentModel.TypeConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
at System.ComponentModel.TypeConverter.ConvertFromInvariantString(String text)
at HangFire.Common.Job.DeserializeArguments()
So I take it HangFire cannot Deserialize my cars object - from Reading your BlogPost I should write a custom TypeConverter for cars object correct?
What I am not sure of is where this TypeConvert should ‘live’ in my code base? As part of HangFireConfig,cs?
Best solution for this for anyone reading is to create a wrapper entity class which has List of your Entitys and a single ID which you can pass in the ID of the wrapper entity class
Yeah it will have an ID associated with it - but this is generated by the DB when the Car is added to it. Basically how I require this to work at the minute - is a User passes a list of cars to the controller. The list of cars then is passed to my webservice which calls the 3rd party web service to get some details about all the cars passed it and as part of this method on My Service Layer response details from the web service will be written to the DB. So I may have a List of for e.g 30 cars being passed to the method - if I don’t have IDs for the object at this point what is the best way to handle that?
But consider to use the scenario, where you create entities in a controller method and pass their identifiers to a background job method. For example, HangFire.Highlighter uses the following process.
Controller
Creates a new record with SourceCode property received from a user, and HighlightedCode property set to null.
Pushes an identifier acquired at step 1 to the background job.
Background job
Fetches a source code snippet by identifier specified in an argument.
Calls external web service to receive highlighted code on a basis of fetched SourceCode.
Sets the entity’s HighlightedCode property and updates a record in DB.
User perspective
If she opens a details page, and there are no value for HighlightedCode, then we need to show that the work is still in progress and poll a server (or use server push with SignalR) for changes. Meanwhile we can show non-highlighted code.
If she opens a page, and HighlightedCode property is not null – we just show it.
Your application
You can create a new entity, say, QueuedCars. Each entity contains a collection of cars that needs to be queried for additional data. Put there a collection of all cars from controller, receive its identifier and push it to background job method argument.
Yes I think you are correct and it would be better to create entity in controller method and then pass the id to background job. What I am still a little unsure of though is:
If I create 30 new car entitys on controller and save them to DB -
//30 cars saved to DB here
BackgroundJob.Enqueue(() => _myService.AddCars(car.Id));
My current signature for AddCars is as:
void AddCars(List<Car> cars);
I guess my question is what is the best way to pass in all 30 car Ids into the AddCars method at once and then change the signature? I was thinking I could wrap the BackgroundJob.Enqueue in a forach loop and chand my method signature of AddCars to be AddCars(string carId); - but this does not seem to be the most elegant solution?
It depends on a web service API. If it provides a coarse grained API, so that you can ask for 30 cars in one web service call, then it is better to wrap this call to a single job. And it is better to wrap the collection with another entity, so that you create a single entity with all 30 cars inside it, pass the single id to background job, and it receives by this id all the 30 cars.
If it provides only fine grained API, when for each car you should issue a single request, it is may want to create 30 jobs - they will run in parallel.
Cool - I will probably have further Questions as I go along as well so expect new discussion threads from me - but at some point I could do everything I have learned up starting from scratch and email it too yourself and you could include it under an FAQ or Tutorial section if you wish