Tuesday, 17 June 2008

Working with Objects in SSDS Part 1

Last time we talked about SQL Server Data Services and serializing objects, we discussed how easy it was to use the XmlSerializer to deserialize objects using the REST interface.  The problem was that when we serialized objects using the XmlSerializer, it left out the xsi type declarations that we needed.  I gave two possible solutions to this problem - one that used the XmlSerializer and 'fixed' the output after the fact, and the other built the XML that we needed using XLINQ and Reflection.

Today, I am going to talk about a third technique that I have been using lately that I like better.  It uses some of the previous techniques and leverages a few tricks with XmlSerializer to get what I want.  First, let's start with a POCO (plain ol' C# object) class that we would like to use with SSDS.

public class Foo
{
    public string Name { get; set; }
    public int Size { get; set; }
    public bool IsPublic { get; set; }
}

In it's correctly serialized form, it looks like this on the wire:

<Foo xmlns:s="http://schemas.microsoft.com/sitka/2008/03/"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:x="http://www.w3.org/2001/XMLSchema">
  <s:Id>someid</s:Id>
  <s:Version>1</s:Version>
  <Name xsi:type="x:string">My Foo</Name>
  <Size xsi:type="x:decimal">10</Size>
  <IsPublic xsi:type="x:boolean">false</IsPublic>
</Foo>

You'll notice that we have the additional system metadata attributes "Id" and "Version" in the markup.  We can account for the metadata attributes by doing something cheesy like deriving from a base class:

public abstract class Cheese
{
    public string Id { get; set; }
    public int Version { get; set; }
}

However this is very unnatural as our classes would all have to derive from our "Cheese" abstract base class (ABC).

public class Foo : Cheese
{
    public string Name { get; set; }
    public int Size { get; set; }
    public bool IsPublic { get; set; }
}

Developers familiar with remoting in .NET should be cringing right now as they remember the hassles associated with deriving from MarshalByRefObject.  In a world without multiple inheritance, this can be painful.  I want a model where I can use arbitrary POCO objects (redundant, yes I know) and not be forced to derive from anything or do what I would otherwise term unnatural acts.

What if instead, we derived a generic entity that could contain any other entity?

public class SsdsEntity<T> where T: class
{
    string _kind;

    public SsdsEntity() { }

    [XmlElement(Namespace = @"http://schemas.microsoft.com/sitka/2008/03/")]
    public string Id { get; set; }

    [XmlIgnore]
    public string Kind
    {
        get
        {
            if (String.IsNullOrEmpty(_kind))
            {
                _kind = typeof(T).Name;
            }
            return _kind;
        }
        set
        {
            _kind = value;
        }
    }

    [XmlElement(Namespace = @"http://schemas.microsoft.com/sitka/2008/03/")]
    public int Version { get; set; }

    [XmlIgnore]
    public T Entity { get; set; }
}

In this case, we have simply wrapped the POCO that we care about in a class that knows about the specifics of the SSDS wire format (or more accurately could serialize down to the wire format).

This SsdsEntity<T> is easy to use and provides access to the strongly typed object via the Entity property.

foomembers

Now, we just have to figure out how to serialize the SsdsEntity<Foo> object and we know that the metadata attributes are taken care of and our original POCO object that we care about is included.  I call it wrapping POCOs in a thin SSDS veneer.

The trick to this is to add a bucket of XElement objects on the SsdsEntity<T> class that will hold our public properties on our class T (i.e. 'Foo' class).  It looks something like this:

[XmlAnyElement]
public XElement[] Attributes
{
    get
    {
        //using XElement is much easier than XmlElement to build
        //take all properties on object instance and build XElement
        var props =  from prop in typeof(T).GetProperties()
                     let val = prop.GetValue(this.Entity, null)
                     where prop.GetSetMethod() != null
                     && allowableTypes.Contains(prop.PropertyType)
                     && val != null
                     select new XElement(prop.Name,
                         new XAttribute(Constants.xsi + "type",
                            XsdTypeResolver.Solve(prop.PropertyType)),
                         EncodeValue(val)
                         );

        return props.ToArray();
    }
    set
    {
        //wrap the XElement[] with the name of the type
        var xml = new XElement(typeof(T).Name, value);

        var xs = new XmlSerializer(typeof(T));

        //xml.CreateReader() cannot be used as it won't support base64 content
        XmlTextReader reader = new XmlTextReader(
            xml.ToString(),
            XmlNodeType.Document,
            null);

        this.Entity = (T)xs.Deserialize(reader);
    }
}

In the getter, we use Reflection and pull back a list of all the public properties on the T object and build an array of XElement.  This is the same technique I used in my first post on serialization.  The 'allowableTypes' object is a HashSet<Type> that we use to figure out which property types we can support in the service (DateTime, numeric, string, boolean, and byte[]).  When this property serializes, the XElements are simply added to the markup.

The EncodeValue method shown is a simple helper method that correctly encodes string values, boolean, dates, integers, and byte[] values for the attribute.  Finally, we are using a helper method that returns from a Dictionary<Type,string> the correct xsi type for the required attribute (as determined from the property type).

For deserialization, what happens is that the [XmlAnyElement] attribute causes all unmapped attributes (in this case, all non-system metadata attributes) to be collected in a collection of XElement.  When we deserialize, if we simply wrap an enclosing element around this XElement collection, it is exactly what we need for deserialization of T.  This is shown in the setter implementation.

It might look a little complicated, but now simple serialization will just work via the XmlSerializer.  Here is one such implementation:

public string Serialize(SsdsEntity<T> entity)
{
    //add a bunch of namespaces and override the default ones too
    XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
    namespaces.Add("s", Constants.ns.NamespaceName);
    namespaces.Add("x", Constants.x.NamespaceName);
    namespaces.Add("xsi", Constants.xsi.NamespaceName);

    var xs = new XmlSerializer(
        entity.GetType(),
        new XmlRootAttribute(typeof(T).Name)
        );

    XmlWriterSettings xws = new XmlWriterSettings();
    xws.Indent = true;
    xws.OmitXmlDeclaration = true;

    using (var ms = new MemoryStream())
    {
        using (XmlWriter writer = XmlWriter.Create(ms, xws))
        {
            xs.Serialize(writer, entity, namespaces);
            ms.Position = 0; //reset to beginning

            using (var sr = new StreamReader(ms))
            {
                return sr.ReadToEnd();
            }
        }
    }
}

Deserialization is even easier since we are starting with the XML representation and don't have to build a Stream in memory.

public SsdsEntity<T> Deserialize(XElement node)
{
    var xs = new XmlSerializer(
        typeof(SsdsEntity<T>),
        new XmlRootAttribute(typeof(T).Name)
        );

    //xml.CreateReader() cannot be used as it won't support base64 content
    XmlTextReader reader = new XmlTextReader(
        node.ToString(),
        XmlNodeType.Document,
        null);
    
    return (SsdsEntity<T>)xs.Deserialize(reader);
}

If you notice, I am using an XmlTextReader to pass to the XmlSerializer.  Unfortunately, the XmlReader from XLINQ does not support handling of base64 content, so this workaround is necessary.

At this point, we have a working serializer/deserializer that can handle arbitrary POCOs.  There are some limitations of course:

  • We are limited to the same datatypes that SSDS supports.  This also means nested objects and arrays are not directly supported.
  • We have lost a little of the 'flexible' in the Flexible Entity (the E in the ACE model).  We now have a rigid schema defined by SSDS metadata and T public properties and enforced on our objects.

In my next post, I will attempt to address some of those limitations and I will introduce a library that handles most of this for you.

Tuesday, 03 April 2007

Expression Web and Blend on MSDN soon

Straight from Somasegar.  This is good news for developers trying to fit in with their visual counterparts.  Initially these were targeted at the professional designer instead of the developer.  I personally think that those two overlap quite a bit.  It was a good move to put this into developer's hands.  You can be sure that they will get used now...

Thursday, 12 October 2006

XSD Object Generation for .NET 2.0

One of my co-workers forwarded this link to me today.  Essentially, it is XSDObjectGen for 2.0 with support for things like partial classes, generics, etc.  We tend to use this as a basis for our service contracts and to help serialize outward facing DTO objects.  I was looking for something similar (or the source to XSDObjectGen to do it myself) a few months back.  Very cool.

Get it here:

http://devauthority.com/blogs/ram_marappan/archive/2006/10/03/4755.aspx

Thursday, 23 June 2005

A quick reminder on Dispose and foreach

Keith points out today that the foreach loop does not actually call Dispose() on the items being enumerated – only on the enumerator itself.  That is actually news to me – I was always under the assumption that Dispose() would be called on the iterated object when it left scope.

Why should you care?  For anyone programming System.DirectoryServices, this actually has a big impact.  Consider the following, very common code:

        DirectoryEntry entry = new DirectoryEntry(
            "LDAP://dc=mydomain,dc=com",
            null,
            null,
            AuthenticationTypes.Secure
            );
 
        using (entry)
        {
            foreach(DirectoryEntry child in entry.Children)
            {
                //do something
            }
        }

Whoops, we have potentially many undisposed DirectoryEntry objects here.  Given that the SDS namespace has a couple problems with leaking memory, we always recommend you Dispose() your objects in SDS.  This will not do it for you (and no, the ‘entry’ will not call Dispose() for its children either here).

In general, always explicitly call Dispose() on the following object types:

  • DirectoryEntry
  • SearchResultCollection (from .FindAll())
  • DirectorySearcher (if you have not explicitly set a SearchRoot)

Keep this is mind now with the foreach loops as well.

 

 

Tuesday, 07 June 2005

TechEd 2005 to this point

I have been attending TechEd 2005 this week and spending the vast majority of my time in the Server Infrastructure Cabana lounge.  I am there answering AD/MIIS questions as best as I can.  It has been an interesting experience as I will readily admit that I know relatively little about technical infrastructure as it relates to DNS and AD.  I happen to know quite a bit about programming against Active Directory, but the majority of users to this point have been asking more questions related to Active Directory topology and replication errors they are seeing than anything related to using ADSI and/or System.DirectoryServices.  When this occurs, I hand them off to some really amazing resources working there that seem able to troubleshoot just about anything in AD.  I have learned quite a bit listening to them in this way myself – just fascinating.

In terms of sessions so far, I have attended a session on ADFS (Active Directory Federation Services), which is being pitched as essentially a web single sign-on solution (SSO) that will be released with 2003 R2 (in beta now).  I previewed this technology a few months back and it has essentially only had minor changes.  It seems to be an interesting solution.  It does not provide all the functionality of some of the vendor available options on the market today like Netegrity, Cleartrust, Oblix, etc., but it certainly fills out a nice portion of that functionality and for a price point that is hard to argue with (it’s free with Windows 2003).  I would expect adoption of this technology to be rapidly be adopted by 6 months after release – it is just that compelling.  It will be released in phases with more functionality scheduled for later.  Expect only web SSO at first, moving to smart clients and SOAP next, and I am speculating here, even more complicated transports in the future (Citrix, RDS, anyone?).

Next, I attended Clemens Vasters’s session on asynchronous design.  He had some really interesting things to say about why and how to design for asynchronous transport.  The only complaint I had was that the demonstrations were much too quick to see what was really going on.  Luckily, he is going to be posting his sample code on his blog so I can inspect it later.  He is releasing a fairly substantial and full featured MSMQ Listener he created that greatly simplifies using ASMX and WSE providers with MSMQ in a pretty seamless and easy manner.  Good stuff, and I intend to dig deeper later this week.

Lastly, I have been very busy to this point and the situation has only been complicated by the utterly abysmal wireless access here this week.  I have yet to be to an MS event where wireless could stand up to the thousands of attendees.  It is almost completely unusable.  My hotel is actually competing with the convention this year to see which can provide crappier service – so far it is neck and neck.  You don’t realize how much you need the Internet until it is gone…

 

Thursday, 10 March 2005

Frank Gehry and totally impractical designs

Why does everyone like Frank Gehry's work? Personally, being from Seattle, I thought the Experience Music Project (EMP) building was just ok. I went to the place only once and it was very unwelcoming with concrete floors and spartan utility. Then, when I came to Cleveland my Alma Mater decided to get a Frank Gehry designed business school. I spent a lot of time in this building taking classes and I can easily say that as interesting as the outside looks - it is completely impractical and crap-tacular in the inside. This is a business school - and it is wall to wall concrete and spartan design again. There are no rugs, there are no areas where students can congregate and mingle. There is a creepy feeling of a penal institution and a thoughts that Frank might actually have been drunk when he designed the building:

  • To Navigate the entire 3rd floor, you would have to get in an elevator from the 3rd, bring it back down to the 1st, get out and walk the length of the building and then get in another bank of elevators to go back up to the 3rd floor. I mean, why would anyone think that a 3rd floor should be continuous? Let me tell you the joy of trying to find room 311A - you can see it, but you can can't get to it.
  • Bathrooms, or more precisely the lack thereof. So, there are like 2 bathrooms that are accessible to most people in the building and only 1 of them has more than 1 toilet. Brilliant! I am sure everyone enjoys queuing up outside the bathroom like a bunch of girls at a downtown nightclub.
  • To be a "modern" classroom, we should have huge server racks installed into each lecture hall. I mean, listening to the roar of what sounds like angry custodians armed with Dustbusters is conducive to concentration for every student.
  • Snow... news flash, it snows in Cleveland occasionally. Designing a roof that tends to hold snow only long enough to gain critical mass and send an icy wall of white death from above is generally not in the public's best interest. I loved seeing the avalanches cascade down the side of the building in a low rumble reverberating inside. Good times... Good times.
  • Cold... so like, every now and then (5 out of 12 months) it is friggin freezing in Cleveland. Concrete is a fantastic choice of building materials - if you want a swimming pool. Yes, the freezing temperatures and lack of drains in the building caused major flooding when the not-so-properly insulated pipes burst and wiped out much of the basement level in the building... not once, but at least 2 (maybe 3) times. I got a kick out of dozens and dozens of computers up on the 1st floor drying out with signs on the "DO NOT PLUG IN!!". Great place for the computer lab...
It came as no shock to me to see this story the other day highlighting his other absurdity down in CA. Good grief, part of good design is an understanding of where it will be used. Frank needs to quit focusing on trying to be 'unique' (or weird) and get back to the basics - like creating a building that is actually useful for something other than providing lots of neat places to hide from bullets.