Oi pessoal, tudo bem?
Depois de umas mini-férias trago mais uma parte da série sobre o BCS, onde abordo como conectar a diferentes fontes de dados externos.
Nessa abordagem aprenda a integrar serviços WCF no SharePoint 2010 pelo Business Connectivity Services. Essa é uma abordagem bem interessante, pois permite que diferentes serviços (inclusive da nuvem) possam ser integrados no SharePoint 2010.
Para a apresentação desse artigo criaremos um serviço WCF, que servirá para a criação de um External Content Type (ECT).
Criação de ECTs via Web Service (WCF)
Para a criação de ECTs via Web Service necessitamos da utilização do SPD2010 e nesse caso, como iremos criar um Web Service, necessitamos do VS2010.
Esse tipo de abordagem é aplicado em ambientes que:
-
Façam integração com uma fonte de dados externos (ex: outros sistemas), quer seja em sua Intranet ou Internet;
-
Haja a necessidade de criação de regras de negócio, o que pode ser implementado durante a criação do Web Service;
-
Utilizem bancos de dados diferentes do SQL Server, o que pode ser implementado na camada de acesso a dados durante a criação do Web Service;
Caso você tenha esse cenário, essa implementação tem um nível maior de dificuldade pela criação do Web Service, que é demonstrado a seguir. Inicialmente trabalharemos com o VS2010 e em seguida finalizaremos a configuração com o SPD2010.
Trabalhando com o Visual Studio 2010
A criação do ECT em questão só é possível com a existência de um Web Service, para tal criaremos uma solução no VS2010. Conforme já mencionado no Bloco 1 da Arquitetura do BCS (Parte I), ambas as extensões .asmx (ASP.NET Web Service Application) e .svc (WCF Service Application) podem ser utilizadas na criação de ECTs e nessa demonstração vou utilizar um WCF Service Application.
Inicie o VS2010, crie uma Blank Solution e adicione 3 projetos conforme a Figura 1:
Figura 1 - Criação da Solução
OBS: Com relação aos arquivos *.cs e App.config disponibilizados por padrão quando da criação de novos projetos, favor excluí-los.
A solução foi separada em projetos que refletem as camadas de acesso a dados e negócio que veremos a seguir. É necessário antes que as seguintes referências sejam adicionadas entre os projetos conforme a Tabela 1:
Projeto | Referência |
ContactServices.Business | ContactServices.Data |
ContactServices.Host | ContactServices.Business |
Tabela 1 - Referências OBS: Em todos os exemplos de código, meu objetivo é de mostrar apenas a funcionalidade na criação de um ECT no SharePoint 2010, portanto utilize o código como base e implemente os tratamentos que toda aplicação necessita possuir (ex: Logs, Exceções, Segurança, etc...). Recomendo a utilização do Enterprise Library.
Camada de Acesso a Dados
Para a criação do projeto ContactServices.Data precisamos adicionar alguns objetos que manipulem o banco de dados, e para fins de demonstração utilizo o LINQ to SQL por ser mais simples de implementar. Adicione esse objeto ao projeto e o nomeie de Dev.dbml, em seguida crie pelo Server Explorer uma nova conexão que utilize Windows Authentication, abra o database e arraste a tabela Contact (Parte II) conforme demonstrado na Figura 2:
Figura 2 - Adição da tabela Contact
Para manipularmos os dados do objeto Contact é necessário criarmos uma classe que disponibilize métodos de um CRUD, para isso crie uma classe chamada ContactManager e adicione o código abaixo, cujos comentários são bem explicativos:
Code Snippet
- public class ContactManager
- {
- /// <summary>
- /// Obtem todos os Contatos
- /// </summary>
- /// <returns>Array de Contatos</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>
- /// Obtem um Contato especifico
- /// </summary>
- /// <param name="contactId">Id do Contato</param>
- /// <returns>Retorna o Contato</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>
- /// Atualiza um Contato especifico
- /// </summary>
- /// <param name="contact">Contato para atualizacao</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();
-
- // Alterando o objeto
- 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>
- /// Adiciona um Contato
- /// </summary>
- /// <param name="contact">Novo Contato</param>
- public void AddContact(Contact contact)
- {
- using (DevDataContext dev = new DevDataContext())
- {
- dev.Contacts.InsertOnSubmit(contact);
- dev.SubmitChanges();
- }
- }
-
- /// <summary>
- /// Apaga um Contato
- /// </summary>
- /// <param name="contactId">Id do Contato</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();
- }
- }
- }
Camada de Negócio
No projeto ContactServices.Business devemos disponibilizar Interfaces e Classes que façam chamadas aos métodos do projeto ContactServices.Data. A criação de Interfaces é importante por 3 motivos na solução:
-
Definição de um contrato para os métodos;
-
Determina um comportamento nas classes que a implementam;
-
Utilização pelo WCF Service Application (projeto ContactServices.Host);
Para implementação do projeto, crie a interface IContactServices e a classe que a implementa chamada ContactServices, conforme os códigos abaixo respectivamente:
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()
- {
- // Implemente sua propria regra de negocio
- return new ContactManager().GetContacts();
- }
-
- public Contact GetContactById(int contactId)
- {
- // Implemente sua propria regra de negocio
- return new ContactManager().GetContactById(contactId);
- }
-
- public void UpdateContact(Contact contact)
- {
- // Implemente sua propria regra de negocio
- new ContactManager().UpdateContact(contact);
- }
-
- public void AddContact(Contact contact)
- {
- // Implemente sua propria regra de negocio
- new ContactManager().AddContact(contact);
- }
-
- public void DeleteContact(int contactId)
- {
- // Implemente sua propria regra de negocio
- new ContactManager().DeleteContact(contactId);
- }
-
- #endregion
- }
A classe ContactServices disponibiliza métodos de um CRUD que serão utilizados no BCS e falam por si só. Servem como uma “casquinha” para a manipulação de dados, pois chamam diretamente os métodos da classe ContactManager do projeto ContactServices.Data.
Nesse caso não foram implementadas regras de negócio, mas se você tiver necessidade faça a implementação nesses métodos.
Host do Serviço
O projeto ContactServices.Host servirá para fazer o host do serviço WCF, que vai disponibilizar os métodos para o BCS. Para isso renomeie o arquivo criado por padrão Service1.svc para ContactServices.svc e altere a referência do serviço na diretiva de página para:
Code Snippet
- <%@ ServiceHost Language="C#" Debug="true" Service="ContactServices.Business.ContactServices" %>
Essa alteração é necessária para o mapeamento da classe ContactServices implementada no projeto ContactServices.Business. Para que o serviço seja disponibilizado também é necessária a alteração do Web.config, que pode ser editado pelo WCF Service Configuration Editor (disponível no VS2010) ou diretamente no arquivo na seção system.serviceModel conforme o código abaixo:
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>
OBS: O behavior serviceDebug contém o atributo includeExceptionDetailInFaults para listar em detalhes qualquer problema do Web Service no Log do SharePoint, o que é bastante útil durante os testes na integração do serviço WCF.
Ao final essa solução deve estar semelhante à exibida na Figura 3:
Figura 3 - Solução Final
Faça o deploy da solução no IIS para que utilizemos um endereço fixo na criação do ECT, que veremos a seguir.
Trabalhando com o SharePoint Designer 2010
Nessa etapa já temos a solução criada e precisamos apenas criar o ECT, mapeando os métodos e parâmetros do Web Service. A Figura 1 (Parte II) nos mostra a etapa inicial de criação do ECT, em seguida adicione uma nova conexão (1) ao Web Service pela seleção do tipo do External Data Source (2), conforme Figura 4:
Figura 4 - Criação de uma nova conexão
Defina os parâmetros de conexão conforme a Figura 5:
Figura 5 - Detalhes da conexão
OBS: Algumas considerações importantes:
-
Os metadados do WCF podem ser obtidos pelo WSDL ou utilizando o endpoint Mex. Ambos podem ser informados em Service Metadata URL / Metadata Connection Mode e estão disponíveis na solução criada.
-
A identidade do usuário (User’s Identity) será utilizada para conectar no serviço do WCF e consequentemente no banco de dados, por isso utilizamos Windows Authentication na conexão.
Após a criação da conexão é necessário criarmos as operações do CRUD para o ECT, etapa essa onde os métodos e parâmetros do Web Service serão mapeados. Para cada figura que mapeia um método temos tabelas que definem seus parâmetros de entrada e retorno (quando aplicados):
Figura 6 - Operação AddContact
A operação AddContact da Figura 6 possui os seguintes parâmetros de entrada exibidos nas Tabelas 2 e 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 | |
Tabela 2 - Parâmetros de Entrada da Operação AddContact
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 |
Tabela 3 - Parâmetros de Entrada da Operação AddContact (Continuação) Vale lembrar que nenhum parâmetro de retorno se aplica na operação AddContact, portanto simplesmente ignore a tela de configuração e finalize a criação do mapeamento.
Figura 7 - Operação DeleteContact
A operação DeleteContact da Figura 7 possui o seguinte parâmetro de entrada exibido na Tabela 4:
Element | .NET Type | Map to Identifier | Identifier | Display Name | Default Value | Filter | Element Path |
contactId | System.Int32 | TRUE | ContactID | ID | <<None>> | | contactId |
Tabela 4 - Parâmetro de Entrada da Operação DeleteContact
Figura 8 - Operação GetContactById
A operação GetContactById da Figura 8 possui os seguintes parâmetros de entrada e retorno exibidos nas Tabelas 5, 6 e 7:
Element | .NET Type | Map to Identifier | Identifier | Display Name | Default Value | Filter | Element Path |
contactId | System.Int32 | TRUE | ContactID | ID | <<None>> | | contactId |
Tabela 5 - Parâmetro de Entrada da Operação GetContactById
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 | |
Tabela 6 - Parâmetro de Retorno da Operação GetContactById
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) |
Tabela 7 - Parâmetro de Retorno da Operação GetContactById (Continuação) Figura 9 - Operação GetContacts
A operação GetContacts da Figura 9 não possui parâmetros de entrada a serem configurados, porém possui os seguintes parâmetros de retorno exibidos nas Tabelas 8 e 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 | |
Tabela 8 - Parâmetro de Retorno da Operação GetContacts
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 |
Tabela 9 - Parâmetro de Retorno da Operação GetContacts (Continuação)
Figura 10 - Operação UpdateContact
A operação UpdateContact da Figura 10 possui os seguintes parâmetros de entrada exibidos nas Tabelas 10 e 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 | |
Tabela 10 - Parâmetros de Entrada da Operação UpdateContact
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 |
Tabela 11 - Parâmetros de Entrada da Operação UpdateContact (Continuação) OBS: Reparem que na maior parte dos casos apenas a nomenclatura dos parâmetros de configuração (colunas) muda, porém os dados são os mesmos. Resolvi criar tabelas de configuração para cada operação no intuito de facilitar o mapeamento com operações separadas.
Uma vez que todas as colunas foram definidas, salve o ECT (1) e observe as operações criadas (2), as quais podem ser editadas a qualquer momento, conforme Figura 11:
Figura 11 - Salvando o ECT
Nesse momento já é possível a criação de um External List que fará a interface visual com os dados externos no SharePoint 2010. Na mesma tela de External Content Types, visualize o menu de contexto (botão direito) e selecione a opção External List. Nomeie para “Contacts”, conforme Figura 12:
Figura 12 - Criação de um External List
Com a finalização da External List, chegamos ao propósito desse artigo. Cabe a você agora testar a External List, o que já foi explicado na Parte II do BCS. Reaproveite o mesmo teste e aplique aqui, pois ele foi criado para esse fim.
O fato de podermos utilizar um Web Service para a integração no SharePoint 2010 mostra que podemos conectar dados de qualquer sistema que disponibilize essa interface. Unifique os dados de diferentes sistemas no SharePoint 2010! Agora você sabe como fazê-lo!
Referência:
http://msdn.microsoft.com/en-us/library/ee556826(v=office.14).aspx
[]’s
Marcel Medina
Clique aqui para ler o mesmo conteúdo em Inglês.