Laden...

WCF Service JSON DataContractJsonSerializer - Dictionary

Erstellt von Pedro_15 vor 7 Jahren Letzter Beitrag vor 7 Jahren 6.848 Views
P
Pedro_15 Themenstarter:in
375 Beiträge seit 2005
vor 7 Jahren
WCF Service JSON DataContractJsonSerializer - Dictionary

Hallo,

habe ein Problem mit JSON in Verbindung mit Dictionary Objekt.

Der WCF Service serialisiert das Dictionary Objekt:


[{
		"Key":"Zeichenfolgeninhalt",
		"Value":"Zeichenfolgeninhalt"
	}]

Ich brauche aber {Key-Wert:Value-Wert} .
Im Internet habe ich gefunden, das man ein Parameter UseSimpleDictionaryFormat in DataContractJsonSerializerSettings setzen kann.
Leider finde ich keinen Weg den Parameter zu setzen.

Ich habe ja kein Code wo ich ansetzen kann, denn die Serialisierung macht ja der WCF Service intern für mich.

Kennt jemand das Problem und kann mir helfen?

Danke!

Pedro

16.806 Beiträge seit 2008
vor 7 Jahren

[] ist das Zeichen für ein Array. Du wirst also an der Stelle aktuell ein Array zurück geben.
Gib ein simples Objekt zurück, dann hast Du direkt {}

P
Pedro_15 Themenstarter:in
375 Beiträge seit 2005
vor 7 Jahren

Hallo,

viellecht war es ein wenig kurz das Beispiel:

es geht um den Type Dictionary<string,string>

er wird im Standard WCF so serializiert.

[{
        "Key":"Key1",
        "Value":"Value1"
    },{
        "Key":"Key2",
        "Value":"Value2"
    }]

er soll aber

{
        "Key1":"Value1",
        "Key2":"Value2"
    }

so aussehen.

Dafür soll man den Parameter UseSimpleDictionaryFormat auf true setzen.

Die Frage ist nur wo.

Gruss!

Pedro

H
523 Beiträge seit 2008
vor 7 Jahren

Schau Dir mal die Klasse DataContractJsonSerializerSettings an:

    
DataContractJsonSerializerSettings settings = new DataContractJsonSerializerSettings();
settings.UseSimpleDictionaryFormat = true;

P
Pedro_15 Themenstarter:in
375 Beiträge seit 2005
vor 7 Jahren

Die Klasse ist mir bekannt. Ich hatte ja geschrieben:

Im Internet habe ich gefunden, das man ein Parameter UseSimpleDictionaryFormat in DataContractJsonSerializerSettings setzen kann.
Leider finde ich keinen Weg den Parameter zu setzen.

Ich habe ja kein Code wo ich ansetzen kann, denn die Serialisierung macht ja der WCF Service intern für mich.

Genau das ist die Frage, wo kann ich das für den internen DataContractJsonSerializer setzen?

Danke!

P
Pedro_15 Themenstarter:in
375 Beiträge seit 2005
vor 7 Jahren

Danke, aber Google habe ich auch. Sorry!
Aber ich habe jetzt schon zwei Tage im Internet zu gebracht.

Es kann sein, dass das zu einer Lösung führt, aber ich habe es so nicht hinbekommen.
Ich möchte auch nicht den ganzen Standard umhauen, es sollen immer noch alle anderen serialisierungen funktionieren.

Die Funktion (ab .net 4.5) ist relativ neu und der Beitrag schon älter. Ich kann mir nicht vorstellen, dass es so aufwendig ist, diesen Parameter zu setzen.

Aber danke für die Hilfe!

H
523 Beiträge seit 2008
vor 7 Jahren

Genau das ist die Frage, wo kann ich das für den internen DataContractJsonSerializer setzen?

Sorry überlesen, einmal googlen hätte Dir (wie Abt bereits geschrieben hat) das gewünsche Ergebnis gebracht:


    DataContractJsonSerializer serializer = 
            new DataContractJsonSerializer(typeof(CustomObject), settings);


P
Pedro_15 Themenstarter:in
375 Beiträge seit 2005
vor 7 Jahren

Sorry, verstehe die Antwort nicht.

Wo soll den der Code hin?

P
Pedro_15 Themenstarter:in
375 Beiträge seit 2005
vor 7 Jahren

Vielleicht stehe ich ja auf dem Schlauch.

Zu lesen ist, auch in dem Artikle von Abt, das "JSON (based on the DataContractJsonSerializer) ".

Ich möchte diesen Serializer nicht ersetzen, sondern nur eine Konfiguaration(Setting) ändern.

H
523 Beiträge seit 2008
vor 7 Jahren

Schau Dir Serialize a Dictionary<string, string> in specific format an, da Beispiel dürfte genau das sein was Du suchst.

EDIT: Was meinst Du mit:

Ich möchte diesen Serializer nicht ersetzen, sondern nur eine Konfiguaration(Setting) ändern.

Poste am besten mal Deinen vorhandenen Code, dann können wir Dir einfacher helfen.

P
Pedro_15 Themenstarter:in
375 Beiträge seit 2005
vor 7 Jahren

Kannst du mir vielleicht sagen wo dieser Code rein soll. Das ist doch genau mein Problem.



    DataContractJsonSerializerSettings settings = new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true };

    DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(MyObject), settings);

Wie gesagt, ich benutze ein WCF Service und die Serialisierung funktioniert intern und automatisch.

16.806 Beiträge seit 2008
vor 7 Jahren

Davon abgesehen, dass WCF sowieso die falsche Basis für Json Services ist (siehe Ähnliche/Gleiche Funktion von WCF-Services in Projekt zusammenfassen - Welche Architektur?).

Vermutlich kannst Du nicht direkt in die WCF Middleware eingreifen, sondern musst - wie bei WCF üblich - einen entsprechenden Message Formatter schreiben und via Web.Config im XML registrieren (oder im Behavior, wenn Du Code-Only arbeitest, was kaum einer macht).

P
Pedro_15 Themenstarter:in
375 Beiträge seit 2005
vor 7 Jahren

Ich soll also Rest und SOAP Webservice mit ASP.Net Core WebApi erstellen.

Da habe ich nicht das Seralisierungsproblem?

Ich brauche in meinem Fall einen Service der mittels REST oder SOAP angesprochen werden kann.
Diesem Service werden Listen von KeyValuePairs übergeben und als Ergebnis werden wieder Listen von KeyValuePairs zurückgegeben (nach einer Verarbeitung natürlich 😃).

Mit WCF funktioniert alles prima nur eben leider nicht die richtige Darstellung von Dictionary (KeyValuePairs) sowohl bei der Übergabe und auch bei der Rückgabe.

Da die Verarbeitung gekapselt ist, könnte ich, hoffe ich zumindest, auch einen neue ASP.Net Core Api verwenden. Ich bräuchte nur eine KeyValuePair Übergabe und Rückgabe.

Könnt Ihr sagen, ob das funktioniert mit der Serialisierung?

{"key1":"value1","key2":"value2"} -> Dictionary<String,String>

Vielen Dank für die Hilfe!

16.806 Beiträge seit 2008
vor 7 Jahren

REST natürlich. SOAP natürlich nicht, denn obwohl es auch mit JSON prinzipiell geht ist SOAP per Definition XML und nicht JSON.
Aber von SOAP hast Du im ganzen Thread auch bisher nichts gesagt.... =)

ASP.NET hat schon vor >5 Jahren WCF in Sachen REST-basierte Services technologisch abgelöst; sowohl XML als auch JSON.
SOAP ist ein Legacy-Austausch und findet so in modernen Services keine Verwendung mehr. Daher musst Du hier weiterhin WCF verwenden, sofern das gewünscht ist.

Wie gesagt; dann wirste wohl nen Custom Formatter brauchen (vermutlich).
Die Default-Darstellung des Serializers ist übrigens nicht falsch, sondern inhaltlich und in Sachen Json absolut korrekt.
Die simple Darstellung formatiert es nur als Json-Objekt.

P
Pedro_15 Themenstarter:in
375 Beiträge seit 2005
vor 7 Jahren

Wie gesagt; dann wirste wohl nen Custom Formatter brauchen (vermutlich).
Die Default-Darstellung des Serializers ist übrigens nicht falsch, sondern inhaltlich und in Sachen Json absolut korrekt.
Die simple Darstellung formatiert es nur als Json-Objekt.

Schade, ich denke die Einstellungsänderung müsste doch eigentlich leicht sein.
Ich weiss, das die Json Darstellung korrekt ist, aber die Kunden würden gerne die andere Darstellung übermitteln und zurück bekommen.

Naja vielleicht finde ich ja noch die Möglichkeit die Einstellung


DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true };

zu ändern.

16.806 Beiträge seit 2008
vor 7 Jahren

Natürlich ist das keine leichte Einstellung, da es streng genommen ein Vertragsbruch ist.

{"key1":"value1","key2":"value2"}

Ist nunmal laut JSON Definition ein Objekt und kein Dictionary. Es gibt aber durchaus Serializer, die das so handlen können, weil es durchaus gebräuchlich ist.
Trotzdem ist es dadurch kein Standard und Du musst Dich auf das Wohlwollen des Serializers verlassen.

Selbst JSON.NET kann das nur mit expliziter Angabe serialisieren.

string json = @"{""key1"":""value1"",""key2"":""value2""}";

Dictionary<string, string> values = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);

Du wirst also wohl oder übel einen Weg gehen müssen.
Wobei man in WCF durchaus auch JSON.NET Serializer nutzen kann und da dann ein manuelles Mapping.

Flappsig kann man sagen:
Der Quellcode kann nicht hellsehen, was Du willst.
Du musst es schon aktiv mitteilen... und die Entscheidung für WCF ist nun mal, dass Du es dadurch nicht wirklich leichter hast.

3.003 Beiträge seit 2006
vor 7 Jahren

Du könntest natürlich auch einfach einen Wrapper für ein Dictionary benutzen, ISerializable implementieren, GetObjectData korrekt überschreiben und dann in deinen Datenverträgen den Wrapper anstelle von Dictionary<T1,T2> benutzen.


class SimpleSerializedDictionary<TKey,TValue> : ISerializable
{
    private readonly Dictionary<TKey,TValue> _wrappedDictionary;
    //weggelassen: implementieren der üblichen Zugriffsmethoden für Dictionaries

    public void GetObjectData( SerializationInfo info, StreamingContext context )
    {
        _wrappedDictionary.ToList().ForEach(p => info.AddValue(p.Key.ToString(), p.Value);
    }

...fertig.

LaTino
PS: dein Anwendungsfall - SOAP und JSON/REST-Endpunkte parallel anbieten - liegt auch bei 75% der von mir betreuten WCF-Services vor. Meiner Meinung nach kann WCF hier richtig glänzen - das Zeug funktioniert einfach. (Genauso wie SOAP Anwendungsfälle hat, in denen es glänzen kann.) Die Technologie ist nicht halb so tot, wie sie gern geredet wird.

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

P
Pedro_15 Themenstarter:in
375 Beiträge seit 2005
vor 7 Jahren

Danke!

Mit dem Beispiel klappt die Rückgabe, aber bei der Übergabe ist die Variable die die KeyValuePairs aufnimmt leer.


POST /ICWebservices.svc/rest/GenericWebservice/v1.0/test HTTP/1.1
Host:localhost:51235
Content-Type: application/json; charset=utf-8
Content-Length: length
Accept:application/json

{
	"IdentId": "Test-IdentId",
	"Password": "Test-Password",
	"ProcessData": 
		{"Key1":"value1","Key2":"value2"}
}

Bringt das Ergebnis:


{
          "ResultList":{"key3":"value3","key4":"value4"}
}

Im TestCode füge ich aber die Eingangsparameter dazu, die Variable ist aber leer.


 SimpleDictionary<String, String> result = new SimpleDictionary<String, String>();
            result.Add("key3", "value3");
            result.Add("key4", "value4");

            foreach (KeyValuePair<String,String> keyvalue in webServiceRequestParams.ProcessData) {
                result.Add(keyvalue.Key, keyvalue.Value);
            }

            r.ResultList = result;

Muss ich für die hin serializierung noch was machen?

Und noch eine Frage:
Ich habe das mit der GetObjectData Methode auch schon mal probiert, aber mit overwrite. Das funktioniert nicht, wieso eigentlich nicht?


 public class KeyValuePairList<TKey, TValue> : Dictionary<TKey,TValue>
    {
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            this.ToList().ForEach(p => info.AddValue(p.Key.ToString(), p.Value));
        }
    }

Danke!

3.003 Beiträge seit 2006
vor 7 Jahren

Lies dir mal die Beschreibung von ISerializable auf der MSDN in Ruhe durch, das sollte die Fragen beantworten und dir auch gleich sagen, wie dein Deserialisierungsproblem zu lösen ist (Hint: c'tor).

Die Frage, wieso ein override von Dictionary<TKey,TValue> nicht das tut, was er soll...kann ich aus dem Hut nicht sagen. Ich vermute, dass das Dictionary irgendwas anders/zusätzlich macht, an das man nicht ohne weiteres denkt. Dafür müsste man mal mit der Referenzsource debuggen, wenn einen das wirklich interessiert. (Ich gebs offen zu: meine Neugier ist da begrenzt 😉 ).

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

P
Pedro_15 Themenstarter:in
375 Beiträge seit 2005
vor 7 Jahren

Konstruktur na klar. Danke für den Tipp.


 [Serializable]
    public class SimpleDictionary : ISerializable
    {
        private Dictionary<string, object> _Dictionary;

        public SimpleDictionary()
        {
            _Dictionary = new Dictionary<string, object>();
        }
        public SimpleDictionary(SerializationInfo info, StreamingContext context)
        {
            _Dictionary = new Dictionary<string, object>();
             foreach (var entry in info)
            {
                _Dictionary.Add(entry.Name, entry.Value);
            }
        }
        public object this[string key]
        {
            get { return _Dictionary[key]; }
            set { _Dictionary[key] = value; }
        }
        public void Add(string key, object value)
        {
            _Dictionary.Add(key, value);
        }

        public Dictionary<string, object>.Enumerator GetEnumerator()
        {
            return _Dictionary.GetEnumerator();
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            _Dictionary.ToList().ForEach(p => info.AddValue(p.Key.ToString(), p.Value));
        }
    }

.

3.003 Beiträge seit 2006
vor 7 Jahren

Wie gesagt, mit ein bisschen mehr Aufwand kannst du das sauberer lösen, indem du die Infrastruktur nutzt, die WCF dir dafür bietet, statt die Serialisierung von Hand zu beeinflussen (was wir gerade machen. Funktioniert erst mal, gehört aber eher in die Kategorie quick&dirty.)

Stichworte dazu:

  • IDispatchMessageFormatter
  • IDispatchMessageInspector
  • IServiceBehavior

Mit mittelgroßem Aufwand (~250 Zeilen) kann man ziemlich sauber eine WCF-Servicekonfiguration implmentieren, bei der man per .config einen Endpunkt definiert, der REST/JSON spricht und für die Serialisierung und Deserialisierung JSON.net benutzt. Und dann kann man wieder zurück zur eigentlichen Arbeit gehen, und wenn man einen JSON/REST-Endpunkt braucht, konfiguriert man ihn sich eben schnell, während der Service läuft.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

P
Pedro_15 Themenstarter:in
375 Beiträge seit 2005
vor 7 Jahren

Danke, aber wenn ich das denn bloß verstehen würde mit den Messages...

P
Pedro_15 Themenstarter:in
375 Beiträge seit 2005
vor 7 Jahren

Ok, wollte es jetzt doch mal mit den Messages probieren und gleich auf Newton
umstellen.

Anleitung:
https://blogs.msdn.microsoft.com/carlosfigueira/2011/05/02/wcf-extensibility-message-formatters/

Leider bekomme ich das nicht in der web.config konfiguriert.

<extensions>
      <behaviorExtensions>
        <add name="newtonsoftJsonBehavior" type="Newtonsoft.Json.Extensions.NewtonsoftJsonBehaviorExtension, NewtonsoftJsonExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      </behaviorExtensions>
    </extensions>
    <behaviors>
      <endpointBehaviors>
        <behavior name="endpointBehavior">
          <webHttp automaticFormatSelectionEnabled="true" helpEnabled="true" />
        </behavior>
        <behavior name="jsonRestEndpointBehavior">
          <webHttp/>
          <newtonsoftJsonBehavior/>
        </behavior>
      </endpointBehaviors>

Bekomme ich die Meldung:> Fehlermeldung:

Schweregrad Code Beschreibung Projekt Datei Zeile Unterdrückungszustand
Warnung Das Element 'behavior' hat ein ungültiges untergeordnetes Element 'newtonsoftJsonBehavior'. Erwartet wurde die Liste der möglichen Elemente: 'clientVia, callbackDebug, callbackTimeouts, clear, clientCredentials, transactedBatching, dataContractSerializer, dispatcherSynchronization, remove, synchronousReceive, webHttp, enableWebScript, endpointDiscovery, soapProcessing'.

Wenn ich meinen Code probiere ohne den Service auf den neuen endpointBehavior umzuleiten bekomme ich schon einen Fehler.> Fehlermeldung:

HTTP/1.1 500 System.ServiceModel.ServiceActivationException

Hat jemand vielleicht schon mal das ganze in einer web.config eingebunden.

3.003 Beiträge seit 2006
vor 7 Jahren

One note about extensions and Visual Studio: when we use behavior extensions, VS will usually issue a warning about a schema violation, and tag the extension with a squiggly line (see below). The warning states that it is not a valid child for the <behavior> element: “The element ‘behavior’ has invalid child element ‘myLogger’. List of possible elements expected: ‘clientVia, callbackDebug, callbackTimeouts, clear, clientCredentials, transactedBatching, dataContractSerializer, dispatcherSynchronization, remove, synchronousReceive, enableWebScript, webHttp, endpointDiscovery, soapProcessing’.” This is just a nuisance, as this error can be safely ignored and won’t cause any problems during runtime. But if you’re someone who gets bugged by warnings (or has a setting in the project to treat all warnings as errors, you can update the configuration schema in Visual Studio at \Program Files\Microsoft Visual Studio 10.0\Xml\Schemas\DotNetSchema.xsd (replace Program Files with Program Files (x86) for 64-bit OS, and replace 10.0 with the appropriate VS version) and update the schema to allow for this new element as well.

Im Prinzip könnte es also trotz der Schemawarnung funktionieren. In deinem Fall kommt mir die Referenzierung der Extension seltsam vor. Versuch's mal nur mit Angabe der Klasse und dll.


<add name="newtonsoftJsonBehavior" type="Newtonsoft.Json.Extensions.NewtonsoftJsonBehaviorExtension, NewtonsoftJsonExtensions" />

Wobei mir jetzt keine der Klassen irgendwas sagt und ich nur hoffen kann, dass die korrekt implementiert sind 😉.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)