Cuyahoga logo

Base architecture

Modified: 2008/11/20 13:04 by nickd - Categorized as: Development
Note: This outline is based on the current SVN version (2006-04-20)

Edit

Application structure

Cuyahoga consists of a number of different assemblies. The main ones are:
  • Cuyahoga.Core: The core components that have no relation with a user interface or web context;
  • Cuyahoga.Web: The aspx pages, ascx controls and core components that work within the web context;
  • Cuyahoga.Modules(.Modulename): The module assemblies.

Edit

Cuyahoga.Core

This assembly contains all of the core logic, except the UI stuff. The most important parts are Domain, Service and DataAccess (in separate Namespaces/Directories):

  • Domain: The entity classes that form the base of the application. When possible, any of the real business logic should be located here.
  • Service: The interfaces that are responsible to perform specific user-driven tasks. In an ideal situation the interface implementors (classes) should be thin and delegate as much as possible to other classes/interfaces. The UI components communicate with the service interfaces to perform actions.
  • DataAccess: The interfaces that perform the database operations. By default, NHibernate is used to make data access easier, but it's perfectly possible to use other data access techniques.

Note that the Service and DataAccess parts use interfaces to decouple everything as much as possible and to allow configurable services (for example, different authentication services).

Edit

Cuyahoga.Web

This assembly is the place for the ASP.NET Web Forms (.aspx) and User Controls (.ascx) and everything else that is UI-related (images, stylesheets). The goal is to keep the code-behind of the Web Forms and User Controls as thin as possible. No application logic should be in there. To perform application logic, the service interfaces in the Cuyahoga.Core assemblies should be utilized.

Some classes might look a little misplaced here, but they all have in common that they need the context of the web application, which is not available in Cuyahoga.Core. Examples of these are HttpModules, HttpHandlers and even the Castle IoC container wrapper (CuyahogaContainer, more about it later). The CuyahogaContainer is in the Cuyahoga.Web assembly because at run-time it lives in the HttpApplication.

Edit

Cuyahoga.Modules(.Modulename)

Cuyahoga modules are kind of little applications themselves. They are loaded by the engine that assembles the web pages and can vary in size and complexity. Every module at least needs to have one UI component (the view User Control) and one non-UI component (the module controller). Bigger modules have more view User Controls and can have their own Domain, Service and DataAccess layer, but the module controller stays the central class.

Edit

Request processing

When someone visits a page that is served by Cuyahoga, two classes are responsible for building the resulting page: first the Cuyahoga.Web.Handlers.PageHandler HttpHandler class and then the Cuyahoga.Web.UI.PageEngine class:

Edit

PageHandler

The PageHandler class intercepts the processing of the HTTP request.

Rewrite url: All urls are translated to a single .aspx page (Default.aspx) where information about the which page or section to load is put into querystring parameters. For example, http://www.cuyahoga-project.org/home.aspx is translated to http://www.cuyahoga-project.org/Default.aspx?ShortDescription=home. All rewrite rules are configurable from the web.config.

Register page in the IoC container: Now here is some tricky stuff: to enable dependency injection in ASP.NET pages, we need to register the page in the container on every request. Luckily an ASP.NET (.aspx) page also is just another class at run-time and the current instance can be obtained with PageParser.GetCompiledPageInstance(). The type of the compiled instance is registered in the container and finally, a brand new page instance is requested from the container that replaces the original instance, but now completely managed by the container. After that, processing continues just like nothing ever happened.

Edit

PageEngine

The following steps only apply to the dynamic pages (frontend of the site)

As said before, all requests are redirected to Default.aspx. This ASP.NET page doesn't have any code or html in it, but only inherits Cuyahoga.Web.UI.PageEngine. This is the class that does the rest of the processing by handling different ASP.NET events.

Edit

override OnInit

All ASP.NET control creation has to take place in the Init event to make sure all ASP.NET mechanisms like viewstate and events keep working as expected.

  • Determine node: A Node is the backbone of the page that is being built. From a Node, you'll have access to everything that is needed for a page. The node is read from the database (or cache) based on the ShortDescription (friendly url), or NodeId or SectionId.
  • Set culture: A Node contains information about its culture. The culture of the current request (via Thread.CurrentThread.CurrentCulture) is switched to the culture of the Node.
  • Check node security: It's also time to check if the current user has read access to the current node.
  • Load Template: A Node is associated with a single Template. This template has placeholders where Sections can be put.
  • Load sections: A Node has a list of associated Sections that are access points to the actual content. Also, a Template can have a list of Sections. Mostly, these are the Sections that must appear on multiple pages.
  • For each section:
    • Check section security: Is the current user allowed to view the Section content?
    • Get module instance: Every Section is associated to a certain module (StaticHtml, Articles, Forum, etc.). The module is obtained with the Cuyahoga.Components.ModuleLoader class that gets an all wired up module instance from the container.
    • Load view User Control: After optionally parsing PathInfo parameters, the module is able to tell which module user control to load. The PageEngine then loads this control and puts it in the controls collection of the placeholder that is associated to the Section.
  • Load custom menus: There might be some custom menus associated to the root Node (accessible from the current Node). These are loaded and their controls are put in the placeholder that is associated to the menu. Note that a placeholder in a template can contain both sections and menus.

Edit

override Render()

  • Render page: The final step is the actual rendering of the page. Just before rendering, the stylesheet definitions and meta tags are inserted and the action attribute of the html form is set to the virtual (original) url instead of the rewritten url, otherwise things like events go terribly wrong.

Edit

Inversion of Control

In the previous parts, 'Inversion of Control (IoC)', 'dependency injection' and 'container' were mentioned. Cuyahoga uses the Castle Windsor IoC container as its IoC container. Now why do we need this stuff, you might think? One simple explanation: it enables different Cuyahoga components or modules to use other components without ever needing to know how to obtain these other components. This creates a very loose coupled system where every class can focus on its own task (single responsibility principle). A side effect of using the Castle container is that it also facilitates lots of other nice things like NHibernate session management and automatic transaction management.

Edit

IoC in the PageEngine

The PageEngine relies on some external components to do its work:
  • ModuleLoader
  • INodeService
  • ISiteService
  • ISectionService

Normally we would need to some factory to deliver those components or instantiate and configurate these everytime when needed. With IoC we don't do that anymore, but let the container take care of this. All we need are are setter properties for these components and the container 'injects' the correct instances. It's also possible to inject dependencies via the constructor (in that case, they are required), but the PageEngine is an ASP.NET page and needs to have a default paramterless constructor. Cuyahoga page requests are all handled by the PageHandler HttpHandler (see above). This PageHandler ensures that every ASP.NET page is managed by the container (and dependencies are injected).

Edit

IoC in the admin pages

All admin pages inherit from Cuyahoga.Web.Admin.UI.AdminBasePage. Just like PageEngine, these pages need some external components that are injected via setter injection.

Edit

IoC in the module controllers

The container tries to resolve every component that is defined as constructor parameter of a module controller. It's also possible to use setter injection here for non-mandatory components. These components can be everything from the Castle/NHibernate ISessionManager, the Cuyahoga ICommonDao to the Cuyahoga ModuleLoader. As long as a component is registered in the container, it will automatically be resolved and the module controller can use it freely.

ScrewTurn Wiki version 2.0.31. Some of the icons created by FamFamFam.