Archive for June, 2012

Author: Tobias Zimmergren
http://www.zimmergren.net | http://www.tozit.com | @zimmergren

Introduction

As many of you know, customizing a form for a SharePoint list isn’t very tricky to do – but when it comes to modifying the forms for an External List it becomes a bit more of a challenge. In this article I will walk you through how the BCS (Business Connectivity Services) model can enable you to modify the New- and Editforms by creating and utilizing your own Custom Field Controls and I will also talk about how you can change the behavior and rendering of your DisplayForm using jQuery.

Related articles about BCS that may be worthwhile:

Scenario

So let’s pose that our scenario is that we’ve got a a source of data coming in through BCS and is represented like it’s always represented out of the box with BCS. What we would like to do is to customize the New- and EditForms to allow custom logic and we would also like to change how the DispForm behaves.

In this sample we’ve got a very simplistic BCS model containing one entity with these properties:

  • Identifier (string)
  • Color (string)
  • Published (boolean)

    In SharePoint, our model is represented like this in the out of the box UI:

    Display Form New- and Edit Forms
    ScreenShot1530 image

    What we really want to achieve in this article is to manipulate the behavior of our Display- and New/Edit forms to look something like this:

    Display Form
    Published: Icon instead of Yes/No text.
    Color: Lit up with the selected color in the UI.
    New- and Edit Forms
    Color: Replaced with a custom field control
    image image

    As you can see in the last two images above, the result of our development is that we’ll be using a custom control (DropDown) instead of the standard TextBox for our Color-field, and we’ll change the way the Published-field looks in the Display Form. It should give you an idea of how you can customize and alter the behavior of your BCS External List Forms.

    So without further ado, let’s get started with modifying our forms!

    (I assume that you already have a BCS Model in place and will not iterate the steps for creating one here…)

    Modifying New/Edit forms: Custom Field Control to the rescue

    When it comes to the BCS Model, you should really always edit it through the XML and not through the Visual Studio UI. Start by right-clicking your BCS Model and selecting the "Open With…" alternative:
    image

    Then choose your favorite XML editor:
    image

    When you’re in this mode, you obviously have to be careful not to mistype or misspell anything as it can result in a broken BCS Model. Awesome :-)

    To cut it short, here’s an extract for the "Creating" Method, which references a Custom Field Control called ColorField:

                <Method Name="Create">
                  <Parameters>
                    <Parameter Name="returnEntity1" Direction="Return">
                      <TypeDescriptor Name="ReturnEntity1" TypeName="TOZIT.Samples.BCSExtensions.BdcModel1.Entity1, BdcModel1">
                        <TypeDescriptors>
                          <TypeDescriptor Name="Identifier1" IdentifierName="Identifier1" TypeName="System.String" />
                          <TypeDescriptor Name="Published" IsCollection="false" TypeName="System.Boolean" />
                          <TypeDescriptor Name="Color" TypeName="System.String">
                            <Properties>
                              <Property Name="SPCustomFieldType" Type="System.String">ColorField</Property>
                            </Properties>
                          </TypeDescriptor>
                        </TypeDescriptors>
                      </TypeDescriptor>
                    </Parameter>
                    <Parameter Name="newEntity1" Direction="In">
                      <TypeDescriptor Name="NewEntity1" TypeName="TOZIT.Samples.BCSExtensions.BdcModel1.Entity1, BdcModel1">
                        <TypeDescriptors>
                          <TypeDescriptor Name="Identifier1" IdentifierName="Identifier1" TypeName="System.String" CreatorField="true" />
                          <TypeDescriptor Name="Published" IsCollection="false" TypeName="System.Boolean" CreatorField="true" />
                          <TypeDescriptor Name="Color" TypeName="System.String" CreatorField="true">
                            <Properties>
                              <Property Name="SPCustomFieldType" Type="System.String">ColorField</Property>
                            </Properties>
                          </TypeDescriptor>
                        </TypeDescriptors>
                      </TypeDescriptor>
                    </Parameter>
                  </Parameters>
                  <MethodInstances>
                    <MethodInstance Name="Create" Type="Creator" ReturnParameterName="returnEntity1" ReturnTypeDescriptorPath="ReturnEntity1" />
                  </MethodInstances>
                </Method>

    As you can see above I’ve referenced a property called "SPCustomFieldType" in the code (Read more on BCS Custom Properties), which in turn is referencing something called "ColorField". The ColorField is my CustomField control that is simply a DropDown-box to be represented in the UI instead of the good’ol text box.

    Modifying Display Forms: jQuery to the rescue

    When it comes to modifying the DispForm of the External List, I’ve had several attempts with the documented RendererDefinition in the past and not one single time it worked for me nor any of my clients. So with that experience, I’ve resorted to using jQuery to modify the rendition of the Display Form. Obviously one could use jQuery for the New- and Edit Forms as well, if you want to.

    Short story, the jQuery looks like this:

    // Found this through google, but can't remember the source. 
    // Great script for string replacements!
    String.prototype.replaceAll = function (str1, str2, ignore) {
        return this.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g, "\\$&"), (ignore ? "gi" : "g")), (typeof (str2) == "string") ? str2.replace(/\$/g, "$$$$") : str2);
    };
    
    $(document).ready(function () 
    {
        // Render an icon instead of Yes/No text
        var publishedHtml = $('h3.ms-standardheader:contains("Published")').closest('tr').children(".ms-formbody").html().toString();
        publishedHtml = publishedHtml.replaceAll("Yes", "<img src='/_layouts/images/TOZIT.Samples.BCSExtensions/checked.png' />");
        publishedHtml = publishedHtml.replaceAll("No", "<img src='/_layouts/images/TOZIT.Samples.BCSExtensions/unchecked.png' />");
        $('h3.ms-standardheader:contains("Published")').closest('tr').children(".ms-formbody").html(publishedHtml);
    
        // Render the entire TD in the color that was chosen
        var colorHtml = $('h3.ms-standardheader:contains("Color")').closest('tr').children(".ms-formbody").html().toString();
        var colorHtmlTd = $('h3.ms-standardheader:contains("Color")').closest('tr').children(".ms-formbody").attr("style", "background-color:" + colorHtml + ";");
    });

    Essentially what this jQuery snippet does is that it will find the correct elements in the HTML markup that corresponds to my Fields (you see the :contains("Published") part? That’s where I find the Published-field in the markup). Then I simply alter the text that is sent to us from the server, replacing "Yes/No" with an image of a checked or unchecked checkbox. Pretty simple trick to light up the form a bit without the hassle of trying to get your RendererDefinition working…

    Tip: If you want to easily inject your jQuery to the External List Forms, I’d recommend using a DelegateControl override on the AdditionalPageHead. The code for my AdditionalPageHead DelegateControl looks something like this:

            protected void Page_Load(object sender, EventArgs e)
            {
                string httpRequestUrlString = HttpContext.Current.Request.Url.AbsoluteUri;
    
                // Note: You should create a smarter verification for when to load the scripts than to use a hardcoded value like I've done in this sample. 
                if ((httpRequestUrlString.Contains("/Lists/Sample Entity/") || httpRequestUrlString.Contains("/Lists/Sample%20Entity/")) && httpRequestUrlString.Contains("DispForm.aspx")) // Only render scripts if it's the Display Form on the Sample Entity list
                {
                    // Add a reference to jQuery if it isn't already loaded
                    ScriptLink.Register(this.Page, "/_layouts/TOZIT.Samples.BCSExtensions/Scripts/jquery-1.7.2.min.js", false);
                    ScriptLink.Register(this.Page, "/_layouts/TOZIT.Samples.BCSExtensions/Scripts/bcsDisplayFormScriptSample.js", false);
                }
            }


    The code snippet above simply checks the current request and determines whether it’s your specific list being loaded, and then also determine if it’s the DispForm.aspx file being served. As noted in the comments, you should modify this if-statement to suit your needs, should you decide to create a DelegateControl like this.

    Summary

    Working with BCS is both fun and challenging at the same time. Like most SharePoint projects your requirements always change, and there’s always a need for further investigating the possibilities for how we can deliver quality solutions. In this article I’ve talked about some tips for how you can modify the NewForm and EditForm of your BCS External List by simply using a Custom Field Control. I’ve also talked about how you can deliver customized forms using jQuery as in the DispForm sample above.

    As a final note I’d like to shout out to Scot Hillier who is a fellow SharePoint MVP and author of the Professional Business Connectivity Services in SharePoint 2010 book for valuable tips and awesome discussions regarding this topic.

  • Author: Tobias Zimmergren
    http://www.zimmergren.net | http://www.tozit.com | @zimmergren

    Introduction

    One of he culprits of working with the LINQ to SharePoint contexts that are being auto-generated for us is that it actually doesn’t necessarily render all the fields or properties you require. For example the built-in fields "Created By" or the "Attachments" property of a list item. Oftentimes we need these fields in our queries, whether they be CAML queries or LINQ queries. With LINQ to SharePoint you have the ability to extend an Entity that you’ve generated, and allow additional code to be executed for those entities. In this article I’ll talk briefly about how you can create and manage an extension to your LINQ context to allow additional fields in your queries.

    One of the powerful advantages of doing this is that you post-deployment can extend your queries. Lets for example say that the list in your deployment gets some additional fields in the future, then perhaps you’ll need to make sure that your LINQ context can cope with querying those fields as well – this can easily be done by extending an entity context as I’ll talk about below.

    In this article I’ll give you a sample of how to extend the LINQ entity to fit our custom needs, including mapping the "Created By" and "Modified By" fields and some additional logical customizations.

    Generate your context

    First and foremost we’ll need to generate our context. This can be done using SPMetal (Which you can read more about in this article: Getting Started with LINQ to SharePoint), or for example the extensions in CKS:Dev. I’m going to generate a quick and easy sample for this by utilizing the CKS:Dev tools and then build my samples on top of that.

    If you haven’t installed the CKS:Dev tools yet, go right ahead and do that – then get back to this step!

    1) Generate a LINQ context by using the CKS:Dev plugin, which will give you this additional context menu in the SharePoint server browser:

    image

    This should auto-generate a class file for you, containing all the entities for the selected site:

    image

    The file contains all the LINQ to SharePoint data context information that a normal SPMetal.exe command would generate, since this tool in essense is using SPMetal.exe:

    image

    As you can see, we’ve easily created the LINQ data context that we need, but unfortunately it’s missing some vital parts for our application – namely some of the built-in fields that you would normally want to work with. In my case, I was lacking the "Created By" and "Modified By" fields and I’d have to find a way to extend the LINQ context to cope with this so my queries can be easily constructed even for those fields.

    Extending an entity in your generated LINQ context

    In order for us to be able to extend the LINQ context, Microsoft provides us with an interface called ICustomMapping.

    We’ll start by inheriting a new class from this interface, and name the class along the lines of the entity we want to extend.

    // ----------------------------------------------------------------------- 
    // <copyright file="AnnouncementExtension.cs" company="TOZIT AB"> 
    // Awesomeness. 
    // </copyright> 
    // -----------------------------------------------------------------------
    
    namespace TOZIT.Samples.ExtendingLinq 
    { 
        using Microsoft.SharePoint.Linq;
    
        /// <summary> 
        /// A LINQ extension to the Announcement-entity 
        /// </summary> 
        public partial class Announcement : ICustomMapping 
        { 
            public void MapFrom(object listItem) 
            { 
                throw new System.NotImplementedException(); 
            }
    
            public void MapTo(object listItem) 
            { 
                throw new System.NotImplementedException(); 
            }
    
            public void Resolve(RefreshMode mode, object originalListItem, object databaseListItem) 
            { 
                throw new System.NotImplementedException(); 
            } 
        } 
    } 

    Make sure you follow these rules:

    • Class name should be the same name as the entity you’re extending (partial class)

    Let’s go ahead and add the necessary code to extend our entity. I’ll throw in some simple sample code here on how to map the fields we want, and to add additional logic to our new extension.

    // -----------------------------------------------------------------------
    // <copyright file="AnnouncementExtension.cs" company="TOZIT AB">
    // Awesomeness.
    // </copyright>
    // -----------------------------------------------------------------------
    
    namespace TOZIT.Samples.ExtendingLinq
    {
        using Microsoft.SharePoint;
        using Microsoft.SharePoint.Linq;
    
        /// <summary>
        /// A LINQ extension to the Announcement-entity
        /// </summary>
        public partial class Announcement : ICustomMapping
        {
            #region Properties
    
            /// <summary>
            /// Gets or sets the Created Byproperty
            /// </summary>
            public string CreatedBy { get; set; }
    
            /// <summary>
            /// Gets or sets the Created By Login Name property
            /// Returns a reader-friendly version of the user's loginname
            /// </summary>
            public string CreatedByLoginName { get; internal set; }
            /// <summary>
            /// Gets or sets the Modified By property
            /// </summary>
            public string ModifiedBy { get; set; }
    
            /// <summary>
            /// Gets or sets the Modified By Login Name property
            /// Returns a reader-friendly version of the user's login name
            /// </summary>
            public string ModifiedByLoginName { get; internal set; }
    
            #endregion
    
            #region Methods
    
            /// <summary>
            /// Assigns a field (column) to a property so that LINQ to SharePoint can read data 
            /// from the field in the content database to the property that represents it.
            /// </summary>
            /// <param name="listItem"></param>
            [CustomMapping(Columns = new[] { "Editor", "Author" })] // Needs to be the InternalName of fields..
            public void MapFrom(object listItem)
            {
                var lItem = listItem as SPListItem;
    
                if (lItem != null)
                {
                    // === MAP THE AUTHOR-FIELD ===
    
                    // Map the Created By field to the Author (Created By) field
                    CreatedBy = lItem["Author"].ToString();
    
                    // Map the CreatedByLoginName field to the Author's actual LoginName
                    SPField authorField = lItem.Fields.GetFieldByInternalName("Author");
                    var authorFieldValue = authorField.GetFieldValue(lItem["Author"].ToString()) as SPFieldUserValue;
                    if (authorFieldValue != null)
                    {
                        CreatedByLoginName = authorFieldValue.User.LoginName;
                    }
    
                    // === MAP THE EDITOR-FIELD ===
                    // Map the Modified By field to the Editor (Modified By) field
                    ModifiedBy = lItem["Editor"].ToString();
    
                    // Map the ModifiedByLoginName field to the Editor's actual LoginName
                    SPField editorField = lItem.Fields.GetFieldByInternalName("Editor");
                    var editorFieldValue = editorField.GetFieldValue(lItem["Editor"].ToString()) as SPFieldUserValue;
                    if (editorFieldValue != null)
                    {
                        ModifiedByLoginName = editorFieldValue.User.LoginName;
                    }
                }
            }
    
            /// <summary>
            /// Assigns a property to a field (column) so that LINQ to SharePoint can save 
            /// the value of the property to the field in the content database.
            /// </summary>
            /// <param name="listItem">List Item</param>
            public void MapTo(object listItem)
            {
                var lItem = listItem as SPListItem;
                if (lItem != null)
                {
                    lItem["Author"] = CreatedBy;
                    lItem["Editor"] = ModifiedBy;
                }
            }
    
            /// <summary>
            /// Resolves discrepancies in the values of one or more fields in a list item with respect to its current client value, 
            /// its current value in the database, 
            /// and its value when originally retrieved from the database
            /// 
            /// Read more: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.linq.icustommapping.resolve.aspx
            /// </summary>
            /// <param name="mode">Refresh Mode</param>
            /// <param name="originalListItem">Original list item</param>
            /// <param name="databaseObject">Database object</param>
            public void Resolve(RefreshMode mode, object originalListItem, object databaseObject)
            {
                var origListItem = (SPListItem)originalListItem;
                var dbListItem = (SPListItem)databaseObject;
    
                var originalCreatedByValue =    (string)origListItem["Author"];
                var dbCreatedByValue =          (string)dbListItem["Author"];
    
                var originalModifiedByValue =   (string)origListItem["Editor"];
                var dbModifiedByValue =         (string)dbListItem["Editor"];
    
                if (mode == RefreshMode.KeepCurrentValues)
                {
                    // Save the Current values
                    dbListItem["Author"] = CreatedBy;
                    dbListItem["Editor"] = ModifiedBy;
                }
                else if (mode == RefreshMode.KeepChanges)
                {
                    // Keep the changes being made
                    if (CreatedBy != originalCreatedByValue)
                        dbListItem["Author"] = CreatedBy;
                    else if (CreatedBy == originalCreatedByValue && CreatedBy != dbCreatedByValue)
                        CreatedBy = dbCreatedByValue;
    
                    if (ModifiedBy != originalModifiedByValue)
                        dbListItem["Editor"] = ModifiedBy;
                    else if (ModifiedBy == originalModifiedByValue && ModifiedBy != dbModifiedByValue)
                        ModifiedBy = dbModifiedByValue;
                }
                else if (mode == RefreshMode.OverwriteCurrentValues)
                {
                    // Save the Database values
                    CreatedBy = dbCreatedByValue;
                    ModifiedBy = dbModifiedByValue;
                }
                
            }
    
            #endregion
        }
    }

    As you can see in my sample above, I not only mapped the two fields I need but I also slightly extended the entity with additional properties for retrieving a clean LoginName from the user-objects. Obviously this can be done using a normal .NET 3.5 extension method on the SPUser object, but here I implemented it in the context to show how it can be easily extended to fit our needs.

    Writing a query with our extended data context

    So if we’ve followed the very simple steps of creating a new extension for one of our entities, we can now easily access these details from the query we’re writing:

    image

    using System.ComponentModel; 
    using System.Web.UI.WebControls.WebParts; 
    using Microsoft.SharePoint;
    
    namespace TOZIT.Samples.ExtendingLinq.LinqWebPart 
    { 
        using System.Linq; 
        using System.Web.UI.WebControls;
    
        [ToolboxItemAttribute(false)] 
        public class LinqWebPart : WebPart 
        { 
            protected override void CreateChildControls() 
            { 
                using (var ctx = new SocialflowDataContext(SPContext.Current.Web.Url)) 
                { 
                    // Fetches the items where the current user is the creator 
                    var myItems = from item in ctx.Announcements 
                                      where item.CreatedByLoginName == SPContext.Current.Web.CurrentUser.LoginName 
                                      select item;
    
                    foreach (var item in myItems) 
                    { 
                        Controls.Add(new Literal { Text = item.Title + " : " + item.CreatedByLoginName + " (you) created this item<br/>" }); 
                    } 
                } 
            } 
        } 
    } 

    The list contains a few items (all created by the current user, which is the system account – please use your imagination… :-)  )

    image

    And the results:

    image

    Summary

    All in all, this gives us the flexibility to customize the way we do our queries in SharePoint using LINQ. I’ve gotten the question about extending LINQ to SharePoint quite a few times over the past years, so I thought it’d be time to reflect those thoughts in this post. I hope you enjoy it and can start utilizing the extension of your LINQ queries!

    Enjoy!