Notas del Terrible
Terrible Bore

Umbraco 4 Overview

February 22, 2010 18:49 by terR0Q

Umbraco won my developer’s heart. It’s the best example of how to set tasks and achieve them. In this article I made an overview of development basics and main concepts. Originally it was written in my Russian blog.

Umbraco meets developer’s reuiqrements of a true CMS completely. Infrastructure is built around concept of content documents and operations in which they are involved.

Document

Document is the fundamental of this CMS forming the very bones of it. Document types are the types of pages that can be published on website. They consist of custom set of fields. These fields are defined by specific data types that include data storage type and render control type in admin console. Fields can be stored in different tabs for ease of use.

Only several data types are available from DB point of view (Integer, Data, Ntext, Nvarchar). I didn’t check whether engine has to be recompiled in order to add new data types or not. These primitive types are coupled with logical and render types: you can define editor type or provide standard set of values that will be selected from a drop-down list. This selection may also define behaviour of data type rendering on output stage through ASP.NET templates or XSLT (later on that), but in the latter case you cane explicitly define not to escape HTML characters. There are 20 standard types that can cover most of use cases. New types can be easily added via Umbraco itself or creating and uploading new DLL that is compiled from just 3 classes (thx to Nibble) (although I didn’t check this yet).

When new document type is created a set of fields is defined for it. These fields can be split among custom categories that are added on ’Tabs’ eh... tab :-) These tabs will be rendered separately in admin GUI. Standard ’Generic Properties’ tab keeps properties that are common for all document types. Additional tabs should be used in order to categorize specific fields and increase ease site management.

Aliases and names can be applied to all entities in Umbraco. This is very important for document and field types: aliases are used to make type checks programmatically in C# or XSLT while names are just readable labels used in GUI. Additionally: icon and description can be set for any document type; document mark-up templates should be selected and child node types can be defined.

Templates are another part of Umbraco foundation. Starting from version 4 Umbraco utilizes ASP.NET master pages as templates. This is coupled by admin GUI being able to show templates dependencies as hierarchy thus making visual order management much easier (no more files garbage in tree-view). And what’s great is: Umbraco GUI provides easy means to add Content Placeholders and Content items with just a few clicks.

Any template defines page design and can use a bunch of helpful items: macros (XSLT, user controls and python modules), dictionary items (hello, localization) and document field values.

As a result we have logical division that is very important for development: data and rendering are separated, as well as functional items are separated into stand-alone controls. Note that we have all this using common ASP.NET without any MVC or MVVM. Yes, we don’t have testing and some other possibilities here, but we have many means to support from small-sized to middle-sized websites at least, and these are most cases when some general CMS should be used.

Macros

Umbraco provides simple and effective way to use macros. It uses .NET on maximum adding up some interoperability means.

Any document rendering can be achieved via XSLT, even pagination tasks. Of course you can create your own C# code coupled by user controls, but it would be wiser to use basic framework. Suppose you need to render all child documents of some specific type from some root document restricting them to 2nd level of nesting. For that purpose create XSLT file with macros (system will ask whether it has to create macros automatically) and implement following code there:

   1:  <?xml version="1.0" encoding="UTF-8"?>
   2:  <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#x00A0;"> ]>
   3:  <xsl:stylesheet 
   4:      version="1.0" 
   5:      xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
   6:      xmlns:msxml="urn:schemas-microsoft-com:xslt"
   7:      xmlns:umbraco.library="urn:umbraco.library"
   8:      exclude-result-prefixes="msxml umbraco.library">
   9:   
  10:   
  11:  <xsl:output method="xml" omit-xml-declaration="yes"/>
  12:   
  13:  <xsl:param name="currentPage"/>
  14:   
  15:  <xsl:template match="/">
  16:   
  17:      <xsl:variable name="rootTextpageNode"
select="$currentPage/ancestor-or-self::node
[@level = 2 and @nodeTypeAlias = 'CWS_Galleries']"
/>
  18:   
  19:      <div class="secondaryNav">
  20:          <h3>
  21:              <xsl:value-of select="$rootTextpageNode/@nodeName"/>
  22:          </h3>
  23:          
  24:          <ul>
  25:              <xsl:for-each select="$rootTextpageNode/node
[@nodeTypeAlias = 'CWS_Galleries']"
>
  26:                  <li>
  27:                      <xsl:if test="$currentPage/ancestor-or-self
::node/@id = current()/@id"
>
  28:                          <xsl:attribute name="class">
  29:                              <xsl:text>selected</xsl:text>
  30:                          </xsl:attribute>
  31:                      </xsl:if>
  32:                      
  33:                      <a href="{umbraco.library:NiceUrl(
current()/@id)}"
>
  34:                          <span>
  35:                              <xsl:value-of select="current()
/@nodeName"
/>
  36:                          </span>
  37:                      </a>
  38:                  </li>
  39:              </xsl:for-each>       
  40:          </ul>
  41:      </div>
  42:   
  43:  </xsl:template>
  44:   
  45:  </xsl:stylesheet>

This script utilizes document attributes in its processing. There’s a bunch of common generic attributes, e. g., node name, id and level (nesting level). Also there’s useful Umbraco library available both in C# and XSLT. And one thing that is heavily used is building URLs to specific document. If you have document ‘Chairs’ that nests ‘Stool’ doc we could use direct URL:

http://site/chairs/common-stool.aspx

That’s pretty good already ’cause we can build URL based on nodes names. But there’s a much better way to get links:

href="{umbraco. library: NiceUrl(current()/@id)}"

We can use anything that can be used in XSLT plus several useful hints from Umbraco. We still have to do some common work to get AJAX using Umbraco, but developer is not restricted on how he can do this: you can select ASP.NET AJAX, jQuery, Dojo or anything else.

Visual Studio and any code development that involves user controls are needed only when some additional functionality is needed that cannot be provided by Umbraco. E. g.: email sending, requests to external systems and web services.

To make developer’s life easier on path of additional functions development common ASP.NET lifecycle works for all user controls. Controls are just added into templates. Umbraco do not throw monkey wrench into public properties processing and we can pass them down to our controls. Moreover, it helps to use them even more easily. Here’s another example how this is done.

First of all, let’s have some control with following properties (just a small bit of code here to make things clear):

public string EmailTo
{
 get
 {
    return _EmailTo;
 }
 set
 {
    _EmailTo = value;
 }
}

public string EmailSubject
{
 get
 {
    return _EmailSubject;
 }
 set
 {
    _EmailSubject = value;
 }
}

public string EmailBody
{
 get
 {
    return _EmailBody;
 }
 set
 {
    _EmailBody = value;
 }
}


* This source code was highlighted with Source Code Highlighter.

Next, when control has been copied into usercontrols/website directory we create new macro and select user control there. After this, clicking ’Properties’ button we get full list of all public properties we added. Just select those you need to be supported on pages:

We can provide almost any values needed in our templates or even specific pages this way:

<umbraco:Macro EmailTo="[#emailTo]"
EmailSubject="[#emailSubject]"
EmailBody="[#emailBody]"
EmailReplyFrom="[#emailReplyFrom]"
EmailReplySubject="[#emailReplySubject]"
EmailReplyBody="[#emailReplyBody]"
EnableSSL="[#enableSSL]"
FormHeader="[#headerText]"
FormText="[#formText]"
ThankYouHeaderText="[#thankYouHeaderText]"
ThankYouMessageText="[#thankYouMessageText]"
Alias="CWS_ContactForm" runat="server"></umbraco:Macro>


* This source code was highlighted with Source Code Highlighter.

These properties will be set from values defined for a specific page (remember those tabs) and passed to control instance where they can be used in any way.

Summary

Umbraco utilizes one of best principles: create once, use many times after (‘use anywhere’ is an idealistic view). Document workflow is coupled by great ease of development. But there’s one important condition required — good knowledge of ASP.NET tech. stack. Add up standard architecture principles that should be developed by team for uniformity of projects.

Most of tasks can be achieved in administration console. Any set of elements including developed user controls and additional libraries can be packed into modules (called packages in Umbraco) and easily added into new Umbraco installations.


Difficulties

There are only 2 main issues that concern system setup and can be a pain. First of all, Umbraco needs web-site root. It won’t work in any subfolder in separate application pool.

Another issue involves web. config files. Settings introduced in these files are inherited from parent folder to children. This can be easily fixed fir < system. web> node: just wrap in into < location> node with attribute inheritInChildApplications="false". But there’s no such solution for < configSection> node, and here Umbraco introduces many settings that can clash with other applications. In my case there was a problem with ScrewTurn Wiki. This engine uses ASP. NET AJAX extensions as well as Umbraco, but utilizing another version. Thus AJAX dependencies of Umbraco meet with dependencies of Wiki with same names and Wiki goes to 500. If I try to fix this for Wiki I get dead Umbraco. The only solution was to move Wiki into separate sub domain.

Uncovered topics

I haven’t researched how Umbraco solves localization in practice. But as I see its language dictionaries partially solves this problem. Dictionary keys-references can be included on templates. This is coupled by locale handling by ASP. NET.

I didn’t cover users’ management as well. Umbraco allows creating console users as well as web-site members. In both cases user rights management is supplied. I think that Umbraco framework provide all required info to templates and macros. But once again: practice required here. To this I can add that Active Directory integration is pretty easy and AD provider is available.

Back- and Front-Office

Decisions on how to separate functions between administration console and web-site can vary. But anyway I think that console should not be covered with all special functions: anything related to users’ functions should be available on web-site itself. Why would common visitors care about any console? This way is supported by Umbraco API available in .NET. But web-site managers will definitely want all their tasks be achievable in console.

Creative Website Starter

One reason I could study Umbraco pretty fast was that I used Creative Website Starter (CWS) to develop my home site. During CWS ‘excavations’ I understood this uncommon and effective development Zen. What makes this especially funny is: I had spent almost an hour trying to find SQL scripts before I have understood how developer has to work with data here.

Feb. 12 2010 Update

Having almost half-a-year practice with Umbraco I’m still glad to work with this CMS. Everything is simple here.

If administrative console has to be extended new ascx-files are developed and added into usercontrols/dashboard directory of Umbraco installation. There data-processing can be made in any way: you just have to provide data logic that is used by some web-site controls as well. Here’s an example. User fills some form in web-site. Implemented user control saves this input in some custom data table in SQL Server or stores input into local xml file. In order to allow admin read this input we have to add admin user control that reads input from the same place where it was saved previously. Good example is available in CWS.

Another thing is standard processing of some data in documents. For that purpose we can create custom cs-file that contains event handlers wired to documents model. Events are wired in Page_Load event. Document events are described in Umbraco API object model. Then we upload this new file in App_Code directory in Umbraco root. Several files can be provided at a time. There’s a good example on Umbraco Web-site. Events can be peeped in object model with Visual Studio. In user control project several references are needed to use CMS API: businesslogic. dll, cms. dll и umbraco. dll.

And one bug: Umbraco handles Before-events after actual document changes so that we’re up to After-events only.


Obsolete & XmlRoot

February 2, 2010 12:48 by terR0Q

There’s a pretty disturbing nuance I’ve found recently. Suppose we have a class with XmlRoot attribute:

[XmlRoot( “SomeXmlNode” )]
public class SomeClass { ... }

If this class has to be marked as Obsolete, this new attribute has to be placed ABOVE XmlRoot. Otherwise this combination will result in XmlIgnore analog and will break XML serialization, exceptions will be thrown like “Unknown element”.

I’d better describe this. Following code falls under erroneous condition:

[XmlRoot( “SomeXmlNode” )]
[Obsolete]
public class SomeClass { ... }

But this code will work:

[Obsolete]
[XmlRoot( “SomeXmlNode” )]
public class SomeClass { ... }

I’ll look into IL later to understand the real difference. But this situation is weird anyway, attribute order should now make difference on class level. Supposedly there is a problem in default XML serialization. Looks like presumption of some architect or coder: “well, if we’ve got an obsolete item here, this node is not needed anymore, so lets ignore it.”

Also I’ve found following note in XmlSerializer documentation:

Objects marked with the Obsolete Attribute no longer serialized. In the .NET Framework 3.5 the XmlSerializer class no longer serializes objects that are marked as [Obsolete].

This means that in case of custom serialization we have total mess. And double bug, by the way.


Tags:
Categories: Development
Actions: E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

ASP.NET pre-compilation

January 21, 2010 22:13 by terR0Q

Any ASP.NET page may be built from several files. They can be compressed up to one, but that’s not handy, and in Visual Studio everything is always split up in 3 files. Anyway, they keep program logic (event handlers mostly) and server mark-up (common html mark-up plus declaration of elements controlled by server).

When client requests some page for the first time declarative mark-up and imperative part (well, code) are compiled by ASP.NET. Although all .cs files are compiled into one dll file for the whole project by VS, separate dll file is created for each page (more info can be found in many good books and articles). That’s a good model, but first request to a page always takes some more time than following requests: time is taken to compile and save new file in ASP.NET cache. But that can be a problem for large projects.

This problem can be solved by pre-compilation of the whole site. Most simple solution for that task, from my point of view, doesn’t need any VS add-ins or any shamanism with publishing or build events. Just 3 steps:

  1. Publish web-site into target directory
  2. Execute following command:
    aspnet_compiler. exe -m /LM/W3SVC/1/Root
  3. Wait until compilation finishes.

There are two more things to say. First of all: /LM/W3SVC/1/Root is a web-node address in IIS hierarchy where web-site is deployed. This value can be used for any ‘default’ web-site, but when there are several web-nodes on IIS, value ‘1’ in this string is changed by some other. To find correct address just a simple utility is needed — IIS Metabase Explorer. It will show full IIS web-nodes hierarchy that is pretty easy to understand ‘cause readable nodes’ names are saved here as well. When node is found you need to take MD_APP_ROOT value — that’s the address to provide for aspnet_compiler.

Second thing: pre-compilation should not be started in a project working folder. If you have active project with several developers in collaboration that includes source control (I hope to never see such one without SC...), than after short period of time its directory will be full of ‘dead’ files. When someone deletes file from project his VS usually erases it from drive. But when other developers will get change list, most common result will be that VS will just remove file from solution leaving it physically in place. Now, when aspnet_compiler will start working it will crawl over all ASP.NET files in directory. This will result in error and interrupted compilation when some file will address a class that exists in project dll no more (VS just has nothing to compile for these files).

Well, the last thing to do is to write script so that to automate publishing and compilation process. But that’s just a trivial local task.


Debug or not to debug

December 29, 2009 12:30 by terR0Q

Stupid Visual Studio tries to convince me from time to time that it is unable to debug ASP.NET web application. And it convinces me no matter what (restarts of IIS or VS itself) until I open web.config and search for debug="true".


BlogEngine.NET, Umbraco, IIS7 Integrated

October 22, 2009 14:58 by terR0Q

One more note for happy usage of BlogEngine.NET with Umbraco in the root with IIS 7 Integrated mode.

We need to add several handlers and modules in web server config node (system.webServer) and remove Umbraco modules inherited from root config. For that purpose write following code into BlogEngine config file:

   1:      <system.webServer>
   2:   
   3:          <security>
   4:              <requestFiltering allowDoubleEscaping='True'/>
   5:          </security>
   6:   
   7:          <modules runAllManagedModulesForAllRequests='true'>
   8:              <add name="WwwSubDomainModule" type="BlogEngine.Core.Web.HttpModules.WwwSubDomainModule, BlogEngine.Core"/>
   9:              <add name="UrlRewrite" type="BlogEngine.Core.Web.HttpModules.UrlRewrite, BlogEngine.Core"/>
  10:              <add name="CompressionModule" type="BlogEngine.Core.Web.HttpModules.CompressionModule, BlogEngine.Core"/>
  11:              <add name="ReferrerModule" type="BlogEngine.Core.Web.HttpModules.ReferrerModule, BlogEngine.Core"/>
  12:              <!--Remove the default ASP.NET modules we don"t need-->
  13:              <remove name="PassportAuthentication"/>
  14:              <remove name="Profile"/>
  15:              <remove name="AnonymousIdentification"/>
  16:          </modules>
  17:   
  18:          <handlers>
  19:              <add verb='*' name='File' path='file.axd' type='BlogEngine.Core.Web.HttpHandlers.FileHandler,BlogEngine.Core' />
  20:              <add verb='*' name='Image' path='image.axd' type='BlogEngine.Core.Web.HttpHandlers.ImageHandler,BlogEngine.Core' />
  21:              <add verb='*' name='Syndication' path='syndication.axd' type='BlogEngine.Core.Web.HttpHandlers.SyndicationHandler,BlogEngine.Core' />
  22:              <add verb='*' name='Sitemap' path='sitemap.axd' type='BlogEngine.Core.Web.HttpHandlers.SiteMap,BlogEngine.Core' />
  23:              <add verb='*' name='Trackback' path='trackback.axd' type='BlogEngine.Core.Web.HttpHandlers.TrackbackHandler,BlogEngine.Core' />
  24:              <add verb='*' name='Pingback' path='pingback.axd' type='BlogEngine.Core.Web.HttpHandlers.PingbackHandler,BlogEngine.Core' />
  25:              <add verb='*' name='OpenSearch' path='opensearch.axd' type='BlogEngine.Core.Web.HttpHandlers.OpenSearchHandler,BlogEngine.Core' />
  26:              <add verb='*' name='Metaweblog' path='metaweblog.axd' type='BlogEngine.Core.API.MetaWeblog.MetaWeblogHandler,BlogEngine.Core' />
  27:              <add verb='*' name='RSD' path='rsd.axd' type='BlogEngine.Core.Web.HttpHandlers.RsdHandler,BlogEngine.Core' />
  28:              <add verb='*' name='CSS' path='css.axd' type='BlogEngine.Core.Web.HttpHandlers.CssHandler,BlogEngine.Core' />
  29:              <add verb='*' name='JS' path='js.axd' type='BlogEngine.Core.Web.HttpHandlers.JavaScriptHandler,BlogEngine.Core' />
  30:              <add verb='*' name='Rating' path='rating.axd' type='BlogEngine.Core.Web.HttpHandlers.RatingHandler,BlogEngine.Core' />
  31:              <add verb='*' name='OPML' path='opml.axd' type='BlogEngine.Core.Web.HttpHandlers.OpmlHandler,BlogEngine.Core' />
  32:              <add verb='*' name='BlogML' path='blogml.axd' type='BlogEngine.Core.Web.HttpHandlers.BlogMLExportHandler,BlogEngine.Core' />
  33:              <add verb='*' name='SIOC' path='sioc.axd' type='BlogEngine.Core.Web.HttpHandlers.Sioc,BlogEngine.Core' />
  34:              <add verb='*' name='APML' path='apml.axd' type='BlogEngine.Core.Web.HttpHandlers.Apml,BlogEngine.Core' />
  35:              <add verb='*' name='FOAF' path='foaf*.axd' type='BlogEngine.Core.Web.HttpHandlers.Foaf,BlogEngine.Core' />
  36:          </handlers>
  37:   
  38:          <validation validateIntegratedModeConfiguration='false' />
  39:   
  40:      </system.webServer>

And to disable Umbraco web configuration inheritance move system.web, system.web.extensions, applicationSettings and system.webServer nodes into location node with inheritInChildApplications attribute:

<location path="." inheritInChildApplications="false">   

Btw, location trick can be useful in many other situations. Unfortunately it can’t solve inheritance problem with configSections node.


C# on C# for C#

October 13, 2009 17:44 by terR0Q

One excerpt from Wikipedia about Mono Project is something special:

In February 2001 de Icaza asked for the missing information on the metadata file format in the .NET mailing lists and at the same time started to work on a C# compiler written in C#, as an exercise in C#

 
MapYourVisitors.COM