Jak obsługiwać pojedynczy element i tablicę dla tej samej właściwości przy użyciu JSON.net


101

Próbuję naprawić moją bibliotekę SendGridPlus, aby radziła sobie ze zdarzeniami SendGrid, ale mam problemy z niespójnym traktowaniem kategorii w interfejsie API.

W poniższym przykładowym ładunku pobranym z dokumentacji interfejsu API SendGrid zauważysz, że categorywłaściwość dla każdego elementu może być pojedynczym ciągiem lub tablicą ciągów.

[
  {
    "email": "john.doe@sendgrid.com",
    "timestamp": 1337966815,
    "category": [
      "newuser",
      "transactional"
    ],
    "event": "open"
  },
  {
    "email": "jane.doe@sendgrid.com",
    "timestamp": 1337966815,
    "category": "olduser",
    "event": "open"
  }
]

Wygląda na to, że moje opcje, aby sprawić, że JSON.NET tak jak ten, to naprawianie ciągu przed jego pojawieniem się lub konfigurowanie JSON.NET do akceptowania niepoprawnych danych. Wolałbym nie przeprowadzać analizy ciągów, jeśli ujdzie mi to na sucho.

Czy jest jakiś inny sposób, w jaki mogę sobie z tym poradzić za pomocą Json.Net?

Odpowiedzi:


203

Najlepszym sposobem radzenia sobie z tą sytuacją jest użycie pliku niestandardowego JsonConverter.

Zanim przejdziemy do konwertera, musimy zdefiniować klasę do deserializacji danych. W przypadku Categorieswłaściwości, która może różnić się między pojedynczym elementem a tablicą, zdefiniuj ją jako a List<string>i oznacz [JsonConverter]atrybutem, aby JSON.Net wiedział, że ma użyć niestandardowego konwertera dla tej właściwości. Poleciłbym również używanie [JsonProperty]atrybutów, aby właściwościom elementów członkowskich można było nadać znaczące nazwy niezależnie od tego, co jest zdefiniowane w JSON.

class Item
{
    [JsonProperty("email")]
    public string Email { get; set; }

    [JsonProperty("timestamp")]
    public int Timestamp { get; set; }

    [JsonProperty("event")]
    public string Event { get; set; }

    [JsonProperty("category")]
    [JsonConverter(typeof(SingleOrArrayConverter<string>))]
    public List<string> Categories { get; set; }
}

Oto jak zaimplementowałbym konwerter. Zauważ, że utworzyłem konwerter generyczny, aby można go było używać z ciągami znaków lub innymi typami obiektów w razie potrzeby.

class SingleOrArrayConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(List<T>));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Array)
        {
            return token.ToObject<List<T>>();
        }
        return new List<T> { token.ToObject<T>() };
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Oto krótki program demonstrujący działanie konwertera z przykładowymi danymi:

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        [
          {
            ""email"": ""john.doe@sendgrid.com"",
            ""timestamp"": 1337966815,
            ""category"": [
              ""newuser"",
              ""transactional""
            ],
            ""event"": ""open""
          },
          {
            ""email"": ""jane.doe@sendgrid.com"",
            ""timestamp"": 1337966815,
            ""category"": ""olduser"",
            ""event"": ""open""
          }
        ]";

        List<Item> list = JsonConvert.DeserializeObject<List<Item>>(json);

        foreach (Item obj in list)
        {
            Console.WriteLine("email: " + obj.Email);
            Console.WriteLine("timestamp: " + obj.Timestamp);
            Console.WriteLine("event: " + obj.Event);
            Console.WriteLine("categories: " + string.Join(", ", obj.Categories));
            Console.WriteLine();
        }
    }
}

I na koniec, oto wynik powyższego:

email: john.doe@sendgrid.com
timestamp: 1337966815
event: open
categories: newuser, transactional

email: jane.doe@sendgrid.com
timestamp: 1337966815
event: open
categories: olduser

Skrzypce: https://dotnetfiddle.net/lERrmu

EDYTOWAĆ

Jeśli potrzebujesz pójść w drugą stronę, tj. Serializować, zachowując ten sam format, możesz zaimplementować WriteJson()metodę konwertera, jak pokazano poniżej. (Pamiętaj, aby usunąć CanWritenadpisanie lub zmienić je na powrót true, w przeciwnym razie WriteJson()nigdy nie zostanie wywołany).

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        List<T> list = (List<T>)value;
        if (list.Count == 1)
        {
            value = list[0];
        }
        serializer.Serialize(writer, value);
    }

Fiddle: https://dotnetfiddle.net/XG3eRy


5
Idealny! Jesteś mężczyzną. Na szczęście wykonałem już wszystkie inne rzeczy związane z użyciem JsonProperty, aby właściwości były bardziej znaczące. Dziękuję za niezwykle kompletną odpowiedź. :)
Robert McLaws

Nie ma problemu; cieszę się, że okazało się to pomocne.
Brian Rogers

1
Doskonały! To jest to, czego szukałem. @BrianRogers, jeśli kiedykolwiek będziesz w Amsterdamie, drinki są na mnie!
Mad Dog Tannen

2
@israelaltar Nie musisz dodawać konwertera do DeserializeObjectwywołania, jeśli używasz [JsonConverter]atrybutu we właściwości list w swojej klasie, jak pokazano w powyższej odpowiedzi. Jeśli nie używasz atrybutu, to tak, musisz przekazać konwerter do DeserializeObject.
Brian Rogers

1
@ShaunLangley Aby konwerter używał tablicy zamiast listy, zmień wszystkie odwołania na List<T>w konwerterze na T[]i zmień .Countna .Length. dotnetfiddle.net/vnCNgZ
Brian Rogers

6

Pracowałem nad tym od wieków i dziękuję Brianowi za jego odpowiedź. Wszystko, co dodam, to odpowiedź vb.net !:

Public Class SingleValueArrayConverter(Of T)
sometimes-array-and-sometimes-object
    Inherits JsonConverter
    Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
        Throw New NotImplementedException()
    End Sub

    Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
        Dim retVal As Object = New [Object]()
        If reader.TokenType = JsonToken.StartObject Then
            Dim instance As T = DirectCast(serializer.Deserialize(reader, GetType(T)), T)
            retVal = New List(Of T)() From { _
                instance _
            }
        ElseIf reader.TokenType = JsonToken.StartArray Then
            retVal = serializer.Deserialize(reader, objectType)
        End If
        Return retVal
    End Function
    Public Overrides Function CanConvert(objectType As Type) As Boolean
        Return False
    End Function
End Class

wtedy w twojej klasie:

 <JsonProperty(PropertyName:="JsonName)> _
 <JsonConverter(GetType(SingleValueArrayConverter(Of YourObject)))> _
    Public Property YourLocalName As List(Of YourObject)

Mam nadzieję, że zaoszczędzi ci to trochę czasu


Literówki: <JsonConverter (GetType (SingleValueArrayConverter (Of YourObject)))> _ Public Property YourLocalName As List (Of YourObject)
GlennG

3

Jako odmianę moll do wielkiego odpowiedzi przez Brian Rogers , tu są dwie wersje manipulowane SingleOrArrayConverter<T>.

Po pierwsze, oto wersja, która działa dla wszystkich List<T>dla każdego typu, Tktóry sam nie jest zbiorem:

public class SingleOrArrayListConverter : JsonConverter
{
    // Adapted from this answer https://stackoverflow.com/a/18997172
    // to /programming/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
    // by Brian Rogers https://stackoverflow.com/users/10263/brian-rogers
    readonly bool canWrite;
    readonly IContractResolver resolver;

    public SingleOrArrayListConverter() : this(false) { }

    public SingleOrArrayListConverter(bool canWrite) : this(canWrite, null) { }

    public SingleOrArrayListConverter(bool canWrite, IContractResolver resolver)
    {
        this.canWrite = canWrite;
        // Use the global default resolver if none is passed in.
        this.resolver = resolver ?? new JsonSerializer().ContractResolver;
    }

    static bool CanConvert(Type objectType, IContractResolver resolver)
    {
        Type itemType;
        JsonArrayContract contract;
        return CanConvert(objectType, resolver, out itemType, out contract);
    }

    static bool CanConvert(Type objectType, IContractResolver resolver, out Type itemType, out JsonArrayContract contract)
    {
        if ((itemType = objectType.GetListItemType()) == null)
        {
            itemType = null;
            contract = null;
            return false;
        }
        // Ensure that [JsonObject] is not applied to the type.
        if ((contract = resolver.ResolveContract(objectType) as JsonArrayContract) == null)
            return false;
        var itemContract = resolver.ResolveContract(itemType);
        // Not implemented for jagged arrays.
        if (itemContract is JsonArrayContract)
            return false;
        return true;
    }

    public override bool CanConvert(Type objectType) { return CanConvert(objectType, resolver); }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        Type itemType;
        JsonArrayContract contract;

        if (!CanConvert(objectType, serializer.ContractResolver, out itemType, out contract))
            throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), objectType));
        if (reader.MoveToContent().TokenType == JsonToken.Null)
            return null;
        var list = (IList)(existingValue ?? contract.DefaultCreator());
        if (reader.TokenType == JsonToken.StartArray)
            serializer.Populate(reader, list);
        else
            // Here we take advantage of the fact that List<T> implements IList to avoid having to use reflection to call the generic Add<T> method.
            list.Add(serializer.Deserialize(reader, itemType));
        return list;
    }

    public override bool CanWrite { get { return canWrite; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var list = value as ICollection;
        if (list == null)
            throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), value.GetType()));
        // Here we take advantage of the fact that List<T> implements IList to avoid having to use reflection to call the generic Count method.
        if (list.Count == 1)
        {
            foreach (var item in list)
            {
                serializer.Serialize(writer, item);
                break;
            }
        }
        else
        {
            writer.WriteStartArray();
            foreach (var item in list)
                serializer.Serialize(writer, item);
            writer.WriteEndArray();
        }
    }
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContent(this JsonReader reader)
    {
        while ((reader.TokenType == JsonToken.Comment || reader.TokenType == JsonToken.None) && reader.Read())
            ;
        return reader;
    }

    internal static Type GetListItemType(this Type type)
    {
        // Quick reject for performance
        if (type.IsPrimitive || type.IsArray || type == typeof(string))
            return null;
        while (type != null)
        {
            if (type.IsGenericType)
            {
                var genType = type.GetGenericTypeDefinition();
                if (genType == typeof(List<>))
                    return type.GetGenericArguments()[0];
            }
            type = type.BaseType;
        }
        return null;
    }
}

Można go używać w następujący sposób:

var settings = new JsonSerializerSettings
{
    // Pass true if you want single-item lists to be reserialized as single items
    Converters = { new SingleOrArrayListConverter(true) },
};
var list = JsonConvert.DeserializeObject<List<Item>>(json, settings);

Uwagi:

  • Konwerter pozwala uniknąć konieczności wstępnego ładowania całej wartości JSON do pamięci jako JTokenhierarchii.

  • Konwerter nie dotyczy list, których pozycje są również serializowane jako kolekcje, np List<string []>

  • canWriteArgument logiczny przekazany do konstruktora określa, czy należy ponownie serializować listy jednoelementowe jako wartości JSON, czy jako tablice JSON.

  • Konwerter ReadJson()używa existingValuepredefiniowanej alokacji if, aby obsługiwać zapełnianie członków listy tylko do pobierania.

Po drugie, oto wersja, która działa z innymi zbiorami ogólnymi, takimi jak ObservableCollection<T>:

public class SingleOrArrayCollectionConverter<TCollection, TItem> : JsonConverter
    where TCollection : ICollection<TItem>
{
    // Adapted from this answer https://stackoverflow.com/a/18997172
    // to /programming/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
    // by Brian Rogers https://stackoverflow.com/users/10263/brian-rogers
    readonly bool canWrite;

    public SingleOrArrayCollectionConverter() : this(false) { }

    public SingleOrArrayCollectionConverter(bool canWrite) { this.canWrite = canWrite; }

    public override bool CanConvert(Type objectType)
    {
        return typeof(TCollection).IsAssignableFrom(objectType);
    }

    static void ValidateItemContract(IContractResolver resolver)
    {
        var itemContract = resolver.ResolveContract(typeof(TItem));
        if (itemContract is JsonArrayContract)
            throw new JsonSerializationException(string.Format("Item contract type {0} not supported.", itemContract));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        ValidateItemContract(serializer.ContractResolver);
        if (reader.MoveToContent().TokenType == JsonToken.Null)
            return null;
        var list = (ICollection<TItem>)(existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
        if (reader.TokenType == JsonToken.StartArray)
            serializer.Populate(reader, list);
        else
            list.Add(serializer.Deserialize<TItem>(reader));
        return list;
    }

    public override bool CanWrite { get { return canWrite; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        ValidateItemContract(serializer.ContractResolver);
        var list = value as ICollection<TItem>;
        if (list == null)
            throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), value.GetType()));
        if (list.Count == 1)
        {
            foreach (var item in list)
            {
                serializer.Serialize(writer, item);
                break;
            }
        }
        else
        {
            writer.WriteStartArray();
            foreach (var item in list)
                serializer.Serialize(writer, item);
            writer.WriteEndArray();
        }
    }
}

Następnie, jeśli twój model używa, powiedzmy, ObservableCollection<T>dla niektórych T, możesz zastosować go w następujący sposób:

class Item
{
    public string Email { get; set; }
    public int Timestamp { get; set; }
    public string Event { get; set; }

    [JsonConverter(typeof(SingleOrArrayCollectionConverter<ObservableCollection<string>, string>))]
    public ObservableCollection<string> Category { get; set; }
}

Uwagi:

  • Oprócz uwag i ograniczeń dotyczących SingleOrArrayListConverter, TCollectiontyp musi być do odczytu / zapisu i mieć konstruktora bez parametrów.

Demo skrzypce z podstawowymi testami jednostkowymi tutaj .


0

Miałem bardzo podobny problem. Moje żądanie Json było dla mnie zupełnie nieznane. Tylko wiedziałem.

Będzie w nim identyfikator objectId i kilka par klucza anonim-wartość ORAZ tablice.

Użyłem go do modelu EAV, który zrobiłem:

Moje żądanie JSON:

{objectId ": 2," firstName ":" Hans "," email ": [" a@b.de "," a@c.de "]," name ":" Andre "," something ": [" 232 "," 123 "]}

Moja klasa zdefiniowała:

[JsonConverter(typeof(AnonyObjectConverter))]
public class AnonymObject
{
    public AnonymObject()
    {
        fields = new Dictionary<string, string>();
        list = new List<string>();
    }

    public string objectid { get; set; }
    public Dictionary<string, string> fields { get; set; }
    public List<string> list { get; set; }
}

a teraz, gdy chcę deserializować nieznane atrybuty z ich wartością i tablicami w nim, mój konwerter wygląda tak:

   public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        AnonymObject anonym = existingValue as AnonymObject ?? new AnonymObject();
        bool isList = false;
        StringBuilder listValues = new StringBuilder();

        while (reader.Read())
        {
            if (reader.TokenType == JsonToken.EndObject) continue;

            if (isList)
            {
                while (reader.TokenType != JsonToken.EndArray)
                {
                    listValues.Append(reader.Value.ToString() + ", ");

                    reader.Read();
                }
                anonym.list.Add(listValues.ToString());
                isList = false;

                continue;
            }

            var value = reader.Value.ToString();

            switch (value.ToLower())
            {
                case "objectid":
                    anonym.objectid = reader.ReadAsString();
                    break;
                default:
                    string val;

                    reader.Read();
                    if(reader.TokenType == JsonToken.StartArray)
                    {
                        isList = true;
                        val = "ValueDummyForEAV";
                    }
                    else
                    {
                        val = reader.Value.ToString();
                    }
                    try
                    {
                        anonym.fields.Add(value, val);
                    }
                    catch(ArgumentException e)
                    {
                        throw new ArgumentException("Multiple Attribute found");
                    }
                    break;
            }

        }

        return anonym;
    }

Więc teraz za każdym razem, gdy otrzymuję AnonymObject, mogę iterować po Słowniku i za każdym razem, gdy pojawia się moja flaga „ValueDummyForEAV”, przełączam się na listę, czytam pierwszą linię i dzielę wartości. Następnie usuwam pierwszy wpis z listy i przechodzę do iteracji ze Słownika.

Może ktoś ma ten sam problem i może to wykorzystać :)

Pozdrawiam Andre


0

Możesz użyć JSONConverterAttributeznalezionego tutaj: http://james.newtonking.com/projects/json/help/

Zakładając, że masz klasę, która wygląda jak

public class RootObject
{
    public string email { get; set; }
    public int timestamp { get; set; }
    public string smtpid { get; set; }
    public string @event { get; set; }
    public string category[] { get; set; }
}

Udekorowałbyś właściwość kategorii, jak widać tutaj:

    [JsonConverter(typeof(SendGridCategoryConverter))]
    public string category { get; set; }

public class SendGridCategoryConverter : JsonConverter
{
  public override bool CanConvert(Type objectType)
  {
    return true; // add your own logic
  }

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
   // do work here to handle returning the array regardless of the number of objects in 
  }

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
    // Left as an exercise to the reader :)
    throw new NotImplementedException();
  }
}

Dzięki za to, ale to nadal nie rozwiązuje problemu. Kiedy pojawia się rzeczywista tablica, nadal generuje błąd, zanim mój kod będzie mógł zostać wykonany dla obiektu, który ma rzeczywistą tablicę. Dodatkowe informacje: nieoczekiwany token podczas deserializacji obiektu: ciąg. Ścieżka „[2] .category [0]”, wiersz 17, pozycja 27. ”
Robert McLaws

+ "\" zdarzenie \ ": \" przetworzone \ ", \ n" + "} \ n" + "]";
Robert McLaws

Pierwszy obiekt przetworzył dobrze i pięknie poradził sobie z żadną tablicą. Ale kiedy utworzyłem tablicę dla drugiego obiektu, nie udało się.
Robert McLaws

@AdvancedREI Nie widząc twojego kodu, domyślam się, że po przeczytaniu JSON zostawiasz czytnik nieprawidłowo ustawiony. Zamiast próbować używać czytnika bezpośrednio, lepiej jest załadować obiekt JToken z czytnika i przejść stamtąd. Zobacz moją odpowiedź na działającą implementację konwertera.
Brian Rogers,

O wiele więcej szczegółów w odpowiedzi Briana. Użyj tego :)
Tim Gabrhel

0

Aby sobie z tym poradzić, musisz użyć niestandardowego JsonConverter. Ale prawdopodobnie miałeś już to na myśli. Po prostu szukasz konwertera, którego możesz użyć od razu. A to oferuje więcej niż tylko rozwiązanie opisanej sytuacji. Podaję przykład z zadanym pytaniem.

Jak korzystać z mojego konwertera:

Umieść atrybut JsonConverter powyżej właściwości. JsonConverter(typeof(SafeCollectionConverter))

public class SendGridEvent
{
    [JsonProperty("email")]
    public string Email { get; set; }

    [JsonProperty("timestamp")]
    public long Timestamp { get; set; }

    [JsonProperty("category"), JsonConverter(typeof(SafeCollectionConverter))]
    public string[] Category { get; set; }

    [JsonProperty("event")]
    public string Event { get; set; }
}

A to mój konwerter:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;

namespace stackoverflow.question18994685
{
    public class SafeCollectionConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return true;
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            //This not works for Populate (on existingValue)
            return serializer.Deserialize<JToken>(reader).ToObjectCollectionSafe(objectType, serializer);
        }     

        public override bool CanWrite => false;

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
}

A ten konwerter używa następującej klasy:

using System;

namespace Newtonsoft.Json.Linq
{
    public static class SafeJsonConvertExtensions
    {
        public static object ToObjectCollectionSafe(this JToken jToken, Type objectType)
        {
            return ToObjectCollectionSafe(jToken, objectType, JsonSerializer.CreateDefault());
        }

        public static object ToObjectCollectionSafe(this JToken jToken, Type objectType, JsonSerializer jsonSerializer)
        {
            var expectArray = typeof(System.Collections.IEnumerable).IsAssignableFrom(objectType);

            if (jToken is JArray jArray)
            {
                if (!expectArray)
                {
                    //to object via singel
                    if (jArray.Count == 0)
                        return JValue.CreateNull().ToObject(objectType, jsonSerializer);

                    if (jArray.Count == 1)
                        return jArray.First.ToObject(objectType, jsonSerializer);
                }
            }
            else if (expectArray)
            {
                //to object via JArray
                return new JArray(jToken).ToObject(objectType, jsonSerializer);
            }

            return jToken.ToObject(objectType, jsonSerializer);
        }

        public static T ToObjectCollectionSafe<T>(this JToken jToken)
        {
            return (T)ToObjectCollectionSafe(jToken, typeof(T));
        }

        public static T ToObjectCollectionSafe<T>(this JToken jToken, JsonSerializer jsonSerializer)
        {
            return (T)ToObjectCollectionSafe(jToken, typeof(T), jsonSerializer);
        }
    }
}

Co to dokładnie robi? Jeśli umieścisz atrybut konwertera, konwerter będzie używany dla tej właściwości. Możesz go użyć na normalnym obiekcie, jeśli spodziewasz się tablicy json z 1 lub bez wyniku. Lub używasz go w miejscu, w IEnumerablektórym oczekujesz obiektu json lub tablicy json. (Wiedz, że array- object[]- jest IEnumerable) Wadą jest to, że ten konwerter można umieścić tylko nad właściwością, ponieważ uważa, że ​​może przekształcić wszystko. I uważaj . A stringjest również IEnumerable.

I oferuje więcej niż odpowiedź na pytanie: jeśli szukasz czegoś według id, wiesz, że otrzymasz tablicę z jednym wynikiem lub bez. PlikToObjectCollectionSafe<TResult>() metoda poradzi sobie z tym za Ciebie.

Jest to przydatne w przypadku pojedynczego wyniku w porównaniu z tablicą przy użyciu JSON.net i obsługuje zarówno pojedynczy element, jak i tablicę dla tej samej właściwości i może konwertować tablicę na pojedynczy obiekt.

Zrobiłem to dla żądań REST na serwerze z filtrem, który zwrócił jeden wynik w tablicy, ale chciałem otrzymać wynik z powrotem jako pojedynczy obiekt w moim kodzie. A także dla odpowiedzi wynikowej OData z rozszerzonym wynikiem z jednym elementem w tablicy.

Baw się dobrze.


-2

Znalazłem inne rozwiązanie, które może obsłużyć kategorię jako ciąg lub tablicę za pomocą obiektu. W ten sposób nie muszę mieszać z serializatorem json.

Popatrz, jeśli masz czas, i powiedz mi, co myślisz. https://github.com/MarcelloCarreira/sendgrid-csharp-eventwebhook

Opiera się na rozwiązaniu na https://sendgrid.com/blog/tracking-email-using-azure-sendgrid-event-webhook-part-1/ ale dodałem także konwersję daty z sygnatury czasowej, zaktualizowałem zmienne, aby odzwierciedlały aktualny model SendGrid (i działające kategorie).

Stworzyłem również handler z podstawową autoryzacją jako opcją. Zobacz pliki ashx i przykłady.

Dziękuję Ci!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.