Social Icons

Mostrando entradas con la etiqueta MOSS. Mostrar todas las entradas
Mostrando entradas con la etiqueta MOSS. Mostrar todas las entradas

miércoles, 11 de febrero de 2015

Se acabaron las funciones recursivas para definir CAML Queries gracias a Camlex

Algunas veces tienes un número aleatorio de condiciones para incluir en una CAML query y en esos casos yo solía definir las consultas usando una función recursiva que normalmente tenía que debugar unas cuantas veces me funciona siempre a la primera.

El código para este tipo de consultas sería algo así (y este es de los simples):

public List<string> GetSomeInfo(string fieldsToSearch, string contentTypesToSearch)
{
    ...

    var queryval = string.Empty;
    if (contentTypesToSearch.IsNullOrEmpty())
        queryval = string.Format("<Where>" + GenerateFieldsQuery(fieldsToSearch.Split(','), 0) + "</Where>", text);
    else
        queryval = string.Format("<Where><And>" + GenerateCTypesQuery(contentTypesToSearch.Split(','), 0) + GenerateFieldsQuery(fieldsToSearch.Split(','), 0) + "</And></Where>", text);

    var scope = "Scope=\"RecursiveAll\"";

    ...
}

private static string GenerateFieldsQuery(string[] fields, int index)
{
    if (fields.Length == 0) return string.Empty;

    if (fields.Length == index + 1)
        return "<Contains><FieldRef Name='" + fields[index] + "' /><Value Type='Text'>{0}</Value></Contains>";

    return "<Or><Contains><FieldRef Name='" + fields[index] + "' /><Value Type='Text'>{0}</Value></Contains>" + GenerateFieldsQuery(fields, ++index) + "</Or>";
}

private static string GenerateCTypesQuery(string[] cTypes, int index)
{
    if (cTypes.Length == 0) return string.Empty;

    if (cTypes.Length == index + 1)
        return "<Eq><FieldRef Name='ContentType' /><Value Type='Choice'>" + cTypes[index] + "</Value></Eq>";

    return "<Or><Eq><FieldRef Name='ContentType' /><Value Type='Choice'>" + cTypes[index] + "</Value></Eq>" + GenerateCTypesQuery(cTypes, ++index) + "</Or>";
}

Eso era hasta ahora... gracias a Camlex (y gracias a Luis por decírmelo), este código se puede escribir así:

public List<string> GetSomeInfo(string fieldsToSearch, string contentTypesToSearch)
{
    ...

    var queryVal = string.Empty;
    var fieldExtensions = new List<Expression<Func<SPListItem, bool>>>();
    var cTypeExtensions = new List<Expression<Func<SPListItem, bool>>>();

    if (!contentTypesToSearch.IsNullOrEmpty())
    {
        foreach (var cType in contentTypesToSearch.Split(','))
            cTypeExtensions.Add(x => (string)x["ContentType"] == cType);
    }

    foreach (var field in fieldsToSearch.Split(','))
        fieldExtensions.Add(x => ((string)x[field]).Contains(text));

    var expressions = new List<Expression<Func<SPListItem, bool>>>();
    expressions.Add(ExpressionsHelper.CombineOr(cTypeExtensions));
    expressions.Add(ExpressionsHelper.CombineOr(fieldExtensions));

    queryVal = Camlex.Query().WhereAll(expressions).ToString();

    ...
}

Echaré de menos los métodos recursivos... me hacían sentir especial...

No hay comentarios:

jueves, 19 de septiembre de 2013

¿Qué campos hay disponibles en un SPListItem?

Cuando traes los elementos usando una consulta CAML con el parámetro ViewFields configurado no hay manera (o al menos yo no la conozco) de encontrar qué campos tienes disponibles y con datos.

La forma estandar de coger la lista de campos del tipo de contenido no vale porque el ContentType es null en esta clase de elementos…

La forma más fácil para ver qué tenemos es mirar en el XML del elemento.

static List<string> ExcludedFields = new List<string> { "z", "ows_ServerRedirected", "ows_FileRef", "ows_PermMask", "ows_FSObjType", "ows__Level", "ows__ModerationStatus" };
/// <summary>
/// Gets a dictionary with the available fields and its values.
/// </summary>
public static Dictionary<string, string> getAvailableFields(this SPListItem item)
{
    XElement row = XElement.Parse(item.Xml);
    Dictionary<string, string> Fields = new Dictionary<string, string>();

    foreach (XAttribute field in row.Attributes())
    {
        if (!ExcludedFields.Contains(field.Name.LocalName))
            Fields.Add(field.Name.LocalName.Substring(4), field.Value);
    }

    return Fields;
}

Usando esto he logrado reducir el tráfico que generaba un método de un web service en un 65%.

No hay comentarios:

¿Cuánto estás trayendote desde SharePoint en tus consultas CAML?

Yo no se tú, pero yo me estoy trayendo más de la cuenta.

Esta es una de esas cosas que no notas hasta que es demasiado tarde. Yo creía que sólo con añadir filtros estrictos a mis consultas CAML para traer estrictamente los elementos que necesitaba era suficiente pero hay algo más que puedes hacer.

Puedes restringir los campos que te estás trayendo... y debes.

La consulta SQL que SharePoint genera para traerse los elementos cuando dejas el parámetro ViewFields de la CAML vacío casi duplica la complejidad de la consulta que se genera cuando especificas los campos que quieres usar o usas una vista.

Dos simples líneas como estas:

query.ViewFields = "<FieldRef Name=\"Value\" />";
query.ViewFieldsOnly = true;
Pueden hacer tu query mejor.

¿Quién podría resistirse cuando es así de simple?

No hay comentarios:

viernes, 23 de agosto de 2013

Scopes en una consulta CAML

He estado trabajando bastante tiempo con CAML queries y el scope, también conocido como ámbito, es siempre algo muy importante a tener en cuenta. ¿Cuántas veces mis consultas no devuelven nada cuando estoy seguro de que deberían tener resultados…

Básicamente tenemos dos modificadores Recursive y All y nada, ¿Podemos llamar a "nada" un modificador? All va a devolver ficheros y carpetas. Recursive repetirá la query en todas las carpetas bajo la que hemos establecido como raíz para la consulta.

Si no pones el scope a All solo traerás ficheros. Si no lo pones a Recursive solo traeras elementos de la carpeta en la que estás trabajando. No hay tantas posibilidades por lo que voy a hacer un ejemplo de cada una.

Vamos a imaginar que tenemos una carpeta de SharePoint como esta y que vamos a lanzar consultas contra ella:

CamlScopeTreeSample

Ya se que no soy bueno con el paint pero lo que quiero mostrar aquí es un arbol en donde tenemos una carpeta que será la raíz de las queries que lancemos(Root), dos sub carpetas y algunos ficheros. Para cada posible caso resaltaré los elementos que puedes esperar recibir de la query.

Antes de empezar te recuerdo que el scope se selecciona en la propiedad ViewAttributes del objeto SPQuery.

Para que quede todavía más claro he pintado los niveles:

CamlScopeTreeSampleLevels

La línea verde mara lo que está dentro de la carpeta raíz, la azul lo que esta dentro de SubFolder1 y la roja el contenido de SubFolder2.

ViewAttributes dejado por defecto:

CamlScopeByDefault
Esto traerá solo los ficheros de la carpeta raíz.

ViewAttributes = "Scope='Recursive'"

CamlScopeRecursive

Esto traerá todos los ficheros de todas las carpetas.

ViewAttributes = "Scope='All'"

CamlScopeAll

Esto devolverá ficheros y carpetas bajo la raíz.

ViewAttributes = "Scope='RecursiveAll'"


CamlScopeRecursiveAll

Y finalmente con RecursiveAll puedes traerte todo lo que hay dentro de la carpeta raíz.

Buena suerte con las consultas.

No hay comentarios:

lunes, 8 de abril de 2013

Localización de Nombres de Listas en SharePoint

No se puede llamar a una lista por su nombre interno por defecto en SharePoint o por lo menos, yo no se cómo, tienes que usar el nombre externo o el GUID y eso es un problema cuando hay que localizar.

Para solucionar este problema he creado un par de extensiones basadas en la URL. La idea es simple, uso una serie de variables para almacenar el "nombre interno" de las listas y siempre las llamo usando estas constantes. Hasta ahora ha funcionado bien.
/// <summary>
/// Returns null if the list is not found
/// </summary>
public static SPList GetListByInternalName(this SPListCollection Lists, string InternalName)
{
    if (string.IsNullOrEmpty(InternalName)) return null;

    Guid ListGuid = Lists.GetListIdByInternalName(InternalName);

    if (ListGuid != Guid.Empty)
        return Lists[ListGuid];

    return null;
}

/// <summary>
/// Returns the UIDC of the list
/// </summary>
static Guid GetListIdByInternalName(this SPListCollection Lists, string InternalName)
{
    if (string.IsNullOrEmpty(InternalName)) return Guid.Empty;

    foreach (SPList list in Lists)
        if (list.GetInternalName().ToLower() == InternalName.ToLower())
            return list.ID;

    return Guid.Empty;
}

/// <summary>
/// Gets the Url of the list. That's what we consider its internal name
/// </summary>
public static string GetInternalName(this SPList list)
{
    string Url = list.RootFolder.ServerRelativeUrl;

    string[] split = Url.Split('/');

    return split[split.Length - 1];
}

Después de esto el nombre externo de la lista no es relevante y la localización es simple o, por lo menos, un poco más simple.

No hay comentarios:

domingo, 7 de abril de 2013

Los 5 errores más tontos (y comunes) de los desarrolladores de SharePoint

Si eres un desarrollador veterano probablemente estarás de acuerdo, si no tienes tanta experiencia probablemente has sufrido por lo menos uno de estos la semana pasada y perdiste una hora intentando enterarte de lo que estaba pasando.

Actualizando la web que no es
Estás cambiando algo, el código funciona, no se lanzan excepciones y vas al sitio de SharePoint para ver tu nueva obra pero no ha cambiado nada... Comprueba que estás cambiando la web que querías modificar y no otra.

Nombre de campo incorrecto
Tu CAML query esta bien, pero recibes una excepción diciendo que los campos de la lista están mal. no es la lista, eres tu Vete a los settings de la lista, a la definición del campo y alli, en la URL tienes el nombre interno del campo tal y como SharePoint espera recibirlo, si es verdad, lo tenías mal en la CAML. A mi me gusta tenerlos definidos como constantes en algún lugar, escríbelos bien una vez y deja que el intellisense te recuerde el nombre por el resto de la vida del proyecto.

Lista equivocada
El fichero que estas creando no aparece en la lista o se queja de los campos y ya lo has comprobado y están bien... Comprueba que estás conectad a la lista correcta. (Por cierto, también tengo constantes para los nombres de las listas)

DLL Incorrecta
Cambias algo en tu código pero el error sigue ahí. Dependiendo de lo que estes haciendo necesitas desplegar la DLL al gac o a la carpeta BIN, la DLL correcta, y luego resetear el servicio correcto, algunas veces no es el IIS. Intenta cambiar una string o algo muy obvio y fácil de notar para asegurarte de que estás ejecutando el código que quieres ejecutar.

Mis web services no se conectan al servidor
Necesitas crear un fichero clientaccesspolicy.xml o un crossdomain.xml y ponerlo en la carpeta de la aplicación web en el inetpub tan simple y tan molesto.
[BONUS] Los cambios que hago no aparecen en SharePoint
La lista está bien, la web está bien, los campos están bien, todo está bien y no pasa nada después de que haya cambiado el objeto... Se te ha olvidado hacer el update. Y esto es válido para un montón de objetos diferentes en SharePoint. En caso de duda yo siempre intento hacer un .Update después de modificar. Cuidado, los updates son normalmente lentos y deben ser llamados la menor cantidad de veces posible..

No hay comentarios:

martes, 2 de abril de 2013

Iterando Listas in Trozos Pequeños y Rendimiento

Hace un par de meses publiqué un post sobre esto y a modo de conclusión dije que me parecía que sería más rápido recorrer la lista entera en trozos pequeños en lugar de hacerlo todo de golpe con una query general... pues no.

He hecho un test con una lista de 100K items para ahorrarte el trabajo y dependiendo en el tamaño de los trozos puede tardar un poco más o un montón más.

Si estás usando un RowLimit pequeño (digamos 5) el numero de veces que tienes que realizar la query para traerte la lista completa puede hacer que el proceso tarde el doble o más. Si estás usando un número mayor (digamos 1000) el rendimiento será similar al que conseguimos trayendo toda la lista en una sola query.

Resumiendo, si estás buscando un número pequeño de valores y posiblemente los consigas en las primeras iteraciones de la query paginada entonces la mejora en la eficiencia será obvia, en algunos casos he notado una media de mejora del 95%, pero ten en cuenta que si estas trayéndote la lista en trozos pequeños y lo más seguro es que tengas que procesar todos los elementos de la query el proceso será más lento, hasta un 400% más lento o incluso más.

No hay comentarios:

lunes, 4 de febrero de 2013

Iterando listas Grandes de SharePoint en trozos Pequeños

¿Por qué querría nadie iterar listas de SharePoint por partes? Por dos razones, principalmente. Primero porque si los resultados de una de tus queries son demasiados (por ejemplo 2000) el rendimiento baja mucho al trabajar con ellas y segundo porque quizás solo necesites trabajar con los primeros 20 elementos que cumplan una condición que es demasiado compleja para ser representada en una CAML query.

Yo estoy usand la paginación de queries para hacerlo y es un 75% más rápido que procesar todos los elementos que devuelve la primera query (esto depende también de la cantidad de datos que tengas en la lista)

Básicamente funciona así, pones el RowLimit a un valor prudente (queremos minimizar el número de queries que lanzamos) y luego haces las consultas a SharePoint hasta que el número de resultados llegue al objetivo que nos hemos marcado o te quedes sin elementos en la lita. (Todavía no he probado si es más rápido iterar sobre todos los elementos en una lista grande de golpe o hacerlo por partes pero algo me dice que de esta forma será más rápido)

/// <summary>
/// Returns the top MaxResults items in the query
/// </summary>
internal static List<SPListItem> GetTopItems(SPWeb web, uint MaxResults = 0)
{
    SPList ListToQuery = web.Lists.GetListByInternalName("ListInternalName");


    List<SPListItem> result = new List<SPListItem>();

    SPQuery query = new SPQuery();
    query.Query = string.Format(@"<Where><Eq><FieldRef Name='State' /><Value Type='Choice'>Live</Value></Eq></Where>");
    query.ViewAttributes = "Scope='RecursiveAll'";
    query.RowLimit = MaxResults + 1;

    SPListItemCollection QueryResults;
    string PagingInfo = string.Empty;

    while (result.Count < MaxResults && PagingInfo != null)
    {
        //We set the beginning of the query to the last item in the previous page
        query.ListItemCollectionPosition = new SPListItemCollectionPosition(PagingInfo);

        //We get the page of items
        QueryResults = ListToQuery.GetItems(query);

        foreach (SPListItem item in QueryResults)
        {
            //We process the items and add them to the result list
            if (ComplexProcessing(item))
                result.Add(item);
        }

        //We set PagingInfo to the last item retrieved in the query
        if (QueryResults.ListItemCollectionPosition != null)
            PagingInfo = QueryResults.ListItemCollectionPosition.PagingInfo.ToStringSafe();
        else //PagingInfo will be null if we reach the end of the pagination
            PagingInfo = null;
    }

    return result;
}

Este método de hacer consultas CAML paginadas ha incrementado enormemente el rendimiento de mi sitio, espero que también a ti te sirva.

No hay comentarios:

miércoles, 3 de octubre de 2012

Refrescando el SharePoint 2010 Timer Service mediante código

Necesito refrescar el servicio de Timer desde una aplicación y me ha costado bastante encontrar cómo hacerlo. Es tan facil como te imaginas

Aquí está el código:
/// 
/// Stops the SharePoint timer.
/// 
public static void TimerStop()
{
    ServiceController timerService = new ServiceController(Constants.SPTimerName);

    if (timerService.Status == ServiceControllerStatus.Running)
    {
        timerService.Stop();
        timerService.WaitForStatus(ServiceControllerStatus.Stopped, Constants.WaitingTimeout);
    }
}

/// 
/// Starts the SharePoint timer.
/// 
public static void TimerStart()
{
    ServiceController timerService = new ServiceController(Constants.SPTimerName);

    if (timerService.Status == ServiceControllerStatus.Stopped)
    {
        timerService.Start();
        timerService.WaitForStatus(ServiceControllerStatus.Running, Constants.WaitingTimeout);
    }
}
Por cierto, las constantes son:
public static TimeSpan WaitingTimeout = new TimeSpan(0, 1, 30);
public static string SPTimerName = "SharePoint 2010 Timer";

No hay comentarios:

jueves, 5 de abril de 2012

Esconder Editar en hoja de datos en el menu Acciones con código javascript

¿Volver después de todo este tiempo para escribir un post tan feo? Ese es exactamente mi estilo…
El título del post explica bastante bien cual era el objetivo a cumplir y todo el mundo sabe que esta no es la mejor forma, pero editar los permisos estaba dándonos problemas en otro lado así que decidimos hacerlo asi.
Este es el enemigo:
Editar en hoja de datos
Y para ocultarlo agregamos un Web Part Editor de Contenido e hicimos click en el botón Editar código fuente:
Content Editor Web Part Source Editor
Allí añadimos el script:
<script type="text/javascript" >
var allMenuItems = document.getElementsByTagName('ie:menuitem'); 
for(var i = 0; i < allMenuItems.length; i++ )   
{
 try
        {
         if (allMenuItems[i].text.toLowerCase() == "editar en hoja de datos")
         {
   var parentNodeOfMenuItem = allMenuItems[i].parentNode;  
                 parentNodeOfMenuItem.removeChild(allMenuItems[i]);                                
  }
 }
 catch(err)
 {}

} 
</script>

Puede parecer un poco desordenado, pero a SharePoint no le importará. Después hacemo click en guardar.
CEWP Source Editor Window
Después de eso podemos poner el web part oculto para que los usuarios no puedan verlo.
Y eso es todo.
Edit in Datasheet Hidden
Enjoy!

No hay comentarios:

martes, 14 de febrero de 2012

Métodos Extensores para Objetos Null

¿Es posible usar un método extensor en algo que es null?

Qué bonito sería poder hacer algo como:
if (!MyString.IsNull()) return MyString.ToString();
Leí hace unos cuantos años que hacer eso sería imposible porque, al ser el objeto null no podrías llamar al método o algo así… Pero en mi cabeza tenía sentido.

Así que me he estado conteniendo para no hacerlo todo este tiempo hasta hoy que me he sentido valiente.

Los métodos extensores:
public static bool IsNull(this string str)
{
    return str == null;
}

public static string ToStringSafe(this object obj)
{
    return (obj ?? string.Empty).ToString();
}
El método main obviamente todo buen programa de pruebas debe ser una aplicación de consola:
static void Main(string[] args)
{
    string str = null;

    if (str.IsNull())
        Console.WriteLine("It was null...");
    else
        Console.WriteLine("It wasn't null...");

    Console.WriteLine(str.ToStringSafe());

    Console.ReadKey();
}
¡Y funciona! Esto abre una nueva dimensión a mis recetas de espagueti.

El primer ejemplo útil que se me viene a la cabeza aparte del ToStringSafe y del ToInt, ToDateTime... ¡Mírame!, ¡No puedo parar! es:

/// 
/// Disposes the object if it's not null.
/// 
public static void DisposeSafe(this IDisposable DisposableObject)
{
    if (DisposableObject != null)
        DisposableObject.Dispose();
}
Con este método nunca más tendremos que preocuparnos de si el objeto es null o no. Todo se libera sin problemas.

A veces me asusta que me den tanta alegría estas cosas…

No hay comentarios:

jueves, 8 de septiembre de 2011

Web Part Proveedor de Filtro con un árbol Silverlight en un ModalDialog

Empecé con esto hace un par de días... No quería hacerlo, porque sabría que iba a ser doloroso... pero me obligaron... Y entonces pensé que sería un post perfecto para el blog por lo de painful.

Lo que queríamos conseguir era filtrar elementos en un List View Web Part por Entidad. En nuestra solución tenemos una jerarquía de entidades por lo que pensamos que sería buena idea que el web part mostrase la jerarquía como un árbol.
Bien, así que necesitamos un web part con un TreeView que sea capaz de mandar la entidad seleccionada como filtro a un LVWP. Fantástico. Lo hice... Y no le gustó a nadie. Lo querían en Silverlight y no solo eso, lo querían en una ventana pop up. Sip, me cogieron, nunca había hecho nada parecido pero, ¿Quién dijo miedo?

Es un montón de código, la mayor parte feo así que solo postearé la parte interesante (básicamente la parte relacionada con la comunicación entre las páginas y el Silverlight) y las URLs de donde cogí las ideas.
Lo primero es poner a funcionar un Filter Provider Web Part. Para ello seguí las instrucciones de aquí. Primero lo intenté con un IWebPartRow, pero no era lo que yo quería así que cambié a ITransformableFilterValues. Esta parte es bastante simple así que no comentaré nada más.
La segunda parte es crear una ventana emergente. Después de googlear si googlear te parece un palabro raro deberías escucharme decir overridar o rollupear un rato me enconté con este post, que me pareció un buen sitio para empezar. Mi código en el web part terminó así:

        protected override void OnLoad(EventArgs e)
        {
            if (Page.IsPostBack)
            {
                if (!string.IsNullOrEmpty(GetFormValue("HiddenEntityName")))
                {
                    Page.Session["SelectedEntityName"] = Page.Request.Form["HiddenEntityName"];
                    Page.Session["SelectedEntityID"] = Page.Request.Form["HiddenEntityID"];

                    RenderHeader();

                    //SelectedEntityText.Text = string.Format("{0}  ", Page.Request.Form["HiddenEntityName"], Page.Request.Form["HiddenEntityID"]);
                }
                else
                    RenderHeader();
            }

            string CurrentWeb = SPContext.Current.Web.Url;
            string height = "500";
            string width = "500";
            string page = "/_layouts/stratex/EntityTree.aspx";

            // use next line for direct with  between  and  
            string scrp = @"
                            ";

            Type t = this.GetType();
            if (!Page.ClientScript.IsClientScriptBlockRegistered(t, "bindWebserviceToAutocomplete"))
                Page.ClientScript.RegisterClientScriptBlock(t, "bindWebserviceToAutocomplete", scrp);
        }
Tuve problemas trayéndome los valores del ModalDialog. No era capaz de encontrar los ID ni los Titles de los controles porque son creados dinámicamente. El truco que usé fue crear dos campos ocultos en el CreateChildControls:
        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            ...

            Page.ClientScript.RegisterHiddenField("HiddenEntityName", "");
            Page.ClientScript.RegisterHiddenField("HiddenEntityID", "");

            ...
        }
Oye Chan, ¿Te has dado cuenta que podrías usar solo la sesión y olvidarte de los HiddenFields? No preguntes, te lo advierto…
La próxima parte es crear la página aspx para el modal dialog. La guardé en un directorio que me creé en _layouts. El código de la página quedó así:
<%@ Page Language="C#" Inherits="StratExFramework.EntityTree,StratExFramework,Version=2.2.0.0,Culture=neutral,PublicKeyToken=311246df7412ca98" %>

<html>
<head>
<title>Select Entity for filtering</title>
<script type='text/javascript'>
    function PassParameterAndClose(EntityName, EntityID) {

        window.returnValue = new Array( EntityName, EntityID) ;

        var version = parseFloat(navigator.appVersion.split('MSIE')[1]);
        if (version >= 7) 
            { window.open('', '_parent', ''); }
        else
            { window.opener = self; }

        window.close();
    }
</script>
</head>
<body></body>
</html>
También me creé una clase de code behind como puedes ver en la primera línea del aspx... :
    public class EntityTree : WebPartPage
    {
        string CurrentWeb;
        string SelectedEntityID;

        protected void Page_Load(object sender, System.EventArgs e)
        {
            CurrentWeb = Request.Params["CurrentWeb"];
            SelectedEntityID = Request.Params["SelectedEntityID"];
        }

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

            string Source = CurrentWeb + "/Lists/XAPLibrary/SilverlightEntityTreeSelector.xap";
            string SilverlightHeight = "515";
            string SilverlightWidth = "500";

            LiteralControl obj = new LiteralControl();
            obj.Text = "<object id='silverlightHost' style='height: " + SilverlightHeight + "; width: " + SilverlightWidth + @"; margin: 0; padding: 0;' data='data:application/x-silverlight-2,' type='application/x-silverlight-2'>
                            <param name='Source' value='" + Source + @"' />
                            <param name='MinRuntimeVersion' value='3.0.40624.0' />
                            <param name='Background' value='#FFFFFFFF' />
                            <param name='initParams' value='" +
                                string.Format("{0}={1}", "site", HttpUtility.UrlEncode(CurrentWeb)) +
                                string.Format(", {0}={1}", "selectedentityid", HttpUtility.UrlEncode(SelectedEntityID)) +
                                @"' />
                            </object>";
            this.Controls.Add(obj);
        }

        public override void VerifyRenderingInServerForm(Control control)
        {
            return;
        }
    }
Lo que hago aquí es coger los parámetros del entorno en donde se ejecuta el web part y pasárselos al Silverlight. Uso el CurrentWeb para decirle a los web services del Silverlight cual es el contexto y el SelectedEntityID para resaltar la entidad que fue seleccionada la última vez que se abrió la ventana modal. Este es el código del Silverlight.
namespace SilverlightEntityTreeSelector
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();

            Tree.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(Tree_PropertyChanged);

            Tree.Show(string.Empty);
        }

        void Tree_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "SelectedEntityID")
                HtmlPage.Window.Invoke("PassParameterAndClose", Tree.SelectedEntityName, Tree.SelectedEntityID);
            else if (e.PropertyName == "TreeLoaded")
                if (Application.Current.Resources.Contains("selectedentityid"))
                    if (!string.IsNullOrEmpty(Application.Current.Resources["selectedentityid"] as string))
                        Tree.ChangeSelectedItemTo(Application.Current.Resources["selectedentityid"] as string);
        }
    }
}

Ya tenemos todos los componentes.
Esto funciona de la siguiente manera: Seleccionas una entidad en el árbol de Silverlight, luego el Silverlight llama a la función javascript de la ventana modal y que pasa los parámetros al web part padre y cierra el popup. Finalmente el web part manda el filtro al List View WebPar.
Si usas este código te faltarán algunos métodos, pero lo que quería compartir aquí es el método que he seguido para conseguirlo básicamente porque no quiero tener que volver a pensarlo si alguna vez me vuelven a pedir que haga algo parecido en el futuro.



Me encantaría enseñaros algunas fotos, pero los web parts no han sido retocados por las hábiles manos de Adam, nuestro diseñador, y se os podrían salir los ojos de las órbitas.

No hay comentarios:

lunes, 11 de julio de 2011

Error HTTP 500 (No authority could be contacted for authentication.) en SharePoint

Después de restaurar mi máquina virtual de desarrollo en una red diferente la de mi casa estaba recibiendo un error 500 al intentar conectar. La cosa era curiosa porque podía ver el sitio desde el server, pero no desde mi máquina física y sí, había comprobado que las IPs estuviesen bien. Lo primero que hice fue configurar el IE9 para que me enseñase los errores HTTP como a mi me gusta verlos de la manera "unfriendly”.

Para hacerlo fui a Tools –> Internet Options –> Advanced, y allí en el arbol debajo de Browsing Browsing desmarqué la opción de “Show friendly HTTP error messages”.

Después de eso pude ver que el problema que estaba teniendo conectando desde fuera era “No authority could be contacted for authentication.”. Aquí vi que la manera más simple de arreglar el problema era sacar el servidor del dominio y voverlo a meter.

Y funcionó... Por fin...

No hay comentarios:

viernes, 8 de julio de 2011

Escondiendo los List View web parts cuando están vacíos

Hoy me han pedido que esconda los List View web parts de la solución cuando están vacíos... No me gusta el javascript, pero era obviamente la solución más simple, así que le pregunté a Google y me mandó a este post.

Copié el código en un web part Editor de Contenido y funcionó más o menos, pero cuando intenté esconder también las cabeceras de las columnas y añadir un mensaje solo funcionaba para dos de los tres web parts que tenía en la página cosas veredes asi que después de buscar un ratito más en Google lo cambié a:

<script type="text/javascript">
 function HideEmptyWebParts()
 {
   var itemsfound = new Array;
   var elements = document.getElementsByTagName('*');
   for(var i=0;i<elements.length;i++)
   {
      if(elements[i].className == 'ms-vb')
      {
         itemsfound.push(elements[i]);
      }
   }
  
   for (var i=0;i<itemsfound.length;i++)
   {
       if (itemsfound[i].innerHTML.indexOf("There are no items to show in this view of the")>-1)
       {
                itemsfound[i].parentNode.parentNode.parentNode.parentNode.innerHTML="<div class='ms-vb'>Not enough data to produce a dashboard.</div>";
         
       }
   }
 }

_spBodyOnLoadFunctionNames.push("HideEmptyWebParts")
 
</script>

Y entonces sí que empezó a funcionar como esperaba.

No hay comentarios:

martes, 1 de febrero de 2011

Ni SharePoint ni SQL Server funcionan en Azure VM Role

Como ya sabréis los que me leéis he estado intentando que me dejaran participar en la beta del Azure VM Role, pues bien, he conseguido una entrevista con los encargados de darme el acceso y me han hecho la pregunta más temida... ¿Y tu eso para que lo quieres?

Mi idea era montar una granja en la nube con SQL y SharePoint de manera que pudiese usar SSIS, nuestro cubo de BI, Reporting Services y SharePoint.

Bien, pues me han comentado que "De momento es imposible" al parecer hay problemas de conectividad entre las máquinas de VM Role y no funcionan correctamente ni SharePoint ni SQL Server.

Consejo que me dan, que lo monte todo en Office 365. Pero claro, primero de todo, es imposible pronunciar Office 365 en inglés y segundo, ¿qué pasa con mi cubo y mis reporting services si no puedo sacar web services del sandbox de Office 365? pues que no pueden funcionar, con lo que me quedaría con la mitad de mi solución funcionando y la otra tristemente olvidada en el último rincón de mi disco duro.

Solución que me dan... que me espere hasta el verano.

Voy a intentar que mi jefe me de 6 meses de vacaciones, pero si esto no funciona voy a tener que buscarme otro sitio en donde montar mi granja.

Ah y, por si alguien lo dudaba, al final no me han dado acceso al beta program.


Y los de Google mientras tanto  regalando portátiles para que la gente pruebe el Chrome OS...

No hay comentarios:

viernes, 3 de diciembre de 2010

Copiando ListViewWebParts entre diferentes sitios con sus correspondientes vistas personalizadas

Por un segundo he dudado si escribir el resto del post en el título.

En otro ejemplo de lo que ahora he dado en llamar Guerrilla Coding gracias a mis asombrosos conocimientos de la lengua que se habla en la pérfida Albión ahora puedo usar palabras como guerrilla en un par de horas tuve que hacer un programita para copiar cientos de páginas llenas de ListViewWebparts cada uno con su vista personalizada. Yo no sé vosotros, pero yo cada vez que tengo que tocar el ListViewWebpart me pongo a temblar...

Los ListViewWebparts crean en la lista de la que son "hijos" una vista Oculta cuando nosotros personalizamos la vista del webpart. Esto implica que para copiar los webparts a otro sitio tendremos que copiar las vistas en la lista del sitio de destino y apuntar al webpart a la nueva vista, no sé si me explico bien... mejor pondré un trozo de código...

Aquí lo que hago es copiar los webparts de cada fichero de origen al de destino, y les doy un tratamiento especial si son ListViewWebpart para esta aplicación es suficiente.

private static void CopyWebParts(SPFile origFile, SPFile destFile)
        {
            SPLimitedWebPartManager origWpm = origFile.GetLimitedWebPartManager(PersonalizationScope.Shared);
            SPLimitedWebPartManager destWpm = destFile.GetLimitedWebPartManager(PersonalizationScope.Shared);

            DeleteWebparts(destWpm);

            foreach (Microsoft.SharePoint.WebPartPages.WebPart webpart in origWpm.WebParts)
            {
                Microsoft.SharePoint.WebPartPages.WebPart newWp;

                if (webpart is ListViewWebPart)
                    newWp = UpdateListViewWebPart(webpart as ListViewWebPart, origFile.GetWeb(), destFile.GetWeb());
                else
                    newWp = webpart;

                destWpm.AddWebPart(newWp, Common.ConvertToString(newWp.ZoneID), newWp.ZoneIndex);
            }

            try
            {
                destFile.Publish("Added Web Parts");
                destFile.Approve("Web Part additions approved");
            }
            catch { }
        }

        private static ListViewWebPart UpdateListViewWebPart(ListViewWebPart webpart, SPWeb origWeb, SPWeb destWeb)
        {
            ListViewWebPart newWebPart = webpart; //yeah I know.

            string oldListName = webpart.ListName;
            string oldViewGuid = webpart.ViewGuid;

            SPList OrigList = origWeb.Lists.GetList(new Guid(oldListName), true);
            
            SPList DestList = destWeb.Lists[OrigList.Title];

            SPView OrigView = OrigList.GetView(new Guid(oldViewGuid));
            SPView DestView = DestList.Views.Add(string.Empty, OrigView.ViewFields.ToStringCollection(), OrigView.Query,
                OrigView.RowLimit, OrigView.Paged, OrigView.DefaultView);
            DestView.Hidden = OrigView.Hidden;
            DestView.Scope = OrigView.Scope;
            DestView.ApplyStyle(destWeb.ViewStyles.StyleByID(Convert.ToInt32(OrigView.StyleID)));

            DestView.Update();

            newWebPart.ViewGuid = DestView.ID.ToString("B").ToUpper();
            newWebPart.ListName = destWeb.Lists[OrigList.Title].ID.ToString("B").ToUpper();


            return newWebPart;
        }
Y, contra todo pronóstico, funcionó... Espero no tener que tocar estos webparts otra vez en mucho tiempo.

No hay comentarios:

martes, 26 de octubre de 2010

SharePoint y RPM de New Relic

Acabo de instalar en uno de mis servers de demo una copia del RPM de New Relic para ver cómo se comportaba con SharePoint y qué es lo que hacía.

Increiblemente se ha instalado sin ningún problema y ha empezado a funcionar cosa poco común cuando tratamos de SharePoint.

Tras instalarlo, la versión gratis por supuesto, y registrarlo me dieron una licencia gold 8 días para que probase el producto en toda su plenitud por lo que a los pocos minutos de desplegar en mi servidor ya tenía disponibles un buen montón de estadísticas y gráficas que me confirmaban otra vez que mis web services son lentos y que los tiempos de respuesta de MOSS recién despertado de un IISRESET no son los mejores del mundo.


Me han gustado bastante las gráficas relativas al rendimiento del sistema. Nunca antes fue tan sencillo para un empleado demostrar que el server es lento y que hay que ampliarlo y en qué parte, bien sea el SQL, la RAM o la CPU se les ha olvidado el disco duro.


Para los que duden en probarlo les puedo asegurar que (en mi entorno) tanto SharePoint, como los web services, como los reporting services como todo lo demás están funcionando perfectamente, o por lo menos tan bien como antes acabo de encontrar, gracias al RPM un error en un report, ya voy a estar dándole la vara al de BI.

En fin, que como lo mío es quejarme y esta vez no he podido no comento más de este producto, que además no me pagan.


Con los servidores públicos tan gordotes que he tenido bajo mi bota y ahora tengo que hacer click muy rápido para hacer como si hubiese tráfico...

Menos pelo, menos poder, de toda la vida.

No hay comentarios:

viernes, 17 de septiembre de 2010

Lambdas, Method Extensions y RunWithElevatedPrivileges

A estas alturas ya no es un problema para nadie abrir un sitio como administrador independientemente del usuario que este logueado, sin embargo, quiero proponer una función que me parece bastante útil.
/// 
        /// Returns a NEW site with full permissions
        /// 
        public static SPSite OpenSiteWithElevatedPrivileges(this SPSite site)
        {
            SPSite newSite = null;

            SPSecurity.RunWithElevatedPrivileges(() => newSite = new SPSite(site.ID));

            return newSite;
        }
Resulta cómodo, ¿no? A mí me gusta hacer
SPSite site = SPContext.Current.Site.OpenSiteWithElevatedPrivileges();
¿Qué opináis?

No hay comentarios:

miércoles, 15 de septiembre de 2010

Microsoft Charts Controls y MOSS2007

Hacía ya bastante tiempo que sabía de la existencia de estos controles, pero nunca se me había planteado la necesidad de usarlos. Las dos veces que he tenido que hacer graficas las he creado a mano poniendo puntos en un bmp (que es lo más simple)
Sin embargo ahora en Manigent tenemos que hacer un montón de gráficas complejas… pensé en usar Excel Services, pero no tenía sentido mandar datos del modelo de objetos a un libro de Excel y crear la gráfica desde ahí por lo que al final me decidí por usar los controles de .Net

Para ello seguí los pasos de este blog para instalar todo lo necesario en el entorno de desarrollo:
Por supuesto, no leí las instrucciones si no que directamente me puse a crear un webpart.
Lo primero que hice fue referenciar la librería System.Web.DataVisualization que estaba instalada en C:\Program Files\Microsoft Chart Controls\Assemblies\

Y con eso ya puedes crear el webpart en visual studio. El código que usé era una adaptación de un ejemplo que venía en las demos de la documentación:
public class ControlAssessmentChart : Microsoft.SharePoint.WebPartPages.WebPart
    {
        Chart Chart1 = new Chart();

        protected override void CreateChildControls()
        {
            Chart1.Width = 400;
            Chart1.Height = 300;
            Chart1.RenderType = RenderType.ImageTag;

            Chart1.Palette = ChartColorPalette.BrightPastel;
            Title t = new Title("StratEx Testing Chart", Docking.Top,
                new System.Drawing.Font("Trebuchet MS", 14, System.Drawing.FontStyle.Bold)
                , System.Drawing.Color.FromArgb(26, 59, 105));
            Chart1.Titles.Add(t);
            Chart1.ChartAreas.Add("Test 1");

            // create a couple of Test
            Chart1.Series.Add("Test 1");
            Chart1.Series.Add("Test 2");

            //ChartType can also be added to the series
            // add points to Test 1
            Chart1.Series["Test 1"].Points.AddY(5);
            Chart1.Series["Test 1"].Points.AddY(8);
            Chart1.Series["Test 1"].Points.AddY(12);
            Chart1.Series["Test 1"].Points.AddY(6);
            Chart1.Series["Test 1"].Points.AddY(9);
            Chart1.Series["Test 1"].Points.AddY(4);

            // add points to Test 2
            Chart1.Series["Test 2"].Points.AddY(2);
            Chart1.Series["Test 2"].Points.AddY(6);
            Chart1.Series["Test 2"].Points.AddY(18);
            Chart1.Series["Test 2"].Points.AddY(16);
            Chart1.Series["Test 2"].Points.AddY(21);
            Chart1.Series["Test 2"].Points.AddY(14);

            Chart1.BorderSkin.SkinStyle = BorderSkinStyle.Emboss;
            Chart1.BorderColor = System.Drawing.Color.FromArgb(26, 59, 105);
            Chart1.BorderlineDashStyle = ChartDashStyle.Solid;
            Chart1.BorderWidth = 2;

            Chart1.Legends.Add("Legend1");

            Chart1.Legends["Legend1"].Enabled = true;

            Controls.Add(Chart1);
        }
    }
Tras crear el webpart, intenté ejecutarlo en sharepoint y recibí el error:

[HttpException (0x80004005): Error executing child request for ChartImg.axd.]

Para solucionarlo seguí el consejo de este foro y añadí la línea:
<add path="ChartImg.axd" verb="GET,HEAD" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>
En el web.config (dentro de <system.web><httpHandlers>)
Acto seguido volví a intentar ver el webpart, pero me encontré con que:

[DirectoryNotFoundException: Invalid temp directory in chart handler configuration [c:\TempImageFiles\].]

Para solucionar este tuve que hacer caso a este post y añadir:
<add key="ChartImageHandler" value="storage=file;timeout=20;" />
En el web.config dentro de <appSettings>

Finalmente lo conseguimos.
RadarStackedBarStackedColumn
Estas gráficas corresponden a un mismo set de datos (diferente al del ejemplo) solo cambiando el parámetro ChartType de las series.

Parece potente, a ver como rinde en producción…

No hay comentarios:

lunes, 22 de febrero de 2010

Búsqueda por propiedades administradas desde parámetros en la URL

Para hacer mi nube de tags mi objetivo primario era trabajar lo menos posible aprovechar al máximo las posibilidades que nos brinda MOSS y para lograrlo, en tema de búsquedas, es primordial encontrar la forma de pasarle tu consulta a la página de resultados de búsqueda en la URL.


Al principio me desanimé bastante porque yo quería usar una clave al estilo k= o s= para las propiedades administradas, pero como seguramente habrás notado no la hay.


Por cierto aprovecho para enumerar los parámetros que conozco y así no se me olvidan:



  • k: Es la consulta en texto libre que mandamos al motor,


    • /Results.aspx?k=Hola

  • s: Es el ámbito en el que vamos a buscar nuestra consulta,


    • /Results.aspx?k=Hola&s=Todos%20los%20sitios

  • v: Orden de resultados,


    • Results.aspx?k=Hola&s=Todos%20los%20sitios&v=date (o también relevance)

  • start: Número del primer elemento que se mostrará en los resultados (útil para paginar),


    • /Results.aspx?k=Hola&s=Todos%20los%20sitios&&v=relevance&start=21

Con esto ya tenemos para ir tirando, pero no me servía de nada para mi nube de tags. ¿Qué podía hacer? pues le pregunté a mi compañero Igor, que está haciendo un proyecto de búsquedas facetadas. Inmediatamente me dio la solución.


Para buscar por propiedades administradas tan solo tenemos que hacer la consulta de este modo:


k=PropiedadAdministrada:Valor


Qué fácil va a ser ahora apuntar las consultas mediante links aprovechando el motor de búsquedas de MOSS y qué poco trabajo me costó preguntar.

1 comentario: