Quantcast
Channel: Ryan Bailey Development
Viewing all 287 articles
Browse latest View live

Sitecore PXM InDesignProcessingService error with DashBoardWebService

$
0
0
When attempting to use PXM (Print Experience Manager) in Sitecore, the following error may appear in the InDesignProcessingService log file.
ERROR JobHandling
Could not connect to http://localhost:8070/DashBoardWebService. TCP error code 10061: No connection could be made because the target machine actively refused it 127.0.0.1:8070.
This is because the Sitecore Print Studio InDesign Processing service is not running (or accessible via network).


Sitecore PXM DashBoardServer error - Message Queuing has not been installed

$
0
0
After following the Print Experience Manager installation guide the following error may appear in the DashBoardServerLog file.
ERROR HasJob:
Message Queuing has not been installed on this computer.
at System.Messaging.Interop.SafeNativeMethods.MQPathNameToFormatName(String pathName, StringBuilder formatName, Int32& count)
at System.Messaging.MessageQueue.ResolveFormatNameFromQueuePath(String queuePath, Boolean throwException)
at System.Messaging.MessageQueue.get_FormatName()
at System.Messaging.MessageQueue.GetMessageEnumerator2()
at System.Messaging.MessageQueue.GetAllMessages()
at Sitecore.PrintStudio.DashBoardServer.DashBoardWebService.HasJob(String serviceType)
This error occurs because Message Queuing (or MSMQ) has not been enabled on the server. Simpy go to the Control Panel and select turn Window features on or off. Then enable:

  • Microsoft Message Queue (MSMQ) Server
  • Microsoft Message Queue (MSMQ) Server Core
On windows server this will need to be done through the server manager, enable features.


Sitecore PXM InDesign is unable to load a project

$
0
0
When inside InDesign and attempting to load a Sitecore Print Experience Manager (PXM) project - via the Load the current item button, the project was not loading for me.


After looking into the printstudio.log older I noticed the following error:
ERROR Access to the path 'C:\inetpub\Jetstream\Website\Projects\sitecore\admin_a1wfr3ls\en\a9880262-14c3-4569-91d0-03aeaed946ed\' is denied.
ERROR BuildPublishXml Object reference not set to an instance of an object.
This was traced back to a setting inside Sitecore.PrintStudio.config - available in App_Config/Include/PXM. The PrintStudio.ProjectsFolder setting needs to be set to the PublishingCache folder rather than the default of projects. The correct setting should be something like: C:\inetpub\wwwroot\Jetstream\PXMPublishing\PublishingCache - which is the folder structure you created manually in the PXM installation.

This error may also trace back to permission/sharing of this folder depending on server/network architecture.

Glass Mapper error with web forms project

$
0
0
I added the Glass Mapper package to a Sitecore project which was web forms based and the following error was appearing when attempting to load the Sitecore admin area.
Could not resolve type name: Glass.Mapper.Sc.Pipelines.Response.GetModelFromView, Glass.Mapper.Sc.Mvc (method:
Sitecore.Configuration.Factory.CreateFromTypeName(XmlNode configNode, String[] parameters, Boolean assert)).
Looking into the details, Glass Mapper added a config file to my solution: Glass.Mapper.Sc.config. This file then referenced the MVC specific DLL: Glass.Mapper.Sc.Mvc in the pipelines. The fix was to remove the following section from the config file:

<mvc.getModel>
<processor patch:before="*[@type='Sitecore.Mvc.Pipelines.Response.GetModel.GetFromItem, Sitecore.Mvc']" type="Glass.Mapper.Sc.Pipelines.Response.GetModel, Glass.Mapper.Sc.Mvc"/>
<processor patch:before="*[@type='Sitecore.Mvc.Pipelines.Response.GetModel.GetFromItem, Sitecore.Mvc']" type="Glass.Mapper.Sc.Pipelines.Response.GetModelFromView, Glass.Mapper.Sc.Mvc"/>
</mvc.getModel>

It is a known issue.

Sitecore caching data when there is no context

$
0
0
When working with areas in Sitecore where the context is not resolving (such as custom index field or pipelines) the standard HttpContext.Current.Cache would not resolve because HttpContext.Current is null. In this case HttpRuntime.Cache will work instead.

I tend to use caching in custom search index fields where configuration is coming from a Sitecore item. This saves a lot calls to Sitecore and processing of the item to get the required data.

Sitecore handling content URLs that change between environments

$
0
0
In some Sitecore implementations there can be various integration points to third party (or other internal web sites) which will have URLs (in content) which change based on environment (development, test or production).

An example of this would be the main content web site (in Sitecore) which has a number of links to an online application web site. In production the URL might be app.mydomain.com, and in test app-test.mydomain.com. The problem here is that as content is synced from production to test, the production links will be present in the test environment. This is not ideal as it can lead to test data in production which testers/content users may not notice - it also doesn't allow for fair end to end testing of the integrated systems.

The way I solved this issue was to create test content management as a publishing target on the production content management server. This means that authorised users are able to publish/refresh content on test via production. This of course could be replaced with other syncing methods such as Sitecore packages, TDS or Razl.

I then created a command (which was added to the ribbon) which would recursively loop through content in the tree and do a replacement on links (based on configurable from and to values).

namespace MyProject.Commands
{
public class LinkChanger : Command
{
private static readonly string GeneralLinkExternal = @"linktype=""external""";
private static readonly ID ConfigItemId = ID.Parse("{6B21822A-3383-4CC8-A32F-2BFEEE28EB79}"); // ID of content item
private static readonly ID LinkChangeTemplate = ID.Parse("{5E4F0DF4-6FA2-4C36-A965-1895FE527B8D}"); // ID of template
private Config config = new Config();

public override void Execute(CommandContext context)
{
GetConfig();

if (config == new Config() || !config.Enabled || string.IsNullOrEmpty(config.ContentPath))
{
Sitecore.Context.ClientPage.ClientResponse.Alert("Error changing links!");
return; // Unable to get config or not enabled or no content path
}

var contentItem = Factory.GetDatabase("master").GetItem(config.ContentPath);

if (contentItem != null)
{
GetItem(contentItem);
}
else
{
Sitecore.Context.ClientPage.ClientResponse.Alert("Error changing links!");
}

Sitecore.Context.ClientPage.ClientResponse.Alert("Link change process complete!");
}

private Item GetItem(Item mainItem)
{
ProcessChanges(mainItem);

if (mainItem.HasChildren)
{
foreach (var myInnerItem in mainItem.Children.ToList())
{
GetItem(myInnerItem);
}
}

return null;
}

private void ProcessChanges(Item itemBeingSaved)
{
var richTextFields = itemBeingSaved.Fields.Where(x => !x.Name.StartsWith("_") && x.Type == "Rich Text");
var linkFields = itemBeingSaved.Fields.Where(x => !x.Name.StartsWith("_") && x.Type == "General Link");

try
{
// Rich Text
foreach (var field in richTextFields ?? new List<Field> { null })
{
foreach (var replacement in config.Replacements)
{
if (field.Value.IndexOf(replacement.From, 0, StringComparison.CurrentCultureIgnoreCase) > -1)
{
using (new Sitecore.Data.Items.EditContext(itemBeingSaved))
{
field.Value = CaseReplace(field.Value, replacement.From, replacement.To, StringComparison.CurrentCultureIgnoreCase);
}
}
}
}

// General Link
foreach (var field in linkFields ?? new List<Field> { null })
{
foreach (var replacement in config.Replacements)
{
if (field.Value.Contains(GeneralLinkExternal) && field.Value.IndexOf(replacement.From, 0, StringComparison.CurrentCultureIgnoreCase) > -1)
{
using (new Sitecore.Data.Items.EditContext(itemBeingSaved))
{
field.Value = CaseReplace(field.Value, replacement.From, replacement.To, StringComparison.CurrentCultureIgnoreCase);
}
}
}
}
}
catch (Exception ex)
{
Log.Error(string.Format("Error in LinkChanger: {0}", ex.ToString()), "");
}
}

private void GetConfig()
{
// Get and add to cache
var configItem = Factory.GetDatabase("master").GetItem(ConfigItemId);

if (configItem == null)
{
Log.Error("Error in Link Changer: Unable to get config item.", "");
return;
}

config = new Config
{
Replacements = new List<LinkReplacement>(),
Enabled = configItem.Fields["Enabled"].Value == "1" ? true : false,
ContentPath = configItem.Fields["Content Path"].Value
};

// Get link changes
var changes = configItem.Children.Where(x => x.TemplateID == LinkChangeTemplate);

foreach(var child in changes ?? new List<Item> { null })
{
config.Replacements.Add(new LinkReplacement
{
From = child.Fields["From"].Value,
To = child.Fields["To"].Value
});
}
}

private static string CaseReplace(string source, string oldValue, string newValue, StringComparison comparisonType)
{
if (source.Length == 0 || oldValue.Length == 0)
return source;

var result = new System.Text.StringBuilder();
int startingPos = 0;
int nextMatch;
while ((nextMatch = source.IndexOf(oldValue, startingPos, comparisonType)) > -1)
{
result.Append(source, startingPos, nextMatch - startingPos);
result.Append(newValue);
startingPos = nextMatch + oldValue.Length;
}
result.Append(source, startingPos, source.Length - startingPos);

return result.ToString();
}

private struct LinkReplacement
{
public string From { get; set; }
public string To { get; set; }
}

private class Config
{
public List<LinkReplacement> Replacements { get; set; }
public bool Enabled { get; set; }
public string ContentPath { get; set; }
}
}
}

You will also need to include the following config patch file (updating namespace and assembly of course):
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<commands>
<command name="tools:changelinks" type="MyProject.Commands.LinkChanger,MyProject" />
</commands>
</sitecore>
</configuration>

Some general notes are:
  • This example will parse over all (non system) rich text and general link fields (external links)
  • It will handle both the href="" and text content of a given link
  • I don't include http:// or https:// in the replacements in order to handle both
  • The function for replacements will handle matches ignoring the case
  • Be careful when using this and consider logging replacements whilst testing. With full domain names, it should not replace non-URL content.
  • The command needs to be hooked up to the ribbon in the core database.
The two configuration templates are - the IDs will need to be changed in code to match your IDs:


Which then are configured as follows:


User/anonymous generated content in Sitecore

$
0
0
User generated content from site visitors might be as simple as a comment on a news article, or more complex such as the submission of structured content (a community venue listing for example). The only question then becomes how to handle the user/anonymous generated content in Sitecore.


There are many different options available for getting content entered by users into Sitecore and a lot of it comes down to the server/network infrastructure and even the amount/load of data being entered. Of course on single server environments you could simply add an item directly to the master database. However with scaled environments where the master connection is not available on content delivery servers there are about adding content.

Web forms for marketers

The web forms for marketers module is a simple way to allow site visitors to enter data into a form and then have that create the content items in Sitecore. This can further be expanded to use workflow which ensures the content is approved before being published along with the use of event handlers for any other business logic. 

Please note that on scaled environments, WFFM uses a custom service to communicate from content delivery to the content management server. Some secure server environments may require port 80 be opened between the servers (which can be locked down to specific IP addresses).

Sitecore item web API

The Sitecore item web API is an HTTP rest-style web service (that outputs JSON) which allows you to manipulate Sitecore content. This would allow content to be created on content delivery server(s) servers by utilising the item web API on the content management server. It would be as simple as creating a new item in a given location of a given template, then approval workflows could be used to get the item back to the web database.

Item handlers can also be used to add more business logic onto this user generated content.

It's important to note that the sc_database parameter should be set to master to ensure the content goes into the correct location.

This is arguably a more legacy option (compared to Sitecore.Services.Client), however it will do the basic manipulation of items needed for user generated content creation in Sitecore.

Sitecore.Services.Client

The Sitecore.Services.Client is available on both the server side and client side of Sitecore. It is configurable/extendable and allows for client - server item manipulation in a clean and consistent way.

Effectively you will be able to use this service (either via client-side JaavScript or Restful API direct) to manipulate (CRUD) content via connecting to the CM instance. It has the features offered via the item web API, however allows for a bit more power when it comes to custom business objects and should save item over writing your own API.

Custom SQL database

In some cases creating a custom database in SQL Server (perhaps mapped in code using entity framework) is the cleanest solution. This option is generally better when using a Sitecore item does not make sense, especially in cases where there are large amounts of data/business logic. The benefits include: less load on Sitecore CMS, ability to have custom database/table structure, ability to use stored procedures and functions (more decoupled business logic) and of course the ability to index the relevant fields at the database level.

An example of where I have used this concept before is with registration of public users, the custom SQL Server database was used to store rows of user registration/password reset requests. It made more sense in SQL because the code would only query a single line item at a time, and once we deal with large amounts of users it could affect Sitecore performance.

Additional Sitecore database

Much like Sitecore already has core, master and web databases it's actually possible for you to add your own custom one. This blog post, goes over the concept of adding an additional Sitecore database called products. The benefit here is that content delivery servers will be able to access this database and it one of the cleanest solutions in terms of custom code needed. The real draw card however is in terms of search indexing of the content in this database, you can target it and allow for fast data access via Lucene. 

MongoDB

MongoDB is an option for storing user generated content with Sitecore, and all servers in a scaled environment will have access to it. This would be ideal for larger amounts of data, particularly that which is written constantly. It means however that code will need to be written to handle the CRUD of data along with ensuring backups/MongoDB replication is configured correctly.

Conclusion

There are a number of different options for creating user (anonymous) generated content in Sitecore and the decision will often come down to network/server architecture and the level of requirements. A high level overview of a number of options has been discussed above, and more research may be required to implement a solution in your Sitecore implementation. 

I didn't discuss it in this article, but custom services are also an option, and they can wrap around any of the options above,

Sitecore search ashx is appearing in search result URLs

$
0
0
I noticed that in some cases a Lucene search that I had built in Sitecore was generating URLs that contained a .ashx extension for PDF files in some cases and .pdf in others.

There is a setting in the Sitecore.config file which controls which extension will be shown for media library URLs. The setting is Media.RequestExtension and by default will be set to ashx.
<setting name="Media.RequestExtension" value="ashx"/>
Using a patch file (or editing directly), simply set this to an empty value to ensure media library content is served up using the correct extension.

However in my case, this did not resolve the issue... Some items were still coming through with .ashx and there was no content differences that could cause this issue. Ultimately setting IncludeExtension in the MediaUrlOptions did solve it:
itemUrl = MediaManager.GetMediaUrl(item, new MediaUrlOptions { AlwaysIncludeServerUrl = true, IncludeExtension = true});

Sitecore search inconsistencies in the result URLs

$
0
0
I noticed on a Lucene search implementation in Sitecore that there were some inconsistencies around the URLs being generated. In some cases spaces where replaced by dashes and in other cases they were not. This was a tricky issue because some of the content names (and therefore paths) included dashes.

of course in the Sitecore.config file there was an encode name replacement to replace all dashes in URLs with spaces.
<replace mode="on" find="" replaceWith="-"/>
However the results weren't showing until the EncodeNames element was set in the UrlOptions when generating the link with the link manager.
itemUrl = Sitecore.Links.LinkManager.GetItemUrl(item, new UrlOptions { AlwaysIncludeServerUrl = true, LanguageEmbedding = LanguageEmbedding.Never, EncodeNames = true});

Sitecore session errors with MongoDB

$
0
0
An interesting issue was reported on a Sitecore environment where users were able to browse to several pages, but eventually the website would stop responding to requests (and it appeared as if the server was too busy to respond). However if cookies were cleared on the user's machine, they could browse for a bit before not getting requests back again.


In this instance the shared/private sessions were being stored in MongoDB. The following error was appearing in the logs:
ERROR Application error.
Exception: System.Web.HttpException
Message: Exception of type 'System.Web.HttpException' was thrown.
Source: System.Web
   at System.Web.HttpAsyncResult.End()
   at System.Web.HttpApplication.AsyncEventExecutionStep.OnAsyncEventCompletion(IAsyncResult ar)
Nested Exception
Exception: MongoDB.Driver.MongoDuplicateKeyException
Message: WriteConcern detected an error ''. (Response was { "ok" : 1, "code" : 11000, "err" : "E11000 duplicate key error collection: session.sessions index: _id_ dup key: { : { s: \"j0lvazyxndl5ihfldr0zzce2\", a: \"private\" } }", "n" : NumberLong(0), "updatedExisting" : false }).
Source: MongoDB.Driver
In our instance we traced the error back to a corrupted MongoDB instance (which is a post for another day), and we moved sessions over to SQL Server to resolve the issue. However if you are seeing this error these are some configurations to check.
  1. Inside the web.config the <sessionState> element is where private sessions are defined.
  2. Inside Sitecore.Analytics.Tracking.config file is where shared sessions are defined
  3. Inside ConnectionString.config is where the connection string for any session databases (MongoDB or SQL Server) should be.
If any of these session configurations are using InProc in an environment with multiple servers (content delivery scaled environments for example) you will have a problem if the load balancer is not using sticky sessions (ensuring the visitor always visits the same server). In these sorts of environments its recommended to use a shared session source (MongoDB or SQL Server).

Sitecore errors on a scaled environment

$
0
0
On a scaled Sitecore 8.1 environment with two content delivery servers and one content management server, I followed the Config_Enable-Disable_Sitecore_8.1_upd3.xlsx spreadsheet to disable/enable the required configuration files. The following errors started appearing in the content delivery logs.
Could not find configuration node: contentSearch/indexConfigurations/defaultFxmLuceneIndexConfiguration
This error required that Sitecore.FXM.Lucene.DomainsSearch.DefaultIndexConfiguration.config (found in App_Config\Include\FXM) be enabled, even though it was marked to be disabled on CD servers.
Invalid cast from 'System.String' to 'Sitecore.Analytics.Aggregation.Data.DataAccess.ICollectionDataProvider2'.
This error may cause images not to load along with other features such as search. The fix for this is to keep Sitecore.Analytics.Reporting.config (found in App_Config\Include) enabled, even though it was marked to be disabled on CD servers.


These issues were found using the Sitecore Habitat solution.

Sitecore scheduled tasks not running as expected

$
0
0
On a Sitecore implementation some key scheduled tasks (set in the content editor rather than agents), would run around the expected time in most cases. However every so often there would be a major delay in their running (and as emails were sent it was noticeable what time it was kicked off).

After investigating the logs, I noticed that in the periods where the custom scheduled task was not being kicked off, a system task was running. The system task Rebuild Suggested Tests Index which relates to content testing (crawls the database and gives candidates for content testing - a/b testing). It was set to run once daily and due to content volume would take anywhere for 5 to 7 hours to run, of course not begin asynchronous it would hold up all other tasks.

The solution here was to set it to run weekly or to set it to be asynchronous.

Sitecore commerce required license missing error

$
0
0
After upgrading to Sitecore 8.1 the following error message may start appearing in the content editor:
Required license is missing.
You require one of the following licenses to use the Sitecore Commerce Connect module. 'Sitecore.Commerce.Connect' or 'Sitecore.Commerce.ConnectPartner'

The solution to suppressing this error message involves removing the Sitecore.Commerce.Pipelines.ContentEditorLicenseWarning processcor pipeline. The following patch file will delete the element, however it should be run last so include it in a z folder in App_Config.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<getContentEditorWarnings>
<processor type="Sitecore.Commerce.Pipelines.ContentEditorLicenseWarning, Sitecore.Commerce">
<patch:delete />
</processor>
</getContentEditorWarnings>
</pipelines>
</sitecore>
</configuration>

Sitecore inconsistencies with the reminder feature

$
0
0
In the Sitecore content editor, there is a handy feature which allows content authors to set a reminder on a content item. This feature will then email the user with the reminder message they set at the specified date/time.

The first problem which I encountered was that the same reminder was being sent multiple times (from the master database and the web database). If an item had an older reminder set and you then published to the web database, the reminder would be re-added to the queue and sent again. The fix for these problems has been addressed by Christian Kay Linkhusen and involves overriding the task database to stop reminders from being sent off the web database.

The second problem I encountered was that some reminders were begin sent as expected where others would not. After looking at the tasks table in the core database, I noticed that when I set a reminder on the Content Management server, it would then be added into the table without an instance name. The task database agent is a timed job which is run on both content management and content delivery servers. When this is run (GetPendingTasks - to get pending tasks to process), the first method it calls is AssignTasks, which looks for any tasks without an instance name and assigns them to the current instance. This meant that one of the content delivery servers could then take ownership of a newly created reminder, and the content management server would never see it. Because this item is in the master database, the content delivery server could never access it so the reminder would never send.

The fix for this problem was to take a copy of the GetPendingTasks() method and remove the AssignTasks() method from running. The code to get the pending tasks actually gets all tasks due to run assigned to the current instance, or where the instance is null - so the tasks without the instance names would still send. The fix above actually overrides the method which calls GetPendingTasks() so these two fixes work well together.

using Sitecore;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Sql;
using Sitecore.Data.SqlServer;
using Sitecore.Diagnostics;
using Sitecore.Diagnostics.PerformanceCounters;
using Sitecore.Reflection;
using Sitecore.Tasks;
using System;
using System.Collections;
using System.Data.SqlTypes;
using Sitecore.Common;

namespace MyProject.Sitecore.Override
{
public class TaskDatabaseAgent : Sitecore.Tasks.TaskDatabaseAgent
{
public new void Run()
{
Task[] pendingTasks = GetPendingTasks(); // Call our copy of the method here
this.LogInfo(string.Concat("Processing tasks (count: ", (int)pendingTasks.Length, ")"));
Task[] taskArray = pendingTasks;
for (int i = 0; i < (int)taskArray.Length; i++)
{
Task logActivity = taskArray[i];
try
{
logActivity.LogActivity = this.LogActivity;

//Test if the Task is the EmailReminderTask and the current database is not master database
if (logActivity.GetType() == typeof(EmailReminderTask) && !logActivity.DatabaseName.ToLower().Equals("master"))
{
//If its not the master database
//Only mark the Task as done, don't execute the reminder
Globals.TaskDatabase.MarkDone(logActivity);
}
else
{
logActivity.Execute();
}

JobsCount.TasksTasksExecuted.Increment((long)1);
}
catch (Exception exception)
{
Log.Error("Exception in task", exception, logActivity.GetType());
}
}
}
public Task[] GetPendingTasks()
{
//this.AssignTasks(); // Removed as CD servers were assigning master tasks to themselves
ArrayList arrayLists = new ArrayList();
int num = 8;
string str = "SELECT [ID], [NextRun], [taskType], [Parameters], [Recurrence], [ItemID], [Database], [InstanceName] \r\n FROM [Tasks] \r\n WHERE [NextRun] <= @now AND [Pending] = 1 AND [Disabled] = 0 AND ([InstanceName] IS NULL OR [InstanceName] = @instanceName) \r\n ORDER BY [NextRun]";
object[] utcNow = new object[] { "now", DateTime.UtcNow, "instanceName", Settings.InstanceName };
object[] array = SqlUtil.GetArray(str, utcNow, Settings.GetConnectionString("core"));
for (int i = 0; i < (int)array.Length - (num - 1); i = i + num)
{
ID d = ID.Parse(array[i]);
DateTime dateTime = ((DateTime)array[i + 1]).SpecifyKind(DateTimeKind.Utc);
string str1 = array[i + 2] as string;
string str2 = array[i + 3] as string;
string str3 = array[i + 4] as string;
ID d1 = ID.Parse(array[i + 5]);
string str4 = array[i + 6] as string;
string str5 = array[i + 7] as string;
Task task = this.CreateTask(str1, dateTime);
if (task != null)
{
task.ID = d;
task.Parameters = str2;
task.RecurrencePattern = str3;
task.ItemID = d1;
task.DatabaseName = str4;
task.InstanceName = str5;
arrayLists.Add(task);
}
}
return arrayLists.ToArray(typeof(Task)) as Task[];
}

protected Task CreateTask(string taskType, DateTime taskDate)
{
if (taskDate == SqlDateTime.MinValue.Value)
{
taskDate = DateTime.MinValue;
}
taskDate = DateUtil.ToUniversalTime(taskDate);
object[] objArray = new object[] { taskDate };
return ReflectionUtil.CreateObject(taskType, objArray) as Task;
}

/// <summary>
/// Logs the info.
/// </summary>
/// <param name="message">The message.</param>
private void LogInfo(string message)
{
if (this.LogActivity)
{
Log.Info(message, this);
}
}
}
}
You'll also need to replace the task database agent to use the one above:
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/" xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<sitecore>
<scheduling>
<agent type="Sitecore.Tasks.TaskDatabaseAgent" method="Run" interval="00:10:00">
<patch:attribute name="type" xdt:Transform="Replace" xdt:Locator="Match(type)">Myproject.Override.TaskDatabaseAgent</patch:attribute>
</agent>
</scheduling>
</sitecore>
</configuration>
As with any overrides or replacement of Sitecore code, you will need to check if the code is updated upon installations of CMS updates and update your code accordingly. This code was taken from version8 update 5.

Bundling CSS and JavaScript with Sitecore

$
0
0
In terms of optimizing web pages for a better user experience and less load on the web server, bundling of css and javascript files can provide one of the quickest wins. The benefits of bundling the css and javascript are:
  1. Less HTTP requests on the web server - for example a single javascript file request instead of 10+, which is a real help when a server is under load.
  2. Less file size as the bundle is minified.
  3. "Bundling and minification in ASP.NET 4.5 is performed at runtime, so that the process can identify the user agent (for example IE, Mozilla, etc.), and thus, improve the compression by targeting the user browser (for instance, removing stuff that is Mozilla specific when the request comes from IE)."

Setting up bundling in your Sitecore solution (web forms)

  1. Install the Microsoft.AspNet.Web.Optimization nuget package. In this example I installed version 1.1.3
  2. Because the the nuget package above has some dependencies that Sitecore uses, set the WebGrease and Newtonsoft.json references to Copy Local false. As we don't want to overwrite the newer versions installed by Sitecore.
  3. The following dependent assembly changes need to be inside the web.config file
  4. <dependentAssembly>
    <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" />
    <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
    </dependentAssembly>
    <dependentAssembly>
    <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
    <bindingRedirect oldVersion="0.0.0.0-1.6.5135.21930" newVersion="1.6.5135.21930" />
    </dependentAssembly>
    The Newtonsoft one will likely already exist, but the WebGrease one won't. Check the DLL versions installed by your version of Sitecore. I am on 8.1 so my Sitecore versions were newer than those used by System.Web.Optimization
  5. In my case, I will be bundling one bundle of CSS and one bundle of JS (referenced at the body closing tag to stop JavaScript page rendering blocking). My config/definitions of the bundle is as follows.
  6. public class BundleConfig
    {
    public void Process(PipelineArgs args)
    {
    BundleConfig.RegisterBundles(BundleTable.Bundles);
    }

    public static void RegisterBundles(BundleCollection bundles)
    {
    bundles.Add(new ScriptBundle("~/bundles/Global").Include(
    "~/assets/plugins/bootstrap/js/bootstrap.min.js",
    "~/assets/plugins/bootstrap-hover-dropdown.min.js",
    "~/assets/plugins/back-to-top.js",
    "~/assets/plugins/jquery-placeholder/jquery.placeholder.js",
    "~/assets/js/main.js"));

    bundles.Add(new StyleBundle("~/Content/Global").Include(
    "~/assets/plugins/bootstrap/css/bootstrap.min.css",
    "~/assets/plugins/font-awesome/css/font-awesome.css",
    "~/assets/css/styles.css"));
    }
    }
    Normally with bundles you define them inside the global.asax file, however as I don't want to edit the out-of-box Sitecore file, I am hooking into the initialize pipeline (which runs on the start off the application).
  7. The patch file to register the pipeline is:
  8. <?xml version="1.0" encoding="utf-8" ?>
    <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
    <pipelines>
    <initialize>
    <processor type="MyProject.Helpers.BundleConfig, MyProject" />
    </initialize>
    </pipelines>
    </sitecore>
    </configuration>
  9. Now on the layout or master page you reference the assembly:
  10. <%@ import namespace="System.Web.Optimization" %>
  11. Now a runat server needs to be added to the head element
  12. <head runat="server">
  13. The CSS is registered in the head like so:
  14. <asp:PlaceHolder runat="server">
    <%: Styles.Render("~/Content/Global") %>
    </asp:PlaceHolder>
  15. The JS is registered in the page like so:
  16. <%: Scripts.Render("~/bundles/Global") %>
After a deploy your css and js will now be nicely bundled and minified and you should get a good scropt from YSlow!



Sitecore tip on the web database prefetch cache

$
0
0
Sitecore has a number of different caching areas which all work together to (hopefully) provide a seamless CMS experience with reasonable load times for content editors and end users alike. Sitecore explains the prefetch caches rather well as:
Prefetch caches are populated at application initialization, based on the information provided in the prefecth configuration files. This results in a smoother user experience after an application restart.However, excessive use of the prefetch cache can increase the time required for the application to restart, giving a negative user experience.
To understand how the prefetch caches are utilized once the application is up and running, a view into how Sitecore caching works is required:
When a database item cache does not contain an entry for an item, Sitecore retrieves the corresponding entry from the database data cache, converts it to a different type, and stores that converted data as an entry in the database item cache. When the database data cache does not contain an entry for an item, Sitecore retrieves the corresponding entry from the database prefetch cache, converts it to a different type, and stores that converted data as an entry in the database data cache. When an entry does not exist for an item in a database prefetch cache, Sitecore retrieves that item from the data provider for that database, converts it to a different type, and stores that converted data as an entry in the database prefetch cache.
What this means is that the prefetch caches are not only populated at initialization, but during the life of the application. This understanding is required when thinking about setting the size of the prefetch caches.
In the Website\App_Config\Prefetch folder of your Sitecore website there are three files which define the prefetch cache for the web, core and master databases. Here the maximum size for the caches is set as well as some other options:
  • <template> — This element tells Sitecore to cache all items based on a given template at application initialization.
  • <children> — This element tells Sitecore to cache all of the children of the specified item. 
By default, the web database prefetch will be configured to cache the default home item and all of it's children. However in many instances that home item is deleted or left unused. Therefore the tip is to investigate which items or templates you want cached for the web database of your Sitecore instance. In many cases, having the main home item and it's children will lead to performance benefits.

Remember that it also leads to a slower application startup which is not as much as a worry for production as development.

Sitecore removing a rendering or sublayout programatically

$
0
0
Sometimes a requirement comes up to remove renderings or sublayouts from Sitecore items programatically. This could be used in the case of a pipeline to detect and remove renderings/sublayouts with broken data sources or to bulk remove/change renderings/sublayouts which have been removed/replaced.

The code below will get a Sitecore item, then all renderings on that item which fall under the default device. It then queries to find a specific rendering, and edits the item with that rendering removed.
var database = Sitecore.Data.Database.GetDatabase("master");
var item = database.Items.GetItem("/sitecore/content/Home/Test/Test-Page/");

// If item has a layout
if (item.Fields[Sitecore.FieldIDs.LayoutField] != null && !String.IsNullOrEmpty(item.Fields[Sitecore.FieldIDs.LayoutField].Value))
{
LayoutField layoutField = new LayoutField(item.Fields[Sitecore.FieldIDs.FinalLayoutField]);
LayoutDefinition layout = LayoutDefinition.Parse(layoutField.Value);
DeviceDefinition device = device = layout.GetDevice("{FE5D7FDF-89C0-4D99-9AA3-B5FBD009C9F3}"); // Get the default device

// Get renderings for the device
var d = layout.GetDevice(device.ID.ToString());
var ren = d.Renderings;

// Query the renderings for a specific one - use any logic here
var rendering = from RenderingDefinition renDef in ren
where renDef.ItemID == "{CD8C466E-22D3-4791-9871-C230AA57F71B}"
select renDef;

if (rendering.FirstOrDefault() != null)
{
item.Editing.BeginEdit();
d.Renderings.Remove(rendering);
layoutField.Value = layout.ToXml();
item.Editing.EndEdit();
}
}
The logic could be expanded to fit the needs of your given requirements.

Sitecore remove renderings or sublayouts with missing datasources

$
0
0
As a Sitecore implementation starts to get larger and older, items can start to have renderings or sublayouts which refer to datasources which no longer exist. The main cause for this is usually a content editor deleting the datasource item and leaving the links intact.

The following code can be setup as a scheduled task in Sitecore or added to a custom admin page to be run as needed. It will recursively move down the content tree and find items which have a layout, it then checks the renderings on those items with datasources and ensures the datasource exists. If a rendering with a broken datasource is found, it is removed from the item.
using Sitecore.Layouts;
using Sitecore.Data.Fields;
using Sitecore.Diagnostics;

public partial class DatasourceFix : System.Web.UI.Page
{
private static Database database = Sitecore.Configuration.Factory.GetDatabase("master");
private static string StartPath = "/sitecore/content/"; // Start path to enumerate
private static string DefaultDevice = "{FE5D7FDF-89C0-4D99-9AA3-B5FBD009C9F3}"; // Default device ID

protected void btnRun_Click(object sender, EventArgs e)
{
var startItem = database.Items.GetItem(StartPath);
CheckItem(startItem);
}

private Item CheckItem(Item mainItem)
{
RenderingRemover(mainItem);

if (mainItem.HasChildren)
{
foreach (var myInnerItem in mainItem.Children.ToList())
{
CheckItem(myInnerItem);
}
}

return null;
}

private void RenderingRemover(Item item)
{
if (item.Fields[Sitecore.FieldIDs.LayoutField] != null && !String.IsNullOrEmpty(item.Fields[Sitecore.FieldIDs.FinalLayoutField].Value))
{
// If item has a layout
LayoutField layoutField = new LayoutField(item.Fields[Sitecore.FieldIDs.LayoutField]);
LayoutDefinition layout = LayoutDefinition.Parse(layoutField.Value);
DeviceDefinition device = device = layout.GetDevice(DefaultDevice); // Get the default device

// Get renderings for the device
var d = layout.GetDevice(device.ID.ToString());
var ren = d.Renderings;

// Query the renderings for those with a datasource
var withDatasource = from RenderingDefinition renDef in ren
where !string.IsNullOrEmpty(renDef.Datasource)
select renDef;

bool itemEdited = false; // item edited flag

// Find missing datasources
foreach (RenderingDefinition rendering in withDatasource.ToList() ?? Enumerable.Empty())
{
var datasource = database.GetItem(new Sitecore.Data.ID(rendering.Datasource)); // Get the datasource

if (datasource == null)
{
// if datasource is broken, flag an item edit and remove the rendering
itemEdited = true;
d.Renderings.Remove(rendering);
Log.Info(string.Format("Removing rendering {0} from item {1} due to datasource not being found.", rendering.ItemID.ToString(), item.Paths.Path), "");
}
}

// edit the item if rendering(s) were removed
if(itemEdited)
{
item.Editing.BeginEdit();
layoutField.Value = layout.ToXml();
item.Editing.EndEdit();
}
}
}
}
It can be quite an expensive operation as it will go through an entire content tree, so be careful when/how it is run.

Installing Sitecore performance counters

$
0
0
The performance monitor tool on windows is a great friend when it comes to troubleshooting a slow performing Sitecore website. In the past I have used performance monitor to troubleshoot IIS, however Sitecore expands upon the concept and provides a number of custom counters to monitor.

The counters can be installed via the following executable from Sitecore: Sitecore Counters. This package appears to be for Sitecore 8+. However if you made the mistake of installing the counters for Sitecore 6, the installation likely fails and corrupts performance monitor on your windows instance. Running the command "LODCTR /R" will fix up performance monitor by rebuilding it's settings.

Once the counters have been installed, the various counters can now be added/monitored in the performance monitor tool.


The important configuration settings in Sitecore related to this are available in Sitecore.config:

  • Counters.Enabled: Indicates if performance counters are enabled.
  • Counters.InstanceName: Instance name for performance counters.
  • Counters.ResetOnStartup: Indicates if performance counters will be reset on Sitecore startup.
Once installing and running these counters will enable you to better troubleshoot your Sitecore instances.

Improving the speed of Sitecore's experience editor

$
0
0
One thing I noticed when speaking with Sitecore content users who have upgraded from earlier versions (such as 6.5) to version 8 and beyond is that the experience editor (or page editor) runs slow in the newer versions.

After opening up developer tools in Google Chrome and running analysis of the network tab, I was able to identify the three components of the experience editor which tool the most time to run.


Of these three items, the following two could be fixed to reduce loading times. In Sitecore implementations with a large amount of items and therefore large master database, these two components can be very slow to load.

  1. Optimization.SuggestedTests.Count: Gives suggestions on A/B multivariate tests which can be run on the page.
  2. ExperienceEditor.MyItems.Count: Shows the number of items currently locked by you, this functionality does not optimally return the count so may take some time to load.

The following Sitecore knowledge base articles provide details on these two items:
  1. How to disable Content Testing functionality
  2. Performance issue with the My items functionality
After applying both fixes, you will notice that Optimization.SuggestedTests.Count will no longer run at all and the performance of ExperienceEditor.MyItems.Count should be improved (this one will be noticed more by content editors who keep items locked).
Viewing all 287 articles
Browse latest View live