Resolviendo el error 403 cuando añadimos elementos por medio de API REST en SharePoint 2013

Hola a todos,

Hoy os quiero mostrar en una breve entrada cómo resolver el error 403 que surge cuando hacemos una petición POST para añadir elementos en una lista a través de la API REST de SharePoint 2013.

En mi caso, estoy trabajando en un proyecto en el que utilizo SharePoint y AngularJS, por eso, los ejemplos de las peticiones que os voy a mostrar, son usando el módulo $http de AngularJS.

Cuando hacemos una petición como esta:

$http({
            url: urlBase,
            method: "POST",
            data: JSON.stringify(likeJSON),
            headers: {
                "accept": "application/json;odata=verbose",
                "content-type": "application/json;odata=verbose",
            }
        });

El resultado que nos devuelve es un error como éste:

“The security validation for this page is invalid and might be corrupted. Please use your web browser’s Back button to try your operation again.”

Para solucionarlo, debemos añadir en la cabecera de la petición el parámetro X-RequestDigest como sigue:

$http({
            url: urlBase,
            method: "POST",
            data: JSON.stringify(likeJSON),
            headers: {
                "accept": "application/json;odata=verbose",
                "content-type": "application/json;odata=verbose",
                "X-RequestDigest": requestDigest,
            }
        });

Esta cabecera contiene información conocida como form digest. Ésta es un objeto que se inserta en una página por SharePoint y es usado para validar las peticiones de cliente

La pregunta ahora es: ¿Cómo conseguimos ese valor?. Pues lo podemos obtener por medio de la siguiente petición:

$http({
        url: "/_api/contextinfo",
        method: "POST",
        headers: {
            "accept": "application/json;odata=verbose",
            "content-type": "application/json;odata=verbose"
        }
    }).then(function (response) {
        requestDigest = response.data.d.GetContextWebInformation.FormDigestValue;
    },
    function () {

    });

Por medio de una petición POST al servicio REST con el EndPoint en la url /_api/contextinfo

De esta forma conseguimos resolver el error 403 que nos devuelven las peticiones POST al servicio REST en SharePoint.

Os dejo un enlace que me ha servido de guía, y en el que me he basado.

http://blogs.msdn.com/b/nadeemis/archive/2012/10/23/tip-handling-http-403-forbidden-when-querying-the-search-rest-service-using-the-postquery-method.aspx

Espero que os haya resultado interesante.

Un saludo

Anuncios

Sharepoint-search: un componente Polymer para usar la búsqueda de SharePoint On-Line

Muy buenas a todos.

En una entrada anterior, os contaba cómo usar la búsqueda de SharePoint a través de su servicio API REST. El enlace ha dicha entrada es la siguiente:

Search Driven Development. Usando la API REST de SharePoint para el servicio de búsqueda

Al final de esta entrada os contaba que como objetivo, tenía el desarrollo de un componente Polymer que permitiera usar este servicio, algo que me resultaba muy útil e interesante. Después de varios días de trabajo, quería compartir con vosotros una primera “versión” de dicho componente. Aún queda trabajo por delante, pero ya hay un punto de partida funcional a partir del cual seguir trabajando.

El componente debía cumplir con los siguientes requisitos:

  • Permitir la posibilidad de usar refinadores.
  • Permitir la posibilidad de establecer opciones de ordenamiento.
  • Controlar la paginación y el número de resultados por página en los resultados de búsqueda.
  • Dar la posibilidad de estilar el contenido del componente en función de como se desee.

El componente “sharepoint-search” nos permite cubrir estos requisitos. Para usarlo, necesitaremos tener cargados los ficheros de Polymer y los propios del componente y utilizaremos la etiqueta de la siguiente manera:

<sharepoint-search tenant="organizer" clientid="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" refinement="author,size,created" ordination="size,created" pageElement="15">
</sharepoint-search>

Para que funcione correctamente, necesitaremos el clientID de una aplicación de Azure AD con los permisos para el servicio de búsqueda de SharePoint concedidos, y obviamente estar logado en nuestra aplicación contra esa aplicación de Azure AD, para ello podemos usar el componente para la autenticación que creé en una entrada anterior y seguir los correspondientes pasos para obtener el clientid:

Registrando una aplicación en Azure Active Directory

login-azuread: Un Componente Web con Polymer y Javascript ADAL

Además deberemos de configurar la aplicación de nuestra Azure AD para permitir el OAuth 2.0 implicit grant flow, lo haremos de la siguiente forma:

  • Acceder a la configuración de la aplicación de Azure AD correspondiente
  • Descargamos el manifiesto
  • Modificamos el parámetro oauth2AllowImplicitFlow dándole el valor true en lugar de false que tiene establecido inicialmente
  • Cargamos de nuevo el manifiesto modificado

El componente “sharepoint-search” permite configurar los siguientes parámetros:

  • tenant: Indicar el tenant al que nos queremos conectar
  • clientid: Clientid de la aplicación con los permisos adecuados
  • refinement: Propiedades para las cuales queremos obtener refinamiento
  • ordination: Propiedades por las que queremos hacer ordenación
  • pageElement: Número de elementos por página a mostrar

Una vez que lo añadimos, funciona como vemos a continuación.

sharepointsearchinit

sharepointsearchsearched

Por defecto, el componente nos muestra los resultados en una lista con el título del documento y la posibilidad de descargar el mismo. Aunque como se indica a continuación, en futuras mejoras, se trabajará en flexibilizar este aspecto del componente.

Os dejo el enlace a mi cuenta de GitHub donde podéis descargar el código del componente web y donde podréis encontrar información sobre cómo usarlo.

https://github.com/jcroav/sharepoint-search

Y esto es todo por hoy, este componente aún tiene áreas de mejora, a las que intentaré dedicarle tiempo en las próximas semanas. Os dejo algunas de las ideas:

  • El componente está pensado para que los resultados de búsqueda usen a su vez un subcomponente para ser mostrados, queda pendiente de desarrollar esa parte.
  • Completar el componente con la funcionalidad de sugerencias
  • Mejorar toda la parte de estilado
  • Refactorizar y trabajar sobre la optimiazación del código para hacer un componente más robusto

Como siempre, todo aquel que quiera colaborar, o quiera usar el componente que os he mostrado hoy para mejorarlo, es libre de hacerlo. El objetivo de estos desarrollos, a parte de para aprender a usar las funcionalidades de Office 365 y SharePoint On-Line, es contribuir a la comunidad.

Un saludo y espero que os sea útil

login-azuread: Un Componente Web con Polymer y Javascript ADAL

Hola a todos,

En la entrada anterior os contaba como se podía usar la librería Javascript ADAL para autenticar una aplicación contra Azure AD usando Javascript y terminaba diciendo que os contaría sobre el componente Polymer que había desarrollado para la autenticación.

Autenticación de nuestra Aplicación usando Javascript ADAL

Lo que he hecho ha sido crear un componente web que permite de una forma sencilla hacer la autenticación y que añadiéndolo a cualquiera de nuestras aplicaciones, podamos gestionar el sign in contra Azure AD de la misma. El código del componente web lo podéis encontrar en mi cuenta de GitHub.

Azuread-login en GitHub

El modo de empleo es muy sencillo, solo tendremos que insertar la siguiente línea en donde queramos tener nuestro modulo de login.

<login-azuread domain="<tenantdomainhere>" clientid="<clientidhere>"
</login-azuread>

Antes, deberemos haber registrado la aplicación en Azure AD, lo que nos proporcionará un client ID. Para que el componente funcione correctamente, deberemos indicar en el atributo domain, el dominio de nuestro tenant y en el atributo clientid el dato obtenido tras el registro de la aplicación en Azure AD.

¿Cuál es el resultado?

Vamos a ver como funciona el componente web una vez lo usamos en un ejemplo.

Cuando un usuario no se ha autenticado.

azureadlogin1

azureadlogin2

Cuando un usuario se ha autenticado
azureadlogin3

Además el componente se ha diseñado de manera que los elementos que lo forman, no se encuentran dentro del Shadow DOM que usa Polymer, sino que están renderizados por el DOM de la página, de manera que podemos estilarlos por medio del CSS de nuestra aplicación. Para ello, solo tenemos que redefinir las clases loginad-name, loginad-a, logoutad-a.

¿Vemos algo de código?

El código es muy sencillo, y aunque lo podéis ver en GitHub os lo pongo aquí y explico algunos detalles. El componente lo forman dos ficheros, uno .html y otro .js

<script src="../../Scripts/polymer.min.js"></script>
<script src="../../Scripts/adal.js"></script>

<polymer-element name="login-azuread" attributes="domain clientID" />
    <template>
        <content select="span"></content>
        <content select="a"></content>
        <content select="a"></content>
    </template>
    <script src="login.js"></script>
</polymer-element>

El fichero .html contiene la definición del componente web. Como véis el template utiliza la etiqueta content, que nos va a permitir que el contenido del componente no esté renderizado en el Shadow DOM y si en el DOM de la página.

window.config = {
    tenant: '',
    clientId: '',
    postLogoutRedirectUri: window.location.origin,
    cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
};

var authContext;

Polymer({
    userName: '',
    domain: '',
    clientID: '',
    ready: function () {
        window.config.tenant = this.domain;
        window.config.clientId = this.clientID;

        authContext = new AuthenticationContext(config);

        var isCallback = authContext.isCallback(window.location.hash);
        authContext.handleWindowCallback();

        if (isCallback &amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp; !authContext.getLoginError()) {
            window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
        }

        // Check Login Status, Update UI
        var user = authContext.getCachedUser();

        if (user)
            this.userName = user.userName;
        else
            this.userName = '';

    },
    attached: function () {
        var name = document.createElement("span");
        name.innerHTML = this.userName;
        name.className = "loginad-name";

        var login = document.createElement("a");
        login.href = "javascript:;";
        login.innerText = "Login";
        login.className = "loginad-a";
        login.addEventListener("click", this.login, true);

        if (this.userName != '')
            login.style.display = "none";
        else
            login.style.display = "inline-block";

        var logout = document.createElement("a");
        logout.href = "javascript:;";
        logout.innerText = "Logout";
        logout.className = "logoutad-a";
        logout.addEventListener("click", this.logout, true);

        if (this.userName != '')
            logout.style.display = "inline-block";
        else
            logout.style.display = "none";

        this.appendChild(name);
        this.appendChild(login);
        this.appendChild(logout);
    },
    login: function () {
        authContext.login();
    },
    logout: function () {
        authContext.logOut();
    }
});

La particularidad de este código es que, como podemos ver, utiliza la función ready de Polymer para cargar el objeto de configuración y comprobar si está autenticado el usuario y la función attached para inyectar todos los elementos que forman el componente, con la funcionalidad de cada uno.

¿Es mejorable?, como siempre, claro que lo es. Y me encantaría recibir feedback de qué más podríamos hacer para que este componente fuera realmente útil y pudiéramos usarlo en nuestras aplicaciones como una alternativa más.

Saludos, espero que lo hayáis encontrado interesante.

Autenticación de nuestra Aplicación usando Javascript ADAL

Muy buenas a todos,

En una entrada anterior, vimos como podíamos desarrollar una aplicación web con MVC .NET que nos permitiera acceder a la información guardada en nuestro Office 365. Una parte importante de ese desarrollo era la autenticación contra Azure AD, lo que se hacía por medio del paquete Nugget ADAL (Active Directory Authentication Library).

Hace unos días tuve conocimiento de una librería de Javascript que nos permitía hacer la autenticación contra Azure AD (conocida como Javascript ADAL) y que se podía utilizar en el desarrollo de Single Page Applications de una forma muy sencilla. Así que decidí probarlo para seguir abriendo y conociendo el abanico de oportunidades para trabajar contra Azure AD y luego con la API de Office 365. El primer paso obviamente será el login para después acceder y consultar la API usando CORS.

Así que en la entrada de hoy, os voy a mostrar cómo hacer el login usando esta librería de Javascript. Para el ejemplo he creado un proyecto de MVC vacío en el que he hecho todo el desarrollo. Antes de nada, os dejo, como siempre, los enlaces que me han servido de guía y donde creo que hay información muy valiosa sobre esta librería.

http://www.cloudidentity.com/blog/2015/02/19/introducing-adal-js-v1/

https://github.com/AzureADSamples/SinglePageApp-jQuery-DotNet

Disponemos de una versión de la librería integrada con Angularjs y otra versión que podremos usar con JQuery y otros frameworks que será la versión que voy a usar en mi ejemplo.

Empezando con el ejemplo

Obviamente el primer paso en cualquier caso, es registrar nuestra aplicación en el Azure Active Directory de nuestro Tenant. Tras esto, tenemos que descargar el fichero adal.js.

El siguiente paso que he seguido es crear la vista del ejemplo, que es tal y como veréis a continuación

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
    <script src="~/Scripts/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
    <script src="~/Scripts/adal.js"></script>
    <script src="~/Scripts/App/app.js"></script>
</head>
<body>
    <div>
        <h2>Index</h2>

        <a href="javascript:;" class="app-login">Login</a>
        <a href="javascript:;" class="app-logout">Logout</a>
    </div>

</body>
</html>

Esta vista tiene dos botones, uno para hacer login y otro para hacer logout, nada más. El fichero que tiene todo el código es el fichero app.js que podéis ver ahora.

(function () {

    //1.- Initial configuration
    window.config = {
        tenant: '<tentantdomain>',
        clientId: '<clientid>',
        postLogoutRedirectUri: window.location.origin,
        cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
    };
    var authContext = new AuthenticationContext(config);

    //2.- Handle redirect after login
    var isCallback = authContext.isCallback(window.location.hash);
    authContext.handleWindowCallback();

    if (isCallback && !authContext.getLoginError()) {
        window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
    }

    //3.- Check for authenticated information
    var user = authContext.getCachedUser();
    if (user) {
        console.log(user);
    } else {
        console.log("error");
    }

    //4.- Handle events for login & logout
    var $signInButton = $(".app-login");

    $signInButton.click(function () {
        authContext.login();
    });

    var $signOutButton = $(".app-logout");

    $signOutButton.click(function () {
        authContext.logOut();
    });
}());

El código de este ejemplo es una simplificación del ejemplo de Github que os he puesto un poco más arriba y que he usado para entender el proceso que seguía. Lo único que hace este código es mostrar en la consola de depuración los datos del usuario si está logueado o mostrar un texto de error si no lo está. Los puntos más importantes del código son los siguiente:

  1. El primer paso es crear el objeto de configuración, con los datos del tenant y client id que hemos obtenido al registrar la aplicación
  2. A continuación se indica el callback que maneja la redirección después del login
  3. Se comprueba y se muestra la información de logueo del usuario
  4. Por último se crean los eventos que dispararán el login y el logout asociados al click en los enlaces

Y esto es todo, si lo probamos comprobaremos que el resultado es el esperado, cuando no estamos logueados, nos muestra la pantalla de signin de windows para indicar nuestro usuario y contraseña del Azure AD de nuestro tenant.

Lo que os voy a enseñar en próximas entradas es un componente web de Polymer que he creado que utiliza este código para implementar la funcionalidad de signin y signout de una manera muy sencilla y que creo que puede ser de utilidad. Este componente, de hecho ya está en mi GitHub

Un saludo a todos y buen finde.

Usando Bower y Grunt en Visual Studio 2013

Muy buenas a todos,

Hoy os quiero contar los pasos que he dado para usar en mis proyectos de Visual Studio 2013 bower, usándolo como gestor de paquetes en el lado del cliente, y grunt, como task runner. Ambos están siendo muy comúnmente usados en el mundo del desarrollo de aplicaciones web y si bien, la versión de Visual Studio 2015 los incluye de forma nativa, podemos configurar nuestros proyectos web de Visual Studio 2013 para usar estas dos herramientas tan útiles.

Para todo aquel que le pueda interesar, dejo la entrada del blog en la que me he basado para hacer toda la configuración, aunque si accedéis veréis que en el post, se usa otro gestor de tareas llamado gulp en lugar de grunt.

http://blogs.msdn.com/b/cdndevs/archive/2015/02/17/using-bower-with-visual-studio-2013.aspx

Pero antes de entrar en detalle, vamos a ver qué son exactamente cada una de estas herramientas.

¿Qué es Bower?

Bower works by fetching and installing packages from all over, taking care of hunting, finding, downloading, and saving the stuff you’re looking for.

La primera pregunta que se nos plantea viendo lo que hace Bower es ¿Y Nugget?. La respuesta a esta pregunta es muy sencilla. Desde su aparición Nugget se ha descubierto como una herramienta muy eficaz para la gestión de paquetes de servidor, pero no así para paquetes de cliente. Bower se ha convertido en el estándar de facto en el desarrollo web. Es poco probable, que un desarrollador que crea una nueva librería en Javascript la ponga disponible como paquete Nugget, sin embargo es frecuente que esté disponible en Bower en todas sus versiones. Esto provoca desactualización o imposibilidad de usar determinadas librerías a través de Nugget, lo que desaconseja su uso como herramientas de gestión de paquetes del lado del cliente.

¿Qué es Grunt?

In one word: automation. The less work you have to do when performing repetitive tasks like minification, compilation, unit testing, linting, etc, the easier your job becomes.

En resumen, con Grunt vamos a poder automatizar determinadas tareas que van a ayudar a tener nuestra aplicación lista para poner en producción con acciones como: minimizar y agrupar los estilos, minimizar y agrupar los javascript, etc. En la entrada veremos una tarea que he creado que puede ser necesaria y recomendable cuando usamos Bower.

Configurando y usando Bower

Para la configuración de Bower, podéis fácilmente seguir la entrada del blog de la msdn sin ningún problema, es muy sencilla de seguir y está muy detallada, de hecho yo la he seguido y es lo que de forma simplificada explicaré por aquí, solo hay una cosa que cambia, porque a mi al menos, o no me quedaba claro en la entrada inicial o no estaba debidamente reflejado. Vamos a ello.

    1. El primer paso es instalar node.js, ya que tanto Bower como Grunt lo utilizan. Enlace de descarga de node.js
    2. A continuación, abrimos la línea de comandos e instalamos bower
npm install bower -g
    1. Tras esto, necesitaremos tener instalado Git, Bower utiliza Git para descargar los paquetes. Concretamente instalaremos msysgit. Durante la instalación, tendremos que seleccionar la opción que vemos en la imagen a continuación para que se realice toda la configuración correctamente

gitinstall

    1. Ya tenemos listo Bower para ser utilizado. Si lo vamos a usar dentro de un proyecto de MVC, antes tendremos que tener en cuenta que por defecto, la plantilla de este tipo de proyectos en Visual Studio incluye una serie de paquetes, entre ellos: bootstrap, jquery, modernizr, etc. Por lo que antes de comenzar a usar bower en nuestro proyecto, tenemos que desinstalarlos. Para ello accedemos a la consola de Nuget y ejecutamos el siguiente comando para cada uno de los paquetes
Uninstall-Package <package>

Ahora que ya tenemos listo Bower vamos a usarlo dentro de nuestro proyecto. Esto lo haremos a través de la línea de comandos. En mi caso, no he usado la interfaz de línea de comandos propia de Windows (cmd), ya que me daba errores al usarla con Bower y Git, y buscando por internet vi que recomendaban usar una interfaz de línea de comandos que se instala con msysgit. Esta es Git Bash.

Vamos ahora a entrar al directorio del proyecto, y comenzaremos con el uso de Bower. Lo primero que haremos será iniciar el fichero de configuración de Bower. Para ello ejecutamos:

bower init

Una vez creado el fichero bower.json, podemos comenzar a instalar paquetes, esto lo haremos de la siguiente forma.

bower install jquery --save
bower install polymer --save

Una Buena Práctica recomendada para el uso de los paquetes que se obtienen con Bower, es no agregar directamente todos los archivos descargados con Bower a nuestro proyecto, ya que los paquetes en muchas ocasiones contienen más archivos de los que realmente necesitaremos, por lo que se recomienda copiar al proyecto solo aquellos archivos que realmente sean necesarios. ¿Cómo hacemos esto, manualmente?. La respuesta evidentemente es no, para esto tenemos nuestro Task Runner, Grunt 😉

Configurando y usando Grunt

En primer lugar, para añadir el soporte a Grunt para nuestro proyecto vamos a crear dos ficheros que añadiremos a la raíz del mismo package.json y gruntfile.js. El fichero package.json inicialmente contendrá la siguiente información.

{
"name": "WebApplication1",
"version": "1.0.0",
"description": "WebApplication package project",
"main": "gruntfile.js",
"license": "ISC";
}

A continuación y como ejemplo, la tarea que vamos a crear va a copiar los ficheros necesarios de los paquetes que hemos obtenido mediante Bower a la carpeta Scripts, donde se encuentran los ficheros js de nuestro proyecto. El código del fichero gruntfile.js es el siguiente:

module.exports = function (grunt) {

    grunt.initConfig({
        copy: {
            main: {
                files: [
                  {
                      expand: true,
                      cwd: '../bower_components/webcomponentsjs/',
                      src: ['webcomponents.js'],
                      dest: 'Scripts'
                  },
                  {
                      expand: true,
                      cwd: '../bower_components/jquery/dist',
                      src: ['jquery.min.js'],
                      dest: 'Scripts'
                  }
                ]
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.registerTask('build', ['copy']);
};

Obviamente, aún podríamos hacer algunas tareas de minimización y agrupamiento de ficheros con grunt y que serían necesarias, aunque eso lo dejo para más adelante, es posible que en alguna entrada futura os cuente como hacerlo.

Para que funcione correctamente, necesitamos un plugin de Grunt, “grunt-contrib-copy”. Cuando necesitemos añadir determinados plugins para nuestras tareas de Grunt, lo haremos desde la línea de comandos usando la utilidad npm de node.js. Nos vamos al directorio del proyecto y ejecutamos el siguiente comando.

npm install grunt-contrib-copy --save-dev

Una vez que ya tenemos instalado el plugin, desde la línea de comandos en el directorio en el que está nuestro archivo gruntfile.js (la raíz del proyecto), ejecutamos grunt. Esto llevará a cabo la tarea que hemos creado. La primera vez que ejecutamos grunt, tendremos que añadir manualmente los archivos que se han añadido a la tarea al proyecto, aunque esta acción no tendremos que volver a hacerla.

Como hemos visto, hasta ahora estamos usando la línea de comando para ejecutar Grunt. Pero, ¿Y si queremos que se ejecute automáticamente Grunt cuando hacemos el deploy del proyecto, esto es posible?, la respuesta es si. Para esto tenemos varias extensiones de Visual Studio que nos lo permiten. En mi caso he utilizado Task Runner Explorer.

La descargamos y la instalamos y ya podremos usarla en nuestro Visual Studio 2013. Vamos a View->Other Windows->Task Runner Explorer y cuando se abre la nueva ventana, lo primero que hacemos es pulsar sobre el botón actualizar para que capture todas las tareas creadas si aún no lo habia hecho.

taskrunnerexplorer

Aquí vemos que podemos configurar si queremos que una determinada tarea se ejecute antes del deploy de la solución, con lo que podremos automatizar correctamente la ejecucion de Grunt y no tendremos que hacerlo manualmente a través de la línea de comandos.

run

binding

Os dejo para terminar, algunos enlaces más de utilidad relacionado con lo que hemos visto en la entrada:

http://gruntjs.com/
http://bower.io/
https://visualstudiogallery.msdn.microsoft.com/8e1b4368-4afb-467a-bc13-9650572db708

Y esto es todo por hoy, yo a partir de ahora voy a utilizar estas dos herramientas en todos mis proyectos, por las ventajas que ofrecen, espero que todos podáis hacerlo.

Un saludo y hasta la próxima

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.