Second Life of a Hungarian SharePoint Geek

September 11, 2010

External list example demonstrating .NET connectivity assembly and custom field type

Filed under: BCS, SP 2010 — Tags: , — Peter Holpar @ 03:45

It is mentioned in several posts (like this one from  René Hézser) that you can use custom fields in BDC models an external links, but I do not really find any real downloadable sample project for that.

Since I like to have complete examples one can download and try in this post I will show you a sample .NET assembly that illustrates using custom field types in external list. For an introduction to .NET connectivity assembly, I suggest you to read and understand the following article:

Creating .NET Assemblies That Aggregate Data from Multiple External Systems for Business Connectivity Services in SharePoint Server 2010

To understand my code, you should know how we can read BDC metadata from code. The Reading metadata model properties section of the post How do I leverage Secure Store from within my .Net Connectivity Assembly? explains exactly that.

Notice: This goal of this sample only to illustrate the capabilities and the limitations of BCS and external lists. The code example is not intended to be used in production. If you would like to enhance it, you are free to use it at your own risk. You should definitely deal with error handling, concurrency and security issues I would not like to detail here due to lack of time.

Our example is a simple link collection, similar to the standard SharePoint Links list, but in this case our links come from the file system. You should create a local folder on the SharePoint server and copy a few links there from your Favorites folder. The sample code will help to list, read, modify and delete these URL files as well as create new ones from an external list on SharePoint UI.

To make a sample a little bit more exciting we will spice it with some XSL tampering (or let’s call it customization to be politically correct) using SharePoint Designer. It includes using the favicon property of the URL to be displayed next to the link and moving the list item menu to the second field of the view (that is not its standard position for an external list as we will see soon).

The topic of internal structure of URL files as well as reading and writing URL files are really beyond the scope of this post. If you need more info about that visit this page.

As you probably already guessed, the custom field we use in this example will be in this case the built-in (not-so-custom) Hyperlink or Picture field of SharePoint.

The Basics

Let’s see first the LobSytem and Entities sections:

  1. <LobSystems>
  2.   <LobSystem Name="ExternalLinks" Type="DotNetAssembly">
  3.     <LobSystemInstances>
  4.       <LobSystemInstance Name="ExternalLinks">
  5.         <Properties>
  6.           <!– modify the value for the Folder property to match to your local links folder path –>
  7.           <Property Name="Folder" Type="System.String">C:\Data\Temp\SampleLinks</Property>
  8.         </Properties>
  9.       </LobSystemInstance>
  10.     </LobSystemInstances>
  11.     <Entities>
  12.       <Entity Name="ExternalLink" Namespace="ExternalLinks" EstimatedInstanceCount="1000" Version="1.0.0.20">
  13.         <Properties>
  14.           <Property Name="Class" Type="System.String">ExternalLinks.ExternalLinkService, ExternalLinks</Property>
  15.         </Properties>
  16.         <Identifiers>
  17.           <Identifier Name="Title" TypeName="System.String" />
  18.         </Identifiers>

Note, that you should modify the value of the Folder property to match the path of your local links folder created earlier. If you alter the BDC model and deploy multiple times, don’t forget to delete the corresponding external system between as described in my former post.

You can reach the value of the Folder property from code as illustrated below:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.IO;
  6. using IContextProperty = Microsoft.BusinessData.SystemSpecific.IContextProperty;
  7. using Microsoft.BusinessData.MetadataModel;
  8. using Microsoft.BusinessData.Runtime;
  9.  
  10.  
  11. namespace ExternalLinks
  12. {
  13.     public class ExternalLinkService : IContextProperty
  14.     {
  15.  
  16.         #region IContextProperty implementation
  17.  
  18.         public IMethodInstance MethodInstance { get; set; }
  19.  
  20.         public ILobSystemInstance LobSystemInstance { get; set; }
  21.  
  22.         public IExecutionContext ExecutionContext { get; set; }
  23.  
  24.         #endregion
  25.  
  26.         #region BDC parameters
  27.  
  28.         public String FolderPath
  29.         {
  30.             get
  31.             {
  32.                 return (String)LobSystemInstance.GetProperties()["Folder"];
  33.             }
  34.         }
  35.  
  36.         #endregion

We had to reference the Microsoft.BusinessData assembly to make this code work.

The ExternalLink class used to represent our links in this sample is defined as:

  1. namespace ExternalLinks
  2. {
  3.     public partial class ExternalLink
  4.     {
  5.         public String Title { get; set; }
  6.         public SPFieldUrlValue Link { get; set; }
  7.         public String IconUrl { get; set; }
  8.     }
  9. }

Title will be read from the file name of the URL, this value will be the same as the Description property of the Link (type of SPFieldUrlValue), the Url property of the Link and the IconUrl will be read from the content of the URL file.

Note, that we use the SPFieldUrlValue type for the Link value.

We want our IconUrl property to be displayed as Icon field, so we have to add the DefaultDisplayName attribute to the TypeDescriptor.

Finder

The definition of the finder method in the model:

  1. <Method Name="ReadList">
  2.   <Parameters>
  3.     <Parameter Direction="Return" Name="returnParameter">
  4.       <TypeDescriptor TypeName="System.Collections.Generic.IEnumerable`1[[ExternalLinks.ExternalLink, ExternalLinks]]" IsCollection="true" Name="ExternalLinkList">
  5.         <TypeDescriptors>
  6.           <TypeDescriptor TypeName="ExternalLinks.ExternalLink, ExternalLinks" Name="ExternalLink">
  7.             <TypeDescriptors>
  8.               <TypeDescriptor TypeName="System.String" Name="IconUrl" DefaultDisplayName="Icon"/>
  9.               <TypeDescriptor TypeName="System.String" IdentifierName="Title" Name="Title" ReadOnly="true" />
  10.             </TypeDescriptors>
  11.           </TypeDescriptor>
  12.         </TypeDescriptors>
  13.       </TypeDescriptor>
  14.     </Parameter>
  15.   </Parameters>
  16.   <MethodInstances>
  17.     <MethodInstance Type="Finder" ReturnParameterName="returnParameter" Default="true" Name="ReadList" DefaultDisplayName="ExternalLink List" />
  18.   </MethodInstances>
  19. </Method>

The methods defined in the ExternalLinkService class do nothing more than forwarding the calls to the corresponding static utility methods defined in the Utils class:

  1. public IEnumerable<ExternalLink> ReadList()
  2. {
  3.     return Utils.GetItems(FolderPath);
  4. }
  5.  
  6. public ExternalLink ReadItem(String title)
  7. {
  8.     return Utils.GetItem(title, FolderPath);
  9. }
  10.  
  11. public void Update(ExternalLink externalLink)
  12. {
  13.     Utils.SaveChanges(externalLink, FolderPath);
  14. }
  15.  
  16. public void Delete(String title)
  17. {
  18.     Utils.Delete(title, FolderPath);
  19. }
  20.  
  21. public ExternalLink Create(ExternalLink newExternalLink)
  22. {
  23.     return Utils.Create(newExternalLink, FolderPath);            
  24. }

For the ReadList method the work is done by these methods:

  1. public static IEnumerable<ExternalLink> GetItems(String folderPath)
  2. {
  3.     List<ExternalLink> externalLinks = new List<ExternalLink>();
  4.     // check for existing folder
  5.     // if missing, returns an empty list
  6.     // modify the Folder property in the view to match your local folder name
  7.     if (Directory.Exists(folderPath))
  8.     {
  9.         foreach (String path in Directory.GetFiles(folderPath, "*.url"))
  10.         {
  11.             FileInfo fileInfo = new FileInfo(path);
  12.             ExternalLink externalLink = Utils.GetLink(fileInfo, true);
  13.             externalLinks.Add(externalLink);
  14.         }
  15.     }
  16.     return externalLinks;
  17. }
  18.  
  19. private static ExternalLink GetLink(FileInfo fileInfo, bool forList)
  20. {
  21.     String url = GetLinkData("URL", String.Empty, fileInfo);
  22.     // splitting .url extension from the end
  23.     String title = fileInfo.Name.Substring(0, fileInfo.Name.Length – 4);
  24.     String iconUrl = GetLinkData("IconFile", String.Empty, fileInfo);
  25.  
  26.     // some HTML hacking for the list view
  27.     if (forList)
  28.     {
  29.         iconUrl = String.Format("<DIV><a href='{0}' target='_blank'><img title='{1}' alt='{1}' src='{2}' border='0'/></a></DIV>",
  30.                                         url,
  31.                                         title,
  32.                                         String.IsNullOrEmpty(iconUrl) ? "/_layouts/images/icgen.gif" : iconUrl);
  33.     }
  34.  
  35.     return new ExternalLink
  36.     {
  37.         Title = title,
  38.         Link = new SPFieldUrlValue(String.Format("{0}, {1}", url, title)),
  39.         IconUrl = iconUrl
  40.     };
  41. }
  42.  
  43. private static String GetLinkData(String key, String defaultValue, FileInfo fileInfo)
  44. {
  45.     String linkData = defaultValue;
  46.     StringBuilder sb = new StringBuilder(1000);
  47.     uint callResult = GetPrivateProfileString("InternetShortcut", key, defaultValue, sb, (uint)sb.Capacity, fileInfo.FullName);
  48.     if (callResult > 0)
  49.     {
  50.         linkData = sb.ToString();
  51.     }
  52.     return linkData;
  53. }

It is important to note that the GetLink method does some kind of HTML manipulation of data if the link is requested for the finder method (forList is true). It assembles the HTML using the URL of the favicon or the URL of the general icgen.gif if no favicon specified.

Reading and writing the URL file is made by these methods:

  1. [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  2. [return: MarshalAs(UnmanagedType.Bool)]
  3. private static extern bool WritePrivateProfileString(string lpAppName,
  4.    string lpKeyName, string lpString, string lpFileName);
  5.  
  6. [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
  7. private static extern uint GetPrivateProfileString(
  8.    string lpAppName,
  9.    string lpKeyName,
  10.    string lpDefault,
  11.    StringBuilder lpReturnedString,
  12.    uint nSize,
  13.    string lpFileName);

After deployment the list should look like this:

image

If you failed to alter the value of the Folder property in the BDC model to match the path of your local links folder earlier, or if the folder contains no valid URL file, then the list is empty.

As you can see, we do not include the custom Link field in this view, I will give you the answer for this later.

Further things to note:

  • The value of the Icon field is not rendered as HTML, so it is no user-friendly.
  • The item menu is assigned to the Icon field.
  • The list is ordered by the Icon field, not by the Title.

SpecificFinder

The definition of the specific finder method in the model:

  1. <Method Name="ReadItem">
  2.   <Parameters>
  3.     <Parameter Direction="In" Name="title">
  4.       <TypeDescriptor TypeName="System.String" IdentifierName="Title" Name="Title" PreUpdaterField="true" />
  5.     </Parameter>
  6.     <Parameter Direction="Return" Name="returnParameter">
  7.       <TypeDescriptor TypeName="ExternalLinks.ExternalLink, ExternalLinks" Name="ExternalLink">
  8.         <TypeDescriptors>
  9.           <TypeDescriptor TypeName="System.String" IdentifierName="Title" Name="Title" ReadOnly="true" />
  10.           <TypeDescriptor TypeName="Microsoft.SharePoint.SPFieldUrlValue" Name="Link">
  11.             <Properties>
  12.               <Property Name="SPCustomFieldType" Type="System.String">URL</Property>
  13.             </Properties>
  14.           </TypeDescriptor>
  15.           <TypeDescriptor TypeName="System.String" Name="IconUrl" DefaultDisplayName="Icon"/>
  16.         </TypeDescriptors>
  17.       </TypeDescriptor>
  18.     </Parameter>
  19.   </Parameters>
  20.   <MethodInstances>
  21.     <MethodInstance Type="SpecificFinder" ReturnParameterName="returnParameter" Default="true" Name="ReadItem" DefaultDisplayName="Read ExternalLink" />
  22.   </MethodInstances>
  23. </Method>

Note the value of the TypeName attribute for the TypeDescriptor having Name “Link”. It is the full name (namespace and class name) of the type used for the custom field. It should match to the type defined in the entity class (see above the type of the  Link property of the ExternalLink class).

You can define the custom field type for your type in BDC model using the SPCustomFieldType property of the corresponding TypeDescriptor. I should note, that although at the description of the BDC Custom Properties it is stated that “this property has no effect on methods other than the SpecificFinder”, fortunately it is not exact since you can use it in the case of Updater and Creator methods as well, as you will see soon. But it is true, that the value of this field is not displayed when included in the finder method. If you included the field in the finder, the field header would be displayed in the view but column would contain no value. This is why we have not included this field in the finder method.

I feel this limitation rather sad. If you get used to play with the different RenderPattern elements of your custom fields to customize the item rendering in the list view, you can feel your creativity somewhat restrained.

It is important, that you should use the TypeAsString    property of the custom field type class ("URL" in this case) and not the TypeDisplayName  property (would be "Hyperlink or Picture") when setting the value of the SPCustomFieldType property.

What if you want to use a custom field and set its properties before using?

Well, then I think you are in a trouble, as the BDC model provides no declarative way to set custom field properties.

You can try to run this code from a console application:

  1. SPListCollection lists = web.Lists;
  2. SPList extList = lists["ExternalLinks"];
  3. SPFieldCollection fields = extList.Fields;
  4. SPFieldUrl link = (SPFieldUrl)fields["Link"];
  5. link.DisplayFormat = SPUrlFieldFormatType.Image;
  6. link.Update();

But running the code will result an SPException:

The field ‘Link’ cannot be updated. The field is in a field collection that cannot be updated, such as one returned as part of a query.

If you try to play with the SchemaXml property of the field, setting its value will throw an NotSupportedException.

You can probably workaround this (not yet tested) via inheriting your own custom control from the specific custom control and set properties to the desired values in your class. For example, you can derive an ImageLink custom field type from SPFieldUrl and have its default  DisplayFormat set to Image. But creating and deploying a custom field for every and each customization request is rather painful.

For the ReadItem method specified for the SpecificFinder BDC method type the work is done by the GetItem method:

  1. public static ExternalLink GetItem(String title, String folderPath)
  2. {
  3.     ExternalLink externalLink = null;
  4.     String path = String.Format(@"{0}\{1}.url", folderPath, title);
  5.  
  6.     if (File.Exists(path))
  7.     {
  8.         FileInfo fileInfo = new FileInfo(path);
  9.         externalLink = Utils.GetLink(fileInfo, false);
  10.     }
  11.  
  12.     return externalLink;
  13. }

Displaying a link will be similar to this image, assuming there is a favicon specified in the URL.

image 

Things to note:

  • The value of the Icon field is more user-friendly than in the case of the list view (finder method). This is due to the little trick with the forList parameter in the GetLink method.
  • The caption of the Title and Link fields are the same.

To tell the truth I’ve tried to remove the Title field from this view but without success. I found no declarative nor programmatic way for that.

For example, let’s see the following code:

  1. SPListCollection lists = web.Lists;
  2. SPList extList = lists["ExternalLinks"];
  3. SPFieldCollection fields = extList.Fields;
  4. SPField title = fields["Title"];
  5. title.ShowInViewForms = false;
  6. title.Update();

But running this code will result an NotSupportedException:

Setting property ‘ShowInViewForms’ to value ‘False’ is not supported on Microsoft.SharePoint.SPFieldText for external lists.

Updater

The updater method is defined like this:

  1. <Method Name="Update">
  2.   <Parameters>
  3.     <Parameter Name="externalLink" Direction="In">
  4.       <TypeDescriptor Name="ExternalLink" TypeName="ExternalLinks.ExternalLink, ExternalLinks">
  5.         <TypeDescriptors>
  6.           <TypeDescriptor TypeName="System.String" IdentifierName="Title" Name="Title" PreUpdaterField="true" />
  7.           <TypeDescriptor Name="Link" TypeName="Microsoft.SharePoint.SPFieldUrlValue" UpdaterField="true">
  8.             <Properties>
  9.               <Property Name="SPCustomFieldType" Type="System.String">URL</Property>
  10.             </Properties></TypeDescriptor>
  11.           <TypeDescriptor Name="IconUrl" TypeName="System.String" DefaultDisplayName="Icon" UpdaterField="true" />
  12.         </TypeDescriptors>
  13.       </TypeDescriptor>
  14.     </Parameter>
  15.   </Parameters>
  16.   <MethodInstances>
  17.     <MethodInstance Name="Update" Type="Updater" />
  18.   </MethodInstances>
  19. </Method>

The following methods in the Utils class will do the job for the Update method:

  1. public static void SaveChanges(ExternalLink externalLink, String folderPath)
  2. {
  3.     String path = String.Format(@"{0}\{1}.url", folderPath, externalLink.Title);
  4.  
  5.     if (File.Exists(path))
  6.     {
  7.         FileInfo fileInfo = new FileInfo(path);
  8.         String newUrl = externalLink.Link.Url;
  9.         String newTitle = externalLink.Link.Description;
  10.  
  11.         SetLinkData("URL", newUrl, path);
  12.         SetLinkData("IconFile", externalLink.IconUrl, path);
  13.  
  14.         // Title property set by renaming
  15.         if (externalLink.Title != newTitle)
  16.         {
  17.             String newPath = String.Format(@"{0}\{1}.url", fileInfo.Directory, newTitle);
  18.  
  19.             // move only if there is no file with that name
  20.             if (!File.Exists(newPath))
  21.             {
  22.                 fileInfo.MoveTo(newPath);
  23.             }
  24.         }
  25.     }
  26. }
  27.  
  28. private static void SetLinkData(String key, String value, String path)
  29. {
  30.     WritePrivateProfileString("InternetShortcut", key, value, path);
  31. }

In the SaveChanges method we change the filename itself (that means the Title field used as the identifier in our model) if the Description of the Link is changed. If there is already a file with the new name, the Title is not changed and it means that the Descriprion remains the same as well.

image

Note that the Title field is hidden due to the PreUpdaterField="true" setting in the TypeDescriptor.

Deleter

The deleter method is the simplest one:

  1. <Method Name="Delete">
  2.   <Parameters>
  3.     <Parameter Name="title" Direction="In">
  4.       <TypeDescriptor Name="Title" TypeName="System.String" IdentifierEntityName="ExternalLink" IdentifierEntityNamespace="ExternalLinks" IdentifierName="Title" /></Parameter>
  5.   </Parameters>
  6.   <MethodInstances>
  7.     <MethodInstance Name="Delete" Type="Deleter" />
  8.   </MethodInstances>
  9. </Method>

And in the Utils class:

  1. public static void Delete(String title, String folderPath)
  2. {
  3.     String path = String.Format(@"{0}\{1}.url", folderPath, title);
  4.  
  5.     if (File.Exists(path))
  6.     {
  7.         File.Delete(path);
  8.     }
  9. }

Creator

The last method defined is the creator:

  1. <Method Name="Create">
  2.   <Parameters>
  3.     <Parameter Name="returnExternalLink" Direction="Return">
  4.       <TypeDescriptor Name="ReturnExternalLink" TypeName="ExternalLinks.ExternalLink, ExternalLinks">
  5.         <TypeDescriptors>
  6.           <TypeDescriptor Name="Title" IdentifierName="Title" ReadOnly="true" TypeName="System.String" />
  7.           <TypeDescriptor Name="Link" TypeName="Microsoft.SharePoint.SPFieldUrlValue">
  8.             <Properties>
  9.               <Property Name="SPCustomFieldType" Type="System.String">URL</Property>
  10.             </Properties></TypeDescriptor>
  11.           <TypeDescriptor Name="IconUrl" TypeName="System.String" />
  12.         </TypeDescriptors>
  13.       </TypeDescriptor>
  14.     </Parameter>
  15.     <Parameter Name="newExternalLink" Direction="In">
  16.       <TypeDescriptor Name="NewExternalLink" TypeName="ExternalLinks.ExternalLink, ExternalLinks">
  17.         <TypeDescriptors>
  18.           <TypeDescriptor Name="Title" IdentifierName="Title" PreUpdaterField="true" TypeName="System.String" CreatorField="false" />
  19.           <TypeDescriptor Name="Link" TypeName="Microsoft.SharePoint.SPFieldUrlValue" CreatorField="true">
  20.             <Properties>
  21.               <Property Name="SPCustomFieldType" Type="System.String">URL</Property>
  22.             </Properties></TypeDescriptor>
  23.           <TypeDescriptor Name="IconUrl" TypeName="System.String" CreatorField="true" />
  24.         </TypeDescriptors>
  25.       </TypeDescriptor>
  26.     </Parameter>
  27.   </Parameters>
  28.   <MethodInstances>
  29.     <MethodInstance Name="Create" Type="Creator" ReturnParameterName="returnExternalLink" ReturnTypeDescriptorPath="ReturnExternalLink" />
  30.   </MethodInstances></Method>

The Create method is used for URL file creation in the back end:

  1. public static ExternalLink Create(ExternalLink newExternalLink, String folderPath)
  2. {
  3.     String newUrl = newExternalLink.Link.Url;
  4.     String newTitle = newExternalLink.Link.Description;
  5.  
  6.     String path = String.Format(@"{0}\{1}.url", folderPath, newTitle);
  7.  
  8.     SetLinkData("URL", newUrl, path);
  9.     SetLinkData("IconFile", newExternalLink.IconUrl, path);
  10.  
  11.     ExternalLink createdExternalLink = new ExternalLink
  12.     {
  13.         Title = newExternalLink.Link.Description,
  14.         Link = newExternalLink.Link,
  15.         IconUrl = newExternalLink.IconUrl,
  16.     };
  17.     return createdExternalLink;
  18. }

In this method we should return an ExternalLink entity based on the new URL file.

image

Note that the Title field is hidden due to the ReadOnly="true" setting in the TypeDescriptor.

Deploying the External List

The solution contains a list instance for our sample as well:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  3.   <ListInstance Title="ExternalLinks"
  4.                 OnQuickLaunch="TRUE"
  5.                 TemplateType="600"
  6.                 FeatureId="00BFEA71-9549-43f8-B978-E47E54A10600"
  7.                 Url="Lists/ExternalLinks"
  8.                 Description="Sample external list">
  9.     <DataSource>
  10.       <Property Name="LobSystemInstance" Value="ExternalLinks" />
  11.       <Property Name="EntityNamespace" Value="ExternalLinks" />
  12.       <Property Name="Entity" Value="ExternalLink" />
  13.       <Property Name="SpecificFinder" Value="ReadItem" />
  14.     </DataSource>
  15.   </ListInstance>
  16. </Elements>

For the details of deploying an external list from Visual Studio 2010 solutions you can read more in this post of Yaroslav Pentsarskyy.

Final Modifications

We completed a lot of things, but you may remember that our view created based on the Finder method is not really user-friendly yet. It is time to correct that.

Start SharePoint Designer, and on the Navigation bar (on the left) click on  List and Libraries then select our external list called ExternalLinks on the right bottom side in the External Lists group.

Open the ExternalLink List view by double-clicking on it.

image

In the code view look for this code snippet:

  1. <Query>
  2.   <OrderBy>
  3.     <FieldRef Name="IconUrl"/>
  4.   </OrderBy>
  5. </Query>
  6. <ViewFields>
  7.   <FieldRef Name="IconUrl" ListItemMenu="TRUE" LinkToItem="TRUE"/>
  8.   <FieldRef Name="Title"/>
  9. </ViewFields>

Alter it as illustrated below to change the ordering and to add item context menu to Title field:

  1. <Query>
  2.   <OrderBy>
  3.     <FieldRef Name="Title"/>
  4.   </OrderBy>
  5. </Query>
  6. <ViewFields>
  7.   <FieldRef Name="IconUrl"/>
  8.   <FieldRef Name="Title" ListItemMenu="TRUE" LinkToItem="TRUE"/>
  9. </ViewFields>

Note that even we set the menu to Title, the first field (IconUrl) will still have the menu.

In the design view, select the content of the Icon field cell. If you selected correctly, you should see the <xsl:value-of> tag. Select the Edit Tag… option.

image

Add the disable-output-escaping="yes" attribute (be sure to use standard quotation marks!) to the tag as shown below and apply the changes:

image

You can read more about the purpose of this action here.

Save the changes you made on the page, and refresh the view in the browser.

image

Voila.

The favicon is shown where it is specified, where it is missing, a standard icon is shown.

When you position the mouse over the icon, the Title of the item is shown as a tooltip.

By clicking the link you open the target URL in a new browser window. The item context menu is bound to the Title, and the items are ordered by the Title as well.

BTW, if you need to change only the order, of course you don’t need to use SPD, simply modify the view on the SharePoint UI.

That is for today, and sorry for the long post. I hope it was worth reading so much and I plan to be back soon with another – bit more complex – BCS sample. You can download this sample from here (including the URL files used in the post).

Lessons learned when working on this sample:

You can use the built-in and custom field types in your BDC model. When setting the SPCustomFieldType property for the TypeDescriptor you should use the TypeAsString property of the custom field type. Use the corresponding field value type for these fields in the BDC model.

Using custom field types is not supported in the Finder views of the external lists.

The custom field support is limited another way also: you cannot set properties of the custom field declaratively, for example from your BDC model. Seems that there is no trivial workaround for this issue.

There are further developer oriented limitations when working with external lists, for example you cannot hide fields on specific view types, like edit or display view.

If you would like to rename your field on the UI, you should apply the DefaultDisplayName attribute with the same value to all instances of the same TypeDescriptor in the model file. If you set it for example only in the Finder method, it seems to have no effect. In this case the Name attribute is displayed.

You can display different values for the same item in the same field in different views (like Finder(s), SpecificFinder).

You can enable HTML rendering by altering the XSL of the XsltListViewWebPart in the .aspx file generated for the view using SPD (or custom code, not covered in this post). It requires circumspection as making HTML errors (like missing closing tags or extra quotes) may cause your view to be rendered totally wrong on the external list UI.

You can set the ordering field both by altering the views on the SharePoint UI or via XSL modification in SPD.

By default, the item menu is linked to the first field in the Finder view, but you can alter that by modifying the rendering XSL using SPD (or custom code, not covered in this post).

LobSystemInstance properties provide a great way to configure your BDC application. You can access these properties even from managed code.

You can alter even the value of the field you used as the identifier of your entity model.

You can deploy your external list declaratively from your Visual Studio solution.

13 Comments »

  1. […] Second Life of a Hungarian SharePoint Geek If your sword is too short, take one step forward « External list example demonstrating .NET connectivity assembly and custom field type […]

    Pingback by Publishing files stored in the file system through external list « Second Life of a Hungarian SharePoint Geek — September 19, 2010 @ 07:48

  2. Hi,Peter
    Your article is great! I have a question,my bdc modle has a Enmu Field,
    I want to display it as dropdownlist ,I need to write a custom field type?
    Or could I use the “Choice” type which built in sharepoint? if can use and how to use that? Thank for your help,I am waiting your reply.

    Comment by ychae — September 21, 2010 @ 04:52

  3. Hi,Peter
    I found this method”
    internal static SPField CreateSPFieldFromEntityField(SPFieldCollection fldcoll, int ifldSPList)” in the Reflector,Excepting custom field type and association field,there only :
    “SPFieldBoolean”:
    “SPFieldDateTime”:
    “SPFieldGuid”:
    “SPFieldInteger”:
    “SPFieldDecimal”:
    “SPFieldNumber”:
    “SPFieldText”:
    “SPFieldUrl”:

    Comment by ychae — September 21, 2010 @ 05:29

    • Hi,

      Your observation is similar to what I’ve experienced when tried to add other built-in field types (like Note a.k.a multiline text) to my external list BDC model. I’ve planed to do a bit more investigation on that and create a post later, but to make it short I’m sharing my experience now.

      I’ve received the following strange exception on the UI (FieldSA is a private property of SPField having only getter and type of SPChunked2DObjectArray):

      Server Error in '/' Application.
      --------------------------------------------------------------------------------

      Property 'FieldsSA' is not supported on Microsoft.SharePoint.SPFieldMultiLineText for external lists.
      Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

      Exception Details: System.NotSupportedException: Property 'FieldsSA' is not supported on Microsoft.SharePoint.SPFieldMultiLineText for external lists.

      Source Error:

      An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

      Stack Trace:

      [NotSupportedException: Property 'FieldsSA' is not supported on Microsoft.SharePoint.SPFieldMultiLineText for external lists.]
      Microsoft.SharePoint.SPExternalList.ThrowNotSupportedExceptionForProperty(String sPropertyName, Boolean fInPropertyGetter, Type typeThrowing) +25368444
      Microsoft.SharePoint.SPField.get_FieldsSA() +59
      Microsoft.SharePoint.SPField.GetFieldBoolValue(String attrName, Int32 columnNumberInSA) +72
      Microsoft.SharePoint.WebPartPages.XsltListViewWebPart.FillInFieldsDictionary(XmlNode viewField, SPList list, Dictionary`2 fieldsDictionary, Dictionary`2 computerdFieldsDictionary) +214
      Microsoft.SharePoint.WebPartPages.XsltListViewWebPart.GetDataSource() +4706
      Microsoft.SharePoint.WebPartPages.BaseXsltListWebPart.AddDataSourceControls() +21
      Microsoft.SharePoint.WebPartPages.DataFormWebPart.CreateChildControls() +575
      Microsoft.SharePoint.WebPartPages.BaseXsltListWebPart.CreateChildControls() +2189
      Microsoft.SharePoint.WebPartPages.WebPartMobileAdapter.CreateChildControls() +72
      System.Web.UI.Control.EnsureChildControls() +132
      System.Web.UI.Control.PreRenderRecursiveInternal() +61
      System.Web.UI.Control.PreRenderRecursiveInternal() +224
      System.Web.UI.Control.PreRenderRecursiveInternal() +224
      System.Web.UI.Control.PreRenderRecursiveInternal() +224
      System.Web.UI.Control.PreRenderRecursiveInternal() +224
      System.Web.UI.Control.PreRenderRecursiveInternal() +224
      System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3394

      If this limitation is real (not a bad joke only), it is very annoying (again).

      Peter

      Comment by Peter Holpar — September 21, 2010 @ 10:33

      • I confirm this limitation. I got the same exception when I tried to use Note field as the SPCustomFieldType property. To create a multiline text field I was forced to develop a custom field type derived from SPFieldText which uses standard control derived from BaseTextField. Unfortunatelly as I mentioned in one of posts below I’m facing a problem with my custom DateOnly field. Let me paste the description from msdn forum:

        [Description]
        I created BcsDateOnly custom field type, which works perfectly on standard list and libraries. I’ve applied it to the typedescriptor of one of System.DateTime fields using BcsDateOnly. The field is being rendered properly on new form, edit form, however I’ve noticed several issues:

        – every add / update operation invoked after pressing the Save button fails because of conflicts Data Source Conflict: “Your changes conflict with those made concurrently by another user”

        – field on view form fails to render with message that BCS Date Only Field throws some internal exception

        To solve the first issue I’ve played with and NormalizeDateTime settings as I realized that maybe data is normalized somehow and during form validation it raises a data source conflict somehow… without success.

        To isolate a problem I made a trick and I replaced all occurences of BcsDateOnly in my bdcm file to DateTime 🙂 I redeployed the model, recreated the web site and external lists. I’ve observed that my custom field / control is replaced now by standard DateTime control, however add / update operations still raises Data Source Conflict: “Your changes conflict with those made concurrently by another user”.

        This is really strange and looks like a external lists bug. Removing the DateTime completely changed nothing in UI, however add / update starts to work.

        I’ve confirmed such behaviour on many fields. My last observation was, that if I use SPCustomFieldType (could be mine BcsDateOnly or standard DateTime) the values are normalized somehow and there is an 2h hours offset comparing to my database. Maybe this is a reason of conflict.

        Any ideas how to solve this problem?

        [/Description]

        Comment by Greg — June 19, 2011 @ 19:00

  4. Hi,
    I build a custom DropDownList Field,I use SPFieldText as the base class,
    In CreateChildControls() the control connects sql server to get items which can bind to dropdownlist,It runs very well.So I think that way is easier than using built-in field types.

    public class DropDownListField : SPFieldText
    {….}
    public class DropDownListFieldControl : BaseFieldControl
    {
    protected DropDownList DDL;
    protected Label L;
    ….
    public override object Value
    {
    get
    {
    EnsureChildControls();
    return DDL.SelectedItem.Text+”#”+DDL.SelectedItem.Value;
    }
    set
    {
    EnsureChildControls();
    base.Value = (String)value;
    }
    }
    protected override void CreateChildControls()
    {
    if (this.Field != null)
    {
    base.CreateChildControls();

    this.DDL = (DropDownList)TemplateContainer.FindControl(“DropDownListControl”);
    this.L = (Label)TemplateContainer.FindControl(“DropDownListControlForDisplay”);

    if (this.ControlMode != SPControlMode.Display)
    {
    if (!this.Page.IsPostBack)
    {
    if (this.ControlMode == SPControlMode.New)
    {
    BindDLL(this.FieldName);
    }
    if (this.ControlMode == SPControlMode.Edit)
    {
    BindDLL(this.FieldName,GetSelectValue(this.ItemFieldValue.ToString()));
    }

    }

    }
    else
    {
    L.Text = (String)this.ItemFieldValue;
    }

    }

    }

    private string GetSelectValue(string itemValue)
    {
    return itemValue.Split(‘#’)[1].ToString();
    }

    protected List GetDDLItems(string typeName)
    {
    List items = new List();
    string connect = “…..”;
    using (SqlConnection conn = new SqlConnection(connect))
    {

    SqlDataAdapter ada = new SqlDataAdapter(“select * from DropDownList where TypeName ='” + typeName + “‘”, conn);
    DataSet ds = new DataSet();
    ada.Fill(ds);
    if (ds.Tables.Count > 0)
    {
    foreach (DataRow dr in ds.Tables[0].Rows)
    {
    ListItem li = new ListItem(dr[“Value”].ToString(),dr[“OId”].ToString());
    items.Add(li);

    }
    }
    }
    return items;
    }

    protected void BindDLL(string typeName)
    {
    this.DDL.Items.Clear();
    List items = GetDDLItems(typeName);
    foreach (ListItem li in items)
    {
    this.DDL.Items.Add(li);
    }
    }
    protected void BindDLL(string typeName, string selectValue)
    {
    this.DDL.Items.Clear();
    List items = GetDDLItems(typeName);
    foreach (ListItem li in items)
    {
    if (selectValue == li.Value)
    li.Selected = true;
    this.DDL.Items.Add(li);
    }
    }

    }

    The ReadList Use this field”Message” as string:

    The ReadItem Use this field”Message” as string,but SPCustomFieldType is DropDownList

    DropDownList

    Comment by ychae — September 21, 2010 @ 11:11

  5. Hi,

    Thanks for sharing your code! Using built-in controls is troublesome anyway due to the limitation there is no way (at least I have not found any yet) to set properties for the field / control, as written in the post above. So one can not choose wheter the field would be displayed as “Hyperlink or Picture”. The default values are applied.

    Peter

    Comment by Peter Holpar — September 21, 2010 @ 11:20

  6. […] By Peter Holpar When you would like to customize the XSL of the external list either using SharePoint Designer 2010 or via deploying your custom XSL from a Visual Studio 2010 solution, it is always useful to know […]

    Pingback by Creating the customization XSL for your external list « Second Life of a Hungarian SharePoint Geek — October 1, 2010 @ 22:30

  7. I’m curious if you were able to get the SPFieldUrlValue to render properly on the list view (not the item view)? I am trying to get it to show in both and currently it only renders properly (that is, showing the title linked) in the item – in the list view it renders a blank value.

    Comment by Colin Bowern — December 14, 2010 @ 22:32

    • Hi Colin,

      Your finding matches my one. As I wrote, using the custom fields in the list view (in the finder method) is not supported and based on my experience it simply doesn’t work as one would expect:

      “If you included the field in the finder, the field header would be displayed in the view but column would contain no value.”

      Peter

      Comment by Peter Holpar — December 21, 2010 @ 22:05

      • In my case I’ve managed to define rendering pattern on xsltlistviewwebpart via custom xslt deployed into 14/Layouts/XSL. I’ve use default fldtypes.xsl as the template and created rendering template for my SPCustomFieldType BcsDateOnly field.

        You can match fields also by FieldRef[@Name='<>’].

        Although the solution above works perfectly, I’m facing another problem with my custom field type derived from the standard datetime field – I’ll share my experience in that area soon.

        Comment by Greg — June 19, 2011 @ 18:33

  8. Hi Peter
    i have a question of crawl blob files via BCS.
    i write a DotnetAssembly model to do this job.
    but i just search .txt file content, others, like .docx .pptx, the indexer cannot read theirs content by right ifilter, it seems the indexer read all files only by txt ifilter.
    may i send a email to you?

    Comment by Eric — March 18, 2011 @ 04:35

  9. I’m not being able to find a other article about using custom fields in BCS like your so in-depth.

    Thanks a lot.

    Comment by alramacciotti — May 26, 2011 @ 16:38


RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.