Scott Hanselman

Breaking All The Rules with WCF

June 10, '09 Comments [35] Posted in Web Services | XML | XmlSerializer
Sponsored By

Sometimes, in my job, I go onsite at partners and work with them, sometimes architecturally, sometimes doing proofs of concepts to make sure they're comfortable with things working together.

This week I’m onsite at a large enterprise and one of the things they wanted to see, amongst many, was .NET interoperating with an existing Web Service. It's not important what platform their Web Service is running on, but it's not Windows and .NET. What was important was that they had WSDL and XSDs for the service, which put them above 99% of the Web Services I come upon in the enterprise.

The team here said that this particular web service used WS-Security and was a compliant web service. I figured, and told them, no problem. That's something .NET is good at. Moving angle-brackets around is something both I, and .NET do pretty well. I figured we had a number of options.

In this scenario was I going to be the Client, I could use:

  • WCF - svcutil.exe - good
  • System.Web.Services - wsdl.exe - pretty good
  • WebClient/XDocument/XmlDocument - not so good, but workable.

You get the idea. There were a few things wrong, though.

Bad-ish WSDL

They gave me the WSDL and when I ran svcutil.exe on it, I got this error (the elements have been changed to protect the innocent.)

C:\Users\Scott\Desktop\foo>svcutil foo.Wsdl foo.xsd /config:app.config
Microsoft (R) Service Model Metadata Tool
[Microsoft (R) Windows (R) Communication Foundation, Version 3.0.4506.2152]
Copyright (c) Microsoft Corporation. All rights reserved.

Error: Cannot import wsdl:binding
Detail: The WSDL binding named FooBinding is not valid because no match for
operation GetFooDetails was found in the corresponding portType definition.
XPath to Error Source: //wsdl:definitions[@targetNamespace='urn:foo:v1']/wsdl:
binding[@name='FooBinding']

Error: Cannot import wsdl:port
Detail: There was an error importing a wsdl:binding that the wsdl:port is dependent on.
XPath to wsdl:binding: //wsdl:definitions[@targetNamespace='urn:foo:v1']
/wsdl:binding[@name='FooBinding']
XPath to Error Source: //wsdl:definitions[@targetNamespace='urn:foo:v1']
/wsdl:service[@name='FooService']/wsdl:port[@name='FooPort']

I googled binged around for this to no avail. After staring at the file long enough, I realized that while this is a lousy error message (to be clear) it was telling me (obscurely) what was up all the while.

Here's a snippet of what I was looking at:

    <Type name="FooType">
<operation name="FooSearch">
<input message="tns:FooSearchRequest"></input>
<output message="tns:FooSearchResponse"></output>
<fault name="FooFault" message="tns:FooFault"></fault>
</operation>
</Type>

<binding name="FooBinding" type="tns:FooType">

<soap:binding style="document" trans="http://schemas.xmlsoap.org/soap/http"></soap:binding>

<operation name="FooSearch">
<soap:operation soapAction=""></soap:operation>
<input name="FooSearchRequest">
<soap:body use="literal"></soap:body>
</input>
<output name="FooSearchResponse">
<soap:body use="literal"></soap:body>
</output>
<fault name="FooFault">
<soap:fault name="FooFault" use="literal"></soap:fault>
</fault>
</operation>
...

The key was that their WSDL didn't have the name="" attribute on the input and output elements of the operation. The name needs to line up to the operation name in the binding.

<Type name="FooType">
<operation name="FooSearch">
<input name="FooSearchRequest" message="tns:FooSearchRequest"></input>
<output name="FooSearchResponse" message="tns:FooSearchResponse"></output>
<fault name="FooFault" message="tns:FooFault"></fault>
</operation>
</Type>

Once these new name="" attributes were added, I was able to generate my client-side stubs. I had to edit their WSDL, which sucks. However, you might argue svcutil.exe could chill out. Either way, a speed bump.

Claiming Compliance

I was told the Web Service would use WS-Security and a usernameToken. However, the actual message seemed like it was missing something.

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:tns="urn:foo:v1" xsi:schemaLocation="http://www.w3.org/2003/05/soap-envelope http://www.w3.org/2003/05/soap-envelope/soap-envelope.xsd urn:foo:v1 com.foo.messages.v1.xsd">
<soapenv:Header>
<wsse:Security>
<wsse:UsernameToken>
<wsse:Username>secret</wsse:Username>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
<soapenv:Body>
<tns:FooRequest>
...

It's been a while (about 18 months) since I did any WCF and WS-Security, but UsernameToken really needs to have a Password element also. Additionally, when you're using WS-Security, you typically get WS-Addressing, etc along for the ride. There's other headers I'd expect to see.

I trudged on, built up the message and tried to send it off. First problem was that the endpoint URI I had was http, not https. It's not possible to send a UsernameToken in plain-text - the system explicitly forbids it. However, their system was setup to default to basic HTTP. Some gnashing of teeth and I found an SSL endpoint I could use. However, it's a hassle to debug SSL traffic. I usually use ProxyTrace or TCPTrace but with SSL, not so much.

Sniffing SSL Traffic with a Proxy

I ended up using Charles, an HTTP Proxy that can act as a man-in-the middle, issue an SSL cert, then decrypt the traffic, and forward it along to the real endpoint. However, the SSL Cert Charles issues isn't from a certificate authority, so I had to make a Policy to blindly (temporarily) accept all certificates:

internal class AcceptAllCertificatePolicy : ICertificatePolicy
{
public AcceptAllCertificatePolicy(){}

public bool CheckValidationResult(ServicePoint sPoint,
X509Certificate cert, WebRequest wRequest, int certProb)
{
return true; //Always accept
}
}

Then I apply it in this (obsolete, but easy) way:

ServicePointManager.CertificatePolicy = new AcceptAllCertificatePolicy();

Now I can run all my traffic through my local man-in-the-middle. I can set the proxy in my config file:

<basicHttpBinding>
<binding name="FooBinding"
...
proxyAddress="http://BigAssLaptop:8888"
useDefaultWebProxy="false">

or in my own binding:

WSHttpBinding oldBinding = new WSHttpBinding();
oldBinding.ProxyAddress = new Uri("http://BIGASSLAPTOP:8888");

FooPortTypeClient svc = new FooPortTypeClient(oldBinding, new EndpointAddress("https://example.com/foo/v1"));

This let me see the outgoing request. I noticed immediately that my WCF client was sending a LOT more stuff that I needed.

Breaking the Rules

It was hard for the client to hear, but here's the deal. They were using the usernameToken element, alone, in the WS-Security namespace in the style of an apiKey. You often see these kinds of APIs in the Web 2.0 world, when intense security isn't needed. You get a key that's unique to you, basically a GUID, and it also acts as a tracker for the provider.

However, this isn't how WS-Security usernameTokens work, or are supposed to work. Perhaps a better way would have been for them to use a custom soap:header, rather than trying to tunnel "apikey" semantics into an existing token.

At this point, regardless of relative-wrongness, I still need to get the WCF client to talk to this unusual endpoint. I could use one of the other XML mechanism available, or, gasp, a StringBuilder, but since I wasn't having trouble with the body of the message, just the envelope.

This essentially means that I wanted WCF to do something incorrect, on purpose. After a call to Steve Maine and team, along with some general freaking out, I was able to get WCF to spit out JUST a usernameToken, like this.

WSHttpBinding oldBinding = new WSHttpBinding();
oldBinding.Security.Mode = SecurityMode.TransportWithMessageCredential;
//Just the username
oldBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
//And basically nothing else
oldBinding.Security.Message.NegotiateServiceCredential = false;
oldBinding.Security.Message.EstablishSecurityContext = false;

//oldBinding.ProxyAddress = new Uri("http://BIGASSLAPTOP:8888");
//oldBinding.UseDefaultWebProxy = false;

//remove the timestamp
BindingElementCollection elements = oldBinding.CreateBindingElements();
elements.Find<SecurityBindingElement>().IncludeTimestamp = false;

//sets the content type to application/soap+xml
elements.Find<TextMessageEncodingBindingElement>().MessageVersion = MessageVersion.Soap12;
CustomBinding newBinding = new CustomBinding(elements);
FooPortTypeClient svc = new FooPortTypeClient(newBinding, new EndpointAddress("https://example.com/foo/v1"));
FooRequest req = new FooRequest();
//...etc...now it's just request and response.

Unfortunate, but I'll put this configuration of a custom binding, and hopefully when they fix it, it'll be a configuration change. This at least got us to a point where I can reliably call their web services.

Long day, but interesting stuff.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Changing where XmlSerializer Outputs Temporary Assemblies

September 16, '07 Comments [9] Posted in ASP.NET | Learning .NET | XML | XmlSerializer
Sponsored By

With this tip, I couldn't find any documentation, so you're on your own. That means no help/support from me or anywhere. YMMV - Your Mileage May Vary. No warranties. Enjoy.

Someone asked:

"When using the XmlSerializer from ASP.NET there are permissions issues can can be solved by granting the user account read/write permissions on the %SYSTEMROOT%\Temp folder but I would much rather have the temporary files created in a different folder"

So I poked around. If you want the XmlSerializer to output it's assemblies elsewhere, here's how. Note that I used the previous tip on How To Debug into a .NET XmlSeraizlizer Generated Assembly just to prove this was working. It's not needed for this tip!

To start, let's see where the temporary files end up usually.

First, I'll add this to my web.config (or whatever.exe.config if you like). This is just to visualize and confirm. It's not needed for this tip.

<configuration>
   <system.diagnostics>
      <switches>
         <add name="XmlSerialization.Compilation" value="1" />
      </switches>
   </system.diagnostics>
</configuration>

I'll debug my application, but I'll use SysInternal's Process Monitor and set the filter to only show processes whose name contains the string "WebDev" and see what files the VS WebDev Server writes to. If you're using IIS, you would search for W3WP or ASPNET_WP.

Process Monitor - Sysinternals www.sysinternals.com

Here I can see it writing to C:\Users\Scott\AppData\Local\Temp. The file names are auto-generated...note their names, in my case 6txrbdy.0.*. To prove these are the real generated XmlSerializers, I'll put a breakpoint on my app just before I call CreateSerializer and then drag the .cs file over from the TEMP folder into VS.NET. Note that the .PDB will get loaded when I hit F11 to step into.

XmlSerializerAlternateLocation (Debugging) - Microsoft Visual Studio (Administrator) (2)

Make note of the Call Stack. See the name of the assembly and the name of the .cs file? So, we're sure now where this XmlSerializer came from; it came from C:\Users\Scott\AppData\Local\Temp. If we were running IIS, it'd have been in %SYSTEMROOT%\Temp. The point being, it's automatic and it's temporary and your process needs WRITE access to that folder.

Now, poking around in Reflector with the not-used-often-enough Analyze method shows that XmlSerializerFactory.CreateSerializer eventually ends up in XmlSerializerCompilerParameters.Create which looks for a Configuration Section called “xmlSerializer” and a key called TempFilesLocation.

Lutz Roeders .NET Reflector (3)

That actually means it's looking for a section called System.Xml.Serializer and an element called xmlSerializer and an attribute called tempFilesLocation.

I'll add this to my web.config now:

<system.xml.serialization> 
  <xmlSerializer tempFilesLocation="c:\\foo"/> 
</system.xml.serialization> 

...and create a c:\\foo directory. Make sure your hosting process has write access to the directory.

I'll run my app again, and check out that new folder.

Administrator CWindowssystem32cmd.exe (2)

I've got newly generated XmlSerializer temporary assemblies in there. Undocumented, yes, but it does the job. Note, stuff like this can totally go away at any minute, so don't base your whole design on things like this. It's your tush, not mine, on the line.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Mixing XmlSerializers with XElements and LINQ to XML

August 28, '07 Comments [0] Posted in Learning .NET | LINQ | Programming | XML | XmlSerializer
Sponsored By

I used to mix and match XmlWriters and XmlSerializers when I had objects that I wanted to serialize in the middle of a larger chunk of XmlWriter-generated XML, like this, where the variable a is typeof(Author).

using (XmlWriter writer = 
        XmlWriter.Create(Response.OutputStream, settings))
{
    //Note the artificial, but useful, indenting
    writer.WriteStartDocument();
        writer.WriteStartElement("bookstore");
            writer.WriteStartElement("book");
                writer.WriteStartAttribute("publicationdate");
                    writer.WriteValue(publicationdate);
                writer.WriteEndAttribute();
                writer.WriteStartAttribute("ISBN");
                    writer.WriteValue(isbn);
                writer.WriteEndAttribute();
                writer.WriteElementString("title", "ASP.NET 2.0");
                writer.WriteStartElement("price");
                    writer.WriteValue(price);
                writer.WriteEndElement(); //price
                XmlSerializer xs = factory.CreateSerializer(typeof(Author));
                xs.Serialize(writer, a);
            writer.WriteEndElement(); //book
        writer.WriteEndElement(); //bookstore
    writer.WriteEndDocument();
}

See how there XmlSerializer just writes directly to the XmlWriter and the object is serialized in the middle of the XmlWriter calls? I found this very useful and used it often.

I wanted to do the same thing with LINQ to XML but was ever so frightened. Earlier I punted and just created the XML myself as an XElement tree. See the author "a" in the middle there?:

        XNamespace ns = "http://example.books.com";
        XDocument books = new XDocument(
            new XElement(ns + "bookstore",
                new XElement(ns + "book",
                    new XAttribute("publicationdate", publicationdate),
                    new XAttribute("ISBN", isbn),
                    new XElement(ns + "title", "ASP.NET 2.0 Book"),
                    new XElement(ns + "price", price),
                    new XElement(ns + "author", 
                        new XElement(ns + "first-name", a.FirstName),
                        new XElement(ns + "last-name", a.LastName)
                        )
                    )
               )
            );

Ion on the XmlTeam explained that the XmlWriter returned by calls to CreateWriter on both XDocument and XElement classes is special. After your done using the XmlWriter and it's Close()d, it will take all the generated XML and Add() it to the parent/owner XDocument or XElement.

So, now I can make an extension method and add my SerializeAsXElement method to XmlSerializer:

   static class XmlSerializerExtension {
       public static XElement SerializeAsXElement(this XmlSerializer xs, object o) {
           XDocument d = new XDocument();
           using (XmlWriter w = d.CreateWriter()) xs.Serialize(w, o);
           XElement e = d.Root;
           e.Remove();
           return e;
       }
   }

Notice the use of using to ensure the XmlWriter is close (Close is called in the Dispose of XmlWriter) and that the root element is removed from the document. Ion explains that "This avoids the cloning of the returned element during any subsequent Add() (eg. functional construction)."

Now, I can use the extension method in the middle of my XElement expression.

        XmlSerializer xs = new XmlSerializer(typeof(Author));
        XDocument books = new XDocument(
            new XElement(ns + "bookstore",
                new XElement(ns + "book",
                    new XAttribute("publicationdate", publicationdate),
                    new XAttribute("ISBN", isbn),
                    new XElement(ns + "title", "ASP.NET 2.0 Book"),
                    new XElement(ns + "price", price),
                    xs.SerializeAsXElement(a)
                    )
               )
            );

And it produces XML identical to the XmlWriter example at the beginning. Big thanks to Ion Vasilian (brilliant moderator of the LINQ Project forums and XmlTeam blogger) for all the help with this question.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

WPF Sample in IronRuby talking via C# to Wesabe

July 23, '07 Comments [12] Posted in eFinance | Programming | Ruby | Web Services | XML | XmlSerializer
Sponsored By

wesabeironrubymoney John Lam and team have released their first drop the IronRubySource code. It's super-pre-alpha so don't hate, but it's looking pretty sweet, if you're a burgeoning Ruby-head like myself. There's incomplete support for some of the cooler Ruby syntax things, but I have full confidence that it's only going to get better. All the underpinnings are there.

John also announce that the team will host IronRuby source on Rubyforge (which is quite the coup, actually) and that they'll be taking external contributions very soon (this being the open part of open source.) 

Not every sample (initially) will look and feel and smell good like Ruby, and this one is no exception. You might remember that last week I did a quicky Wesabe client in C# and put it up on Google Code. We've got four folks up there improving the code, which is cool.

Today, Shady the intern and I decided to do a sample in IronRuby that would call the C# Wesabe client API and display some account data via WPF. This is part of my new plan to take over Money and Quicken's business and you can tell from the hours of intense UI design that Shady and I did, that my plan is inevitable.

We started from ScottGu's HelloWorld sample, and slogged creatively coded our way forward. Here's how it went.

imageFirst, get the IronRuby source and upzip. Open on Visual Studio 2005 command prompt and compile the DLR and IronRuby by running build.cmd.

All the interesting stuff will show up in bin/release, including the IronRuby equivalent of IRB, the interactive Ruby Interpreter.  You can run this from the command line and try things out interactively if you like.

We used Notepad2 with Ruby Support for our editor, although I'm deeply digging "e" the TextMate for Windows and will likely move over to it for my text editor of choice when I start at Microsoft in September.

Next, we tried to add a require statement to bring in the Wesabe library. You run a IronRuby app (today, in this build) by running rbx.exe YourApp.rb. We put our .rb text file in one folder and had rbx.exe in another, so when we added our wesabelib.dll we had to add it to the same folder that rbx.exe was in, otherwise the Assembly load would fail. You can also put things in the GAC and get to them there. Just remember that Fusion will load from the same folder rbx.exe is in, not the folder that your program is in.

For debugging, we used the classic "got here" debugging of old, via the standard MessageBox, so we added a requre for Windows Forms also and a constant as a alias for the MessageBox class:

require 'System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
require 'wesabelib, Version=1.0.0.0'
MessageBox = System::Windows::Forms::MessageBox

As this build is a pre-Alpha, likely created in the dark of the night just hours ago, there's all sorts of weirdness and stuff that didn't "just work."

The naming integration with .NET is persnickety at best. The C# Wesabe client had a lowercase class called "wesaberest" that IronRuby just couldn't see, so I changed it to uppercase and aliased it with a constant.

#this build of IronRuby doesn't like Class Names that are lowercase,
# so I made mine Uppercase
Wesabe = Wesaberest

As I said, The Wesabe client API that we created is written in C#, and returns a array of Account objects. In the click event of a button, we pass in the username and password from our TextBox and PasswordBox respectively and store our Account object.

Our goal is to spin through the list of Account objects in the Array, but there's a few gotchas, again because of the pre-alphaness. First, we can't use for because in Ruby for is a sugar syntax on top of "each" that assumes an each method exists. Each isn't implemented yet, but the plan is to have for syntax just work over IEnumerable things.

Second, we can't index into arrays because the [] operator isn't implemented yet, but we can call GetValue(), which is the same thing.

#in the future we'll be able to index into CLR arrays...
#oneAccount = myAccounts[i]
oneAccount = myAccounts.GetValue(i)

Thirdly, in IronRuby there's FixNum, which is a DLR extension of Integer and there's MutableStrings, that have private StringBuilder. A lot of this will be hidden in the future, but in this build I had to be aware of some of the impedance mismatches as I tried to coerce my numbers into strings for output.

For example, none of these worked, but I expect they will one day.

Lastly, currentBalance is a float (System.Single) and IronRuby wouldn't respect it's ToString method, so I changed the value to a System.Double and all was fine.

Here's some failed attempts at String Concatenation. Notice the "secret" call to to_clr_string. Those should all go away and the Ruby types should marshal cleanly in most cases. Note that I'm assuming a lot, and in some cases basing these assumptions on my chats with John, but also as educated guesses as to how it ought to work.

#Ruby string concatenation works fine...
my_message.content = "scott" + " hanselman"

#Attempt0
my_message.content = oneAccount.name + oneAccount.currentBalance.to_string("C").to_clr_string

#Attempt1
my_message.content = oneAccount.name + "$" + oneAccount.currentBalance.to_s.to_clr_string

#Attempt2
my_message.content = System::String.Concat("$" + oneAccount.currentBalance.to_s.to_clr_string)

#Attempt3
#There's a name conflict when trying to use the StringBuilder:
# System.MemberAccessException: uninitialized constant ScriptModule::Text
s = System::Text::StringBuilder.new
s.Append(oneAccount.name)
s.Append("$")
s.Append(oneAccount.currentBalance.to_s.to_clr_string)
my_message.content = s.to_string

At this point, I just added a new property called "DisplayString" in the Wesabe Account class and moved on. As for the loop we used a while. It's not clean, but it's clean enough:

my_button.click do |sender, args|
   a = Wesabe.getAccounts(text_user.text,text_password.password)
   myAccounts = a.Items
   i = 0
   accounts_listbox.items.clear
   while (i < a.Items.Length)
      oneAccount = myAccounts.GetValue(i) #GetValue is on System.Array

      i+=1 #needed for the while
      
      # Create new label
      my_message = Label.new
      my_message.font_size = 36
      my_message.content = oneAccount.DisplayString

      #Add the Label to the ListBox
      accounts_listbox.items.add(my_message)
   end
end 

There's a couple of ways to make this even more clean, and those ways will come. I'll update this sample as the new features roll into IronRuby.

I think it'd be nice to add some charts and graphs, make the whole thing a ClickOnce app, etc. Probably, for now, it'll be easier in C#, but it was fun to hack it together in today's drop of IronRuby and the interns learned something.

You can get my source for this Ruby App, along with a custom build of the Wesabe client (with my changes) here. Remember to get the IronRuby source, compile it, then copy the Wesabe client to the bin\release folder (or move the .rb files into there along with the Wesabe dll). I've left all the comments in the source for fun.

Technorati Tags: , , , ,

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

XmlSerializing a Generic List<>

April 17, '07 Comments [10] Posted in Programming | XmlSerializer
Sponsored By

There's lots of good techniques out there for using the XmlSerializer to serialize a Generic List<> type. You can implement IXmlSerializable and some other trickiness.

Here's the simplest possible thing I could do to make it work while working on a hobby project over lunch today with a friend.

UPDATE: I'm a dork and didn't see the forest for the trees on this one. I don't need a parallel array at all. That's my old 1.1 brain creeping in. Thanks to folks in the comments. It works fine just like this. I'm not sure why I thought it didn't work when I tried it before. Thanks folks!

namespace Poo
{
    public class Foo
    {
        public Foo() { FooReadings = new List(); }

        [XmlArray("FooReadings")] //Even this attribute isn't really needed if you accept the default.
public List<FooReading> FooReadings; } public class FooReading { public FooReading() { } public FooReading(DateTime date, decimal thing2) { this.Thing2 = thing2; this.Date = date; } public decimal Thing2; public DateTime Date; } }

The technique below is useful for other things, but not in this instance.

namespace Poo
{
    public class Foo
    {
        public Foo() { FooReadings = new List(); }

        [XmlIgnore]
        public List<FooReading> FooReadings;

        [XmlArray("FooReadings")]
        public FooReading[] ArrayFooReadings
        {
            get { return FooReadings.ToArray(); }
            set { FooReadings = new List(value); }
        }
    }

    public class FooReading
    {
        public FooReading() { }

        public FooReading(DateTime date, decimal thing2)
        {
            this.Thing2 = thing2;
            this.Date = date;
        }

        public decimal Thing2;
        public DateTime Date;
    }
}

The "Real List<>" is ignored and the "fake" one is presented as an Array in the Getters/Setter. Not pretty, to be sure.

What better ways are there that you prefer?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Page 1 of 7 in the XmlSerializer category Next Page

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.