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 !

Projection y Query en Orchard CMS

El módulo Projector nos permite crear instancias de Query y Projection, consultas de datos y páginas que muestran el resultado de estas.

En este artículo crearemos una consulta que devuelva todos nuestros productos y una página que muestre el listado de estos. Suponemos que inicialmente en nuestro proyecto ya existen diferentes Content Types que tienen asociado un ProductPart.
public class ProductPart : ContentPart<ProductPartRecord>
{
  public string SerialNumber
  {
    get { return Retrieve(r => r.SerialNumber); }
    set { Store(r => r.SerialNumber, value); }
  }

  public decimal Price
  {
    get { return Retrieve(r => r.Price); }
    set { Store(r => r.Price, value); }
  }
}

public class ProductPartRecord : ContentPartRecord
{
  public virtual string SerialNumber { get; set; }
  public virtual decimal Price { get; set; }
}
Necesitamos una Query que devuelva todos los elementos cuyo tipo de contenido contenga un ProductPart adjunto. En el framework no tenemos un filtro que permita esta funcionalidad, así que creamos una nueva clase que herede de IFilterProvider:
public class ProductPartFilter : IFilterProvider
{
  public void Describe(DescribeFilterContext describe)
  {
    describe.For("Content","Content","Content")
        .Element("ProductPartFilter", "ProductPartFilter", "ProductPartFilter", ApplyFilter, DisplayFilter);
  }

  private void ApplyFilter(FilterContext context)
  {
    context.Query = context.Query.Join(x => x.ContentPartRecord(typeof(ProductPartRecord)));
  }
}
Ahora ya podemos añadir la nueva Query desde la interfaz web de administración asignándole el título "Productos". La editamos y hacemos clic en "Add a new Filter" para seleccionar el filtro que hemos creado anteriormente.

Es posible que por alguna razón específica de nuestro proyecto necesitemos realizar lo mismo programando directamente, en este caso podemos utilizar el siguiente código:
string name = "Productos";
string type = "ProductPartFilter";

QueryPart query = _queryService.CreateQuery(name);

var form = new Form { ContentTypes = type };
var serializer = new XmlSerializer(form.GetType());
StringWriter sw = new StringWriter();
XmlWriter writer = XmlWriter.Create(sw);
serializer.Serialize(writer, form);
var state = sw.ToString();

query.FilterGroups[0].Filters.Add(new FilterRecord
{
  Category = "Content",
  Description = "ProductPartFilter",
  Position = 0,
  State = state,
  Type = type
});

_contentManager.Publish(query.ContentItem);

[Serializable]
public class Form
{
  public string Description { get; set; }
  public string ContentTypes { get; set; }
}
Por último, para crear la página que muestra el listado de productos, hacemos clic en New Projection. Asignamos el título "Listado Productos", la url "productos" y seleccionamos en el desplegable la Query "Productos".

Una vez más, si necesitamos crear la instancia de Projection programando, tenemos el código equivalente:
var projection = _contentManager.New("ProjectionPage");
projection.As<TitlePart>().Title = "Listado Productos";
projection.As<AutoroutePart>().DisplayAlias = "productos";
projection.As<ProjectionPart>().Record.QueryPartRecord = query.Record;

string ownerName = _siteService.GetSiteSettings().SuperUser;
var owner = _membershipService.GetUser(ownerName);
projection.As<ICommonPart>().Owner = owner;

_contentManager.Create(projection, VersionOptions.Published);
Projection - Orchard Documentation

Proyecto Orchard: CMS en ASP.NET MVC

Orchard es un gestor de contenidos web o CMS que parece contar con la bendición de Microsoft. Recientemente hemos estado desarrollando con este CMS, como alternativa a Umbraco, otro conocido CMS de .NET.


Orchard nos ha gustado especialmente por sus características funcionales y técnicas.

Orchard cubre bastante bien aspectos que otros CMS quizás no tengan demasiado presente, como el soporte multiidioma y multicultural, que acostumbra a ser un problema. También dispone de una buena extensibilidad a muy diversos niveles, que nos permite trabajar con vistas de MVC para definir hasta el más mínimo aspecto de la web. Tenemos control absoluto del HTML resultante y eso es muy positivo. También contempla bien aspectos de SEO, tan necesarios hoy en día. Y finalmente, ofrece un buen rendimiento, aspecto que también puede resultar complejo en CMS basados en Microsoft .NET.

Nuestro último proyecto en Orchard es el desarrollo de una web con CMS, haciendo uso deFoundation Zurb como framework de diseño responsable. Estamos realmente satisfechos con el resultado y con Orchard. Os recomiendo que lo tengáis presente en vuestro portfolio de soluciones CMS.

Pruebas de carga y de rendimiento web con JMeter

Recientemente en un proyecto de intranet nos ha surgido la necesidad de tomar medidas de rendimiento y realizar pruebas de carga para validar el correcto funcionamiento del sistema y mejorar algunos puntos concretos. De las herramientas disponibles en el mercado para estos menesteres nos hemos decantado por JMeter, una herramienta open source que forma parte de la Apache Software Foundation y que nos ha sorprendido muy agradablemente por sus capacidades y su funcionamiento intuitivo.

Evidentemente se trata de un software 100% Java, por lo que debemos disponer de la máquina virtual de Java instalada para ejecutar esta aplicación desktop. Una vez instalada podemos crear nuevos proyectos de medición y estrés. Disponemos de características como:

  • Posibilidad de realizar mediciones sobre distintos tipos de servidores:
    • Web (HTTP / HTTPS)
    • SOAP
    • Base de datos vía JDBC
    • LDAP
    • JMS
    • Mail (SMTP(S) / POP3(S) / IMAP(S))
  • Comandos nativos o shell scripts
  • Framework multithread, que nos permite la toma de medidas concurrentes en distintos grupos de threads
  • Una GUI muy cuidada y completa
  • Caching y posibilidad de análisis off-line
  • Gran extensibilidad vía plugins

image_2

Uno de los aspectos que nos ha parecido especialmente interesante es la gran variedad de salida de datos que disponemos para analizar las medidas. Existen numerosos Receptores, como se denominan en JMeter, que nos permiten ver los datos en distintos formatos o enviarlos hacia algún destino en fichero. En la imagen anterior, por ejemplo, podemos ver un Receptor de tipo Gráfico, mostrando la medida media, mínima y máxima de un total de 10 llamadas concurrentes a distintas páginas de una web.

image_6

Enviar información entre métodos de acción (MVC)

Hay veces en las que necesitamos enviar información de un método de acción (ActionMethod) a otro, por ejemplo si queremos realizar una acción determinada y luego volver al método anterior.

Podemos almacenar la información necesaria en el objeto TempDataDictionary del controlador, antes de llamar al método RedirectToAction que nos redirigirá a la próxima acción. El valor de la propiedad TempData se almacena en el estado de la sesión, y cualquier método llamado después de guardarlo podrá leerlo y procesarlo. El valor del objeto TempData se conservará hasta la expiración de la sesión o hasta que sea leído.

Ejemplo:

- Guardar información en el objeto TempData:

public ActionResult AddUsuario(Usuario model)
{
    if (ModelState.IsValid)
    {
	//guardar usuario en la base de datos        

        TempData["message"] = "Usuario creado correctamente.";
        return RedirectToAction("DetailsUsuario ", new { USU_Id = model.USU_Id } );
    }

    return View(model);
 }
public ActionResult DetailsUsuario(Guid USU_Id)
{
    //Consultar usuario de la base de datos
    if(TempData["message"] != null)
    {
        ViewBag.message = TempData["message"];
    }
 return View(usuario);
}

Nueva versión de Umbraco en MVC 3

La versión 5 del conocido gestor de contenidos web Umbraco está disponible desde enero de 2012. La nueva versión, inicialmente denominada “Jupiter”, ha sido reconstruida para adaptarse a la arquitectura ASP.NET MVC 3, lo cual es una buena noticia para todos los desarrolladores que trabajan con Umbraco. Tambien es una buena noticia que a pesar de la actualización de arquitectura se consevan todas las funcionalidades de la versión anterior.

La nueva arquitectura ofrece un mayor rendimiento y mayores posibilidades de integración con sistemas externos a Umbraco.

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.

Lanzada la beta 3 de jQuery Mobile Framework

image_2 (3)Así és! La beta 3 de este framework, basado en jQuery y con el objetivo de facilitar el desarrollo de interfaces de usuario web amigables para dispositivos móviles, ha sido lanzada.

Jquery Mobile soporta multitud de dispositivos (iOS, Android, Blackberry, etc.), pudiendo diseñar aplicaciones web que son compatibles con todos ellos. La ventaja respecto al diseño de aplicaciones nativas a cada plataforma es evidente. Diseñar nativamente a cada plataforma requiere multiplicar el esfuerzo de desarrollo y disponer de los conocimientos necesarios para cada una de ellas. Com jQuery Mobile podemos desarrollar aplicaciones web en PHP o ASP.NET o cualquier otro lenguaje, navegables desde cualquier dispositivo móvil y con una interfaz agradable, similar a la de iOS.

Todavía es beta 3 pero nos da muy buenas sensaciones. Hasta ahora disponíamos de otros frameworks de desarrollo web para dispositivos móviles pero no resultaban del todo completos. jQuery Mobile dispone de un amplio conjunto de elementos de UI y el sopore propio de jQuery, imprescindible hoy en dia para muchos de nosotros.

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)

Borrar caché de report en Reporting Services

Trabajando en un buscador me encontré con un problema:

En cada registro del resultado de la búsqueda había un icono que me permetía abrir un report en PDF. Ahora bien, si se cambiaban los datos que debía mostrar, cuando volvía a abrirlo el PDF no me mostraba dichos cambios.

Si cerraba la ventana y la volvía a abrir sí me mostraba la información correcta, por tanto, empecé a pensar que era alguna cosa como que el report se estaba almacenando de alguna forma en caché.

Descubrí que cuando abres el report, si le pasas como parámetro &rs:ClearSession=true, borra dicha caché y se muestra el report actualizado.

Aquí os dejo un enlace muy interesante sobre los distintos parámetros que le puedes pasar a un report (llamándolo por url): http://www.compute-rs.com/es/consejos-176858.htm