jueves, 16 de diciembre de 2010

Obtener PublicKeyToken de una assembly

Muchas veces, nos hemos encontrado con la necesidad de obtener el PublicKeyToken de una assembly para poderlo añadir por ejemplo a una directiva <%@ Page %> o <%@ Control %>  de nuestra solución, esto se consigue mediante la herramienta sn.exe (Strong Name Tool).

Para usar esta herramienta sería necesario abrir una consola de visual studio y ejecutar esta herramient pasándole los parámetros apropiados. Podemos automatizar este proceso usando la opción de añadir una External Tool a nuestro Visual Studio, lo podemos hacer la siguiente forma:

En Visual Studio hacemos:

  • Menú Tools > External Tools
  • Pulsamos Add
  • Title: Get &PublicKeyToken
  • Command: C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\sn.exe
  • Arguments: -Tp $(TargetPath)
  • Marcamos la opción “Use Output Window”
  • Pulsamos OK

Con este procedimiento se nos ha añadido una nueva opción de menú en el menú Tools con el nombre Get PublicKeyToken. Y voilà ya podemos obtener nuestro publicKeyToken del proyecto en el que estemos situados.

Fuente: http://blogs.msdn.com/b/kaevans/archive/2008/06/18/getting-public-key-token-of-assembly-within-visual-studio.aspx

miércoles, 1 de diciembre de 2010

Reflection y como ser un Jedi (Parte 3)

En el anterior post vimos la potencia de Reflection a la hora de preguntar por sus métodos, propiedades, variables, etc. a un ensamblado y además hubo un poco de teoría en cuanto a conceptos como MSIL, CLR, JIT, etc., eso sí, muy básica. Para más información Google.

Por último decir, que la potencia sin control no sirve de nada, con reflection la línea que hay entre la fuerza y el lado oscuro es muy delgada. Es decir con un par de líneas de código nos podemos saltar muchos principios de los lenguajes orientados a objetos, como por ejemplo la encapsulación. Reflection nos permitiría invocar a un constructor privado, o invocar a un método privado, o obtener las variables privadas de cualquier classe. Por eso, tenemos que ser muy conscientes de que estamos tocando con reflection.

Reflection y como ser un Jedi (Part 2)

Resumen: En la entrada anterior vimos una definición de reflection y los namespaces necessarios, para trabajar con reflection. Más adelante se vió algunos ejemplos de cómo crear objetos y como consultar los diferentes constructores de un tipo determinado.

Hecho este pequeño recordatorio vamos a ver que más cosillas podriamos hacer con reflection y subir así un nivel en nuestro curso de Jedi, a saber, ser un Padawan.

Haciendo algún pequeño cambio a nuestro programa, podemos consultar también los métodos, propiedades y variables (fields) de nuestro código.

EJEMPLO 2:
Vamos a ver que métodos tiene nuestra StupidClass:

using System;
using System.Reflection;

namespace StupidNameSpace
{
public class Program
{
static void Main(string[] args)
{
// Obtenemos el tipo, tenemos que pasadle todo el fully qualified name,
// tened en cuenta que estamos en el mismo namespace, y en el mismo assembly, sino fuera así,
// tendriamos que pasarle, el nombre del assembly.
var type = Type.GetType("StupidNameSpace.StupidClass");

MethodInfo[] methods = type.GetMethods();
foreach (MethodInfo mi in methods)
{
Console.WriteLine(string.Format("Method name: {0}",mi.Name));
if(mi.IsPrivate) Console.WriteLine("Private");
else Console.WriteLine("Public");
if(mi.IsStatic) Console.WriteLine("Static");

if (mi.IsVirtual) Console.WriteLine("Virtual");
var parameters = mi.GetParameters();
if(parameters.Length > 0) Console.WriteLine("Parameters:");

foreach(ParameterInfo pi in parameters)
{
Console.WriteLine(string.Format("--> Parameter name: {0}",pi.Name));
Console.WriteLine(string.Format("--> Parameter type: {0}",pi.ParameterType));
}
}

Console.Read();
}
}
}



Cómo se puede observar no supondría ninguna complicación mostrar las propiedades utilizando el método GetProperties que devuelve un array de PropertyInfo o el mostrar los fields utilizando el método GetFields que a su vez devuelve un array de FieldInfo.



Es más, podriamos usar el método GetMethodBody() de la clase MethodInfo, que nos prové acceso a los metadatos y al MSIL del método.



Recordad MSIL significa Microsoft Intermediate Language, es el código que se genera al compilar nuestra aplicación, este código no es un lenguaje máquina en sí, bueno es un “lenguaje máquina” capaz de ser leído por el CLR (Common Language Runtime, “la máquina virtual de .NET”), éste código, será transformado en verdadero código máquina para nuestra procesador por el JIT (just-in-time compiler) que es un componente del CLR.



De esta forma podriamos crearnos un lector de código MSIL, para obtener el código MSIL de los ensamblados que queramos. Es interesante ver algunos ejemplos de esto mismo en:



http://blogs.msdn.com/b/haibo_luo/archive/2005/10/02/476242.aspx



http://www.codeproject.com/KB/cs/sdilreader.aspx



Aquí obtenemos una buena explicación del MSIL:



http://www.codeproject.com/KB/msil/ilassembly.aspx



Llegados a este punto decir que disponemos de una maravillosa herramienta, llamada .Net Reflector que va un paso más allá y nos permite obtener tanto el código MSIL de un assembly, como el código de alto nivel C#, VB, etc.

martes, 19 de octubre de 2010

Reflection y cómo ser un Jedi (Part 1)

¿Qué es reflection?

Respuesta wikipedia: "Reflection es el proceso por el cual un programa puede observar y modificar su propia estructura o comportamiento en tiempo de ejecución."

Respuesta maestro Jedi: "reflection es el control de la fuerza."

Respuesta serie Numb3rs: "Usamos reflection todos los días, al usar el IntelliSense, para debuggar, al usar contenedor IoC. También lo usamos para añadir webparts, para inspeccionar el código y para predecir su comportamiento. Con reflection podemos solucionar los misterios que se nos planteen."

¿Qué debemos hacer para empezar a utilizar la fuerza?

Existe un namespace llamado System.Reflection y una clase conocida como System.Type que nos acompañaran, en esta, fabulosa aventura hacia el interior del código.

Y así, sin más, ¿puedo empezar a usarlo?

Claro, pero no esperes aquí una clase magistral… <tono de Bilbao> ¡¡que somos desarrolladores joder!!</tono de Bilbao>. Dame un  punto de apoyo  HelloWorld y moveré el mundo conoceré toda la sintaxis.

Así que aquí van unas cuantos ejemplos, para la explicación vamos a utilizar una clase estúpida dentro de un namespace estúpido.

namespace StupidNameSpace
{
public class StupidClass
{
private string _field1;
private string _field2;

public StupidClass()
{
}

public StupidClass(string field1, string field2)
{
_field1 = field1;
_field2 = field2;
}

public string Field1
{
get { return _field1; }
set { _field1 = value; }
}

public string Field2
{
get { return _field2; }
set { _field2 = value; }
}

public string FooMethod1()
{
return string.Empty;
}

public string FooMethod2(string parameter)
{
return parameter;
}
}


Ahora vamos a crear una pequeña aplicación de consola para interrogar, utilizando la fuerza (reflection), a nuestra clase.

Ejemplo 1:
En el primer ejemplo vamos a crear un objeto de tipo StupidClass, usando el constructor público sin parámetros y luego utilizando el constructor con parámetros.



using System;
using System.Reflection;

namespace StupidNameSpace
{
public class Program
{
static void Main(string[] args)
{
// Obtenemos el tipo, tenemos que pasadle todo el fully qualified name,
// tened en cuenta que estamos en el mismo namespace, y en el mismo assembly, sino fuera así,
// tendriamos que pasarle, el nombre del assembly.
var type = Type.GetType("StupidNameSpace.StupidClass");
// Podriamos haber usado:
// var type = typeof(StupidClass);

// Usamos el constructor sin parámetros (default constructor) para crear un objecto llamado
// myStupidObject
var types = new Type[0];
var constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, types, null);
var myStupidObject = (StupidClass)constructorInfo.Invoke(null);

// Ahora hacemos lo mismo pero llamamos al constructor que recibe 2 parámetros
// y creamos un objeto llamado myStupidObject2
types = new Type[2];
types[0] = typeof (string);
types[1] = typeof (string);

var parameters = new object[2];
parameters[0] = "param1";
parameters[1] = "param2";

constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, types, null);
var myStupidObject2 = (StupidClass) constructorInfo.Invoke(parameters);
}
}
}


Esto esta muy bien, pero porque sabemos que constructor tiene la clase, si es público o privado, si recibe parámetros, etc. Pero y si no lo sabemos. Pues preguntémosle.



using System;
using System.Reflection;

namespace StupidNameSpace
{
public class Program
{
static void Main(string[] args)
{
// Obtenemos el tipo, tenemos que pasadle todo el fully qualified name,
// tened en cuenta que estamos en el mismo namespace, y en el mismo assembly, sino fuera así,
// tendriamos que pasarle, el nombre del assembly.
var type = Type.GetType("StupidNameSpace.StupidClass");

ConstructorInfo[] constructors = type.GetConstructors();
foreach(ConstructorInfo ci in constructors)
{
Console.WriteLine("===========");
Console.WriteLine("CONSTRUCTOR");
Console.WriteLine("===========");
Console.WriteLine(ci.Name);
if(ci.IsPrivate) Console.WriteLine("Private");
else Console.WriteLine("Public");
if(ci.IsStatic) Console.WriteLine("Static");
if(ci.IsAbstract) Console.WriteLine("Abstract");
if (ci.IsVirtual) Console.WriteLine("Virtual");
var parameters = ci.GetParameters();
if(parameters.Length > 0) Console.WriteLine("Parameters:");

foreach(ParameterInfo pi in parameters)
{
Console.WriteLine("-->" + pi.Name);
Console.WriteLine("-->" + pi.ParameterType);
}
}

Console.Read();
}
}
}

lunes, 11 de octubre de 2010

Error JavaScript en LookupField Control

Hoy he estado peleándome con uno de esos errores que odias encontrar, ya que son de esos errores que cuesta un poco reproducir, y por lo tanto su resolución no se prevé facilona.

Voy a intentar situarlos en contexto:

Cuando mostramos un LookupField control, por ejemplo al editar un item de una lista que uno de sus campos es un lookup hacia otra lista (editform.aspx), éste se puede mostrar de dos maneras diferentes, una cuando la lista a la que hacemos el lookup tiene menos de 20 items o 20 items y otra cuando tiene más de 20 items. En el primer caso, el control muestra un DropDownList y en el segundo muestra un textbox, un par de literales y una imagen (para no hacer que el dropdown sea demasiado largo), con ciertas llamadas a funciones javascript.

Una de estas llamadas se ejecuta al hacer click en la imagen, concretamente es una llamada a la función ShowDropdown que se encuentra en el fichero core.js. Esta función acaba llamando a otras dos funciones AbsLeft y AbsTop, que son el objeto de este post.

Código original de las funciones:

function AbsLeft(obj)
{
var x=obj.offsetLeft;
var parent=obj.offsetParent;
while (parent.tagName !="BODY")
{
x+=parent.offsetLeft;
parent=parent.offsetParent;
}
x+=parent.offsetLeft;
return x;
}

function AbsTop(obj)
{
var y=obj.offsetTop;
var parent=obj.offsetParent;
while (parent.tagName !="BODY")
{
y+=parent.offsetTop;
parent=parent.offsetParent;
}
y+=parent.offsetTop;
return y;
}


Resulta que estas dos funciones lanzan un null reference exception cuando el control lookup esta contenido dentro de un div y ese div contiene un position:relative.



Googleando un poco me he encontrado que este era un error conocido, pero la resolución que daban en un foro de sharepoint no me parecía la correcta, a saber, eliminar el position:relative.



http://social.msdn.microsoft.com/Forums/en-US/sharepointdevelopment/thread/91f91a91-434f-444d-8a96-7823c91f862e



Mi solución final ha sido sobreescribir estas funciones en la master page de mi solución:



function AbsLeft(obj) {
var x = obj.offsetLeft;
var parent = obj.offsetParent;

while (parent != null && parent.tagName != "DIV" && parent.tagName != "BODY") {
x += parent.offsetLeft;
parent = parent.offsetParent;
}

if (parent != null && parent.tagName != "DIV" && parent != null) x += parent.offsetLeft;

return x;
}

function AbsTop(obj) {
var y = obj.offsetTop;
var parent = obj.offsetParent;

while (parent != null && parent.tagName != "DIV" && parent.tagName != "BODY") {
y += parent.offsetTop;
parent = parent.offsetParent;
}

if (parent != null && parent.tagName != "DIV" && parent != null) y += parent.offsetTop;

return y;
}


Pero aquí no ha acabado todo, estas funciones estan dentro del core.js y el core.js tiene el atributo defer=True, por lo tanto, nunca se llamaban a las funciones que había sobreescrito. Solución: cambiar este attributo añadiendo lo siguiente en la master page.

<SharePoint:ScriptLink language="javascript" name="core.js" Defer="false" runat="server"/>



Para más información sobre el attribute defer, aquí.

lunes, 4 de octubre de 2010

Periodic table HTML 5

Os dejo un enlace a una chuleta donde se muestran los diferentes elementos existentes en HTML 5, en forma de tabla períodica de los elementos. Cada “elemento” contiene links a W3C developer’s guide y a W3Schools.

http://joshduck.com/periodic-table.html

jueves, 30 de septiembre de 2010

SharePoint Reference Sheet

Hay que agradecer cuando alguien hace un excelente servicio, como recoger en una misma página tantísimas propiedades muy usadas cuando trabajas con SharePoint y CAML.

http://abstractspaces.wordpress.com/2008/08/01/sharepoint-reference-sheet/

lunes, 27 de septiembre de 2010

Truquillo CQWP

Hoy toca un pequeño truquillo que va bien para mostrar los campos que le estan llegando a una Content Query Web Part, así como el valor de estos campos.

La ideas es my simple, modificar el itemstyle.xsl o crear uno custom y añadir el siguiente trozo de xsl:

<xsl:for-each select="@*">
Name:<xsl:value-of select="name()" />
Value:<xsl:value-of select"." />
</xsl:for-each>


Una explicación más detallada la podemos encontrar aquí.

martes, 21 de septiembre de 2010

Crear una managed metadata column

Crear una managed metadata column no es tan fácil como seria crear una columna de tipo texto o numérica, etc. El proceso se parece más bien a la manera de crear una columna de tipo lookup.

Este sería el tag Field para una managed metadata column:

<Field Type="TaxonomyFieldTypeMulti"
DisplayName="MyName"
List="TaxonomyHiddenList"
WebId="/"
ShowField="Term1033"
Required="FALSE"
EnforceUniqueValues="FALSE"
Mult="TRUE"
Sorteable="FALSE"
ID="{71A679B9-B7A4-474D-B2EF-D5230C3285AD}"
StaticName="MyName"
Name="MyName"
Group="MyColumns"
Indexed="FALSE">
<Default />
<Customization>
<ArrayOfProperty>
<Property>
<Name>SspId</Name>
<Value xmlns:q1="http://www.w3.org/2001/XMLSchema"
p4:type="q1:string"
xmlns:p4="http://www.w3.org/2001/XMLSchema-instance"></Value>
</Property>
<Property>
<Name>GroupId</Name>
</Property>
<Property>
<Name>TermSetId</Name>
<Value xmlns:q2="http://www.w3.org/2001/XMLSchema"
p4:type="q2:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">MyTermSetId</Value>
</Property>
<Property>
<Name>AnchorId</Name>
<Value xmlns:q3="http://www.w3.org/2001/XMLSchema"
p4:type="q3:string"
xmlns:p4="http://www.w3.org/2001/XMLSchema-instance"></Value>
</Property>
<Property>
<Name>UserCreated</Name>
<Value xmlns:q4="http://www.w3.org/2001/XMLSchema"
p4:type="q4:boolean"
xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">false</Value>
</Property>
<Property>
<Name>Open</Name>
<Value xmlns:q5="http://www.w3.org/2001/XMLSchema"
p4:type="q5:boolean"
xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">false</Value>
</Property>
<Property>
<Name>TextField</Name>
<Value xmlns:q6="http://www.w3.org/2001/XMLSchema"
p4:type="q6:string"
xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">{AD2D9982-10C9-4BDA-A04C-754EC1DCA9FA}</Value>
</Property>
<Property>
<Name>IsPathRendered</Name>
<Value xmlns:q7="http://www.w3.org/2001/XMLSchema"
p4:type="q7:boolean"
xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">false</Value>
</Property>
<Property>
<Name>IsKeyword</Name>
<Value xmlns:q8="http://www.w3.org/2001/XMLSchema"
p4:type="q8:boolean"
xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">false</Value>
</Property>
<Property>
<Name>TargetTemplate</Name>
</Property>
<Property>
<Name>CreateValuesInEditForm</Name>
<Value xmlns:q9="http://www.w3.org/2001/XMLSchema"
p4:type="q9:boolean"
xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">false</Value>
</Property>
<Property>
<Name>FilterAssemblyStrongName</Name>
<Value xmlns:q10="http://www.w3.org/2001/XMLSchema"
p4:type="q10:string"
xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">Microsoft.SharePoint.Taxonomy, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Value>
</Property>
<Property>
<Name>FilterClassName</Name>
<Value xmlns:q11="http://www.w3.org/2001/XMLSchema"
p4:type="q11:string"
xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">Microsoft.SharePoint.Taxonomy.TaxonomyField</Value>
</Property>
<Property>
<Name>FilterMethodName</Name>
<Value xmlns:q12="http://www.w3.org/2001/XMLSchema"
p4:type="q12:string"
xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">GetFilteringHtml</Value>
</Property>
<Property>
<Name>FilterJavascriptProperty</Name>
<Value xmlns:q13="http://www.w3.org/2001/XMLSchema"
p4:type="q13:string"
xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">FilteringJavascript</Value>
</Property>
</ArrayOfProperty>
</Customization>
</Field>


Además hay que tener en cuenta que al crear una managed metadata column en una lista, también se añade una columna de tipo Note, asociada a esta. Aquí mostramos un ejemplo.



<?xml version="1.0" encoding="utf-8" ?>
<Field Type="Note"
DisplayName="MyName_0"
StaticName="MyNameTaxHTField0"
Name="MyNameTaxHTField0"
ID="{AD2D9982-10C9-4BDA-A04C-754EC1DCA9FA}"
ShowInViewForms="FALSE"
Required="FALSE"
Hidden="TRUE"
CanToggleHidden="TRUE" />


Como podéis ver en el primer xml (reference al field de la managed metadata column) hay un conjunto de atributos o elementos que no los podemos saber cuando creamos el xml, sino que lo sabremos en tiempo de ejecución o de provisioning.



Estos elementos serían los siguientes:




  • WebId


  • List


  • SspId


  • TermSetId


  • AnchorId


  • *TextField (esto lo podemos saber si nosotros previamente hemos provisionado el field note que irá asociado a esta columna)



Esto lo hemos solucionando, con un feature receiver que introduce estos atributos (o elementos) en el xml, durante la ejecución, a continuación se muestra algún trozo de esta solución.



Los pasos a seguir serían los siguientes:



// Add Note Field

string fileManagedMetadataNoteField = @"C:\NoteField.xml";

var xmlDocNoteField = XDocument.Load(fileManagedMetadataNoteField);

web.Site.RootWeb.Fields.AddFieldAsXml(xmlDocNoteField);

// Get Managed Metadata Column xml
string filePath = @"C:\ManagedMetadataColumn.xml";

var xmlDoc = XDocument.Load(filePath);

// Modify WebId and ListId
ReplaceWebIdAndListId(xmlDoc, web);

// Modify Customization Element
ModifyCustomization(web, xmlDoc);

web.Site.RootWeb.Fields.AddFieldAsXml(xmlDoc);

private void ReplaceWebIdAndListId(XDocument doc, SPWeb web)
{
var attributeWebId = from f in doc.Elements("Field")
select f.Attribute("WebId");

var attributeList = from f in doc.Elements("Field")
select f.Attribute("List");

string webUrl = attributeWebId.First().Value;

string listName = attributeList.First().Value;

SPWeb referencedWeb = web;
if (!string.IsNullOrEmpty(webUrl))
{
referencedWeb = web.Site.OpenWeb(webUrl);
}

attributeWebId.First().SetValue(referencedWeb.ID.ToString());

SPList referencedList = referencedWeb.Lists[listName];

attributeList.First().SetValue(referencedList.ID.ToString("B"));

if (!string.IsNullOrEmpty(webUrl))
{
referencedWeb.Dispose();
}
}


private void ModifyCustomization(SPWeb web, XDocument doc)
{
var properties = from p in doc.Descendants("Property")
select p;

TermStore termStore = null;
TermSet termSet = null;

foreach (var property in properties)
{
switch (property.Element("Name").Value)
{
case "SspId":
termStore = GetDefaultSiteCollectionTermStore(web.Site);
property.Element("Value").SetValue(termStore.Id.ToString());
break;
case "TermSetId":
termSet = GetTermSet(termStore, property.Element("Value").Value);
property.Element("Value").SetValue(termSet.Id.ToString());
break;
case "AnchorId":
Guid termId = GetTermId(termSet, property.Element("Value").Value);
property.Element("Value").SetValue(termId.ToString());
break;
}
}

}

private TermStore GetDefaultSiteCollectionTermStore(SPSite site)
{
TaxonomySession session = new TaxonomySession(site);
return session.DefaultSiteCollectionTermStore;
}

private TermSet GetTermSet(TermStore termStore, String termSetName)
{
TermSetCollection termSets = termStore.GetTermSets(termSetName, termStore.WorkingLanguage);
return termSets[0];
}

private Guid GetTermId(TermSet termSet, String termNames)
{
TermCollection termCollection = termSet.Terms;
Term term = null;

if (!string.IsNullOrEmpty(termNames))
{
termNames.Split('/').ToList().ForEach(delegate(String termName)
{
term = termCollection[termName];
termCollection = term.Terms;
});
}

if (term == null) return Guid.Empty;
else return term.Id;
}

Add managed metadata programmatically II

En el post anterior comentaba el nuevo servicio de managed metadata introducido en MSS 2010, además se explicaba como añadir los términos utilizando la interfaz gráfica. En este nuevo post se va a hacer incapie de como añadir estos metadatos a través del modelo de objetos de MSS 2010.

TaxonomySession session = new TaxonomySession(site);
TermStore termStore = session.DefaultSiteCollectionTermStore;
ImportManager im = termStore.GetImportManager();

var groupToAddTermSet = termStore.CreateGroup("Group name");
groupToAddTermSet.Description = "Group description";

bool allTermsAdded = false;
string errorMessage = string.Empty;

using (TextReader streamReader = new StreamReader(csvPath))
{
im.ImportTermSet(groupToAddTermSet, streamReader, out allTermsAdded, out errorMessage);
}

termStore.CommitAll();


Cómo podemos ver en este code snippet, le estamos pasando un TextReader a la función de ImportTermSet del objecto de tipo ImportManager. Este TextReader es un stream a un fichero .csv, con el siguiente formato:



"Term Set Name","Term Set Description","LCID","Available for Tagging","Term Description","Level 1 Term","Level 2 Term","Level 3 Term","Level 4 Term","Level 5 Term","Level 6 Term","Level 7 Term"

Add managed metadata programmatically I

Con MSS 2010 disponemos de un nuevo servicio llamado Managed Metadata Service, con este servicio podemos tener conjuntos de términos, términos y subtérminos que pueden ser utilizados en nuestros sitios como metadatos para nuestros items, de esta forma podemos “etiquetar” de una manera elegante nuestro contenido.

Además, MSS 2010, dispone de un conjunto de utilidades, para navegar por estas managed metadatas.

En este post, se intentará explicar como proveer estos metadatos de forma programatically.

Para más información sobre este servicio podéis consultar la siguiente url:

http://technet.microsoft.com/en-us/library/ee424402.aspx

Un compañero de spenta, Ignasi Tebé, en su blog también comenta esta nueva funcionalidad de manera detallada:

MSS 2010: Servicio de Metadatos Administrados (I)
MSS 2010: Servicio de Metadatos Administrados (II) Content Type Hubs
MSS 2010: Servicio de Metadatos Administrados (III) Ejemplo de uso
MSS 2010: Metadatos administrados: Terminos anidados.
MSS 2010: Metadatos administrados: Importación de estructuras.

Lo que voy a explicar, también se puede hacer directamente a través de la interfaz gráfica de la administración central, a continuación voy a explicar los pasos a seguir para hacerlo a través de la administración central.

  • Ir a Central Administration
  • Manage Service Applications
  • Managed Metadata Services
  • Add term group
    • Add term set
      • Add term
        • Add sub term

miércoles, 28 de julio de 2010

FileNotFoundException al crear un objeto SPSite en Visual Studio 2010

Hoy me he encontrado con un problema tonto, pero que me ha hecho perder un ratillo de trabajo, y es el siguiente:

He creado una aplicación de consola con Visual Studio 2010 y me he propuesto hacer un simple SPSite site = new SPSite("http://localhost");, me he quedado estupefacto cuando esta instrucción me ha lanzado una excepción del tipo FileNotFoundException. Al principio he pensado que era algo de temas de permisos y tal y he empezado a mirar detalladamente, pero nada todo parecía estar correcto.

Googleando he visto el siguiente hilo: http://social.msdn.microsoft.com/Forums/en-US/sharepointdevelopmentprerelease/thread/ae57f9f1-7a6c-48bc-95ac-2643b76867d8 y efectivamente, !!esto es lo que me pasaba a mi!!

Mi entorno es un entorno de 64bits donde tengo instalado mi granja de MSS 2010. Parece ser que cuando creas un proyecto de console application en visual studio 2010, queda configurado x86 como platform target, simplemente cambiando esto a x64 o Any CPU, funciona correctamente.

Para cambiarlo, hemos de acceder a las propiedades del proyecto y en la pestaña Build cambiar el dropdown de platform target.

martes, 27 de julio de 2010

Add managed properties programmatically

Siguiendo con el tema de búsqueda en MSS 2010, nos podemos encontrar que deseamos crear managed properties en tiempo de despliegue, por lo que necesitariamos de alguna forma poder crear managed properties dentro de un feature receiver por ejemplo. Antes de proceder comentar que si bien esto es posible, primero necesitariamos contar con crawled properties ya generadas (por ejemplo de nuestros metadatos), y para ello primero necesitariamos una primera indexación. Una vez comentado esto procederemos a explicar como provisionar estas managed properties.

Lo primero que deberiamos hacer es obtener el searchserviceapplication, para que a partir de este podamos obtener el schema. Esto lo vimos en el post anterior.



SPServiceContext serviceContext = SPServiceContext.GetContext(site);

SPServiceApplicationProxy proxy = serviceContext.GetDefaultProxy(typeof(SearchServiceApplicationProxy));
SearchServiceApplicationProxy searchAppProxy = proxy as SearchServiceApplicationProxy;

SearchServiceApplicationInfo ssai = searchAppProxy.GetSearchServiceApplicationInfo();
SearchServiceApplication application =
Microsoft.Office.Server.Search.Administration.SearchService.Service.SearchApplications.
GetValue(ssai.SearchServiceApplicationId);

Schema schema = new Schema(application);



Una vez tenemos esto podemos proceder a crear la managed property, una managed property consta principalmente de un nombre, un tipo de dato (text, integer, datetime), y un mapping con una o más crawled properties.



MappingCollection mappingCollection = new MappingCollection();

Microsoft.Office.Server.Search.Administration.ManagedProperty managedProperty = schema.AllManagedProperties.Create("My Managed Property", ManagedDataType.Text);


CrawledProperty crawledProperty = GetCrawledProperty(schema, "MyCrawledProperty", ManagedDataType.Text);
Mapping mapping = new Mapping(crawledProperty.Propset, crawledProperty.Name, crawledProperty.VariantType, managedProperty.PID);

mappingCollection.Add(mapping);


managedProperty.SetMappings(mappingCollection);

managedProperty.Update();



En este trozo de código podemos ver que hacemos una llamada a un método que nos devuelve una crawled property a partir del schema, un tipo y un nombre. A continuación muestro este método que se encarga de recorrer las crawled properties para obtener la que hemos solicitado.



protected CrawledProperty GetCrawledProperty(Schema schema, string name, ManagedDataType type)
{
CrawledProperty result = null;

foreach (CrawledProperty cproperty in schema.QueryCrawledProperties(string.Empty, 1000, Guid.NewGuid(), string.Empty, true))
{
if (cproperty.Name == name)
{
if (cproperty.VariantType == GetVariantType(type))
{
result = cproperty;
break;
}
}
}

return result;
}

protected int GetVariantType(ManagedDataType type)
{
switch (type)
{
case ManagedDataType.Text: return 31;
case ManagedDataType.Decimal: return 5;
case ManagedDataType.Integer: return 20;
case ManagedDataType.DateTime: return 64;
default: return 31;
}
}


Bueno, pues esto es todo.

lunes, 26 de julio de 2010

Crear exclude crawling rules programmatically 2010

Hace unos días comentaba como crear exclude crawling rules en Microsoft Office SharePoint Server 2007 , hoy haremos lo mismo pero ahora con Microsoft SharePoint Server 2010.


1. Obtener el Search Context a partir del SPSite



SPServiceContext serviceContext = SPServiceContext.GetContext(site);



2. Obtenemos el proxy del application service de search (esto es nuevo en 2010, como sabemos ahora desaparecido el shared service provider y tenemos una nueva arquitectura de servicios de aplicación)



SPServiceApplicationProxy proxy = serviceContext.GetDefaultProxy(typeof(SearchServiceApplicationProxy));
SearchServiceApplicationProxy searchAppProxy = proxy as SearchServiceApplicationProxy;



3. Obtenemos el objecto de la aplicación de search


SearchServiceApplicationInfo ssai = searchAppProxy.GetSearchServiceApplicationInfo();
SearchServiceApplication application =
Microsoft.Office.Server.Search.Administration.SearchService.Service.SearchApplications.
GetValue(ssai.SearchServiceApplicationId);



4. Obtenemos el Content Source a partir del objecto SerchApplication



Content content = new Content(application);



A partir de aquí es bastante parecido a como trabajabamos con MOSS 2007.

5. Añadir la crawl rule a la colección de crawlrules del content source


CrawlRule crawlRule = content.CrawlRules.Create(CrawlRuleType.ExclusionRule, "http://*/forms/*");



6. Añadir propiedades



crawlRule.FollowComplexUrls = true;



7. Commit la regla en la base de datos



crawlRule.Update();

martes, 13 de julio de 2010

Instalar sharepoint 2010 con un usuario local sí, pero no


Hace unos días comentaba que nos podiamos saltar la restricción que imponian desde Microsoft en cuanto a la instalación de un MSS 2010 (Microsoft SharePoint Server 2010) y creación de una granja bajo una cuenta de administrador local. Hoy he de decir que si que es posible, pero nos encontraremos problemas respecto al servicio de búsqueda.

Al intentar configurar el servicio de búsqueda e intentar realizar alguna búsqueda me encontraba con un error del tipo: "Internal server error exception".


Estirando del hilo, surfeando por logs e intentado descrifrar el porqué y cuando se producía este error llegue a ver otro error del tipo:

EnumerateQueryComponents returned an error after enumerating 0 components. Error returned was 0x80131600. cd74d139-25c5-4db5-8c81-a9b9a6e17b1f
07/13/2010 12:39:51.68 w3wp.exe (0x14D0) 0x151C SharePoint Server Search Query Processor e2tq High Source: Microsoft.Office.Server.Search Description: No query components were found for this search application.

Entonces dije, bueno pues voy a crear añadir un query component siguiendo los pasos que se indican aquí.

Al intentar finalizar el último paso me encontré de nuevo con otra barrera. Y con el siguiente error:

Component cbfb21b3-805b-4e3e-9c37-8339756385f9-query-0 of search application 'Search Service Application' has failed to execute transition sequence 'initialize with empty catalog' due to the following error: System.ArgumentException: The SDDL string contains an invalid sid or a sid that cannot be translated.

Por último googleando un poco más y buscando el error que me había dado encontre algunos post que hacian referencia a que no es compatible el servicio de búsqueda y una granja configurada con un usuario local.







jueves, 8 de julio de 2010

Ordenar listas en MOSS 2007

Si alguna vez nos hemos fijado en una lista de links, esta incorpora una action que nos permite ordenarla. Esta action lo que nos hace es llevarnos a una application page llamada reorder.aspx.

Luego puedes utilizar el campo order para ordenar los items en una content query webpart por ejemplo.

Tirando un poco más del hilo observas que toda item de cualquier lista, contiene ese campo order.

Y aquí es donde surge la pregunta: ¿por qué los señores de Redmond no han incorporado esto a todas las listas y así poder usar esta ordenación en las content query webparts?

Pensando que ha sido un despite, nos decidimos a incorporar esta funcionalidad a todas las listas creando una custom action y añadiendola a todas las listas. Pero cuál fue nuestro despiste que vimos que esta ordenación funcionaba bien excepto para las document libraries.

Haciendo un poquito de debugging y reflectoring vimos que el problema reside en el método que usa para ordenar las listas, este método es un método de la clase SPListItemCollection, el método se llama ReorderItems.

Ahora ya habiamos cruzado ese umbral que es más facil seguir para adelante que volverse para atrás así que nos decidimos a rehacer ese algoritmo de ordenación para que funcionase también con las document libraries.

En definitiva los artefactos que hemos creado han sido:

- Una custom action que se añade a todas las listas y su correspondiente feature
- Un page adapter para capturar el submit de la página reorder.aspx y a partir de aquí llamar a nuestro nuevo algoritmo de reordenación.
- Un extensión method para SPListItemCollection que implementa el algoritmo de ordenación.

martes, 6 de julio de 2010

Instalar sharepoint 2010 con un usuario local

Por defecto, el wizard de sharepoint 2010 no nos permite crear una granja a partir de una cuenta de usuario local, pero nos podemos saltar esta protección gracias a un comando de powershell, en concreto: New-SPConfigurationDatabase. Una vez creamos la base de datos de configuración a partir de este comando, podemos arrancar de nuevo el wizard de instalación y ya nos reconocera una base de datos de configuración que habrá sido creada y configurada con nuestro usuario local.

Para más info:

http://www.dev4side.com/community/blog/2010/5/2/how-to-install-sharepoint-2010-with-a-local-account.aspx

miércoles, 30 de junio de 2010

¿Hasta cuándo el divide y vencerás?

En los inicios el aprendizaje se aglutinaba en una sola materia, a saber, LA FILOSOFÍA, aunque ya Aristóteles en su libro FÍSICA, dividía entre matemáticas, física, filosofía primera, etc.
Pero esta división hay ido aumentando con el paso de los años y han aparecido muchísimas materias, entre ellas, historia, geografía, matemáticas, física, química, religión, medicina, etc., y ojo que estoy enumerando las materias troncales, porque de éstas además aparecen otras como, ingeniería de puertos y caminos, ingeniería informática, ingeniería industrial, estadística, y un sinfín que no me voy a poner ahora a enumerar. Si además cogemos una de estas materias vemos que aparecen dentro de ellas innumerables especializaciones, y el lector en este punto se preguntará que a dónde quiero llegar.

Antes de que el lector se desoriente del todo y abandone este blog voy a explicarle, el porqué de esta entrada. Llevo un tiempo fijándome en que la gente se suele especializar en una rama de una materia concreta haciéndose poseedor de un gran conocimiento sobre su especialización, un sabio, se podría decir, y esto no es malo, pero puede llegar a un punto que antepone su conocimiento al resto de los conocimientos, y de alguna forma puede llegar a ningunear los "otros conocimientos". Para intentar clarificar mi posición voy a poner un ejemplo, hoy he estado leyendo un blog sobre una persona experta en usabilidad y arquitectura de la información, en uno de sus posts había escrito que "lo más importante" son las interfaces y el autor podría enumerar muchos argumentos donde le tendríamos que dar la razón de manera inexorable. Pero da la casualidad que si hablas con un administrador de bases de datos, te dirá que "lo más importante" son los datos y de nuevo nos bombardeará con n argumentos, todos dignos de aprobación, y es increíble que si hablásemos con un experto en cualquier otra rama podría ofrecernos un abanico de argumentos exponiéndonos que su rama es "lo más importante". Y aquí es donde empieza a perder la credibilidad el primero, el segundo y el resto de expertos.

Personalmente, creo que nada es "lo más importante" sino que cada cosa es importante en su medida, y lo realmente importante es el todo y el todo visto como una composición de las partes, y digo composición y no agregación, porque no creo que ese todo pueda sobrevivir a una de sus partes. En el campo del desarrollo (que es donde mejor me muevo, debido a mi "especialización"), no puede existir una gran aplicación donde la interfaz sea muy buena, pero por ejemplo la aplicación no sea mantenible, o no puede existir una gran aplicación donde tenga una estructura de datos inmejorable, pero la interfaz sea bochornosa, y así con cada una de sus partes. Dejemos de "dividir" y empecemos a "componer".

Pero bueno en realidad no estoy diciendo nada nuevo, yo creo que esto es sabido por todos, pero no soporto cuando leo a expertos que a propósito o inducidos por su súper yo piensan que su rama o especialización se puede sobreponer a cualquier otra, para mi esto es el claro ejemplo de la definición de hombre masa expuesta por Ortega y Gasset.

"He aquí un precioso ejemplar de este extraño hombre nuevo que he intentado, por una y otra de sus vertientes y haces, definir. He dicho que era una configuración humana sin par en toda la historia. El especialista nos sirve para concretar enérgicamente la especie y hacernos ver todo el radicalismo de su novedad. Porque antes los hombres podían dividirse, sencillamente, en sabios e ignorantes, en más o menos sabios y más o menos ignorantes. Pero el especialista no puede ser subsumido bajo ninguna de esas dos categorías. No es sabio, porque ignora formalmente cuanto no entra en su especialidad; pero tampoco es un ignorante, porque es «un hombre de ciencia» y conoce muy bien su porciúncula de universo. Habremos de decir que es un sabio-ignorante, cosa sobremanera grave, pues significa que es un señor el cual se comportará en todas las cuestiones que ignora no como un ignorante, sino con toda la petulancia de quien en su cuestión especial es un sabio."
José Ortega y Gasset. La rebelión de las masas.

martes, 15 de junio de 2010

SharePoint css y error javascript al mover webparts

Hoy me he encontrado con un problemilla al intentar mover (drag and drop) unas webparts de una webpart zone a otra, el problema era bien simple, me ha saltado un error javascript y no me dejaba hacer esta operación.

He debugado el javascript que me estaba dando problemas y resulta que era una función llamada MSOLayout_GetRealOffset. Esta función se encuentra en el fichero  \TEMPLATE\LAYOUTS\1033\IE55UP.JS.

Googleando un poco me he encontrado que este problema aparece cuando tu master page contiene algún referencia a estilo con position:relative. Y este era mi caso. Puedes observarlo aquí.

Googleando también he econtrado una solución a este problema aquí. Ésta básicamente reside o bien en quitar el position:relative del css (cosa que no me ha interesado) o bien sobreescribir esta función. Por lo que he optado por esta segunda. Para sobreescribir esta función simplemente has de copiar el siguiente código después del SPWebPartManager.

Updating SPListItem

En un proyecto en el que estoy trabajando me he encontrado con la necesidad de añadir datos a listas en tiempo de despliegue, a partir de un archivo xml, más o menos como lo que haria la feature listinstance cuando le especificamos un child element data.

Más info...

Una vez leído este fichero xml obtenemos un diccionario key-value, con los fields (keys) de nuestro item y sus valores (value), en nuestro caso ambos serian string.

El problema con el que nos encontramos es que en un SPListItem almacenamos objects, y por lo tanto tendriamos que parsear de alguna forma nuestros strings al tipo de campo que requiere ese field. Para saberlo necesitamos consultar el SPField y este SPField tiene un SPFieldValueType que nos permite determinar el tipo de dato que se almacena en ese campo. Luego añadimos un poco de reflection y utilizamos una función muy interesante que he descubierto, Convert.ChangeType, que permite cambiar de un tipo a otro, y eureka!!! Tenemos un extensión method super útil.

/// 
/// Update an item using a dictionary with key-value pairs
///

/// The item
/// The data collection
public static void Update(this SPListItem item, Dictionary data)
{
bool overwriteVersion = false;

foreach (String internalFieldName in data.Keys)
{
if (item.Fields.Contains(internalFieldName))
{
SPField field = item.Fields.GetFieldByInternalName(internalFieldName);

ConstructorInfo constructor = field.FieldValueType.GetConstructor(new Type[] { typeof(string) });

if (constructor != null)
{
item[internalFieldName] = constructor.Invoke(new object[] { data[internalFieldName]});
}
else
{
item[internalFieldName] = Convert.ChangeType(data[internalFieldName], field.FieldValueType, CultureInfo.InvariantCulture);
}

if (field.ReadOnlyField || field.Type == SPFieldType.Computed) overwriteVersion = true;
}
}

if (overwriteVersion) item.UpdateOverwriteVersion();
else item.Update();
}

miércoles, 9 de junio de 2010

Expresiones regulares

Hoy en un proyecto he tendio que buscar un texto dentro una cadena que cumpla un determinado patrón, bueno esto como seguramente todos conocen lo podemos solventar utilizando expresiones regulares. La verdad es que nunca me ha gustado batallar con expresiones regulares, pero creo que son de gran utilidad para estos casos.

He estado buscando en la red alguna herramienta que sea de ayuda a la hora de elaborar estas expresiones regulares y he encontrado esta herramienta online que me ha venido muy bien: http://gskinner.com/RegExr/

Supongo que como esta, habrán otras muchas herramientas similares.

Hago también una mención especial a la siguiente web site ya que me ha servido de gran ayuda http://www.radsoftware.com.au/articles/regexlearnsyntax.aspx

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);
}
}
}





jueves, 29 de abril de 2010

Popup para seleccionar lista

Alguna vez nos hemos preguntado como mostrar una propiedad que nos permita seleccionar una lista dentro de la site collection, de la misma forma que hace la content query webpart "CQWP". El siguiente post intentará mostrar como hacer esto:

Lo primero que se ha hecho es crear un editor part que carga un user control, este user control tendría que tener un textbox para añadir la url de la lista, y un botón que llame a un javascript, en nuestro caso "launchpicker" que nos permita mostrar el popup.



var lastSelectedListSmtPickerId = '';

function launchPicker(id) {

if (!document.getElementById) return;

var listurlfield = document.getElementById(id);
var defaulturl = '<%=SPContext.Current.Web.ServerRelativeUrl %>';

var url = defaulturl;

if (listurlfield != null && listurlfield.value != '') {
url = listurlfield.value.substring(0, listurlfield.value.lastIndexOf('/'));
}

var callback = function(results) {
if (results == null || results == undefined || results[1] == null || results[2] == null) return;

lastSelectedListSmtPickerId = results[0];
var listUrl = '/';
if (results[1].charAt(0) == '/') results[1] = results[1].substring(1);
listUrl = listUrl + results[1];
if (listUrl.substring(listUrl.length - 1) != '/') listUrl = listUrl + '/';
if (results[2].charAt(0) == '/') results[2] = results[2].substring(1);
listUrl = listUrl + results[2];
listurlfield.value = listUrl;
};

LaunchPickerTreeDialog('CbqPickerSelectListTitle', 'CbqPickerSelectListTitle', 'listsOnly', '', url, lastSelectedListSmtPickerId, '', '', '/_layouts/images/smt_icon.gif', '', callback);
}



Luego nuestra editor part tendría que cargar dinámicamente este user control que hemos creado, además ha de implementar los métodos Synchanges, de donde obtendremos el valor actual configurado en la webpart y el método ApplyChanges donde haremos un set de la propiedad de la webpart.



public class FooEditorPart: EditorPart
{
#region Fields

protected GetListUserControl editorUC;

#endregion

#region Constructor

public FooEditorPart(string webpartid)
{
this.ID = "FooEditorPart" + webpartid;
this.Title = "Link Editor";
}

#endregion

#region Overriden Methods

///
/// Create the controls
///

protected override void CreateChildControls()
{
base.CreateChildControls();

try
{
editorUC = (GetListUserControl)this.Page.LoadControl("~/_CONTROLTEMPLATES/GetListUserControl.ascx");

Controls.Add(editorUC);
}
catch (Exception ex)
{
LogHelper(ex)
}
}

///
/// Get the value from webpart
///

public override void SyncChanges()
{
EnsureChildControls();

WebpartFoo webPart = WebPartToEdit as WebpartFoo;
if (webPart != null)
{
editorUC.TbListUrl.Text = webPart.LinkList;
}
}

///
/// ApplyChanges in webpart
///

///
public override bool ApplyChanges()
{
EnsureChildControls();
WebpartFoo webPart = WebPartToEdit as WebpartFoo;
if (webPart != null)
{
webPart.LinkList= editorUC.TbListUrl.Text;
}
return true;
}

#endregion
}



Por último nuestra webpart tendrá que tener una propiedad string con el atributo WebBrowsable(false), y tendrá que implementar la interfaz IWebEditable, esto nos obliga a implementar el método CreateEditorParts, donde añadiremos nuestro nuevo editor part, y la propiedad WebBrowsableObject.



public class WebpartFoo: WebPart, IWebEditable
{
#region Properties

[Personalizable(PersonalizationScope.Shared)]
[WebBrowsable(false)]
public string LinkList { get; set; }

#endregion

#region Default Constructor
public WebpartFoo()
{
ExportMode = WebPartExportMode.All;
}

#endregion


#region IWebEditable Members

EditorPartCollection IWebEditable.CreateEditorParts()
{
List editors = new List();

// Add the base editor parts
EditorPartCollection baseParts = base.CreateEditorParts();
foreach (EditorPart basePart in baseParts)
editors.Add(basePart);

editors.Add(new FooEditorPart(this.ID));
return new EditorPartCollection(editors);
}

object IWebEditable.WebBrowsableObject
{
get { return this; }
}

#endregion

}



A partir de aquí ya podriamos usar nuestra lista seleccionada, cómo curiosidad, remarcar que el popup al obtener la url de la lista no le añade el /Lists ni la url correcta de la lista, sino que le añade el title de la lista, por lo que para poder desde codigo obtener la lista podriamos hacer algo como lo siguiente:



private void foo()
{
if (!String.IsNullOrEmpty(LinkList))
{
string UrlWeb = LinkList.Substring(0, LinkList.LastIndexOf("/"));
string ListName = LinkList.Substring(LinkList.LastIndexOf("/") + 1);

using (SPWeb web = SPContext.Current.Site.OpenWeb(UrlWeb))
{
SPList list = web.Lists[ListName];

// TODO
}
}
}

viernes, 16 de abril de 2010

Crear exclude crawling rules programmatically

Añadir "exclude crawling rules" es muy sencillo.

Los pasos a seguir serían los siguiente:

1. Obtener el Search Context

//get the site SSP's search server instance
SearchContext searchContext = SearchContext.GetContext(site);

2. Obtener el Content Source

//get the content source for the search server
Content sspContent = new Content(searchContext);

3. Añadir la crawl rule a la colección de crawlrules del content source

string rule = "http://*/forms/*";
//create the crawl rule, setting the type of crawl rule and the crawl rule string
CrawlRule crawlRule = sspContent.CrawlRules.Create(CrawlRuleType.ExclusionRule, rule);

4. Añadir propiedades

crawlRule.FollowComplexUrls = true;

5. Commit la regla en la base de datos

//commit the rule in the database
crawlRule.Update();


Esta información ha sido extraída del siguiente blog:

http://blog.richfinn.net/blog/2008/06/19/CreateSearchCrawlRulesProgrammatically.aspx

martes, 13 de abril de 2010

FORO DE ARQUITECTOS BARCELONA

El próximo 13 de Mayo se hará en Barcelona un FORO DE ARQUITECTOS, donde entre otras cosas se presentará la propuesta Arquitectura DDD N-Layer.

Podéis registraros en siguiente link:

http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032446174&Culture=es-ES

jueves, 8 de abril de 2010

SharePoint fields objects

A continuación se muestra una tabla donde se definen los diferentes nombres de classe para cada tipo de datos de MOSS 2007, esta información ha sido extraida del siguiente blog:

http://www.tonstegeman.com/Blog/Lists/Posts/Post.aspx?List=70640fe5%2D28d9%2D464f%2Db1c9%2D91e07c8f7e47&ID=32


miércoles, 7 de abril de 2010

Evitar pop-up warning al editar un item

Hoy me he encontrado un problema al editar un item, este item tiene un custom field que hace postback, y al hacerlo me aparece el siguiente mensaje:





Si pulsamos cancelar no hay problema, pero al pulsar OK, esto implica que aparezca el siguiente error al intentar luego guardar el item.






La solución la he extraido de una respuesta de un conocido amigo (Edin Kapic) en un foro. Añado enlace al post original: http://social.msdn.microsoft.com/Forums/es-ES/mossdeves/thread/3d46ab13-b821-4f31-939a-678eb30be81f



El problema viene dado que cuando tenemos la feature de publishing activada, se añade un javascript que nos controla cuando estamos en edición y nos pregunta antes de salir de la página, si queremos guardar los cambios.



La solución es desactivar la feature de publishing o añadir un tag script en nuestra página donde ponga la variable g_bWarnBeforeLeave a false, ya que es esta variable por la que pregunta el javascript antes mencionado.

lunes, 15 de marzo de 2010

XSD2Code Generator

XSD2Code Generator es una herramienta que nos permite generar business class a partir de schemas xml (ficheros .xsd). Es una evolución de la herramienta xsd.exe que viene con nuestra instalación de visual studio.

Cómo características a resaltar respecto a la herramienta xsd.exe es que soporta generics y custom collections. Cuando generamos classes con xsd, xsd.exe nos generaba clases con atributos del tipo array, mientras que xsd2code nos genera clases con propiedades del tipo List. Además se nos añade como un Addin para visual studio 2008.


Un ejemplo bien secillo de su uso sería el siguiente:


1. Obtener o generar un xml para el que queremos generar la classe que lo serialize / deserialize.







foo1
foo2


foo1
foo2





2. Generar el schema (fichero .xsd) Menú XML > Create Schema, se nos generará un archivo cómo el siguiente.






























3. Por último hacemos botón derecho sobre el fichero .xsd desde el solution explorer y pulsamos la opción Run xsd2code generation. Se nos abrirá una ventana modal donde podemos elegir las propiedades para la generación del código, como por ejemplo el lenguaje, si queremos una clase base, etc. A continuación muestro un trozo de código generado con esta herramienta.




using System;
using System.Diagnostics;
using System.Xml.Serialization;
using System.Collections;
using System.Xml.Schema;
using System.ComponentModel;
using System.Collections.Generic;

namespace prueba {

public partial class FooClass {

private List fooSubClassesField;

private string attribute1Field;

public FooClass() {
this.fooSubClassesField = new List();
}

[System.Xml.Serialization.XmlArrayItemAttribute("FooSubClass", IsNullable=false)]
public List FooSubClasses {
get {
return this.fooSubClassesField;
}
set {
this.fooSubClassesField = value;
}
}

public string Attribute1 {
get {
return this.attribute1Field;
}
set {
this.attribute1Field = value;
}
}
}

public partial class FooClassFooSubClass {

private string field1Field;

private string field2Field;

public string field1 {
get {
return this.field1Field;
}
set {
this.field1Field = value;
}
}

public string field2 {
get {
return this.field2Field;
}
set {
this.field2Field = value;
}
}
}
}

martes, 9 de marzo de 2010

ListInstance (FeatureId y TemplateType)

Cuando estamos creando una feature para provisionar una lista a nuestra solución MOSS (Element ListInstance), aparecen dos campos de suma importancia:

FeatureId: Text obligatorio. Especifica el identificador GUID de la característica. Si no se especifica un GUID de característica, Windows SharePoint Services usa el identificador de la característica predeterminada; en otras palabras, el que contiene el elemento ListInstance. Si la plantilla de lista para la instancia de lista se encuentra dentro de otra característica, se debe especificar el identificador de dicha característica mediante el atributo FeatureId.

TemplateType: Integer obligatorio. Devuelve el identificador del entero de la plantilla de lista que se va a usar.

La siguiente tabla muestra las listas disponibles en MOSS (algunas también en WSS) , sus Type IDS y sus GUIDS.

Type ID
Description

GUID of feature

100

Generic list00bfea71-de22-43b2-a848-c05709900100

101

Document library00BFEA71-E717-4E80-AA17-D0C71B360101

102

Survey00BFEA71-EB8A-40B1-80C7-506BE7590102

103

Links list00BFEA71-2062-426C-90BF-714C59600103

104

Announcements list00BFEA71-D1CE-42de-9C63-A44004CE0104

105

Contacts list00BFEA71-7E6D-4186-9BA8-C047AC750105

106

Events list00BFEA71-EC85-4903-972D-EBE475780106

107

Tasks list00BFEA71-A83E-497E-9BA0-7A5C597D0107

108

Discussion board00BFEA71-6A49-43FA-B535-D15C05500108

109

Picture library00BFEA71-52D4-45B3-B544-B1C71B620109

110

Data sources00BFEA71-F381-423D-B9D1-DA7A54C50110

111

Site template gallerySYSTEM

113

Web Part gallerySYSTEM

114

List template gallerySYSTEM

115

XML Form library00BFEA71-1E1D-4562-B56A-F05371BB0115

116

Masterpage and page layouts librarySYSTEM

117

No-code workflow library00BFEA71-F600-43F6-A895-40C0DE7B0117

118

Workflow process list00BFEA71-2D77-4A75-9FCA-76516689E21A

119

Web page library00BFEA71-C796-4402-9F2F-0EB9A6E71B18

120

Custom grid for a list00BFEA71-3A1D-41D3-A0EE-651D11570120

130

Data connection library00BFEA71-DBD7-4F72-B8CB-DA7AC0440130

140

Workflow history list00BFEA71-4EA5-48D4-A4AD-305CF7030140

150

Gantt tasks list00BFEA71-513D-4CA0-96C2-6A47775C0119

1100

Issue tracking00BFEA71-5932-4F9C-AD71-1557E5751100