The Weekly Source Code 32- Atom, AtomPub and BlogSvc, an AtomPub Server in WCF
In my new ongoing quest to read source code to be a better developer, Dear Reader, I present to you thirty-second in a infinite number of posts of "The Weekly Source Code."
UPDATE: The BlogSvc.NET code has been refactored considerably, so consider checking out the Recent CheckIns to see what's changed..
BlogSvc.NET - The AtomPub Server for WCF and .NET
Much respect to people who not only release Open Source Software, but also take the time to get a nice, clean logo for their project as BlogSvc.NET did. It's written by Jarrett Vance and the code is up on CodePlex with Documentation and details on the main BlogSvc.NET site.
It also appears that the site at http://www.blogsvc.net is also the Demo Site for BlogSvc itself! Actually, if you download the source, you're downloading the complete implementation of the BlogSvc.net website. Booyachaka. Much respect.
Since the project uses Atom and AtomPub he can work against it using Windows Live Writer. You can learn all about how WLW likes Atom by reading the series of posts the most excellent Joe Cheng of the Live Writer team did on how this is all implemented.
Made it this far and wondering what the heck it all means? Let us turn to Wikipedia who gets it mostly right this time.
The name Atom applies to a pair of related standards. The Atom Syndication Format is an XML language used for web feeds, while the Atom Publishing Protocol (short AtomPub or APP) is a simple HTTP-based protocol for creating and updating web resources.
One way to look at it is that RSS is as a syndication format that looked like XML, but didn't really respect XML at its heart. Atom does. For publishing to blogs or content sites, the Blogger/MetaWeblog APIs are based on XML/RPC (a protocol that you either love or it makes you ill. Or both) while AtomPub is based on the RESTmodel and Atom and went through a more Standardsy adoption process. Ultimately it's a safe bet to use Atom and/or AtomPub. Everyone's pretty much on board. Microsoft got on board earlier this year. My hero Pablo Castro, the Principal Architect of ADO Data Services is supporting AtomPub in his project.
That said, BlogSvc is:
"...an open source implementation of the Atom Publishing Protocol. It is built on top of a provider model. There are providers for the file system and databases. The service is compatible with Live Writer."
Enough chatter, let's see some code. It's 0.3, but it's marching right along. When Jarrett starts using .NET 3.5 SP1, he'll likely get to do some "coding via subtraction" and swap out a lot of the boring Atom Object Model code with the new System.ServiceModel.Syndication and use the ServiceDocument. Jarreyy might be able to remove most of his "BlogService.Core" project. The new 3.5 SP1 stuff includes Atom and AtomPub object models as well as an Rss20 object model. It'll be interesting to see if those new object models can stand up against Argotic.
He's got a pretty comprehensive IAtomPub namespace with the UriTemplates that correspond to the "tag" URI Scheme from RFC 4151 that he's complying with.
[ServiceContract]
public interface IAtomPub
{
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, UriTemplate = "{workspaceName}/{collectionName}")]
Stream CreateResource(string workspaceName, string collectionName, Stream stream);
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, UriTemplate = "{workspaceName}/{collectionName}/*")]
Stream Annotate(string workspaceName, string collectionName, Stream stream);
[OperationContract]
[WebGet(UriTemplate = "{workspaceName}/{collectionName}/*")]
Stream RetrieveResource(string workspaceName, string collectionName);
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, UriTemplate = "{workspaceName}/{collectionName}/*", Method = "PUT")]
Stream UpdateResource(string workspaceName, string collectionName, Stream stream);
[OperationContract]
[WebInvoke(UriTemplate = "{workspaceName}/{collectionName}/*", Method = "DELETE")]
void DeleteResource(string workspaceName, string collectionName);
[OperationContract]
[WebInvoke(UriTemplate = "{workspaceName}/{collectionName}/*", Method = "HEAD")]
void HeadResource(string workspaceName, string collectionName);
[OperationContract]
[WebGet(UriTemplate = "service")]
AppService RetrieveService();
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, UriTemplate = "service", Method = "PUT")]
AppService UpdateService(AppService serviceDoc);
[OperationContract]
[WebGet(UriTemplate = "{workspaceName}/{collectionName}/category?scheme={scheme}")]
AppCategories RetrieveCategories(string workspaceName, string collectionName, string scheme);
[OperationContract]
[WebGet(UriTemplate = "{workspaceName}/{collectionName}")]
AtomFeed RetrieveFeed(string workspaceName, string collectionName);
}
Now, let's look at CreateMedia which would be called as a part of CreateResource for any type of data that's not an Atom content type. CreateMedia is the kind of thing you'd want to do if you were attaching a picture or a video to your blog post.
public AtomEntry CreateMedia(string workspace, string collection, Stream stream)
{
try
{
if (!Authenticate(true)) return null;
string slug = WebOperationContext.Current.IncomingRequest.Headers["Slug"];
string contentType = WebOperationContext.Current.IncomingRequest.ContentType;
string etag;
AtomEntry entry = Atom.Provider.GetService().GetCollection(workspace, collection).CreateMedia(stream, slug, contentType, out etag);
WebOperationContext.Current.OutgoingResponse.ContentType = Atom.ContentTypeEntry;
WebOperationContext.Current.OutgoingResponse.Headers["Content-Location"] = entry.Location.AbsoluteUri;
WebOperationContext.Current.OutgoingResponse.ETag = etag;
WebOperationContext.Current.OutgoingResponse.SetStatusAsCreated(entry.Location);
return entry;
}
catch (ResourceNotFoundException rnfe)
{
WebOperationContext.Current.OutgoingResponse.SetStatusAsNotFound(rnfe.Message);
}
catch (InvalidContentTypeException icte)
{
WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotAcceptable;
WebOperationContext.Current.OutgoingResponse.StatusDescription = icte.Message;
}
catch (Exception ex)
{
WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError;
WebOperationContext.Current.OutgoingResponse.StatusDescription = ex.Message;
}
return null;
}
Personally I'd have done a little refactoring in and pulled out WebOperationContext.Current.OutgoingResponse into a variable, but you get the idea. I think there's some more opportunity for some larger, more profound refactorings as this method looks a lot like most of the others. CRUD (Create, Read, Update, Delete) code does tend to repeat.
I think Jarrett will be able to move all the try/catch and the caching work for eTags and what not into some helpers. The project site says the next step is to add some authentication and have that factored out into a module, and that's confirmed by his copious TODO comments.
I really enjoy reading code where someone, like Jarrett, has taken the time to actually put in TODO: comments. It's easier to get into the programmers head if you know where they are going, and TODOs also tell you where they've been. I can tell when I'm reading this that the project owner knows what's good and what's not, and from that, gauge the trajectory of the project.
Great stuff and I hope that we see more work like this in the .NET Open Source space around Atom and AtomPub. Why should you care? Perhaps you work for a company that has a content management system, or a sales catalog system where sales people want to edit project web pages. Why not put something like BlogSvc in front of it and let your sales folk use Windows Live Writer or some other AtomPub compliant editor? The possibilities are really endless.
WEIRD SIDE NOTE: I had a weird COM Interop Error when opening his "Web.csproj" - I'd get "System.Runtime.InteropServices.COMException." I opened the csproj in notepad and noticed two things. First, the IISUrl was set to his computer name, http://rocket, so I changed that to http://localhost. The other was that it said UseIIS="True" so I suspect that was a lame error message trying to tell me that I didn't have the IIS 6.0 compatibility extensions on my Vista system. I set it to UseIIS="False" and the project loaded fine.
CONFIRMED: I ran into this bug on Connect and I shall now yell about it internally.
It'll be interesting to see how BlogSvc and ADO Data Services in 3.5 SP1 intersect. BTW: You can learn more about ADO Data Services by watching the How Do I videos on MSDN. (Yes, the video player sucks, but you can download the WMVs.)
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
P.S. ServiceDocument http://www.hanselman.com/blog/ServiceDocument is 404
DJ Park was interviewed recently by Charlie Calvert, and they talked about this new feature (at the end of the video). I wrote about it here on my blog.
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, UriTemplate = "{workspaceName}/{collectionName}/*", Method = "PUT")]
Stream UpdateResource(string workspaceName, string collectionName, Stream stream);
This one throws excption
System.InvalidOperationException: For request in operation UpdateResource to be a stream the operation must have a single parameter whose type is Stream.
Comments are closed.