| 

.NET C# Java Javascript Exception

6
Hallo

Ich stehe vor dem Problem das ich jetzt komplexte Objekte mit Android an einen WCF Service übermitteln muss. Bislang waren es nur einfache Datentypen die man einfach mit ToString() übermitteln konnte. Aber bei komplexen Datentypen laufe ich voll auf.

Interface:
[WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.WrappedRequest)]
List<StammdatenUpdate> RecordsUpdate(StammdatenType type, HashList listWithHashes);

Implementation:
public List<StammdatenUpdate> RecordsUpdate(StammdatenType type, HashList listWithHashes)
{
switch(type)
{
case StammdatenType.Client:
return PITServiceApplication.GetDao<StammdatenDao>().ReadClientList(listWithHashes);
default:
return new List<StammdatenUpdate>();
}
}

Klasse HashList:
public class Hash
{
private long m_Id;
private string m_Sha1Hash;

public long ID
{
get
{
return this.m_Id;
}
set
{
this.m_Id = value;
}
}

public string Sha1Hash
{
get
{
return this.m_Sha1Hash;
}
set
{
this.m_Sha1Hash = value;
}
}
}

public class HashList : List<Hash>
{

}

Android:

...
Hash[] hashes = new Hash[objectCollection.size()];

for(int i = 0; i < objectCollection.size(); i++)
{
ObjectBase objectBase = (ObjectBase)objectCollection.get(i);

Hash hash = new Hash();
hash.setId(objectBase.getId());
hash.setSha1Hash(objectBase.getSha1Hash());

hashes[i] = hash;
}

...

Gson gson = new GsonBuilder().create();
String url = postData.getUrl();
String[] parameters = new String[] { "type", "listWithHashes" };
String[] values = new String[] { gson.toJson(StammdatenType.CLIENT), gson.toJson(hashes) };

if(parameters.length != values.length)
{
return "";
}

// Durch alle Parameter laufen und die Felder abfüllen.
JSONObject data = new JSONObject();
List<NameValuePair> nameValuesPair = new ArrayList<NameValuePair>(parameters.length);
for(int pos = 0; pos < parameters.length; pos++)
{
nameValuesPair.add(new BasicNameValuePair(parameters[pos], values[pos]));
data.put(parameters[pos], values[pos]);
}

...

se = new StringEntity(data.toString());
httpPost.setEntity(se);


Die WCF Seite kann damit nichts anfangen. Mit gson ein String zu erstellen damit ich das Objekt hashes übertragen kann wird dem WCF nicht schmecken. Das sieht dann in etwas im WCF wie folgt aus.
<type type="string">0</type>
<listWithHashes type="string">[{"Sha1Hash":"3c0fa90c9e358d699b7afeebb5f487c2405eec81","ID":10}


Ich denke das Problem liegt beim type="string". Dort müsste wahrscheinlich type="HashList" stehen. Nur, wie bekomm ich das korrekt hin?

Update: 24.09.2012
Wenn ich die Signatur beim WCF auf string ändere wird sie ausgeführt.
RecordsUpdate(StammdatenType type, string listWithHashes)

Dann habe ich in listWithHashes das JSON was ich auf dem Android generiert habe. Jetzt könne ich es natürlich in der Methode deserialisieren. Aber eigentlich möchte ich das ja dem WCF überlassen.

Der Body vom POST sieht so aus.
RequestMessage {<root type="object">
<type type="string">0</type>
<listWithHashes type="string">[{"Sha1Hash":"3c0fa90c9e358d699b7afeebb5f487c2405eec81","ID":10},{"Sha1Hash":"404ce3f542dd50c50d4ca515c8716aa16b934f02","ID":100}

Ich denke, wenn ich es erreich im Android zu definieren das der type nicht string sondern HashList ist, dann sollte es klappen. Aber dazu müsste man dem Response Objekte übergeben können und nicht nur strings.
News:
21.09.2012
GENiALi 2,5k 1 2 8
GENiALi 2,5k 1 2 8
Irgend wie hat es am Ende den Code zerissen. Stimmt nicht ganz.
GENiALi 21.09.2012
Die InnerException hilft auch nicht weiter.

<ExceptionType>System.Runtime.Serialization.SerializationException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>
<Message>Expecting state 'Element'.. Encountered 'Text' with name '', namespace ''. </Message>
GENiALi 21.09.2012
Code-Formatierung jetzt besser? Merke: [i] als \[i] oder [ i ] schreiben.
Matthias Hlawatsch 21.09.2012
hab meine Antwort nochmal geändert
puls200 21.09.2012
3 Antworten
1
Ich denke, Dein Problem besteht aus 3 Teilen:

1. Herausfinden, wie die korrekte Serialisierung für die beiden Parameter der Operation auszusehen hat.
2. Herausfinden, wie die (serialisierten) Parameter in den Request-Body gesteckt werden, so dass sie .NET den beiden Methoden-Parametern zuordnen kann.
3. Herausfinden, wie Du Android überreden kannst, den Body wie gewünscht zu bestücken.

Zu 3.) kann ich nichts sagen, und zu 1. und 2. mangels Zeit leider nur Vorschläge und Vermutungen anbieten, keinen selbst überprüften Code.

zu 1.) Bau doch mal testhalber zwei Methoden, die die jeweiligen Typen der Request-Parameter stattdessen als Response haben - und schau Dir an, was .NET abschickt (mit Fiddler oder etwas ähnlichem).
zu 2.) Wie das im XML-Format aussehen müßte, weiß ich auch nicht.
BodyStyle = WebMessageBodyStyle.WrappedRequest
finde ich auf jeden Fall schon mal den richtigen Ansatz. Wenn die Response JSON-formatiert sein soll, fände ich es aber ganz natürlich, auch den Request per JSON zu übermitteln. Du müßtest dazu wie von puls200 vorgeschlagen noch
RequestFormat = WebMessageFormat.Json
ergänzen, und dann müßte der Body mMn so aussehen (wie gesagt, hab grad keine Zeit, es selbst zu testen):
{"type":"CLIENT","listWithHashes":[{"ID":1, "Sha1Hash":"hash1"}, {"ID":2, "Sha1Hash":"hash2"}]}

Schau, dass Du mit irgendeinem Tool (müßte doch auch mit Fiddler gehen, oder?) den Post-Request möglichst direkt zusammenbaust und abschickst - also noch ohne Android. Bringe auf die Weise erst mal den Service zum Laufen. Danach kannst Du Dich auf die Android-Seite konzentrieren und schauen, dass Du von dort den Request richtig abgeschickt bekommst.
22.09.2012
Matthias Hlawatsch 13,2k 4 9
Nachtrag. Nach langem suchen zeigte sich das ich nicht WrappedRequest sondern nur Wrapped drin hatte. :-(
GENiALi 26.09.2012
1
Freut mich, dass es jetzt funktioniert und ich Dir ein wenig helfen konnte. Die Sache mit Wrapped war mir schon in Deinem Kommentar vom Montag aufgefallen, wo Du den Contract nochmal gepostet hattest. Wobei es dann doch eigentlich erst am Client hätte Probleme geben müssen, nicht schon beim Empfang des Requests am Server, denn laut Doku sollte Wrapped doch WrappedRequest beinhalten? Und ich hatte Dich so verstanden, dass die Operation am Server gar nicht korrekt aufgerufen wird. Deshalb hatte ich diesem Unterschied keine größere Beachtung geschenkt.
Matthias Hlawatsch 26.09.2012
1
Bei WebInvoke würde ich noch
RequestFormat = WebMessageFormat.Json
hinzufügen..

EDIT:
Eins fällt mir noch ein: Bei meiner iOS app verwende ich ein Dictionary<string,string> als Argument. Damit der WCF Service das lesen kann muss ich das JSON mit "Key": <key-wert> und "Value":<value> aufbauen, das naheliegende "Key":"Value" funktioniert nicht!
Das liegt auf Microsoftseite an dem etwas depperten DataContractJSONSerializer, der das nicht richtig kann. Siehe auch diese Frage.
21.09.2012
puls200 3,8k 7
Hab ich auch schon versucht. Leider ohne Erfolg.
GENiALi 21.09.2012
0
OK. Das hier würde funktionieren.
Gefällt mir aber definitif nicht.
public List<StammdatenUpdate> RecordsUpdate(StammdatenType type, string listWithHashes)
{
HashList list = JsonDeserialize<HashList>(listWithHashes);

switch (type)
{
case StammdatenType.Client:
return PITServiceApplication.GetDao<StammdatenDao>().ReadClientList(list);
default:
return new List<StammdatenUpdate>();
}
}

public static T JsonDeserialize<T>(string jsonString)
{
T obj = Activator.CreateInstance<T>();
using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(jsonString)))
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
obj = (T)ser.ReadObject(ms);
}
return obj;
}

Wie kann ich das der WCF Infrastrucktur überlassen? WCF müsste ja anhand des Typs im Parameter genau das machen können.
24.09.2012
GENiALi 2,5k 1 2 8
Wie Du das WCF überlassen kannst, habe ich versucht, in meiner Antwort zu skizzieren (1. und 2.). Was davon hast Du ausprobiert, was hat funktioniert, was nicht?
Matthias Hlawatsch 24.09.2012
Bei WCF kommen diese zwei Parameter an.

RequestMessage {<root type="object">
<type type="string">0</type>
<listWithHashes type="string">[{"Sha1Hash":"3c0fa90c9e358d699b7afeebb5f487c2405eec81","ID":10},{"Sha1Hash":"404ce3f542dd50c50d4ca515c8716aa16b934f02","ID":100} ...

Das Problem ist der type.
Erklörung ist die, dass der .NET Client ein JSON ein ein SOAP verpackt und dort dann den richtigen Typ setzt. Aber Android macht das nicht. Dort sthet dann immer string drin.
GENiALi 24.09.2012
Der Contract sieht schon länger so aus.
[WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Wrapped)]
List<StammdatenUpdate> RecordsUpdate(StammdatenType type, string listWithHashes);
GENiALi 24.09.2012
Zuerst zu Deinem letzten Kommentar: Du hast "schon länger" listWithHashes als string typisiert? Dann wird ja kaum was anderes funktionieren als die Notlösung mit der manuellen Deserialisierung.
Matthias Hlawatsch 24.09.2012
Zu Deinem ersten Kommentar: wo kommt Denn nun auf einmal SOAP ins Spiel?
Wie gesagt: vergiß erst mal die Clients, schnapp Dir ein Tool, mit dem Du möglichst direkt den POST-Body manipulieren kannst, bau die Anfrage damit zusammen und schau, dass dann der Service mit automatischer Deserialisierung der HashList läuft. Danach dann alles andere.
Dieser Mix aus XML und JSON sieht mir jedenfalls ziemlich unverdaulich aus.
Matthias Hlawatsch 24.09.2012
Nein, die Typisierung ist nicht shon langer string. Aber das Response und Request Format sind schon lange auf JSON.
GENiALi 24.09.2012
Das es unverdaulich aussieht dem stimme ich zu. Solange du nur einfache Datentypen nimmst ist das auch kein Problem. Nur bei komplexen tut es nicht.
Das hier (http://www.creativecodedesign.com/node/71) brachte mich auf die SOAP Geschichte und erklärt auch wie so type=string nicht tut. Aber die Lösung brachte es mir nicht.
GENiALi 24.09.2012

Stelle deine .net-Frage jetzt!
TOP TECHNOLOGIES CONSULTING GmbH