Hangfire does not serialize MailMessage

Created on 16 Sep 2016  ·  3Comments  ·  Source: HangfireIO/Hangfire

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.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.

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 ?

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

All 3 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cbmek picture cbmek  ·  3Comments

cindro picture cindro  ·  3Comments

jeffsugden picture jeffsugden  ·  4Comments

nathvi picture nathvi  ·  4Comments

abdelrady picture abdelrady  ·  4Comments