XmlTextReader ignores CheckCharacters=false when Normalisation is on - c#

I have implemented my XmlTextReader with overridden setting for CheckCharacters. Something like this:
class MyXmlTextReader : XmlTextReader
{
public MyXmlTextReader(TextReader input) : base(input)
{
}
/// <summary>
/// Settings
/// </summary>
public override XmlReaderSettings Settings
{
get { return new XmlReaderSettings { CheckCharacters = false }; }
}
}
When I use it in normal scenario with invalid xml data everything works fine:
var sr3 = new StringReader(xml);
var xr3 = new MyXmlTextReader(sr3);
var obj3 = (MyObject)ser.Deserialize(xr3);
But as soon as I turn on normalisation, I start getting InvalidCharacter exceptions:
var sr3 = new StringReader(xml);
var xr3 = new MyXmlTextReader(sr3);
xr3.Normalization = true;
var obj3 = (MyObject)ser.Deserialize(xr3);
Is there a way to have normalisation, but at the same time ignore invalid xml characters?
Here is a sample application to reproduce the problem:
https://gist.github.com/ncksol/29bd6490edd0580c25f7338b417b37d3

This appears to be a shortcoming in the implementation:
XmlReader has no Normalization property.
XmlReader.Create allows you to pass CheckCharacters as a setting, but since it returns XmlReader, you can't control the normalization through it.
XmlTextReader (actually wrapping XmlTextReaderImpl) has Normalization, but no public CheckCharacters property, and no way of accepting XmlReaderSettings.
Finally, XmlTextReaderImpl, which does all the real work, can do both normalization and omitted character checking, but due to all of the above, there is no public path to configuring it that way.
If you don't mind relying on the implementation in this case, it can be done through reflection:
var sr3 = new StringReader(xml);
var xr3 = XmlReader.Create(sr3, new XmlReaderSettings { CheckCharacters = false });
// xr3.Normalization is not accessible
xr3.GetType()
.GetProperty("Normalization", BindingFlags.Instance | BindingFlags.NonPublic)
.SetValue(xr3, true);
var obj3 = (MyObject)ser.Deserialize(xr3);
Hacky, but still far preferable over implementing XmlTextReader from scratch which, given all the cleverness in the implementation, is not something to undertake lightly.
Note that XmlReader.Create is not contractually obligated to return an instance of a type that has a Normalization property, it just happens to do so in the current implementation.

Related

Reading CollectionJson content in Web Api Project

I have a project similar(Almost identical) to Conference API project which is taking similar approach to the noted project for returning CollectionJson content. I am having difficulty Setting the Collection property of the ReadDocument (Line 30) as it does not have any setter. I could bypass this problem by doing the following change
public CollectionJsonContent(Collection collection)
{
var serializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
Formatting = Newtonsoft.Json.Formatting.Indented,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
collection.Version = "1.0";
Headers.ContentType = new MediaTypeHeaderValue("application/vnd.collection+json");
using (var writer = new JsonTextWriter(new StreamWriter(_memoryStream)){CloseOutput = false})
{
//var readDocument = new ReadDocument(); {IReadDocument.Collection = collection};
var serializer = JsonSerializer.Create(serializerSettings);
serializer.Serialize(writer,collection);
writer.Flush();
}
_memoryStream.Position = 0;
}
Although above code compiles and to some extent sorts out the problem but then again I will have another problem of not being able to consume the JsonCollection content in my controller unit tests. Consider the following unit test code snippet:
using (var request = CreateRequest())
{
var controller = new TestController(DataService) {Request = request};
var temp = await controller.ListAsync(gridSearchData, sampleSearchData);
if ((temp is NotFoundResult) && (sampleCollection.Any()))
{
Assert.Fail("Controller did not return any result but query did");
}
var json = await temp.ExecuteAsync(cancellationTokenSource);
var readDocument = json.Content.ReadAsAsync<ReadDocument>(new[] {new CollectionJsonFormatter()}, cancellationTokenSource).Result;
}
Since I did not set the collection property of ReadDocument readDocument is always empty and I cant read its content.
How do you asynchronously read the contents of JsonCollection on the client side in WEB API projects?
To get a Clear picture of the approach look at the Conference Web Api
and the authors blog
OK all, this has been fixed. The Collection property is now settable again.
I have just pushed release 0.7.0 with this fix, a major naming refactoring as well as a nice improvement to serialization to not write out empty collections.
Please see the release notes for the changes (especially the naming as the package names and namespaces have changed)
As far as I see from your code, you do not serialize a ReadDocument object, but only a property of it (Collection), and then you try to deserialize that value into a new ReadDocument object.
A sample ReadDocument should serialize like this
"{"Collection": [1,2,3,4,5] }"
But you serialize collection, so you get
"[1,2,3,4,5]"
I recommend a surrogate class for serialization like this
class SerializableReadDocument
{
public Collection Collection { get; set; }
}
and update your serialization code like this
using (var writer = new JsonTextWriter(new StreamWriter(_memoryStream)){CloseOutput = false})
{
var readDocument = new SerializableReadDocument() { Collection = collection };
var serializer = JsonSerializer.Create(serializerSettings);
serializer.Serialize(writer, readDocument);
writer.Flush();
}
But, this will not resolve your problem when you try to deserialize your output since ReadDocument does not have a settable Collection property, deserialization will either fail, or return a ReadDocument object with an empty Collection.
You can use SerializableReadDocument if you like in your unit tests.
I am looking into this and will come up with a solution hopeful this weekend, which will either be to make it a public setter, or make the setter internal and have public ctor that accepts a collection.
Sorry for the difficulty.

How to change SecureConversationVersion property in SessionSecurityToken class

I'm trying to configure Security Token Service using WCF so it could create tokens (SessionSecurityToken) using WSSecureConversationFebruary2005 namespace. SessionSecurityToken class has property SecureConversationVersion which in theory can be set to my desired value. Unfortunately by default it is not and I don't see a way to change it. After analysing source code I noticed that there is constructor which can change this value but it is internal.
http://msdn.microsoft.com/pl-pl/library/system.identitymodel.tokens.sessionsecuritytoken.secureconversationversion%28v=vs.110%29.aspx
Code I use for hosting STS looks like:
BindingElementCollection bindings = new BindingElementCollection();
SecurityBindingElement security = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
security.AllowInsecureTransport = true;
security.EnableUnsecuredResponse = true;
security.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
bindings.Add(security);
TextMessageEncodingBindingElement textMessageEncoding = new TextMessageEncodingBindingElement();
bindings.Add(textMessageEncoding);
textMessageEncoding.MessageVersion = MessageVersion.Soap11WSAddressingAugust2004;
HttpTransportBindingElement httpsTransport = new HttpTransportBindingElement();
bindings.Add(httpsTransport);
var myBinding = new CustomBinding(bindings);
var host = new WSTrustServiceHost(Configuration, new Uri(BaseAddress));
host.AddServiceEndpoint(
typeof(IWSTrustFeb2005SyncContract),
myBinding,
STS_ADDRESS);
The example response looks like:
<t:RequestedSecurityToken>
<SecurityContextToken d6p1:Id="_6d497e66-9851-4b74-bf6d-e5f46c652837-35820CA1B23B6109FC1DC2F6A0D3ACC3"
xmlns:d6p1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512">
<Identifier>urn:uuid:b067f4fc-0adc-4e78-98a5-449c6b58c234</Identifier>
<Cookie xmlns="http://schemas.microsoft.com/ws/2006/05/security"></Cookie>
</SecurityContextToken>
</t:RequestedSecurityToken>
But instead of docs.oasis-open.org/ws-sx/ws-secureconversation/200512 I expect schemas.xmlsoap.org/ws/2005/02/sc
Finally I was able to achieve my goal by creating custom token handler that overrides WriteToken method. The implementation is a slightly modified version from base class and looks like this:
public class MyTokenHandler : SessionSecurityTokenHandler
{
public override void WriteToken(XmlWriter writer, SecurityToken token)
{
SessionSecurityToken sessionSecurityToken = token as SessionSecurityToken;
sessionSecurityToken.IsReferenceMode = true;
string ns = "http://schemas.xmlsoap.org/ws/2005/02/sc";
string localName = "SecurityContextToken";
string localName2 = "Identifier";
XmlDictionaryWriter xmlDictionaryWriter;
if (writer is XmlDictionaryWriter)
{
xmlDictionaryWriter = (XmlDictionaryWriter)writer;
}
else
{
xmlDictionaryWriter = XmlDictionaryWriter.CreateDictionaryWriter(writer);
}
xmlDictionaryWriter.WriteStartElement(localName, ns);
xmlDictionaryWriter.WriteElementString(localName2, ns, sessionSecurityToken.ContextId.ToString());
xmlDictionaryWriter.WriteEndElement();
xmlDictionaryWriter.Flush();
}
}
Probably this is not most elegant solution but I couldn't find better.

How to use XmlWriterSettings() when using override void WriteEndElement()?

I am working with a legacy application that does not import abbreviated empty xml elements. For example:
BAD empty:
<foo />
GOOD empty:
<foo></foo>
I know the solution to achieve this, which I will present now:
public class XmlTextWriterFull : XmlTextWriter
{
public XmlTextWriterFull(Stream stream, Encoding enc) : base(stream, enc)
{
}
public XmlTextWriterFull(String str, Encoding enc) : base(str, enc)
{
}
public override void WriteEndElement()
{
base.WriteFullEndElement();
}
}
and the client code:
var x_settings = new XmlWriterSettings();
x_settings.NewLineChars = Environment.NewLine;
x_settings.NewLineOnAttributes = true;
x_settings.NewLineHandling = NewLineHandling.Replace;
x_settings.CloseOutput = true;
x_settings.Indent = true;
x_settings.NewLineOnAttributes = true;
//var memOut = new MemoryStream();
var writer = new XmlTextWriterFull(outputFilename, Encoding.UTF8); //Or the encoding of your choice
var x_serial = new XmlSerializer(typeof(YOUR_OBJECT_TYPE));
x_serial.Serialize(writer, YOUR_OBJECT_INSTANCE);
writer.Close();
However, if you observed carefully the XmlWriterSettings are never used in the client code. Therefore the xml output is terribly formatted. My questions is this: how do I adapt the above code to accept XmlWriterSettings?
The use of factory creation methods and sealed/internal/abstract classes makes this difficult to implement an override.
I will accept an alternative solution, I am not married to my above solution.
WORKAROUND SOLUTION
Step 1: create the following class in your solution:
public class XmlTextWriterFull : XmlTextWriter
{
public XmlTextWriterFull(TextWriter sink) : base(sink)
{
Formatting = Formatting.Indented;
}
public override void WriteEndElement()
{
base.WriteFullEndElement();
}
}
Step 2: Add the following client code. Make sure to replace YOUR_OBJECT_TYPE and YOUR_OBJECT_INSTANCE with the class and instance your are working with:
TextWriter streamWriter = new StreamWriter(outputFilename);
var writer = new XmlTextWriterFull(streamWriter);
var x_serial = new XmlSerializer(typeof (YOUR_OBJECT_TYPE));
x_serial.Serialize(writer, YOUR_OBJECT_INSTANCE);
writer.Close();
The workaround above will produce the following empty xml element formatting:
<foo>
</foo>
The issue with this workaround is that it adds a line feed (notice the elements are on separate lines). This may be acceptable for you but causes issues with my legacy application.
How about this.
Grab the awesome XmlWrappingWriter class from http://www.tkachenko.com/blog/archives/000585.html (I have omitted the code for the sake of brevity).
With that, we can create a sub-class as follows (very similar to your original one):
public class XmlTextWriterFull2 : XmlWrappingWriter
{
public XmlTextWriterFull2(XmlWriter baseWriter)
: base(baseWriter)
{
}
public override void WriteEndElement()
{
base.WriteFullEndElement();
}
}
It can then be invoked like this (again very similar):
var x_settings = new XmlWriterSettings();
x_settings.NewLineChars = Environment.NewLine;
x_settings.NewLineOnAttributes = true;
x_settings.NewLineHandling = NewLineHandling.None;
x_settings.CloseOutput = true;
x_settings.Indent = true;
x_settings.NewLineOnAttributes = true;
using (XmlWriter writer = XmlWriter.Create(outputFilename, x_settings))
{
using (XmlTextWriterFull2 xmlTextWriterFull = new XmlTextWriterFull2(writer))
{
var x_serial = new XmlSerializer(typeof(YOUR_OBJECT_TYPE));
x_serial.Serialize(xmlTextWriterFull, YOUR_OBJECT_INSTANCE);
}
}
In my case, an element that had previously been rendered as
<Foo>
</Foo>
became
<Foo></Foo>
As you alluded to in your question, this is actually quite a tricky problem due to everything being sealed/internal etc., making overrides rather difficult. I think my biggest problem was trying to get an XmlWriter to accept XmlWriterSettings: beyond this approach, I could find no way of getting the original XmlTextWriterFull to respect the given XmlWriterSettings.
MSDN states that this method:
XmlWriter.Create(XmlWriter, XmlWriterSettings)
Can be used to apply the XmlWriterSettings to the XmlWriter. I couldn't get this to work like I wanted (the indentation never worked, for example), and upon decompiling the code, it does not appear that all the settings are used with this particular method, hence why my invocation code just passes in the outputFile (a stream of some sort would work just as well).
The workaround solution you gave in your question adds extra line breaks (when indenting is enabled) because we're telling the writer to treat this element as if it had children.
Here is how I modified your workaround to manipulate the indenting dynamically so as to avoid those extra line breaks.
public class XmlTextWriterFull : XmlTextWriter
{
public XmlTextWriterFull(TextWriter sink)
: base(sink)
{
Formatting = Formatting.Indented;
}
private bool inElement = false;
public override void WriteStartElement(string prefix, string localName, string ns)
{
base.WriteStartElement(prefix, localName, ns);
// Remember that we're in the process of defining an element.
// As soon as a child element is closed, this flag won't be true anymore and we'll know to avoid messing with the indenting.
this.inElement = true;
}
public override void WriteEndElement()
{
if (!this.inElement)
{
// The element being closed has child elements, so we should just let the writer use it's default behavior.
base.WriteEndElement();
}
else
{
// It looks like the element doesn't have children, and we want to avoid emitting a self-closing tag.
// First, let's temporarily disable any indenting, then force the full closing element tag.
var prevFormat = this.Formatting;
this.Formatting = Formatting.None;
base.WriteFullEndElement();
this.Formatting = prevFormat;
this.inElement = false;
}
}
}
Following code snippet force printing of closing tag on the same line (sorry for vb version, it should be easy to rewrite the same using C#):
Imports System.Xml
Imports System.IO
Public Class CustomXmlTextWriter
Inherits XmlTextWriter
Public Sub New(ByRef baseWriter As TextWriter)
MyBase.New(baseWriter)
Formatting = Xml.Formatting.Indented
End Sub
Public Overrides Sub WriteEndElement()
If Not (Me.WriteState = Xml.WriteState.Element) Then
MyBase.WriteEndElement()
Else
Formatting = Xml.Formatting.None
MyBase.WriteFullEndElement()
Formatting = Xml.Formatting.Indented
End If
End Sub
End Class
Another option.
public class XmlCustomTextWriter : XmlTextWriter
{
private TextWriter _tw = null;
public XmlCustomTextWriter(TextWriter sink)
: base(sink)
{
_tw = sink;
Formatting = Formatting.Indented;
Indentation = 0;
}
public void OutputElement(string name, string value)
{
WriteStartElement(name);
string nl = _tw.NewLine;
_tw.NewLine = "";
WriteString(value);
WriteFullEndElement();
_tw.NewLine = nl;
}
}
Leaving this here in case someone needs it; since none of the answers above solved it for me, or seemed like overkill.
FileStream fs = new FileStream("file.xml", FileMode.Create);
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter w = XmlWriter.Create(fs, settings);
w.WriteStartDocument();
w.WriteStartElement("tag1");
w.WriteStartElement("tag2");
w.WriteAttributeString("attr1", "val1");
w.WriteAttributeString("attr2", "val2");
w.WriteFullEndElement();
w.WriteEndElement();
w.WriteEndDocument();
w.Flush();
fs.Close();
The trick was to set the XmlWriterSettings.Indent = true and add it to the XmlWriter.
Edit:
Alternatively you can also use
w.Formatting = Formatting.Indented;
instead of adding an XmlWriterSettings.

Override class name for XmlSerialization

I need to serialize IEnumerable. At the same time I want root node to be "Channels" and second level node - Channel (instead of ChannelConfiguration).
Here is my serializer definition:
_xmlSerializer = new XmlSerializer(typeof(List<ChannelConfiguration>), new XmlRootAttribute("Channels"));
I have overriden root node by providing XmlRootAttribute but I haven't found an option to set Channel instead of ChannelConfiguration as second level node.
I know I can do it by introducing a wrapper for IEnumerable and using XmlArrayItem but I don't want to do it.
Like so:
XmlAttributeOverrides or = new XmlAttributeOverrides();
or.Add(typeof(ChannelConfiguration), new XmlAttributes
{
XmlType = new XmlTypeAttribute("Channel")
});
var xmlSerializer = new XmlSerializer(typeof(List<ChannelConfiguration>), or,
Type.EmptyTypes, new XmlRootAttribute("Channels"), "");
xmlSerializer.Serialize(Console.Out,
new List<ChannelConfiguration> { new ChannelConfiguration { } });
Note you must cache and re-use this serializer instance.
I will also say that I strongly recommend you use the "wrapper class" approach - simpler, no risk of assembly leakage, and IIRC it works on more platforms (pretty sure I've seen an edge-case where the above behaves differently on some implementations - SL or WP7 or something like that).
If you have access to the type ChannelConfiguration, you can also just use:
[XmlType("Channel")]
public class ChannelConfiguration
{...}
var xmlSerializer = new XmlSerializer(typeof(List<ChannelConfiguration>),
new XmlRootAttribute("Channels"));
xmlSerializer.Serialize(Console.Out,
new List<ChannelConfiguration> { new ChannelConfiguration { } });
This should do the trick, if I remember correctly.
[XmlType("Channel")]
public class ChannelConfiguration {
}

Help me convert .NET 1.1 Xml validation code to .NET 2.0 please

It would be fantastic if you could help me rid of these warnings below.
I have not been able to find a good document. Since the warnings are concentrated in just the private void ValidateConfiguration( XmlNode section ) section, hopefully this is not terribly hard to answer, if you have encountered this before.
Thanks!
'System.Configuration.ConfigurationException.ConfigurationException(string)' is obsolete: 'This class is obsolete, to create a new exception create a System.Configuration!System.Configuration.ConfigurationErrorsException'
'System.Xml.XmlValidatingReader' is obsolete: 'Use XmlReader created by XmlReader.Create() method using appropriate XmlReaderSettings instead. http://go.microsoft.com/fwlink/?linkid=14202'
private void ValidateConfiguration( XmlNode section )
{
// throw if there is no configuration node.
if( null == section )
{
throw new ConfigurationException("The configuration section passed within the ... class was null ... there must be a configuration file defined.", section );
}
//Validate the document using a schema
XmlValidatingReader vreader = new XmlValidatingReader( new XmlTextReader( new StringReader( section.OuterXml ) ) );
// open stream on Resources; the XSD is set as an "embedded resource" so Resource can open a stream on it
using (Stream xsdFile = XYZ.GetStream("ABC.xsd"))
using (StreamReader sr = new StreamReader(xsdFile))
{
vreader.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);
vreader.Schemas.Add(XmlSchema.Read(new XmlTextReader(sr), null));
vreader.ValidationType = ValidationType.Schema;
// Validate the document
while (vreader.Read()) { }
if (!_isValidDocument)
{
_schemaErrors = _sb.ToString();
throw new ConfigurationException("XML Document not valid");
}
}
}
// Does not cause warnings.
private void ValidationCallBack( object sender, ValidationEventArgs args )
{
// check what KIND of problem the schema validation reader has;
// on FX 1.0, it gives a warning for "<xs:any...skip" sections. Don't worry about those, only set validation false
// for real errors
if( args.Severity == XmlSeverityType.Error )
{
_isValidDocument = false;
_sb.Append( args.Message + Environment.NewLine );
}
}
Replace
throw new ConfigurationException(....)
with
throw new ConfigurationErrorsException(....)
Replace XmlValidatingReader vreader = new XmlValidatingReader(...)
with
var vreader = XmlReader.Create(new StringReader(section.OuterXml),
new XmlReaderSettings
{
ValidationType = ValidationType.Schema
});
Basically, it's telling you to use the XmlReaderSettings instead of the XmlValidatingReader, which was deprecated.
Personally I'm not going to do the conversion, I think that you actually doing that will be good for your coding development, so here is some resources:
Look at the overloads of the XmlReader.Create() method, specifically this one.
Then have a look at the different properties associated with the XmlReaderSettings class: http://msdn.microsoft.com/en-us/library/system.xml.xmlreadersettings_members.aspx
Give it a try, see what happens and if your still having problems, ask another question :)
HTH

Categories

Resources