Welcome to MSDN Blogs Sign in | Join | Help

Content Deployment and CEWP Absolute URLs

Content Deployment and CEWP Absolute URLs

The Content Editor Web Part (CEWP) has a Rich Text Editor. This allows non-technical authors to generate custom content using a web part. This is a great feature for team collaboration sites, but some customers also use the CEWP on publishing site pages. Avoiding the discussion of web parts verses field controls in published pages, there are issues using the CEWP to create content on published pages.

cdwp1

The Problem

A Rich Text Editor sounds like a great feature; however, there is a problem with content deployment, described by Andrew Connell. (There is a related problem for sites that can be accessed through multiple AAMs as described by Maxime Bombardier.) The basic problem is the Rich Text Editor forces all URLs to be absolute. If you look at the HTML generated by the above HTML editor, you will see:

 cewp2

As Andrew Connell points out:

If you have a link to http://staging.adventureworkstravel.com/pages/contactus.aspx in a CEWP on a page and then do content deployment to http://www.adventureworkstravel.com, the link will be pointing back to the staging site (which will... or should... be inaccessible).

The absolute URL is not fixed up automatically during content deployment, so the target page will still point to the original URL location, not a location within the target farm. This means the absolute URL must be corrected in the target farm itself. In the preceding example, we want the target farm HTML to be a relative URL that points to locations within the target farm:

cewp3 

Maxime Bombardier’s control adapter strategy can be leveraged. With slight modification, Maxime’s code can be modified to convert absolute URLs to relative URLs, so they effectively point to the appropriate location in the target farm. How do we convert an absolute URL to a relative URL? Looking at the above example, we need to strip out the host portion of the URL; that is, we need to remove “http://moss.litwareinc.com”

The Solution

The difference between the content deployment fix we need, and Maxime’s AAM fix, is what gets stripped. Maxime’s AAM fix strips the AAM host names of the current web application. The content deployment fix needs to strip the AAM host names of the authoring web application, which the target farm does not know. In other words, we need a way to tell the control adapter which host names to strip. The solution is to make the host names configurable. This solution uses the SharePoint Config Store solution developed by Chris O'Brien.

cewp4 

So, the primary difference between this control adapter and Maxime’s control adapter is the GetAlternativeUrls method. This method’s logic is changed to read AAMs from the Config store list, rather than using the object module to get the AAMs from the current web application.

The Code

using COB.SharePoint.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.Adapters;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;  

namespace Litware.SharePoint.WebPartPages.CewpControlAdapter

    public class ContentEditorWebPartAdapter : ControlAdapter 
   
        protected override void Render(System.Web.UI.HtmlTextWriter writer)
       
{
            StringBuilder sb = new StringBuilder(); 
  
            // Allow the CEWP to render itself into a string that we provide 
            HtmlTextWriter htw = new HtmlTextWriter(new StringWriter(sb));

           
base.Render(htw); 
            string output = sb.ToString(); 

            // Wrap the adatper rendering logic in a try-catch so any error 
            // in the adapter won't prevent the CEWP from rendering. 
            try 
           
                // Now we post-process the CEWP rendering to convert absolute URLs to relative URLs 
                string[] alternativeUrls = GetAlternativeUrls(); 
 
                if (alternativeUrls != null
               
                    foreach (string replaceableUrl in alternativeUrls) 
                   
                        // Do a simple String.Replace() of the alternativeUrls to generate a relative url 
                        searchFor = replaceableUrl; 
                        replaceWith = "/"
                        output = output.Replace(searchFor, replaceWith); 
                   
               
           
            catch (Exception ex) 
           
                // log exception here 
           

            // Finally, write the rendering to the page 
            writer.Write(output); 
        
}

        private string[] GetAlternativeUrls()
        {
            string[] alternativeUrls = null

            try 
           
                alernativeUrls = (string[])HttpContext.Current.Cache["alternativeUrls"]; 
 
                if (alternativeUrls == null
               
                    string temp = String.Empty; 
 
                    // Get the URLs to be replaced from the config store 
                    temp = ConfigStore.GetValue("CEWP Adapter", "AAMs"); 

                   
if (String.IsNullOrEmpty(temp)) 
                        throw new ArgumentNullException("AAMs config store parameter is null or empty"); 
 
                    // Split apart the config parameter using a semicolon separater value 
                    char[] separaters = { ';' }; 
                    alternativeUrls = temp.Split(separaters); 
 
                    // Validate the config value 
                    if (alternativeUrls == null || alternativeUrls.Length == 0) 
                        throw new ArgumentNullException(
                            "AAMs config store parameter is null or empty after split"); 
 
                    for (int i = 0; i < alternativeUrls.Length; i++) 
                   
                        // Ensure the URL is "/" terminated for consitent replacement behavior 
                        string replaceableUrl = alternativeUrls[i]; 
                        if (!string.IsNullOrEmpty(replaceableUrl) && !replaceableUrl.EndsWith("/")) 
                            alternativeUrls[i] += "/"
                   
 
                    // Sort, and then reverse the array 
                    // to put the longest; that is, the most specific 
                    // URLs first in the list 
                    Array.Sort(alternativeUrls); 
                    Array.Reverse(alternativeUrls); 
 
                    // Cache for 5 minutes to allow for a somewhat quick refresh
                    // if the configuration values are changed 
                    HttpContext.Current.Cache.Add("alternativeUrls",alternativeUrls, null,
                         DateTime.Now.AddMinutes(5), System.Web.Caching.Cache.NoSlidingExpiration,
                         System.Web.Caching.CacheItemPriority.Normal, null); 
               
           
            catch (Exception ex) 
           
                // log exception here 
           
 
            return alternativeUrls;
        }
   
}
 

Build the output assembly as strongly named, and then deploy it to the GAC on every WFE. A simple solution package (wsp file) can be created to automate the deployment to all WFEs. Creating a solution package is automatic if you create your project using the Visual Studio 2008 extensions for Windows SharePoint Services 3.0, v1.2.

Browser File

The control adapter is associated with the CEWP through browser file entries. The default browser file, compat.browser, is in the App_Browsers folder of each web application’s virtual directory. We don’t want to update this file; instead, we will create a separate file for our control adapter.

Additional browser files can be added to the same directory; however, additional files will not be recognized by ASP.NET until compat.browser is recompiled. Recompilation is forced by opening compat.browser in a text editor like Notepad, making an innocuous change (e.g., add and then delete a space), and then saving the file.

The customized browser file, litware.browser, is created and placed in the App_Browsers folder of every web application where the control adapter is to be enabled. Without this browser file, the control adapter is not called even though it may be installed in the GAC. The default compat.browser is then updated and saved to force a recompiled of the local application browser files as described in the preceding paragraph.

cewp5 

Looking at the litware.browser code, the entry controlType attribute tells ASP.NET to associate this control adaptor with the CEWP. The adapterType attribute has the control adapter type and assembly. The refID=”Default” attribute tells ASP.NET to apply the adapter to all browser types.

<?xml version="1.0" encoding="utf-8" ?>
<browsers>
  <browser refID="Default">
    <controlAdapters>
      <adapter controlType="Microsoft.SharePoint.WebPartPages.ContentEditorWebPart"
               adapterType="Litware.SharePoint.WebPartPages.CewpControlAdapter.ContentEditorWebPartAdapter,
                    Litware.SharePoint.WebPartPages.CewpControlAdapter, Version=1.0.0.0, Culture=neutral,
                    PublicKeyToken=daf6fd1bbe0cfc20
" />
    </controlAdapters>
  </browser>
</browsers>

Note, App_Browser directory must be updated on every WFE in the target farm.

References

Browser Definition File Schema (browsers Element)
Securing Browser Definition Files

Published Monday, February 16, 2009 4:15 PM by jimmiet

Comments

# Links (2/19/2009) &laquo; Steve Pietrek - Everything SharePoint

Anonymous comments are disabled
 
Page view tracker