Monday, October 20, 2014

Do NOT use Sitecore.Data.Database constructor

How to kill your website - part x

This post could indeed also be called "How to kill your website" because that is what using the constructor of the Sitecore.Data.Database class in Sitecore will do. 

If you set your log level to INFO and see something like this in the log files:

4184 08:30:17 INFO  Cache created: 'web[blobIDs]' (max size: 500KB, running total: 624MB)
4184 08:30:17 INFO  Cache created: 'web[blobIDs]' (max size: 500KB, running total: 624MB)
4184 08:30:17 INFO  Cache created: 'web[blobIDs]' (max size: 500KB, running total: 625MB)
4184 08:30:17 INFO  Cache created: 'web[blobIDs]' (max size: 500KB, running total: 625MB)
4184 08:30:17 INFO  Cache created: 'web[blobIDs]' (max size: 500KB, running total: 626MB)
4184 08:30:17 INFO  Cache created: 'web[blobIDs]' (max size: 500KB, running total: 626MB)
4184 08:30:17 INFO  Cache created: 'web[blobIDs]' (max size: 500KB, running total: 627MB)

This means you have a memory leak...  you're site is dying. Slowly (maybe), but surely.
Look into your code and see if you have ever used new Database(). And then get rid of that code.

How to fetch a database

The wrong way:

var database = new Database("web");


The right way(s):

var database = Sitecore.Data.Database.GetDatabase("web");
or
var database = Sitecore.Configuration.Factory.GetDatabase("web")

Thursday, October 16, 2014

Security in ComputedIndexFields

Custom ComputedIndexField

How to create a custom ComputedIndexField in Sitecore can be found on several other location on the net. But what if you want to check the security of your item when populating this field? 

A simple function could check if your item was readable by an anonymous user:
const string anonymous = @"extranet\Anonymous";
var account = Account.FromName(anonymous, AccountType.User);
return AuthorizationManager.IsAllowed(item, AccessRight.ItemRead, account);

This will work in a normal environment, but if you try this during population of your ComputedIndexField you will always get true. Why?

SitecoreItemCrawler

The responsible crawler - Sitecore.ContentSearch.SitecoreItemCrawler - is designed in such a way to wrap the calls for your ComputedIndexField in a SecurityDisabler. So, the true value in our security check is by design.

And maybe for the best.. it seems like a much better approach to perform security checks indexed results are being accessed. And how do I do this?

Security and index results


Well, you don't. Sitecore does this for you. When you query your index, Sitecore has a Security check build in to the pipeline:  <processor type="Sitecore.Pipelines.Search.SecurityResolver, Sitecore.Kernel" /> . This checks the security of the item involved, not of any related items that you might have used to fill your ComputedIndexField. If you also want a security check on those, write a processor to extend the pipeline :)

Tuesday, October 14, 2014

ComputedIndexField with extra attributes

ComputedIndexField attributes

In the code that populates my ComputedIndexField I needed a reference to a Sitecore item. I needed to know a "context" item from which I had t retrieve data. Apparently you can extend Sitecore to do this (off course).

My configuration look like this (in a separate config file):

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
 <sitecore>
  <contentSearch>
   <configuration type="Sitecore.ContentSearch.LuceneProvider.LuceneSearchConfiguration, 
                                  Sitecore.ContentSearch.LuceneProvider">
    <indexes hint="list:AddIndex">
     <index id="MyIndexName" 
         type="Sitecore.ContentSearch.LuceneProvider.LuceneIndex,Sitecore.ContentSearch.LuceneProvider">
      <configuration type="Demo.Search.LuceneIndexConfiguration, Demo">
       <documentOptions type="Sitecore.ContentSearch.LuceneProvider.LuceneDocumentBuilderOptions">
        <ProcessDependencies>true</ProcessDependencies>
       </documentOptions>
       <fields hint="raw:AddComputedIndexField">
        <field fieldName="DemoContent" label="{1174978C-47CA-405D-13FE-4C808F3A85E7}">
          Demo.Search.DemoField, Demo
        </field>
       </fields>
      </configuration>
     </index>
    </indexes>
   </configuration>
  </contentSearch>
 </sitecore>
</configuration>

 So I added an attribute "label" in my field definition. Now we need a custom IndexConfiguration, and we will start with the one from Sitecore (reverse engineer that one and you will see we only added a few lines).

Custom IndexConfigration

public class LuceneIndexConfiguration : Sitecore.ContentSearch.LuceneProvider.LuceneIndexConfiguration
{
  public override void AddComputedIndexField(XmlNode configNode)
  {
    Assert.ArgumentNotNull(configNode, "configNode");
    string fieldName = XmlUtil.GetAttribute("fieldName", configNode, true);
    string type = XmlUtil.GetValue(configNode);            
    if (string.IsNullOrEmpty(fieldName) || string.IsNullOrEmpty(type))
    {
      throw new InvalidOperationException("Could not parse computed index field entry: " + configNode.OuterXml);
    }
    var field = ReflectionUtil.CreateObject(type) as IComputedIndexField;

    // here starts custom code
    var constructableField = field as IConstructable;
    if (constructableField != null)
    {
     constructableField.Constructed(configNode);
    }
    // end custom code

    if (field != null)
    {
      field.FieldName = fieldName.ToLowerInvariant();
      DocumentOptions.ComputedIndexFields.Add(field);
    }
  }
}

The extra stuff is based on the IConstuctable interface. This allows you to created a Constructed method that can read your xml node. You can use your own interfaces as well surely, but this one is already in the box..

And then we adapt the code for our ComputedIndexField:

IConstructable

public class DemoField : Sitecore.ContentSearch.ComputedFields.IComputedIndexField, IConstructable
{
  private Item MyItem { get; set; }

  public void Constructed(XmlNode configuration)
  {
    var label = XmlUtil.GetAttribute("label", configuration, true);
    if (string.IsNullOrEmpty(label))
    {
      ....
      return;
    }

    Guid id;
    if (Guid.TryParse(label, out id))
    {
      MyItem = Factory.GetDatabase("master").GetItem(new ID(id));
    }
  }

  public object ComputeFieldValue(IIndexable indexable)
  {
    ...
  }
}

We implement the Constructed method and read the xml node. The GetAttribute function is very useful here. We set the MyItem that will be used later on in the ComputedFieldValue function called by the crawler.

Monday, October 13, 2014

Multiple datasources for Associated Content

Associated Content

In the definition of a sublayout in Sitecore you can select a Datasource location and a Datasource template. This makes it easier for your editors to pick the right datasource when using this sublayout. But what if you want to allow more than one datasource template? The answer was found somewhere well hidden on the Sitecore developers site:


Please refer to the following Release note, which describes the officially recommended way to implement such behavior: http://sdn.sitecore.net/Products/Sitecore%20V5/Sitecore%20CMS%206/ReleaseNotes/ChangeLog.aspx
The "Select Associated Content" dialog has been made more extensible. (339396)
Developers can now specify multiple allowed templates by hooking into the <getRenderingDatasource> pipeline and populating the new TemplatesForSelection property.
The default behavior is to use value of the "Datasource Template" field on the rendering item.

How to use multiple datasource templates?


So, all you need to do is create a config file and place it in the include folder (do not alter the web.config):

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
 <sitecore>
   <!-- PIPELINES -->
   <pipelines>
     <getRenderingDatasource>
      <processor patch:after="*[@type='Sitecore.Pipelines.GetRenderingDatasource.GetDatasourceTemplate, 
              Sitecore.Kernel']" type="Demo.SitecorePipelines.GetMultipleDataSourceTemplates, Demo"/>
     </getRenderingDatasource>
   </pipelines>
 </sitecore>
</configuration>


and create your class:

public class GetMultipleDataSourceTemplates
{
    public void Process(GetRenderingDatasourceArgs args)
    {
        Assert.ArgumentNotNull(args, "args");
        if (args.Prototype != null)
        {
            return;
        }

        var data = args.RenderingItem["Datasource Template"];
        if (string.IsNullOrEmpty(data))
        {
            return;
        }

        var templates = data.Split('|');
        foreach (var name in templates)
        {
            var item = args.ContentDatabase.GetItem(name);
            if (item == null)
            {
                continue;
            }
            var template = TemplateManager.GetTemplate(item.ID, args.ContentDatabase);
            if (template != null)
            {
                args.TemplatesForSelection.Add(template);
            }
        }
    }
}

Note that we use "|" as separator here. That is just as an example.
Ok, that's it. Now we can use it like this in Sitecore:



When saving your sublayout item, Sitecore will complain because the field in the datasource template cannot be linked to an item. Don't worry about this. Your editors won't notice and it works.

Template updates

One drawback is that Sitecore is not able to link the template to the field anymore. This means that if you move or rename the template the field will not be updated (as a normal datasource template field will).

Tuesday, April 1, 2014

Configure the Media Browser default view

Sitecore Media Browser

When you browse for an image from an Image field or Rich Text field in Sitecore, you get to see the Media Browser. In version 6 you would just see a tree view, from version 7 you get a search view as default. The tree view is also there, but you need to switch tabs..  Some people prefer the tree view though and want that as default.

Sitecore 7.0

 In Sitecore 7.0 you can alter the default tab in the Media Browser by editing the \sitecore\shell\Applications\Media\MediaBrowser\MediaBrowser.xml. You will see a Tabstrip with 2 tabs ("MediaLibraryTab" and "MediaTab"). The first tab is the default, so by switching them around you can set the MediaTab ("browse") as default.
The best way to handle your new file is to place it in the "\sitecore\shell\Override" folder.

The result will look like this:


Sitecore 7.1

In Sitecore 7.1 they changed the Media Browser. The search pane even appears to have a bug (the datasource if provided is not taken into account), but that's not our focus here.
The top of the Media Browser now looks like:



Switching the panes can be done with the buttons on the right (above the search field). Setting the default to the tree view however is again possible: in order to use the tree view as a default view you need to change the definition of the Media Browser in the "\App_Config\Include\Sitecore.Speak.config" file like this:

Default config:


<overrideXmlControls>
    <override xmlControl="Sitecore.Shell.Applications.Media.MediaBrowser" 
           with="/sitecore/client/Sitecore/Common/Dialogs/SelectMediaDialog" />
</overrideXmlControls>

Tree view config:


<overrideXmlControls>
    <override xmlControl="Sitecore.Shell.Applications.Media.MediaBrowser" 
            with="/sitecore/client/Sitecore/Common/Dialogs/SelectMediaViaTreeDialog" />
</overrideXmlControls>


The result will look like this:






This view takes your datasource into account (as it should) and displays it as the topfolder of the tree.

Sitecore 8

In Sitecore 8, the "overrideXmlControls" element can be found in "App_config\Include\Sitecore.Speak.Applications.config". The rest should stay the same.

Patching

A good remark was made that it is better to patch this value. It is actually always a good idea to use patches to alter Sitecore configs. The patch should look something like this:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/" >
  <sitecore>
    <overrideXmlControls>
      <override xmlControl="Sitecore.Shell.Applications.Media.MediaBrowser">
   <patch:attribute name="with">/sitecore/client/applications/Dialogs/SelectMediaViaTreeDialog</patch:attribute>
      </override>
    </overrideXmlControls>    
  </sitecore>
</configuration>

Thursday, March 27, 2014

Sitecore Parameter Templates: Invalid cast exception

Sitecore Parameter Templates

Lots of blog posts have already been written about this subject and it's not the main topic for this one, but a small introduction could be useful.
If you work with Sitecore, you probably use sublayouts. Some of those sublayouts probably need parameters. One way to get these parameters is to let the editors provide them when they attach the sublayout to the presentation of an item. It's a very flexible way to work, but before the "Parameter Templates" it was quite difficult to get a content author to enter the correct configurations for a sublayout in the "Additional Parameters" property.

Creating and using a Parameter Template

Actually it is quite simple:
  1. Create a template based upon the "Standard Rendering Parameters" (/sitecore/templates/System/Layout/Rendering Parameters/Standard Rendering Parameters)
  2. Add your parameters to this template using regular Sitecore fields types
  3. Select the template in the "Parameters Template" field of your Sublayout item (Editor Options section) 

API
Reading the values in your code can be done the same way as with the Additional Parameters (they are stored in the same way): [example in WebForms]

var sublayout = Parent as Sublayout;
var parameters = Sitecore.Web.WebUtil.ParseUrlParameters(sublayout.Parameters);
var pageIndex = parameters["PageIndex"];

Note that all values are strings. If your parameter value was an item, you will get the guid.


Result
The result in Sitecore will look like this:
 The Parameter template here had a section "NewsBox" and 2 fields "PageIndex" and "Count".


Compatibility with "Additional Parameters"
If you had additional parameters and you create a parameter template that uses the same keys as already used in those additional parameters, the data will be automatically shown in the parameter template fields (and no longer in the additional fields).

If you remove a parameter template from a sublayout the values entered on items using the sublayout with the parameters will not be deleted: they will still be shown in the additional parameters.


Invalid cast from 'System.String' to 'System.Web.UI.Page'



What I actually wanted to add in this post to the already existing ones out there is the above error.
You can get this with the use of sublayout parameters (template version or additional).
All you have to do is call your parameter key "Page".

Solution: do not call your parameter key 'Page'.. ;)