V5 design, part two

Published September 12, 2008
Advertisement
So, how do the services produce their XHTML? I can hear you all quivering with curiosity and anticipation.

The services composing the site aren't all ones that you, the users, can consume directly from your web browser: there are a number of internal services as well. Amongst these internal services is the Renderer service.

The job of the Renderer service is to take some XML which describes the page content in high-level terms - it contains elements in the GDNet namespace, with names like "defaultLayout," "loginForm," "threadView" and so on - and render it to XHTML. This is done by loading the input XML into a DOM tree, and then repeatedly taking subtrees which have root elements in the GDNet namespace, and replacing them with an 'evaluation' of them. It's like the XML is one big expression tree that we're trying to determine a value for. One of the benefits of this approach is that independent subtrees can be evaluated simultaneously, on separate threads. One thread can be generating the XHTML at the top part of the page, while another thread is fetching things from the database and generating the XHTML at the bottom part of the page. The actual multithreading isn't in yet, but the structure will make it fairly straightforward to add later.

Before we get more into the Renderer, let's talk for a moment about consuming services. Consuming a service using WCF is about as easy as providing one; you need a proxy object (which Visual Studio can generate for you if you want) and then you just instantiate it and start making calls on it. This is simple code, but it's fairly repetitive, so it would be nice to avoid duplicating it in every service call.

This is where WCF's "lightly-annotated regular code" really shines. We saw last time how a service contract was just a regular in-code interface, and could be treated as such - implemented or consumed in whatever way you like. The same is true for actual service implementations - they're just plain old classes which implement service contracts. You don't have to derive them from anything, or implement any interfaces other than your service contract. This means that the implementations of my web-facing services are all free to derive from a custom class, WebFacingService, which provides various useful bits of functionality:


  • Access to a 'Session' object which stores/loads session data in a DB

  • Automatic parsing of cookies from the HTTP request (and extraction of the session ID)

  • Browser capabilities

  • Helper methods for things like redirecting the user to another URI

  • Sending XML to the renderer, and returning it to the browser with the appropriate content-type and encoding



It's not a huge class - about 170 lines of code, so far. But it means that my implementation of IDiscussionService.GetActiveTopicsPage() just looks like this:

public Stream GetActiveTopicsPage()        {            return RenderToXhtml(GDNet.Discussion.DiscussionServices.ActiveTopics);        }


GDNet.Discussion.DiscussionServices.ActiveTopics is an XML file embedded as a resource; VS generates a strongly-typed data member I can use to get its content as a string, which is what I'm passing to RenderToXhtml there. What does that XML file look like, you wonder?

               title="Active Topics">                       src="/discuss/activeTopics.json"                     pagesize="20"/>  


The XML can be equally easily generated on-the-fly by the service implementation, but as it happens, all the data that changes for Active Topics is retrieved from the JSON feed instead of being embedded directly into the page - meaning that the Active Topics page itself never actually changes.

On the client side, I've got a &#106avascript object that generates a paged, sortable listing of topics using data pulled from a JSON feed. This is pretty much a win all round: the base page is probably going to get to a state where it can be directly cached, so the only bandwidth used is in pulling the JSON feed. Changing pages or resorting by a new column happens immediately within your client instead of posting back to the server, which generally speaking is much faster - a couple of milliseconds, rather than the few hundred it takes to roundtrip to the server. And at my end, the number of requests generated by AT drops from one-per-page-or-new-sort-type to just two requests for the whole shebang.

I'm taking care to think about the downsides of client-side behavior like this, mind you. For example, this means that by default you'll be unable to get an URI for "page 3 of active topics, sorted by number of replies ascending." Is that really a huge problem? I think it isn't; Active Topics changes so regularly that referring directly to subresources within it seems pointless. There's a case for storing things like the sort mode as a user preference, which I'll explore using cookies or a #suffix to the URI.
Previous Entry V5 design
0 likes 1 comments

Comments

benryves
Sounds good, and you're making me jealous with all your shiny WCF tech. [smile]

Speaking of linking to particular pages, a current issue is that when you click on a post link, it converts the post ID into a thread ID and page number. People have a habit of clicking the link then copying out the address bar, rather than copying it directly, which causes problems if they have a different number of posts-per-page to you.

I look forwards to seeing the results of your work!
September 16, 2008 05:05 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement