I have created an ASP.NET MVC 4 Web Application in Visual Studio and need to be able to receive XML through HTTP POST. What is the best way to go about this?
Data coming to your controler via HTTP POST is read from
HttpContext.Current.Request.InputStream
You can read it into a string using StreamReader's ReadToEnd method.
Since it is xml as specified in the title, if you want to load it into an XDocument you should read it into a byte array and create a MemoryStream from that byte array to use in the Load method of XDocument.
Example:
var message = XDocument.Load(new UTF8Encoding().GetBytes(new StreamReader(HttpContext.Current.Request.InputStream).ReadToEnd()));
Gives you an XDocument containing the xml that was POSTed.
You will have to add a xmlvalueproviderfactory
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Web.Mvc;
using System.Xml;
using System.Xml.Linq;
public class XmlValueProviderFactory : ValueProviderFactory
{
private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, XElement xmlDoc)
{
// Check the keys to see if this is an array or an object
var uniqueKeys = new List<String>();
int totalCount = 0;
foreach (XElement element in xmlDoc.Elements())
{
if(!uniqueKeys.Contains(element.Name.LocalName)) {
uniqueKeys.Add(element.Name.LocalName);
}
totalCount++;
}
bool isArray;
if (uniqueKeys.Count == 1) {
isArray = true;
}
else if (uniqueKeys.Count == totalCount) {
isArray = false;
}
else {
// Not sure how to deal with a XML doc that has some keys the same, but not all
// For now don't process this node
return;
}
// Add the elements to the backing store
int elementCount = 0;
foreach (XElement element in xmlDoc.Elements())
{
if (element.HasElements)
{
if (isArray)
{
// Omit local name for arrays and add index instead
AddToBackingStore(backingStore, String.Format("{0}[{1}]", prefix, elementCount), element);
}
else
{
AddToBackingStore(backingStore, MakePropertyKey(prefix, element.Name.LocalName), element);
}
}
else
{
backingStore.Add(MakePropertyKey(prefix, element.Name.LocalName), element.Value);
}
elementCount++;
}
}
private static XDocument GetDeserializedXml(ControllerContext controllerContext) {
var contentType = controllerContext.HttpContext.Request.ContentType;
if (!contentType.StartsWith("text/xml", StringComparison.OrdinalIgnoreCase)
&& !contentType.StartsWith("application/xml", StringComparison.OrdinalIgnoreCase)) {
// Are there any other XML mime types that are used? (Add them here)
// not XML request
return null;
}
XDocument xml;
try
{
// DTD processing disabled to stop XML bomb attack - if you require DTD processing, read this first: http://msdn.microsoft.com/en-us/magazine/ee335713.aspx
var xmlReaderSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit };
var xmlReader = XmlReader.Create(controllerContext.HttpContext.Request.InputStream, xmlReaderSettings);
xml = XDocument.Load(xmlReader);
}
catch (Exception)
{
return null;
}
if (xml.FirstNode == null)
{
// No XML data
return null;
}
return xml;
}
public override IValueProvider GetValueProvider(ControllerContext controllerContext) {
XDocument xmlData = GetDeserializedXml(controllerContext);
if (xmlData == null) {
return null;
}
Dictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
AddToBackingStore(backingStore, String.Empty, xmlData.Root);
return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
}
private static string MakeArrayKey(string prefix, int index) {
return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
}
private static string MakePropertyKey(string prefix, string propertyName) {
return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName;
}
}
And then in your application start, call
ValueProviderFactories.Factories.Add(new XmlValueProviderFactory());
Related
I'm using Reflection to load some values from an XML into a structure. All works but I got a problem to manage the array. With an Int32 it's ok, but how can I manage an Int32[]?
EDIT: Here's my code ... it's not complete due the lenght ... I want to improve the save and load for the array types.
//My struct ... it should be a class too
public struct stOptions
{
public int IntNumber;
public double DoubleNumber;
public string String;
public Point aPoint;
}
//How my class works
private void Form1_Load(object sender, EventArgs e)
{
stOptions options = new stOptions();
//Populate the struct
options.aPoint = new Point(12, 24);
options.DoubleNumber = 34d;
options.IntNumber = 17;
options.String = "Hello";
ManageSettings ms = new ManageSettings();
ms.SaveSettings("e:\\test001.xml", options);
options = default(stOptions);
options = ms.LoadSettings<stOptions>("e:\\test001.xml");
}
//A portion of my class
public T LoadSettings<T>(string FileName)
{
Type type = typeof(T);
var returnObject = Activator.CreateInstance(type);
List<Settings> list = null;
try
{
using (StreamReader reader = File.OpenText(FileName))
{
XmlSerializer serializer = new XmlSerializer(typeof(List<Settings>));
list = (List<Settings>)serializer.Deserialize(reader);
}
}
catch
{
//Error accessing the file
_errors.Add("Unable to locate the file or access denied: " + FileName);
return default(T);
}
try
{
foreach (Settings entry in list)
{
FieldInfo field = returnObject.GetType().GetField(entry.Key.ToString());
if (field != null)
{
SetField(field, entry.Value.ToString(), returnObject);
}
field = null;
PropertyInfo prop = returnObject.GetType().GetProperty(entry.Key.ToString());
if (prop != null)
{
SetField(prop, entry.Value.ToString(), returnObject);
}
prop = null;
}
list = null;
}
catch
{
//Error processing the XML
_errors.Add("Errore processing the XML file: " + FileName);
return default(T);
}
return (T)returnObject;
}
private void SetField(FieldInfo prop, string value, object returnObject)
{
switch (prop.FieldType.Name.ToLower())
{
case "uint16":
prop.SetValue(returnObject, Convert.ToUInt16(value));
break;
case "uint32":
prop.SetValue(returnObject, Convert.ToUInt32(value));
break;
etc.
[...]
default:
//An unmanaged type
Debug.WriteLine("Found an unmanaged type: " + prop.FieldType.Name);
break;
}
}
When it is completed I will publish it.
I have a object {System.Collections.Generic.List<object>} that contains 1000 object {DynamicData} inside of it, each one with 4 keys and values and one more List with 2 keys and values inside.
I need to serialize this object into a XML File, i tried normal serialization but it gives me this exception = The type DynamicData was not expected, how can i serialize this object?
Here is the code:
//output is the name of my object
XmlSerializer xsSubmit = new XmlSerializer(output.GetType());
var xml = "";
using (var sww = new StringWriter())
{
using (XmlWriter writers = XmlWriter.Create(sww))
{
try
{
xsSubmit.Serialize(writers, output);
}
catch (Exception ex)
{
throw;
}
xml = sww.ToString(); // Your XML
}
}
I can create the xml file writing line by line and element by element, but i want something more faster and with less code.
The structure of my object is like this:
output (count 1000)
[0]
Costumer - "Costumername"
DT - "Date"
Key - "Key"
Payment - "x"
[0]
Adress - "x"
Number - "1"
[1]...
[2]...
You can implement your own serialize object by using IXmlSerializable
[Serializable]
public class ObjectSerialize : IXmlSerializable
{
public List<object> ObjectList { get; set; }
public XmlSchema GetSchema()
{
return new XmlSchema();
}
public void ReadXml(XmlReader reader)
{
}
public void WriteXml(XmlWriter writer)
{
foreach (var obj in ObjectList)
{
//Provide elements for object item
writer.WriteStartElement("Object");
var properties = obj.GetType().GetProperties();
foreach (var propertyInfo in properties)
{
//Provide elements for per property
writer.WriteElementString(propertyInfo.Name, propertyInfo.GetValue(obj).ToString());
}
writer.WriteEndElement();
}
}
}
Usage;
var output = new List<object>
{
new { Sample = "Sample" }
};
var objectSerialize = new ObjectSerialize
{
ObjectList = output
};
XmlSerializer xsSubmit = new XmlSerializer(typeof(ObjectSerialize));
var xml = "";
using (var sww = new StringWriter())
{
using (XmlWriter writers = XmlWriter.Create(sww))
{
try
{
xsSubmit.Serialize(writers, objectSerialize);
}
catch (Exception ex)
{
throw;
}
xml = sww.ToString(); // Your XML
}
}
Output
<?xml version="1.0" encoding="utf-16"?>
<ObjectSerialize>
<Object>
<Sample>Sample</Sample>
</Object>
</ObjectSerialize>
Note : Be careful with that, if you want to deserialize with same type
(ObjectSerialize) you should provide ReadXml. And if you want to
specify schema, you should provide GetSchema too.
I'm running into an issue where my JSON serializer is failing randomly due to the character < showing up from time to time. I can't nail down where this is coming from and I want to - on exception - reserialize using a different method so I can see a full representation of the offending object. Is there any way to do this?
My current code:
// data is of type 'object'
serialized = JsonConvert.SerializeObject(data, new JsonSerializerSettings() {
Error = delegate(object sender, ErrorEventArgs args) {
// reserialize here and output object so I know what the heck is going on
}
})
There is no foolproof way to serialize any and every possible c# object.
Instead, you have a few ways to attack your problem:
Turn on Json.NET tracing. See Debugging with Serialization Tracing. This should tell you where in your object graph the problem is occurring.
Rather than serializing with JsonConvert.SerializeObject(), if you serialize with JsonSerializer.Serialize() and write to a string using a JsonTextWriter wrapping a StringWriter, you can flush the writer and log the partial serialization. That may give some idea where the problem arises.
You can try serializing using various other serializers, and if any work, log the result.
If one of your object properties is throwing an exception, you might try to force serialization of fields instead. See JSON.Net: Force serialization of all private fields and all fields in sub-classes.
For instance, putting #1, #2 and #3 together gives the following method:
public static class JsonSerializerExtensions
{
public static string SerializeObject(object obj, JsonSerializerSettings settings = null)
{
settings = settings ?? new JsonSerializerSettings();
var sb = new StringBuilder();
using (var writer = new StringWriter(sb))
using (var jsonWriter = new JsonTextWriter(writer))
{
var oldError = settings.Error;
var oldTraceWriter = settings.TraceWriter;
var oldFormatting = settings.Formatting;
try
{
settings.Formatting = Newtonsoft.Json.Formatting.Indented;
if (settings.TraceWriter == null)
settings.TraceWriter = new MemoryTraceWriter();
settings.Error = oldError + delegate(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs args)
{
jsonWriter.Flush();
var logSb = new StringBuilder();
logSb.AppendLine("Serialization error: ");
logSb.Append("Path: ").Append(args.ErrorContext.Path).AppendLine();
logSb.Append("Member: ").Append(args.ErrorContext.Member).AppendLine();
logSb.Append("OriginalObject: ").Append(args.ErrorContext.OriginalObject).AppendLine();
logSb.AppendLine("Error: ").Append(args.ErrorContext.Error).AppendLine();
logSb.AppendLine("Partial serialization results: ").Append(sb).AppendLine();
logSb.AppendLine("TraceWriter contents: ").Append(settings.TraceWriter).AppendLine();
logSb.AppendLine("JavaScriptSerializer serialization: ");
try
{
logSb.AppendLine(new JavaScriptSerializer().Serialize(obj));
}
catch (Exception ex)
{
logSb.AppendLine("Failed, error: ").AppendLine(ex.ToString());
}
logSb.AppendLine("XmlSerializer serialization: ");
try
{
logSb.AppendLine(obj.GetXml());
}
catch (Exception ex)
{
logSb.AppendLine("Failed, error: ").AppendLine(ex.ToString());
}
logSb.AppendLine("BinaryFormatter serialization: ");
try
{
logSb.AppendLine(BinaryFormatterExtensions.ToBase64String(obj));
}
catch (Exception ex)
{
logSb.AppendLine("Failed, error: ").AppendLine(ex.ToString());
}
Debug.WriteLine(logSb);
};
var serializer = JsonSerializer.CreateDefault(settings);
serializer.Serialize(jsonWriter, obj);
}
finally
{
settings.Error = oldError;
settings.TraceWriter = oldTraceWriter;
settings.Formatting = oldFormatting;
}
}
return sb.ToString();
}
}
public static class XmlSerializerExtensions
{
public static T LoadFromXML<T>(this string xmlString)
{
using (StringReader reader = new StringReader(xmlString))
{
return (T)new XmlSerializer(typeof(T)).Deserialize(reader);
}
}
public static string GetXml<T>(this T obj)
{
using (var textWriter = new StringWriter())
{
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " " };
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
new XmlSerializer(obj.GetType()).Serialize(xmlWriter, obj);
return textWriter.ToString();
}
}
}
public static class BinaryFormatterExtensions
{
public static string ToBase64String<T>(T obj)
{
using (var stream = new MemoryStream())
{
new BinaryFormatter().Serialize(stream, obj);
return Convert.ToBase64String(stream.GetBuffer(), 0, checked((int)stream.Length)); // Throw an exception on overflow.
}
}
public static T FromBase64String<T>(string data)
{
return FromBase64String<T>(data, null);
}
public static T FromBase64String<T>(string data, BinaryFormatter formatter)
{
using (var stream = new MemoryStream(Convert.FromBase64String(data)))
{
formatter = (formatter ?? new BinaryFormatter());
var obj = formatter.Deserialize(stream);
if (obj is T)
return (T)obj;
return default(T);
}
}
}
You would likely replace the final Debug.WriteLine() with an appropriate logging method, then replace JsonConvert.SerializeObject(data) with JsonSerializerExtensions.SerializeObject(data) in your applications code.
I'm trying to figure out how to do it right. So far I'm using this method to serialize a list of objects:
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(MyInfo));
using (var writer = new StreamWriter("SerializedValues.xml"))
{
foreach (var o in dic.Values)
serializer.Serialize(writer, o);
}
And it turns into a list of separate XML files, each with its own header, but all in one file. I'm assuming that is not right.
Then I try deserializing it and it gives me the following error:
Unexpected XML declaration. The XML declaration must be the first node in the document, and no white space characters are allowed to appear before it. Line 5, position 17.
And that's exactly where the next XML header begins. So I guess I'm serializing it wrong.
There's also the issue that comes from the fact deserialization returns a single object, which already sounds wrong because I'm expecting a list of sorts.
How do I serialize it correctly?
How about serialize the whole dictionary?
EDIT: OK, how about as a List of KeyValuePair?
var list = dic.ToList();
XmlSerializer s = new XmlSerializer(list.GetType());
using (StreamWriter file = new StreamWriter("SerializedValues.xml"))
s.Serialize(file, list);
I managed to do this by converting the values to an array prior to serialization:
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(MyInfo[]));
using (var writer = new StreamWriter("SerializedValues.xml"))
{
serializer.Serialize(writer, dic.Values.ToArray());
}
And deserialize it like this:
using (var reader = new StreamReader("SerializedValues.xml"))
{
var array = (MyInfo[])serializer.Deserialize(reader);
foreach (var v in array)
dic[v.id] = v;
}
Although your solution works for this one specific case - because the Value also has information about the Key - I would still go for a more general solution.
The modified answer of agentnega will work as well, even if the Value has no information about the key.
My favorite though is the SerializableDictionary. Because there is no additional work in (de)serialization! Taken from here:
http://csharpcodewhisperer.blogspot.co.at/2013/06/namespace-xmlserializabledictionary.html
namespace XMLSerializableDictionary
{
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
[Serializable]
[XmlRoot("Dictionary")]
public class SerializableDictionary<TKey, TValue>
: Dictionary<TKey, TValue>, IXmlSerializable
{
private const string DefaultTagItem = "Item";
private const string DefaultTagKey = "Key";
private const string DefaultTagValue = "Value";
private static readonly XmlSerializer KeySerializer =
new XmlSerializer(typeof(TKey));
private static readonly XmlSerializer ValueSerializer =
new XmlSerializer(typeof(TValue));
public SerializableDictionary() : base()
{
}
protected SerializableDictionary(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
protected virtual string ItemTagName
{
get { return DefaultTagItem; }
}
protected virtual string KeyTagName
{
get { return DefaultTagKey; }
}
protected virtual string ValueTagName
{
get { return DefaultTagValue; }
}
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty)
{
return;
}
try
{
while (reader.NodeType != XmlNodeType.EndElement)
{
reader.ReadStartElement(this.ItemTagName);
try
{
TKey tKey;
TValue tValue;
reader.ReadStartElement(this.KeyTagName);
try
{
tKey = (TKey)KeySerializer.Deserialize(reader);
}
finally
{
reader.ReadEndElement();
}
reader.ReadStartElement(this.ValueTagName);
try
{
tValue = (TValue)ValueSerializer.Deserialize(reader);
}
finally
{
reader.ReadEndElement();
}
this.Add(tKey, tValue);
}
finally
{
reader.ReadEndElement();
}
reader.MoveToContent();
}
}
finally
{
reader.ReadEndElement();
}
}
public void WriteXml(XmlWriter writer)
{
foreach (KeyValuePair<TKey, TValue> keyValuePair in this)
{
writer.WriteStartElement(this.ItemTagName);
try
{
writer.WriteStartElement(this.KeyTagName);
try
{
KeySerializer.Serialize(writer, keyValuePair.Key);
}
finally
{
writer.WriteEndElement();
}
writer.WriteStartElement(this.ValueTagName);
try
{
ValueSerializer.Serialize(writer, keyValuePair.Value);
}
finally
{
writer.WriteEndElement();
}
}
finally
{
writer.WriteEndElement();
}
}
}
}
}
So I have an xmlDocument and I need to check to see if a credit score was appended. To do this I am using xmlNodes.SelectSingleNode and then checking the innerText.
My issue is this: one of the nodes has an ID field in the actual node name. So I think C# is interpreting that as part of the node name.
public void DeperPostAppend()
{
DirectoryInfo CompDir = new DirectoryInfo(FilePrep.CompletedDirectory);
foreach (FileInfo File in CompDir.GetFiles())
{
// Load xml documents for sorting
XmlDocument xmlDoc = new XmlDocument();
try
{
xmlDoc.Load(File.FullName);
}
catch
{
if (File.Extension != ".xml")
return;
}
//XmlNode auto = xmlDoc.SelectSingleNode("//ACORD//InsuranceSvcRq//PersAutoPolicyQuoteInqRq");
XmlNode home = xmlDoc.SelectSingleNode("//ACORD//InsuranceSvcRq//HomePolicyQuoteInqRq");
XmlNode creditAuto = xmlDoc.SelectSingleNode("//ACORD//InsuranceSvcRq//PersAutoPolicyQuoteInqRq//PersPolicy//CreditScoreInfo//CreditScore");
XmlNode creditHome = xmlDoc.SelectSingleNode("//ACORD//InsuranceSvcRq//HomePolicyQuoteInqRq//PersPolicy//CreditScoreInfo//CreditScore");
//if file is type home quote
if (File.Extension == ".xml" && creditHome != null)
{
if (creditHome.InnerText != "ERR" || creditHome.InnerText != "NOH")
{
DeperHome();
}
}
//If file is type Auto Quote
else if (File.Extension == ".xml" && creditAuto != null)
{
if (creditAuto.InnerText != "ERR" || creditAuto.InnerText != "NOH")
{
DeperAuto();
}
}
}
}//end DeperPostAppend
//ACORD//InsuranceSvcRq//HomePolicyQuoteInqRq//PersPolicy//CreditScoreInfo//CreditScore
PersPolicy is where the issue is. the node looks like this on the document.
<PersPolicy id="AE4562BEE086A92470D4">
I want to ignore the id portion due to the fact that it changes every document and i have thousands of docs to process.
I just decided to use classes to handle the nodes as follows
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
/// <summary>
///
/// </summary>
public class PersPolicy
{
XElement self;
public PersPolicy(XElement self) { this.self = self; }
public CreditScoreInfo CreditScoreInfo { get { return _CreditScoreInfo ?? (_CreditScoreInfo = new CreditScoreInfo(self.Element("CreditScoreInfo"))); } }
CreditScoreInfo _CreditScoreInfo;
public string PolicyNumber
{
get { return (string)self.Element("PolicyNumber"); }
set { self.Element("PolicyNumber").SetValue(value); }
}
}
/// <summary>
///
/// </summary>
public class CreditScoreInfo
{
XElement self;
public CreditScoreInfo(XElement self) { this.self = self; }
public string CreditScore
{
get { return (string)self.Element("CreditScore"); }
set { self.Element("CreditScore").SetValue(value); }
}
public string CreditScoreDt
{
get { return (string)self.Element("CreditScoreDt"); }
set { self.Element("CreditScoreDt").SetValue(value); }
}
}