Skip to content

Web API Sitecore ID Formatter

When using Web API, Sitecore ID’s serialise with {} around the IDs. This can cause issues when working with JSON as this denotes objects encapsulation.

JSON Sitecore ID's

JSON Sitecore ID’s

Below is a custom formatter to format Sitecore item IDs without the {}. This example has Web API registered to run alongside Web Item API.

See Patrick Delancy’s blog post about running Web API alongside Web Item API http://patrickdelancy.com/2013/08/sitecore-webapi-living-harmony/

The corresponding JSON

The corresponding JSON

  public class RegisterWebApiRoute
    {
        public void Process(PipelineArgs args)
        {
            var config = GlobalConfiguration.Configuration;
            config.Routes.MapHttpRoute("DefaultApiRoute",
                                     "mikerobbinsapi/{controller}/{id}",
                                     new { id = RouteParameter.Optional });

            var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
            config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
           config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new IDConverter());
        }
    }
public class IDConverter : JsonConverter
   {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(ID);
        }
        public override bool CanRead
        {
            get
            {
                return false;
            }
        }
        public override bool CanWrite
        {
            get
            {
                return true;
            }
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var id = (ID)value;
            writer.WriteValue(id.ToString().Replace("{", "").Replace("}", "").ToUpperInvariant());
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            // Default reader used
            throw new NotImplementedException();
        }
    }

Sitecore SPEAK Visual Studio Project and Item Templates

To help developers new to Sitecore SPEAK development and speed up development for existing developers, I’ve created some Visual Studio templates. These help to setup new VS projects correctly for SPEAK development and add SPEAK items quickly. Ill be adding more SPEAK Visual Studio templates in the future.

The first is a project template that creates a new project ready to create an SPEAK application.

Sitecore SPEAK Application

Sitecore SPEAK Application

The second allows you to add new PageCode files directly from the add item window.

SPEAK PageCode

SPEAK PageCode

GitHub Releases – Visual Studio application and item templates

https://github.com/sobek1985/SPEAKTemplatesForVisualStudio/releases

GitHub Source Code

https://github.com/sobek1985/SPEAKTemplatesForVisualStudio

Install Solr as Windows Service for Sitecore Content Search

  1. Download and setup Solr for Sitecore
    (Great tutorial from Dan Solovay on this http://www.dansolovay.com/2013/05/setting-up-solr-with-sitecore-7.html)
  2. Download Non Sucking Service Manager from http://nssm.cc/download.
  3. Copy the nssm.exe into your solr directory.

    NSSM

    NSSM

  4. Create a bat file within the Solr/example folder. Example below, where CD is your example folder within your Solr directory.
    @echo off
    cd "C:\solr\example\"
    java -Xms64M -Xmx256M -jar start.jar
    
  5. Run Command Prompt as Admin. CD to the directory containing of your NSSM.exe
  6. Run the following script to install Solr as a service.”nssm install YourDesiredServiceName” “c:/Solr/LocationOfBatScript””E.g. nssm install MikeRobbinsSolr “C:\solr\example\Solr.bat”

    Solr Service Installed

    Solr Service Installed

  7. Start the service from the services snap in

    Solr Services

    Solr Services

 

 

Sitecore SPEAK Uploader Uploaded Item

Quite often when using the Sitecore SPEAK Uploader to upload items to the Media Library, you need to be able to access the item after upload within the JavaScript page code to work with the item.

Below is an example of how to get access to the Media Item model after it has been uploaded into the Media Library.

This method is only supported in Sitecore 7.1 rev. 140324 (7.1 Update-2). I raised this as a bug with Sitecore as Item Id’s weren’t returned, and its been fixed in this release.

  initialize: function () {

   //Subscribe to the "upload-fileUploaded" event of the Uploader. 
   //This is called after each item is uploaded successfully to the media library. 
   //Specify a function to handle the uploaded event.

   this.on("upload-fileUploaded", fileUploaded, this);
  },
var fileUploaded = function (model) {
    // Access the model for the uploaded Media Item.
    var itemId =   model.itemId
};

Write to Web Forms Programmatically

Recently I had a requirement to capture a number of parameters from a users session and provide Sitecore administrators a way of reporting on this data and exporting the data to CSV for analysis.

The reporting within WFFM was exactly what I was looking to achieve. I wondered if I could tap into this programmatically and use WFFM as my analysis and exporting tool.

Form Reports

Form Reports

The idea is to create a new Form within WFFM and instead of adding the form to the page, instead create an instance of the form in memory, complete form and post the data into the WFFM database.

Below is a code sample (commented) of completing the Form and posting the data back programmatically.

public void SubmitToWFFM(ID formID, string username, string anotherString)
{
//Get form item by Item ID
Sitecore.Forms.Core.Data.FormItem formItem = Sitecore.Forms.Core.Data.FormItem.GetForm(formID);

//Get form fields of the WFFM
FieldItem[] formFields = formItem.Fields;

//Get all form fields of the form by name
FieldItem usernameField = formFields .FirstOrDefault(x => x.FieldDisplayName == "Username");

FieldItem anotherField = loggingFields.FirstOrDefault(x => x.FieldDisplayName == "AnotherField");

//Populate fields with values
ControlResult controlUsernameTerm = new ControlResult(usernameField.Name, username, null);
ControlResult controlAnotherField = new ControlResult(anotherField.Name, anotherString, null);

//Create collection of fields
AdaptedControlResult[] acr = new AdaptedControlResult[2];
acr[0] = new AdaptedControlResult(controlUsernameTerm, false);
acr[1] = new AdaptedControlResult(controlAnotherField, true);

AdaptedResultList arl = new AdaptedResultList(acr);

//Save form
SaveToDatabase dbSave = new SaveToDatabase();
try
{
dbSave.Execute(formId, arl, null);</pre>

catch (Exception ex)
{
Sitecore.Diagnostics.Log.Error(ex.Message, ex, this);
}
}

Sitecore SPEAK Dialog Within Desktop

Here is a short useful tutorial for anyone wanting to add a SPEAK Dialog control within the Sitecore desktop view. Basically it uses a Sitecore sheer ui application as a proxy to point at the url of the SPEAK application.

SPEAK Dialog

SPEAK Dialog

  • Create a Sitecore dialog application using Sitecore rocks within the core database.

    SPEAK Dialog Application

    SPEAK Dialog Application

  • Create a standard application item under content / applications in the core database.Set the application field to an link pointing at the url at the SPEAK application.Example Raw Value: <link url=”/sitecore/client/MikeRobbins/Dialogs/RolePermissions” linktype=”internal” id=”” />

    Application Item

    Application Item

  • Create an application shortcut pointing at the application within the applications folder.<link linktype=”internal” url=”/applications/RolePermissions” querystring=”” target=”” id=”{5DD732CF-67CE-4EF3-9F76-D53E16225693}” bdgtk

    Application Shortcut

    Application Shortcut

An example of this method can be seen in my bulk user permissions module on GitHub https://github.com/sobek1985/SitecoreSPEAKBulkRolePermissions

The next stage is to use this method to create a command button in the content editor and pass in the context item using query string parameters.

Solr Search On Exact Phrase

Very brief code sample of how to perform a exact phrase match query in Solr.

Using the Solr Admin’s Query tool to perform a search on a field, you will notice that Solr splits each word into a token to search on.

Solr Phrase Match 1

Solr Admin Query

 

Most of the time this is the desired functionality, however there could be times where you want to return results where the exact phrase is matched. If you perform a search wrapping quotes around the phrase within the Solr Admin Query tool, Solr will perform a query looking for an exact match on the phrase (see screenshot below, notice the reduced number of results).

 

Solr Phrase Match 2

Solr Admin Phase Search With Quotes

 

Below is a code sample of how to perform a search using Sitecore 7’s Content Search API’s. The code checks whether the keywords searched upon start and end with quotes. If the quotes are present, the keywords are then passed into the search intact with the quotes present. If the quotes aren’t present the keywords are split into an array on a space and the search is performed as normal.

private Expression<Func<SearchItem, bool>> GetKeywordFilters(string keywords)
 {
 var predicate = PredicateBuilder.True<SearchItem>();

 var keywordCollection = new List<string>();

 if (keywords.StartsWith("\"") && keywords.EndsWith("\""))
 {
 keywordCollection.Add(keywords);
 }
 else
 {
 keywordCollection = keywords.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
 }

 var filters = keywordCollection.Aggregate(predicate, (current, keyword) =>
current.Or(i => (i.Body.Equals(keyword)
 || i.Title.Equals(keyword)
);

 return filters;
 }
public SearchResult<SearchItem> Search(string keywords)
 {

 using (var context = Sitecore.ContentSearch.ContentSearchManager.GetIndex(_searchIndex).CreateSearchContext())
 {
 var queryable = context.GetQueryable<SearchItem>();

 Expression<Func<SearchItem, bool>> filters;

 filters = GetKeywordFilters(keywords);
 queryable = queryable.Where(filters);

 var results = queryable.GetResults();
 }

Text Based Content Search Based On Tagging

Out of the box Sitecore offers Content Editors the power to create tags, and assign them to content items using the Tagging field in standard values.

Tagging Standard Value

Tagging Standard Value

When analysing the indexes using LUKE, you can see that Sitecore indexes the ID’s of each tag. This makes it difficult to perform a text based content search that also matches on tags.

Luke Index View

Luke Index View

However Sitecore gives us the power to create computed fields in search indexes. This means we can create new fields within the index and programmatically create text into the index.

This example below uses a computed field that instead of indexing the ID of the tag, instead adds the item name of each tag seperated by a space. This then allows you to perform a text based search against the tag. (Sitecore by default indexes the __Semantics field, so here i’m creating a new field in the index called “tags” not to override any standard Sitecore functionality)

 

Computed Field Class

namespace MikeRobbins.CMS.ComputedFields
{
public class Tags : IComputedIndexField
{
public object ComputeFieldValue(Sitecore.ContentSearch.IIndexable indexable)
{
var tags = new StringBuilder();

Item obj = (Item)(indexable as SitecoreIndexableItem);

if (obj != null)
{
var tagField = (Sitecore.Data.Fields.MultilistField)obj.Fields["__Semantics"];

if (tagField != null)
{
var items = tagField.GetItems();

foreach (var item in items)
{
tags.Append(item.Name + " ");
}
}
}

return tags.Length != 0? tags.ToString():null;
}

public string FieldName { get; set; }
public string ReturnType { get; set; }
}
}

XML Config

Add this XML to the “AddComputedIndexField” section of your search index config.

<field fieldName="tags"                           >MikeRobbins.CMS.ComputedFields.Tags,MikeRobbins.CMS</field>

Sitecore Item Web API XML Maintaining Sitecore SPEAK

Recently I have had a client request asking to use Sitecore Item Web API in XML format rather than JSON. Luckily within the Item Web API documentation Sitecore provide a Pipeline Processor to output XML rather than JSON.  http://sdn.sitecore.net/upload/sdn5/modules/sitecore%20item%20web%20api/sitecore_item_web_api_developer_guide_sc66-71-a4.pdf

Sitecore SPEAK also makes use of the Sitecore Item Web API, but an unfortunate side effect of this code snippet is that it breaks Sitecore SPEAK as it expects JSON responses back from the web service. The code snippet below is an adapted version of the Sitecore code snippet. It allows bypassing the XMLSerializer by default unless a query string parameter of “type=xml” is present.

This allows you to maintain Sitecore SPEAK functionality while allowing the calling client to easily switch between JSON and XML. Add this XML to the include folder in app_config

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
 <sitecore>
 <pipelines>
 <itemWebApiRequest>
 <processor patch:before="*[@type='Sitecore.ItemWebApi.Pipelines.Request.SerializeResponse, Sitecore.ItemWebApi']" type="MikeRobbins.CMS.WebItemAPI.SwitchToXmlSerializer,MikeRobbins.CMS" />
 </itemWebApiRequest>
 </pipelines>
 </sitecore>
</configuration>
namespace MikeRobbins.CMS.WebItemAPI
{
 public class SwitchToXmlSerializer : RequestProcessor
 {
 public override void Process([NotNull] RequestArgs arguments)
 {
 if (System.Web.HttpContext.Current.Request.QueryString["type"] == "xml")
 {
 Context.Current.Serializer = new XmlSerializer();
 }
 }
 }
}
namespace MikeRobbins.CMS.WebItemAPI
{
 public class XmlSerializer : ISerializer
 {
 public string SerializedDataMediaType
 {
 get
 {
 return "text/xml";
 }
 }

 public string Serialize(object value)
 {
 return Serialize((Dynamic)value).ToString();
 }

 private XElement Serialize(Dynamic value)
 {
 var element = XElement.Parse("<object/>");

 foreach (var property in value)
 {
 var propertyName = string.Format("_{0}", property.Key);
 propertyName = propertyName.Replace("{", "");
 propertyName = propertyName.Replace("}", "");
 propertyName = propertyName.Replace(" ", "-");

 var child = XElement.Parse(string.Format("<{0}/>", propertyName));
 var array = property.Value as Dynamic[];

 if (array != null)
 {
 foreach (var item in array)
 {
 child.Add(Serialize(item));
 }
 }
 else if (property.Value is Dynamic)
 {
 child.Add(Serialize((Dynamic)property.Value));
 }
 else
 {
 child.Add(property.Value.ToString());
 }

 element.Add(child);
 }

 return element;
 }
 }
}

Workflow RSS Feed Custom Email Action

I have worked with a number of clients that have asked why the email notifications they receive from workflow didn’t reflect what is available from the RSS feed within the workbox.

This seems to have been a goal of a number of developers in the past, but nothing concrete has been created to replicate the feature. There is a awesome workaround from Nick Wesselman using Rss2email. http://www.techphoria414.com/Blog/2012/April/Sitecore_Workflow_Emails_with_rss2email

As part of a recent hacking session I decided to see if I could replicate the RSS feed within a more advance email action.

Solution
The solution is available as a Sitecore package on the marketplace or the source is available of GitHub at the bottom of this blog. Please feel free to extend and contribute.

Workflow history

Using the GetWorkflow().GetHistory(Item) API gives full access to the entire workflow history or a particular Sitecore Item.

   var workflowItemHistory = Item.State.GetWorkflow().GetHistory(Item);

However on testing this code threw up an unexpected error. Due to the email action being called from a command the workflow command hasn’t been fully executed, so the workflow history doesn’t contain the currently executing workflow command. This was solved by creating a function that gets the current workflow command that has fired, working out the target state of the action before appending this data to a collection of workflow history.

        private ItemWorkflowHistory GetCurrentWorkflowHistoryItem()
        {

            var history = new ItemWorkflowHistory()
            {
                ItemDateTime = DateTime.Now,
                User = Sitecore.Context.GetUserName(),
                PreviousState = workflowPipelineArgs.DataItem.State.GetWorkflowState().DisplayName,
                CurrentState = GetCorrectWorkflowState().DisplayName,
                Comment = workflowPipelineArgs.Comments
            };

            return history;
        }

private WorkflowState GetCorrectWorkflowState()
        {
            var emailAction = GetEmailAction();

            var command = emailAction.Parent;

            var nextStateId = command[&amp;amp;amp;quot;Next state&amp;amp;amp;quot;];

            var itemWorkflow = Sitecore.Data.Database.GetDatabase(&amp;amp;amp;quot;master&amp;amp;amp;quot;).WorkflowProvider.GetWorkflow(workflowPipelineArgs.DataItem);

            return itemWorkflow.GetState(nextStateId.ToString());
        }

Workflow commands
The biggest challenge was to build the command links to allow the user to fire the correct commands at the relevant stage of workflow.

This suffered from the same issue as the workflow history, the correct workflow commands weren’t shown because the currently executing command hasn’t completed. Again this was solved using the method above of getting the firing command and getting its target state of workflow. The correct workflow commands can then be taken from this target state of workflow.

private string GetCommandLinks(Item workflowItem, WorkflowState state)
        {
            var sb = new StringBuilder();

            var workflow = Sitecore.Data.Database.GetDatabase(&amp;amp;amp;amp;quot;master&amp;amp;amp;amp;quot;).WorkflowProvider.GetWorkflow(workflowItem);

            var commands = workflow.GetCommands(state.StateID);

            sb.Append(&amp;amp;amp;amp;quot;&amp;amp;amp;amp;lt;ul&amp;amp;amp;amp;gt;&amp;amp;amp;amp;quot;);

            foreach (var command in commands)
            {
                var submit = Tools.GetContentEditorLink(ContentEditorMode.Submit, workflowItem, HostName, new ID(command.CommandID));
                var submitComment = Tools.GetContentEditorLink(ContentEditorMode.Submit, workflowItem,HostName, new ID(command.CommandID));

                sb.Append(&amp;amp;amp;amp;quot;&amp;amp;amp;amp;lt;li&amp;amp;amp;amp;gt;&amp;amp;amp;amp;lt;a&amp;amp;amp;amp;gt;&amp;amp;amp;amp;quot; + command.DisplayName + &amp;amp;amp;amp;quot;&amp;amp;amp;amp;lt;/a&amp;amp;amp;amp;gt; or &amp;amp;amp;amp;lt;a&amp;amp;amp;amp;gt;&amp;amp;amp;amp;quot; + command.DisplayName + &amp;amp;amp;amp;quot; &amp;amp;amp;amp;amp;amp; comment&amp;amp;amp;amp;lt;/a&amp;amp;amp;amp;gt;&amp;amp;amp;amp;lt;/li&amp;amp;amp;amp;gt;&amp;amp;amp;amp;quot;);
            }

            string editLink = Tools.GetContentEditorLink(ContentEditorMode.Editor, workflowItem, HostName, ID.NewID);
            string previewLink = Tools.GetContentEditorLink(ContentEditorMode.Preview, workflowItem,HostName, ID.NewID);

            sb.Append(&amp;amp;amp;amp;quot;&amp;amp;amp;amp;lt;li&amp;amp;amp;amp;gt;&amp;amp;amp;amp;lt;a&amp;amp;amp;amp;gt;Edit&amp;amp;amp;amp;lt;/li&amp;amp;amp;amp;gt;&amp;amp;amp;amp;quot;);
            sb.Append(&amp;amp;amp;amp;quot;&amp;amp;amp;amp;lt;li&amp;amp;amp;amp;gt;&amp;amp;amp;amp;lt;a&amp;amp;amp;amp;gt;Preview&amp;amp;amp;amp;lt;/li&amp;amp;amp;amp;gt;&amp;amp;amp;amp;quot;);
            sb.Append(&amp;amp;amp;amp;quot;&amp;amp;amp;amp;lt;li&amp;amp;amp;amp;gt;&amp;amp;amp;amp;lt;a /&amp;amp;amp;amp;gt;Workbox&amp;amp;amp;amp;lt;/li&amp;amp;amp;amp;gt;&amp;amp;amp;amp;quot;);

            sb.Append(&amp;amp;amp;amp;quot;&amp;amp;amp;amp;lt;/ul&amp;amp;amp;amp;gt;&amp;amp;amp;amp;quot;);

            return sb.ToString();
        }

Reviewing the command links in the Workbox RSS feed I was able to build a helper class to build the command links in the correct URL format for the links.

public static string GetContentEditorLink(ContentEditorMode contentEditorMode, Item item, string hostName, ID commandId)
        {
            switch (contentEditorMode)
            {
                case ContentEditorMode.Editor:
                    return (&amp;amp;amp;amp;quot;http://&amp;amp;amp;amp;quot; + hostName.Replace(&amp;amp;amp;amp;quot;http://&amp;amp;amp;amp;quot;, &amp;amp;amp;amp;quot;&amp;amp;amp;amp;quot;) + &amp;amp;amp;amp;quot;/sitecore/shell/Applications/Content%20editor.aspx?fo=&amp;amp;amp;amp;quot; + item.ID.ToString() + &amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;id=&amp;amp;amp;amp;quot; + item.ID.ToString() + &amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;la=&amp;amp;amp;amp;quot; + item.Language.Name + &amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;v=&amp;amp;amp;amp;quot; + item.Version.Number.ToString() + &amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;sc_bw=1&amp;amp;amp;amp;quot;);

                case ContentEditorMode.Preview:
                    return (&amp;amp;amp;amp;quot;http://&amp;amp;amp;amp;quot; + hostName.Replace(&amp;amp;amp;amp;quot;http://&amp;amp;amp;amp;quot;, &amp;amp;amp;amp;quot;&amp;amp;amp;amp;quot;) + &amp;amp;amp;amp;quot;/sitecore/shell/feeds/action.aspx?c=Preview&amp;amp;amp;amp;amp;id=&amp;amp;amp;amp;quot; + item.ID.ToString() + &amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;la=&amp;amp;amp;amp;quot; + item.Language.Name + &amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;v=&amp;amp;amp;amp;quot; + item.Version.Number.ToString());

                case ContentEditorMode.Submit:
                    return (&amp;amp;amp;amp;quot;http://&amp;amp;amp;amp;quot; + hostName.Replace(&amp;amp;amp;amp;quot;http://&amp;amp;amp;amp;quot;, &amp;amp;amp;amp;quot;&amp;amp;amp;amp;quot;) + &amp;amp;amp;amp;quot;/sitecore/shell/feeds/action.aspx?c=Workflow&amp;amp;amp;amp;amp;id=&amp;amp;amp;amp;quot; + item.ID.ToString() + &amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;la=&amp;amp;amp;amp;quot; + item.Language.Name + &amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;v=&amp;amp;amp;amp;quot; + item.Version.Number.ToString() + &amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;cmd=&amp;amp;amp;amp;quot; + commandId.ToString());

                case ContentEditorMode.SubmitComment:
                    return (&amp;amp;amp;amp;quot;http://&amp;amp;amp;amp;quot; + hostName.Replace(&amp;amp;amp;amp;quot;http://&amp;amp;amp;amp;quot;,&amp;amp;amp;amp;quot;&amp;amp;amp;amp;quot;) + &amp;amp;amp;amp;quot;/sitecore/shell/feeds/action.aspx?c=Workflow&amp;amp;amp;amp;amp;id=&amp;amp;amp;amp;quot; + item.ID.ToString() + &amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;la=&amp;amp;amp;amp;quot; + item.Language.Name + &amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;v=&amp;amp;amp;amp;quot; + item.Version.Number.ToString() + &amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;cmd=&amp;amp;amp;amp;quot; + commandId.ToString() + &amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;nc=1&amp;amp;amp;amp;quot;);
            }
            return &amp;amp;amp;amp;quot;&amp;amp;amp;amp;quot;;
        }
    }

Workflow History

20140521-194216-70936959.jpg

20140521-194216-70936793.jpg

Source

https://github.com/sobek1985/AdvancedEmailAction

Sitecore Marketplace

https://marketplace.sitecore.net/en/Modules/Sitecore_Advanced_Email_Action.aspx

Follow

Get every new post delivered to your Inbox.

Join 198 other followers

%d bloggers like this: