Codepoint

by Trentia Consulting

SharePoint 2013 : Evitando el error ‘The Maximum allowed value is 4096’

Recientemente trabajando en el desarrollo de una aplicación web con base de SharePoint 2013, se me ordenó el desarrollo de un centro de búsqueda y de descarga de contenidos en SharePoint, así que me puse manos a la obra.

Esta parte de la aplicación permite al usuario recibir gran cantidad de contenidos por lo que decidimos atacar al servicio de búsqueda de SharePoint 2013 utilizando la librería CSOM 15, dado que las búsquedas sería mucho más optimas y mejoraría la experiencia de usuario. Así mismo existen gran cantidad de filtros que permite al usuario realizar una criba muy especifica de los contenidos que desea recibir. En este proceso se construye una consulta en Keyword Query Languaje (KQL en adelante) dependiendo de los datos que el usuario haya seleccionado en la sección de filtros.

Así pues el código que genera la consulta tiene un aspecto similar a este :

/// <summary>
/// Searches al documents that maches the specified metadata and retrieve all specified properties
/// </summary>
/// <param name="Filters">Filters to add</param>
/// <param name="PropertiesName">Properties to retrieve</param>
/// <returns></returns>
public IEnumerable<IDictionary<string, object>> SearchDocuments(IDictionary<string, object> Filters, IEnumerable<string> PropertiesName)
{
    using (var context = new ClientContext(_host))
    {

        context.AuthenticationMode = ClientAuthenticationMode.FormsAuthentication;
        context.FormsAuthenticationLoginInfo = new FormsAuthenticationLoginInfo(_user, _password);

        KeywordQuery query = new KeywordQuery(context);
        if(PropertiesName != null)
        {
            foreach (var PropertyName in PropertiesName)
            {
                query.SelectProperties.Add(PropertyName);
            }
        }

        query.QueryText = "IsDocument=true";

        if (Filters != null && Filters.Any())
            query.QueryText = query.QueryText + " AND ";

        foreach (var filter in Filters)
        {
            if (filter.Value is Array)
            {
                query.QueryText = query.QueryText + "( ";
                foreach (var valueItem in filter.Value as IEnumerable<object>)
                {
                    query.QueryText = query.QueryText + filter.Key + ":*" + valueItem.ToString() + "*";
                    if (((IEnumerable<object>)filter.Value).Last() != valueItem)
                        query.QueryText = query.QueryText + " OR ";
                    else
                        query.QueryText = query.QueryText + " ) ";
                }
            }
            else
            {
                if (filter.Value is bool)
                {
                    query.QueryText = query.QueryText + filter.Key + "=" + filter.Value.ToString().ToLower();
                }
                else
                {
                    query.QueryText = query.QueryText + filter.Key + ":*" + filter.Value.ToString() + "*";
                }
            }
            if (Filters.Last().Key != filter.Key)
                query.QueryText = query.QueryText + " AND ";
        }

        SearchExecutor executor = new SearchExecutor(context);
        ClientResult<ResultTableCollection> results = executor.ExecuteQuery(query);

        context.ExecuteQuery();
        return results.Value[0].ResultRows;
    }
}

 

Habiendo visto el código que genera las consultas, es fácil imaginar que es posible que la consulta KQL alcance grandes tamaños sobretodo si añadimos que muchos de los valores que se usan en la búsqueda son identificadores globales únicos (GUID por sus siglas en inglés).

Si construimos pues, una consulta excesivamente larga y la lanzamos contra el servicio de búsqueda de SharePoint 2013, recibiremos esta excepción : ( y ningún resultado )

System.ArgumentOutOfRangeException : The Maximum allowed value is 4096, Parameter Name ‘QueryText’

   ¿Por que es lanzada esta excepción ? SharePoint 2013 tiene un limite de caracteres en la búsqueda cuando se usa KQL, este por defecto está establecido en 4096 bytes lo que no permite realizar consultas muy largas o muy complejas. Pero esto tiene solución o por lo menos para la mayoría de los casos, pues este límite se puede incrementar. lo que en la mayoría de casos nos permitirá realizar las consultas que antes no nos permitía el límite de caracteres.

¿ Por que digo que se puede solucionar para la mayoría de los casos ? Pues por que el límite “sólo” se puede incrementar hasta 20480 bytes, lo que en la mayoría de casos nos permitirá realizar las consultas que antes no nos permitía el límite de caracteres. Esta tarea es sencilla pero no se puede realizar desde la administración central de SharePoint 2013 si no que tendremos que establecer el nuevo valor desde la “SharePoint 2013 Management Shell”. El valor que tenemos que modificar es una propiedad llamada MaxKeywordQueryTextLength del servicio de búsqueda. Para realizar eso abriremos como administradores una “SharePoint 2013 Management Shell” e introduciremos los siguientes comandos en la consola :

PS C:\Users\trentia> $search = Get-SPServiceApplication | where {$_.displayname -contains "search"}
PS C:\Users\trentia> $search.MaxKeywordQueryTextLength
4096
PS C:\Users\trentia> $search.MaxKeywordQueryTextLength = 20480
PS C:\Users\trentia> $search.Update()
PS C:\Users\trentia> $search.MaxKeywordQueryTextLength
20480
## Quizás no sea necesario pero cerciorémonos que todo quede intacto
 
PS C:\Users\trentia> Restart-Service spsearchhostcontroller
PS C:\Users\trentia> IISRESET

Y con este sencillo proceso el límite queda incrementado hasta 20 kb lo que nos permitirá realizar consultas muchísimo más complejas.

Espero que sea de ayuda !

Solucionar problema con la intercalación desde Linq

En caso de necesitar realitzar búsquedas contra el motor de base de datos sin tener en consideración mayúsculas/minusculas, acentos y otros símbolos de puntuación, desde SQL Server es necesario que la base de datos use una intercalación *_CI_AI

Pero aunque la base de datos esté bien configurada para que las consultas sean no sensitivas a este tipo de carácteres, si se realiza la consulta desde nuestra aplicación la consulta no será efectiva puesto que desde linq no se explota la facilidad que nos ofrece la base de datos.

Para solucionar esto hay un método que es el que muestro en el siguiente código:

string ntext = new string("TEXTO_A_BUSCAR".Normalize(NormalizationForm.FormD).Where(c => c < 128).ToArray());
Lista = Lista.Where(x => (UTF8Encoding.UTF8.GetString(UTF8Encoding.GetEncoding("ISO-8859-8").GetBytes(x.CAMPO_BD)).IndexOf(ntext, StringComparison.InvariantCultureIgnoreCase) > -1)).ToList();

En la primera instrucción normalizamos el texto a localizar en la base de datos, y en la segunda se realiza la búsqueda en sí misma sobre el campo de la base de datos con el cual deseamos hacer el filtro.

Espero que os sirva de ayuda.

Error al cargar y ejecutar el receptor de eventos. “0x8007047e”

Necesitaba que al crear cualquier site todas sus páginas tuviesen un diseño de página customizado.

Aunque en la configuración del site padre tenía configurado que tan sólo podían crearse páginas con el diseño de página en concreto, cuando se creaba el site, la página por defecto (Pages/default.aspx) seguía teniendo el diseño de página “WelcomeLinks”.

Para cambiarlo creé un EventReceiver sobreescribiendo el WebProvisioned (que se dispara una vez que el site se ha creado). Dentro del método, cambiaba el PageLayout y también su ContentType también customizado (añadiéndolo también a la biblioteca de Páginas, ya que no existía por defecto).

Éste es el código del event receiver:

SPWeb w = properties.Web;
PublishingWeb curPubWeb = PublishingWeb.GetPublishingWeb(properties.Web);
SPSite s = properties.Web.Site;
SPContentType contentType = s.RootWeb.ContentTypes["PaginaInvestigador"];
SPList spList = curPubWeb.PagesList;
PublishingPageCollection ppc = curPubWeb.GetPublishingPages();

foreach (PageLayout curLayout in curPubWeb.GetAvailablePageLayouts())
{
     if (ppc.Count > 0)
     {
          PublishingPage curPage = ppc[0];
          curPage.CheckOut();
          curPage.Layout = curLayout;
          curPage.ListItem["ContentTypeId"] = curLayout.AssociatedContentType.Id;
          curPage.ListItem.Update(); 
          curPage.Update();
          curPage.CheckIn(""); 
     }        
}

Para probarlo creaba un nuevo sitio. Cuando lo había creado intentaba editar la página default para comprobar que tanto el diseño de página como el tipo de contenido se habían cambiado correctamente y me daba el siguiente error:

Error al cargar y ejecutar el receptor de eventos Client.Web.SharePointApp.ResearcherSiteEventReceiver.ResearcherSiteEventReceiver en Client.Web.SharePointApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d3b00d8781e2eddd. A continuación se incluye información adicional.

: <nativehr>0x8007047e</nativehr><nativestack></nativestack>

Lo solucioné registrando el event receiver como síncrono de la siguiente forma:

En el fichero Elements.xml, añadir dentro de <Receiver>:

<Synchronization>Synchronous</Synchronization>

Imprimir directamente una imagen. Quitar el asistente de impresión de fotografías de windows.

En el último post hablé de cómo mandar varios documentos a una impresora pdf para terminar imprimiento un sólo documento que los contuviera todos.

Me encontré que cuando mandaba una imagen y PDFCreator la queria imprimir se abría siempre el cuadro de diálogo del asistente de impresión de fotografías de windows. Intenté cambiar el programa por defecto según la extensión, etc. pero nada dió resultado.

La solución: entrar en el registro de windows (regedit) y seguir los siguientes pasos:

  1. Ir a: HKEY_CLASSES_ROOT\SystemFileAssociations\image\shell\print\
  2. Cambiar el nombre de la carpeta “DropTarget” a “DropTarget.old”.
    image8
  3. Dentro de la carpeta “command” cambiar el valor por la cadena siguiente:

"%SystemRoot%\System32\rundll32.exe" "%SystemRoot%\System32\shimgvw.dll",ImageView_PrintTo /pt "%1" "PDFCreator" "" ""

Con esto debería funcionar!

PDFCreator no ordena correctamente los documentos

Para generar un pdf que contenga varios documentos se puede utilizar la impresora PDFCreator. Añadiendo la referencia a vuestro proyecto podréis enviar varios documentos a la impresora y cuando deseéis hacer una impresión que recoja todo lo que tenga en la cola y lo plasme en un sólo pdf.

Creé una función para ir añadiendo dichos documentos, que tenía el siguiente aspecto:

public void AddPrintJob(string FileName) 
{ 
       jobs++; 
       creator.cPrintFile(FileName); 
} 

Si os encontráis con que mandáis varios documentos seguidos y luego, en el pdf final, os aparecen en distinto orden al que los habéis mandado, añadid el siguiente código a la función anterior:

public void AddPrintJob(string FileName) 
{ 
       jobs++; 
       creator.cPrintFile(FileName); 
       while (creator.cCountOfPrintjobs != jobs) 
       { 
       } 
} 

El bucle simplemente esperará hasta que la impresora pdf (en este caso “creator”) haya añadido el documento a la cola de impresión.

Conversión de tipos fecha/hora de un servicio web a tipo Date de JavaScript

Las fechas que nos llegan a través de un servicio web nos llegan en formato ISO-8601 (ej: 2005-03-26T19:51:34Z). Para poderlas tratar desde JavaScript debemos realizar la conversión de esta cadena a un tipo Date. Podemos hacer uso, por ejemplo, de una extensión sencilla del tipo Date, como la siguiente:

Date.prototype.setISO8601 = function (string) {
 var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
 "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
 "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
 var d = string.match(new RegExp(regexp));

 var offset = 0;
 var date = new Date(d[1], 0, 1);

 if (d[3]) { date.setMonth(d[3] - 1); }
 if (d[5]) { date.setDate(d[5]); }
 if (d[7]) { date.setHours(d[7]); }
 if (d[8]) { date.setMinutes(d[8]); }
 if (d[10]) { date.setSeconds(d[10]); }
 if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
 if (d[14]) {
 offset = (Number(d[16]) * 60) + Number(d[17]);
 offset *= ((d[15] == '-') ? 1 : -1);
 }

 offset -= date.getTimezoneOffset();
 time = (Number(date) + (offset * 60 * 1000));
 this.setTime(Number(time));
}

Una vez tenemos la extensión podemos realizar las conversiones con llamadas del tipo:

var date = new Date();
date.setISO8601("2005-03-26T19:51:34Z");

LINQPad, la herramienta para nuestras consultas LINQ

linkMuchos de vosotros conoceréis LINQPad , la herramienta que nos permite crear y ejecutar consultas LINQ de forma fácil y asistida. Las últimas versiones permiten conectar con orígenes diversos de datos y hacer LINQ contra objetos, SQL, Entity Framework, etc. Además incorpora una ayuda con muchísimos ejemplos preparados para ejecutar que nos permiten indagar y aprender los secretos más íntimos de esta tecnología.

Os animo a probarlo. Lo podéis descargar desde su web,http://www.linqpad.net. Sin coste alguno podéis descargar la herramienta básica que ya nos quita de muchos apuros. También podemos adquirir el producto profesional que nos ofrece algunas funcionalidades adicionales interesantes, como el autocompletado de las consultas.

link2

Alternativa a Redgate .NET Reflector

Desde principios de este año la archiconocida herramienta de inspección y desensamblado para ensamblados de .NET se debe pagar. Reflector ha dejado de ser una herramienta gratuita por lo que la comunidad rápidamente ha empezado a buscar alternativas. Si no quereis pagar los $35 que pide Redgate por .NET Reflector, la mejor alternativa que existe esILSpy. Se trata de una herramienta de características muy similares a Reflector y que seguro se hará popular en poco tiempo.

image_2 (5)

Windows Phone 7

El pasado 11 de Octubre fue la presentación oficial del nuevo Windows Phone 7, el nuevo sistema operativo móvil de Microsoft.

Windows Phone 7 se convirtió en uno de los lanzamientos más esperados, y muchos fabricantes como Samsung, HTC o LG ya lo están usando como complemento en alguno de sus modelos, y Nokia acaba de anunciar una alianza para incorportarlo en sus modelos de gama alta.

NOkia-Windows-Phone-

Microsoft pretende hacer un punto y aparte en el diseño de su sistema operativo para móvil, ya que las últimas versiones de Windows mobile no eran todo lo eficientes que se esperaban, y sus competidores iban cogiendo ventaja en cuanto a diseño y prestaciones. De hecho, Windows Phone 7 no está basado en Windows Mobile 6.5, sino en la interfaz de Zune.

Ha pasado ya un tiempo pruedencial desde su lanzamiento, y ya hemos podido probarlo e incluso empezar a verlo entre manos de algunos usuarios.  Hay muchas novedades, es un sistema operativo móvil diferente al anterior, pero también a otros que ya había en el mercado, y de ello nos damos cuenta con sólo ver la pantalla de inicio, que ahora se encuentra dividida en paneles dinámicos, dando un cambio estético muy interesante.

Además tenemos nuevos conceptos, como los “Windows Phone Hubs”, que son vistas sobre temas específicos que reflejan las actividades que el usuario más demanda en el móvil: Gente (redes sociales, etc…), Imágenes (para compartir fotos y vídeos), Juegos (Xbox live en tu teléfono), y Música + Vídeo (lo mejor de Zune, con servicios online de música, e inluso radio). Y otra gran novedad es que incorpora un “Mercado de aplicaciones”, algo como la AppStore de Apple, o el Market de android, un sitio donde centralizar las aplicaciones para la plataforma, para buscarlas, comprarlas y descargarlas.

safe_image

Nosotros hace un par de semanas ya mirábamos –y lo publicábamos en nuestra página de Facebook- las particularidades del desarrollo de aplicaciones para Windows Phone gracias al material facilitado por Microsoft faculty para la programación de este sistema operativo móvil en C#, y que podéis bajar gratutitamente.

En este manual se tratan, entre otros, una introducción a esta nueva plataforma, cómo vender tus programas vía Marketplace, un poco de Silverlight, tanto de programación como de diseño, uso de Data Services o XNA para crear juegos para tu dispositivo.

Desplegables en cascada en Infopath 2010

Aunque con Infopath 2007 ya se podían realizar listas desplegables en cascada aplicando filtros en el campo correspondiente, teníamos la limitación de que no se podía utilizar para los formularios web (como desde una intranet Sharepoint).

Sin embargo, en la nueva versión de Infopath, esto ya está resuelto, ya que se ha trabajado bastante la integración entre Sharepoint 2010 e Infopath 2010.

Para ver cómo podemos hacer un desplegable en cascada, os dejo unos sencillos pasos, con unas capturas de pantalla:

En nuestro entorno, tenemos un típico caso con una lista Paises, y una Provincias, en la que cada una de las provincias pertenece a un país. Así que al control desplegable "Pais", le asignamos el Origen de datos que devuelve los países de la lista, de forma convencional.

 1

Y al campo Provincias, seguimos el mismo procedimiento.

 2

Sólo debemos tener en cuenta que al elegir los campos, en el asistente para la conexión de datos, tendremos que seleccionar también el campo Pais de la lista de Provincias:

 3

Ahora, aplicamos un filtro. Haciendo click en el icono "Seleccionar XPath" de "Entradas":

 4

En esta ventana, añadimos un filtro de datos:

 5

Y seleccionamos mostrar los datos que cumplan las siguientes condiciones:

El Pais de la provincia, debe ser igual al país seleccionado en el anterior desplegable (del origen de datos principal)

 6

En la segunda parte de la igualdad seleccionamos un campo:

 7

Aceptamos la creación del filtro:

 8

Dándole una vuelta de tuerca más al tema de los desplegables en cascada, es posible que los necesitemos insertar en una Tabla extensible.

 9

En este caso, nostros tenemos unos "tipos de actividad", de los que dependen "subtipos de actividad" como entidades débiles en base de datos.

 10

En este caso, el desplegable no se puede hacer en cascada simplemente con filtros, así que después de enlazar cada control con su fuente de datos, igual que en el apartado anterior, la carga de los datos en el control de la entidad débil (subtipo) hemos decidido hacerla con código.

Para ello, usamos el evento "Changed" del desplegable de la entidad fuerte (en nuestro caso el tipo de actividad):

 11

Dentro del código: public void ExperienciaTipusActivitat_Changed (object sender, XmlEventArgs e) tendremos en cuenta de que si queremos que sea compatible con web, no podemos identificar el control que genera el evento (XmlEventArgs e) por su dirección con la propiedad Match.

public void ExperienciaTipusActivitat_Changed(object sender, XmlEventArgs e) { 
	XPathNavigator form = MainDataSource.CreateNavigator(); 
	XPathNavigator selectedSubtipusActivitat = e.Site.SelectSingleNode("../my:ExperienciaSuptipusActivitat", NamespaceManager); 
	if (selectedSubtipusActivitat != null){ 
		string subtipo = selectedSubtipusActivitat.Value; 
		string tipo = e.Site.Value; 
		XPathNavigator navProv = DataSources["ObtenirTipusActivitatsSubtipus"].CreateNavigator(); 
		if (navProv.Select("//d:SharePointListItem_RW[d:TipusActivitat='" + tipo + "' and d:ID='" + subtipo + "']", NamespaceManager).Count == 0)
		{selectedSubtipusActivitat.SetValue(""); }
	} 
}