Starter Site Architecture Part 1 - SiteModule and SiteContext
For the first section of the Architecture Series I'm going to concentrate on the Starter Site. I'll be covering the Control Library in a later series of posts.
The main code of the site lives in the App_Code directory. For those new to ASP.NET 2.0, the contents of this directory are compiled together into an assembly that is used by the entire site. All common classes should live inside of App_Code.
Inside App_Code lies two important classes and they hold the foundation to the Starter Site. The first, SiteModule, is invisible in the day-to-day code, but it sets the stage. The second, SiteContext, is explicitly used nearly everwhere.
The first class to look at is CommerceSite.SiteModule in the file App_Code\SiteModule.cs. This module is the entry point of the Starter Site application. Note that we could have just as easily used global.asax to house this code, however we chose a Module format so that we could derive from CommerceModule allowing us to enforce a dependency on Commerce Server (line 38).
SiteModule plugs into three places in the request life-cycle in order to do some important work:
- OnBeginRequest (line 51) - during this phase the SiteModule initializes the SiteContext object for this request. The SiteContext class is one of the most important classes in the starter site and I will cover it in more detail later in this post.
- OnAuthenticateRequest (line 56) - during this phase the SiteModule calls into the SiteSecurity class to recognize the user and enforce password change policy. Starter Site has three classes of users: anonymous, recognized, and authenticated. The latter two classes are registered users (no distinction is made between a new anonymous user and a recognized anonymous user). This, also, will be covered in more detail in a later post.
- OnPreRequestHandlerExecute (line 62) - this event might be new to some people, it's not often used. This event gets called after ASP.NET determines which HttpHandler is used to handle the request but before the handler gets executed. It is at this point that SiteModule accesses the PageContext settings for the current page. PageContext uses attributes of the handler class to determine properties of the page and this is the soonest we have an instance of the class. I will cover how the attributes work in a later post. The other work done in this event is to apply the user's preferences.
The ApplyUserPreferences method (line 84) does the actual work of setting preferences. Currently the only two preferences set are currency and language. They are both set from a recognized user's profile however if the user is anonymous or does not have a preferred language an attempt is made to choose a language from the user's browser preferences (several user agents support setting a list of preferred languages to use when interacting with the site). The supported languages are determined by the web config and should be an intersection of the languages supported by the site (resource files) and the catalog. Out of the box the list is set to the languages supported by the catalog even though the Starter Site itself is not localized into any language other than English. If you want to set the user's preferences based on a cookie this is the place to do it.
The SiteContext class (App_Code\SiteContext.cs) is the launching point for many of the operations on the Starter Site. It is used similarly to HttpContext and CommerceContext. SiteContext is a request singleton, meaning that only one instance exists per request and they are not shared between requests. A new SiteContext is created at the beginning of the request (see above) and it is discarded at the end of the request.
InitializeSiteContext (line 105) creates a new SiteContext and adds it to the HttpContext Items dictionary. This dictionary exists only for the life of the HttpContext object, which is also a request singleton. This method is called by SiteModule.OnBeginRequest.
Two static properties used most often are Current and Configuration. Configuration returns the SiteConfiguration object which holds the parsed contents of web.config. All properties are strongly-typed and the config section is parsed using the ASP.NET 2.0 configuration classes. The Current property holds the instance of SiteContext for the current request; this is how you access the SiteContext instance and you will see it used throughout the site.
I won't go through all of the members (you can consult the Starter Site developer documentation for that) but I will cover some of the more important ones.
- LogOff (line 173) removes the user cookie so the user is no longer recognized, but it does not deauthenticate the user from ASP.NET security. This method is intended to be called from the OnLoggedOut event handlers of ASP.NET security controls (see StandardLayout.master.cs line 91).
- ShoppingBasketName (line 136) is used to determine which basket name to use for the shopping basket. This is to differentiate between anonymous and registered users. Anonymous carts get a special name so that the cart purge utility can easily identify them.
- The BuildXXXUri (line 223 et al)methods are used to create site-specific URLs. The BuildCatalogUri methods take into account whether URL rewriting is enabled.
- When the user is anonymous (no profile associated with the user id) the call to CommerceContext.Current.UserProfile has a high overhead as it attempts to fetch the non-existant profile from the sub-system (throwing and catching an exception or two in the process). IsAnonymousUser (line 117) makes the call once and stores the result.
The members of ISiteContext are a special bunch. Because the control library is intended to be site-agnostic we needed a way to get site-specific functionality, things like product URLs, browse page, and order status display names, into the control libraries. The members are:
- GetCatalogItemUrl (line 354) - this is used by the BasketDetail and OrderDetail controls to get the URL to display for a product. If the product doesn't exist the method returns null and the control will display the display name that is in the OrderForm. This method is to allow the user to change their preferred language and get the product displayed in their current language rather than the language used when the product was placed in the cart.
- GetBrowseRootUrl (line 369) - returns the root browse page, that is the page that sits at the top of the product browse hierarchy.
- GetAssetUri (line 391) - takes a relative path and returns the Uri corresponding to the asset location for that resource. This is provided so that you can store assets (such as product images) in a site-specific place or even on a separate web server.
- GetOrderStatusDisplayName (line 410) - because the order statuses are configurable and site-specific the site controls what the display name of the order status it. This method queries the site to find the display name for a given status, such as "New Order" for "NewOrder".
And that's SiteContext and SiteModule -- two rather simple classes that every page uses.