Sitecore users custom profile properties

Sitecore is using the standard .net security framework. This makes it easy to setup custom profiles on users, and to add custom profile properties. To do so, do the following:

STEP 1: ADD A PROFILE TEMPLATE TO SITECORE

Go to the CORE database and create a new template containing the fields you wish to add to a profile. I have added mine at /sitecore/templates/System/Security/CustomProfile but you can put yours anywhere.

Security Template

Security Template

STEP 2: ADD A USER PROFILE BASED ON THE TEMPLATE YOU JUST CREATED

Go to the CORE database and add a new item based on the template you created, below /sitecore/system/Settings/Security/Profiles.

Custom Profile

Custom Profile

Add the new item below the standard “User” profile. Sitecore will automatically base any new user on the first profile in the list.

STEP 3: THE CODE – APPLY THE PROFILE TO A USER

In this example I have created an extranet user which profile needs to be based on my new profile. To apply the profile to my user I need to write the following code:

public void UpdateUserProfile(string userName, string profileID)
{
  Sitecore.Security.Accounts.User user = Sitecore.Security.Accounts.User.FromName(userName, true);
  user.Profile.ProfileItemId = profileID;
  user.Profile.Save();
}

// How to use the method
UpdateUserProfile("extranet\\bp", "{7BA5AA76-582D-4463-BCF3-775508C8624E}"); 

Remember to prefix the user name with the domain name (in this example, bp is a member of the extranet domain). The GUID is the ID of the profile item I created.

The profile is now based on this profile:

Profile is added to the user

Profile is added to the user

STEP 4: THE CODE – APPLY TEXT TO THE CUSTOM PROFILE

To write text on the custom profile, do the following:

public void UpdateUser(string userName, string address, string phone)
{
  Sitecore.Security.Accounts.User user = Sitecore.Security.Accounts.User.FromName(userName, true);
  user.Profile.SetCustomProperty("Address", address);
  user.Profile.SetCustomProperty("Phone", phone);
  user.Profile.Save();
}

// Calling the method:
UpdateUser("extranet\\bp", "Zepperlinerhallen, Islands Brygge 55, 2300 København S", "70 23 33 30");

The text is now added to the profile:

Text is added to the profile

Text is added to the profile

NOTES:

  • You don’t really need to create the profile in Sitecore. This is just so you can see the custom properties in the Sitecore security editor.
  • The Boolean “true” in the call to Sitecore.Security.Accounts.User.FromName(userName, true) authenticates the user, hence allowing you to edit the user and the user profile.
  • If you get the users (members) with System.Web.Security.Membership.GetAllUsers instead, the user (member) will not be authenticated, and you cannot edit the user (member, sigh…)
  • Remember to call Profile.Save(), if you don’t, the profile is not changed.
Posted in c#, Sitecore 6, Sitecore 7 | Tagged , , , , , | 5 Comments

URL Rewrite and Sitecore

The Microsoft URL Rewrite module is an ISS extension that allows you to rewrite one URL to another using regular expressions. The extension is like the Sitecore aliases on steroids, and is especially useful for mapping old dynamic URL’s to new Sitecore URL’s.
If you launch a new website on an existing domain, you can be forced to keep old API URL’s as they were, and the URL Rewrite will help you with that.

Download the URL Rewrite extension here.

Here is an example on how to use URL Rewrite Rules to map an old API URL to a new Sitecore .ashx CustomHandler.

The URL rewrite rules are easiest kept in a separate configuration file, so you need to apply the following to the system.webserver part of your web.config:

<system.webServer>    
  ...
  <rewrite>
    <rules configSource="App_Config\UrlRewriteRules.config" />
  </rewrite>
</system.webServer>

You then place the rewrite rules in a file called UrlRewriteRules.config  in the App_Config folder:

<?xml version="1.0" encoding="utf-8"?>
<rules>
  <clear />
  <rule name="ImageScaler" stopProcessing="true">
    <match url="^/scaleimage/([^/]*)/w/(\d+)/h/(\d+)/name/([a-zA-Z]+)\.([a-zA-Z]+)$" />
    <conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
    <action type="Rewrite" url="/Sitecore Modules/web/ImageResizer.ashx?id={R:1}&amp;width={R:2}&amp;height={R:3}&amp;name={R:4}&amp;extension={R:5}" appendQueryString="false" logRewrittenUrl="true" />
  </rule>
  ...
  ...
</rules>

The “match” regular expression determines which URL to be redirected.
In this example I try to catch /scaleimage/1200/w/800/h/600/name/scaledimage.jpg.
The “rewrite” determines the URL to rewrite to (in this case /Sitecore Modules/web/ImageResizer.ashx) and the regular expression matches are appended as parameters to the .ashx page by the {R:n} syntax.

This was the URL Rewrite part, now onto some code.

I need to register my /Sitecore Modules/web/ImageResizer.ashx in Sitecore, and I can do this by adding the following to the web.config:

</sitecore>
  <customHandlers>
    ...
    <handler trigger="/Sitecore Modules/web/ImageResizer/" handler="ImageResizer" />
  </customHandlers>
</sitecore>
...
...
<system.webserver>
  <handlers>
    ...
    <add verb="*" path="/Sitecore Modules/web/ImageResizer.ashx" type="MyCode.ImageResizer, MyCode" name="ImageResizer" />
  </handlers>
</system.webserver>

The .ashx handler is ready to be called (example code only, will not compile):

namespace MyCode
{
  public class ImageResizer : IHttpHandler
  {
    public void ProcessRequest(HttpContext context)
    {
      // Retrieving the parameters from the request as defined in the 
      // "rewrite" rule:
      // ?id={R:1}&amp;width={R:2}&amp;height={R:3}&amp;name={R:4}&amp;extension={R:5}
      string ID = context.Request.QueryString["id"];
      string width = context.Request.QueryString["width"];
      string height = context.Request.QueryString["height"];
      string name = context.Request.QueryString["name"];
      string extension = context.Request.QueryString["extension"];

      // Do some code to get the image and resizing it
      Image image = GetSomeImaginaryImageClass();

      // Stream it back to the user
      context.Response.Clear();
      context.Response.ContentType = "image/jpeg";
      context.Response.StatusCode = (int)HttpStatusCode.OK;
      context.Response.BufferOutput = true; 
      byte[] bytes = image.GetBytes();
      context.Response.OutputStream.Write(bytes, 0, bytes.Length);
      context.Response.Flush();
    }
  }
}

MORE TO READ:

 

Posted in .net, c#, General .NET, Sitecore 6, Sitecore 7 | Tagged , , , , | Leave a comment

Sitecore 7 create item and update index at the same time

With Sitecore 7, the Sitecore index (based on Lucene.NET) have greatly improved, thus making the index even more usable.

One thing you should know though, is that when you add items to Sitecore using the Sitecore.Data.Items.Item.Add() method, the index is not immediately updated. The index is updated shortly after, but the time varies depending on your index update strategy.
The result is that if you query the index just after you added a new item, it is not a given that the item exists in the index.

There is a solution to this (of course, this is Sitecore). When you have added the new item, you must manually update the search index for this specific item.

I have made an Extension Method to show how this can be done:

using System;
using System.Linq;
using Lucene.Net.Index;
using Sitecore.ContentSearch;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;

namespace MyProject
{
  public static class ItemExtensions
  {
    public static Item AddAndUpdate(this Item item, string name, TemplateID templateID)
    {
      Assert.ArgumentNotNull(item, "item");
      Assert.IsTrue(item.Database.Name.ToLower() == "master", "Could not add item to " + item.Paths.FullPath + ": item is not in the 'master' database");
      Item newItem = item.Add(ItemUtil.ProposeValidItemName(name), templateID);
      Log.Audit("Item create: " + AuditFormatter.FormatItem(newItem), typeof(ItemExtensions));
      var tempItem = (SitecoreIndexableItem)newItem;
      ContentSearchManager.GetIndex("sitecore_master_index").Refresh(tempItem);
      return newItem;
    }

  }
}

The trick lies within these 2 lines of code:

var tempItem = (SitecoreIndexableItem)newItem;
ContentSearchManager.GetIndex("sitecore_master_index").Refresh(tempItem);

First the item is converted into a SitecoreIndexableItem, and then the master index is refreshed for this single item.

The extension method can be used like this:

using MyProject;

namespace MyNamespace
{
  public void AddItem(Item parent, string name, TemplateID templateID)
  {
    Item newItem = parent.AddAndUpdate(name, templateID);
  }
}

Please note that this will increase the time to create an item substantially, from barely mesureable milliseconds to a full second or so.

MORE TO READ:

 

 

Posted in .net, c#, Sitecore 7 | Tagged , , , , | 5 Comments

Unable to serialize the session state. In ‘StateServer’ and ‘SQLServer’ mode

When switching the sessionState mode of your web project from InProc to SQLServer you might encounter this error:

Exception: System.Web.HttpException
Message: Unable to serialize the session state. In ‘StateServer’ and ‘SQLServer’ mode, ASP.NET will serialize the session state objects, and as a result non-serializable objects or MarshalByRef objects are not permitted. The same restriction applies if similar serialization is done by the custom session state store in ‘Custom’ mode.

.NET handles the storage of session objects differently from InProc to SQLServer. When storing session objects in a SQL Server, .NET will serialize the objects. This is necessary because the session object needs to be transferred from server to server.

To support this, all you need is to mark the objects that are part of your sessions with the [Serializable]  attribute:

namespace MyNameSpace
{
  // Class is marked as serializable
  [Serializable]
  public class MyClass
  {
    // Some code here
  }
}

So how do you find the classes that need the [Serializable] attribute?

.NET is pretty good at telling which classes needs to be serializable in the nested exception:

Nested Exception

Exception: System.Runtime.Serialization.SerializationException
Message: Type ‘MyNamespace.MyClass‘ in Assembly ‘MyAssembly, Version=4.0.4.18681, Culture=neutral, PublicKeyToken=null’ is not marked as serializable.
Source: mscorlib

MORE TO READ:

NOTE TO SITECORE DEVELOPERS:

Sitecore does not as such support other modes than InProc session state. As Sitecore states (quote from the Sitecore Scaling Guide):

The Sitecore CMS user interfaces require in-process ASP.NET session management. For CM
instances, the value of the mode attribute of the /configuration/system.web/sessionState
element in the web.config file must be InProc.
In-process session management requires you to configure the CM load balancer for server affinity —
also known as sticky sessions. You can use other values for the mode attribute in the CD
environment.

This means that the Sitecore client and page editor is not tested using other session state methods than InProc. But since the content delivery servers do not touch the Sitecore UI, it is up to you to make the front end code compatible with StateServer or SQLServer methods.

The best session state management for Sitecore CD servers would still be InProc, and let your load balancer use sticky sessions.

Posted in c#, General .NET, Sitecore | Tagged , , , , , , | 1 Comment

Stream Sitecore media items to HttpResponse

Here is a small tip for you who is doing API’s based on Sitecore. How to get an item from the Sitecore Media Library and stream it to the HttpResponse of a page.

This is an example of an .ashx HttpHandler class that gets the ID of an Sitecore Media Library item and streams the item. Add your own exception handling as you wish.

using System;
using System.Net;
using System.Web;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Configuration;

namespace MyProject
{
  public class GetImage : IHttpHandler
  {
    public void ProcessRequest(HttpContext context)
    {
      // ID of media item
      string id = context.Request.QueryString["id"];

      // Get media item
      MediaItem item = (MediaItem)Factory.GetDatabase("web").GetItem(id);

      // Get name to be shown when image is saved
      string imageName = item.Name + "." + item.Extension;

      context.Response.Clear();
      context.Response.ContentType = mediaItem.MimeType;
      context.Response.AppendHeader("Content-Disposition", string.Format("inline;filename=\"{0}\"", imageName));
      context.Response.StatusCode = (int)HttpStatusCode.OK;
      context.Response.BufferOutput = true;
      // Copy the media stream to the response output stream
      mediaItem.GetMediaStream().CopyTo(context.Response.OutputStream);
      // As momma always said: Always remember to flush
      context.Response.Flush();
      context.Response.End();
    }

    public bool IsReusable
    {
      get
      {
        return false;
      }
    }
}

The trick lies within the type casting of an Item to a MediaItem. The MediaItem contains a media stream that can be copied to the OutputStream of the HttpResponse.

Another trick is to construct a file name from the item name + the file extension found on the MediaItem. Adding this to the output stream will make the name appear as the default file name when users saves your file.

Posted in .net, c#, Sitecore 7 | Tagged , , , | Leave a comment

Sitecore Code Generator and O/R mapper

You probably have never used a code generator for the same reason I never used a code generator:

  • It was not developed by yourself
  • It’s not generating the code you need
  • You cannot extend it with the stuff you need
  • They create the same code for all of your Sitecore templates
  • It’s difficult to install
  • It was not developed by yourself.

A code generator that creates an O/R relationship between your Sitecore templates and your code can save you a ton of work though:

  • Automatically update your code when new templates are created
  • There is never any mismatch between your code and Sitecore templates
  • Code class names matches Sitecore template names
  • All field names are mapped correctly

That is why I introduce the open-source, freeware, use-as-you-please:

SITECOREORM, The object/relational code generator for Sitecore.

You can download it here for free, including the source code:

SitecoreORM O/R Mapper and Code Generator

SitecoreORM

http://marketplace.sitecore.net/en/Modules/SitecoreORM_Object-relational_Code_Generator.aspx

The SitecoreORM is a code generator framework that allows you to create code templates based on well-known user controls (.aspx) pages, and generate code from all of your Sitecore templates.

There is no Sitecore package; all you do is to place the code in your solution and call \sitecore modules\Shell\SitecoreORM\Execute.aspx to execute the code generation.

Out of the box, SitecoreORM generates simple classes that maps all fields on a Sitecore template, references to inherited templates, and a static struct of field names to be used by Sitecore’s sc:FieldRenderer.

But the idea behind SitecoreORM is that you define yourself how your classes should look like.

Changing the output is done by simply changing some .aspx pages. For example, the code template is an .aspx page. This is an example of how it looks (simplified, download the code for the complete example):

namespace <%# Namespace %>
{
  public class <%# ClassName %> : BaseItem
  {
    public <%# ClassName %>(Item innerItem)
    {
      InnerItem = innerItem;
    }

    public Item InnerItem { get; private set; }

    public override FieldCollection Fields
    {
      get { return InnerItem.Fields; }
    }

    #region Inherited Templates
<%# ExecuteCodeProvider("SomeClass, SomeDll", "SomeParameter") %>
    #endregion
  }
}

Generating fields, inherited templates or other thing you need, is done using code providers, that you plug in. SitecoreORM uses reflection to find the code, and your class only needs to implement an ICodeProvider interface that has 1 method: Get().

Here is an example of an .aspx file that generates a Sitecore field (a CheckBox field):

<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Import Namespace="Sitecore.Data.Items" %>
<%@ Import Namespace="SitecoreORM.CodeProviders" %>
<%@ Import Namespace="SitecoreORM.Services" %>
<script runat="server">

  void Page_Load(object sender, System.EventArgs e)
  {
    DataBind();
  }

  private TemplateFieldItem Field
  {
    get { return GenerateFieldProperties.Field; }
  }

  private string PropertyName
  {
    get { return NameNormalizerService.GetValidName(Field.Name); }
  }

</script>

    /// <summary>
    /// <%# TemplateFieldItemCommentService.GetComment(Field) %>
    /// </summary>
    public Sitecore.Data.Fields.CheckboxField <%# PropertyName %>
    {
      get { return InnerItem.Fields["<%# Field.Name %>"]; }
    }

The SitecoreORM framework is written with the fewest lines of code that will do the job. The code is as simple as possible, making it easier to extend, and for you to understand.

Features of SitecoreORM includes:

  • O/R mapping Sitecore templates to C# code.
  • Every aspect of the code generation can be customized:
    • .aspx page based code generation allows you to choose the class format yourself.
    • Each field type from Sitecore can be mapped by .aspx pages, making it easy to determine how each field type from Sitecore should be mapped.
    • Inherited templates can be mapped as well.
    • Field names can be mapped to be used by sc:FieldRenderer
    • Plug in your own code; SitecoreORM uses reflection to find the code generators.
  • Customizable output using an include file (/app_config/include/sitecoreorm.config)
    • Determine class name suffixes
    • Determine name space prefixes
    • Determine removing of parts of template path from namespace
    • Determine file destination for generated classes
    • Specify which template folders to ignore (for example /system/ and /common/)
    • Specify which template folders to include
    • Specify file destination for each template folder to include, hence supporting component based development
  • Simple code. Less than 20 classes makes up the framework. 5 services and one repository does most of the job.

Check out SitecoreORM here:

http://marketplace.sitecore.net/en/Modules/SitecoreORM_Object-relational_Code_Generator.aspx

Posted in c#, Sitecore, Sitecore 7 | Tagged , , , | 19 Comments

Sitecore.Data.ID The call is ambiguous

In Sitecore you cannot compare an ID with NULL; it will produce the following error:

The call is ambiguous between the following methods or properties: ‘Sitecore.Data.ID.operator ==(Sitecore.Data.ID, Sitecore.Data.ID)’ and ‘Sitecore.Data.ID.operator ==(Sitecore.Data.ID, Sitecore.Data.ShortID)’

I.e. the following code is invalid:

Sitecore.Data.ID someID = GetSomeID();
if (someID == null)
{
  // do code
}

Instead, you need to call Sitecore.Data.ID.IsNullOrEmpty():

ID someID = GetSomeID();
if (Sitecore.Data.ID.IsNullOrEmpty(someID))
{
  // do code
}

A quick tip for you.

Posted in c#, Sitecore, Sitecore 7 | Tagged , | 2 Comments

Get Sitecore placeholders and rendering hierarchy from a Sitecore item

This article explains how you can get the hierarchy of placeholders and render them to your screen for debugging or documentation purposes.

The Sitecore rendering engine allows you to place sublayouts inside placeholders. These sublayouts can contain more placeholders, thus creating a hierarchy of placeholders inside a page.
This hierarchy can get pretty complex. So the following code demonstrates how you can read the layout from an item, the placeholders from a layout or sublayout, which renderings will be rendered where and how to write it all to an XML document that will show you which sublayout contains which placeholders and which renderings is rendered where.

STEP 1: THE BASIC METHODS:

First i give you the basic methods for getting layout, placeholder and rendering information. These methods can be copied into your own project directly:

  /// <summary>
  /// Return all placeholder keys defined on one item
  /// </summary>
  private IEnumerable<string> GetPlaceholderKeys(Sitecore.Data.Items.Item item)
  {
    List<string> uniquePlaceholderKeys = new List<string>();
    Sitecore.Layouts.RenderingReference[] renderings = GetRenderingReferences(item, "default");
    foreach (var rendering in renderings)
    {
      if (!uniquePlaceholderKeys.Contains(rendering.Placeholder))
        uniquePlaceholderKeys.Add(rendering.Placeholder);
    }
    return uniquePlaceholderKeys;
  }

  /// <summary>
  /// Search for all placeholder controls in a specific file and return all the placeholder keys
  /// </summary>
  /// <param name="relativePath"></param>
  /// <returns></returns>
  private IEnumerable<string> GetPlaceholderKeysFromFile(string relativePath)
  {
    string text = System.IO.File.ReadAllText(HttpContext.Current.Server.MapPath(relativePath));
    string pattern = "<sc:Placeholder (.*?) />";
    MatchCollection matches = Regex.Matches(text, pattern, RegexOptions.IgnoreCase);
    foreach (Match match in matches)
    {
      string keyPattern = @"Key=""(.*?)""";
      Match keyMatch = Regex.Match(match.Value, keyPattern, RegexOptions.IgnoreCase);
      yield return keyMatch.Value.Replace("Key=", "").Replace("key=", "").Replace("\"", "");
    }
  }

  /// <summary>
  /// Return all renderings to be rendered in a specific placeholder on the "default" device
  /// </summary>
  private IEnumerable<Sitecore.Data.Items.RenderingItem> GetRenderings(string placeholderKey, Sitecore.Data.Items.Item item)
  {
    Sitecore.Layouts.RenderingReference[] renderings = GetRenderingReferences(item, "default");
    foreach (var rendering in renderings)
    {
      if (rendering.Placeholder == placeholderKey)
      {
        yield return rendering.RenderingItem;
      }
    }
  }

  /// <summary>
  /// Return all renderings from an item defined on a device
  /// </summary>
  private Sitecore.Layouts.RenderingReference[] GetRenderingReferences(Sitecore.Data.Items.Item item, string deviceName)
  {
    Sitecore.Data.Fields.LayoutField layoutField = item.Fields["__renderings"];
    Sitecore.Layouts.RenderingReference[] renderings = layoutField.GetReferences(GetDeviceItem(item.Database, deviceName));
    return renderings;
  }

  /// <summary>
  /// Get the layout from an item defined on a device
  /// </summary>
  private Sitecore.Data.Items.LayoutItem GetLayout(Sitecore.Data.Items.Item item, string deviceName)
  {
    Sitecore.Data.Fields.LayoutField layoutField = item.Fields["__renderings"];
    return new Sitecore.Data.Items.LayoutItem(item.Database.GetItem(layoutField.GetLayoutID(GetDeviceItem(item.Database, deviceName))));
  }

  /// <summary>
  /// Convert a Sitecore item to a Sublayout item
  /// </summary>
  private Sitecore.Data.Items.SublayoutItem GetSublayout(Sitecore.Data.Items.Item item)
  {
    return new Sitecore.Data.Items.SublayoutItem(item);
  }

  /// <summary>
  /// Get the device item from a device name
  /// </summary>
  private Sitecore.Data.Items.DeviceItem GetDeviceItem(Sitecore.Data.Database db, string deviceName)
  {
    return db.Resources.Devices.GetAll().Where(d => d.Name.ToLower() == deviceName.ToLower()).First();
  }

  /// <summary>
  /// Get all placeholder settings that defines the specified placeholderKey
  /// </summary>
  private IEnumerable<Sitecore.Data.Items.Item> GetPlaceholderSettings(Sitecore.Data.Database db, string placeholderKey)
  {
    Sitecore.Data.Items.Item root = db.GetItem("/sitecore/layout/Placeholder Settings");
    foreach (Sitecore.Data.Items.Item descendant in root.Axes.GetDescendants())
    {
      if (descendant.Template.Key != "placeholder")
        continue;
      if (descendant["Placeholder Key"].ToLowerInvariant() == placeholderKey.ToLowerInvariant())
        yield return descendant;
    }
  }

Each of these methods can be used individually, but will reference each other some times.

To get all placeholders (i.e. the placeholder “keys” which is the unique definition of a placeholder) that contain renderings, call GetPlaceHolderKeys().

To get all placeholders that is defined in a file, call GetPlaceholderKeysFromFile(). This method uses a Regular Expression to extract all the <sc:Placeholder /> definitions found inside a source file.

To get all renderings that is rendered to a specific placeholder, call GetRenderings().

STEP 2: GET THE HIERACHY OF RENDERINGS:

Here is how to use the methods. These 2 functions will render an XML structure of the hierarchy of layouts, sublayouts, placeholder etc. You should replace /sitecore/templates/somepage/__Standard Values with the page you wish to get the hierarchy from:

  void Page_Load(object sender, System.EventArgs e)
  {
    Sitecore.Data.Database db = Sitecore.Configuration.Factory.GetDatabase("master");
    Sitecore.Data.Items.Item item = db.GetItem("/sitecore/templates/somepage/__Standard Values");

    Response.Write(string.Format(@"<template name=""{0}"">", GetLayout(item, "default").Name));
    foreach (string s in GetPlaceholderKeysFromFile(GetLayout(item, "default").FilePath))
    {
      RenderPlaceholders(s, item);
    }
    Response.Write("</template>");
  }

  private void RenderPlaceholders(string placeholderKey, Sitecore.Data.Items.Item item)
  {
    IEnumerable<Sitecore.Data.Items.Item> placeholderSettings = GetPlaceholderSettings(item.Database, placeholderKey);
    int settingsCount = placeholderSettings.Count();
    bool editable = placeholderSettings.Any(i => i["Editable"] == "1");
    Response.Write(string.Format(@"<placeholder key=""{0}"" editable=""{1}"">", placeholderKey, settingsCount, editable));

    foreach (var rendering in GetRenderings(placeholderKey, item))
    {
      Response.Write(string.Format(@"<sublayout name=""{0}"">", rendering.Name));
      foreach (string s in GetPlaceholderKeysFromFile(GetSublayout(rendering.InnerItem).FilePath))
      {
        RenderPlaceholders(s, item);
      }
      Response.Write(@"</sublayout>");
    }
    Response.Write(@"</placeholder>");
  }

The method will output something like this:

<template name="Default">
  <placeholder key="SeoRegion" editable="0">
    <sublayout name="MetaTags"></sublayout>
    <sublayout name="DocumentDescription"></sublayout>
    <sublayout name="OpenGraph"></sublayout>
  </placeholder>
  <placeholder key="phPageHolder" editable="1">
    <sublayout name="DefaultPage">
      <placeholder key="DefaultColumn1" editable="0">
        <sublayout name="DefaultPage">
        </sublayout>
      </placeholder>
    </sublayout>
  </placeholder>
  <placeholder key="ScriptsRegion" editable="0">
    <sublayout name="GoogleAnalytics"></sublayout>
  </placeholder>
</template>

That’s it. Happy coding.

Posted in c#, General .NET, Sitecore 5, Sitecore 6, Sitecore 7 | Tagged , , , , , | 6 Comments

Register local search terms in Sitecore DMS

When installing Sitecore and DMS, Sitecore comes pre-installed with an Executive Insight Dashboard Report that will generate a report containing which search words users have executed in your local search page.

All you need to do is to register a Page Event called “Search” on your search result page:

using Sitecore.Analytics;
using Sitecore.Analytics.Data;

protected void RegisterSearchPageEvent(string searchQuery)
{
  if (!Tracker.IsActive || Tracker.Visitor == null || Tracker.Visitor.CurrentVisit == null)
    return;

  var page = Tracker.Visitor.CurrentVisit.CurrentPage;
  if (Tracker.Visitor.CurrentVisit.PreviousPage != null)
    page = Tracker.Visitor.CurrentVisit.PreviousPage;

  page.Register(new PageEventData("Search")
  {
    Data = searchQuery,
    DataKey = searchQuery.ToLowerInvariant(),
    Text = searchQuery
  });
}

What happens behind the scenes is that the search terms (the parameter “searchQuery” is registered as a page event on the current page. The page event “Search” is defined in Sitecore:

Page Event "Search"

Page Event “Search”

And a SQL Query Report is defined to get the visits per data query.

SQL Query

SQL Query

And the Executive Dashboard is configured to display the search terms from this SQL Query (you will find the report under “Site Search“):

Dashboard displaying the search terms

Dashboard displaying the search terms

Please note that the report will not display any data below 50 hits, unless you configure this in the .config file located here:

\sitecore\shell\Applications\Reports\Dashboard\Configuration.config

Look for the setting called “MinimumVisitsFilter“. This filter determines how many (or how few) visits are required before they are considered a significance.

In the demo above, I have set the value to “5”:

<ConfigurationParams>
  ...
  <MinimumVisitsFilter value="5" />
  ...
</ConfigurationParams>

Also note that this setting influences every report in the Executive Dashboard, so be careful not to set it too low or it will slow down other reports significantly.

READ MORE:

 

 

Posted in .net, c#, Sitecore 6, Sitecore 7 | Tagged , , , , | 1 Comment

JavaScript ReferenceError: Can’t find variable: __doPostBack

This error can occur in asp.net pages. In certain situations .net will fail to create the __doPostBack JavaScript function and the hidden __EVENTVALIDATION input field. The situations that can cause this are:

  • When using asp:ImageButton or asp:LinkButton controls AND:
  • When viewing the webpage using IE11
  • When viewing the webpage using an iPhone or an iPad that was updated to the latest iOS 7.

The problem revolves around the BrowserDefinitions in .NET. .NET uses these Browser Definitions to identify the capabilities of the current browser. As per default, if a browser is not known (i.e. not defined in the BrowserDefinitions), .net assumes that the browser have no JavaScript capabilities, and therefore the __doPostBack function is not needed.

Assuming that a browser is not JavaScript compatible by default in the year 2013 is probably pretty stupid. However, this is the reality and we need to cope with it. 

The browserdefintions are defined in the belly of .net (for example here: c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\Browsers).

The later a .net version you have, the newer the .net version, the more likely it is that the browser is known. .NET 4.5 should know about  IE11 and the latest iPhone and iPad browsers.

POSSIBLE SOLUTION: SETTING THE CLIENTTARGET=UPLEVEL ON THE .ASPX PAGE

There is a way to override the automatic detection of browser capabilities. Add the following to the <% Page %> directive on all of your .aspx pages:

<%@ Page Language="C#"
    AutoEventWireup="true"
    CodeBehind="somecodebehind"
    ClientTarget="uplevel"
    Inherits="somecode" %>

Setting ClientTarget=”uplevel” forces the page to generate JavaScript as it assumes that the browser has at least the capabilities of IE6.

MORE TO READ:

Posted in .net, c#, General .NET | Tagged , , , , | 1 Comment