Como cada vez queda menos para uno de los eventos que estamos organizando dentro de la comunidad de SharePoint y ese no es otro que el Office & SharePoint Application Challenge vamos a ir profundizando un poco sobre aspectos muy importantes para la realización de las APPS, vamos a realizar una serie de post (que comenzamos con el anterior de inicio de NAPA) y cuya finalidad tiene que los asistentes al evento tengan base para poder ir haciendo las APPs y para los que no asistan que sepan las grandes novedades que trae esta versión de como dice Juan Carlos nuestro servidor preferido.
Una de las grandes novedades en esta versión como es sabido es el uso de la API REST en este artículo vamos a poner una demostración de como hacer las operaciones básicas haciendo uso de la misma. Desde la consulta de listas, añadir elementos en la misma, cambiar el nombre de la lista y eliminar la propia lista creada. Esto lo vamos a realizar desde dentro de una APP accediendo a las listas del sitio donde se aloja para ello tenemos que autentificando utilizando OAuth. Y veremos lo sencillo que es.
Esta demostración la vamos a realizar contra un SharePoint Online, para ello en nuestro Visual Studio tendremos que tener instaladas las Office Tools for Developer. Una vez las tenemos instaladas el primer paso es crearnos un Proyecto de APP de SharePoint y le tenemos que indicar que es una APP AutoHosted y a continuación visualizamos un proyecto como el que tenemos en la imagen. Para este tipo de proyecto no vamos a realizar nada utilizando JavaScript por lo que la carpeta Scripts la podemos eliminar. Lo que vamos a realizar es colocar la estructura que queremos visualizar dentro del Default.aspx y dentro del code-behind añadiremos las llamadas a la API Rest. También conviene comentar la clase TokenHelper que ya viene en nuestro proyecto y es una clase que contiene una serie de clases para manejar el token de autentificación de nuestra APP contra SharePoint de una forma semejante a la que se visualiza en la siguiente imagen:
Esta es lo que tendríamos que añadir en el Default.aspx, su código no tiene mucha chicha son una serie de botones y una tabla en la que mostraremos las listas que tenemos en nuestro site.
A continuación, ya seria ponernos manos a la obra con los aspectos donde esta la chicha de la aplicación. Para empezar nuestra APP una vez se cargue lo primero que vamos a hacer es mostrar las listas que tenemos en nuestro Site principal para ello tendríamos que hacer la siguiente función:
//This method retrieves all of the lists on the host Web. private void RetrieveLists(string accessToken) { if (IsPostBack) { sharepointUrl = new Uri(Request.QueryString["SPHostUrl"]); } AddItemButton.Visible = false; AddListItemBox.Visible = false; DeleteListButton.Visible = false; ChangeListTitleButton.Visible = false; ChangeListTitleBox.Visible = false; RetrieveListNameBox.Enabled = true; ListTable.Rows[0].Cells[1].Text = "List ID"; //Add pertinent namespaces to the namespace manager. xmlnspm.AddNamespace("atom", "http://www.w3.org/2005/Atom"); xmlnspm.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices"); xmlnspm.AddNamespace("m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"); //Execute a REST request for all of the site's lists. HttpWebRequest listRequest = (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/Web/lists"); listRequest.Method = "GET"; listRequest.Accept = "application/atom+xml"; listRequest.ContentType = "application/atom+xml;type=entry"; listRequest.Headers.Add("Authorization", "Bearer " + accessToken); HttpWebResponse listResponse = (HttpWebResponse)listRequest.GetResponse(); StreamReader listReader = new StreamReader(listResponse.GetResponseStream()); var listXml = new XmlDocument(); listXml.LoadXml(listReader.ReadToEnd()); var titleList = listXml.SelectNodes("//atom:entry/atom:content/m:properties/d:Title", xmlnspm); var idList = listXml.SelectNodes("//atom:entry/atom:content/m:properties/d:Id", xmlnspm); int listCounter = 0; foreach (XmlNode title in titleList) { TableRow tableRow = new TableRow(); LiteralControl idClick = new LiteralControl(); //Use Javascript to populate the RetrieveListNameBox control with the list id. string clickScript = "<a onclick="\"document.getElementById(\'RetrieveListNameBox\').value" href="\"#\"">" + idList[listCounter].InnerXml + "</a>"; idClick.Text = clickScript; TableCell tableCell1 = new TableCell(); tableCell1.Controls.Add(new LiteralControl(title.InnerXml)); TableCell tableCell2 = new TableCell(); tableCell2.Controls.Add(idClick); tableRow.Cells.Add(tableCell1); tableRow.Cells.Add(tableCell2); ListTable.Rows.Add(tableRow); listCounter++; } }
Explicando un poco este código tenemos el Token para establecer la autentificación este token lo veremos como lo obtenemos en el Load y lo explicare más adelante, con este token lo que hacemos es hacer la petición a la Api Rest «/_api/Web/lists» Una vez obtenemos las listas del sitio las añadimos a la table y las mostramos quedaría algo semejante a la siguiente pantalla:
A continuación lo que vamos a realizar es ver como podemos crear Listas en nuestro Sitio para ello lo podemos hacer siguiendo esto código
//This method adds a list with the specified title. private void AddList(string accessToken, string newListName) { if (IsPostBack) { sharepointUrl = new Uri(Request.QueryString["SPHostUrl"]); } try { //Add pertinent namespace to the namespace manager. xmlnspm.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices"); //Execute a REST request to get the form digest. All POST requests that change the state of resources on the host //Web require the form digest in the request header. HttpWebRequest contextinfoRequest = (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/contextinfo"); contextinfoRequest.Method = "POST"; contextinfoRequest.ContentType = "text/xml;charset=utf-8"; contextinfoRequest.ContentLength = 0; contextinfoRequest.Headers.Add("Authorization", "Bearer " + accessToken); HttpWebResponse contextinfoResponse = (HttpWebResponse)contextinfoRequest.GetResponse(); StreamReader contextinfoReader = new StreamReader(contextinfoResponse.GetResponseStream(), System.Text.Encoding.UTF8); var formDigestXML = new XmlDocument(); formDigestXML.LoadXml(contextinfoReader.ReadToEnd()); var formDigestNode = formDigestXML.SelectSingleNode("//d:FormDigestValue", xmlnspm); string formDigest = formDigestNode.InnerXml; //Execute a REST request to add a list that has the user-supplied name. //The body of the REST request is ASCII encoded and inserted into the request stream. string listPostBody = "{'__metadata':{'type':'SP.List'}, 'Title':'" + newListName + "', 'BaseTemplate': 100}"; byte[] listPostData = System.Text.Encoding.ASCII.GetBytes(listPostBody); HttpWebRequest listRequest = (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/lists"); listRequest.Method = "POST"; listRequest.ContentLength = listPostBody.Length; listRequest.ContentType = "application/json;odata=verbose"; listRequest.Accept = "application/json;odata=verbose"; listRequest.Headers.Add("Authorization", "Bearer " + accessToken); listRequest.Headers.Add("X-RequestDigest", formDigest); Stream listRequestStream = listRequest.GetRequestStream(); listRequestStream.Write(listPostData, 0, listPostData.Length); listRequestStream.Close(); HttpWebResponse listResponse = (HttpWebResponse)listRequest.GetResponse(); RetrieveLists(accessToken); } catch (Exception e) { AddListNameBox.Text = e.Message; } }
Lo interesante de este código, vemos la llamada a la API Rest pero de esta forma lo que estamos haciendo es un Create (para crear la lista), pero le pasamos el tipo de lista que vamos a crear y los campos que va a tener esto lo tenemos puesto en la variable listPostBody. Con este código ya podemos crear listas utilizando la API Rest. Una vez la lista esta creada lo que hacemos es volver a llamar a la API Rest para ver las listas que hay en el Site y comprobamos que realmente se ha creado con éxito.
Ahora cual seria el siguiente paso, podemos ver como consultamos los elementos a esta lista que nos hemos creado también se puede realizar de una forma muy sencillita:
//This method retrieves all items from a specified list. private void RetrieveListItems(string accessToken, Guid listId) { if (IsPostBack) { sharepointUrl = new Uri(Request.QueryString["SPHostUrl"]); } //Adjust the visibility of controls on the page in light of the list-specific context. AddItemButton.Visible = true; AddListItemBox.Visible = true; DeleteListButton.Visible = true; ChangeListTitleButton.Visible = true; ChangeListTitleBox.Visible = true; RetrieveListNameBox.Enabled = false; ListTable.Rows[0].Cells[1].Text = "List Items"; //Add pertinent namespaces to the namespace manager. xmlnspm.AddNamespace("atom", "http://www.w3.org/2005/Atom"); xmlnspm.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices"); xmlnspm.AddNamespace("m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"); //Execute a REST request to get the list name. HttpWebRequest listRequest = (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/Web/lists(guid'" + listId + "')"); listRequest.Method = "GET"; listRequest.Accept = "application/atom+xml"; listRequest.ContentType = "application/atom+xml;type=entry"; listRequest.Headers.Add("Authorization", "Bearer " + accessToken); HttpWebResponse listResponse = (HttpWebResponse)listRequest.GetResponse(); StreamReader listReader = new StreamReader(listResponse.GetResponseStream()); var listXml = new XmlDocument(); listXml.LoadXml(listReader.ReadToEnd()); var listNameNode = listXml.SelectSingleNode("//atom:entry/atom:content/m:properties/d:Title", xmlnspm); string listName = listNameNode.InnerXml; //Execute a REST request to get all of the list's items. HttpWebRequest itemRequest = (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/Web/lists(guid'" + listId + "')/Items"); itemRequest.Method = "GET"; itemRequest.Accept = "application/atom+xml"; itemRequest.ContentType = "application/atom+xml;type=entry"; itemRequest.Headers.Add("Authorization", "Bearer " + accessToken); HttpWebResponse itemResponse = (HttpWebResponse)itemRequest.GetResponse(); StreamReader itemReader = new StreamReader(itemResponse.GetResponseStream()); var itemXml = new XmlDocument(); itemXml.LoadXml(itemReader.ReadToEnd()); var itemList = itemXml.SelectNodes("//atom:entry/atom:content/m:properties/d:Title", xmlnspm); TableRow tableRow = new TableRow(); TableCell tableCell1 = new TableCell(); tableCell1.Controls.Add(new LiteralControl(listName)); TableCell tableCell2 = new TableCell(); foreach (XmlNode itemTitle in itemList) { tableCell2.Text += itemTitle.InnerXml + " "; } tableRow.Cells.Add(tableCell1); tableRow.Cells.Add(tableCell2); ListTable.Rows.Add(tableRow); }
Como podemos ver a la vista del código que hemos añadido la única diferencia es que hacemos la petición a una lista concreta y que mostramos solo el campo Title (para añadir más campos es tan sencillo como concatenar el nombre de los mismos.
Una vez sabemos consultar estos elementos para finalizar el post nos quedaría mirar como podemos añadir elementos a la lista para ello no es algo muy difícil para ello bastaría con una función como la siguiente:
//This method adds a list item to the specified list. private void AddListItem(string accessToken, Guid listId, string newItemName) { if (IsPostBack) { sharepointUrl = new Uri(Request.QueryString["SPHostUrl"]); } try { //Add pertinent namespaces to the namespace manager. xmlnspm.AddNamespace("atom", "http://www.w3.org/2005/Atom"); xmlnspm.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices"); xmlnspm.AddNamespace("m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"); //Execute a REST request to get the form digest. All POST requests that change the state of resources on the host //Web require the form digest in the request header. HttpWebRequest contextinfoRequest = (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/contextinfo"); contextinfoRequest.Method = "POST"; contextinfoRequest.ContentType = "text/xml;charset=utf-8"; contextinfoRequest.ContentLength = 0; contextinfoRequest.Headers.Add("Authorization", "Bearer " + accessToken); HttpWebResponse contextinfoResponse = (HttpWebResponse)contextinfoRequest.GetResponse(); StreamReader contextinfoReader = new StreamReader(contextinfoResponse.GetResponseStream(), System.Text.Encoding.UTF8); var formDigestXML = new XmlDocument(); formDigestXML.LoadXml(contextinfoReader.ReadToEnd()); var formDigestNode = formDigestXML.SelectSingleNode("//d:FormDigestValue", xmlnspm); string formDigest = formDigestNode.InnerXml; //Execute a REST request to get the list name and the entity type name for the list. //The entity type name is the required type when you construct a request to add a list item. HttpWebRequest listRequest = (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/Web/lists(guid'" + listId + "')"); listRequest.Method = "GET"; listRequest.Accept = "application/atom+xml"; listRequest.ContentType = "application/atom+xml;type=entry"; listRequest.Headers.Add("Authorization", "Bearer " + accessToken); HttpWebResponse listResponse = (HttpWebResponse)listRequest.GetResponse(); StreamReader listReader = new StreamReader(listResponse.GetResponseStream()); var listXml = new XmlDocument(); listXml.LoadXml(listReader.ReadToEnd()); var entityTypeNode = listXml.SelectSingleNode("//atom:entry/atom:content/m:properties/d:ListItemEntityTypeFullName", xmlnspm); var listNameNode = listXml.SelectSingleNode("//atom:entry/atom:content/m:properties/d:Title", xmlnspm); string entityTypeName = entityTypeNode.InnerXml; string listName = listNameNode.InnerXml; //Execute a REST request to add an item to the list. string itemPostBody = "{'__metadata':{'type':'" + entityTypeName + "'}, 'Title':'" + newItemName + "'}}"; Byte[] itemPostData = System.Text.Encoding.ASCII.GetBytes(itemPostBody); HttpWebRequest itemRequest = (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/Web/lists(guid'" + listId + "')/Items"); itemRequest.Method = "POST"; itemRequest.ContentLength = itemPostBody.Length; itemRequest.ContentType = "application/json;odata=verbose"; itemRequest.Accept = "application/json;odata=verbose"; itemRequest.Headers.Add("Authorization", "Bearer " + accessToken); itemRequest.Headers.Add("X-RequestDigest", formDigest); Stream itemRequestStream = itemRequest.GetRequestStream(); itemRequestStream.Write(itemPostData, 0, itemPostData.Length); itemRequestStream.Close(); HttpWebResponse itemResponse = (HttpWebResponse)itemRequest.GetResponse(); RetrieveListItems(accessToken, listId); } catch (Exception e) { AddListItemBox.Text = e.Message; }
Para finalizar el post hay que mencionar el tema del Token, sin el token que es el encargado de autentificar nuestra APP contra SharePoint sin él no podemos hacer nada como conseguimos este token pues es relativamente sencillo con esta linea de código en el Load de la Página seria suficiente:
TokenHelper.TrustAllCertificates(); string contextTokenString = TokenHelper.GetContextTokenFromRequest(Request); if (contextTokenString != null) { contextToken = TokenHelper.ReadAndValidateContextToken(contextTokenString, Request.Url.Authority); sharepointUrl = new Uri(Request.QueryString["SPHostUrl"]); accessToken = TokenHelper.GetAccessToken(contextToken, sharepointUrl.Authority).AccessToken; }
Este ejemplo lo podemos descargar desde este link de Micrososft
Conclusión
En este desarrollo hemos visto lo sencillo que es utilizar la API Rest, en este ejemplo podemos ver las operaciones básicas de acceso a datos (crear, leer, actualizar, eliminar). Es una base para ir cogiendo confianza sobre esta nueva forma de desarrollar.