This blog is used as a memory dump of random thoughts and interesting facts about different things in the world of IT. If anyone finds it useful, the author will be just happy! :-)

Showing posts with label sitecore. Show all posts
Showing posts with label sitecore. Show all posts

Friday, September 10, 2010

Back to basics: Versioned, Unversioned and Shared fields

It is well-known that each field of a template can be versioned (default option), unversioned or shared. The Template Builder UI exposes the Unversioned and Shared properties as two independent checkboxes. And thus, despite it’s a very basic Sitecore concept, it is sometimes asked what’s the point of marking a field both shared and unversioned. The answer is “a field marked both shared and unversioned is still a shared field”. Think about “shared” as a superset of “unversioned” – the field can’t be shared (between all versions of all languages) without being unversioned (between all versions of one language).

Let’s see how it works under the hood when the field “sharing” level is changed. Let’s create a simple template with just a single field. We’ll keep the defaults so far (versioned). Now create a content item based on this template and fill in the field.

Sitecore fields are stored in three different tables inside the database: VersionedFields, UnversionedFields and SharedFields. The names are quite self-explanatory. Let’s run the following SQL query:

      SELECT * FROM VersionedFields WHERE FieldId = '{GUID-GOES-HERE-…}'

As a result, one record is returned – the field information of the item we’ve just created is stored in the VersionedFields table. The similar queries for UnversionedFields and SharedFields give 0 records.

Now change the field to be Unversioned and run all 3 queries again – it will return 1 record for UnversionedFields table and 0 for others. Change the field to be both Shared and Unversioned and repeat the experiment – the field info now resides in SharedFields table. Now if you uncheck Unversioned and leave it just Shared, it will still show 1 record for SharedFields table and 0 for others. So, here’s the evidence!

NOTE: changing the field “sharing” level might result in a data loss (similar to type cast operation in C#), and Sitecore warns you about it.

You might think that two checkboxes are to be blamed for this confusion. Check out the hot VS extension called Sitecore Rocks – a brand new tool (CTP for now) for developers working with Sitecore projects in VS 2010. It seems to look more natural in this way, isn’t it?

RocksDesignTemplate

Sunday, July 19, 2009

XSLT: inline blocks of managed code

It’s not a secret that XSLT supports blocks of code, written in another language, to be used inside the stylesheet. It seems to have been there from the very beginning – at least, XSLT 1.1 understands it.

However, Microsoft enriched this option with their own element, msxsl:script. It offers pretty much the same functionality, but you can also write the code in C# or any other language of .NET platform. XSLT gurus might argue that it is superfluous stuff and it is unnecessary in 99% of cases. Well, as for me, XSLT lacks a number of useful functions in the standard library, such as ToLower/ToUpper, EndWith, etc. You never think about such low level things when programming C#, but you often have to invent a wheel trying to do the same with XSLT.

More details can be found in the official documentation, but here is a brief extract:

  • guess an extra prefix and let XSLT processor know about it:

    <xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:msxsl="urn:schemas-microsoft-com:xslt"
      xmlns:ext="http://my.domain.com/ext">
       ...
    </xsl:stylesheet>

    Also, pay attention how msxsl prefix is defined – it is required to use msxsl:script syntax.
  • code your extension function:

    <msxsl:script language="C#" implements-prefix="ext">
       public string ToUpper(string inString)
       {
          return inString.ToUpper();
       }
    </msxsl:script>
  • and finally use it:

    <xsl:value-of select="ext:ToUpper(@Name))"/>

Obviously, it is not a good idea to write lots of code this way. It makes the XSLT stylesheet larger and a bit harder to maintain. And, according to Microsoft, you should “avoid script blocks from XSLT files, because they require loading the script engine multiple times”. Actually, if you created an XSLT stylesheet to fill it with tones of .NET code, you’re definitely doing something wrong. But it seems to be good addition to small, but useful “one-line” operations.

Sitecore and msxsl:script

If you plan to take advantage of inline blocks of C# code in Sitecore XSL rendering, you’ll have to do one more step. By default, .NET API to handle the XSL transforms disables the possibility to use msxsl:script. It is probably done for security reason. But the web.config of your Sitecore solution contains the setting EnableXslScripts, which you can easily set to true and be happy:

<!--  ENABLE XSLT SCRIPTS
      Determine whether XSLT script support should be enabled.
      If script support is not enabled, it will be an error if the XSLT file contains script blocks.
      Default value: false.
-->
<setting name="EnableXslScripts" value="true" />

The performance seems to be the same for this simple code either written in msxsl:script block, or wrapped into XSL extension. So, the choice is yours.

WiX and msxsl:script

The heat.exe utility of the WiX toolset has an option to run the harvested authoring against XSLT transform. This is a checkpoint when you can mutate the output before it is done. INHO, it is the most powerful extension option of Heat, because you can do anything with the XML fragment in XSLT.

However, it was a bit disappointing to find out the scripts are disabled by default, and it is not customizable, and the easiest way to fix this is to patch Heat itself and prepare custom WiX build. It would be great if this option is available one day in the base, either as a command line argument, or a configuration setting.

That’s it. If you have some experience with this trick, knowing its pros and cons deeper, share it here. And as usual, any comments are welcome.

P.S. this post was written with the help of Windows Live writer :-)

Sunday, March 22, 2009

Validating the source of TreeList

Sitecore 6 validation was designed to validate the field values. Recently, I also found it useful to control the source of the complex field types, like TreeList. In this post, I'll explain this option taking the TreeList field type as an example.

I'm skipping the validation basics here, since this topic is covered by Alexey Rusakov in his validation series.

You can define a number of parameters in the source of TreeList field type. The complete list is described in the paragraph 2.4.2 "How to Control the List of Items in a Selection Field" of the Data Definition cookbook. These parameters can filter the available and visible items in the content tree (IncludeTemplatesForSelection, ExcludeItemsForDisplay, etc.), define the tree root (DataSource), control multiple selection (AllowMultipleSelection), etc.

But modifying this long list of parameters in a one-line edit field can lead to a simple typos, both in the parameters' names and values. Let's examine how this can be "solved" by introducing a source validator.

The BaseValidator class, the very root of the validator hierarchy in Sitecore API, has a protected method GetField(), which returns an instance of a Field - the one we validate. Hence, the Source property is also available. We want to validate only complex source here, thus skipping if it is an ID or an item path:

        protected override ValidatorResult Evaluate()
        {
            ValidatorResult result = ValidatorResult.Valid;

            Field field = GetField();
            if (field != null)
            {
                string fieldSource = field.Source;
                if (!string.IsNullOrEmpty(fieldSource) && !ID.IsID(fieldSource) 
                    && !fieldSource.StartsWith("/", StringComparison.InvariantCulture))
                {
                    result = EvaluateSourceParameters(fieldSource);
                }
            }

            return result;
        }

Ok, let's start the validation from just the verification if the source is "well-formed". It might happen that a certain parameter was left without a value, or a typo was introduced to the well-known name. Sitecore will never throw an error in such a case, but instead you may receive an orphaned field with nothing to choose from. Thus, the simplest validation includes these two checks, otherwise it keeps the name/value pairs for further analysis:

        ValidatorResult EvaluateSourceParameters(string fieldSource)
        {
            SafeDictionary parameters = new SafeDictionary();
            string[] sourceParts = fieldSource.Split('&');
            foreach (string part in sourceParts)
            {
                if (string.IsNullOrEmpty(part))
                {
                    continue;
                }
                if (!part.Contains("=") || part.EndsWith("="))
                {
                    Text = string.Format("The value is not set for source parameter '{0}'", part.TrimEnd('='));
                    return GetFailedResult(ValidatorResult.Error);
                }
                else
                {
                    string parameterName = part.Substring(0, part.IndexOf('=')).ToLower();
                    if (!sourceParameters.Contains(parameterName))
                    {
                        Text = string.Format("Unknown source parameter '{0}'", parameterName);
                        return GetFailedResult(ValidatorResult.Error);
                    }
                    else
                    {
                        string parameterValue = part.Substring(part.IndexOf('=') + 1);
                        parameters.Add(parameterName, parameterValue);
                    }
                }
            }
            return EvaluateWellFormedParameters(parameters);
        }

The further validation can go deeper and verify the presence of the specified template or item. The method EvaluateWellFormedParameters in this example just iterates the name/value pairs of parameters and applies a certain validation strategy, for instance:

        ValidatorResult EvaluateTemplates(string value, Database database)
        {
            string[] templates = value.Split(new char[] { ',' });
            foreach (string template in templates)
            {
                if (!string.IsNullOrEmpty(template) && Query.SelectSingleItem(string.Format("/sitecore/templates//*[@@key='{0}']", template.ToLower()), database) == null)
                {
                    Text = string.Format("The template '{0}' doesn't exist in the '{1}' database", template, database.Name);
                    return ValidatorResult.Warning;
                }
            }
            return ValidatorResult.Valid;
        }

I'm attaching the full code of this example

There are several notes to consider:
  • The DatabaseName parameter is not validated, because Sitecore takes over this. Try specifying DatabaseName=nosuchdb, and press Save
  • The parameter names are case-insensitive. This is because the parameters are extracted with the StringUtil.ExtractParameter() method, which ignores the case
  • The TreeList field type doesn't "tolower" the values of IncludeItemsForDisplay and ExcludeItemsForDisplay parameters. Hence, be sure to specify an item key instead of an item name here
  • The content tree filter is built out of the "ForDisplay" parameters using 'and' operation. Thus, if IncludeItemsForDisplay contain items of other templates than those specified in IncludeTemplatesForDisplay, this results in an empty tree. This can also be a point of extension of this validator's functionality
Hope anyone finds this article useful. As usual, I would appreciate any comments.