External content types are defined as reusable objects with metadata descriptions of connectivity information to external systems and data definitions. They could also contain the behaviors you want to apply to the external data. They are similar to the content types that we have dealt so far. The difference is that we will be managing the external data in external content types. The external data can be from relational databases like SQL server or Oracle or data coming from some web service.
For our recipe, we will use a SQL server table and create a content type based on the table data. We will use the HumanResources.Department
table in the AdventureWorks
database. We will create a GetDepartments
method that will retrieve the list of all departments and a GetDepartment
method that retrieves the single department object.
We will use LINQ to SQL to access relational data as objects. You do not have to use LINQ to SQL for data access in external content types. In this example, we are using it for the sake of simplicity of the code. If you want to learn more about LINQ to SQL, refer to MSDN at: http://msdn.microsoft.com/en-us/library/bb425822.aspx.
For this recipe, you need to have access to the AdventureWorks
database. You can download it from: http://msftdbprodsamples.codeplex.com/.
Entity1.cs
and Entity1Service.cs
associated with this entity. System.Int16
. Visual Studio will add a new service file called DepartmentService.cs
in your solution explorer. Departments
table and it to the designer. Visual Studio will generate the necessary LINQ DataContext
classes for the external table. You can refer to this DataContext
class in the Department.designer.cs
file. Close this designer surface. We will not be making any changes to this class file. GetDepartmentList
. Also Create a Finder Specific
method and name it GetDepartment
. The final screenshot should look like the following: Type Name
property to Current Project | Department. Make sure that the Is Enumerable checkbox is selected. Type Name
property to Current Project | Department. In this case, the Is Enumerable checkbox is not selected. Underneath this department, add a Type Descriptor
and name it DepartmentID. The type name should be System.Int16
. In the same manner, add three more Type Descriptors named Name, GroupName, and ModifiedDate. The Type Name for the Name and the GroupName is System.String
and for ModifiedDate it is System.DateTime
. The final screen should be as shown in the following screenshot: DepartmentService.cs
file and add code to retrieve the departments from the database to the GetDepartmentList
method and add code to retrieve the single object to the GetDepartment
method. Your code should be as follows. Make sure to change the database connection string to your environment:public static IEnumerable<Department> GetDepartmentList() { DepartmentDataContext dx = new DepartmentData Context("Data Source=intsql;Initial Catalog=Adventure Works;Integrated Security=True"); return dx.Departments; } public static Department GetDepartment(short id) { DepartmentDataContext dx = new DepartmentData Context("Data Source=intsql;Initial Catalog=AdventureWorks; Integrated Security=True"); return dx.Departments.Single(d => d.DepartmentID == id); }
Departments
table in the AdventureWorks
database as shown in the next screenshot:Visual Studio refers to external content types as an entity because BDC schema refers to this as entities. BDC Designer is built on top of ADO.Net Entity Designer and hence that reference. If you are familiar with ADO.Net Entity Designer, this will work the same way. In here, we can create entities and associate them with some relationship.
For our external content type to work, we need to create at least two methods of type Finder
and SpecificFinder
method. In our example, we created GetDepartmentList
and GetDepartment
that corresponds to these types. This is important because SharePoint calls these methods to render the list and display the selected item when the item is selected. There are other types of methods that can be used in external content types. Refer to MSDN at: http://msdn.microsoft.com/en-us/library/ee557363(office.14).aspx for more information on different method types that can be developed in the external content types.
The finder method, GetDepartmentList
does not take any input parameters, but has one return parameter. This is defined in the Method Details window. In here, we provided information about the type of our parameter and direction of the parameter. The direction can be "In", "Out", "InOut", and "Return". The "In" direction is used on the parameters that are used to pass in the values to the methods. The "Out" direction, as the name refers to, is the output parameter and "InOut" is similar to "ref" parameter in C#. We also provided the type of our parameter. For this, we used the Department
object that was created through LINQ to SQL. We set the type of the parameter through type descriptors. Type descriptors are metadata information that is used during the runtime to determine the type of the parameter passed or returned. There are other properties in type descriptors that tell SharePoint how to use this parameter. For example, the Creator Field
property in a type descriptor tells that it is used in the form for user input. the collection property tells that the parameter is a collection. Not all of the type descriptors make sense for each of the parameters that pass in the method. It comes in handy, depending upon the type we want to retrieve or pass.
Each of the methods that we defined also has a method instance. This is because, in BCS, when we define a method, it is defined as an abstract piece of code. An instance is created during run time and executed.
If you receive Access denied by Business Data Connectivity error as shown here, follow these steps:
For our preceding example, to add other CRUD methods like create, update, and delete methods, follow these steps:
CreateDepartment
. For updating, the method type should be Updater Method
and for delete Deleter Method
. Also name the updater method as UpdateDepartment
and the deleter method as DeleteDepartment
. DepartmentService.cs
. Add the code in the DepartmentService.cs
for create, update, and delete. Your code should look as follows:public static Department CreateDepartment(Department newDepartment) { DepartmentDataContext dx = new DepartmentDataContext("Data Source=intsql;Initial Catalog=AdventureWorks;Integrated Security=True"); newDepartment.ModifiedDate = DateTime.Today; dx.Departments.InsertOnSubmit(newDepartment); dx.SubmitChanges(); return dx.Departments.Single(d => d.DepartmentID == newDepartment.DepartmentID); } public static void UpdateDepartment(Department department) { DepartmentDataContext dx = new DepartmentDataContext("Data Source=intsql;Initial Catalog=AdventureWorks;Integrated Security=True"); Department dept = dx.Departments.Single(d => d.DepartmentID == department.DepartmentID); dept.GroupName = department.GroupName; dept.Name = department.Name; dept.ModifiedDate = DateTime.Today; dx.SubmitChanges(); } public static void DeleteDepartment(short departmentID) { DepartmentDataContext dx = new DepartmentDataContext("Data Source=intsql;Initial Catalog=AdventureWorks;Integrated Security=True"); external content typeCRUD methodsdx.Departments.DeleteOnSubmit(dx.Departments.Single(d => d.DepartmentID == departmentID)); dx.SubmitChanges(); }
In our example, we have hard coded the connection string. There are several ways you can access the connection string data without hard coding it like getting the connection string from web.config
or from a custom SharePoint list that is used to store the configuration information. The other approach is to use the BCS LobSystemInstance
object. For our example:
Microsoft.BusinessData.dll
found in Program FilesCommon FilesMicrosoft SharedWeb Server Extensions14ISAPI
. DepartmentService
implement the IContextProperty
interface. The following is the code for getting the DB connection string.public string GetDBCon() { Microsoft.BusinessData.MetadataModel.Collections.INamedPropertyDictionary dic = this.LobSystemInstance.GetProperties(); if (dic.ContainsKey("DBCon")) return dic["DBCon"].ToString(); else return ""; } public Microsoft.BusinessData.Runtime.IExecutionContext ExecutionContext public Microsoft.BusinessData.Runtime.IExecutionContext ExecutionContext { get; set; } public Microsoft.BusinessData.MetadataModel.ILobSystemInstance LobSystemInstance { get; set; } public Microsoft.BusinessData.MetadataModel.IMethodInstance MethodInstance { get; set; }
The other method is to use the secure store service to store the connection string. Although it is used to store the user credentials for logging into the external system, it can be used to store the connection string. Information on configuring the secure store service can be found on MSDN at http://technet.microsoft.com/en-us/library/ee806866.aspx.
For our example, create a new secure store application called AdventureWorks with one field of type Generic. Call that field Connection String as shown here:
Follow the next steps to get credentials from the secure store programmatically.
SecureStoreUtilities.cs
. Microsoft.BusinessData.dll
from the ISAPI folder. Microsoft.Office.SecureStoreService.dll
from WindowsassemblyGAC_MSILMicrosoft.Office.SecureStoreService14.0.0.0__71e9bce111e9429c
. SecureStoreUtilities.cs
to retrieve and decrypt the string:public static Dictionary<string, string> GetSSCreds(string applicationID) { var credKVP = new Dictionary<string, string>(); //Key Value pair using (SPSite site = new SPSite("http://intsp1/")) { // Console.WriteLine(site.RootWeb.CurrentUser.Name); SPServiceContext serviceContext = SPServiceContext.GetContext(site); var secureStoreProvider = new SecureStoreProvider { Context = serviceContext }; using (var creds = secureStoreProvider. GetCredentials(applicationID)) { var taFields = secureStoreProvider.GetTarget ApplicationFields(applicationID); for (var i = 0; i < taFields.Count; i++) { var field = taFields[i]; var credential = creds[i]; var decryptedCredential = DecryptCredential(credential.Credential); external content typeconnection stringscredKVP.Add(field.Name, decryptedCredential); } } } return credKVP; } public static string DecryptCredential(this SecureString encryptedString) { var ssBSTR = Marshal.SecureStringToBSTR (encryptedString); try { return Marshal.PtrToStringBSTR(ssBSTR); } finally { Marshal.FreeBSTR(ssBSTR); } }
DepartmentService.cs
called GetDBConnectionString
and pass the return value from this method to the DepartmentDataContext
constructor:public static string GetDBConnectionString() { string sDbCon = ""; Dictionary<string, string> ssList = SecureStoreUtilities.GetSSCreds("AdventureWorks"); foreach (KeyValuePair<string, string> kvp in ssList) { if (kvp.Key == "Connection String") { sDbCon = kvp.Value; break; } } return sDbCon; }
You can use this approach to dynamically construct a LOB connection string by storing the credentials in the secure store.
18.116.239.195