Friday, November 25, 2011

Aprobar una Tarea Programáticamente

Hola de nuevo. Este es un tema que varios han comentado bastante en Foros, Blogs, y bueno cada medio que permita dar a entender la problemática.
En resumen lo que necesitábamos en nuestro caso era poder Aprobar una Tarea asociada a un WorkFlow diseñado con SharePoint Designer, pero que al tiempo el WorkFlow vaya avanzando.
Uno de los inconvenientes que los usuarios finales encuentran con los WorkFlows de SharePoint Designer es tener que completar las tareas asignadas, pero de paso también tener que aprobar el ítem de la lista que tiene asociado el WorkFlow.
Para evitar esto, podemos crear una WebPart que nos permita tanto aprobar (realizar la tarea y cambiar el estado del ítem), como puede ser rechazar o finalmente cancelar en caso de no querer ejecutar ninguna acción en el momento.
Supongamos entonces que tenemos un WorkFlow bastante simple, con 3 pasos, durante los cuales un primer paso es asignar una tarea a un Primer Aprobador. Luego en el siguiente paso el primer aprobador requiere Revisar y Aprobar un ítem de una lista o quizá un documento en una biblioteca, y quiere poder hacer todo desde un único punto, y no tener que vérselas con la opción de Realizar Tarea. En caso que el primer aprobador apruebe, entonces viene un tercer paso donde el segundo aprobador da por terminado el proceso Aprobando o Rechazando el ítem.
A continuación se puede apreciar el WorkFlow diseñado:
Task1
Cuando se crea un WorkFlow en SharePoint Designer, este a su vez crea una serie de formularios ASPX personalizados que se pueden encontrar en la siguiente localización, junto a los archivos de configuración y reglas del WorkFlow. Esas páginas ASPX se corresponden o sirven con mecanismo que permite la Realización de la Tarea, que se ha configurado como una Acción en algún paso del WorkFlow, por ejemplo en los pasos Revisar y Aprobar 1 del WorkFlow de este ejemplo.
Task2
En rojo se enmarcan las páginas ASPX respectivas de cada Acción de Tarea en los pasos del WorkFlow.
Pero llega el momento de crear la WebPart que permita entonces reemplazar la WebPart por defecto de cada página ASPX asociada a una Tarea. La WebPart por defecto lo único que permite realizar es la acción de Completar la Tarea, que es lo que queremos evitar, y que mejor eso se haga y también se modifique automáticamente el estado del WorkFlow.
La lista que se asociará con el WorkFlow deberá contener un campo llamado Estado de tipo elección y campo llamado Aprobador de tipo Texto. Los dos campos podrán estar ocultos en la lista porque son únicamente un medio de ejecución y control para la WebPart personalizada.
No voy a explicar cómo construir la WebPart ya se aen WSPBuilder para SharePoint 2007 o una WebPart visual en SharePoint 2010. Dejo el código a continuación. Este ejemplo asume que la WebPart utiliza un control ASCX. En el cual estarán las tres acciones de Aprobar, Rechazar y Cancelar, que son básicamente botones.
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;
using Microsoft.SharePoint.Utilities;
using System.Collections;
using System.Globalization;
namespace WSPBuilderCOA
{
    public partial class EditTask : System.Web.UI.UserControl
    {
        private string url;
        public string Url
        {
            set
            {
                url = value;
            }
            get
            {
                return url;
            }
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            //this.lblMensaje = "";
        }
        protected void btnAprobar_Click(object sender, EventArgs e)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite site = new SPSite(url))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        web.AllowUnsafeUpdates = true;
                        try
                        {
                            SPList task = web.Lists["Tareas"];
                            SPListItem item = task.Items.GetItemById(Convert.ToInt32(Request.Params["ID"]));
                            if (item["WorkflowListId"] != null)
                            {
                                Guid sourceListID = new Guid(item["WorkflowListId"].ToString());
                                SPList sourceList = web.Lists.GetList(sourceListID, true);
                                int sourceListItemID = Convert.ToInt32(item["WorkflowItemId"]);
                                SPListItem sourceListItem = sourceList.GetItemById(sourceListItemID);
                                //El primer aprobador realiza la tarea y cambia el estado del ítem a Aprobado en Primera Instancia
                                if (sourceListItem["Aprobador"].ToString() == "Aprobador 1" && Convert.ToString(sourceListItem["Estado"]) == "En Revisión")
                                {
                                    sourceListItem["Estado"] = "Aprobado en Primera Instancia";
                                    sourceListItem["Aprobador"] = "Aprobador 1";
                                    sourceListItem.Update();
                                    //Get the workflow instance id from Task item
                                    Guid taskWorkflowInstanceID = new Guid(item["WorkflowInstanceID"].ToString());
                                    SPWorkflow workflow = item.Workflows[taskWorkflowInstanceID];
                                    SPWorkflowTask wfTask = workflow.Tasks[item.UniqueId];
                                    Hashtable ht = new Hashtable();
                                    ht[SPBuiltInFieldId.Completed] = "TRUE";
                                    ht["Completed"] = "TRUE";
                                    ht[SPBuiltInFieldId.PercentComplete] = 1.0f;
                                    ht["PercentComplete"] = 1.0f;
                                    ht["Status"] = "Completed";
                                    ht[SPBuiltInFieldId.TaskStatus] = SPResource.GetString(new CultureInfo((int)wfTask.Web.Language, false), Strings.WorkflowStatusInProgress, new object[0]);
                                    ht[SPBuiltInFieldId.WorkflowOutcome] = "Approved";
                                    ht["TaskStatus"] = "Approved";
                                    ht["FormData"] = SPWorkflowStatus.InProgress;

                                    SPWorkflowTask.AlterTask((wfTask as SPListItem), ht, true);
                                    Response.Redirect(Request.Params["Source"]);
                                }
                                else
                                {
                                    if (sourceListItem["Aprobador"].ToString() == "Aprobador 1" && Convert.ToString(sourceListItem["Estado"]) == "Aprobado en Primera Instancia")
                                    {
                                        sourceListItem["Estado"] = "Aprobado en Segunda Instancia";
                                        sourceListItem["Aprobador"] = "Aprobador 2";
                                        sourceListItem.Update();
                                        //Get the workflow instance id from Task item
                                        Guid taskWorkflowInstanceID = new Guid(item["WorkflowInstanceID"].ToString());
                                        SPWorkflow workflow = item.Workflows[taskWorkflowInstanceID];
                                        SPWorkflowTask wfTask = workflow.Tasks[item.UniqueId];
                                        Hashtable ht = new Hashtable();
                                        ht[SPBuiltInFieldId.Completed] = "TRUE";
                                        ht["Completed"] = "TRUE";
                                        ht[SPBuiltInFieldId.PercentComplete] = 1.0f;
                                        ht["PercentComplete"] = 1.0f;
                                        ht["Status"] = "Completed";
                                        ht[SPBuiltInFieldId.TaskStatus] = SPResource.GetString(new CultureInfo((int)wfTask.Web.Language, false), Strings.WorkflowStatusInProgress, new object[0]);
                                        ht[SPBuiltInFieldId.WorkflowOutcome] = "Approved";
                                        ht["TaskStatus"] = "Approved";
                                        ht["FormData"] = SPWorkflowStatus.InProgress;
                                        SPWorkflowTask.AlterTask((wfTask as SPListItem), ht, true);
                                        Response.Redirect(Request.Params["Source"]);
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            //lblMensaje.Text = "Ha ocurrido un error aprobando: " + ex.Message + " - " + ex.Source;
                        }
                        finally
                        {
                            web.AllowUnsafeUpdates = false;
                        }
                    }
                }
            });
        }

        protected void btnRechazar_Click(object sender, EventArgs e)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite site = new SPSite(url))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        web.AllowUnsafeUpdates = true;
                        try
                        {
                            SPList task = web.Lists["Tareas"];
                            SPListItem item = task.Items.GetItemById(Convert.ToInt32(Request.Params["ID"]));
                            if (item["WorkflowListId"] != null)
                            {
                                Guid sourceListID = new Guid(item["WorkflowListId"].ToString());
                                SPList sourceList = web.Lists.GetList(sourceListID, true);
                                int sourceListItemID = Convert.ToInt32(item["WorkflowItemId"]);
                                SPListItem sourceListItem = sourceList.GetItemById(sourceListItemID);
                                //El primer aprobador realiza la tarea y cambia el estado del ítem a Aprobado en Primera Instancia
                                if (sourceListItem["Aprobador"].ToString() == "Aprobador 1" && Convert.ToString(sourceListItem["Estado"]) == "En Revisión")
                                {
                                    sourceListItem["Estado"] = "Rechazado";
                                    sourceListItem["Aprobador"] = "Aprobador 1";
                                    sourceListItem.Update();
                                    //Get the workflow instance id from Task item
                                    Guid taskWorkflowInstanceID = new Guid(item["WorkflowInstanceID"].ToString());
                                    SPWorkflow workflow = item.Workflows[taskWorkflowInstanceID];
                                    SPWorkflowTask wfTask = workflow.Tasks[item.UniqueId];
                                    Hashtable ht = new Hashtable();
                                    ht[SPBuiltInFieldId.Completed] = "TRUE";
                                    ht["Completed"] = "TRUE";
                                    ht[SPBuiltInFieldId.PercentComplete] = 1.0f;
                                    ht["PercentComplete"] = 1.0f;
                                    ht["Status"] = "Completed";
                                    ht[SPBuiltInFieldId.TaskStatus] = SPResource.GetString(new CultureInfo((int)wfTask.Web.Language, false), Strings.WorkflowStatusInProgress, new object[0]);
                                    ht[SPBuiltInFieldId.WorkflowOutcome] = "Approved";
                                    ht["TaskStatus"] = "Approved";
                                    ht["FormData"] = SPWorkflowStatus.InProgress;
                                    SPWorkflowTask.AlterTask((wfTask as SPListItem), ht, true);
                                    Response.Redirect(Request.Params["Source"]);
                                   
                                }
                                else
                                {
                                    if (sourceListItem["Aprobador"].ToString() == "Aprobador 1" && Convert.ToString(sourceListItem["Estado"]) == "Aprobado en Primera Instancia")
                                    {
                                        sourceListItem["Estado"] = "Rechazado";
                                        sourceListItem["Aprobador"] = "Aprobador 2";
                                        sourceListItem.Update();
                                        //Get the workflow instance id from Task item
                                        Guid taskWorkflowInstanceID = new Guid(item["WorkflowInstanceID"].ToString());
                                        SPWorkflow workflow = item.Workflows[taskWorkflowInstanceID];
                                        SPWorkflowTask wfTask = workflow.Tasks[item.UniqueId];
                                        Hashtable ht = new Hashtable();
                                        ht[SPBuiltInFieldId.Completed] = "TRUE";
                                        ht["Completed"] = "TRUE";
                                        ht[SPBuiltInFieldId.PercentComplete] = 1.0f;
                                        ht["PercentComplete"] = 1.0f;
                                        ht["Status"] = "Completed";
                                        ht[SPBuiltInFieldId.TaskStatus] = SPResource.GetString(new CultureInfo((int)wfTask.Web.Language, false), Strings.WorkflowStatusInProgress, new object[0]);
                                        ht[SPBuiltInFieldId.WorkflowOutcome] = "Approved";
                                        ht["TaskStatus"] = "Approved";
                                        ht["FormData"] = SPWorkflowStatus.InProgress;
                                        SPWorkflowTask.AlterTask((wfTask as SPListItem), ht, true);
                                        Response.Redirect(Request.Params["Source"]);
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            //lblMensaje.Text = "Ha ocurrido un error aprobando: " + ex.Message + " - " + ex.Source;
                        }
                        finally
                        {
                            web.AllowUnsafeUpdates = false;
                        }
                    }
                }
            });
        }
    }
}
El código es únicamente como demostración, no hemos revisado ninguna buena práctica o que quede mejor ordenado y estructurado porque no es el objetivo de esta entrada. Lo realmente interesante de todo eso, es la parte donde se ejecutan las acciones que permiten Aprobar y Terminar cada tarea asociada, pero que adicionalmente cambian el campo Estado de la lista y que hace que el WorkFlow pueda ir avanzando acorde a la lógica diseñada en SharePoint Designer.
En la siguiente imagen se aprecian la WebPart personalizada y la que ofrece SharePoint por defecto para cada Tarea. El ejemplo muestra lo que vería el usuario Aprobador 2 cuando va a Editar su Tarea asignada.
Task3
Feliz Tarea Programática.

Tuesday, November 01, 2011

Video con SharePoint 2010


Hola de nuevo. Lo primero comentar sobre una gran falencia de nuestra querida plataforma, y es el no poder embeber con facilidad vídeo dentro de campos de tipo Rich HTML. Una gran falencia porque debería ser algo natural de hacer, y no se puede. No voy a entrar en detalles, pero por ejemplo les dejo una muestra, de todo lo que toca hacer para embeber video en una entrada de Blog de SharePoint 2010.

http://blogs.msdn.com/b/sharepointdesigner/archive/2009/12/11/video-blogging-with-javascript-and-the-media-web-part.aspx

Así sucesivamente si se navega por internet buscando soluciones, ninguna es simple, y siempre tocará hacer unos cuantos pasos.

Ahora bien, más allá de eso esta entrada quiere mostrarles cómo usar la WebPart de Media que SharePoint Server 2010 nos ofrece como mecanismo para presentar vídeo y audio. Esta WebPart aparentemente tiene una limitación, y es que para el usuario final, solo le permitirá establecer "rígidamente" el vínculo a un vídeo o archivo de audio, sin permitir ningún tipo de interacción dinámica. Recuerden siempre el cliente quiere una presentación de vídeo tipo YouTube, no un único vídeo preestablecido.

Lo comentado al inicio de esta entrada va directamente relacionado con esto, porque lo primero que los clientes quieren es, por ejemplo, embeber vídeo en cuerpos de Noticias, en entradas de Blog, y en general tener canales de vídeo, tipo YouTube.

Entonces, luego de tanta palabra, pongamos manos a la obra:

  • Desarrollando la lógica de negocio: El método a continuación que se explica, puede meterse en un proyecto de biblioteca de clases.

El siguiente método tiene como objetivo conectarse a una biblioteca de Media y desplegar el listado de Vídeos, que se filtran a través de una vista de dicha biblioteca.

La vista es una excelente técnica cuando por ejemplo se quiere limitar a una cantidad de ítems, ordenarlos, agruparlos, y en general poder tener los ítems listos para ser consumidos a través de código .NET.

Lo primero que tenemos a continuación es la rutina en C#, que a través del modelo de objetos administrado retorna la lista genérica con los ítems consultados de la vista de esa biblioteca de media de SharePoint.

public IList<TGVideo> GetVideos(string listName, string viewName)
{
IList<TGVideo> videos = new List<TGVideo>();
TGVideo video = null;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite(SPContext.Current.Site.Url))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists[listName];
if (list != null)
{
try
{
SPView view = list.Views[viewName];
SPListItemCollection items = list.GetItems(view);
foreach (SPListItem item in items)
{
video = new TGVideo();
video.Title = item.Name.Split('.')[0];
video.UrlVideo = "/paginas/videos.aspx?video=" + item.Name;
if (item["AlternateThumbnailUrl"] != null)
video.Thumbnail = ((SPFieldUrlValue)item["AlternateThumbnailUrl"]).Url;
else
video.Thumbnail = "/_layouts/images/VideoPreview.png";
videos.Add(video);
}
}
catch (Exception ex)
{
EventLog log = new EventLog();
if (!(log.Source == "Errores WebPart"))
log.Source = "Errores WebPart";
log.WriteEntry("Error en el Método GetVideos: " + ex.Message + "- InnerException: " + ex.InnerException + "- Source: " + ex.Source + "- StackTrace: " + ex.StackTrace,
EventLogEntryType.Error, 1);
throw;
}
}
}
}
});
return videos;
}

Lo primero que se aprecia es que el método retorna una lista genérica de elementos de tipo TGVideo. Dicha clase luce así:

public class TGVideo
{
private string title;
public string Title
{
get
{
return title;
}
set
{
title = value;
}
}
private string thumbnail;
public string Thumbnail
{
get
{
return thumbnail;
}
set
{
thumbnail = value;
}
}
private string urlVideo;
public string UrlVideo
{
get
{
return urlVideo;
}
set
{
urlVideo = value;
}
}
}

El método recibe como parámetros el nombre de la lista e incluso el nombre de la vista que finalmente servirá para consultar los ítems.

Recordemos que una sitio de SharePoint hay usuarios que se loguean y tendrán privilegios de administración, pero otros de solo lectura. Esos usuarios de solo lectura son muy limitados, y si eso no se tiene en cuenta, las WebParts que construimos pueden fallar. Por tal razón es que toda la parte esencial del método se envuelve dentro de la rutina SPSecurity.RunWithElevatedPrivileges, que en términos generales permite ejecutar todo el código que encierra con permisos elevados.

La manera de instanciar objetos SPSite y SPWeb, es la mejor práctica usar USING. En este caso no se aprovecha obtener los objetos del contexto de SharePoint con la clase SPContext, porque SPSecurity.RunWithElevatedPrivileges no funciona así, por tal motivo es que esas instancias se obtienen de ese modo, y por lo tanto se hace DISPOSE formal con la clausula USING.

Luego se puede apreciar como se obtiene la lista a través de su nombre. Muy importante recordar que no es una buena práctica cuando se tiene muchas listas. Lo mejor será instanciar la lista a través de su ID para aumentar el rendimiento. Luego se obtienen los ítems desde la vista y a partir de ahí se itera para recuperar la información deseada a través de la clase de entidad TGVideo.

La página Video.aspx es la que servirá como contenedor o visor de los videos, más adelante se explica su funcionalidad.

Luego si quiere obtener el Thumbnail asociado al video se usa el campo con su nombre interno AlternateThumbnailUrl. En caso que no se haya establecido la ruta una imagen que sirva como thumbnail se usa la imagen por defecto de SharePoint, para tal caso se encuentra en la ruta: /_layouts/images/VideoPreview.png

Finalmente como manejo de errores se escribe una rutina que permita registrar los sucesos en el visor de eventos de Windows, nombrando la entrada como Errores WebPart.

  • Desarrollando la WebPart Visual: Las WebParts visuales son un concepto y técnica esperado desde hace ya bastante tiempo, al menos desde que desarrollamos para SharePoint 2007. En esencia es una separación entre lógica de WebPart (eventos) y presentación (HTML y Codebehind), a través de una clase que hereda de la clase WebPart y otra que es básicamente un control de usuario ASCX. Muy importante recordar que cuando vayan a crear el proyecto de tipo SharePoint 2010 en VS.NET 2010, deben configurarlo como un deploy de tipo FARM. Las WebParts visuales no pueden ser desplegadas como tipo SandBox. En este caso se asume que se va a agregar al proyecto de tipo SharePoint 2010 un nuevo ítem de tipo WebPart visual llamado VideosPart.

En el HTML del control ASCX tendremos lo siguiente:

<div>
<asp:GridView ID="grvVideos" CellSpacing="10" CellPadding="5" runat="server" GridLines="None" ShowHeader="false" AutoGenerateColumns="false">
    <Columns>
        <asp:TemplateField>
            <ItemTemplate>
                <a href='<%#Eval("UrlVideo")%>'>
                    <asp:Image ID="imgThumbnail" ImageUrl='<%#Eval("Thumbnail")%>' runat="server" Width="120" Height="90" BorderWidth="0" />
                </a>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:Label ID="lblTitle" runat="server" Text='<%#Eval("Title")%>' />
                <br />
                <a href='<%#Eval("UrlVideo")%>'>Ver Video</a>
               
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>
</div>
<div>
    <asp:Label ID="lblMessage" runat="server" />
</div>

En esencia cada Eval() hace llamado a las propiedades definidas en la clase de entidad TGVideo.

Ahora bien, el codebehind del control ASCX deberá tener lo siguiente:

private TGServices services = null;

        private string listName;
        public string ListName
        {
            get { return listName; }
            set { listName = value; }
        }

        private string viewName;
        public string ViewName
        {
            get { return viewName; }
            set { viewName = value; }
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            this.lblMessage.Text = "";
            if (!Page.IsPostBack)
            {
                services = new TGServices();
                IList<TGVideo> videos = null;
                try
                {
                    videos = services.GetVideos(listName, viewName);
                    grvVideos.DataSource = videos;
                    grvVideos.DataBind();
                }
                catch (Exception ex)
                {
                    this.lblMessage.Text = "Se ha presentado un error cargando la información: " + ex.Message;
                }
                if (grvVideos.Rows.Count == 0 || videos == null)
                    this.lblMessage.Text = "Actualmente no hay información para desplegar en este elemento.";
            }
        }

Si se fijan es muy poco código, porque toda la complejidad se delegó en la capa de negocio. Lo primero es una instancia de la clase que contendrá el método que se explicó de la lógica de negocio. En este ejemplos e asume que dicha clase se llama TGServices. Luego se definen 2 propiedades que servirán como puente de comunicación entre la clase WebPart y el con trol ASCX. Para este caso la WebPart proporcionará como propiedades configurables el nombre de la lista y de la vista. Y Finalmente se invoca el método que retorna los videos en forma de lista genérica que se pasa como fuente de datos al GridView.

Finalmente así lucirá el código de la clase WebPart:

[Personalizable(), WebBrowsable, Category("Configuración"), WebDisplayName("Nombre de la Lista")]
        public String ListName { get; set; }

        [Personalizable(), WebBrowsable, Category("Configuración"), WebDisplayName("Nombre de la Vista")]
        public String ViewName { get; set; }

        // Visual Studio might automatically update this path when you change the Visual Web Part project item.
        private const string _ascxPath = @"~/_CONTROLTEMPLATES/Familia.WebParts/VideosPart/VideosPartUserControl.ascx";

        protected override void CreateChildControls()
        {
            Control control = Page.LoadControl(_ascxPath);
            ((VideosUserControl)control).ListName = ListName;
            ((VideosUserControl)control).ViewName = ViewName;
            Controls.Add(control);
        }

Se aprecia que las propiedades definidas en el code behind del control de usuario son usadas para pasar dichos valores desde la clase de tipo WebPart.

Hasta este punto tenemos ya lista la WebPart visual que consume y despliega un listado de Videos. Pero como ya se explicó, la URL configurada al hacer clic sobre el thumbnail o la opción Ver Video de la WebPart, lleva a una página llamada Videos.aspx. Como se pudo observar es una página que se ha creado en una biblioteca llamada Páginas de SharePoint. La página Videos.aspx se ha creado como una Página con WebParts. En esa página se deberá agregar un DIV de esta forma, por ejemplo en el Place Holder Main:

<div id="divMediaWebpart">
</div>

El único objetivo del DIV anterior es ser el contenedor de lo que será la WebPart de Media de SharePoint 2010, agregada dinámicamente.

Luego si se quiere agregando una WebPart de Editor de contenido, o directamente en el HTML de la página a través de SharePoint Designer 2010, agregar el siguiente JavaScript:

<script type="text/javascript">
   
        $(document).ready(function() {
            var videoHolder = document.getElementById('divMediaWebpart');
            mediaPlayer.createMediaPlayer(
            videoHolder, videoHolder.id, '640px', '390px',
            {
                 displayMode: 'Inline',
                 mediaTitle: '',
                 mediaSource: '/Videos/' + getQuerystring('video',''),
                 previewImageSource:'/_layouts/images/VideoPreview.png',
                 autoPlay: true,
                 loop: false,
                 mediaFileExtensions:'wmv;wma;avi;mpg;mp3;',
                 silverlightMediaExtensions:'wmv;wma;mp3;'
             }
         );

           
        });
 
    </script>

Muy importante a tener en cuenta los siguiente:

  1. El JavaScript anterior solo funciona agregando la siguiente referencia, bien sea directamente en la página o en la página maestra del sitio: <script type="text/javascript" src="/_layouts/mediaplayer.js"></script>
  2. El JavaScript usa una función que permite leer valores pasados por QueryString cuya implementación es similar a esto:

    function getQuerystring(key, default_)
    {
       if (default_==null) default_="";
          key = key.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
       var regex = new RegExp("[\\?&]"+key+"=([^&#]*)");
       var qs = regex.exec(window.location.href);
       if(qs == null)
         return default_;
       else
           return qs[1];
    }

    La función anterior en el JavScript de la página Videos.aspx estaría esperando un nombre de parámetro llamado video, que en esencia trae el nombre del video junto a su extensión. Dichos Videos deberán alojarse en una biblioteca de Media llamada Videos.

  3. Debido a que se hace uso de algunas opciones de JQuery, es también obligatorio adicionar una referencia a la librería respectiva, por ejemplo en la página maestra lo siguiente:

    <script type="text/javascript" src="/SiteAssets/JS/jquery-1.4.3.min.js"></script>

Con lo anterior se puede disfrutar de una página que despliega Video dinámicamente así:

WebPart desplegando el listado de Videos:

VideoPart

Luego al hacer clic en Ver Video o en el thumbnail se hace un redirect a la página ya explicada Videos.aspx:

Video

Feliz video player!!