Recent posts

Easy SharePoint site branding with the ASP.NET ExpressionBuilder

Branding a SharePoint site is usually a case of deciding which content is common for different brands and which content is really different. If at this point you find that there really is common content and that the only difference you want to express for that content is styling and a few optical tweaks, you will find that the challenge is then to maintain that in a single place. What you don´t want is duplicating that common information across different sites, with the risk of stray site differences or a lot of extra work for every simple change. The solution that SharePoint offers is content replication, this would then require defining the different brand sites and deploy content from a central 'mother' site.

As an alternative I will propose here to consider branding by defining you own ExpressionBuilder class and using that across pages and of course in the master page. SharePoint offers its own SPUrlExpressionBuilder in the form of $SPUrl. You can find it used in the default publishing templates like this:

 

    <SharePoint:CssRegistration name="<% $SPUrl:~SiteCollection/Style Library/~language/Core Styles/Band.css%>" runat="server"/>

    <SharePoint:CssRegistration name="<% $SPUrl:~sitecollection/Style Library/~language/Core Styles/controls.css %>" runat="server"/>

    <SharePoint:CssRegistration name="<% $SPUrl:~SiteCollection/Style Library/zz1_blue.css%>" runat="server"/>

...

The SharePoint SPUrlExpressionBuilder is documented and interprets the the expression you pass it by replacing ~SiteCollection with the site collection Url and the ~language expression with the current locale for the site. The easiest solution to branding would be introducing your own ~brand expression. That would allow you to simply add your own branding css style:

<SharePoint:CssRegistration name="<% $SPUrl:~SiteCollection/Style Library/MyProduct/~brand/Brand.css%>" runat="server"/>

Or you could reference your branded images like this:

        <asp:Image ID="Logo" runat="server" CssClass="logo" ImageUrl="<%$SPUrl:~SiteCollection/Style Library/MyProduct/Images/~brand/logo.gif%>" ToolTip="Logo " ></asp:Image>

You can trace it to the Microsoft.SharePoint.Publishing dll and you can even reference the Expression builder in the Microsoft.SharePoint.Publishing.WebControls.SPUrlExpressionBuilder. If you then dive into its code with Reflector you will be disappointed by finding that it cannot be extended to interpret your expressions for branding and that it is a sealed piece of code. This leaves no other option than use containment of the .SPUrlExpressionBuilder class and extend it by introducing some extra code. Here is my version:

using System;
using System.Collections.Generic; using System.Text;
using System.Web.Compilation;
using System.Web;
using Microsoft.SharePoint.Security;
using Microsoft.SharePoint.Publishing.WebControls;
using Microsoft.SharePoint.Utilities;
using System.Security.Permissions;
using System.CodeDom;
using System.ComponentModel;
using System.Web.UI;
 
/// <summary>
/// This class allows the brand to be part of a url expression. It is based on the SPUrl expression builder object and extends it with
/// with specific branding for your product.
/// </summary>
[ExpressionPrefix("MySPUrl")]
public class MySPUrlExpressionBuilder: ExpressionBuilder
{
   const string BrandPlaceholder = "~brand"; 
 
    // Contains base class, because it is a sealed class 
   private SPUrlExpressionBuilder builderBase; 
 
   /// <summary> 
   /// Replace placeholders with relevant data 
   /// </summary> 
   /// <param name="expression"></param> 
   /// <returns></returns> 
   public static object EvaluateUrlExpression(string expression) 
  
      string serverRelativeUrlFromPrefixedUrl =   SPUtility.GetServerRelativeUrlFromPrefixedUrl(expression); 
 
      for (int i = serverRelativeUrlFromPrefixedUrl.IndexOf(BrandPlaceholder, 0, StringComparison.OrdinalIgnoreCase); 
i > 0; i = serverRelativeUrlFromPrefixedUrl.IndexOf(BrandPlaceholder, 0, StringComparison.OrdinalIgnoreCase)) 
     
         StringBuilder builder = new StringBuilder(serverRelativeUrlFromPrefixedUrl.Substring(0, i)); 
         int PlaceholderLength = BrandPlaceholder.Length; 
 
         builder.Append(GetBrand()); // You need to find a way to get the brand at run time, maybe from the host headers 
         if ((i + PlaceholderLength) < serverRelativeUrlFromPrefixedUrl.Length) 
        
               builder.Append(serverRelativeUrlFromPrefixedUrl.Substring(i + PlaceholderLength)); 
           
            serverRelativeUrlFromPrefixedUrl = builder.ToString(); 
        
         return serverRelativeUrlFromPrefixedUrl; 
     
 
      /// <summary> 
      /// Default constructur 
      /// </summary> 
      public MySPUrlExpressionBuilder() 
     
         builderBase = new SPUrlExpressionBuilder(); 
     
 
      /// <summary> 
      /// Implements the abstract base method to return the string as built by SPUrl and additional replacements 
      /// </summary> 
      /// <param name="target"></param> 
      /// <param name="entry"></param> 
      /// <param name="parsedData"></param> 
      /// <param name="context"></param> 
      /// <returns></returns> 
      public override object EvaluateExpression(object target, System.Web.UI.BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context) 
  
      string expression = (string)builderBase.EvaluateExpression(target, entry, parsedData, context); 
 
         if (expression != null) 
            return EvaluateUrlExpression(expression.ToString()); 
         return null; 
     
 
      /// <summary> 
      /// Implements the abstract base method to return a reference to a static evaluator method 
      /// </summary> 
      /// <param name="entry"></param> 
      /// <param name="parsedData"></param> 
      /// <param name="context"></param> 
      /// <returns></returns> 
      public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context) 
  
      if (entry == null) 
         return null; 
      CodeExpression[] parameters = new CodeExpression[] { new CodePrimitiveExpression(entry.Expression.Trim()) }; 
      CodeExpression expression = new       CodeMethodInvokeExpression(new    CodeTypeReferenceExpression(base.GetType()), "EvaluateUrlExpression", parameters); 
 
      if (entry.PropertyInfo == null) 
     
         return expression; 
     
      PropertyDescriptor descriptor = TypeDescriptor.GetProperties(entry.DeclaringType)[entry.PropertyInfo.Name]; 
      return new CodeCastExpression(descriptor.PropertyType, expression); 
  
 
   /// <summary> 
   /// Implements the abstract base method to signify that this class supports an evaluate method 
   /// </summary> 
   public override bool SupportsEvaluate 
  
      get 
     
         return true; 
     

 

If you put this in a dll and turn it into a SharePoint solution, you should be in business. Of course an expression builder needs to be declared in the expressionBuilder section of the web.config of the site, just like the other expression builders:

 

<compilation batch="false" debug="false">

<expressionBuilders>

<add expressionPrefix="MySPUrl" type="MyCompany.MyProduct.MySPUrlExpressionBuilder, MyCompany.MyProduct, …" />

</expressionBuilders>

</compilation>

After this you will finally be able to do easy branding:

<SharePoint:CssRegistration name="<% $MySPUrl:~SiteCollection/Style Library/MyProduct/~brand/Brand.css%>" runat="server"/>

And reference your branded images like this:

        <asp:Image ID="Logo" runat="server" CssClass="logo" ImageUrl="<%$MySPUrl:~SiteCollection/Style Library/MyProduct/Images/~brand/logo.gif%>" ToolTip="Logo " ></asp:Image>

Have your way with the code and have fun!


Published: 29-09-2008 by Wim The | 0 Comments | 0 Links to this post
 

Removing namespaces from XHTML

Last week we needed to get some html fragments from a XHTML document. A straightforward process one might say, although we ran into some namespace problems along the way.

Unlike HTML, XHTML is in essence a XML document and you can therefore use the XmlDocument class to load an in memory representation of the document.

Our document:

   1:  <?xml version="1.0" encoding="utf-8"?><!DOCTYPE html
   2:    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
   3:  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl">
   4:     <head>
   5:        <title>title something</title>
   6:     </head>
   7:     <body>
   8:        <div id="broodtekst">
   9:           <h1>get this part</h1>
  10:        </div>  
  11:     </body>
  12:  </html>

Our objective is to retrieve the html in between the <div id="Broodtekst"><h1>get this part</h1></div>.

Retrieving the html:

   1:  string result = string.Empty;
   2:  XmlDocument doc = new XmlDocument();
   3:   
   4:  doc.Load(bestand);
   5:  XmlNamespaceManager man = new XmlNamespaceManager(doc.NameTable);
   6:  man.AddNamespace("d", "http://www.w3.org/1999/xhtml");
   7:              
   8:  XmlNode node =  doc.SelectSingleNode("//d:div[@id='broodtekst']",man);
   9:  result = node.InnerXml;

 

Our result unfortunately did not yield the expected string "<h1>get this part</h1>", in stead it yielded "<h1 xmlns="http://www.w3.org/1999/xhtml">get this part</h1>". It turn out the XmlDocument keeps track out of which namespace the html is queried and puts the namespace in all the tags related to the namespace. A good feature, but not what we wanted. After a lot of searching and asking around our colleague Keren came up with the answer (thx Keren). Remove all the namespaces from the XmlDocument before proceding with a XPath query.

Removing all namespaces and retrieve html:

   1:   string result = null;
   2:  System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
   3:  using (MemoryStream ms = new MemoryStream(bestand))
   4:  {
   5:       using (XmlTextReader tr = new XmlTextReader(ms))
   6:       {
   7:             tr.Namespaces = false;
   8:             tr.ProhibitDtd = false;
   9:             doc.Load(tr);
  10:        }
  11:   }
  12:  //Extract content div
  13:  XmlNode node2 = doc.SelectSingleNode("//div[@id='broodtekst']");
  14:  if (node != null)
  15:  {
  16:       result = node2.InnerXml;
  17:  }

Instead of loading the stream directly into the XmlDocument class you put it in a XmlTextReader which has a couple of sweet properties to remove namespaces (line 7) and prohibit the XmlDocument to retrieve optional DTD files (line 8, thx William).

Hopefully this might help some people in the .Net community struggeling with XML/XHTML and namespaces.


Published: 22-09-2008 by Hans ter Wal | 0 Comments | 0 Links to this post
 

Aviva Solutions Summer Event 2008: Mallorca

In its first full year Aviva Solutions celebrated and went for a weekend trip to Spain. As an encore, this year on Friday the 12th of September the entire company stepped on planes to Mallorca. More than doubling the employee count this time.

The same recipe: No slides, no speeches, no busy team building program. Instead, just a weekend of fun, sun, beach and a dip into the night life. To make it easier to ‘manage’ food and drinks for the whole crew the organizing committee chose the all-inclusive hotel Palma Bay Club El Arenal.

Hotel Palma Bay Club El Arenal

Another tourist trap for some, but with a group this size you need to cater for all different needs:

·         Want to take it easy? Stay in the hotel area and take a sip from your cocktail on one of the terraces, sunbathe alongside the pool or hop from snacks to the barbeque or the restaurant.

·         Want to sunbathe and shop? Take a walk to the long La Palma beach and boulevard.

·         Want to do something more adventurous? Rent a bike or take the bus to La Palma city center.

·         More into the night life? Sleep all day and dance all night in the heavy night life of El Arenal.

Judging by the enthusiastic and sometimes hilarious mails that the organizing committee received after the weekend it has been a grandiose success for all. It was only a weekend, but it felt like a dream holiday of a week. Even better, the plan is now to turn this into a tradition: More sun, sea, beach and fun in 2009!

A short impression of the whole weekend:

It all started with a very early departure from Schiphol airport. Unfortunately, there was not much choice in suitable flights.

Too early for some, but not everyone...

Too early...

After arrival and check-in a luxury catamaran trip for all with music, drinks and a chance to dip into a quiet ‘blue lagoon’ bay.

I am sailing...

All the ladies on the boat say: ooh!

Rest and relaxation

Blue lagoon

After a first deep dip into the (mostly German) night life and a taste of the beach, I rented some bikes together with colleagues for some sightseeing.

Start from the hotel

Short stop at some interesting rocks

The grand La Palma Cathedral

Rene has seen the light

Saturday evening everyone went to a restaurant on the boulevard to have some tapas and almost the entire crew went to dance or exercise his or her German in the Riu Palace discotheque. No pictures please!

More calamares please!

Riu Palace

The last day, late breakfast on the beach and a last bathe in the sea and the sun.

Sun, sea, beach...

It was great! Thanks all people of Aviva Solutions and let’s go for it again next year!

Time to go home...

"It was great mum!"


Published: 16-09-2008 by Wim The | 0 Comments | 0 Links to this post