Quantcast
Channel: briancaos – Brian Pedersen's Sitecore and .NET Blog
Viewing all 264 articles
Browse latest View live

Migrating huge amounts of Sitecore content – Use the Sitecore Serializer

$
0
0

Migrating contents from one Sitecore to another is a common task. We move templates, layouts, content, media library items etc. from development to test and from test to production all the time.

Usually we use the Sitecore Pagkage Designer to pack contents into a package that can be installed on another Sitecore. But sometimes that’s not the best solution.

MIGRATING HUGE AMOUNTS OF DATA

Yesterday I had to move 28.000 items + 16 GB of Media Library contents from development to test.

The packager will not support this amount of contents. A Sitecore package is basically a .zip file with serialized Sitecore items. Each item is a file inside the .zip file.

I tried to create a package containing my 28.000 items, but the size of the zip file grew bigger than 2 GB which caused the package to fail.

HOW TO EASILY MIGRATE 16+GB OF DATA – THE INITIAL SETUP

Instead of creating one (or several) packages of data I create one package containing the “basics” of the project to migrate, i.e.:

  • New Templates (adding dynamically from /sitecore/templates)
  • New Layouts (adding dynamically from/sitecore/layouts)
  • New System settings (adding dynamically from /sitecore/system)
  • New …

Furthermore i add the ROOT node of the contents to migrate:

  • The root node of the Media Library folder I used to create my media library items
  • The root node of the contents to move
Adding root nodes only

Adding root nodes only

The package is now substantially smaller, about 1 Mb  which is a great size for a package. It installs fast and is easy to move around.

This package is installed on the destination Sitecore.

MOVING THE 16+GB DATA

Now I am ready to migrate the actual contents. As said before, 16 GB of data is not easily movable. Unless you use … the Sitecore tree serializer!

The Sitecore serializer can be found on the “Developer” tab. Right click the tabs and select the Developer tab:

Developer Tab

Right click to select the Developer Tab

Select the item to serialize and click the “Serialize Tree” button:

Serialize Tree

Now Sitecore is serializing the item + all sub items into separate files on your hard drive  usually in the /app_data/serialization folder:

Serialized Data

Serialized Data

It will take some time. (It took me 3 hours to generate 16 GB media library items in 12.000 individual files.)

When it’s finished, copy the files to your destination Sitecore machine, and reverse the process by selecting the root node you included in your package and clicking “Revert Tree”:

Revert Tree

Revert Tree

It will take a LOT of time if you have 16+gb of data. But don’t worry, Sitecore is creating a Sitecore Job to de-serialize the data. So you can close the browser while the process is running.

When the job is done you can remove the serialized files from the source and destination machines.



Sitecore Users and C#

$
0
0

The Sitecore security framework is based on the .NET security. Managing Authentication, Authorization, User Accounts and Roles can be done using the standard System.Web.Security namespace.

But Sitecore also provides its own Security framework that you can use for easy manipulation of users and roles seen from a Sitecore perspective.

BASICS OF SITECORE SECURITY

There is 2 things you need to know about Security in Sitecore:

  • Sitecore prefixes user names with a domain name. This is used to differentiate users between those with access to the Sitecore editor (domain: sitecore) and those with access to the Sitecore extranet (domain: extranet).
    So when accessing Sitecore users from System.Web.Security, make sure you remember to ask for sitecore\admin, and not admin.
    (Advanced Sitecore users know that you can create as many domains as you like).
  • In Sitecore there is no such thing as “not been logged in”. If you are not logged in, you will have a user called “extranet\Anonymous“.
    This means that you will always have a user, no matter the context you are running in.

ENOUGH TALK, LETS CODE

Get a user from the domain name, user name and password:

using System.Linq;
using Sitecore.Common;
using Sitecore.Security;
using Sitecore.Security.Accounts;

namespace PT.Framework.NemLogin
{
  public class UserRepository
  {
    /// <summary>
    /// Gets the <see cref="Sitecore.Security.Accounts.User"/>.
    /// </summary>
    /// <param name="domainName">Name of the domain.</param>
    /// <param name="userName">Name of the user.</param>
    /// <param name="password">The password.</param>
    /// <returns><see cref="Sitecore.Security.Accounts.User"/> if found or null if not found</returns>
    public static User GetUser(string domainName, string userName, string password)
    {
      if (!System.Web.Security.Membership.ValidateUser(domainName + @"\" + userName, password))
        return null;
      if (User.Exists(domainName + @"\" + userName))
        return User.FromName(domainName + @"\" + userName, true);
      return null;
    }
  }
}

The above function demonstrates how you can use the System.Web.Security and the Sitecore.Security namespace simultaneously. The function first validates the user using standard .NET security, then uses the Sitecore namespace to get the user.

Login:

The following function will do a login of a specified user:

using Sitecore.Security.Accounts;

using Sitecore.Security.Authentication;
using Sitecore.Web.Authentication;

public static bool Login(string domainName, string userName, string password)
{
 return AuthenticationManager.Login(domainName + @"\" + userName, password, false);
}

And this function will also do a login, but it utilizes the Sitecore TicketManager. The TicketManager manages persistent logins and is used to remember you when you log into the Sitecore backend:

public static bool Login(User user)
{
  string ticketID = TicketManager.GetCurrentTicketId();
  if (!string.IsNullOrEmpty(ticketID))
    TicketManager.RemoveTicket(ticketID);
  return AuthenticationManager.Login(user);
}

Managing Custom Properties on User Profiles:

This is an example on how to store custom data on a user profile, and later search for the user based on the value in the custom field:

#region

using System.Linq;
using Sitecore.Common;
using Sitecore.Security;
using Sitecore.Security.Accounts;

#endregion

namespace MyCode
{
  public class UserRepository
  {
    public static User GetUserFromCustomField(string fieldName, string fieldValue)
    {
      IFilterable<User> allUsers = UserManager.GetUsers();
      return allUsers.Where(user => user.Profile.GetCustomProperty(fieldName) == fieldValue).FirstOrDefault();
    }

    public static void SetCustomField(User user, string fieldName, string fieldValue)
    {
      UserProfile profile = user.Profile;
      profile.SetCustomProperty(fieldName, fieldValue);
      profile.Save();
    }
  }
}

Read more here:

 


Sitecore Save Dialog appears for unchanged content

$
0
0

Have you ever experienced that Sitecore keeps popping the “Do you want to save the changes to the item?” dialog box, even when you have made no changes to the item? And the box keeps appearing, even when you have chosen “yes”?

Save Dialog

Save Dialog

This scenario can occur if the contents of any multiline field or textbox field contains new lines \n or carriage returns \r.

Memo Box with \r or \n

Memo Box with \r or \n

In rare situations, the browser will interprete \n and \r differently from Sitecore, thus telling Sitecore that the contents in the box differs from the contents in the Sitecore database.

The situation rarely occurs with manually entered text, but if you have programatically added the text to the box, the \n and \r might misalign.

So if you add text to the multiline field (or textbox field) you should ensure that any \n and \r are removed before adding the text.

I made this string extension method to remove all new lines, carriage returns, tabs and strange \xA0′s (which looks like a space but isn’t):

public static string CleanNewLinesTabsAndOtherStuff(this string s)
{
  return s.Replace("\r\n", " ").Replace("\r", " ").Replace("\n", " ").Replace("\t", " ").Replace('\xA0', '\x20');
}

I replace all of them with spaces, but in other situations you could replace them with string.Empty.


Sitecore media library missing PDF icons

$
0
0

When uploading PDF (or Word/Excel/…) documents to the Sitecore media library, the PDF icon is not shown:

PDF icon Missing

PDF icon Missing

In this case it is only my test server that is not showing the icons. My local development machine (my own laptop) displays the icons perfectly:

PDF icon Shown

PDF icon Shown on my laptop

The reason is that Siteore use the associated icon from the OS. Acrobat Reader is installed on my laptop, not on my test server. So the test server displays a default icon instead.

WHAT’S THE SOLUTION THEN?

You can specify which static file to use for which extension in the web.config. To add the PDF icon, do the following:

Download a .pdf icon from the Adobe Website (due to licensing restrictions, Sitecore is not shipped with an Acrobat Reader icon). Place the icon wherever you wish.

Go to the web.config and find the <mediaType name=”PDF file” extensions=”pdf”> section.

Add the following:

<mediaType name="PDF file" extensions="pdf">
  <mimeType>application/pdf</mimeType>
  <forceDownload>false</forceDownload>
  <sharedTemplate>system/media/unversioned/pdf</sharedTemplate>
  <versionedTemplate>system/media/versioned/pdf</versionedTemplate>
  <!-- New section -->
  <thumbnails>
    <generator type="Sitecore.Resources.Media.MediaThumbnailGenerator, Sitecore.Kernel">
      <extension>png</extension>
    </generator>
    <staticFile>File location. For example /sitecore/shell/Themes/pdficon_large.png</staticFile>
  </thumbnails>
  <!-- end:New section -->
</mediaType>

Clear the following folders:

  • /temp/ folder (at least all files named “fileIcon”)
  • The Sitecore media cache (default located at /app_data/mediacache/)

Clear your browser cache.

Restart the website.

That’s it. You now have PDF icons in Sitecore.

MORE READING:


Create components for Sitecore Page Editor

$
0
0

This quick tutorial will guide you trough your first Sitecore Page Editable Component.

The Sitecore Page Editor is build around the concept of components. A component is a reusable piece of data that is parred with a sublayout and placed in a placeholder.

Sitecore Page Editor

Sitecore Page Editor’

This tutorial will create a very simple component: Adding an Image.

To create components you must go through a series of steps.

STEP 1: CREATE THE TEMPLATE

First you need a Sitecore Template where you store the data. My component must show an image, so all my template contains is an image field called “DocumentImage”:

Document Image Template

Document Image Template

STEP 2: FIND A HOME FOR TEMPLATES OF THIS TYPE

Next step is to define a place in your web site structure where you will store templates of this type. Templates of different types can share a folder, or you can create a folder specifically for this type.

You will need the folder in a few steps.

STEP 3: CREATE A SUBLAYOUT

Now you need a sublayout that will present the data from the template.

Document Image Sublayout

Document Image Sublayout

When creating the sublayout you must determine the contents of these fields:

  • Editable: Check this box. If unchecked, the sublayout is not editable and cannot be used in the Page Edittor as a component.
  • Datasource location: Select the folder you chose in STEP 2. When the component is added to a page, this is where the data will be stored.
  • Datasource template: Select the template you created in STEP 1. When the component is added to a page, this is the template that is created.

STEP 4: ASSIGN THE SUBLAYOUT TO A PACEHOLDER SETTING

Each placeholder that is capable of having controls added must be defined in the folder /sitecore/layout/Placeholder Settings.

Select the placeholder setting where you would like to allow your control to be added and add it to the “Allowed controls” field:

Placeholder setting

Placeholder setting

Read more about placeholder settings here.

STEP 5: CODE THE CONTENTS OF THE SUBLAYOUT

When a component is added to a page it is parred to a item through the “datasource” parameter on the layout. This means that you cannot get the data for your component with Sitecore.Context.Item. Sitecore.Content.Item will point to the actual page where the component is added, not the item assigned to the component.

The following method will get the item from the “datasource”, with a fallback to the current item. The fallback is a safety mechanism that ensures that the item returned is never null, as this will crash the entire web page.

protected Item CurrentContextItem
{
  get
  {
    Sublayout thisSublayout = (Parent as Sublayout);
    if (thisSublayout == null)
      return Sitecore.Context.Item;
    if (string.IsNullOrEmpty(thisSublayout.DataSource))
      return Sitecore.Context.Item;
    string dataSource = thisSublayout.DataSource;
    Item dataSourceItem = Sitecore.Context.Database.GetItem(dataSource) ??
                          Sitecore.Context.ContentDatabase.GetItem(dataSource);
    if (dataSourceItem == null)
      return Sitecore.Context.Item;
    return dataSourceItem;
  }
}

(This method of getting the datasource has not changed the last 5 years).

You now assign the Item parameter of the sc:Image webcontrol to CurrentContextItem, and the control will take its data from that item:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="DocumentImage.ascx.cs" Inherits="PT.Document.DocumentImage" %>
<%@ Register TagPrefix="sc" Namespace="Sitecore.Web.UI.WebControls" Assembly="Sitecore.Kernel" %>

<div class="Image">
  <sc:Image field="DocumentImage" id="Image" runat="server" Item="<%# CurrentContextItem %>" MaxHeight="200" />
</div>

If your component uses an EditFrame you need to get the actual value from the datasource, not the item it points to. In this case you need another method:

protected string DataSourceValue()
{
  Sublayout sublayout = (Parent as Sublayout);
  return (((sublayout == null) || string.IsNullOrEmpty(sublayout.DataSource)) ? string.Empty : sublayout.DataSource);
}

You can now use this method to ensure your EditFrame uses the correct item:

<sc:EditFrame runat="server" ID="editFrame" Buttons="???" DataSource="<%# DataSourceValue %>">
  ...
  ...
</sc:EditFrame>

That’s all there is to it. Happy coding.

More reading:


Sitecore 404 without 302

$
0
0

It’s a well known issue that when Sitecore displays a 404 page, it’s done by doing a 302 redirect to the 404 page, hence the 302 status code is thrown before the 404.

The behavior is partially because of the way .net handles redirects internally. But fortunately you can overcome it pretty easily.

My colleague Anders Laub came up with this extension to the Sitecore httpRequestBegin pipeline.

By adding a processor to the pipeline you can switch the missing item with a 404 item inside Sitecore. The item in Sitecore will then have a sublayout that will throw the 404. In effect, Sitecore (or .net) will not know that there is a page missing and will continue the business as usual. It’s up to us developers to handle the 404. Just like we like it.

First the httpRequestBegin processor:

<httpRequestBegin>
  <processor type="Sitecore.Pipelines.PreprocessRequest.CheckIgnoreFlag, Sitecore.Kernel" />
  ...
  ...
  <processor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel" />
  <processor type="MyCode.Custom404ResolverPipeline, MyDll" />
  ...
  ...
</httpRequestBegin>

The processor is added just after the ItemResolver. The ItemResolver returns null if the item is not found.

We then switch the missing item with our 404 item:

public class Custom404ResolverPipeline : HttpRequestProcessor
{
  public override void Process(HttpRequestArgs args)
  {
    Assert.ArgumentNotNull(args, &quot;args&quot;);

    // Do nothing if the item is actually found
    if (Sitecore.Context.Item != null || Sitecore.Context.Database == null)
      return;

    // all the icons and media library items 
    // for the sitecore client need to be ignored
    if (args.Url.FilePath.StartsWith(&quot;/-/&quot;))
      return;

    // Get the 404 not found item in Sitecore.
    // You can add more complex code to get the 404 item 
    // from multisite solutions. In a production 
    // environment you would probably get the item from
    // your website configuration.
    Item notFoundPage = Sitecore.Context.Database.GetItem(&quot;{DFE03D7A-00B9-4C15-8AB7-482D82B3484E}&quot;);
    if (notFoundPage == null)
      return;

    // Switch to the 404 item
    Sitecore.Context.Item = notFoundPage;
  }
}

The page we switched to needs to have a sublayout that will do the actual 404 return code:

public partial class _404 : System.Web.UI.UserControl
{
  protected void Page_Load(object sender, EventArgs e)
  {
    HttpContext.Current.Response.StatusCode = (int)HttpStatusCode.NotFound;
    HttpContext.Current.Response.TrySkipIisCustomErrors = true;
    HttpContext.Current.Response.StatusDescription = &quot;Page not found&quot;;
  }
}

Remember to set the TrySkipIisCustomErrors or your 404 page will be ignored by IIS 7.5.

MORE READING:


The URL-encoded form data is not valid error in Sitecore Page Editor

$
0
0

In ASP.NET 3.5 you may encounter one of the following error messages:

“Operation is not valid due to the current state of the object”

or:

“The URL-encoded form data is not valid”

WHY?

Microsoft released a Security update at dec 29 2012. The update contained (amongst other features) an upper bound to the number of simultaneous HTTP form elements that can be posted. The new limit is 1000.

In the Sitecore Page Editor you may experience that for large pages you have more than 1000 form elements.

WHAT TO DO?

To fix this you should add the following to the web.config:

In the <appSettings> of the web.config add the following:

<add key=”aspnet:MaxHttpCollectionKeys” value=”value greater than 1000” />

Thanks to Alin Parjolea for the tip.


Sitecore 7 is coming: My wish list for Sitecore 7.01

$
0
0

The next big release of Sitecore 7 is on it’s way. This time Sitecore have listened to many MVP’s and have adressed some technical issues.

Most of this release revolves around scalability and indexing. The Lucene index have been updated to allow even more impressive performance and even higher scalability. The old Sitecore 6 would easily swallow 1.000.000 items. Sitecore 7 have almost no limitations. Even on folder level, where the old Sitecore 6 stalled at around 200 items at the same level; Sitecore 7 implements a revamped version of Sitecore Item Buckets, allowing you to have an infinite amount of items at the same level.

So what’s next on my wish list? Could I ask for more? Of course. More wants more. Here is the areas where I would like Sitecore to focus on for the first update of Sitecore 7:

SITECORE PAGE EDITOR

AN EXTENSIBLE PAGE EDITOR

We need the Sitecore Page Editor to be extensible, like the rest of Sitecore.

If you wish to add a new field to the Sitecore shell, you write some code, put it in a DLL and points to the field. No modifications to Sitecore itself.

In the Page editor, you need to modify one of Sitecore’s own Javascript files (FieldChromeType.js). This file changes with every version of Sitecore, and you cannot just append your modifications, as you need to modify namespaces and switch statements.

This is bad and very Sitecore-unlike behavior. Sitecore is a platform. You never modify the platform. You extend it. Except for the page editor. Sigh.

PAGE EDITOR LINK FIELD TYPE NEEDS TO ALLOW LOOKUP FIELDS

If you would like to have a link popup box in the page editor, you must have a general link field. The page editor should have a popup for all lookup fields, so you can have fields where only internal links are allowed.

PAGE EDITOR SHOULD HAVE A BUTTON FOR THE MEDIA LIBRARY

A button that opened the media library would be nice please. It’s 10 lines of code. I can give you the code.

PAGE EDITOR SHOULD BE ABLE TO EDIT NON-VISUAL FIELDS

Not all fields are visible. But you should be able to edit them anyway. Thomas Stern made a solution. This should be standard.

PAGE EDITOR SHOULD BE ABLE TO PUBLISH EVERYTHING THAT’S INCLUDED ON THE PAGE

The publish button on the page editor will only publish the current item (and it’s subitems). But most pages have included components. These components are not stored on the item, and often not as children to the current item. When publishing from the page editor, these components should be published as well. Otherwise you cannot publish them from the Page Editor at all.

PAGE EDITOR SHOULD BE CHEAPER TO DEVELOP FOR

This is not a no-brainer. The Page Editor is the coolest thing since sliced bread. Customers are requesting page editable websites more and more often.

Unfortunately, if you are aiming at building a page-editor only website (a website that is edited completely using the page editor) costs explode. The number of lines explode, as you have to consider many more states and exceptions.

You have to add code to allow fields that is hidden if empty to be shown, only in page edit mode. And how do you make a carrousel page editable? And what about the metadata fields?

How do you allow the customer to version pages, if the page consists of components?

All of these simple, little decisions complicate your code, making it more expensive to develop, and more expensive to test.

SITECORE SHELL

ADDING COMPONENTS USING THE SHELL

If you wish to use the Sitecore DMS in it’s full extent you are bound to make a Page Editable website. Why? Because the Page Editor is the only place where you can add components to a page. Components are necessary if you would like to use A/B Testing and Real Time Personalisation.

We need the dialogs for adding components to a page available in the Sitecore Shell.

I would expect the Sitecore Shell to contain the full set of functions, and the Page Editor to be a subset of functionality.

Yes I know that I can use the Presentation->Details button to open the Layout Details and from there I can add my components. But my customer cannot. It’s a developer tool and way too complicated to use.

Please make a user friendly way of adding components to a page from the Sitecore Shell.

CODING

SC:FIELDRENDERER

The sc:FieldRenderer should be able to get a NULL item without crashing. This would remove half of my code lines in my components.

SC:LINK

The sc:Link should be able to use lookup fields and not only the general link field.

SC:EDITFRAME

The sc:EditFrame should accept a Sitecore item instead of a Item path in the DataSource property.



Sitecore allow email adress as user name

$
0
0

If you try to create a new user in Sitecore with an email address as account name you will be met with a validation error:

“user name is not valid in the selected domain.”

But do not worry, it’s not the underlying security framework that disallows email as username, it’s just a default validation of user names in the user manager dialog box.

The default validation is based on this regular expression: ^\w[\w\s]*$

You can change the regular expression using the AccountNameValidation setting. Add the following to your web.config:

<setting name="AccountNameValidation" value=".+" />

When using the users email address as username, you should consider changing the membership provider in Sitecore to require a unique email. Otherwise you risk having users that share email addresses.

The change is simple. Go to web.config and change the attribute “requiresUniqueEmail” to “true“:

<add name="sql"
type="System.Web.Security.SqlMembershipProvider"
connectionStringName="core"
applicationName="sitecore"
minRequiredPasswordLength="1"
minRequiredNonalphanumericCharacters="0"
requiresQuestionAndAnswer="false"
requiresUniqueEmail="true"
maxInvalidPasswordAttempts="256" />

 


Sitecore 6.6: CSRF form field is missing

$
0
0

In the latest version of Sitecore 6.6 (release 13.04.04) I sometimes get this error:

Exception: Sitecore.Security.AntiCsrf.Exceptions.PotentialCsrfException
Message: CSRF form field is missing.
Source: Sitecore.Security.AntiCsrf
at Sitecore.Security.AntiCsrf.SitecoreAntiCsrfModule.RaiseError(Exception ex, HttpContext context)
at Sitecore.Security.AntiCsrf.SitecoreAntiCsrfModule.PreRequestHandlerExecute(Object sender, EventArgs e)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

The issue seemes to be related to an implementation of AntiCSRF, a Microsoft Public License library that prevents Cross Site Request Forgery.

HOW TO FIX IT:

The fix is easy. Clear your cookies, clear the browser cache, close the browser and try again.

UPDATE:

The clever guys at Sitecore Support have come up with this (untested) quick fix that you can try:

Please add these lines to the Sitecore.AntiCsrf.config file (website/app_config/include/Sitecore.AntiCsrf.config):

<ignore wildcard="/sitecore/shell/*Applications/Security/User*Manager*?*Cart_Users_Callback=yes"/>
<ignore wildcard="/sitecore/shell/*Applications/Security/Role*Manager*?*Cart_Roles_Callback=yes"/>
<ignore wildcard="/sitecore/shell/*Applications/Security/Domain*Manager*?*Cart_Domains_Callback=yes"/>
<ignore wildcard="/sitecore/shell/~/xaml/Sitecore.Shell.Applications.Security.SelectAccount*Cart_*_Roles_Callback=yes"/>
<ignore wildcard="/sitecore/shell/~/xaml/Sitecore.Shell.Applications.Security.SelectAccount*Cart_*_Users_Callback=yes"/>

UPDATE 2:

The tough guy could choose to disable AntiCSRF completely. Add the following line in the /App_Config/Include/Sitecore.AntiCSRF.config file:

<?xml version="1.0"?>
<configuration>
  <sitecore>
    <AntiCsrf>
      <rules>
        <rule name="shell">
          <!-- Ingore AntiCSRF completely -->
          <ignore wildcard="/sitecore/*"/>
        </rule>
      </rules>
    </AntiCsrf>
  </sitecore>
</configuration>

.NET DateTime to JSON UNIX JavaScript datetime

$
0
0

When posting datetimes to web services or REST services, you might need to convert the standard .NET DateTime to a UNIX format.

Please note than neither REST, nor JavaScript has its own DateTime format. But some systems based on REST (Java applications for example) have a love affair with the UNIX Epoch datetime format, which is the number of seconds since 1/1/1970.

So in order to convert a standard .NET DateTime to a number of seconds, you need to calculate the TimeSpan between the current DateTime and 1/1/1970:

public string ToUnixEpoch(DateTime dateTime)
{
  DateTime d1 = new DateTime(1970, 1, 1);
  DateTime d2 = dateTime.ToUniversalTime();
  TimeSpan ts = new TimeSpan(d2.Ticks - d1.Ticks);
  return ts.TotalMilliseconds.ToString("#");
}

The string returned contains the integer of the timespan.

In some systems you need to add /Date()/ around your timespan, so the resulting UNIX Epoch string looks like this:

/Date(1234567891234)/

To convert the UNIX datetime to a .NET DateTime you reverse the process:

public DateTime FromUnixEpoch(long epochTime)
{
    var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    return epoch.AddSeconds(epochTime);
}

Some systems will return the UTC time, not the time from the current timezone. To convert from UTC to the current timezone, use the TimeZone class from .NET:

TimeZone.CurrentTimeZone.ToLocalTime(yourutctime)

 


Programming for Sitecore DMS Engagement Plans

$
0
0

Sitecore DMS Engagement plans allow you to control some of the specific ways in which your website interacts and communicates with the visitors to your website. Think of engagement plans as a configurable flow state engine. It allows you to push the responsibility of automation to your customer.

Engagement plans are especially usefull when you have named visitors, ie users with a username and an email.

Sample Engagement Plan

Engagement plans are controlled through the Sitecore.Analytics.Automation.VisitorManager class and the Sitecore.Analytics.Tracker class.

The Tracker class can be used for connecting a named user to the DMS visitor:

using Sitecore.Analytics;
using Sitecore.Analytics.Automation;

private void CheckAndSetTracker(string userName)
{
  if (Tracker.IsActive)
  {
    Tracker.Visitor.ExternalUser = userName;
  }
}

The userName is a fully qualified name, ie extranet\user.

To assign a user to an engagement plan, simply call VisitorManager.AddVisitor with the ID of the engagement plan state:

public bool AssignToEngagementPlan(User user, ID engagementPlanIdStartState)
{
  bool addVisitor = VisitorManager.AddVisitor(user.Name, engagementPlanIdStartState);
  return addVisitor;
}

The User is the Sitecore.User.

usually you would assign the user to the first step of your engagement plan, but you can in fact assign the user to any state you wish.

You can also move a visitor from one state to another:

VisitorManager.MoveVisitor(userName, source, destination);

You can also search for a specific user in an engagement plan to see is the user is alreay assigned to any state in that engagement plan:

private bool UserIsInAnyEngagementPlanState(string userName, ID engagementPlan, out ID stateId)
{
  var result = false;
  stateId = null;
  foreach (Item state in GetEngagementPlanItem(engagementPlan).Children)
  {
    result = VisitorManager.GetStateVisitors(state.ID).Any(visitor => visitor.Equals(userName));
     if (result)
    {
      stateId = state.ID;
      break;
    }
  }
  return result;
}

That’s basically it. It’s very easy to work with once you get the hang of it.

READ MORE:

 


A potentially dangerous Request.QueryString value was detected from the client

$
0
0

One of my colleagues encountered this error in Sitecore 6.6:

A potentially dangerous Request.QueryString value was detected from the client

You might think that this error was caused by the Microsoft AntiCSRF implementation by Sitecore.  But it is not, it’s actually caused by .NET 4.0:

According to Microsoft, they have changed the ASP.NET Request validation:

In ASP.NET 4, by default, request validation is enabled for all requests, because it is enabled before the BeginRequest phase of an HTTP request. As a result, request validation applies to requests for all ASP.NET resources, not just .aspx page requests. This includes requests such as Web service calls and custom HTTP handlers. Request validation is also active when custom HTTP modules are reading the contents of an HTTP request. Source: http://www.asp.net/whitepapers/aspnet4/breaking-changes#0.1__Toc256770147

If you encounter this error, you need to switch the RequestValidationMode back to the good old mode:

<httpRuntime requestValidationMode="2.0" />

Read more here:

Thanks to Anders Laub Christoffersen for the tip.


(System.Web.UI.HtmlControls.HtmlIframe) is not compatible with the type of control (System.Web.UI.HtmlControls.HtmlGenericControl)

$
0
0

Yesterday I got the following error:

Parser Error Message: The base class includes the field ‘IFrame’, but its type (System.Web.UI.HtmlControls.HtmlIframe) is not compatible with the type of control (System.Web.UI.HtmlControls.HtmlGenericControl).

Source Error:

Line 14: <iframe id=”IFrame” frameborder=”0″  runat=”server” visible=”false”/>

Reason:

The code was written using .NET 3.5 but executed in the .NET 4.5 runtime. From .NET 4.5, Microsoft decided to change the iframe from a HtmlGenericControl to its own control, a HtmlIframe.

They did this with a wide range of controls for example System.Web.UI.HtmlControls.HtmlTableCell and System.Web.UI.HtmlControls.HtmlAnchor.

Solution:

You need to recompile the code using the .net 4.5 runtime.
And when doing this, you need to change the *.designer.cs file reference from:

protected global::System.Web.UI.HtmlControls.HtmlGenericControl IFrame;

To:

protected global::System.Web.UI.HtmlControls.HtmlIframe IFrame;

A bug has been reported that the VS2012 does not make this change itself, but all I had to do was to rename the ID of the IFrame control, and VS figured it out for me.

More information:


Custom rules and conditions for Sitecore personalization

$
0
0

In Sitecore it is possible to change the sublayouts or data sources on a page depending on conditions. This means that users can see the same page differently, depending on a set of rules. Sitecore comes standard with a set of standard rules (The user’s geographic place, has the user met any goals, has the user been referred etc.).

As always with Sitecore (well, except for the page editor) you can extend Sitecore’s functionality by creating your own rules.

THE EXAMPLE: QUERY STRING CONDITION

This is an example of a rule that checks for a certain QueryString (in this case a ZipCode). If the querystring contains a certain value, the condition is met.

First you need to set up the condition. Conditions are defined in folders below /sitecore/system/Settings/Rules/Segment Builder/Conditions:

My Custom Condition

My Custom Condition

The cryptic text attribute is used to explain and configure the condition at the same time:

Where the querystring parameter zipcode [operatorid,Operator,,compares to] [value,,,value]

The string is case sensitive. [operatorid,Operator,,compares to] gives you the options “equal to, not equal to, less than, greater than, …”, and [value,,,value] gives you the possibility to enter a value.

Then you need to write the code. This condition compares integers, so I inherit from the IntegerComparisonCondition. There are other conditions to inherit from as well, like the OrCondition, NotCondition, AndCondition, OperatorCondition, BinaryCondition etc.

namespace MyCode.Infrastructure
{
  public class CheckQuerystringCondition<T> : IntegerComparisonCondition<T> where T : RuleContext
  {
    protected override bool Execute(T ruleContext)
    {
      Assert.ArgumentNotNull(ruleContext, "ruleContext");
      var zipCodeQuerystringValue = HttpContext.Current.Request.QueryString["zipcode"];
      int zipCode;
      if (string.IsNullOrEmpty(zipCodeQuerystringValue))
      {
        return false;
      }

      if (!int.TryParse(zipCodeQuerystringValue out zipCode))
      {
        return false;
      }

      return Compare(zipCode);
    }
  }
}

The condition is now ready to be used.  To use it you need to find the layout of the page you wish to personalize. You can see if there is conditions on a layout by the little number mark:

Layout Details

Layout Details

Select the control to personalize and start personalizing. In this example I choose between 2 diferent sublayous depending on the condition. You can also choose to switch the data context (i.e. where the component gets its data from).

Personalize The Component

Personalize The Component

The personalization is pretty straight forward, and reminds of the way you would set up rules in Microsoft Outlook:

Rule Set Editor

Rule Set Editor

Thanks to Emil Klein for the code.

READ MORE:



Avoid the use of Sitecore.Context.Item

$
0
0

Or: How Sitecore DMS and Personalization killed the Sitecore.Context.Item. This post is about how to properly ensure that you get data from the correct item when you develop websites that can be personalized.

The Sitecore.Context.Item returns the current item, i.e. the item that corresponds with the URL that the user has entered. Old Sitecore developers like me has been used to adding the fields of a page to the current item, and then accessing these fields directly using the sc:FieldRenderer:

<%@ Register TagPrefix="sc" 
    Namespace="Sitecore.Web.UI.WebControls" 
    Assembly="Sitecore.Kernel" %>

<sc:FieldRenderer ID="MyField" runat="server" 
FieldName="NameOfMyField"  />

This approach is no longer a good approach, as the the customer have been given access to DMS and personalization. Now you no longer know if the content that the usercontrol need, is part of the current item, or another item.

Personalize the Component

Personalize the Component

The customer can now acces any component he likes, and use personalization to alter the data source (or even the component itself). This breaks the pattern of assuming that content is part of the current item.

So what can we do?

Before we grab any field, we need to go through these steps:

  • Check the datasource of the current sublayout
  • If the datasource is set, use the item from the datasource
  • If not, use the current item

These 2 extension methods ease the process:

namespace SitecoreExtensions
{
  public static class SublayoutExtensions
  {
    public static Item GetDataSourceItem(this Sublayout sublayout)
    {
      string dataSource = sublayout.DataSource;
      if (string.IsNullOrEmpty(dataSource))
        return (Item) null;
      return Context.Database.GetItem(dataSource);
    }
  }

  public static class UserControlExtensions
  {
    public static Item GetDataSourceItem(this UserControl control)
    {
      Sublayout sublayout = control.Parent as Sublayout;
      return sublayout == null ? (Item) null : SublayoutExtensions.GetDataSourceItem(sublayout);
    }

    public static Item GetDataSourceOrContextItem(this UserControl control)
    {
      return UserControlExtensions.GetDataSourceItem(control) ?? Context.Item;
    }
  }
}

The SublayoutExtensions extend the Sitecore Sublayout. The GetDataSourceItem returns the item that the datasource points at, or null if the datasource is not set.

The UserControlExtensions extend the .NET UserControl (that really is the base of a Sitecore Sublayout). The GetDataSourceOrContextItem returns the datasource item, with a fallback to Sitecore.Context.Item.

To use the method, you must add the function to the item property of the sc:FieldRenderer:

<sc:FieldRenderer ID="MyField" runat="server" 
FieldName="NameOfMyField" 
Item="<%# this.GetDataSourceOrContextItem() %>" />

And you must remember to call DataBind().

This pattern is good because you never end up with a NULL item, as sc:FieldRenderer will crash if you feed it with a NULL value in the item property. There is no reason to crash the entire page just because one component is not configured correct.

From code-behind you simply replace Sitecore.Context.Item with this.GetDataSourceOrContextItem:

var text = Sitecore.Context.Item["NameOfMyField"]; // old way
var text = this.GetDataSourceOrContextItem()["NameOfMyField"]; // new way

I would not be surprised if Sitecore in the future would build these functions into sc:FieldRenderer, but untill then we have to do it ourselves.


Merge Sitecore layout details using XmlDeltas

$
0
0

One of the features that Sitecore introduced somewhere in the Sitecore 6.x range is “Layout Deltas“,  the possibility to merge an item’s layout details with the layout details on the item’s __Standard Values template.

It means that you can add sublayouts to the __Standard Values, and still modify the sublayouts on the actual item, without overwriting __Standard Values.

Because instead of copying all the layout details from the __Standard Values, Sitecore will only keep a difference list on the item, not the entire layout details. Sitecore then uses the XmlDeltas field type to merge the two.

If you look at the raw values in Sitecore, you will see that in the “Layout” field of your __Standard values, the contents look like this:

<r xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
  <d id="{FE5D7FDF-89C0-4D99-9AA3-B5FBD009C9F3}" l="{45D794F6-9DBB-4DBE-9826-606456562FB4}">
    <r id="{153F8C66-033E-456A-9C5F-A98D587C7A7E}" ph="" uid="{DE3402CB-9E79-4539-8C54-1DA8FB8D3733}" />
    <r ds="" id="{69EB7A6D-ACEF-4D06-A923-804918AA245E}" par="" ph="NewsletterHeadSection" uid="{CECCDC8D-3AF8-448D-A527-F8471C139FE9}" />
    <r id="{424A40CD-CAF3-45D9-87BA-CBE88C895A64}" ph="" uid="{48640574-E4FB-433B-97B4-0B202D609CB2}" />
    <r ds="" id="{A5975387-6B3A-4A43-BDF5-2A422057E518}" par="" ph="NewsletterBody" uid="{56CA9222-D31E-400A-B697-B620CF431DFF}" />
    <r id="{E429A2F3-DCFC-416E-ACB1-6BB7DE70C846}" ph="" uid="{7DBE3A38-12AD-42B7-8115-F50121D38B84}" />
    <r ds="" id="{175839E3-AEF4-4830-B679-0F51B90B438E}" par="" ph="NewsletterFooter" uid="{DA53887E-B21F-4C8B-A7D7-C3770B79DDF3}" />
  </d>
</r>

But the “Layout” field of the actual item look like this:

<r xmlns:p="p" xmlns:s="s" p:p="1">
  <d id="{FE5D7FDF-89C0-4D99-9AA3-B5FBD009C9F3}">
    <r uid="{7B831873-DE0D-46AA-AA38-9B5A72A3A1B7}" p:before="r[@uid='{7DBE3A38-12AD-42B7-8115-F50121D38B84}']" s:ds="{C57FBE62-5181-49B8-BF6A-E7CEE7BED209}" s:id="{7CCB9656-AE33-4609-92F0-1FDC5632BB8B}" s:ph="newsletterbody" />
  </d>
</r>

Notice the p:before attribue in the last XML? These are the attributes telling Sitecore how to apply the changes to the finished layout details.

You can do the merge yourself using the XmlDeltas field. This example merges the __Standard values onto the item itself:

using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;

/// <summary>
/// Merge layouts
/// </summary>
/// <param name="destinationItem">Item that receives the merged layouts</param>
/// <param name="standardItem">Item to merge from (the Standard Values usually)</param>
private string MergeLayouts(Item destinationItem, Item standardItem)
{
  LayoutField layoutDestinationItem = destinationItem.Fields[Sitecore.FieldIDs.LayoutField];
  LayoutField layoutStandardItem = standardItem.Fields[Sitecore.FieldIDs.LayoutField];
  string deltaLayout = XmlDeltas.ApplyDelta(layoutDestinationItem.Value, layoutStandardItem.Value);
  destinationItem.Editing.BeginEdit();
  try
  {
    destinationItem.Fields[Sitecore.FieldIDs.LayoutField].Value = deltaLayout;
    destinationItem.Editing.EndEdit();
    return deltaLayout;
  }
  catch (Exception ex)
  {
    destinationItem.Editing.CancelEdit();
    throw new Exception("Updating '" + destinationItem.Paths.FullPath + "' failed: " + ex.ToString());
  }
}

It is the XmlDeltas.ApplyDelta that executes the merging.

Please notice that if you merge from __Standard Values, and later modify the item that was merges, Sitecore will identify the merge, and “unmerge” your merged item. It does so using the XmlDeltas.GetDelta() method:

string deltaLayout = XmlDeltas.GetDelta(layoutDestinationItem.Value, layoutStandardItem.Value);

MORE INFORMATION:


Sitecore ECM 2.1: The target device layout does not include ‘Processes Personalization Tokens’ sublayout og ‘Target item’ rendering

$
0
0

When using Sitecore Email Campaign Manager, you might encounter the following error:

The target device layout does not include ‘Process Personalization Tokens’ sublayout and ‘Target Item’ rendering, you may encounter this error for every variant in this message, if you have admin right to fix variant ‘A’, please click here to start the Layout dialog.

ECM Error

ECM Error

The error does not only occur if the rendering and the sublayout is missing, but also if they are placed imporperly:

  • The “Target Item” render needs to be the first rendering.
  • The “Process Personalization Tokens” needs to be the last rendering.
Peoper placement of the 2 renderings

Peoper placement of the 2 renderings

To do this you must place the Process Persinalization Tokens sublayout in a non-editable placeholder. This placeholder must be placed at the bottom of the control stack, as described in the image above.

Furthermore, do not use __Standard Values. ECM cannot find the Target Item and Process Personalization Tokens controls if they are located in the __Standard Values of the message root item.

You must copy all the controls to the actual item and delete the controls from the layout field of the __Standard Values. If you have added controls to the item with the page editor you will need to merge the layouts from __Standard Values to the item. See this arcticle on how to merge layout details.

 


JavaScript ReferenceError: Can’t find variable: __doPostBack

$
0
0

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:


Register local search terms in Sitecore DMS

$
0
0

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:

 

 


Viewing all 264 articles
Browse latest View live




Latest Images