SharePoint: How to make error messages more detailed

December 21st, 2011 No comments

    If an unexpected error occurs on a SharePoint environment, by default, you get a meaningless error message as the following: An unexpected error has occurred. Despite the given error message is considered as friendly one for users, we as developers want to have more information to detect the reason. SharePoint is built on ASP.Net technology, it means we can enable the detailed error message and, in addition, displaying of call-stack by changing some settings in a web.config file. We need to find the node customErrors and change its mode to “Off”, which specifies that custom errors are disabled and the detailed errors are shown to the local and remote clients. Then we need to find the node SafeMode and change its CallStack to “true”. CallStack attribute defines whether a call-stack and an exception message are displayed when a system-level exception takes place while ASP.NET processes a request from the local and remote clients. The last step is to save the made changes. The nodes we are interested in are shown below:

Before:

<customErrors mode="On" />
<SafeMode MaxControls="200" CallStack="false" ... >

After:

<customErrors mode="Off" />
<SafeMode MaxControls="200" CallStack="true" ... >

The required web.config file containing main settings locates in \inetpub\wwwroot\wss\VirtualDirectories\<SHAREPOINT APP PORT NUMBER>. For SharePoint 2007 it’s usually enough to make changes in the given file only. As for SharePoint 2010, the changes also should be applied to a number of additional web.config files in 14 hive (\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14) of SharePoint 2010. If you make a search for web.config inside the 14 folder you likely will find more than 30 different files. I recommend firstly to make changes in web.config files locating in \14\TEMPLATE\ADMIN and \14\TEMPLATE\LAYOUTS and then check whether you still get not detailed error message. If so, continue making the changes file by file until the message will get more detailed.

Related posts:

SharePoint 2010: Some issues during solution package deploying

December 17th, 2011 No comments

    Deploying a .wsp-file on SharePoint 2010, you may stumble over the next error message:
Install-SPSolution : Admin SVC must be running in order to create deployment timer job.

How have I got it? In SharePoint 2010 Management Shell I had successfully performed the following command to upload my SP2010 solution package into the system:

Add-SPSolution "C:\WSPDeployment\My2010Solution.wsp"

Then I had tried to run the next one to deploy the uploaded solution:

Install-SPSolution -Identity My2010Solution.wsp -GACDeployment -AllWebApplications

and after that I received the mentioned error.

Now, how to fix it. It’s needed to go to Administrative Tools -> Services and manually start the service called SharePoint 2010 Administration. Not to have such error in the future, change service’s Startup type to Automatic.

SharePoint 2010 Administration service

Having made this, I run Install-SPSolution once again and received a new error:
A deployment or retraction is already under way for the solution “My2010Solution.wsp”, and only one deployment or retraction at a time is supported.

It looked like an appropriate deployment job was added to the list of timer jobs, but the attempt to execute the job failed due to unstarted SharePoint 2010 Administration service. I found a solution here. We need to cancel the deployment job by performing the next steps:

  1. Grab the id of the job by running the command: stsadm -o enumdeployments. Yep, the old friend, stsadm helps us out ๐Ÿ™‚ Look through the result, find the required deployment job and grab the id. The result should look like
    C:\...red\Web Server Extensions\14\BIN>stsadm -o enumdeployments
    
    <Deployments Count="1">
       <Deployment JobId="2de92a5f-6c70-4325-ac2c-293a34dd9c67">
          <Title>Microsoft SharePoint Foundation Solution Deployment for "My2010Solution.wsp"</Title>
          <Type>Deployment</Type>
          <State>Pending</State>
          <Schedule>12/16/2011 3:47 PM</Schedule>
          <File>My2010Solution.wsp</File>
          <ServerType>Front-end Web server</ServerType>
          <Target>http://mySPServer/</Target>
       </Deployment>
    </Deployments>
    
  2. Using the found id, cancel the job by means of the command stsadm-o canceldeployment-id โ€œfound Job idโ€. For example, in my case it’s
    stsadm -o canceldeployment -id "2de92a5f-6c70-4325-ac2c-293a34dd9c67"

    If it succeed you get “Operation completed successfully” message.

  3. Run again stsadm -o enumdeployments to make sure that the given deployment job disappeared

Ok, now you can repeat deployment of your solution, run Install-SPSolution again. The issues was overcome in my case.

SharePoint: How to get value from BDC

December 13th, 2011 No comments

    I’ve implemented a few classes to simplify an access to BDC meta data and values from external data sources. By means of these classes you can make request for values from external data source, using a BDC Entity Instance identifier(s) or a value of a certain BDC Entity field.

The root class BDCMetaRequest allows to interact with BDC as a meta data store. The main goal of the BDCMetaRequest is to get a meta descriptor of a certain external data type registered in BDC. The found descriptor is an object of the Entity class and can be accessed through the FoundEntity property.

A base abstract class BDCRequest derived from BDCMetaRequest contains some general members responsible for searching the external data item that meets criteria and for fetching any field value out from the found item. It’s supposed that the criteria and criteria-specific search implementation are encapsulated inside derived classes. The BDCRequest supplies the following important members:

  • FoundEntityInstance returns the external data item that meets criteria. In terms of BDC the found data item is a BDC Entity Instance;
  • GetBdcEntityInstance returns the found data item as DataTable with one row. The row represents either the record found in database or the object returned by a Web service;
  • GetBdcEntityInstanceFieldValue returns a value of a certain field. The field value is being extracted from the found data item;

*Note that all classes described in this post actively use the lazy loading, so the searching for external data item will be performed only if it hasn’t been found before.

As I mentioned above, the derived classes BDCRequestById and BDCRequestByValue are responsible for different search criteria and criteria-specific search implementation. The BDCRequestById accepts the identifier(s) of a sought-for BDC Entity Instance in the form of an encoded string or array of objects. While the BDCRequestByValue accepts a value, which will be applied to the first available BDC Entity filter (WildcardFilter or ComparisonFilter). BDCRequestByValue acts just as the user who, interacting with Picker Dialog, chooses the filter, types the required value in the proper text field and presses the Search button (see picture below).

Sample of Dialog Picker

Ok, below is the classes and a digram of them:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Portal.WebControls;
using Microsoft.Office.Server.ApplicationRegistry.Infrastructure;
using Microsoft.Office.Server.ApplicationRegistry.MetadataModel;
using Microsoft.Office.Server.ApplicationRegistry.Runtime;
using Microsoft.Office.Server;
using System.Data;

namespace BDC
{
    /// <summary>
    /// Abstracts an interaction with BDC meta data
    /// </summary>
    public class BDCMetaRequest
    {
        #region fields & properties
        protected string _lobSystemInstanceName = null;
        protected string _bdcEntityName         = null;

        protected LobSystemInstance _lobSystemInstance = null;
        protected Entity            _entity            = null;

        /// <summary>
        /// Name of the requested Lob System Instance
        /// </summary>
        public string RequestedLobSystemInstanceName
        {
            get { return _lobSystemInstanceName; }
        }
        /// <summary>
        /// Name of the requested external data type
        /// </summary>
        public string RequestedEntityName
        {
            get { return _bdcEntityName; }
        }
        /// <summary>
        /// Found meta descriptor of external data type 
        /// </summary>
        public virtual Entity FoundEntity
        {
            get { return _entity == null ? (_entity = GetEntity()) : _entity; }
        }
        /// <summary>
        /// Found Lob System Instance
        /// </summary>
        public virtual LobSystemInstance FoundLobSystemInstance
        {
            get { return _lobSystemInstance == null ? (_lobSystemInstance = GetLobSystemInstance()) : _lobSystemInstance; }
        }
        #endregion

        #region public methods
        /// <summary>
        /// Initializes a new instance of the BDCMetaRequest class
        /// </summary>
        /// <param name="lobSysInstanceName">Name of the requested Lob System Instance</param>
        /// <param name="bdcEntityName">Name of the requested external data type</param>        
        public BDCMetaRequest(string lobSysInstanceName, string bdcEntityName)
        {
            // check if lobSysInstanceName isn't NULL or empty string
            EnsureParamIsNotNullOrEmpty("lobSysInstanceName", lobSysInstanceName);
            // check if bdcEntityName isn't NULL or empty string
            EnsureParamIsNotNullOrEmpty("bdcEntityName", bdcEntityName);

            _lobSystemInstanceName = lobSysInstanceName;
            _bdcEntityName         = bdcEntityName;
        }
        /// <summary>
        /// Initializes a new instance of the BDCMetaRequest class
        /// </summary>
        /// <param name="businessDataField">SharePoint field of BusinessData type</param>        
        public BDCMetaRequest(BusinessDataField businessDataField)
        {
            // check if businessDataField isn't NULL
            EnsureParamIsNotNull("businessDataField", businessDataField);

            _lobSystemInstanceName = businessDataField.SystemInstanceName;
            _bdcEntityName         = businessDataField.EntityName;
        }
        #endregion

        #region internal methods
        /// <summary>
        /// Gets the Entity object from BDC that meets criteria
        /// </summary>
        /// <returns>Found BDC Entity</returns>
        protected Entity GetEntity()
        {
            // get BDC Entity from Lob System Instance by name
            return FoundLobSystemInstance.GetEntities()[RequestedEntityName];
        }
        /// <summary>
        /// Gets the LobSystemInstance from BDC that meets criteria
        /// </summary>
        /// <returns>Found LobSystemInstance</returns>
        protected LobSystemInstance GetLobSystemInstance()
        {
            // get Lob System Instance by name
            return ApplicationRegistry.GetLobSystemInstanceByName(RequestedLobSystemInstanceName);
        }
        /// <summary>
        /// Throws an exception if the passed parameter value is NULL or empty string
        /// </summary>
        /// <param name="paramName">Parameter name</param>
        /// <param name="value">Parameter value</param>
        protected static void EnsureParamIsNotNullOrEmpty(string paramName, string value)
        {
            if (string.IsNullOrEmpty(value))
                throw new ArgumentException(string.Format("{0} must not be NULL or empty!", paramName), paramName);
        }
        /// <summary>
        /// Throws an exception if the passed parameter value is NULL
        /// </summary>
        /// <param name="paramName">Parameter name</param>
        /// <param name="value">Parameter value</param>
        protected static void EnsureParamIsNotNull(string paramName, object value)
        {
            if (value == null)
                throw new ArgumentNullException(paramName, string.Format("{0} must not be NULL!", paramName));
        }
        #endregion
    }

    /// <summary>
    /// Abstracts an interaction with BDC
    /// </summary>
    public abstract class BDCRequest : BDCMetaRequest
    {
        protected bool            _formattedValues = false;
        protected IEntityInstance _iEntityInstance = null;
        
        /// <summary>
        /// Indicates if result values will be formatted
        /// </summary>
        public bool FormattedValues
        {
            get { return _formattedValues; }
            set { _formattedValues = value; }
        }

        /// <summary>
        /// Gets found data item from external datasource
        /// </summary>
        public IEntityInstance FoundEntityInstance
        {
            get { return _iEntityInstance == null ? (_iEntityInstance = GetEntityInstance()) : _iEntityInstance; }
        }

        /// <summary>
        /// Initializes a new instance of the BDCRequest class
        /// </summary>
        /// <param name="lobSysInstanceName">Name of the Lob System Instance</param>
        /// <param name="bdcEntityName">Type name of returned Business Data Object</param>
        /// <remarks>This constructor is called by derived class constructors</remarks>
        public BDCRequest(string lobSysInstanceName, string bdcEntityName) : base(lobSysInstanceName, bdcEntityName)
        {            
        }
        /// <summary>
        /// Initializes a new instance of the BDCRequest class
        /// </summary>
        /// <param name="businessDataField">SharePoint field of BusinessData type</param>
        /// <remarks>This constructor is called by derived class constructors</remarks>
        public BDCRequest(BusinessDataField businessDataField) : base(businessDataField)
        {            
        }

        /// <summary>
        /// Returns a DataTable with one row, which represents either a record from a database or an object returned by a Web service
        /// </summary>
        /// <returns>DataTable with one row as the found data item from external data source</returns>
        public virtual DataTable GetBdcEntityInstance()
        {
            IEntityInstance entityInstance = FoundEntityInstance;
            return FormattedValues ? entityInstance.EntityAsFormattedDataTable : entityInstance.EntityAsDataTable;
        }
        /// <summary>
        /// Returns a value of certain field of the data item found in external data source
        /// </summary>
        /// <param name="bdcEntityFieldName">Field name</param>
        /// <returns>Field value</returns>
        public virtual object GetBdcEntityInstanceFieldValue(string bdcEntityFieldName)
        {
            if (!string.IsNullOrEmpty(bdcEntityFieldName))
            {
                IEntityInstance entityInstance = FoundEntityInstance;
                return GetFieldValue(entityInstance, bdcEntityFieldName);
            }
            return null;
        }

        /// <summary>
        /// Gets BDC Entity Instance that meets criteria
        /// </summary>
        /// <returns>Found BDC Entity Instance</returns>
        protected abstract IEntityInstance GetEntityInstance();

        /// <summary>
        /// Gets a value of certain field of BDC Entity Instance
        /// </summary>
        /// <param name="entityInstance">BDC Entity Instance</param>
        /// <param name="bdcEntityFieldName">Field name</param>
        /// <returns>Field value</returns>
        protected virtual object GetFieldValue(IEntityInstance entityInstance, string bdcEntityFieldName)
        {
            foreach (Field field in entityInstance.ViewDefinition.Fields)
                if (bdcEntityFieldName.Equals(field.Name, StringComparison.OrdinalIgnoreCase) ||
                   bdcEntityFieldName.Equals(field.DefaultDisplayName, StringComparison.OrdinalIgnoreCase) ||
                   (field.ContainsLocalizedDisplayName && bdcEntityFieldName.Equals(field.LocalizedDisplayName, StringComparison.OrdinalIgnoreCase)))
                    return FormattedValues ? entityInstance.GetFormatted(field.Name) : entityInstance[field.Name];
            return null;
        }        
    }

    /// <summary>
    /// Represents search of data item in external data source, using identifier(s)
    /// </summary>
    public class BDCRequestById : BDCRequest
    {
        protected string _bdcEntityInstanceEncodedId = null;
        protected object[] _bdcEntityInstanceIds = null;

        /// <summary>
        /// Encoded identifier(s) of data item
        /// </summary>
        public string BdcEntityInstanceEncodedId
        {
            get { return _bdcEntityInstanceEncodedId; }
        }
        /// <summary>
        /// Array containing identifier(s) of data item
        /// </summary>
        public object[] BdcEntityInstanceIds
        {
            get { return _bdcEntityInstanceIds; }
        }

        /// <summary>
        /// Initializes a new instance of the BDCRequestById class
        /// </summary>
        /// <param name="lobSysInstanceName">Name of the Lob System Instance</param>
        /// <param name="bdcEntityName">Type name of returned Business Data Object</param>
        /// <param name="bdcEntityInstanceEncId">Encoded identifier(s) of data item</param>
        public BDCRequestById(string lobSysInstanceName, string bdcEntityName, string bdcEntityInstanceEncId)
            : base(lobSysInstanceName, bdcEntityName)
        {
            // check if bdcEntityInstanceEncId is a valid encoded identifier(s)
            EnsureParamIsEncodedIdentifier("bdcEntityInstanceEncId", bdcEntityInstanceEncId);

            _bdcEntityInstanceEncodedId = bdcEntityInstanceEncId;

            // get decoded version of passed encoded identifier(s)
            _bdcEntityInstanceIds = EntityInstanceIdEncoder.DecodeEntityInstanceId(_bdcEntityInstanceEncodedId);
        }

        /// <summary>
        /// Initializes a new instance of the BDCRequestById class
        /// </summary>
        /// <param name="businessDataField">SharePoint field of BusinessData type</param>
        /// <param name="bdcEntityInstanceEncId">Encoded identifier(s) of data item</param>
        public BDCRequestById(BusinessDataField businessDataField, string bdcEntityInstanceEncId)
            : base(businessDataField)
        {
            // check if bdcEntityInstanceEncId is a valid encoded identifier(s)
            EnsureParamIsEncodedIdentifier("bdcEntityInstanceEncId", bdcEntityInstanceEncId);

            _bdcEntityInstanceEncodedId = bdcEntityInstanceEncId;

            // get decoded version of passed encoded identifier(s)
            _bdcEntityInstanceIds = EntityInstanceIdEncoder.DecodeEntityInstanceId(_bdcEntityInstanceEncodedId);
        }

        /// <summary>
        /// Initializes a new instance of the BDCRequestById class
        /// </summary>
        /// <param name="lobSysInstanceName">Name of the Lob System Instance</param>
        /// <param name="bdcEntityName">Type name of returned Business Data Object</param>
        /// <param name="bdcEntityInstanceIds">Array containing identifier(s) of data item</param>
        public BDCRequestById(string lobSysInstanceName, string bdcEntityName, object[] bdcEntityInstanceIds)
            : base(lobSysInstanceName, bdcEntityName)
        {
            // check if array bdcEntityInstanceIds isn't NULL and empty
            EnsureArrayIsNotNullOrEmpty("bdcEntityInstanceIds", bdcEntityInstanceIds);

            _bdcEntityInstanceIds = bdcEntityInstanceIds;

            // get encoded version of passed identifier(s)
            _bdcEntityInstanceEncodedId = EntityInstanceIdEncoder.EncodeEntityInstanceId(bdcEntityInstanceIds);
        }

        /// <summary>
        /// Initializes a new instance of the BDCRequestById class
        /// </summary>
        /// <param name="businessDataField">SharePoint field of BusinessData type</param>
        /// <param name="bdcEntityInstanceIds">Array containing identifier(s) of data item</param>
        public BDCRequestById(BusinessDataField businessDataField, object[] bdcEntityInstanceIds)
            : base(businessDataField)
        {
            // check if array bdcEntityInstanceIds isn't NULL and empty
            EnsureArrayIsNotNullOrEmpty("bdcEntityInstanceIds", bdcEntityInstanceIds);

            _bdcEntityInstanceIds = bdcEntityInstanceIds;

            // get encoded version of passed identifier(s)
            _bdcEntityInstanceEncodedId = EntityInstanceIdEncoder.EncodeEntityInstanceId(bdcEntityInstanceIds);
        }

        /// <summary>
        /// Throws an exception if the passed parameter value is a valid encoded identifier(s)
        /// </summary>
        /// <param name="paramName">Parameter name</param>
        /// <param name="value">Parameter value</param>
        public static void EnsureParamIsEncodedIdentifier(string paramName, string value)
        {
            if (string.IsNullOrEmpty(value) || !EntityInstanceIdEncoder.IsEncodedIdentifier(value))
                throw new ArgumentException("Invalid encoded identifier!", paramName);
        }

        /// <summary>
        /// Throws an exception if the passed parameter value isn't NULL and empty array
        /// </summary>
        /// <param name="paramName">Parameter name</param>
        /// <param name="value">Parameter value</param>
        public static void EnsureArrayIsNotNullOrEmpty(string paramName, object[] value)
        {
            if (value == null || value.Length == 0)
                throw new ArgumentException("Array must not be NULL or empty!", paramName);
        }

        /// <summary>
        /// Gets BDC Entity Instance that meets identifier(s)
        /// </summary>
        /// <returns>Found BDC Entity Instance</returns>
        protected override IEntityInstance GetEntityInstance()
        {
            // get BDC Entity Instance by its identifier(s)
            return FoundEntity.FindSpecific(BdcEntityInstanceIds, FoundLobSystemInstance);
        }
    }

    /// <summary>
    /// Represents search of data item in external data source by basing on a certain field value. 
    /// The value has to belong to the field used in item picker in UI
    /// </summary>
    public class BDCRequestByValue : BDCRequest
    {
        protected string _value = null;

        /// <summary>
        /// Field value to search data item by
        /// </summary>
        public string BdcEntityInstanceFieldValue
        {
            get { return _value; }
        }

        /// <summary>
        /// Initializes a new instance of the BDCRequestByValue class
        /// </summary>
        /// <param name="lobSysInstanceName">Name of the Lob System Instance</param>
        /// <param name="bdcEntityName">Type name of returned Business Data Object</param>
        /// <param name="bdcEntityInstanceFieldValue">Field value to search data item by</param>
        public BDCRequestByValue(string lobSysInstanceName, string bdcEntityName, string bdcEntityInstanceFieldValue)
            : base(lobSysInstanceName, bdcEntityName)
        {
            // check if bdcEntityInstanceFieldValue isn't NULL and empty string
            EnsureParamIsNotNullOrEmpty("bdcEntityInstanceFieldValue", bdcEntityInstanceFieldValue);                    

            _value = bdcEntityInstanceFieldValue;
        }

        /// <summary>
        /// Initializes a new instance of the BDCRequestByValue class
        /// </summary>
        /// <param name="businessDataField">SharePoint field of BusinessData type</param>
        /// <param name="bdcEntityInstanceFieldValue">Field value to search data item by</param>
        public BDCRequestByValue(BusinessDataField businessDataField, string bdcEntityInstanceFieldValue)
            : base(businessDataField)
        {
            // check if bdcEntityInstanceFieldValue isn't NULL and empty string
            EnsureParamIsNotNullOrEmpty("bdcEntityInstanceFieldValue", bdcEntityInstanceFieldValue);

            _value = bdcEntityInstanceFieldValue;
        }

        /// <summary>
        /// Gets BDC Entity Instance that meets the field value
        /// </summary>
        /// <returns>Found BDC Entity Instance</returns>
        protected override IEntityInstance GetEntityInstance()
        {            
            // search the entity using the first filter of available ones
            FilterCollection fc = FoundEntity.GetFinderFilters();
            if (fc[0] is WildcardFilter)
                ((WildcardFilter)fc[0]).Value = BdcEntityInstanceFieldValue; //"%" + request.BdcEntityInstanceFieldValue + "%";
            else if (fc[0] is ComparisonFilter)
                ((ComparisonFilter)fc[0]).Value = BdcEntityInstanceFieldValue;

            // find suitable BDC Entity instances and take the first one
            IEntityInstanceEnumerator entityInstanceEnumerator = FoundEntity.FindFiltered(fc, FoundLobSystemInstance);
            entityInstanceEnumerator.MoveNext();
            return entityInstanceEnumerator.Current;
        }
    }
}

BDC Request Class Diagram

There is an ability to get formatted values by setting FormattedValues = true, however, complex formatting slows performance, so use it only if really necessary.

How to use the classes:

BusinessDataField businessDataField = null;

// usage of BDCRequestByValue
BDCRequestByValue requestByValue = new BDCRequestByValue("ExternalProductDB_Instance", "Products", "Microsoft Office");
DataTable theFirstFoundRecord = requestByValue.GetBdcEntityInstance();
object producer = theFirstFoundRecord.Rows[0]["Producer"];
object price    = theFirstFoundRecord.Rows[0]["Price"];

object fieldValue = requestByValue.GetBdcEntityInstanceFieldValue("Producer");

requestByValue = new BDCRequestByValue(businessDataField, "Microsoft Office") { FormattedValues = true };
theFirstFoundRecord = requestByValue.GetBdcEntityInstance();
fieldValue = requestByValue.GetBdcEntityInstanceFieldValue("Producer");

// usage of BDCRequestById
BDCRequestById requestById = new BDCRequestById("ExternalProductDB_Instance", "Products", "__dk410035008400140025005400k410...");
theFirstFoundRecord = requestById.GetBdcEntityInstance();
producer = theFirstFoundRecord.Rows[0]["Producer"];
price    = theFirstFoundRecord.Rows[0]["Price"];

fieldValue = requestById.GetBdcEntityInstanceFieldValue("Producer");

requestById = new BDCRequestById("ExternalProductDB_Instance", "Products", new object[] { 2 });
theFirstFoundRecord = requestById.GetBdcEntityInstance();

requestById = new BDCRequestById(businessDataField, new object[] { 2 });
theFirstFoundRecord = requestById.GetBdcEntityInstance();

SharePoint: Understanding BusinessData Column (BDC Field)

December 6th, 2011 No comments

    This is the logical continuation of the article SharePoint: Brief introduction to Business Data Catalog (BDC)

Business Data Catalog (BDC) provides SharePoint with an ability to display, choose and store data from such external data sources as Web services and DataBases. Data item from external data source (either a record from a database or an object returned by a Web service) might be called BDC Entity Instance. BDC Column (or BDC Field, they are synonyms) is a column of ‘Business Data’ type. Once BDC Column is added to a list, users will be able to choose a BDC Entity Instance from the BDC to store as the value of the BDC Column.

BDC Column can be considered as a complex field, because technically it’s a group of several logically related SP fields. Adding one ‘Business Data’ column to the list, at least two fields will be added to the list in fact. The first is intended to contain a display value that will be shown to the user, while the second is intended to store an encoded identifier of the chosen BDC Entity Instance.

Let’s take a look at an example. Let’s assume that we have a Business Data Catalog that provides us with the Product object with the following structure:

Product 
{
	ID,
	Name,
	Price,
	Producer
}

If we create a new ‘Business Data’ column on a list as it shown in the picture below,
Add Business Data Column
the list schema exposes the following new fields:

<Field Type="BusinessData" DisplayName="Product" 
Required="FALSE" ID="{bc203358-6113-470f-9b08-f6100cc034f2}" 
StaticName="Product" BaseRenderingType="Text" Name="Product" 
SystemInstance="ExternalProductDB_Instance" Entity="Products" 
BdcField="Name" Profile="" HasActions="False" 
SecondaryFieldBdcNames="Price:Producer" RelatedField="Products_ID" 
SecondaryFieldWssNames="Product_x003a__x0020_Price:Product_x003a__x0020_Producer" 
RelatedFieldBDCField="" RelatedFieldWssStaticName="Products_ID" 
SecondaryFieldsWssStaticNames="Product_x003a__x0020_Price:Product_x003a__x0020_Producer" />
  
<Field Type="Note" DisplayName="Products_ID" Hidden="TRUE" ReadOnly="TRUE"
BdcField="Products_ID" ID="{0d37c424-0e57-4429-8f92-0b8faec5a5bd}" 
StaticName="Products_ID" Name="Products_ID" />

<Field Type="Note" DisplayName="Product: Price" ReadOnly="TRUE" 
BdcField="Price" ID="{c56d7123-cb3f-46f4-8f45-874ac5cee13d}" 
StaticName="Product_x003a__x0020_Price" Name="Product_x003a__x0020_Price" />

<Field Type="Note" DisplayName="Product: Producer" ReadOnly="TRUE" 
BdcField="Producer" ID="{df740ff6-2998-46e1-ac40-974d66ba7dc7}" 
StaticName="Product_x003a__x0020_Producer" Name="Product_x003a__x0020_Producer" />

*Note: some redundant attributes of fields are skipped

We have four added SP Fields that are logically related. As I mentioned before, the first field (Name=”Product”) is intended to store a value, which is displayed to the user. Besides, the field contains a number of attributes that refer to all of the rest of logically related fields. We’ll study these attributes a bit later. The second field (Name=”Products_ID”) is a hidden field, which is intended to store an encoded identifier of the BDC Entity Instance. The encoded identifier usually looks like “__dk410035008400140025005400k410…”. It’s permissible if more than one BDC Entity fields compose the identifier.

Other two additional fields (Name=”Product_x003a__x0020_Price” and Name=”Product_x003a__x0020_Producer”) are to store and show such properties of the chosen BDC Entity Instance as Price and Producer. These additional fields are optional, you might be unwilling to have any extra information in the list.

Ok, let’s examine closer the first field (Name=”Product”). It includes the attributes that describe what properties of the chosen BDC Entity Instance will be captured and what additional SP fields they will be stored in. The most important attributes are shown below:

  • SystemInstance – the name of the Lob System Instance or Business Data Catalog Application Instance, which BDC Column is bound to;
  • Entity – the type name of returned business data object, which BDC Column is bound to;
  • BdcField – the name of BDC Entity field, the value of which will be stored in this SharePoint field. For the Product field specifically, the stored value will be displayed to the user;

  • RelatedField – the internal name of a hidden SharePoint field, which is to store an encoded identifier of the chosen BDC Entity Instance;
  • RelatedFieldBDCField – usually an empty string, probably, it’s reserved for future usage, or it’s a legacy attribute;
  • RelatedFieldWssStaticName – the static name of a hidden SharePoint field, which stores an encoded identifier of the chosen BDC Entity Instance;

  • SecondaryFieldBdcNames – the colon-separated names of the fields within BDC Entity, the values of those fields are to be stored in the additional SharePoint fields. The attribute contains the same number of names as the SecondaryFieldWssNames attribute;
  • SecondaryFieldWssNames – the colon-separated internal names of the additional SharePoint fields, that are to store additional values from the chosen BDC Entity Instance. The attribute contains the same number of names as the SecondaryFieldBdcNames attribute;
  • SecondaryFieldsWssStaticNames – the colon-separated static names of the additional SharePoint fields, that are to store additional values from the chosen BDC Entity Instance. The attribute contains the same number of names as the SecondaryFieldBdcNames and SecondaryFieldWssNames attributes;

All of the rest of fields have BdcField-attributes as well. The attribute contains the name of BDC Entity field, the value of which will be stored in corresponding SharePoint field. But I’ve found an exception. This exception is the hidden field, which is intended to store an encoded identifier. In the given case it’s the Products_ID field. The BDCField-attribute of such fields always contains an invalid value, which equals to the name of the field itself. In our case is BdcField=”Products_ID”, and there is no BDC Entity field with the name Products_ID defined either in DataBase or Application Definition File. And the same situation is with all BusinessDate SharePoint fields I investigated.

Below is a summary schema that exposes what BusinessData column attributes refer to

BDC Field Schema Explanation

To work with BusinessData field programmatically we can cast SPField-object to BusinessDataField type defined in the Microsoft.SharePoint.Portal.WebControls namespace:

using Microsoft.SharePoint.Portal.WebControls;
...
SPList spList = ...
SPField spField = spList.Fields["display name of some field"];
BusinessDataField bizDataField = (BusinessDataField)spField;

To get values of attributes and to get all logically related SharePoint fields we can use the following code:

...
string systemInstance = bizDataField.SystemInstanceName;
string entity         = bizDataField.EntityName;
string bdcField       = bizDataField.BdcFieldName;

// get info about related field, which is to store the encoded identifier of BDC Entity Instance
string relatedField              = bizDataField.RelatedField;
string relatedFieldBDCField      = bizDataField.GetProperty("RelatedFieldBDCField");
string relatedFieldWssStaticName = bizDataField.GetProperty("RelatedFieldWssStaticName");

// get info about additional fields, that are to store additional values from BDC Entity Instance
string[] secondaryFieldBdcNames = bizDataField.GetSecondaryFieldsNames();

string[] separators = new string[] { ":" };

string secondaryFieldWssNamesVal = bizDataField.GetProperty("SecondaryFieldWssNames");
string[] secondaryFieldWssNames = string.IsNullOrEmpty(secondaryFieldWssNamesVal) ? 
    new string[0] :
    secondaryFieldWssNamesVal.Split(separators, StringSplitOptions.RemoveEmptyEntries);

string secondaryFieldsWssStaticNamesVal = bizDataField.GetProperty("SecondaryFieldsWssStaticNames");
string[] secondaryFieldsWssStaticNames = string.IsNullOrEmpty(secondaryFieldsWssStaticNamesVal) ?
    new string[0] :
    secondaryFieldsWssStaticNamesVal.Split(separators, StringSplitOptions.RemoveEmptyEntries);
        
// get all logically related sp fields
SPField relatedSPField = spList.Fields.GetFieldByInternalName(relatedField);
List<SPField> secondarySPFields = new List<SPField>(secondaryFieldWssNames.Length);
foreach (string secondaryFieldName in secondaryFieldWssNames)
    secondarySPFields.Add(spList.Fields.GetFieldByInternalName(secondaryFieldName));

Silverlight for Windows Phone 7: How to use ZXing 7.1 in Silverlight

November 22nd, 2011 No comments

    ZXing is an open source library for processing of multi-format 1D/2D barcode images. Using a built-in camera on your mobile phone you can scan a barcode (i.e. make a picture containing the barcode) and decode it by exploiting ZXing library. As the result you may have a recognized and decoded string value of barcode. The library supports such formats as traditional UPC-A and UPC-E, EAN-8 and EAN-13, trendy QR Code and etc. The library initially is implemented in Java, but there is a port to C#. However, if you decide to use this port in Silverlight Windows Phone 7 project, you’ll run into some problems with compatibility as Silverlight provides a “limited” C#. To be more specific, the C# port of ZXing actively utilizes such non-generic collections as Hashtable and ArrayList from System.Collections namespace, but almost everything under this namespace was completely removed from Silverlight. For details, please read the following article – Non-Generic Collections to be Removed from Silverlight. Hashtable and ArrayList can be replaced with System.Collections.Generic.Dictionary<Object, Object> and System.Collections.Generic.List<Object> respectively. Instances are received as System.Collections.ArrayList.Synchronized() can be smoothly turned into usual instances, that are not thread safe, because multithreading isn’t used in the library. If it’s necessary to provide thread safety, it can be done outside the library at the stage of instantiating of and accessing to the barcode readers contained in ZXing library. Just follow the rule – each thread must allocate its own object.

Furthermore, the ZXing C# port uses Color, Rectangle and Bitmap classes from System.Drawing, which is unavailable in Silverlight as well. As the replacement we can consider System.Windows.Media.Color, System.Windows.Rect and System.Windows.Media.Imaging.WriteableBitmap respectively, but they aren’t absolutely compatible with the preceding ones and some kind of adjustment code is required.

System.SerializableAttribute isn’t accessible too. We can just get rid of it. A number of other small changes are required as well.

As the result, I made a Silverlight port of ZXing 7.1 from C# port. You can download it here or here. Where the original code was replaced, I left commentaries kind of “here was .net follower” ๐Ÿ™‚

A few words of how to use it. In my application I try reading and decoding barcodes of UPC-A and UPC-E formats. For my needs I implemented a very simple wrapper around ZXing libraryBarcodeHelper, probably, it may be useful for somebody else (the helper is in downloadable solution). The wrapper is shown below. In the method TryToRecognizeBarcode you can see how interaction with ZXing is usually being put into practice.
Update: other methods, TryToRecognizeQRcode and TryToRecognizeDataMatrix were added to the BarcodeHelper. They allow to recognize QR and Data Matrix codes, respectively.

using System;
using System.Collections.Generic;
using System.Windows.Media.Imaging;

using com.google.zxing;

namespace Helpers
{
    public static class BarcodeHelper
    {
        public static bool TryToRecognizeBarcode(WriteableBitmap wb, out string barCode)
        {            
            // set some recognition settings
            var zxhints = new Dictionary<object, object>() 
                { 
                    { DecodeHintType.TRY_HARDER, true }, 
                    { DecodeHintType.POSSIBLE_FORMATS, new List<Object>() { BarcodeFormat.UPC_A, BarcodeFormat.UPC_E } } 
                };

            // create reader instance
            var reader = new com.google.zxing.oned.MultiFormatUPCEANReader(zxhints);
            return TryToRecognize(wb, reader, zxhints, out barCode);
        }

        public static bool TryToRecognizeQRcode(WriteableBitmap wb, out string qrCode)
        {
            // create reader instance
            var reader = new com.google.zxing.qrcode.QRCodeReader();
            return TryToRecognize(wb, reader, null, out qrCode);            
        }

        public static bool TryToRecognizeDataMatrix(WriteableBitmap wb, out string dtm)
        {
            // create reader instance
            var reader = new com.google.zxing.datamatrix.DataMatrixReader();
            return TryToRecognize(wb, reader, null, out dtm);             
        }

        private static bool TryToRecognize(WriteableBitmap wb, Reader reader, Dictionary<object, object> zxhints, out string output)
        {
            bool res = false;
            output = null;
            try
            {
                var luminiance = new RGBLuminanceSource(wb, wb.PixelWidth, wb.PixelHeight);
                var binarizer  = new com.google.zxing.common.HybridBinarizer(luminiance);
                var binBitmap  = new com.google.zxing.BinaryBitmap(binarizer);

                // recognize
                var results = reader.decode(binBitmap, zxhints); // exception will be thrown if reader cannot decode image.

                output = results.Text;
                res = true;
            }
            catch (Exception ex)
            {
            }
            return res;
        }
    }
}

Below is the sample code of usage taken from TestApplication (see downloadable solution):

WriteableBitmap wbmp = new WriteableBitmap(barcodeSample.Source as BitmapImage);
string recognizedOutput = string.Empty;
if (BarcodeHelper.TryToRecognizeBarcode(wbmp, out recognizedOutput))
// if (BarcodeHelper.TryToRecognizeQRcode(wbmp, out recognizedOutput))
// if (BarcodeHelper.TryToRecognizeDataMatrix(wbmp, out recognizedOutput))
    resultTexBlock.Text = recognizedOutput;
else
    resultTexBlock.Text = "Unrecognizable barcode!";

Hint: if the recognition failed on an image with a high resolution and, apparently, a big size, try reducing the image size in two times step by step until the image is recognized.

The following part of another simple test application demonstrates how to use camera to make picture containing barcode and recognize it then. You can find this small application in downloadable solution as well.

using System;
using System.Windows;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Tasks;
using System.Windows.Media.Imaging;
using System.IO;
using Helpers;

namespace WindowsPhoneApplication2
{
    public partial class MainPage : PhoneApplicationPage
    {
        private CameraCaptureTask camera = new CameraCaptureTask();

        // Constructor
        public MainPage()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            button1.IsEnabled = false;            

            camera.Completed += new EventHandler<PhotoResult>(camera_Completed);
            camera.Show();
        }

        void camera_Completed(object sender, PhotoResult e)
        {
            camera.Completed -= new EventHandler<PhotoResult>(camera_Completed);

            if (e.TaskResult == TaskResult.OK)
            {
                BitmapImage bmp = new BitmapImage();
                bmp.CreateOptions = BitmapCreateOptions.None; //Don't delay creation
                bmp.SetSource(e.ChosenPhoto);

                resultTexBlock.Text = string.Empty;

                WriteableBitmap wbmp = new WriteableBitmap(bmp);
                string recognizedBarcode = string.Empty;
                if (BarcodeHelper.TryToRecognizeBarcode(wbmp, out recognizedBarcode))
                    resultTexBlock.Text = recognizedBarcode;
                else
                    resultTexBlock.Text = "Unrecognizable barcode!";
            }            

            button1.IsEnabled = true;
        }
    }
}

If you find a bug, please, leave an appropriate comment here and the issue will be fixed in a timely manner.

Download ZXing7_1_Port.zip (Visual Studio 2010 Solution)

ps There is an alternative – Windows Phone 7 Silverlight ZXing Barcode Scanning Library, but, as I see, the version of ZXing they use isn’t up-to-date.

Related posts: