When I try to trigger a method which accepts MailMessage as the input parameter, job does not run and keeps failing.
Here's the code
--Hangfire call
Hangfire.BackgroundJob.Enqueue(() => FireSMTPSend(mailMessage));
--Method that is triggered
public static void FireSMTPSend(MailMessage mailMessage)
{
using (var smtp = new SmtpClient())
{
smtp.Send(mailMessage);
}
}
Here's the error that has been logged
Failed
Can not change the state to 'Processing': target method was not found.
Newtonsoft.Json.JsonSerializationExceptionUnable to find a constructor to use for type System.Net.Mail.MailAddress. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'To[0].DisplayName', line 1, position 172.
Newtonsoft.Json.JsonSerializationException: Unable to find a constructor to use for type System.Net.Mail.MailAddress. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'To[0].DisplayName', line 1, position 172.
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(IList list, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
at Hangfire.Common.JobHelper.FromJson(String value, Type type)
at Hangfire.Storage.InvocationData.DeserializeArgument(String argument, Type type)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Hangfire.Storage.InvocationData.DeserializeArgument(String argument, Type type)
at Hangfire.Storage.InvocationData.DeserializeArguments(MethodInfo methodInfo, String[] arguments)
at Hangfire.Storage.InvocationData.Deserialize()
However If I change the same method call to something like given below, it is working
--Trigger call to HangFire
Hangfire.BackgroundJob.Enqueue(() => FireSMTPSend(mailMessage.To[0].Address.ToString(),mailMessage.Body));
--Method being consumed by Hangfire
public static void FireSMTPSend(string To,string mailBody)
{
var mailMessage = new MailMessage();
mailMessage.To.Add(new MailAddress(To));
mailMessage.Body = mailBody;
using (var smtp = new SmtpClient())
{
smtp.Send(mailMessage);
}
}
It would seem NewtonJson is not able to serialize the MailMessage method. Is there a workaround for this ?
As a best practice, pass only simple types/structures which you fully control.
Avoid passing complex objects originating from .net framework itself or from 3rd party libraries, as they may serialize differently across versions, or they may use custom serializers you don't control/have access to, or they may be not serializable at all.
Also avoid using types without a default constructor, as some conditions must be met for them to successfully deserialize from JSON.
Serializing MailMessage is a pain. If you really need to, this piece of code can serialize to and from a string, and you would then pass that string to your enqueue method and then deserialize back to a MailMessage inside your job
https://github.com/burningice2866/CompositeC1Contrib.Email/blob/master/Email/MailMessageSerializer.cs#L70
https://github.com/burningice2866/CompositeC1Contrib.Email/tree/master/Email/Serialization
@pieceofsummer Thank You :)
@burningice2866 Thank you for the response :). I ended up calling hangfire one level up where I have primitive datatypes as the method parameters which is working fine.
Most helpful comment
Serializing MailMessage is a pain. If you really need to, this piece of code can serialize to and from a string, and you would then pass that string to your enqueue method and then deserialize back to a MailMessage inside your job
https://github.com/burningice2866/CompositeC1Contrib.Email/blob/master/Email/MailMessageSerializer.cs#L70
https://github.com/burningice2866/CompositeC1Contrib.Email/tree/master/Email/Serialization