La nueva API de Office 365 Unificada

Buenas tardes a todos,

Esta semana desde Microsoft se lanzó la noticia de la aparición de una nueva API de Office 365 unificada, que busca facilitar y estandarizar todas las operaciones que se pueden realizar contra los servicios de Office 365 a través de su API, además de añadir nuevas opciones y servicios a la misma. Ayer tuve la oportunidad de echar un vistazo a esta API unificada y me pareció que mejora mucho la versión anterior y particularmente me gusta más esta línea que la que llevaba hasta ahora.

Hoy os voy a contar en qué consisten todas estas mejoras y enseñaros un ejemplo de cómo trabajar con ella que podréis comparar con un ejemplo que publiqué hace un tiempo sobre el uso de la API de Office 365 que hasta ahora teníamos vigente. Esta versión de API unificada está aún en Preview, por lo que aún pueden haber mejoras o modificaciones, no obstante lo que veremos hoy es la filosofía de la misma.

Antes de entrar de lleno os voy a dejar una recopilación de enlaces que creo que son fundamentales para entender la nueva API Unificada.

http://dev.office.com/unifiedAPIs

https://msdn.microsoft.com/office/office365/HowTo/office-365-unified-api-overview#msg_how_learn_unified_api

https://msdn.microsoft.com/en-us/office/office365/howto/get-started-with-office-365-unified-api

https://msdn.microsoft.com/en-us/office/office365/howto/office-365-unified-api-reference

http://www.vrdmn.com/2015/05/using-office-365-unified-api-in-aspnet.html

¿En qué consiste esta API de Office 365 Unificada?

La nueva API de Office 365 Unificada pretende seguir ofreciendo a los desarrolladores la oportunidad de utilizar los servicios de la suite de productividad de Office 365, pero de forma unificada y a través de una API REST, que nos permite hacer todas las operaciones de una forma mucho más sencilla y alineada con el “standard” de las API de servicios web hoy en día.

Además añaden acceso a nuevos servicios dentro de Office 365, quedando ahora el mapa de entidades de la siguiente forma:

O365_unified_API_entities

Con la API tradicional para usar algunas de las entidades disponibles teníamos siempre que realizar las siguientes acciones:

  • Usar el servicio discovery para encontrar los endpoints
  • Determinar la URL de los servicios a los que la app se quería conectar
  • Obtener y manejar el token de cada servicio y hacer la petición a cada uno de ellos directamente

Desde ahora, por medio de esta nueva API unificada, evitaremos estas operaciones para sustituirlas por una simple API REST. Además no es necesario descubrir y navegar a cada uno de los diferentes endpoints para cada servicio ya que todos comparten uno mismo, con lo que comparten también el sistema de autenticación y autorización. Estos nos permite además fácilmente consumir desde cualquier herramienta o lenguaje de programación esta API.

¿Cómo podemos usar la API?

Vamos a ver ahora algunas de las URLs que tenemos disponible en la API REST unificada para acceder a las distintas entidades de Office 365.

La Url de referencia al servicio la tenemos en la siguiente dirección https://graph.microsoft.com/{version}. A partir de aquí, voy a dejar distintos ejemplo de cómo podemos usar esta referencia para acceder a las distintas entidades.

Acceder a One Drive
https://graph.microsoft.com/{version}/me/files
Accediendo al servicio de Exchange OnLine
https://graph.microsoft.com/{version}/me/Messages
Accediendo a los usuarios de mi tenant
https://graph.microsoft.com/{version}/{myOrganization}/users
Accediendo a los grupos de Office 365
https://graph.microsoft.com/{version}/{myOrganization}/groups
Aplicaciones registradas en Azure AD
https://graph.microsoft.com/{version}/{myOrganization}/applications

Obviamente estas son solo algunas de las URLs principales de los servicios. En el enlace a la referencia de la API podemos ver todas las opciones y todas las oportunidades que tenemos disponibles y os animo a que naveguéis por ahí para ver todo lo que se puede hacer, que como veréis es mucho.

Ejemplo de uso de la nueva API de Office 365 Unificada

Para terminar con el artículo, vamos a ver un ejemplo de código de cómo podemos usar en una aplicación MVC esta nueva API REST.

Para poder usar la API REST unificada de Office 365, tenemos en primer lugar que registrar nuestra App en Azure Active Directory tal y como se comentaba en la entrada que os enlazo a continuación:

Registrando una aplicación en Azure Active Directory

Lo único que tenemos que hacer diferente a cuando íbamos a usar la versión anterior es a la hora de establecer los permisos para la app que estamos registrando. En esta ocasión deberemos añadir los permisos para la nueva API unificada, como se ve en la imagen siguiente, tenemos esta opción disponible.

unifiedAPI

Una vez que ya hemos registrado adecuadamente nuestra app, podemos pasar al código de la aplicación. En la demo que vamos a ver, voy a leer los archivos y directorios de mi One Drive, tal y como hice en el ejemplo con la API anterior. He creado una aplicación MVC y todo el código por simplicidad en el ejemplo, lo he puesto en la misma acción del controlador, aunque si accedéis al último enlace de los que indiqué arriba, podréis ver como se podría organizar el código a través de varias acciones en una aplicación MVC. Vamos a ver el código y luego lo explicaré destacando los puntos más importantes del mismo:

public async Task<ActionResult> Files(string  code)
{
       string authCode = Request.Params["code"];

       // The url in our app that Azure should redirect to after successful signin
       string redirectUri = Url.Action("Files", "Home", null, Request.Url.Scheme);

       var authContext = new AuthenticationContext("https://login.microsoftonline.com/common");

       if(authCode == null)
       {

           // Generate the parameterized URL for Azure signin
           Uri authUri = authContext.GetAuthorizationRequestURL("https://graph.microsoft.com/", ConfigurationManager.AppSettings["ida:ClientID"],
               new Uri(redirectUri), UserIdentifier.AnyUser, null);

           // Redirect the browser to the Azure signin page
           return Redirect(authUri.ToString());
       }

       ClientCredential creds = new ClientCredential(
           ConfigurationManager.AppSettings["ida:ClientID"],
           ConfigurationManager.AppSettings["ida:Password"]);

       // Get the token
       var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
           authCode, new Uri(redirectUri), creds, "https://graph.microsoft.com/");

       // Save the token
       string accesstoken = authResult.AccessToken;

       // Make request
       using (var client = new HttpClient())
       {
           string endpointUri, resourceId;

           using (var request = new HttpRequestMessage(HttpMethod.Get,
             "https://graph.microsoft.com/beta/me/files"))
           {
               request.Headers.Add("Authorization", "Bearer " + accesstoken);

               using (var response = await client.SendAsync(request))
               {
                   List<OneDriveFile> modelList = new List<OneDriveFile>();

                   var content = await response.Content.ReadAsStringAsync();
                   foreach (var item in JObject.Parse(content)["value"])
                   {
                       OneDriveFile newFile = new OneDriveFile
                       {
                          Name = item["name"].ToString(),
                          Size = double.Parse(item["size"].ToString())
                       };

                       modelList.Add(newFile);
                   }

                   ViewBag.files = modelList;

                }
            }
       }

       return View();
}

Vamos a destacar tres aspectos fundamentales de este código:

  • Lo primero que haremos en nuestro código es comprobar si nos hemos autenticado contra Azure Directory, tal y como hacíamos con la API tradicional, y en el caso de que no lo esté nos redirigirá a la página correspondiente para autenticarnos, para todo el proceso de autenticación y autorización se va a usar como hacíamos anteriormente la librería ADAL que tenemos en Nuget. (Líneas 10 a 19)
  • El siguiente paso, es capturar el token de acceso para obtener autorización al hacer las peticiones REST. (Líneas 26 a 30)
  • En el último paso usaremos un cliente Http que nos proporciona C# para hacer llamadas a la API y procesar las respuestas. (Líneas 33 a 62)

Las diferencias con respecto al uso de la versión anterior de la API para Office 365 basada en el servicio discovery la podemos ver con la entrada que publiqué sobre este tema. Nos obstante son obvias, por la facilidad con la que ahora podemos acceder a la API de Office, sin tener que hacer todos los pasos para simplemente acceder al servicio que eran necesarios. Esta es la entrada que os comentaba.

Usando la API de Office 365

Y nada más por hoy, como siempre espero que os resulte interesante, a mi esta nueva versión unificada me ha gustado mucho. Os recomiendo echar un vistazo por todos los enlaces que os he comentado, y ver las oportunidades que nos ofrece esta API, como dije al principio me parecen muy interesantes.

Un saludo a todos.

Anuncios

JSON Light en API REST de SharePoint

Muy buenas a todos,

Uno de los medios de que disponemos a la hora de acceder a la información de SharePoint es por medio de la API REST. Hoy he conocido una de las opciones que nos ofrece SharePoint para, a través de su API REST, obtener respuestas más ligeras y sencillas. Esto es el soporte para JSON Light. Os dejo un extracto de la entrada del blog oficial de Office donde se explica esta nueva posibilidad de que disponemos.

One of the big bits of feedback we got from developers using the REST SharePoint API was about the payload of the data that was returned. Our response was to add support for JSON Light. JSON Light is an open standard that allows developers to provide in the header of the request how much metadata is returned

Os dejo el enlace a la entrada del blog oficial para el que quiera ver el post completo

http://blogs.office.com/2014/08/13/json-light-support-rest-sharepoint-api-released/

¿Y cómo usamos esto?. Habitualmente cuando hacemos una llamada a API REST, en el header de la petición incluimos la siguiente línea accept: application/json;odata=verbose. Con el soporte para JSON Light, disponemos de nuevas opciones para el parámetro odata que nos devolverán peticiones con distinta cantidad de metadatos en función de la opción que hayamos seleccionado. Las opciones de que disponemos son las siguientes:

  • odata=verbose: Este es el modo que veníamos usando hasta ahora
  • odata=minimalmetadata: Este modo nos devuelve una cantidad limitada de metadatos
  • odata=nometadata: Es el modo más ligero de que disponemos
  • Si no indicamos ningún valor para odata, por defecto tomará el valor minimalmetadata

Bueno, hasta aquí, prácticamente solo me he dedicado a transcribir en español y de una forma resumida lo que viene en el artículo del blog. Lo que he querido hacer es comprobar por mí mismo cómo funcionaban las distintas opciones, la información que devolvía cada una y los tiempos de respuesta. Y eso es lo que os quería enseñar en la entrada.

He creado una SharePoint Hosted App en la que he incluido el siguiente código.

$(document).ready(function () {
    
    var appweburl = GetParameter("SPAppWebUrl");
    var hostweburl = GetParameter("SPHostUrl");

    var executor = new SP.RequestExecutor(appweburl);

    executor.executeAsync({
        method: "GET",
        url: appweburl + "/_api/SP.AppContextSite(@target)/web/Lists/getbytitle('Noticias')/Items?@target='" + hostweburl + "'",
        headers: {
            "accept": "application/json;odata=verbose",
            "content-type": "application/json;odata=verbose"
        },
        success: function (data) {
            console.log(JSON.parse(data.body));
        },
        error: function (data) {
        }
    });

    executor.executeAsync({
        method: "GET",
        url: appweburl + "/_api/SP.AppContextSite(@target)/web/Lists/getbytitle('Noticias')/Items?@target='" + hostweburl + "'",
        headers: {
            "accept": "application/json;odata=minimalmetadata",
            "content-type": "application/json;odata=minimalmetadata"
        },
        success: function (data) {
            console.log(JSON.parse(data.body));
        },
        error: function (data) {
        }
    });

    executor.executeAsync({
        method: "GET",
        url: appweburl + "/_api/SP.AppContextSite(@target)/web/Lists/getbytitle('Noticias')/Items?@target='" + hostweburl + "'",
        headers: {
            "accept": "application/json;odata=nometadata",
            "content-type": "application/json;odata=nometadata"
        },
        success: function (data) {
            console.log(JSON.parse(data.body));
        },
        error: function (data) {
        }
    });


});

function GetParameter(paramToRetrieve) {
    var params =
            document.URL.split("?")[1].split("&");
    var strParams = "";
    for (var i = 0; i < params.length; i = i + 1) {
        var singleParam = params[i].split("=");
        if (singleParam[0] == paramToRetrieve)
            return decodeURIComponent(singleParam[1]);
    }
}

El código como veréis es muy sencillo. Hago la misma petición API REST, pero cada una con un valor para odata distinto y lo imprimo en consola para ver que devuelve. Este es el resultado

odata=verbose

verbose

odata=minimalmetadata

minimalmetadata

odata=nometadata

nometadata

Como podéis ver, la complejidad de las respuestas disminuye con cada tipo que usamos, cada una de ellas contiene menos información y los objetos que nos devuelve son más simples, por lo que, en ocasiones, en función de la información de la respuesta a la que queramos acceder, podemos usar peticiones que nos devuelve datos más ligeros.

¿Y como influye esto en el tamaño de la respuesta?, eso era otra de las cosas que quería comprobar, ya que como sabéis al trabajar del lado del cliente, uno de los aspectos más críticos, es intentar tener respuestas lo más ligeras posibles. Os dejo una captura donde se puede ver como se comporta cada una de las peticiones.

responsesize

Como podéis observar, las peticiones cada vez son más ligeras en función del modo que utilizamos.

Y nada más por ahora, quería probar y contar estas nuevas opciones que he descubierto hoy a la hora de usar la API REST y contaros lo que iba viendo. Espero que os sirva de ayuda por si no lo sabíais.

Saludos a todos.

Campos de tipo DateTime y API REST en SharePoint

Hola a todos,

Como os comentaba ayer, para poner en práctica los últimos avances que he tenido con las aplicaciones de SharePoint, he desarrollado una SharePoint Hosted App que utiliza, para la comunicación y las operaciones con SharePoint, API REST.

RoomReservation: Mi primera SharePoint Hosted App

Código en GitHub

Lo que hoy os quiero contar, es una de las cosas que más me ha entretenido en el desarrollo de la aplicación y tiene que ver con la manipulación de campos de tipo DateTime y la API REST. En sí, no tiene nada especialmente complejo, aunque a continuación veremos las particularidades que hay que tener en cuenta.

Filtrando campos de tipo DateTime en una petición API REST

El filtrado de campos de tipo fecha a través de API REST es muy sencillo, tan solo hay que pasar la fecha en el formato correcto:

https:///_api/web/lists/getbytitle(list)/items?$filter=DateFieldName eq datetime’yyyy-mm-ddThh:mm:ss’

El formato de la fecha como podéis ver es el siguiente “yyyy-mm-ddThh:mm:ss”

Datos devueltos en una consulta de API REST de tipo DateTime

Con los datos de tipo DateTime que nos devuelve una consulta API REST, empieza a haber algunas cosas a tener en cuenta. SharePoint, a través de API REST, nos devuelve todos los campos de tipo DateTime formateados como UTC. Por tanto, si mostramos los datos tal cual nos los devuelve API REST y nuestra configuración regional es distinta de la UTC (por ejemplo, en la península es UTC +1), no veremos los resultados que esperábamos. Por ejemplo, si hemos almacenado la siguiente fecha 2015-03-03T10:30:00 en el calendario de SharePoint y estamos en UTC+1, nos devolverá 2015-03-03T09:30:00.

Para solventar esto tenemos que aplicar la corrección correspondiente al TimeOffSet con respecto a la hora en formato UTC. Os pongo el código que he usado yo en mi aplicación y os cuento lo más importante. Para manipular las fechas he utilizado una librería de javascript llamada moment.js que añade algunas funcionalidades extra al objeto de tipo Date básico de Javascript, y que permitirá hacer sumas y restas a fechas.


var offset;

function GetTimeOffset(){
        var deferred = $.Deferred();

        var appweburl = MyApp.AppSPUtils.GetQueryStringParameter("SPAppWebUrl");
        var hostweburl = MyApp.AppSPUtils.GetQueryStringParameter("SPHostUrl");

        executor = new SP.RequestExecutor(appweburl);

        executor.executeAsync({
            method: "GET",
            url: appweburl + "/_api/SP.AppContextSite(@target)/web/regionalsettings/timezone?@target='" + hostweburl + "'",
            headers: {
                "accept": "application/json;odata=verbose",
                "content-type": "application/json;odata=verbose"
            },
            success: function (data) {
                var jsonObject = JSON.parse(data.body);
                offset = jsonObject.d.Information.Bias;

                deferred.resolve();
            },
            error: function (data) {
                deferred.reject();
            }
        });

        return deferred.promise();
    }
function ConvertToGetLocalTime(UTCdate) {

        var date = moment(UTCdate);

        if (date.utcOffset() > 0)
            date = date.subtract(offset, 'm');
        else
            date = date.add(((-1) * offset), 'm');

        return date.toISOString();
    }

El primer código que vemos, nos permite obtener por medio de API REST, el TimeOffSet de la configuración regional que tenemos en nuestro sitio de SharePoint con respecto a UTC. Si por ejemplo, estamos en UTC+1, el valor que obtendremos será -60, que son los minutos que habría que restar a la hora, para convertirla a formato UTC.

Por tanto, una vez obtenido el offset de nuestra configuración regional de sitio, aplicamos la conversión que vemos en la función ConvertToGetLocalTime que recibirá un string con el formato de fecha obtenido de la consulta por API REST (yyyy-mm-ddThh:mm:ss) y devuelve una fecha con el mismo formato y adaptado a la hora local configurada en nuestro sitio.

Añadiendo elementos a una lista con campos de tipo DateTime con API REST

Cuando lo que queremos es usar POST para añadir a través de API REST un elemento que tiene campos de tipo DateTime, el problema es el contrario. Lo que la API REST considera que recibe es una fecha en formato UTC y al almacenarla, la convertirá en el formato correspondiente a la configuración regional establecida. Por ejemplo, si pasamos como campo 2015-03-03T10:30:00 y tenemos una configuración UTC+1, lo que se almacenará será 2015-03-03T11:30:00.

Para solucionar esto, aplicaremos la configuración inversa que en el caso anterior por medio de la siguiente función:

function ConvertToPostLocalTime(UTCdate) {

        var date = moment(UTCdate);

        if (date.utcOffset() > 0)
            date = date.add(offset, 'm');
        else
            date = date.subtract(((-1) * offset), 'm');

        return date.toISOString();
    }

Y esto es todo por hoy, como podéis ver, trabajar con fechas y API REST requiere tener en cuenta la configuración regional y aplicar las conversiones a las fechas que correspondan, ya que ésta siempre trabaja con UTC tanto a la hora de hacer POST como GET.

Espero que os sea útil y os ahorréis ese tiempo de más que me ha llevado a la hora de trabajar con esto.

Saludos a todos.

RoomReservation: Mi primera SharePoint Hosted App

Muy buenas a todos,

Llevaba algunos días sin publicar nada en el blog, y es que he estado algo ocupado. Durante los meses de enero y febrero he estado trabajando en conocer el nuevo modelo de desarrollo de SharePoint, en qué consiste el nuevo modelo de Apps y que son y como se usan las API Cliente. He escrito algunos artículos sobre estos temas, relacionados con la API REST, CSOM y cómo crear SharePoint Hosted Apps. Pero para poder profundizar más en su conocimiento decidí crear una aplicación de SharePoint que tuviera en cuenta todo lo que había estado aprendiendo.

Por esto surge principalmente RoomReservation. RoomReservation es una SharePoint Hosted App que nos proporciona un sistema muy sencillo de reserva de salas. La aplicación, consiste en un formulario y un calendario. El formulario nos permite rellenar los campos básicos para hacer una reserva de salas, nos muestra la fecha, las horas y las salas disponibles. La utilidad de la aplicación, es que a medida que vamos completando los datos de fecha y hora las salas disponibles se van actualizando, permitiéndonos solo reservar aquellas salas que realmente están libres en el horario que estamos solicitando.

roomreservationapplication

Esta es la interfaz de la aplicación. Para que funcione correctamente, necesita dos lista, una lista personalizada con los recursos o salas disponibles y otra lista de tipo calendario donde se almacenarán las reservas realizadas.

El modelo de desarrollo que he seguido para la App ha sido el siguiente:

  • Todo el acceso a datos de SharePoint se ha hecho a través de API REST.
  • Las listas que son necesarias, son provisionadas remotamente por la propia aplicación, si tras instalarla, no existían ya. Para ello se ha usado también API REST, la aplicación evalúa si existen las listas, si no, automáticamente te redirige a una página donde tienes la opción de crear las listas.

install

Todo el código de la aplicación está en GitHub en el siguiente enlace:

https://github.com/jcroav/room-reservation

A partir de ahora comenzaré a utilizar más a menudo GitHub para subir mis desarrollos, e incluso ejemplos y demos que vaya publicando en mi blog.

Os animo a todos a que ayudéis RoomReservation o propongáis aspectos de mejora, serán siempre bienvenidos. Yo también intentaré aprovechar todo lo que he aprendido y lo que vaya a aprender próximamente para seguir mejorando esta App y desarrollando algunas nuevas.

¿Cuáles son los aspectos de mejora?

Como por supuesto no creo que sea perfecta, ni mucho menos, tengo ya previstas algunas áreas de mejora. Próximamente espero seguir trabajando en mejorar la interfaz gráfica, hacerla más adaptativa, añadir alguna funcionalidad extra y trabajar en los idiomas de la App, ya que ahora mismo solo está en inglés.

Pues nada más de momento, lo dicho, espero que os sirva como siempre, y que lo encontréis útil.

Un saludo y buena semana.

Usando Promises con JQuery y API REST de SharePoint

Hola de nuevo a todos,

Empezando a trabajar con Javascript de forma intensiva, me he topado con la necesidad de usar un concepto del que había oído hablar hace tiempo, pero que hasta ahora no me había hecho falta. Este concepto es lo que se conoce como Promesas o Promises.

Cuando estamos trabajando con varias APIs a través de peticiones asíncronas o con una API pero realizamos varias peticiones a la vez, al final te puedes encontrar con el problema de que, peticiones que necesitas que se ejecuten después que otras lo están haciendo antes, por la propia naturaleza de las llamadas asíncronas. Puede ocurrir también que, funciones que necesitas que se ejecuten al finalizar las llamadas asíncronas lo estén haciendo antes. Para evitar esto puedes pensar en un primer momento en una montón de llamadas asíncronas anidadas en el callback de éxito de las llamadas asíncronas o por el contrario, usar las promesas.

¿Que son las promesas?

Para explicar lo que son las promesas, voy a tomar prestado la explicación de la siguiente entrada de un blog que he encontrado:

http://www.funcion13.com/2012/03/26/comprendiendo-promesas-y-deferreds-en-jquery/

La promesa (promise) en sí, es un objeto que representa un evento en el tiempo, como la llegada de datos de una petición asíncrona o el fin del procesamiento de todos los datos obtenidos en una función asíncrona. Un objeto promise es una versión de solo lectura de un objeto deferred.

Los deferred comienzan en estado pendiente, y es lógico si lo pensáis, pudiendo pasar a estado resuelto (la tarea se completó) o rechazado (si es que falló). En el momento en que un deferred cambie su estado a resuelto o rechazado no podrá cambiar nunca más.

Vamos a usar las promesas para manejar peticiones asíncronas, y controlar que se reciba una respuesta sea correcta o no. Además, con JQuery, podremos controlar, la respuesta de varias peticiones asíncronas por medio de promesas que se ejecutan simultáneamente. El código javascript, además, quedará mucho más ordenado.

Os dejo algunos enlaces que he usado yo para entender bien el concepto de promises.

http://www.danieldemmel.me/blog/2013/03/22/an-introduction-to-jquery-deferred-slash-promise/

http://joseoncode.com/2011/09/26/a-walkthrough-jquery-deferred-and-promise/

http://api.jquery.com/deferred.promise/

¿Cómo se usan las promesas?

En el ejemplo que os voy a mostrar a continuación, he usado las promesas para trabajar con la API REST de SharePoint. En mi caso, en una aplicación que estoy desarrollando, necesitaba hacer varias peticiones API REST para saber si varias listas existían para, en función de si lo hacían o no indicarlo al usuario para darle la oportunidad de crearlas.

El problema me surgía por la dificultad de controlar el resultado de las peticiones, ya que al ser asíncronas, las evaluaciones de su resultado se hacían antes de que la petición se hubiera realizado realmente, por lo que no funcionaba como quería que lo hiciera.

Para ello he usado el concepto de promesas, por medio de ellas, puedo controlar que todas las peticiones se hagan y den un resultado, y esperar hasta que se obtenga la respuesta de todas para llevar la acción que corresponda. Voy a mostraros el código y posteriormente explicarlo, en mi caso he usado la librería JQuery y el objeto Deferred de la misma:

function ExistList(listname) {

        var deferred = $.Deferred();

        var appweburl = GetQueryStringParameter("SPAppWebUrl");
        var hostweburl = GetQueryStringParameter("SPHostUrl");

        var executor = new SP.RequestExecutor(appweburl);

        executor.executeAsync({
            method: "GET",
            url: appweburl + "/_api/SP.AppContextSite(@target)/web/Lists/GetByTitle('" + listname + "')?@target='" + hostweburl + "'",
            headers: { "Accept": "application/json; odata=verbose" },
            success: function (data) {
                deferred.resolve();
            },
            error: function (data) {
                console.log(data);
                deferred.reject();
            }
        });

        return deferred.promise();
    }

function Validation(){

            var list1 = "List1";
            var list2 = "List2";

            $.when(ExistList(list1), ExistList(list2))
             .done(function () {

             })
             .fail(function () {
                 RedirectApp("Install.aspx");
             });
                
        }
  • El primer aspecto importante se encuentra en la función ExistList y es el objeto deferred, se crea al inicio de la función y al final de la función se devuelve la promesa. Dentro de la función se hace la petición asíncrona y en el callback de éxito y de error de la misma llamada, se indica si la promesa se ha cumplido (por medio de deferred.resolve()) o no (por medio de deferred.reject()) respectivamente.
  • En la función de validación el primer elemento importante es la llamada $.when() a la que le indicaremos como parámetro (tal y como podemos ver), todas las promesas que queremos que espere a obtener resultado. Los callback no se ejecutarán hasta que no se haya recibido la respuesta de todas las promesas indicadas como parámetro de la llamada.
  • En el caso de que todas las promesas hayan respondido positivamente se utiliza el callback de la llamada done.
  • En el caso de que alguna de las promesas haya respondido negativamente se ejecuta el callbak de la llamada fail

En ocasiones también podremos encontrarnos que se usa la llamada then que lleva como parámetros los dos callback de éxito y error, pero a mi me ha resultado más claro usar la llamada done y fail.

Cuando trabajamos con JQuery y usamos la llamada $.ajax(), ya de por sí devuelve una promesa por lo que no debemos encapsularla en una función para crear el objeto deferred.

Y con esto espero que si alguien no sabía o no había trabajado habitualmente con promesas, encuentre aquí como trabajar con ellas. Aunque el trabajo con promesas no es una novedad, hasta ahora no había necesitado trabajar con ellas, me ha resultado muy fácil, además de que el código queda muchísimo más limpio y organizado.

Un saludo a todos.

Añadiendo elementos a una lista usando la API REST de SharePoint 2013

Muy buenas a todos.

Hoy quiero dar un paso más en el uso de la API REST de SharePoint 2013 y el conocimiento de la misma. Hasta ahora, solo la había usado para obtener datos de SharePoint: de usuarios, listas y sitios. Lo que hoy quiero mostrar en este post es, cómo añadir a través de API REST elementos a una lista. Os dejo en primer lugar algunos enlaces a post donde trataba sobre todo lo anterior que os comentaba

Acceso a datos desde una Aplicación en SharePoint 2013 usando API Rest

Introducción a la API REST de SharePoint 2013 (Usuarios y grupos)

Introducción a la API REST de SharePoint 2013

Para poder cumplir el objetivo que me he propuesto, voy a crear una SharePoint Hosted App, en la que implementaré todo el funcionamiento. Va a ser una App sencilla, por lo que voy a utilizar la estructura que por defecto se crea con el proyecto, y modificaré los ficheros Default.aspx y App.js.

Además en esta aplicación vamos a añadir una Lista Personalizada que llamaremos Noticias, y que tendrá además del campo Title, uno llamado Subject y otro Body y que añadiremos a través de Visual Studio.

Código del fichero Default.aspx

<%-- Las 4 líneas siguientes son directivas ASP.NET necesarias cuando se usan componentes de SharePoint --%>

<%@ Page Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" MasterPageFile="~masterurl/default.master" Language="C#" %>

<%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<%-- El marcado y el script del elemento Content siguiente se pondrán en el elemento <head> de la página --%>
<asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
    <script type="text/javascript" src="../Scripts/jquery-1.9.1.min.js"></script>
    <script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
    <script type="text/javascript" src="/_layouts/15/sp.js"></script>
    <script type="text/javascript" src="/_layouts/15/SP.RequestExecutor.js"></script>
    <meta name="WebPartPageExpansion" content="full" />

    <!-- Agregue sus estilos CSS al siguiente archivo -->
    <link rel="Stylesheet" type="text/css" href="../Content/App.css" />

    <!-- Agregue el código JavaScript al siguiente archivo -->
    <script type="text/javascript" src="../Scripts/App.js"></script>
</asp:Content>

<%-- El marcado del elemento Content siguiente se pondrá en el elemento TitleArea de la página --%>
<asp:Content ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server">
    Page Title
</asp:Content>

<%-- El marcado y el script del elemento Content siguiente se pondrán en el elemento <body> de la página --%>
<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">

    <div>
        <p id="message">
            <!-- El contenido siguiente se reemplazará con el nombre de usuario cuando ejecute la aplicación; vea App.js -->
            initializing...
        </p>
    </div>

    <label for="title">Título:&nbsp;</label>
<input id="title" type="text" />

    <label for="title">Subtítulo de la noticia:&nbsp;</label>
<input id="subtitle" type="text" />

    <label for="title">Cuerpo:&nbsp;</label>
<textarea id="body" rows="3" cols="30"></textarea>
    


    <button id="submit" >Añadir Nueva Noticia</button>

</asp:Content>

El código de este fichero es muy sencillo, tan solo se ha modificado el mismo para añadir, los inputs que nos van a permitir insertar una nueva noticia y el botón correspondiente y una etiqueta p donde vamos a insertar las listas de forma consecutiva.

Un aspecto que si que es importante está en la línea 18, donde se encuentra la referencia al script RequestExecutor.js con la librería Cross-Domain que vamos a usar para hacer las peticiones con JSOM y API REST.

Código del Fichero App.js

'use strict';

var hostweburl;
var appweburl;

$(document).ready(function () {
    //Get the URI decoded URLs.
    hostweburl =
        decodeURIComponent(
            getQueryStringParameter("SPHostUrl")
    );
    appweburl =
        decodeURIComponent(
            getQueryStringParameter("SPAppWebUrl")
    );

    // resources are in URLs in the form:
    // web_url/_layouts/15/resource
    //var scriptbase = hostweburl + "/_layouts/15/";

    LoadNews(appweburl);

    $("#submit").click(function () {
        var title = $("#title").val();
        var subtitle = $("#subtitle").val();
        var body = $("#body").val();

        var executor = new SP.RequestExecutor(appweburl);

        executor.executeAsync({
            method: "POST",
            url: appweburl + "/_api/web/Lists/GetByTitle('Noticias')/items",
            body: "{'__metadata': {'type': 'SP.Data.NoticiasListItem'},'Title': '" + title + "', 'Subject1': '" + subtitle + "', 'Body1': '" + body + "'}",
            headers: {
                "accept": "application/json;odata=verbose",
                "content-type": "application/json;odata=verbose"
            },
            success: function (data) {
                console.log(data);
                LoadNews(appweburl);
            },
            error: function (data) {
                console.log(data);

            }
        });

        
        return false;
    })
});

function LoadNews(appweburl)
{
    var executor = new SP.RequestExecutor(appweburl);

    executor.executeAsync({
        method: "GET",
        url: appweburl + "/_api/web/Lists/GetByTitle('Noticias')/items",
        headers: { "Accept": "application/json; odata=verbose" },
        success: onLoadSuccess,
        error: onLoadError
    });
}

function onLoadSuccess(data) {
    var jsonObject = JSON.parse(data.body);
    var announcementsHTML = "";

    var results = jsonObject.d.results;
    for (var i = 0; i < results.length; i++) {
        announcementsHTML = announcementsHTML +
            "<p><h1>" + results[i].Title +
            "</h1>" + results[i].Body1 +
            "</p><hr>";
    }

    document.getElementById("message").innerHTML =
        announcementsHTML;
}

function onLoadError() {
    alert("Error loading Data");
}

function getQueryStringParameter(paramToRetrieve) {
    var params =
        document.URL.split("?")[1].split("&");
    var strParams = "";
    for (var i = 0; i < params.length; i = i + 1) {
        var singleParam = params[i].split("=");
        if (singleParam[0] == paramToRetrieve)
            return singleParam[1];
    }
}

En el archivo app.js, para facilitar el desarrollo del código, se usa JQuery. Además, vamos a destacar los siguientes aspectos del mismo:

  • La función LoadNews(), hace una petición REST para obtener las noticias de la lista que se creó anteriormente, y se usa la función onLoadSuccess para leer e imprimir los datos en pantalla
  • En la línea 23, se crea la función que asocia al evento click del botón la solicitud REST para insertar el elemento. Se utiliza el objeto SP.RequestExecutor para hacer la petición
  • La línea 33 también es importante, por medio del parámetro body se le indica a través de JSON, el elemento que se desea añadir. El primer parámetro indica el tipo de dato. Éste, será siempre, dependiendo de la lista, SP.Data.NOMBREDELALISTAListItem.
  • El parámetro url indicará, la dirección de API REST para acceder a los elementos de la lista en donde se quiere añadir el parámetro
  • Como lo que queremos es insertar un elemento, usaremos en esta ocasión como valor del parámetro method de la función POST

Viendo el resultado de la App

Vamos a ver el resultado de probar la App que acabamos de desarrollar.

Captura de pantalla 2015-02-06 a las 0.16.04

A continuación veremos como al pulsar sobre el botón de añadir aparece una nueva noticia en la lista de noticias.

Captura de pantalla 2015-02-06 a las 0.16.24

En el código, cuando la operación se realizaba correctamente, se escribía en consola el resultado de la respuesta. En la siguiente captura lo podéis ver, donde se confirma que la petición devuelve un resultado correcto.

Captura de pantalla 2015-02-06 a las 0.24.00

Y esto es todo por hoy. Espero que os haya resultado interesante como con la API REST se puede, tanto obtener como escribir información en SharePoint. En próximos artículos, quiero centrarme en ejemplos de SharePoint Provider Hosted App y en cómo trabajar con la otra API, CSOM, que me queda por conocer.

Saludos.

Acceso a datos desde una Aplicación en SharePoint 2013 usando API Rest

Muy buenas a todos, En una entrada anterior os contaba cómo se podía crear una app para SharePoint 2013 o SharePoint OnLine y lo hacía por medio de una sencilla aplicación “Hola Mundo”.

Creando una app para SharePoint OnLine

En el post de hoy quiero mostrar las opciones básicas que tenemos para acceder a datos desde una app de SharePoint. Vamos a mostrar tres opciones para el acceso a datos:

  • Acceso a datos dentro del ámbito de la aplicación
  • Acceso a datos dentro de la misma colección de sitios
  • Acceso a datos de colecciones de sitio distintas

Para llevar a cabo este ejemplo, lo primero que se hará es crear el proyecto, crearemos una aplicación para SharePoint de tipo “App for SharePoint”. Vamos a ver cada uno de los distintos tipos de acceso a datos que podemos encontrar y cómo implementar cada uno de ellos.

Para mostrar los resultados, utilizaremos el siguiente código HTML en la página de inicio de la aplicación, Default.aspx

<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
    <div id="renderAnnouncements"></div>
</asp:Content>

En la aplicación usaremos la librería cross-domain para acceder a los datos, lo haremos a través del siguiente código:

var hostweburl;
var appweburl;

$(document).ready(function () {
    hostweburl =
        decodeURIComponent(
            getQueryStringParameter("SPHostUrl")
    );
    appweburl =
        decodeURIComponent(
            getQueryStringParameter("SPAppWebUrl")
    );

    var scriptbase = hostweburl + "/_layouts/15/";

    $.getScript(scriptbase + "SP.RequestExecutor.js", execRequest);
});

function getQueryStringParameter(paramToRetrieve) {
    var params =
        document.URL.split("?")[1].split("&");
    var strParams = "";
    for (var i = 0; i < params.length; i = i + 1) {
        var singleParam = params[i].split("=");
        if (singleParam[0] == paramToRetrieve)
            return singleParam[1];
    }
}

En cada uno de los apartados que vienen a continuación, vamos a redefinir la función execRequest, en cada caso, con el código necesario para obtener la información del lugar deseado. En cada caso, los dos aspectos que van a hacer posible acceder a los datos son, por un lado, los permisos que le proporcionemos a la aplicación y por otro, el valor del parámetro url de la función executeAsync

Acceso dentro del ámbito de la aplicación

Esta es la opción más sencilla con la que nos podemos encontrar, dentro del ámbito de la aplicación es suficiente con los propios permisos inherentes a la propia app, por lo que sólo tenemos que hacer la llamada a través de API REST para obtener la información

function execRequest() {
    var executor = new SP.RequestExecutor(appweburl);

    executor.executeAsync(
        {
            url:
        appweburl +
        "/_api/web/lists/getbytitle('Noticias')/items",
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: successHandler,
            error: errorHandler
        }
    );
}

Acceso dentro de la misma colección de sitios

Otro de los escenarios que se puede presentar es la necesidad de acceder a datos que se encuentran almacenados dentro de la misma colección de sitios. Para que esto sea posible, deberemos establecer los permisos adecuados para la aplicación, lo que se hará a través del archivo AppManifest.xml. En este caso, le daremos permisos para acceder en modo lectura a la colección de sitios, abriendo el archivo y el apartado de permisos seleccionando lo siguiente.

Captura de pantalla 2015-01-28 a las 0.22.48

El código de la función que ejecuta la llamada, quedaría de la siguiente manera:

function execRequest() {
    var executor = new SP.RequestExecutor(appweburl);

    executor.executeAsync(
        {
            url:
        appweburl +
        "/_api/SP.AppContextSite(@target)/web/lists/getbytitle('Noticias')/items?@target='" + hostweburl +"'",
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: successHandler,
            error: errorHandler
        }
    );
}

Acceso a datos de distintas colecciones de sitio

La última de las opciones que vamos a plantear, tiene que ver con la posibilidad de acceder a información que se encuentra almacenada en colecciones de sitio distintas. Para ello, deberemos conceder permisos a nivel de tenant en nuestra AppManifes.xml, tal y como hicimos en el caso anterior, aunque cambiando el tipo de permisos que se concede.

Captura de pantalla 2015-01-28 a las 0.21.57

El código de la función, en este caso quedaría de la siguiente forma:

function execRequest() {
    var executor = new SP.RequestExecutor(appweburl);

    executor.executeAsync(
        {
            url:
        appweburl +
        "/_api/SP.AppContextSite(@target)/web/lists/getbytitle('Noticias')/items?@target='https://DifferentSiteUrl/'",
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: successHandler,
            error: errorHandler
        }
    );
}

A continuación se incluye el código de las funciones successHandler y errorHandler.

function successHandler(data) {
    var jsonObject = JSON.parse(data.body);
    var announcementsHTML = "";

    var results = jsonObject.d.results;
    for (var i = 0; i < results.length; i++) {
        announcementsHTML = announcementsHTML +
            "<p><h1>" + results[i].Title +
            "</h1>" + results[i].nwhz +
            "</p><hr>";
    }

    document.getElementById("renderAnnouncements").innerHTML =
        announcementsHTML;
}

function errorHandler(data, errorCode, errorMessage) {
    document.getElementById("renderAnnouncements").innerText =
        "Could not complete cross-domain call: " + errorMessage;
}

Y esto es todo por hoy, como podéis ver es muy sencillo obtener información en las distintas aplicaciones de SharePoint, en unos días espero poder enseñaros como hacer lo mismo, pero en vez de accediendo por API REST, hacerlo usando JSOM .

Os dejo algunos enlaces de interés que espero que os sean de utilidad y que he utilizado a la hora de escribir el post:

http://blog.ctp.com/2014/06/23/data-access-in-sharepoint-hosted-apps/

https://msdn.microsoft.com/en-us/library/office/fp179927(v=office.15).aspx

https://msdn.microsoft.com/en-us/library/office/fp179897(v=office.15).aspx

Un saludo, hasta la próxima.