Hi folks,
After a mini holiday here there is one more part of the BCS series, whose main approach is to show how to connect to different external data sources.
Learn how to integrate WCF services in SharePoint 2010 through Business Connectivity Services. This is a very interesting approach because it allows that different services (including the cloud) to be integrated into SharePoint 2010.
In this article a WCF service will be created, so an External Content Type (ECT) can be created.
Creating ECTs via Web Service (WCF)
In order to create ECTs via Web Service it is needed the utilisation of SPD2010 and in this case, due to the fact the Web Service is going to be coded from the scratch, VS2010 is required as well.
Use this type of approach in environments that:
-
Integrates with an external data source (i.e.: other systems), either on your Intranet or Internet;
-
There is a need for creating business rules (i.e.: any validation) which can be implemented during the creation of the Web Service;
-
Uses different databases rather than SQL Server, which can be implemented in a data access layer during the creation of the Web Service;
If you have this scenario, this implementation has a higher level of difficulty by creating the Web Service. Both VS2010 and SPD2010 are utilised, the former for the Web Service development and the latter for the configuration.
Working with Visual Studio 2010
The creation of the ECT in this approach is only possible with the existence of a Web Service, that's why a solution in VS2010 is going to be created. As already mentioned in the Block 1 of the BCS Architecture (Part I), both extensions .asmx (ASP.NET Web Service Application) and .svc (WCF Service Application) can be used in the creation of ECTs, and in this demonstration I am going to use a WCF Service Application.
Start VS2010, create a Blank Solution and add 3 projects according to the Figure 1:
Figure 1 - Creating the Solution
Note: Delete the files *.cs and App.config that are created by default in new projects.
The solution was split into projects that represent the data access and business logic layers. Some references between the projects need to be added, according to the Table 1:
Project | Reference |
ContactServices.Business | ContactServices.Data |
ContactServices.Host | ContactServices.Business |
Table 1 - References Note: In all code examples, my goal is just to show the functionality in creating an ECT in SharePoint 2010. Don’t forget to implement the best practices and patterns such as the Application Blocks (i.e.: Logging, Exception Handling, Security, etc...). I strongly recommend the use of Enterprise Library.
Data Access Layer
In order to create the project ContactServices.Data some objects that manipulate the database need to be added, and for demonstration purposes I use the LINQ to SQL because it is simpler to implement. Add this object to the project and name it Dev.dbml, then create a new connection that uses Windows Authentication using the Server Explorer, open the database and drag the table Contact (Part II) as displayed in Figure 2:
Figure 2 - Adding table Contact
Create a class that contains CRUD methods, so the object Contact can be handled. To do that, create a class called ContactManager and add the code below, which comments tell by themselves:
Code Snippet
- public class ContactManager
- {
- /// <summary>
- /// Gets all the Contacts
- /// </summary>
- /// <returns>Contacts Array</returns>
- public Contact[] GetContacts()
- {
- var contacts = new List<Contact>();
-
- using (DevDataContext dev = new DevDataContext())
- {
- contacts = (from cont in dev.Contacts
- select cont).ToList();
- }
- return contacts.ToArray();
- }
-
- /// <summary>
- /// Gets a specific Contact
- /// </summary>
- /// <param name="contactId">Contact Id</param>
- /// <returns>Returns the Contact</returns>
- public Contact GetContactById(int contactId)
- {
- var contact = new Contact();
-
- using (DevDataContext dev = new DevDataContext())
- {
- contact = (from cont in dev.Contacts
- where cont.ContactID == contactId
- select cont).First();
- }
- return contact;
- }
-
- /// <summary>
- /// Updates a specific Contact
- /// </summary>
- /// <param name="contact">Contact to be updated</param>
- public void UpdateContact(Contact contact)
- {
- var contactDB = new Contact();
-
- using (DevDataContext dev = new DevDataContext())
- {
- contactDB = (from cont in dev.Contacts
- where cont.ContactID == contact.ContactID
- select cont).First();
-
- // Alters the object
- contactDB.Address = contact.Address;
- contactDB.City = contact.City;
- contactDB.CompanyName = contact.CompanyName;
- contactDB.ContactName = contact.ContactName;
- contactDB.ContactTitle = contact.ContactTitle;
- contactDB.Country = contact.Country;
- contactDB.Email = contact.Email;
- contactDB.Fax = contact.Fax;
- contactDB.Phone = contact.Phone;
- contactDB.PostalCode = contact.PostalCode;
- contactDB.Region = contact.Region;
-
- dev.Refresh(System.Data.Linq.RefreshMode.KeepChanges, contactDB);
- dev.SubmitChanges();
- }
- }
-
- /// <summary>
- /// Adds a Contact
- /// </summary>
- /// <param name="contact">New Contact</param>
- public void AddContact(Contact contact)
- {
- using (DevDataContext dev = new DevDataContext())
- {
- dev.Contacts.InsertOnSubmit(contact);
- dev.SubmitChanges();
- }
- }
-
- /// <summary>
- /// Deletes a Contacts
- /// </summary>
- /// <param name="contactId">Contact Id</param>
- public void DeleteContact(int contactId)
- {
- using (DevDataContext dev = new DevDataContext())
- {
- var contact = (from cont in dev.Contacts
- where cont.ContactID == contactId
- select cont).First();
- dev.Contacts.DeleteOnSubmit(contact);
- dev.SubmitChanges();
- }
- }
- }
Business Logic Layer
The project ContactServices.Business should contain Interfaces and Classes that call methods of the ContactServices.Data project. Creating Interfaces are important because of 3 main reasons in this Solution:
-
Establishes a contract for methods;
-
Sets a behaviour in classes that implement them;
-
Utilisation by WCF Service Application (project ContactServices.Host);
To perform that, create the interface IContactServices and the class called ContactServices that implements it, according to the code below:
Code Snippet
- [ServiceContract]
- public interface IContactServices
- {
- [OperationContract]
- Contact[] GetContacts();
-
- [OperationContract]
- Contact GetContactById(int contactId);
-
- [OperationContract]
- void UpdateContact(Contact contact);
-
- [OperationContract]
- void AddContact(Contact contact);
-
- [OperationContract]
- void DeleteContact(int contactId);
- }
Code Snippet
- public class ContactServices : IContactServices
- {
- #region IContactServices Members
-
- public Contact[] GetContacts()
- {
- // Create your own business rules
- return new ContactManager().GetContacts();
- }
-
- public Contact GetContactById(int contactId)
- {
- // Create your own business rules
- return new ContactManager().GetContactById(contactId);
- }
-
- public void UpdateContact(Contact contact)
- {
- // Create your own business rules
- new ContactManager().UpdateContact(contact);
- }
-
- public void AddContact(Contact contact)
- {
- // Create your own business rules
- new ContactManager().AddContact(contact);
- }
-
- public void DeleteContact(int contactId)
- {
- // Create your own business rules
- new ContactManager().DeleteContact(contactId);
- }
-
- #endregion
- }
The class ContactServices contains CRUD methods that are going to be used by the BCS and tell by themselves. They work as a "thin layer" for handling data, because directly call the ContactManager methods in the project ContactServices.Data.
In this example no business rules were implemented, but you can if you need them. Just code against the methods above.
Service Host
The project ContactServices.Host was created to work as the WCF Service host, which let methods to be available for BCS consuming. Start renaming the file Service1.svc to ContactServices.svc and update the service reference at the page directive:
Code Snippet
- <%@ ServiceHost Language="C#" Debug="true" Service="ContactServices.Business.ContactServices" %>
This change is necessary because the ContactServices class needs to be mapped, which was implemented in the project ContactServices.Business. An update in the Web.config is also necessary, by editing it in the WCF Service Configuration Editor (available in the VS2010) or directly in the section system.serviceModel according to the code below:
Code Snippet
- <system.serviceModel>
- <services>
- <service behaviorConfiguration="ContactServicesBehavior" name="ContactServices.Business.ContactServices">
- <endpoint binding="wsHttpBinding" bindingConfiguration="" contract="ContactServices.Business.IContactServices" />
- <endpoint address="mex" binding="mexHttpBinding" bindingConfiguration="" contract="ContactServices.Business.IContactServices" />
- </service>
- </services>
- <behaviors>
- <serviceBehaviors>
- <behavior name="ContactServicesBehavior">
- <serviceMetadata httpGetEnabled="true" />
- <serviceDebug includeExceptionDetailInFaults="true" />
- </behavior>
- </serviceBehaviors>
- </behaviors>
- </system.serviceModel>
Note: The behavior serviceDebug contains the attribute includeExceptionDetailInFaults to list in details any Web Service problem in the SharePoint Log, which is very useful during the WCF integration tests.
In the end, your solution should be similar to the Figure 3:
Figure 3 – Final Solution
Deploy the solution in the IIS, so you can get the Url for creating the ECT, as demonstrated in the next sections.
Working with SharePoint Designer 2010
At this stage the solution was already created, and now the ECT needs to be created, by mapping the Web Service methods and parameters. The Figure 1 (Part II) displays the first step in creating the ECT, afterwards add a new connection (1) to the Web Service by choosing the External Data Source Type (2), according to the Figure 4:
Figure 4 - Creating a new connection
Set the connection parameters according to the Figure 5:
Figure 5 – WCF Connection details
Note: Some important considerations:
-
The WCF metadata can be obtained through WSDL or endpoint Mex. Both of them can be informed in the fields Service Metadata URL / Metadata Connection Mode, and are available in this solution.
-
The User’s Identity can be used to connect to the WCF service and hence to the database, that’s why the Windows Authentication is required.
After creating the WCF connection the CRUD operations need to be mapped. This is the stage in which the Web Service methods and parameters should be mapped for the creation of the ECT. For each figure below there is an operation whose parameters are displayed in tables:
Figure 6 - AddContact Operation Properties
The operation AddContact as shown in Figure 6 has the following Input parameters according to the Tables 2 and 3:
Element | .NET Type | Map to Identifier | Identifier | Field | Display Name | Foreign Identifier |
ContactID | System.Int32 | TRUE | ContactID | ContactID | ID | |
Address | System.String | FALSE | | Address | Address | |
City | System.String | FALSE | | City | City | |
CompanyName | System.String | FALSE | | CompanyName | Company Name | |
ContactName | System.String | FALSE | | ContactName | Contact Name | |
ContactTitle | System.String | FALSE | | ContactTitle | Contact Title | |
Country | System.String | FALSE | | Country | Country | |
Email | System.String | FALSE | | Email | E-mail | |
Fax | System.String | FALSE | | Fax | Fax | |
Phone | System.String | FALSE | | Phone | Phone | |
PostalCode | System.String | FALSE | | PostalCode | Postal Code | |
Region | System.String | FALSE | | Region | Region | |
Table 2 - AddContact Operation Input Parameters
Element | Default Value | Filter | Element Path |
ContactID | <<None>> | | contact.ContactID |
Address | <<None>> | | contact.Address |
City | <<None>> | | contact.City |
CompanyName | <<None>> | | contact.CompanyName |
ContactName | <<None>> | | contact.ContactName |
ContactTitle | <<None>> | | contact.ContactTitle |
Country | <<None>> | | contact.Country |
Email | <<None>> | | contact.Email |
Fax | <<None>> | | contact.Fax |
Phone | <<None>> | | contact.Phone |
PostalCode | <<None>> | | contact.PostalCode |
Region | <<None>> | | contact.Region |
Table 3 - AddContact Operation Input Parameters (Continuation) There are no Return parameters to be configured for the operation AddContact, so simply ignore the configuration screen and finish this mapping.
Figure 7 - DeleteContact Operation Properties
The operation DeleteContact as shown in Figure 7 has the following Input parameter according to the Table 4:
Element | .NET Type | Map to Identifier | Identifier | Display Name | Default Value | Filter | Element Path |
contactId | System.Int32 | TRUE | ContactID | ID | <<None>> | | contactId |
Table 4 - DeleteContact Operation Input Parameters
Figure 8 - GetContactById Operation Properties
The operation GetContactById as shown in Figure 8 has the following Input and Return parameters according to the Tables 5, 6 and 7:
Element | .NET Type | Map to Identifier | Identifier | Display Name | Default Value | Filter | Element Path |
contactId | System.Int32 | TRUE | ContactID | ID | <<None>> | | contactId |
Table 5 - GetContactById Operation Input Parameters
Data Source Element | .NET Type | Map to Identifier | Identifier | Field | Display Name | Foreign Identifier |
ContactID | System.Int32 | TRUE | ContactID | ContactID | ID | |
Address | System.String | FALSE | | Address | Address | |
City | System.String | FALSE | | City | City | |
CompanyName | System.String | FALSE | | CompanyName | Company Name | |
ContactName | System.String | FALSE | | ContactName | Contact Name | |
ContactTitle | System.String | FALSE | | ContactTitle | Contact Title | |
Country | System.String | FALSE | | Country | Country | |
Email | System.String | FALSE | | Email | E-mail | |
Fax | System.String | FALSE | | Fax | Fax | |
Phone | System.String | FALSE | | Phone | Phone | |
PostalCode | System.String | FALSE | | PostalCode | Postal Code | |
Region | System.String | FALSE | | Region | Region | |
Table 6 - GetContactById Operation Return Parameters
Data Source Element | Element Path | Required | Read-Only | Office Property |
ContactID | GetContactById.ContactID | FALSE | TRUE | Custom Property |
Address | GetContactById.Address | FALSE | FALSE | Business Address (BusinessAddress) |
City | GetContactById.City | FALSE | FALSE | Business Address City (BusinessAddressCity) |
CompanyName | GetContactById.CompanyName | FALSE | FALSE | Company Name (CompanyName) |
ContactName | GetContactById.ContactName | TRUE | FALSE | Full Name (FullName) |
ContactTitle | GetContactById.ContactTitle | FALSE | FALSE | Title (Title) |
Country | GetContactById.Country | FALSE | FALSE | Business Address Country/Region (BusinessAddressCountry) |
Email | GetContactById.Email | TRUE | FALSE | Email 1 Address (Email1Address) |
Fax | GetContactById.Fax | FALSE | FALSE | Business Fax Number (BusinessFaxNumber) |
Phone | GetContactById.Phone | TRUE | FALSE | Business Telephone Number (BusinessTelephoneNumber) |
PostalCode | GetContactById.PostalCode | FALSE | FALSE | Business Address Postal Code (BusinessAddressPostalCode) |
Region | GetContactById.Region | FALSE | FALSE | Business Address State (BusinessAddressState) |
Table 7 - GetContactById Operation Return Parameters (Continuation) Figure 9 - GetContacts Operation Properties
The operation GetContacts as shown in Figure 9 does not have Input parameters to be configured, but has the following Return parameters according to the Tables 8 and 9:
Element | .NET Type | Map to Identifier | Identifier | Field | Display Name | Foreign Identifier |
ContactID | System.Int32 | TRUE | ContactID | ContactID | ID | |
Address | System.String | FALSE | | Address | Address | |
City | System.String | FALSE | | City | City | |
CompanyName | System.String | FALSE | | CompanyName | Company Name | |
ContactName | System.String | FALSE | | ContactName | Contact Name | |
ContactTitle | System.String | FALSE | | ContactTitle | Contact Title | |
Country | System.String | FALSE | | Country | Country | |
Email | System.String | FALSE | | Email | E-mail | |
Fax | System.String | FALSE | | Fax | Fax | |
Phone | System.String | FALSE | | Phone | Phone | |
PostalCode | System.String | FALSE | | PostalCode | Postal Code | |
Region | System.String | FALSE | | Region | Region | |
Table 8 - GetContacts Operation Return Parameters
Element | Element Path | Required | Read-Only | Show in Picker | Timestamp Field |
ContactID | GetContacts.GetContactsElement.ContactID | FALSE | TRUE | FALSE | FALSE |
Address | GetContacts.GetContactsElement.Address | FALSE | FALSE | FALSE | FALSE |
City | GetContacts.GetContactsElement.City | FALSE | FALSE | FALSE | FALSE |
CompanyName | GetContacts.GetContactsElement.CompanyName | FALSE | FALSE | FALSE | FALSE |
ContactName | GetContacts.GetContactsElement.ContactName | TRUE | FALSE | FALSE | FALSE |
ContactTitle | GetContacts.GetContactsElement.ContactTitle | FALSE | FALSE | FALSE | FALSE |
Country | GetContacts.GetContactsElement.Country | FALSE | FALSE | FALSE | FALSE |
Email | GetContacts.GetContactsElement.Email | TRUE | FALSE | FALSE | FALSE |
Fax | GetContacts.GetContactsElement.Fax | FALSE | FALSE | FALSE | FALSE |
Phone | GetContacts.GetContactsElement.Phone | TRUE | FALSE | FALSE | FALSE |
PostalCode | GetContacts.GetContactsElement.PostalCode | FALSE | FALSE | FALSE | FALSE |
Region | GetContacts.GetContactsElement.Region | FALSE | FALSE | FALSE | FALSE |
Table 9 - GetContacts Operation Return Parameters (Continuation)
Figure 10 - UpdateContact Operation Properties
The operation UpdateContact as shown in Figure 10 has the following Input parameters according to the Tables 10 and 11:
Element | .NET Type | Map to Identifier | Identifier | Field | Display Name | Foreign Identifier |
ContactID | System.Int32 | TRUE | ContactID | ContactID | ID | |
Address | System.String | FALSE | | Address | Address | |
City | System.String | FALSE | | City | City | |
CompanyName | System.String | FALSE | | CompanyName | Company Name | |
ContactName | System.String | FALSE | | ContactName | Contact Name | |
ContactTitle | System.String | FALSE | | ContactTitle | Contact Title | |
Country | System.String | FALSE | | Country | Country | |
Email | System.String | FALSE | | Email | E-mail | |
Fax | System.String | FALSE | | Fax | Fax | |
Phone | System.String | FALSE | | Phone | Phone | |
PostalCode | System.String | FALSE | | PostalCode | Postal Code | |
Region | System.String | FALSE | | Region | Region | |
Table 10 - UpdateContact Operation Input Parameters
Element | Default Value | Filter | Element Path |
ContactID | <<None>> | | contact.ContactID |
Address | <<None>> | | contact.Address |
City | <<None>> | | contact.City |
CompanyName | <<None>> | | contact.CompanyName |
ContactName | <<None>> | | contact.ContactName |
ContactTitle | <<None>> | | contact.ContactTitle |
Country | <<None>> | | contact.Country |
Email | <<None>> | | contact.Email |
Fax | <<None>> | | contact.Fax |
Phone | <<None>> | | contact.Phone |
PostalCode | <<None>> | | contact.PostalCode |
Region | <<None>> | | contact.Region |
Table 11 - UpdateContact Operation Input Parameters (Continuation) Note: Notice that in most cases just the configuration parameters (columns) nomenclature changes, but data is the same. I have decided to create configuration tables for each operation in order to facilitate the mapping with separate operations.
Once all the columns were set properly, save the ECT (1) and check the operations created (2), which can be edited at any time, according to the Figure 11:
Figure 11 - Saving the ECT
Now it is possible to create an External List that will provide a visual interface for the external data in SharePoint 2010. In the same screen of External Content Types, choose the option External List on the context menu. Name it to “Contacts”, according to the Figure 12:
Figure 12 - Creating an External List
When the External List is created, the purpose of this article is accomplished. Now it is up to you to test the External List, which was already explained in the Part II. Reuse the same test and apply it here, since it was created for this purpose.
The fact of using a Web Service for integration in SharePoint 2010 shows us that it is possible to transmit data from and to any system that provides this interface. Unify data from different systems in SharePoint 2010! Now you know how to do it!
Reference:
http://msdn.microsoft.com/en-us/library/ee556826(v=office.14).aspx
Cheers
Marcel Medina
Click here to read the same content in Portuguese.