Share

LinkedIn

Sitecore Data Access – Some Extension Methods

Recently, I had a client that did not wish to use the Page Editing controls provided by Sitecore.  They were only interested in the Content Editor.  This greatly simplified our data access needs.  We were able to grab field data and not worry about field names, etc…

Once field render controls were not needed, our CMS access scenarios were VERY simple and VERY repetitive (for example, getting the value of text field X).  To meet this need, we created a set of simple extension methods.

Requirements:

  1. Get data from Sitecore fields for a given item
  2. Have that data be independent of Sitecore once retrieved
  3. Assist controls in not erring when Sitecore data is missing

Getting the data is easy enough, especially with the extension methods below.

Having the data be independent of Sitecore was required to enable unit testing independent of the Sitecore context.  Also, our business logic was constructed to be independent of the data source.  So, hypothetically, we could change to a different CMS and only revise our data access layer.

We did have an issue converting Images and Links away from Sitecore.  For that, we made wrapper classes called ContentImage and ContentLink.  But, that solution has some problems.  See more info below.

Having controls not error when data was missing was an excellent requirement.  Overall, we tried to minimize author access to templates and presentation settings.  But, for business reasons, we could not lock those down entirely.  So, we always ran the risk of sub-layouts being added to a page, but without the required data being on that page.  When this would happen, we wanted to avoid a server error.

To help with that, these extension methods never assume that data is present.  Instead, default values are returned as needed.  Then, it was the responsibility of the business logic to determine what data is required, and the responsibility of the UI layer to respond intelligently  when the business layer indicated missing data.

That all said, here are the extension methods…

Validation methods:

Field Exists: We didn’t use this much, as the methods to access field data would check for field existence.

    <Extension()> _
    Public Function FieldExists(ByVal sitecoreItem As Item, ByVal fieldName As String) As Boolean
        Dim targetField As Sitecore.Data.Fields.Field = sitecoreItem.Fields(fieldName)
        Return (targetField IsNot Nothing)
    End Function

Field Exists and is Populated: Also, not used much.

    <Extension()> _
    Public Function FieldExistsAndIsPopulated(ByVal sitecoreItem As Item, ByVal fieldName As String) As Boolean
        Dim targetField As Field = sitecoreItem.Fields(fieldName)
        Return (targetField IsNot Nothing) AndAlso (Not String.IsNullOrEmpty(targetField.Value))
    End Function

Is Checkbox Checked: This defaults to false if the checkbox does not exist.  In that since, we used it as an opt in.

<Extension()> _
    Public Function IsCheckBoxChecked(ByVal sitecoreItem As Item, ByVal fieldName As String) As Boolean
        Dim targetField As Sitecore.Data.Fields.CheckboxField = sitecoreItem.Fields(fieldName)
        Return (targetField IsNot Nothing) AndAlso targetField.Checked       
    End Function

Data access methods:

Get Text Field Value: This was the most commonly used method.  It defaults to the empty string when not populated.

    <Extension()> _
    Public Function GetTextFieldValue(ByVal sitecoreItem As Item, ByVal fieldName As String) As String
        If sitecoreItem.FieldExistsAndIsPopulated(fieldName) Then
            Return sitecoreItem.Fields(fieldName).Value
        Else
            Return String.Empty
        End If
    End Function

Get Link Field ContentLink: Returns a link wrapper class with link data.  Or, if the field does not exist or was empty, it returns an empty ContentLink object.  It was considered to return nothing.  But, that made lazy loading more complicated.

    <Extension()> _
    Public Function GetLinkFieldContentLink(ByVal sitecoreItem As Item, ByVal fieldName As String) As ContentLink
        Dim thisLink As LinkField = sitecoreItem.Fields(fieldName)
        If thisLink IsNot Nothing Then
            Return thisLink.ConvertToContentLink()
        Else
            Return New ContentLink()
        End If
    End Function

Get ImageField ContentImage: Returns an image wrapper class with image data.  Or, if the field does not exist or was empty, it returns an empty ContentImage object.  It was considered to return nothing.  But, that made lazy loading more complicated.

<Extension()> _
    Public Function GetImageFieldContentImage(ByVal sitecoreItem As Item, ByVal fieldName As String) As ContentImage
        Dim thisImage As Sitecore.Data.Fields.ImageField = sitecoreItem.Fields(fieldName)
        If thisImage IsNot Nothing Then
            Dim imageData As ContentImage = thisImage.ConvertToContentImage()
            If imageData IsNot Nothing Then
                imageData.FieldName = fieldName
                imageData.DataSource = sitecoreItem.Paths.FullPath
            End If           
            Return imageData
        Else
            Return New ContentImage()
        End If
    End Function

Navigation methods:

Is Sitecore Descendant: Compares two items to see if there is an ancestor relationship.

    <Extension()> _
    Public Function IsSitecoreDescendant(ByVal possibleAncestor As Item, ByVal possibleDescendant As Item) As Boolean
        Return IsSitecoreAncestor(possibleDescendant, possibleAncestor)
    End Function

Is Ancestor of Context Item: Compares an item to the context item.

    <Extension()> _
    Public Function IsAncestorOfContextItem(ByVal possibleAncestor As Item) As Boolean
        Return IsSitecoreAncestor(Sitecore.Context.Item, possibleAncestor)
    End Function

Is Sitecore Ancestor: Compares two items to see if there is an ancestor relationship.

<Extension()> _
    Public Function IsSitecoreAncestor(ByVal possibleDescendant As Item, ByVal possibleAncestor As Item) As Boolean
        Dim ancestorPath As String = String.Concat(possibleAncestor.Paths.FullPath(), "/")
        Dim descendantPath As String = String.Concat(possibleDescendant.Paths.FullPath(), "/")

        Return descendantPath.Contains(ancestorPath)
    End Function

Is Context Item: Checks to see if an item is the current item.

<Extension()> _
    Public Function IsContextItem(ByVal sitecoreItem As Item) As Boolean
        Return sitecoreItem.ID = Sitecore.Context.Item.ID
    End Function

Get Page Item Url: Gets the friendly Url of a page item.

    <Extension()> _
    Public Function GetPageItemUrl(ByVal sitecoreItem As Item) As Uri
        Return New System.Uri(Sitecore.Links.LinkManager.GetItemUrl(sitecoreItem), UriKind.Relative)
    End Function

Get Children of Template: Get an item’s children that are of a specified template.

    <Extension()> _
    Public Function GetChildrenOfTemplate(ByVal sitecoreItem As Item, ByVal templateName As String) As IList(Of Item)
        Dim result As IList(Of Item) = New List(Of Item)

        If sitecoreItem.HasChildren Then
            Dim childList As Sitecore.Collections.ChildList = sitecoreItem.GetChildren()
            If childList.Count > 0 Then
                For Each childItem As Item In childList
                    If String.Equals(childItem.TemplateName, templateName, StringComparison.OrdinalIgnoreCase) Then
                        result.Add(childItem)
                    End If
                Next
            End If
        End If

        Return result
    End Function

Image and Link field extensions:

Convert to ContentImage: Returns a ContentImage from an image field.  This was mostly called with the associated item method above.

<Extension()> _
Public Function ConvertToContentImage(ByVal value As ImageField) As ContentImage
    If String.IsNullOrEmpty(value.Value) Then
        Return New ContentImage()
    Else
        Return New ContentImage(value.Src, value.Alt, value.Width, value.Height)
    End If
End Function

Convert to ContentLink: Returns a ContentLink from a link field. This was mostly called with the associated item method above.

<Extension()> _
Public Function ConvertToContentLink(ByVal value As LinkField) As ContentLink

    If String.IsNullOrEmpty(value.Value) Then
        Return New ContentLink()
    Else
        Dim href As String = value.Url
        If Not String.IsNullOrEmpty(value.QueryString) Then
            href = String.Concat(href, "?", value.QueryString.Replace("?", ""))
        End If
        Return New ContentLink(href, value.Text, value.Target)
    End If
End Function

Regarding the use of Image and Link Wrappers:

When grabbing image data from Image fields and link data from Link fields, we decided to create wrapper classes.  These basically contained the separate data pieces for the image, or link.

The motivator for using wrapper classes was keep the business logic independent of Sitecore.  Apart from this goal, we would definitely  have kept the data in the Sitecore field classes.  The Sitecore field classes have several advantages: they wrap the data already, they will be updated by Sitecore with any upgrades, and the field renderer will render them intelligently.

Our wrappers fail in all of these areas.  They don’t contain all possible data (for example, we didn’t capture the CSS class entered for a link).  Sitecore upgrades could make them out-dated for the data they do contain.  Rendering controls for the wrappers would have to be created, and include lots of tedious logic for things like, does this link field have a target.

However, using wrappers, we were able to take Sitecore image data and pass it into a business logic layer that did not know about Sitecore.  Then, we were able to run unit tests on our business logic without creating a Sitecore context.

Note: one other idea was to wrap these with ASP.NET controls.  For example, we could have used HtmlAnchor and HtmlImage.  These have the advantage of pre-existing rendering controls (they are rendering controls).  However, we had the additional requirement that our business logic layer should have no references to System.Web.  So, wrapper classes was the only way to go.

Regarding not using Page Edit Mode:
Our reasons for abandoning the Page Editor were to:

  • Eliminate another need for author training
  • Simplify development
  • Allow the business logic to be Sitecore agnostic

This allowed for a different, and interesting code architecture.   I hope to post more details on that in the near future.

Sitecore development, Sitecore custom code

Comments

Add a Comment

*
*

Please confirm you are human by typing the text you see in this image: