Hola, hoy quiero compartir con vosotros una nueva experiencia que he tenido en los últimos días. Por requerimientos de un nuevo proyecto tenía que consumir los Webservices de la Salesforce API desde .Net con C# algo que no debe de plantearnos ningún problema ya que es un Webservice más y en este caso de uno de los CRM que adía de hoy están más de actualidad.
Ya os adelanto que es bastante sencillo, sin embargo dado que me he encontrado con algunas cosillas las comparto en esté artículo con vosotros y haremos un paso a paso de «Consumiendo Webservices de Salesforece API desde .NET con C#».
Lo primero de todo es tener un entorno Salesforce algo obvio y que muchos de vosotros cuando estáis leyendo estás líneas ya tendréis solucionado, sin embargo si os queréis iniciar en este mundo de Salesforece sin necesidad de comparar un entorno y pagar por el, podréis hacerlo de igual forma entrando en Salesforce.com y creándoos un entorno para desarrolladores, al igual que otros clientes existe la opción de un entorno para pruebas, algo que es muy de agradecer.
Una vez estáis dentro de vuestro entrono lo primero que vamos a hacer es generarnos un Token de Seguridad, si no sabéis cual es deberéis resetearlo aunque posiblemente nunca lo hayáis usado antes. Para hacer esto que es bastante sencillo sencillo, pulsaréis en la parte superior derecho de vuestro entrono sobre vuestro nombre, se os desplegarán 3 opciones seleccionamos «My Settings»
Ahora en la parte lateral izquierda deberemos desplegar el menú «Personal» y pulsar sobre la opción «Reset My Security Token», hecho esto se os abrirá la ventana con la opción de reset, una vez reseteada recibiréis un mail que incluirá vuestro nuevo Token que deberéis guardar para usarlo en está aplicación.
Seguimos todavía dentro de Salesforce, no hemos comenzado a picar código ya que debemos ahora obtener el webservice para usarlo en nuestra solución .NET. Para esto accedemos a la opción de Setup->Buscamos Api->Seleccionamos Api y ahí seleccionamos el webservice que nos convenga, nosotros hemos generado el «Enterprise WDSL». Una vez generado descargamos el fichero XML.
Ya si que vamos directamente a Visual Studio, dentro de nuestra solución en nuestro caso de consola vamos a agregar una «referencia de servicio» o «service reference» que apunte a nuestro fichero descargado con el XML del Servicio. Tener claro que le he dado el nombre WS_Enterprise si utilizáis algo de nuestro código deberéis sustituirlo por el nombre que le deis a vuestro WebService.
Hemos creado un fichero que en nuestro caso se llama clsConnectSalesforce y que incluye las clases, ForceConnectionStringBuilder, ForceConnection y ApiService , estás clases las podéis encontrar en los ejemplos que Salesforce tiene disponible en su web oficial, aun así vamos a compartirlas aquí con vosotros.
ForceConnectionStringBuilder
public class ForceConnectionStringBuilder
{
public string Username { get; set; }
public string Password { get; set; }
public string Token { get; set; }
public string ConnectionString
{
get
{
return String.Format(«Username={0}; Password={1}; Token={2};»);
}
set
{
string[] pairs = value.Split(‘;’);
foreach (string pair in pairs)
{
if (String.IsNullOrWhiteSpace(pair))
continue;
string[] parts = pair.Split(‘=’);
if (parts.Length != 2)
{
throw new ApplicationException(«Malformed connection string parameter. The connection string should be formated list this: username=value1; password=value2; token=value3;»);
}
string key = parts[0].Trim();
string setting = parts[1].Trim();
if (String.IsNullOrEmpty(key) || String.IsNullOrEmpty(setting))
continue;
switch (key.ToLower())
{
case «username»:
Username = setting;
break;
case «password»:
Password = setting;
break;
case «token»:
Token = setting;
break;
default:
throw new ApplicationException(String.Format(«Invalid parameter {0}», parts[0]));
}
}
}
}
public ForceConnectionStringBuilder()
{
}
public ForceConnectionStringBuilder(string connectionString)
{
ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings[connectionString];
if (settings != null)
{
ConnectionString = settings.ConnectionString;
}
else
{
ConnectionString = connectionString;
}
}
}
ApiService
public class ApiService : IDisposable
{
public static Dictionary<Guid, List<WS_Enterprise.sObject>> asyncResults;
private WS_Enterprise.SforceService salesforceService;
const int defaultTimeout = 30000;
public ApiService()
{
salesforceService = new WS_Enterprise.SforceService();
salesforceService.Timeout = defaultTimeout;
asyncResults = new Dictionary<Guid, List<WS_Enterprise.sObject>>();
}
public ApiService(int timeout)
: this()
{
salesforceService.Timeout = timeout;
}
public List<T> Query<T>(string soql) where T : sObject, new()
{
List<T> returnList = new List<T>();
SetupService();
QueryResult results = salesforceService.query(soql);
for (int i = 0; i < results.size; i++)
{
T item = results.records[i] as T;
if (item != null)
returnList.Add(item);
}
return returnList;
}
public T QuerySingle<T>(string soql) where T : sObject, new()
{
T returnValue = new T();
SetupService();
QueryResult results = salesforceService.query(soql);
if (results.size == 1)
returnValue = results.records[0] as T;
return returnValue;
}
public Guid QueryAsync(string soql)
{
SetupService();
salesforceService.queryCompleted += salesforceService_queryCompleted;
Guid id = Guid.NewGuid();
salesforceService.queryAsync(soql, id);
return id;
}
void salesforceService_queryCompleted(object sender, queryCompletedEventArgs e)
{
Guid id = (Guid)e.UserState;
List<sObject> results = e.Result.records.ToList();
if (asyncResults.ContainsKey(id))
asyncResults[id].AddRange(results);
else
asyncResults.Add((Guid)e.UserState, results);
}
public SaveResult[] Update(sObject[] items)
{
SetupService();
return salesforceService.update(items);
}
public UpsertResult[] Upsert(string externalID, sObject[] items)
{
SetupService();
return salesforceService.upsert(externalID, items);
}
public SaveResult[] Insert(sObject[] items)
{
SetupService();
return salesforceService.create(items);
}
public DeleteResult[] Delete(string[] ids)
{
SetupService();
return salesforceService.delete(ids);
}
public UndeleteResult[] Undelete(string[] ids)
{
SetupService();
return salesforceService.undelete(ids);
}
private void SetupService()
{
ForceConnection connection = new ForceConnection(«SalesforceLogin»);
salesforceService.SessionHeaderValue =
new SessionHeader() { sessionId = connection.SessionID };
salesforceService.Url = connection.ServerUrl;
}
public void Dispose()
{
salesforceService.Dispose();
}
}
ForceConnection
public class ForceConnection
{
public string SessionID { get; set; }
public string ServerUrl { get; set; }
public ForceConnection(string connectionString)
{
ForceConnectionStringBuilder connectionBuilder =
new ForceConnectionStringBuilder(connectionString);
Login(connectionBuilder.Username, connectionBuilder.Password, connectionBuilder.Token);
}
public ForceConnection(string username, string password, string securityToken)
{
Login(username, password, securityToken);
}
private bool Login(string username, string password, string securityToken)
{
try
{
using (WS_Enterprise.SforceService service = new WS_Enterprise.SforceService())
{
WS_Enterprise.LoginResult loginResult =
service.login(username, String.Concat(password, securityToken));
this.SessionID = loginResult.sessionId;
this.ServerUrl = loginResult.serverUrl;
}
return true;
}
catch (Exception ex)
{
return false;
}
}
}
Una vez hemos creado nuestro fichero con estás tres clases vamos a introducir una nueva key en el fichero de configuración con nuestro usuario, contraseña y Token, en nuestro caso usamos el fichero app.config, pero vosotros podréis poner estos datos en cualquier lugar del código de forma «hardcodeada», no es lo aconsejable pero para las pruebas podréis hacerlo como queráis. En la sección connectionStrings del fichero hemos introducido nuestros datos:
<connectionStrings>
<add name=»SalesforceLogin» connectionString=»Username=[TU_USUARIO]; Password=[TU_CONTRASEÑA]; Token=[TU_TOKEN];» providerName=»NetToSalesforce.Salesforce»/>
</connectionStrings>
Hecho esto ya estamos listos para consumir directamente los servicios de Salesforce. Tenemos 2 métodos, uno que crea la conexión y el otro que realiza la consulta directamente vamos con el de conexión:
ForceConnection connection = new ForceConnection(«SalesforceLogin»);
Esté método solo tiene está línea lo hemos separado por probar, quizás lo más eficiente es que esté todo junto en un mismo método, esto ya lo dejo a vuestra elección yo aún estoy realizando pruebas. El segundo método hace lo siguiente:
// Se usa el lenguaje SOQL, muy similar a SQL aquí un pequeño ejemplo
string soql = @»SELECT u.Email, u.FirstName, u.LastName, u.Name FROM User u»;
//WHERE c.Email <> »»;
// Informamos los valores necesarios para la conexión, tened en cuenta que usamos la SessionID en lugar del usuario, pwd y Token
// Estos tres valores se utilizan para obtener la URL y la SessionID que usamos aquí en el método anterior
SforceService ap = new SforceService();
ap.Url = connection.ServerUrl;
ap.SessionHeaderValue = new SessionHeader();
ap.SessionHeaderValue.sessionId = connection.SessionID;
// Realizamos la llamada
QueryResult result = ap.query(soql);
// Mostramos en pantalla que es lo que nos ha devuelto la consulta
if(result.size > 0)
{
foreach(object user in result.records)
{
Console.WriteLine(«Usuario: » + ((User)user).Name);
}
}
Console.ReadLine();
Aquí tenéis este sencillo ejemplo que espero os sea de ayuda, como sabéis podéis dejar cualquier comentario y si tenéis dudas os intentaremos ayudar en lo que podáis. Ahora os dejo un error que me he encontrado mientras realizaba esta conexión y que espero no os encontréis.
Mientras intentamos conectar a Salesforce.com os puede devolver algo así como:
Unable to generate a temporary class (result=1). Cannot convert type ‘xxx.Salesforce.ListViewRecordColumn[]’ to ‘xxx.Salesforce.ListViewRecordColumn’
Esto viene dado por la forma en que .NET genera la clase Reference.cs, siguiendo estos pasos se soluciona de forma sencilla, solo necesitamos modificar nuestro fichero descargado en el paso 2 desde Salesforce (xml del webservice), buscar por » <complexType name=»ListViewRecord»>» en el fichero y añadir la línea «<xsd:attribute name=»tmp» type=»xsd:string»/>», el objeto quedaría así:
<complexType name=»ListViewRecord»>
<sequence>
<element name=»columns» type=»tns:ListViewRecordColumn» maxOccurs=»unbounded»/>
</sequence>
<xsd:attribute name=»tmp» type=»xsd:string»/>
</complexType>
Guardamos el fichero y nos vamos a .NET, damos a actualizar la referencia del Webservice y todo solucionado, tened cuidado si creáis algún campo nuevo y os volvéis a descargar el WDSL, deberéis volver a hacerlo antes de actualizar la referencia.