lunes, 17 de mayo de 2010

SPGridView con generic list

Yo soy de esas personas que me gusta trabajar con objetos POCO, me gusta la frase que decía Einstein: "Se debe hacer todo tan sencillo como sea posible, pero no más sencillo", no me gustan las estructuras de datos como pueden ser los datatable y otros objetos del estilo.

Parece que trabajar con SharePoint y no usar tipos de datos como datatables es algo imposible, pero no es así, y este post es una pequeña explicación de como usar listas genéricas (http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx) como datasource de un SPGridView y no perder funcionalidades tales como Paging, Filtering, Sorting, etc.

Para ello hacemos uso de LinqDataSource que de una manera muy simple nos permite asignar una lista genérica al datasource y un único método para obtener los datos (a partir del evento Selecting). Esto implica tener habilitadas las extensiones del framework 3.5 en nuestro entorno SharePoint, para ello podemos utilizar la solución de codeplex: http://features.codeplex.com/

Para ello imaginemos que partimos de la siguiente classe Foo:


public class Foo
{
public String Field1 { get; set; }
public String Field2 { get; set; }

public Foo()
{
}

public Foo(String Field1, String Field2)
{
this.Field1 = Field1;
this.Field2 = Field2;
}
}


Para el ejemplo hemos hecho una simple webpart, que renderiza una SPGridView, donde le hemos asignado como datasource un LinqDataSource que contiene nuestra lista genérica de objetos Foo.

Para ello simplemente hemos sobreescrito el método CreateChildControls, en el creamos nuestro SPGridView y le asignamos propiedades como filtering, sorting, paging, etc. y creamos nuestro datasource, añadimos tanto el datasource, como el SPGridView a la coleción de controles de la webpart y hacemos un bind entre la grid y el datasource. Además de esto creamos un event handler para el evento Selecting del DataSource, este evento permitirá obtener los datos.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Web.UI.WebControls.WebParts;

namespace MyWebparts
{
public class FooWebpart: System.Web.UI.WebControls.WebParts.WebPart
{
#region Variables
///
/// The grid
///

private SPGridView myGrid;

///
/// The datasource
///

private LinqDataSource lqDataSource;

#endregion

public FooWebpart()
{
this.ExportMode = WebPartExportMode.All;
}

protected override void CreateChildControls()
{

// Set basic gridview properties
myGrid = new SPGridView();
myGrid.ID = "MyGrid";
myGrid.AutoGenerateColumns = false;

// Set properties for sorting
myGrid.AllowSorting = true;

// Set properties for paging
myGrid.AllowPaging = true;
myGrid.PageSize = 25;
myGrid.PagerSettings.Mode = PagerButtons.NumericFirstLast;

// Set properties for filtering
myGrid.AllowFiltering = true;
myGrid.FilterDataFields = "Field1, Field2";
myGrid.FilteredDataSourcePropertyName = "Where";
myGrid.FilteredDataSourcePropertyFormat = "{1} == \"{0}\"";

// Set columns of the SPGridView
SPBoundField bfield1 = new SPBoundField();
bfield1.DataField = "Field1";
bfield1.HeaderText = "Field1";
bfield1.SortExpression = "Field1";
myGrid.Columns.Add(bfield1);

SPBoundField bfield2 = new SPBoundField();
bfield2.DataField = "Field2";
bfield2.HeaderText = "Field2";
bfield2.SortExpression = "Field2";
myGrid.Columns.Add(bfield2);

// Create the linqDatasource
lqDataSource = new LinqDataSource();
lqDataSource.ID = "MyLinqDS";
lqDataSource.Selecting += new EventHandler(lqDataSource_Selecting);

// Add datasource to controls collection
Controls.Add(lqDataSource);

// Bind SPGridView to linqdatasource
myGrid.DataSourceID = lqDataSource.ID;
Controls.Add(myGrid);

myGrid.PagerTemplate = null;

base.CreateChildControls();
}

void lqDataSource_Selecting(object sender, LinqDataSourceSelectEventArgs e)
{
List myGenericList = new List();
myGenericList.Add(new Foo("uno", "uno"));
myGenericList.Add(new Foo("dos", "dos"));

e.Result = myGenericList;
}

}
}

lunes, 10 de mayo de 2010

jquery, ajax, json y sharepoint

Una forma de implementar una llamada AJAX desde jquery dentro de nuestro entorno MOSS es a través de un httplander.

La llamada jquery va a enviar un conjunto de datos en formato JSON y va a recibir otros datos también en JSON, por lo que tendremos que serializar y deserializar este json en nuestro código servidor.

Vamos a intentar describir todo el procedimiento a partir de un ejemplo, este ejemplo será la implementación de un cascading dropdownlist, primero tendremos un dropdownlist que muestra un conjunto de categorias y al seleccionar una categoria concreta luego otro dropdownlist mostrará las subcategorias presentes para esa categoria. (Típico ejemplo) Todo esto lo haremos dentro de un user control.

1. User control (ascx)





A continuación se muestra el código jquery para hacer la llamad ajax a un web handler llamado FooHandler.ashx.

Veremos que este código JavaScript tiene dos partes una es el bind de la acción change y keyup al dropdownlist categorias y otra es la llamada ajax al web handler.



$(function() {
// DropDownLists
var $ddlCategories = $("#<%= DDLCategories.ClientID %>");
var $ddlSubCategories = $("#DDLSubCategories");

// Labels
var $LBSubCategories = $("#<%= LBSubCategories.ClientID %>");

$ddlCategories.focus();
$ddlCategories.bind("change keyup", function() {
if ($(this).val() != "0") {

loadSubCategoriesProducts($("#<%= DDLCategories.ClientID %> option:selected").val());
$LBSubCategories.fadeIn("slow");
$ddlSubCategories.fadeIn("slow");

} else {
// Hide the others dropdownlists and labels
$LBSubCategories.fadeOut("slow");
$ddlSubCategories.fadeOut("slow");
}
});

});

function loadSubCategoriesProducts(selectedItem) {
$.ajax({
type: "POST",
url: '_layouts/Handlers/FooHandler.ashx',
data: "{ method: 'GetSubCategories', ID: '" + selectedItem + "'}",
contentType: "application/json; charset=utf-8",
dataType: "json",
async: true,
success: function(data) {
$("#DDLSubCategories").children().remove();
for (var i = 0; i < val =" data[i].Id;" text =" data[i].Title;">").val(val).html(text));
}
}
});
}



1.1 User control (code behind)

Nuestro user control en el code behind sólo tendrá la carga inicial de las categorias en el primer dropdown:

 


public class FOOCodeBehind: UserControl
{

#region Protected Fields

protected DropDownList DDLCategories;

#endregion

#region Overriden Methods

protected override void CreateChildControls()
{

base.CreateChildControls();

DDLCategories.DataSource = GetCategories();
DDLCategories.DataTextField = "Title";
DDLCategories.DataValueField = "Id";
DDLCategories.DataBind();

}

#endregion

#region Private Methods

private List GetCategories()
{
var categories = new List();

categories.Add(new Category("1", "Category1"));
categories.Add(new Category("2", "Category2"));
categories.Add(new Category("3", "Category3"));
categories.Add(new Category("4", "Category4"));

return categories;
}

#endregion
}



2. Ahora toca definir el handler que hemos llamado FooHandler.ashx. Como podéis observar en la llamada Ajax, esto en realidad es una página (.ashx) que esta dentro del folder Handlers que a la vez esta dentro del folder layouts del folder 12 de MOSS.

Esta página simplemente contendrá lo siguiente:



2.1 Code Behind Web Handler

Aquí necesitaremos implementar nuestro handler, este será una clase que derive de IHttpHandler e implemente el método ProcessRequest.
Este método comprobará si el content type recibido es de tipo json, y entonces deserializará lo que hemos recibido en el inputstream a un diccionario key, value, donde las claves son strings y los values objects.

Para eso hemos creado un extensión de object que serializa y deserializa json. Esta extensión en el código lo tenemos cuando hemos añadido la referencia Core.JSON, esta es una referencia propia, pero tranquilos que también añado el código:

 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using Core.JSON;

namespace MyHandlers
{
public class FooHandler: IHttpHandler
{

#region IHttpHandler Members

bool IHttpHandler.IsReusable
{
get { return true; }
}

void IHttpHandler.ProcessRequest(HttpContext context)
{
if (context.Request.ContentType.Contains("json"))
{
var inputStream = new System.IO.StreamReader(context.Request.InputStream);
var inputJson = inputStream.ReadToEnd();

var collectionJson = inputJson.ToObject>();

context.Response.Clear();
context.Response.ContentType = "application/json";

if (!string.IsNullOrEmpty(collectionJson["method"].ToString()))
{
var json = string.Empty;

switch (collectionJson["method"].ToString())
{
case "GetSubCategories":
json = GetSubCategories(collectionJson["id"].ToString());
break;
}

context.Response.Write(json);
}
}

}

#endregion

#region Private Methods

private string GetSubCategories(String id)
{
var subcategories = new List();

subcategories.Add(new SubCategory("1", "SubCategory 1"));
subcategories.Add(new SubCategory("2", "SubCategory 2"));
subcategories.Add(new SubCategory("3", "SubCategory 3"));
subcategories.Add(new SubCategory("4", "SubCategory 4"));

return subcategories.ToJSON();
}
}
}





3. Extension methods para serializar/deserializar JSON

 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Script.Serialization;

namespace Core.JSON
{
public static class ojectExtensionJSON
{
public static string ToJSON(this object obj)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
return serializer.Serialize(obj);
}

public static string ToJSON(this object obj, int recursionLimit)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
serializer.RecursionLimit = recursionLimit;
return serializer.Serialize(obj);
}

public static T ToObject(this string json)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
return serializer.Deserialize(json);
}
}
}