csharp-sparkpost icon indicating copy to clipboard operation
csharp-sparkpost copied to clipboard

RuntimeBinderException

Open ManuLin opened this issue 9 years ago • 9 comments

I am receiving a number of RuntimeBinderExceptions upon sending an e-mail via "Transmission.Send(...)":

Exception thrown: 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' in Microsoft.CSharp.dll Additional information: Newtonsoft.Json.Linq.JObject does not contain a definition for results. Additional information: Newtonsoft.Json.Linq.JObject does not contain a definition for id. Additional information: Newtonsoft.Json.Linq.JObject does not contain a definition for total_accepted_recipients. Additional information: Newtonsoft.Json.Linq.JObject does not contain a definition for total_rejected_recipients.

Additional information: The type Newtonsoft.Json.Linq.JValue cannot be converted to a string implicitely (missing cast?)

I'm using the latest SparkPost version (1.1.1) and Newtonsoft.Json 8.0.3. Despite the exceptions being raised, sending the e-mails works as expected. Any suggestions?

ManuLin avatar Apr 25 '16 06:04 ManuLin

Can you send a code sample? Just remove your API key.

darrencauthon avatar Apr 25 '16 12:04 darrencauthon

        // Create API client
        var client = new Client(apiKey);
        // Construct transmission object
        var transmission = new Transmission();
        transmission.Content.TemplateId = "templateId";
        transmission.Content.Subject = "Subject";
        transmission.Recipients.Add(new Recipient { Address = new Address { Email = receiver } });

        transmission.SubstitutionData["historyPositions"] = items;
        transmission.SubstitutionData["isSingleItem"] = numItems == 1;
        transmission.SubstitutionData["singleItemText"] = Strings.NotificationSingleItemChanged;

        try
        {
            await client.Transmissions.Send(transmission).WithCurrentCulture();
            return true;
        }
        catch (ResponseException)
        {
            return false;
        }

Items is a collection of such objects:

public class HistoryItemElement
{
    public string CreationDate { get; set; }

    public string Field { get; set; }

    public string HistoryType { get; set; }

    public bool IsNewItem { get; set; }

    public bool IsPosition { get; set; }

    public bool IsPositionHeader { get; set; }

    public string NewValue { get; set; }

    public string Number { get; set; }

    public string OldValue { get; set; }

    public string Project { get; set; }

    public string Source { get; set; }

    public string User { get; set; }
}

ManuLin avatar Apr 25 '16 12:04 ManuLin

With this line:

await client.Transmissions.Send(transmission).WithCurrentCulture();

I'm not familiar with this method on Task<T>... what is it meant to do?

If I comment out that method call and run it as-is (but changing my template to one that I know works), the email is fired, I get a success message, and it appears in my inbox.

darrencauthon avatar Apr 25 '16 16:04 darrencauthon

Same exception here!

[Microsoft.CSharp.RuntimeBinder.RuntimeBinderException] = {"'Newtonsoft.Json.Linq.JObject' does not contain a definition for 'results'"}

My code (using SparkPost 1.3.0 and Newtonsoft.Json 6.0.5) :

client.CustomSettings.SendingMode = SendingModes.Sync;
client.Transmissions.Send(trasmission).Wait(); //--> KO: Email fired but RuntimeBinderException exception returns
//client.Transmissions.Send(trasmission);   //--> OK: Email fired, no exception returned (nor catched)

Looking at Trasmissions.cs:

public async Task<SendTransmissionResponse> Send(Transmission transmission)
{
    var request = new Request
    {
        Url = $"api/{client.Version}/transmissions",
        Method = "POST",
        Data = dataMapper.ToDictionary(transmission)
    };

    var response = await requestSender.Send(request);
    if (response.StatusCode != HttpStatusCode.OK) throw new ResponseException(response);

    var results = JsonConvert.DeserializeObject<dynamic>(response.Content).results;
    return new SendTransmissionResponse()
    {
        Id = results.id,
        ReasonPhrase = response.ReasonPhrase,
        StatusCode = response.StatusCode,
        Content = response.Content,
        TotalAcceptedRecipients = results.total_accepted_recipients,
        TotalRejectedRecipients = results.total_rejected_recipients,
    };
}

Does "JsonConvert.DeserializeObject(response.Content).results" cause the problem AFTER "requestSender.Send(request)" triggered successfully?

if2m avatar Apr 27 '16 15:04 if2m

@if2m

screenshot_042716_115715_am

Can you try including the SparkPost code as a separate project in your solution, and then debug it to try to get the answer?

Plus, I just released 1.3.0, with sync support. Do you want to try going with sync mode to see if that resolves your problem? Instructions below:

https://github.com/SparkPost/csharp-sparkpost#special-note-about-async

darrencauthon avatar Apr 27 '16 16:04 darrencauthon

The web call is not causing the problem, I agree. JsonConvert.DeserializeObject could be: property ".results" resolving at runtime may cause the exception?

if2m avatar Apr 27 '16 17:04 if2m

@if2m It could be. I'm assuming that a 200 response from SparkPost will return that information, but what if it doesn't?

I'll tell you what: We can put some additional checks around it. I'll continue to report back what I got as Content

darrencauthon avatar Apr 27 '16 17:04 darrencauthon

Issue found: the Json DLL I included was compiled for .NET 3.5 and did not support "dynamic". This code works:

public async Task<SendTransmissionResponse> Send(Transmission transmission)
{
    var request = new Request
    {
        Url = $"api/{client.Version}/transmissions",
        Method = "POST",
        Data = dataMapper.ToDictionary(transmission)
    };

    var response = await requestSender.Send(request);
    if (response.StatusCode != HttpStatusCode.OK) throw new ResponseException(response);

    /*
    //Works with JSON.NET 6.0.3 (.NET 4.5 version)
    var results = JsonConvert.DeserializeObject<dynamic>(response.Content).results;
    return new SendTransmissionResponse()
    {
        Id = results.id,
        ReasonPhrase = response.ReasonPhrase,
        StatusCode = response.StatusCode,
        Content = response.Content,
        TotalAcceptedRecipients = results.total_accepted_recipients,
        TotalRejectedRecipients = results.total_rejected_recipients,
    };
    */

    //Works with JSON.NET (.NET 3.5 version)
    var results = JsonConvert.DeserializeObject<dynamic>(response.Content)["results"];
    return new SendTransmissionResponse()
    {
        Id = results["id"].Value,
        ReasonPhrase = response.ReasonPhrase,
        StatusCode = response.StatusCode,
        Content = response.Content,
        TotalAcceptedRecipients = (int) results["total_accepted_recipients"].Value,  //(int) long
        TotalRejectedRecipients = (int) results["total_rejected_recipients"].Value,  //(int) long
    };
}

See: http://stackoverflow.com/questions/13683757/newtonsoft-json-dynamic-objects

if2m avatar May 02 '16 13:05 if2m

Ah, that's awesome, @if2m , thank you very much!

I wonder... perhaps I could switch this code to deserialize to a string object dictionary? It's something I was considering, anyway, as they seem to be easier to manage than the dynamic. I want to get the receiving of data to work via conventions as well, as lines like this are just begging to be removed:

TotalAcceptedRecipients = results.total_accepted_recipients
TotalAcceptedRecipients = (int) results["total_accepted_recipients"].Value

The DataMapper class alreday converts TotalAcceptedRecipients to total_accepted_recipients, and I want a way to go the other way.

Thank you for passing this along, I'll take a deeper look at it this week and see what I can do.

darrencauthon avatar May 02 '16 13:05 darrencauthon