ServiceStack Parse Xml Request to Dictionary - c#

One of our requirements is to have a decoupled architecture where we need to map data from one system to another, and the intermediate mapping is handled by a ServiceStack service request. Our issue is that a vendor can only provide data via Xml that does not conform to the standard dictionary request that ServiceStack offers like below:
<Lead xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:KeyValueOfstringstring>
<d2p1:Key>String</d2p1:Key>
<d2p1:Value>String</d2p1:Value>
</d2p1:KeyValueOfstringstring>
</Lead>
Instead they need a mechanism like:
<Lead>
<LeadId>Value</LeadId>
<FirstName>First Name</FirstName>
<LastName>Last Name</LastName>
...
</Lead>
Since the nodes in their xml request may change over time, and we're simply acting as a middle-man, is there a native way to accept a dynamic request or handle this as Dictionary with data similar to what's below?
Dictionary<string, string>
{
{ "LeadId", "Value" },
{ "FirstName", "First Name" },
{ "LastName", "Last Name" }
...
};

The default XML Serialization doesn't provide any way that you could transparently infer an XML fragment into a string dictionary so you're going to need to manually parse the XML which you can do in ServieStack by telling ServiceStack to skip built-in Serialization by implementing IRequiresRequestStream which ServiceStack will inject with the Request Stream so you can deserialize it yourself, e.g:
public class Lead : IRequiresRequestStream
{
public Stream RequestStream { get; set; }
}
In your Service you'd then manually parse the raw XML and convert it to the data collections you want, e.g:
public class RawServices : Service
{
public object Any(Lead request)
{
var xml = request.RequestStream.ReadFully().FromUtf8Bytes();
var map = new Dictionary<string, string>();
var rootEl = (XElement)XDocument.Parse(xml).FirstNode;
foreach (var node in rootEl.Nodes())
{
var el = node as XElement;
if (el == null) continue;
map[el.Name.LocalName] = el.Value;
}
return new LeadResponse {
Results = map
}
}
}

Related

What's the best way to properly consume one of two "successful" responses from the same endpoint on a web api?

I have C# client that's making requests to another Web API.
The Web API I'm consuming can respond with 2 "successful" responses, both 200's.
I need to intercept the response before I map it to its corresponding object and am looking for the best practice.
I own both the client and the server, so either can be adjusted to fit best practices.
I haven't tried much, but I've considered the following
Having an understood map of unclaimed status codes to each error.
Try-catching the attempt to map the JSON to either of the two business objects or otherwise parsing and comparing the JSON to the expected format each object expects before mapping it.
response = await PostAsync(
$"{BaseUrl}/endpoint/{identifier}",
new StringContent(jsonBody, Encoding.UTF8, "application/json")
);
var responseJson = await response.Content.ReadAsStringAsync();
var responseObject = json.Deserialize<ResponseObject>(responseJson);
businessObject = new BusinessObject(responseObject);```
//These are two example responses
{
"StartDate": "01/01/01",
"EndDate": "01/01/01"
"Object": {
"Property1": 1,
"Property2": "someStr"
}
}
//OR
{
"message": "Valid reason you are not receiving the JSON above, status 200 btw"
}
A low-budget solution could be to simply assess the response body before deserializing.
var response = await PostAsync(
$"{BaseUrl}/endpoint/{identifier}",
new StringContent(jsonBody, Encoding.UTF8, "application/json")
);
var responseJson = await response.Content.ReadAsStringAsync();
var responseObject = responseJson.Contains("[special string or property name]")
? json.Deserialize<ResponseObjectA>(responseJson)
: json.Deserialize<ResponseObjectB>(responseJson);
I recently had a similar issue but mine was when I was consuming messages from a queue. The way I resolved it was by telling Newtonsoft to add the type when serializing my object. You can do this like so:
JsonConvert.SerializeObject(response, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
With SerializeObject and DeserializeObject, you can pass some optional serialization settings. Here, we are passing a new instance of the JsonSerializerSettings to this parameter and settings it's TypeNameHandling to the enum value TypeNameHandling.All. This is telling Newtonsoft to embed the types of whatever it's serializing into the JSON itself. For instance, if you had a class that looked like this (based on your example JSON):
public class SuccessOne
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public SuccessOneChild Object { get; set; }
}
public class SuccessOneChild
{
public int Property1 { get; set; }
public string Property2 { get; set; }
}
Then the JSON you get will look like this:
{
"$type":"Functions.Tests.SuccessOne, Functions.Tests",
"StartDate":"2019-07-09T09:32:11.0090507+01:00",
"EndDate":"2019-07-16T09:32:11.0091048+01:00",
"Object":{
"$type":"Functions.Tests.SuccessOneChild, Functions.Tests",
"Property1":1,
"Property2":"someStr"
}
}
Notice the extra $type properties that have been added? These have been added automatically by Newtonsoft because you told it to in the serialization settings.
Then, when you come to deserialising, you can also tell it to use the type handling. It will look at the extra type property in your JSON and deserialize to whatever that type is. When doing this, you don't need to give DeserializeObject a type argument:
var response = JsonConvert.DeserializeObject(response, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
Note that wherever you're deserializing will need to know about the classes. So it may be useful to have those classes in a shared library that both your API and consumer can reference to avoid duplicating your code.
You can then handle the response based on it's type. I did this by using a switch statement:
switch (response.GetType().Name)
{
case nameof(SuccessOne):
var successOneResponse = (SuccessOne)response;
handleSuccessOne(successOneResponse);
break;
case nameof(SuccessTwo):
var successTwoResponse = (SuccessTwo)response;
handleSuccessTwo(successTwoResponse);
break;
default:
// Throw exception or whatever you want to do
break;
}
I've also created a fiddle showing the serialization and deserialization here: https://dotnetfiddle.net/UcugGg

RavenDB 4.0 storing raw json

I'm using NLOG for logging with the JsonLayout and want to store these logs in RavenDB 4.0. Basically I'm trying to store raw json into ravendb using the RavenDB .NET client.
I had a working solution in RavenDB 3.5: using a class inheriting from "RavenJObject" but this functionality was depricated in 4.0.
I got it working by deserializing the json into a dynamic and storing the dynamic object. But this stores the document in a meaningless "JObjects" collection. It also seems overhead to convert json to dynamic and back to json again.
var obj = JsonConvert.DeserializeObject<dynamic>(jsonString);
session.Store(obj);
I know it's possible using the UI (raven studio). But i can't find a way doing it using the .Net client.
I think it's maybe possible using the http api, doing a post with the raw json. But I haven't found any documentation about this.
Who can help me out? Any advice is appreciated!
Here you have code sample which allows you to operate on generic type and assigns collection properly:
[Fact]
public void RawJson()
{
using (var store = GetDocumentStore())
{
using (var session = store.OpenSession())
{
var json = "{ 'Name' : 'John', '#metadata' : { '#collection': 'Users' } }";
var blittableJson = ParseJson(session.Advanced.Context, json);
var command = new PutDocumentCommand("users/1", null, blittableJson);
session.Advanced.RequestExecutor.Execute(command, session.Advanced.Context);
}
using (var session = store.OpenSession())
{
var user = session.Load<User>("users/1");
Assert.Equal("John", user.Name);
}
}
}
public BlittableJsonReaderObject ParseJson(JsonOperationContext context, string json)
{
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
return context.ReadForMemory(stream, "json");
}
}
public class User
{
public string Id { get; set; }
public string Name { get; set; }
}

How to do a polymorphic deserialization in C# given a XSD?

I have the following given:
1) A XML Schema, XSD-file, compiled to C# classes using the XSD.EXE tool.
2) A RabbitMQ message queue containing well formed messages in XML of any type defined in the XML Schema. Here are two snippets of different messages:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<UserReport xmlns=".../v5.1"; ... >
... User report message content...
</UserReport>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<CaptureReport xmlns=".../v5.1"; ...>
... Capture report message content...
</CaptureReport>
3) Experience using the XmlSerializer .Net class to deserialize, when the type is known.
The question is how to deserialize messages from XML to a an object, when the type is unknown. It's not possible to instantiate the XmlSerializer, because the type is unknown.
One way is to loop through all possible types until deserialiation succeed, which is a bad solution because there are many different types defined in the XML Schema.
Is there any other alternatives?
There are a few approaches you can take depending on how exactly you've achieved your polymorphism in the XML itself.
Element name is the type name (reflection approach)
You could get the root element name like this:
string rootElement = null;
using (XmlReader reader = XmlReader.Create(xmlFileName))
{
while (reader.Read())
{
// We won't have to read much of the file to find the root element as it will be the first one found
if (reader.NodeType == XmlNodeType.Element)
{
rootElement = reader.Name;
break;
}
}
}
Then you could find the type by reflection like this (adjust reflection as necessary if your classes are in a different assembly):
var serializableType = Type.GetType("MyApp." + rootElement);
var serializer = new XmlSerializer(serializableType);
You would be advised to cache the mapping from the element name to the XML serializer if performance is important.
Element name maps to the type name
If the XML element names are different from the type names, or you don't want to do reflection, you could instead create a Dictionary mapping from the element names in the XML to the XmlSerializer objects, but still look-up the root element name using the snippet above.
Common root element with polymorphism through xsi:type
If your XML messages all have the same root element name, and the polymorphism is achieved by having types identified using xsi:type, then you can do something like this:
using System;
using System.Xml.Serialization;
namespace XmlTest
{
public abstract class RootElement
{
}
public class TypeA : RootElement
{
public string AData
{
get;
set;
}
}
public class TypeB : RootElement
{
public int BData
{
get;
set;
}
}
class Program
{
static void Main(string[] args)
{
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(RootElement),
new Type[]
{
typeof(TypeA),
typeof(TypeB)
});
RootElement rootElement = null;
string axml = "<RootElement xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"TypeA\"><AData>Hello A</AData></RootElement>";
string bxml = "<RootElement xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"TypeB\"><BData>1234</BData></RootElement>";
foreach (var s in new string[] { axml, bxml })
{
using (var reader = new System.IO.StringReader(s))
{
rootElement = (RootElement)serializer.Deserialize(reader);
}
TypeA a = rootElement as TypeA;
if (a != null)
{
Console.WriteLine("TypeA: {0}", a.AData);
}
else
{
TypeB b = rootElement as TypeB;
if (b != null)
{
Console.WriteLine("TypeB: {0}", b.BData);
}
else
{
Console.Error.WriteLine("Unexpected type.");
}
}
}
}
}
}
Note the second parameter to the XmlSerializer constructor which is an array of additional types that you want the .NET serializer to know about.
This is an answer based on #softwariness one, but it provides some automation.
If your classes are generated via xsd then all root types are decorated with XmlRootAttribute so we can use it:
public class XmlVariantFactory
{
private readonly Dictionary<string, Type> _xmlRoots;
public XmlVariantFactory() : this(Assembly.GetExecutingAssembly().GetTypes())
{
}
public XmlVariantFactory(IEnumerable<Type> types)
{
_xmlRoots = types
.Select(t => new {t, t.GetCustomAttribute<XmlRootAttribute>()?.ElementName})
.Where(x => !string.IsNullOrEmpty(x.ElementName))
.ToDictionary(x => x.ElementName, x => x.t);
}
public Type GetSerializationType(XmlReader reader)
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
return _xmlRoots[reader.LocalName];
}
}
throw new ArgumentException("No known root type found for passed XML");
}
}
It scans all type in executing assembly and finds all possible XML roots. You can do it for all assemblies:
public XmlVariantFactory() : this(AppDomain.CurrentDomain.SelectMany(a => a.GetTypes())
{
}
And then you juse use it:
var input = new StringReader(TestResource.firstRequestResponse);
var serializationType = new XmlVariantFactory().GetSerializationType(XmlReader.Create(input));
var xmlReader = XmlReader.Create(input);
bool canDeserialize = new XmlSerializer(serializationType).CanDeserialize(xmlReader);
Assert.True(canDeserialize);
The responsible for the XML Schema have added the xml-tag to a content field in the RabbitMQ protocol header. The header holds the tag for the dto, data transfer object, sent and serialized to xml. This means that a IOC container becomes handy. I have coded a dto builder interface and its implementation by a generic builder. Thus the builder will build a dto when the dto class is specified for generic part. Note that the dto-class is generated by the xsd-tool. In a IOC container like MS Unity I registered the builder interface implementations for dto all classes and added the xml-tag to the register call. The IOC container’s resolver function is called with the actual received xml-tag from the RabbitMQ header in order to instantiate the specific builder of the dto.

Can the C# MongoClient be used to return valid JSON without first serializing to a .NET type?

I would like to have ASP.NET MVC return a document stored in MongoDB as JSON, but have no need for it to be serialized to a .NET type first. However, BSONDocument.ToJSON() returns JSON that looks like this:
{_id:ObjectId("someid")}
The browser's JSON parser does not like "ObjectId(nnn)" and so the call fails with a parser error. I am able to get parse-able JSON using a Regex hack:
public ActionResult GetFormDefinitionsJSON()
{
var client = new MongoDB.Driver.MongoClient(ConfigurationManager.ConnectionStrings["mongodb"].ConnectionString);
var db = client.GetServer().GetDatabase("formthing");
var result = db.GetCollection("formdefinitions").FindAll().ToArray();
var sb = new StringBuilder();
sb.Append("[");
var regex = new Regex(#"(ObjectId\()(.*)(\))");
var all = result.Select(x => regex.Replace(x.ToJson(), "$2"));
sb.Append(string.Join(",", all));
sb.Append("]");
return Content(sb.ToString(), "application/json");
}
This returns parse-able JSON:
{_id:"someid"}
But it smells. Is there any way without regex and string building hackery to get the official MongoDB driver to return JSON that can be parsed by the browser? Alternatively, am I missing something on the browser side that would allow {_id:ObjectId("someid")} to be parsed as valid?
You have two options that I can think of.
The first one is to use the JavaScript JsonOutputMode mode. This results in the ID serializing to "_id" : { "$oid" : "51cc69b31ad71706e4c9c14c" } - not quite ideal, but at least it's valid Javascript Json.
result.ToJson(new JsonWriterSettings { OutputMode = JsonOutputMode.JavaScript })
The other option is to serialize your results into an object and use the [BsonRepresentation(BsonType.String)] attribute. This results in much nicer Json: "_id" : "51cc6a361ad7172f60143d97"; however, it requires you to define a class to serialize it in to (this can affect performance)
class Example
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public ObjectId ID { get; set; }
public string EmailAddress { get; set; }
}
// Elsewhere in Code - nb you need to use the GetCollection<T> method so
// that your result gets serialized
var result = database.GetCollection<Example>("users").FindAll().ToArray();
var json = result.ToJson();
Some more details on the differences between JsonOuputModes (Strict, Javascrpt and Mongo):
http://docs.mongodb.org/manual/reference/mongodb-extended-json/

.NET serialization of JSON data with dynamic structure (schema)

The web service I consume responces with json data.
it gives resultObject as array:
resultObject:[{object1}, {object2},...] if there more then one object
and it returns
resultObject:{object1} if there only one object.
for serializing in .NET I created a "static" structure of classes to map json schema. But if in one case i've got an array (list) of objects an in other case just one object, how is it possible to handle this situation?
I have found a plethora of ugly solutions to this one, but so far goes:
If you use the System.Web.Script.Serialization.JavaScriptSerializer you have very limited control. If the result data type is simple, you could simply use the DeserializeObject method; it will translate everything into Dictionary and the "resultObject" property will in the first case be a Dictionary while the latter case will turn it into an array of such dictionary. It will not save you the headache of the final translation, but you will get the data into dictionaries which could be considered a first step.
I also attempted to use the KnownTypes and the DataContractJsonSerializer, but alas the datacontract serializer needs "hints" in the form of specially named properties to aid it deserializing. (Why am I using the KnownType attribute wrong?). This is a hopeless strategy if you don't have any control of the serialization which I guess is the case for you.
So now we are down to the butt-ugly solutions of which trial-and-error is my first choice:
When using the ScriptSerializer the conversion will fail with an InvalidOperationException if anything is wrong. I then created two data types one with data-as-arrays and one where data is a single instance (the DataClass is my invention since you do not specify the data types):
[DataContract]
public class DataClass
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public int BirthYear { get; set; }
public override string ToString()
{
return "FirstName : '" + FirstName + "', BirthYear: " + BirthYear;
}
}
[DataContract]
public class ResultSingle
{
[DataMember]
public DataClass Data { get; set; }
}
[DataContract]
public class ResultArray
{
[DataMember]
public List<DataClass> Data { get; set; }
}
Using these data types it is possible to translate using
JavaScriptSerializer jSer = new JavaScriptSerializer();
var one = jSer.Deserialize<ResultSingle>(jsonWithSingleInstance);
var many = jSer.Deserialize<ResultArray>(jsonWithArray);
But this of course require you to known the data type in advance and you get two different result types. Instead you could choose to always convert to ResultArray. If you get an exception you should convert as ResultSingle and then instantiate the ResultArray and manually add the data object to the Data list:
static ResultArray ConvertJson(string json)
{
ResultArray fromJson;
JavaScriptSerializer jSer = new JavaScriptSerializer();
try
{
fromJson = jSer.Deserialize<ResultArray>(json);
return fromJson;
}
catch (InvalidOperationException)
{
var single = jSer.Deserialize<ResultSingle> (json);
fromJson = new ResultArray();
fromJson.Data = new List<DataClass>();
fromJson.Data.Add(single.Data);
return fromJson;
}
}
static void Main(string[] args)
{
var jsonWithSingleInstance = "{\"Data\":{\"FirstName\":\"Knud\",\"BirthYear\":1928}}";
var jsonWithArray = "{\"Data\":[{\"FirstName\":\"Knud\",\"BirthYear\":1928},{\"FirstName\":\"Svend\",\"BirthYear\":1930}]}";
var single = ConvertJson(jsonWithSingleInstance);
var array = ConvertJson(jsonWithArray);
}
I do not say this is a beautiful solution (it isn't), but it should do the job.
You could also look at json.net which seem to be the json serializer of choice in .NET: How to install JSON.NET using NuGet?
Well, my service provider finally said that it is really a bug.
http://jira.codehaus.org/browse/JETTISON-102
says that is it because of java version that they use.

Categories

Resources