Gregor.Media
Sunday, November 5, 2006
Content Rendering
Note: This project is still under construction, altough the basic nuts and bolts are in place.
Gregor.Media has started out as a simple CGI application providing a configurable web interface to my personal music database. The original version can still be found at the downloads page.
The domain of an organizer application for music and other media is also the reason for the project name. Somehow, I got stuck with that name. So let's be historical, and interpret "media" to mean "content", and we'll be fine.
Various Output Formats
Gregor.Media is meant for rendering textual content of any kind. While so far, the focus is on HTML rendering, there's more on the horizon:
- Rendering UI markup adhering to other schemas. Think of XAML and other XML-based user interface languages.
- Inspired by the former, I'm toying arround with the idea of defining an XML schema for Windows Forms applications. This way, a WinForms app could naturally work like a client/server app. Or like a rich-client web browser.
- What excites me the most is actually a combination of using HTML and WinForms. For example, HTML for displaying data, and WinForms for editing.
All of this has be integrated into WebEdit.NET. While WebEdit can already talk to a web server (using the built-in HTTP storage provider) as well as databases (with the DataDocs AddIn), what's missing is true interactivity. So I'm thinking along the lines of interfacing with a web server, but having smarter document views than are available right now. For example, Web View provided by the WebBrowser AddIn doesn't have a clue about hyperlinks; and nowhere are there real forms for editing structured data.
Last but not least, Gregor.Media should be suitable for code generation, altough it probably won't add much to Gregor.Core's text generation features.
Handling Output
The basic Gregor.Media project offers a synchronous request/response model for clients. It can be hosted by a CGI application, and also it offers a few utility modules for making just that easier - but that's just one option.
As a class library, it is easy to integrate into any application, like a Windows application that needs flexible UI/content rendering, or a simple console program (a code generator, for instance).
Project Interna
Mainly, Gregor.Media uses the following goodies of the Gregor.NET framework:
- The text generation facilities defined in Gregor.Core.
- The .NET Console code interpreter.
The solution actually consists of several projects, as outlined below.
Gregor.Media Project
The central project is Gregor.Media, a class library. It defines the base rendering mechanisms and configuration options (see the root namespace, and the Configuration sub namespace). Everything else (the types in all other namespaces) is optional:
- For instance, there are data access and meta data classes for those cases where the data to render comes from a relational database - see the DataAccess sub namespace.
- The DomainModel sub namespace is intended for providing uniform interfaces for domain-specific data. So far, there's the IDomainObject tagging interface.
- For CGI applications using the rendering engine, the Gateway sub namespace offers the ServerEnvironment module, which assists in reading in the client's request parameters. The ServerOutput module helps writing a proper HTTP response the standard output.
- The GUI sub namespace is quite empty so far. Think of it as an equivalent of the Gateway sub namespace for Windows applications. It will provide generic helpers for rendering a GUI from XML (and related) output.
The rendering engine is configured with several configuration files. There is one starter file (see the Gregor.Media.Music project for an example), which describes the page structure, assemblies to load, as well as any additional parameters. It's represented by the Configuration.CConfigInfo class:
<Config> <Assembly name="Gregor.Media.Music.dll" /> <Param name="config-name" value="Media" /> <Param name="config-file" value="Gregor.Media.Music.xml" /> <Param name="connection-string" value="Provider=... /> <Param name="css-stylesheet" value="/styles/media.css" /> <Param name="trace" value="0" /> <Page name="Page1"> <Sheet name="Sheet1" pathinfo="Sheet1.PathInfo.xml" /> <Sheet name="Sheet2" pathinfo="Sheet2.PathInfo.xml" /> </Page> </Config>
Note that while the Gregor.Media libary parses all of the elements in this example, it doesn't process all of this information. Assemblies will be loaded and the page/sheet structure will be set up, but the parameters are stored only, to be used by domain-specific extensions (see below).
Assembly loading serves the purpose of supplying the data from which the content is to be rendered. The common case is accessing a database, and while Gregor.Media provides helpers for that (see above), there are in fact no restrictions whatsoever on the data model - all that's needed is a graph of objects.
A page (Configuration.CPageInfo) is the basic navigation target. Pages consist of one or more sheets, but a sheet (Configuration/CSheetInfo) may appear in several pages.
Sheets are actually path info documents linking to templates (see here for some background information):
<Sheet outfilename="Gregor.Media.Music.Sheet1.html"> <Root select="Context.Root" template="Sheet1.Header.Template.html"> <Artist select="Root.AllArtists[*]" expression="Artist"> </Artist> </Root> </Sheet>
The path info document for a sheet is the first place where the code interpreter comes into place. The primary entry point into domain-specific data is - by convention - the Context object. Its class should extend Gregor.Core.Collections.CGraphContext (which helps interfacing with object graph, and keeping track of changes as the text generator walks through that graph).
The code interpreter must implement Gregor.Core.ICodeInterpreter2, as does Gregor.NetConsole.Engine.CCurlyInterpreter. It is shared among several objects - the request, the text generator, the object graph - and it is also invoked from the templates.
Here's a template:
<div> <p> Sheet1 Header (Database: $Root.Name%) </p> <p> Number of Artists: $Root.AllArtists.Count% </p> </div>
Gregor.Media.Music Project
This is a domain-specific extension. Its task is to provide data from my personal music database in a way that is easily accessible from the path information documents and templates, which it also supplies.
For now, this class library defines an extension to the context and the database connection classes, so that the interpreted code can find its way into the music object model through a central root.
Gregor.Media.Music.CgiConsole Project
This is a simple console exe runnable as a CGI application, specifically working with the Gregor.Media.Music project.
It's possible to write a generic CGI application that hosts the general content generation engine (Gregor.Media) as well as any domain-specific extension (like Gregor.Media.Music). Such an application would require a bit of configuration, and I'll probable implement one some time.
However, wiring it all together in a domain-specific host app isn't too difficult, either:
CConfigInfo config = MediaCenter.CreateConfiguration("Gregor.Media.Music"); config.Load(@"Gregor.Media.Music.xml"); CRequest request = MediaCenter.CreateRequest(config); request.Parameters.AddRange(ServerEnvironment.GetQueryParams()); string sConfigName = config.Parameters["config-name"] as string; string sConnect = config.Parameters["connection-string"] as string; using(CMusicContext context = MusicCenter.CreateContext(sConfigName, sConnect)) { request.Interpreter.SetObject(CObjectGraph.VARIABLE_CONTEXT, context); CResponse response = request.GetResponse(); string sText = response.GetText(); ServerOutput.WriteResponseHeader(); ServerOutput.WriteLine(sText); }
That is most of the executable's code - but yes, it's easy to identify the generic parts.
Gregor.Media.Music.WinFormsClient Project
This is the Windows Forms equivalent to CgiConsole program. So far, it's just a little demo of how Gregor.Media can render XAML-like markup, which can be interpreted so as to create a WinForms GUI. Here's the path info:
<Sheet outfilename="Gregor.Media.Music.WinSheet1.html" literalbefore="<WinForms>" literalafter="</WinForms>"> <Root select="Context.Root" literalbefore=" <ListView>" literalafter=" </ListView>"> <Artist select="Root.AllArtists[*]" literalbefore=" <Item>" literalafter=" </Item>" expression="Artist.Name"> </Artist> </Root> </Sheet>
The code is quite simple, but perhaps this screen shot will convey the general idea.
Gregor.Media.CodeGenerator Project
This is a helper project, compiling into a console executable. It uses the meta classes defined in Gregor.Media.DataAccess, which offer schema access for OleDb-accessible databases (CDatabaseInfo, CTableInfo, CColumnInfo) for generating C# classes. These classes can then be used in a specific object model.
The code generator uses standard text generation techniques (building a graph of meta data objects controlled with a path info document, and text templates, and the code interpreter).
It can be invoked from the command line, supplying a configuration file (like the one fed to the rendering engine itself) for reading DB connect info, a path info document, and the names of one or more tables.