Social Icons

Mostrando entradas con la etiqueta Los Métodos Perdidos. Mostrar todas las entradas
Mostrando entradas con la etiqueta Los Métodos Perdidos. Mostrar todas las entradas

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:

lunes, 19 de agosto de 2013

El modelo de objetos de cliente es fantástico

Esta es una de esas cosas que sabes que están ahí pero que nunca usas porque ya sabes cuarenta maneras diferentes de hacerlo.

Tan solo cuatro años después de haber empezado a trabajar con SharePoint 2010 pensé "¿Por qué no darle una oportunidad al Modelo de Objetos de Cliente de SharePoint?" en realidad lo que pasó es que tuve un requerimiento de un cliente y debo decir que estoy MUY impresionado. La simplicidad, la velocidad y la predecibilidad son buenísimas, para los estádares a los que estamos acostumbrados.

Aunque el MOC (o COM) es muy bueno me he creado unos cuantos metodos y extensiones que me ayudan a manejar las situaciones más comunes. Algunas son las mismas que uso en el modelo de objetos de SharePoint pero traducidas a COM y otras son nuevas, vamos a empezar:

Es muy fácil conectar usando autenticación windows o basada en formularios. Más sencillo que ninguna otra cosa que haya visto hasta la fecha.

public static ClientContext GenerateClientContextWinAuth(string URL)
{
    return new ClientContext(URL);
}

public static ClientContext GenerateClientContextFBAAuth(string URL, string userName, string password)
{
    ClientContext ctx = new ClientContext(URL);
    ctx.AuthenticationMode = ClientAuthenticationMode.FormsAuthentication;
    ctx.FormsAuthenticationLoginInfo = new FormsAuthenticationLoginInfo(userName, password);

    return ctx;
}


Windows auth 1 linea de código FBA 3. Bien.

¿Te acuerdas de como era leer y escribir ficheros de SharePoint? Pue mira qué fácil es con el modelo de objetos de cliente.

public static string ReadFile(ClientContext Context, string ListName, string FileName)
{
    List StorageList = Context.Web.Lists.GetByTitle(ListName);
    string SharePointFilePath = GetFilePathInSharePoint(Context, StorageList, FileName);

    FileInformation fileInfo = Microsoft.SharePoint.Client.File.OpenBinaryDirect(Context, SharePointFilePath);

    using (fileInfo.Stream)
    {
        using (StreamReader sr = new StreamReader(fileInfo.Stream))
        {
            return sr.ReadToEnd();
        }
    }
}

public static void WriteFile(ClientContext Context, string LocalFilePath, string ListName, string SPFileName)
{
    List StorageList = Context.Web.Lists.GetByTitle(ListName);

    string SharePointFilePath = GetFilePathInSharePoint(Context, StorageList, SPFileName);

    using (FileStream fs = new FileStream(LocalFilePath, FileMode.Open))
        Microsoft.SharePoint.Client.File.SaveBinaryDirect(Context, SharePointFilePath, fs, true);
}

private static string GetFilePathInSharePoint(ClientContext ctx, List StorageList, string FileName)
{
    if (StorageList.RootFolder.ServerObjectIsNull != false)
    {
        ctx.Load(StorageList.RootFolder);
        ctx.ExecuteQuery();
    }

    string ListRootFolderURLDocuments = StorageList.RootFolder.ServerRelativeUrl;
    return Path.Combine(ListRootFolderURLDocuments, FileName);
}


Quitando la funcioncita para encontrar la url del elemento en la biblioteca de documentos más fácil imposible.

También me he creado un par de métodos para facilitar el trabajo con el StratexFramework.

Si estás desarrollando un programa y quieres usar algo de este código perfecto y si no pues puedes modificarlo para que se adecúe a tu entorno. Aquí hay un método para traerse la entidad raíz del framework:

public static ListItem GetRootEntity(this List ComList)
{
    CamlQuery camlQuery = new CamlQuery();
    camlQuery.ViewXml = string.Format(@"<View>
                                            <Query>
                                                <Where>
                                                    <Eq>
                                                        <FieldRef Name='ContentType'/>
                                                        <Value Type='Choice'>Entity</Value>
                                                    </Eq>
                                                </Where>
                                                <RowLimit>1</RowLimit>
                                            </Query>
                                        </View>");

    ListItemCollection items = ComList.GetItemsExecuted(camlQuery);

    if (items.Count == 1)
        return items[0];
    else
        return null;
}

Bonito y fácil.

La query es un pelín distinta de la que usamos siempre en el SPQuery pero aún así bastante similar.

Después me he creado un método que te ayudará cuando quieras hacer una query dentro de una carpeta sin importarte el resto del framework. Primero voy a poner el helper y luego la aplicación para que veáis como lo utilizo:

public static CamlQuery CreatePositionedQuery(this ListItem StartEntity)
{
    CamlQuery camlQuery = new CamlQuery();
    camlQuery.ViewXml = string.Format(@"<View Scope='RecursiveAll' >
                                            <Query>
                                                <Where>
                                                    <And>
                                                        <Eq><FieldRef Name='FileDirRef' /><Value Type='Text'>{0}</Value></Eq>
                                                        {1}
                                                    </And>
                                                </Where>
                                            </Query>
                                        </View>", GetChildrenFolder(StartEntity), "{0}"); //This is the folder url and the placeholder for the real query.


    return camlQuery;

}

private static string GetChildrenFolder(ListItem startItem)
{
    startItem.InitializeIfNeeded("FileDirRef");
    startItem.InitializeIfNeeded("FileLeafRef");

    return startItem["FileDirRef"] + "/" + startItem["FileLeafRef"];
}

Con esto, básicamente le decimos a SharePoint que ejecute la query debajo de la carpeta del elemento que le estamos pasando como parámetro. Y de esta manera podemos posicionar fácilmente nuestras consultas en el arbol de carpetas.

Antes que nada, una función para traer todas las entidades hijas que están debajo de una entidad dada y luego otra función para traer un elemento con un nombre dado bajo una carpeta dada:

public static ListItemCollection GetChildEntities(ListItem StartEntity)
{
    List ComList = StartEntity.ParentList;

    CamlQuery camlQuery = CreatePositionedQuery(StartEntity);
    camlQuery.ViewXml = string.Format(camlQuery.ViewXml,
                                    @"<And>
                                        <Eq><FieldRef Name='State' /><Value Type='Choice'>Live</Value></Eq>
                                        <Eq><FieldRef Name='ContentType' /><Value Type='Choice'>Entity</Value></Eq>
                                        </And>");

    return ComList.GetItemsExecuted(camlQuery);
}

public static ListItem GetChildItem(this ListItem StartEntity, string Title)
{
    List ComList = StartEntity.ParentList;

    CamlQuery camlQuery = CreatePositionedQuery(StartEntity);
    camlQuery.ViewXml = string.Format(camlQuery.ViewXml,
                                    string.Format("<Eq><FieldRef Name='Title' /><Value Type='Text'>{0}</Value></Eq>", Title));

    ListItemCollection result = ComList.GetItemsExecuted(camlQuery);

    if (result.Count > 0)
        return result[0];
    else
        return null;
}
Crear un elemento nuevo e también muy fácil. Lo más complicado es decidir si el elemento es carpeta u hoja:
public static ListItem CreateItemUnder(this ListItem ParentItem, string Title, string ContentType, bool isLeaf)
{
    ListItemCreationInformation itemCreateInfo = new ListItemCreationInformation();
    itemCreateInfo.FolderUrl = GetChildrenFolder(ParentItem);
    itemCreateInfo.LeafName = Title;

    if (isLeaf)
        itemCreateInfo.UnderlyingObjectType = FileSystemObjectType.File;
    else
        itemCreateInfo.UnderlyingObjectType = FileSystemObjectType.Folder;

    ListItem NewItem = ParentItem.ParentList.AddItem(itemCreateInfo);

    if (ContentType != null)
        NewItem["ContentTypeId"] = GetContentType(ParentItem.ParentList, ContentType).Id;

    return NewItem;
}
Un pequeño atajo para ejecutar consultas:
public static ListItemCollection GetItemsExecuted(this List listToQuery, CamlQuery query)
{
    ListItemCollection childItems = listToQuery.GetItems(query);

    listToQuery.Context.Load(listToQuery);
    listToQuery.Context.Load(childItems);
    listToQuery.Context.ExecuteQuery();

    return childItems;
}
En algunos casos el campo que quieres leer no viene en la ejecución de la query quizá porque hass sido una mijita más restrictivo de la cuenta con el parametro viewfields... Puedes probar este truco:
public static void InitializeIfNeeded(this ListItem item, string InternalName)
{
    if (!item.FieldValues.ContainsKey(InternalName))
    {
        item.Context.Load(item);
        item.Context.ExecuteQuery();
    }
}

¿Estás intentando conseguir un tipo de contenido de una lista? Fácil.


public static ContentType GetContentType(List list, string cTypeName)
{
    list.Context.Load(list.ContentTypes);
    list.Context.ExecuteQuery();

    foreach (ContentType ctype in list.ContentTypes)
    {
        if (ctype.Name == cTypeName) return ctype;
    }

    return null;
}

¿Quieres guardar un SPFieldUserValue en un SPFieldUser usando el modelo de objetos de cliente o necesitas leerlo desde allí? Complicado. Yo diría que innecesariamente complicado, pero tengo un último truco:

public static string UserToString(object fieldUserValue)
{
    if (fieldUserValue == null) return string.Empty;

    FieldUserValue user = fieldUserValue as FieldUserValue;

    return string.Format("{0};{1}", user.LookupId, user.LookupValue);
}

public static FieldUserValue StringToUser(object fieldUserValueString)
{
    FieldUserValue user = new FieldUserValue();

    if (string.IsNullOrEmpty(fieldUserValueString.ToStringSafe())) return user;

    string[] tokens = fieldUserValueString.ToStringSafe().Split(';');

    user.LookupId = tokens[0].ToNullableInt() ?? 0;

    return user;
}

El modelo de objetos de cliente me ha parecido potente y muy facil de usar. Te aconsejo que lo uses, si no lo haces ya.

No hay comentarios:

lunes, 22 de abril de 2013

Editando SPListItems desde SPWeb.GetSiteData

En mi solución tengo un número desconocido de subsitios que contienen un número desconocido de SPListItems que necesitan ser actualizados. Parece la manera perfecta de probar el  GetSiteData.

La documentación es útil y podrás probar las consultas sin problemas ¿De verdad? Síp la documentación está bien pero este método trae de vuelta un objeto DataTable y yo lo que necesito es actualizar los SPListItems.

Bueno, si miras al DataTable verás que tienes todo lo necesario para traerte el SPListItem fácilmente.

Me he creado un extension method, bueno dos,... Me encantan los extension methods.
public static SPListItem GetListItemFromSiteData(this DataRow ItemRow, SPSite ParentSite)
{
    using (SPWeb Web = ItemRow.GetWebSiteData(ParentSite))
    {
        return Web.Lists[new Guid(ItemRow["ListId"].ToString())].GetItemById(Convert.ToInt32(ItemRow["ID"]));
    }
}

public static SPWeb GetWebSiteData(this DataRow ItemRow, SPSite ParentSite)
{
    return ParentSite.OpenWeb(new Guid(ItemRow["WebId"].ToString()));
}

Usando estos dos podrás iterar fácilmente por la colección de filas del DataTable, seleccionar qué elementos necesitan ser actualizados y actualizarlos.

No he probado cómo de rápido es comparado con traerte los objetos con una CAML query. No tener que crear la SPListItemCollection podría hacer este método más rápido y más conveniente para según que cosas... Lo probaré,

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: