May 2005 - Posts

I take my first steps with the May Avalon/Indigo Beta 1 RC

It's good to have a version at last that works with Visual Studio 2005 Beta 2 - apart from the benefits of at least one element of the development setup being relatively stable and robust, it was horribly confusing maintaining different test environments with different versions of the tools.
It's also encouraging to see the spookily-independant-pillars-of-Longhorn (good name for a band, that) inching towards Beta: not only is that encouraging from the point of view of wanting these things to actually see daylight one day rather than lingering on forever in a CTP half-world (in which case Microsoft would have to be renamed Mordorsoft), but it also gives cause to hope that the day might not be far off when things like styling and binding syntax are not drastically refactored every couple of months.

That day has of course not yet come :-)
It goes without saying that I have a lot of catching up to do.
I thoroughly recommend Tim Sneath's article A Hitchhiker's Guide to Avalon Beta 1 RC (original link via Lauren Lavoie's blog) - it's an excellent introduction to some of the changes and especially new features in this release, with clear and relevant examples. Microsoft documentation writers please read and learn One of the features Tim introduces is support for speech (both recognition and synthesis), provided the speech APIs are installed on the machine (which is one reason I'm pleased that I'm using a Tablet PC as my Avalon/Indigo/WinFX test machine). I tried the synthesis side last night and I'm pleased to report that it not only works, but it's as easy to use as it should be. It took me a little while to hunt down the necessary reference (it was cunningly hidden as er, Speech. How was I to know?), but after that it was laughably simple.
Visual Studio was alarmingly sluggish at returning to normal after running a speech app in debug, but I'm not sure how much of that is the SDK integration and how much has to do with the speech engine (I assume there's a lot of interop going on).

I'm still not sure what to make of the new project type, Avalon “Express“ applications. Apparently these are effectively ClickOnce for Avalon, and are tightly sandboxed. The sticking point for me is that they will not be able to use Indigo, which seems to limit their usefulness for data-driven applications. They can however call ASMX web services: that in itself might keep ASMX alive long after it might otherwise be regarded as having been rendered obsolete by Indigo. Actually, I'm assuming (perhaps incorrectly) that the bit about not being able to use Indigo may need to be qualified, since while I have not tried Indigo (yeah, it's on the list) I assume that it is possible to produce an Indigo service which in its relations with the outside world does not differ in any significant respect from an ASMX web service.

Now time to get my head around all the changes...

Fun with SAP and web service interop

The project I am currently working om (I mean for the day job) requires various financial information to be posted to SAP on a nightly basis. Our interface to SAP is via web services exposed from the SAP end, which act as an interface to BAPIs written by our SAP service providers. In this particular case it's a very simple web service with one method and a handful of parameters, one being a header structure and the others being structure arrays passed by reference. I'm not too keen on passing ref parameters to web services, partly because it's a bit of a hack (we all know that in reality the proxy just repopulates the parameters from return values), but mostly because it promotes an illusory way of looking at what is happening with the service (actually now that I think of it those two objections are closely related).
But never mind, what the proxy wants the proxy gets.

Except that in this case it didn't work. While I was able to verify that we were communicating successfully with the remote server, the referenced arrays were coming back as null.

Eventually I added a trace to the proxy method call and dumped the outgoing and incoming Soap messages to a file...at which point I was surprised to see that the response was being returned as expected - it just wasn't being deserialised.
Given that deserialisation was essentially controlled by attributes on the proxy, and the proxy was generated from the WSDL using the WSDL.exe tool, then it seemed that either I was using an incorrect command line switch or there was something wrong with the WSDL. I checked my parameters and checked the options for the WSDL tool: there really aren't very many, so that didn't look like it could be the problem. I then compared the attributes on one of our earlier SAP web service clients that has similar parameters but is known to work,  and noted that there were some differences in attributes where I wouldn't have expected them. When I used the WSDL tool to generate a new proxy for that service, lo and behold that one also failed to work...then acting on a hunch I tried again using the .NET Framework 1.0 version of the WSDL tool (we're now on 1.1), and it did work. Naturally I tried that on the service that my own project is calling, and that worked too. The upshot is that if I run the 1.0 version of WSDL.exe against our SAP web service WSDL, I get a valid proxy. If we run the 1.1 version against the same WSDL we get a proxy with additional attributes which appear to result in deserialisation not working.

The SAP web service is Java-based, but that has not previously been a problem for us. I have not been able to find a description of a breaking change in WSDL.exe that matches this behaviour, so I strongly suspect that it's one of those things that Microsoft regards as a bug fix...which suggests that there is something a bit dodgy in the WSDL that formerly didn't cause any problems, but which the 1.1 tool interprets more strictly in such a way that the end result is useless.

The devil as they say is in the details, and I think it's clear that web service interoperability is very dependent on disparate tools agreeing to intepret WSDL exactly the same way, which can be tricky (define same).  

We have a workaround obviously in this case in that we can simply use the 1.0 version of the WSDL tool when creating SAP proxies (which is what I'm doing now), but that's not an ideal solution.

At some point I'll have to work out exactly what in the WSDL causes the tool to generate those additional attributes, and what I need to change to get it not to. Then I think I'll use an XSLT transformation as a kind of pre-processor to generate WSDL that the tool will be more happy with.

Sigh.

Some interim Atom publishing code for Blogger

I still haven't tracked down the source of The Dreaded Supernumerary DIV, and unfortunately my day job has been intruding more into my spare time as we near the end of the current project and make the sprint for the finish line (last week I completed development of all major functionality for a our web-based booking system, and today I've started work on SAP integration for the financials. Yawwwwwwn). So I don't really have much to add Atom-wise. I also have to reinstall the February VS 2005 CTP before I can do any more with Avalon (and anything with Indigo, which I have yet to try), having had an, um, “industrial accident” with my existing installation (let's just say this morning I had the “I Really Wish I Hadn't Clicked That Button” blues). I'll deal with that over the next day or so.
So to keep things fresh, I decided to dish up some crappy code that's currently just sitting there getting stale.

So far for my Atom experiments with Blogger I've knocked together (sorry: “Carefully architected according to what demented blind sheep on medication could be bribed to agree were best practices”) a class to handle most of the heavy lifting, and another to represent an entry. Ta da. And a few other bits. The Entry class is currently just a data bucket: that will probably change somewhat before I'm finished - I'm inclined to make it handle its own reading and writing (I don't believe in doing that with database access, but I think it makes sense for XML serialisation and deserialisation. Sometimes). I haven't used the XmlSerializer for two reasons: firstly, I want this code to be usable from the current version of the .NET Compact Framework, which lacks serialisation support, and secondly, I frankly don't have the faintest idea (without expending actual effort) how to handle the custom serialisation of the date values.

I've therefore implemented the code for writing the entry using an XmlTextWriter (I haven't integrated the reading part with the Entry class at all yet).

Anyway, here's the Entry class:

using System;
using System.Xml;
using System.IO;
using System.Text;

namespace
DalySoftware //See remarks elswhere about the namespace. Sigh.
{
   
public class Entry
    {
        
private string title = string.Empty;
        
private DateTime issued = DateTime.Now;
       
private DateTime created = DateTime.Now;
       
private DateTime modified = DateTime.Now;
       
private string content;
       
private string entryId;

       
public Entry(){}

        public Entry(string title, string content)
        {
           
this.title = title;
            
this.content = content;
        }
   
    public Entry(string title, string content, DateTime issued):this(title, content)
        {
           
this.issued = issued;
        }

        public Entry(string title, string content, string issuedAsString):this(title, content)
        {
            
this.issued = DateTime.Parse(issuedAsString);
        }

        public string Title
        {
           
set {title = value;}
           
get {return title;}
        }
       
public DateTime Issue;
        {
           
set {issued = value;}
           
get {return issued;}
        }
       
public string Content
        {
           
set {content = value;}
           
get {return content;}
        }
        
public string EntryId
        {
            
set {entryId = value;}
           
get {return entryId;}
        }
    }

}

(Indenting as usual is crappy owing to the translation-to-web effect).

And here's the bit that does all the work (and I'm sorry, I'm just too sleepy to even try to repair the line spacing and indenting this time):

using System;

using System.Xml;

using System.Net;

using System.IO;

using System.Text;

namespace DalySoftware //usual tediously dorky namespace

{

public class AtomEngine

{

private string userName;

private string password;

private HttpStatusCode statusCode;

private AuthenticationType authenticationType;

public AtomEngine(string userName, string password):this(userName, password, AuthenticationType.Basic){}

public AtomEngine(string userName, string password, AuthenticationType authenticationType)

{

this.userName = userName;

this.password = password;

this.authenticationType = authenticationType;

}

public XmlDocument GetFeed(string feedUri)

{

return CallAtomHost(AtomMethod.Get,feedUri,null);

}

public XmlDocument PostEntry(string feedUri, Entry entry)

{

return CallAtomHost(AtomMethod.Post,feedUri,entry);

}

public XmlDocument PutEntry(string feedUri, Entry entry)

{

return CallAtomHost(AtomMethod.Put,feedUri,entry);

}

private XmlDocument CallAtomHost(string method, string uri, Entry entry)

{

HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);

req.Method = method;

req.ProtocolVersion = new Version(1,1);

if(authenticationType==AuthenticationType.Basic)

{

req.Credentials = new NetworkCredential(userName,password);

}

if(method==AtomMethod.Post||method==AtomMethod.Put)

{

req.ContentType = "application/atom+xml";

}

req.Accept = "application/atom+xml";

req.PreAuthenticate = true;

req.AllowWriteStreamBuffering = true;

req.SendChunked = false;//for Blogger

if(entry!=null)

{

WriteEntry(entry, req.GetRequestStream());

}

XmlDocument response = new XmlDocument();

HttpWebResponse resp = null;

try

{

resp = (HttpWebResponse)req.GetResponse();

}

catch(WebException ex)

{

resp = (HttpWebResponse)ex.Response;

}

finally

{

if(resp.StatusCode==HttpStatusCode.OK)

{

XmlTextReader reader = new XmlTextReader(resp.GetResponseStream());

response.Load(reader);

reader.Close();

}

statusCode = resp.StatusCode;

resp.Close();

}

return response;

}

private void WriteEntry(Entry entry, Stream requestStream)

{

Encoding encoding = Encoding.UTF8;

MemoryStream stream = new MemoryStream();

XmlTextWriter writer = new XmlTextWriter(stream,encoding);

writer.Formatting = Formatting.None;

writer.WriteStartDocument(true);

writer.WriteStartElement("entry", "http://purl.org/atom/ns#");

writer.WriteStartElement("title");

writer.WriteAttributeString("mode","escaped");

writer.WriteAttributeString("type","text/plain");

writer.WriteString(string.Format("{0}",entry.Title));

writer.WriteEndElement();

writer.WriteElementString("issued", string.Format("{0:yyyy-MM-ddTHH:mm:sszzzz}",entry.Issued));

writer.WriteStartElement("generator");

writer.WriteAttributeString("url","http://kevdaly.aspxconnection.com");

writer.WriteString("Atomic Blogger");

writer.WriteEndElement();

writer.WriteStartElement("content");

writer.WriteAttributeString("type","application/xhtml+xml");

writer.WriteStartElement("div","http://www.w3.org/1999/xhtml");

writer.WriteRaw(entry.Content);

writer.WriteEndElement();

writer.WriteEndElement();

writer.WriteEndElement();

writer.WriteEndDocument();

writer.Flush();

requestStream.Write(stream.GetBuffer(),0,(int)stream.Length);

requestStream.Close();

stream.Close();

writer.Close();

}

public XmlDocument DeleteEntry(string feedUri)

{

return CallAtomHost(AtomMethod.Delete,feedUri,null);

}

public HttpStatusCode StatusCode

{

get {return statusCode;}

}

public AuthenticationType AuthenticationType

{

set {authenticationType = value;}

get {return authenticationType;}

}

}

}

The casually observant reader will note that this code does not use normal Atom authentication: since it's designed for Blogger, it assumes common old garden-variety Basic Authentication over SSL (I have made some small provision for imlementing Atom authentication in the future, however). At the moment it's pretty bare, and I'll probably be refactoring it quite drastically before I'm finished, but if you want to have a play around it does give you basic POST, GET, PUT and DELETE functionality.
Sorry again about the indenting (that's where cut and paste into HTML gets you. Yet again, *Sigh*).