HOW TO – Creando nuestro propio DataControlField

Revisando unos WireFrames que se le han entregado al cliente y cuyos WebParts los estoy desarrollando yo, me he encontrado  en unos de ellos que se quería mostrar una imagen en la celda de un SPGridView para que el usuario al situarse sobre ella le muestre un texto descriptivo que se carga de una propiedad.

No tendría que haber ningún problema, usamos un ImageField y listo, no? pero:

¿Y sí necesitamos añadirle un ToolTip con el valor de otra propiedad del DataBinder?

¿Y sí además en el DataBinder no existe dicha imagen como enlace de datos?

La solución: que tenemos que crearnos el nuestro propio. Así que vamos a ver como nos crearnos nuestro propio DataControlField. Para empezar lo primero es heredar de la clase DataControlField

public class ExtendedImageField : DataControlField

Una vez hecho podemos agregar nuestras propiedades (Si las necesitamos):

#region Propiedades
 
/// <summary>
/// Gets or sets the image URL.
/// </summary>
/// <value>The image URL.</value>
public string ImageUrl
{
    get { return _imageUrl; }
    set { _imageUrl = value; }
}
 
/// <summary>
/// Gets or sets the name of the property.
/// </summary>
/// <value>The name of the property.</value>
public string PropertyName
{
    get { return _propertyName; }
    set { _propertyName = value; }
}
 
#endregion

 

En mi caso. estas propiedades son para poner una imagen al control y de que propiedad del DataBinder queremos obtener información para ponerla en el ToolTip.

A continuación necesitamos sobreescribir 2 métodos. El primero CreateField:

/// <summary>
/// When overridden in a derived class, creates an empty <see cref="T:System.Web.UI.WebControls.DataControlField"/>-derived object.
/// </summary>
/// <returns>
/// An empty <see cref="T:System.Web.UI.WebControls.DataControlField"/>-derived object.
/// </returns>
protected override DataControlField CreateField()
{
    return new ExtendedImageField();
}

 

para devolver nuestro tipo (ExtendedImageField) y acto seguido sobreescribimos InitializeCell y creamos un evento:

/// <summary>
/// Adds text or controls to a cell's controls collection.
/// </summary>
/// <param name="cell">A <see cref="T:System.Web.UI.WebControls.DataControlFieldCell"/> that contains the text or controls of the <see cref="T:System.Web.UI.WebControls.DataControlField"/>.</param>
/// <param name="cellType">One of the <see cref="T:System.Web.UI.WebControls.DataControlCellType"/> values.</param>
/// <param name="rowState">One of the <see cref="T:System.Web.UI.WebControls.DataControlRowState"/> values, specifying the state of the row that contains the <see cref="T:System.Web.UI.WebControls.DataControlFieldCell"/>.</param>
/// <param name="rowIndex">The index of the row that the <see cref="T:System.Web.UI.WebControls.DataControlFieldCell"/> is contained in.</param>
public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
{
    base.InitializeCell(cell, cellType, rowState, rowIndex);
 
    if (cellType == DataControlCellType.DataCell)
    {
        cell.DataBinding += new EventHandler(OnBindingField);
    }
}
 
/// <summary>
/// Called when [binding field].
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void OnBindingField(object sender, EventArgs e)
{
    Control target = (Control)sender;
    
    TableCell tCell = target as TableCell;
 
    if (tCell != null)
    {
        tCell.Attributes.Add("valign", "center");
 
        object dataItem = DataBinder.GetDataItem(target.NamingContainer);
        string _tooltipText = (string)DataBinder.GetPropertyValue(dataItem, _propertyName);
 
        if (!String.IsNullOrEmpty(_tooltipText))
        {
            Image image = new Image();
            image.ImageUrl = _imageUrl;
            image.ToolTip = _tooltipText;
            target.Controls.Add(image);
        }
    }
}

En el primero (InitializeCell) lo que hacemos es comprobar que tipo de celda se está inicializando ya que tenemos 3 tipos posibles:

  • Header
  • Footer
  • DataCell

http://msdn.microsoft.com/en-us/library/yc8taz9h.aspx

y comprobamos que es del tipo DataControlCellType.DataCell (Las que tienen los controles y los datos) para suscribirmos a su evento DataBinding y ahí entra en juego el evento creado OnBindingField, en el que hacemos un cast del control que viene por parámetro a TableCell usando la palabra reservada as y sino no es nulo, obtenemos mediante el nombre de la propiedad que le añadimos al control el contenido de la propiedad del DataBinder, que en el caso de que no sea ni nula ni vacia, nos creamos un control imagen, al que le añadimos sus propiedades ImageUrl con la que tiene nuestro control y el ToolTip con el valor de la propiedad del DataBinder que hemos recogido.

Con esto hemos conseguido nuestro objetivo 🙂

Esto es todo!!!

Microsoft Code Analysis Tool .NET (CAT.NET) v1 CTP

CAT.NET es un Add-in para Visual Studio, qué también podemos utilizar a través de la línea de comandos, para analizar el código de nuestras aplicaciones y detectar vulnerabilidades ya conocidas como Cross Site Scripting, SQL Injection, Process Command Injection, File Canonicalization, Exception Information, LDAP Injection, XPATH Injection y Redirection to User Controlled Site.

Lo he instalado y he decido probarlo con un mini-cutre web site:

catnet

La página UserInfo.aspx es vulnerable a SQL Injection:

protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            User user = new User();
            string _userid = Request.QueryString["userid"];
 
            using (SqlConnection conexion = new SqlConnection(""))
            {
                conexion.Open();
                ///Pruebas SQL Injection CAT.NET
                SqlCommand comando = new SqlCommand();
                comando.CommandText = "SELECT Name, Surname FROM Users WHERE Id = '" + _userid + "'";
                comando.Connection = conexion;
 
                IDataReader reader = comando.ExecuteReader();
 
                if (reader.Read())
                {
                    user.Id = _userid;
                    user.Name = reader.GetString(0);
                    user.Surname = reader.GetString(1);
                }
            }
        }
    }

Y en el Handler File.aspx he puesto el código de la famosa vulnerabilidad de canonicalización de BlogEngine para ver sí también lo detecta:

public void ProcessRequest(HttpContext context)
    {
        if (!string.IsNullOrEmpty(context.Request.QueryString["file"]))
        {
            string fileName = context.Request.QueryString["file"];
            string folder = "/files/";
            if (System.IO.File.Exists(context.Server.MapPath(folder) + fileName))
            {
                context.Response.AppendHeader("Content-Disposition", "attachment; filename=" + context.Server.UrlEncode(fileName));
                context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
                context.Server.Transfer(folder + fileName, false);
            }
            else
            {
                context.Response.Status = "404 Bad Request";
            }
        }
    } 

 

 

Arrancamos CAT.NET:

catnet2

Ejecutamos y esperamos los resultados:

catnet3

Como se puede observar, ha detectado la vulnerabilidad de SQL Injection, indicando las líneas donde se encuentran dichas vulnerabilidades aunque la del Handler no.

Podemos acceder a los ficheros de Reglas de CAT.NET, añadir nuevas reglas, modificar las existentes, habilitarlas/deshabilitarlas…

catnet4

Seguiré investigando y a partir de ahora utilizaré esta herramienta para auditar mí código.

Esto es todo!!!

HOW TO – (MOSS 2007) Crear nuestro propio ToolPart

Desarrollando unos WebPart para el proyecto en el que estoy actualmente, necesitaba añadir una propiedad a un WebPart para permitir sólo su visibilidad a ciertos perfiles. Estos perfiles los suministra una aplicación que se encarga de la seguridad de las aplicaciones.

Para otros caso me vale una Enum como fuente de datos (Veáse Tipo de Objeto, Tipo de Barra…) pero para este caso no, así que tuve que crearme mi propio ToolPart como se muestra en la imagen:

toolpart1

En concreto el desplegable en cuestión es el de Perfil Permitido.

Para crearnos nuestro propio ToolPart, lo primero es crearnos una clase que herede de ToolPart (Microsoft.SharePoint.WebPartPages.ToolPart):

   public class ExtendedWorkLoadToolPart : ToolPart

Lo siguiente será sobreescribir los métodos CreateChildControls y RenderToolPart:

protected override void CreateChildControls()
{
    base.CreateChildControls();
 
    /// Añadir nuestros controles  
}

 

 
/// <summary>
/// Sends the tool part content to the specified HtmlTextWriter object, 
/// which writes the content to be rendered on the client.
/// </summary>
/// <param name="output">The HtmlTextWriter object that receives the tool part content.</param>
protected override void RenderToolPart(System.Web.UI.HtmlTextWriter output)
{
    // Establish a reference to the Web Part.
    WorkLoadWebPart workLoadWebPart =
        (WorkLoadWebPart)this.ParentToolPane.SelectedWebPart;
 
    output.Write("<table width="100%" cellspacing="0" cellpadding="2"><tr><td colspan="2">");
    _lblDataHeader.Text = SPUtility.GetLocalizedString("$Resources:webpart_lblDataHeader_text", "company", SPContext.Current.Web.Language);
    _lblDataHeader.Font.Bold = true;
    _lblDataHeader.RenderControl(output);
    output.Write("<hr></td></tr><tr><td>");
    _lblObjectType.Text = SPUtility.GetLocalizedString("$Resources:webpart_lblObjectType_text", "company", SPContext.Current.Web.Language);
    _lblObjectType.RenderControl(output);
    output.Write("</td><td>");
    _ddlObjectType.SelectedValue = Enum.GetName(typeof(EnumObjectType), workLoadWebPart.TipoObjeto);
    _ddlObjectType.RenderControl(output);
    output.Write("</td></tr><tr><td>");
    _lblActiveUser.Text = SPUtility.GetLocalizedString("$Resources:webpart_lblActiveUser_text", "company", SPContext.Current.Web.Language);
    _lblActiveUser.RenderControl(output);
    output.Write("</td><td>");
    _chkActiveUser.Checked = workLoadWebPart.UsuarioActivo;
    _chkActiveUser.RenderControl(output);
    output.Write("</td></tr><tr><td>");
    _lblDelimerChar.Text = SPUtility.GetLocalizedString("$Resources:webpart_lblDelimerChar_text", "company", SPContext.Current.Web.Language);
    _lblDelimerChar.RenderControl(output);
    output.Write("</td><td>");
    _txtDelimiterChar.Text = workLoadWebPart.DelimitadorUsurios.ToString();
    _txtDelimiterChar.RenderControl(output);
    output.Write("</td></tr><tr><td colspan="2">");
    _lblVariosHeader.Text = SPUtility.GetLocalizedString("$Resources:webpart_lblVariosHeader_text", "company", SPContext.Current.Web.Language);
    _lblVariosHeader.Font.Bold = true;
    _lblVariosHeader.RenderControl(output);
    output.Write("<hr></td></tr><tr><td>");
    _lblBarType.Text = SPUtility.GetLocalizedString("$Resources:webpart_lblBarType_text", "company", SPContext.Current.Web.Language);
    _lblBarType.RenderControl(output);
    output.Write("</td><td>");
    _ddlBarType.SelectedValue = Enum.GetName(typeof(EnumBarType), workLoadWebPart.TipoBarra);
    _ddlBarType.RenderControl(output);
    output.Write("</td></tr><tr><td>");
    _lblShowLabels.Text = SPUtility.GetLocalizedString("$Resources:webpart_lblShowLabels_text", "company", SPContext.Current.Web.Language);
    _lblShowLabels.RenderControl(output);
    output.Write("</td><td>");
    _chkShowLabels.Checked = workLoadWebPart.MostrarEtiquetas;
    _chkShowLabels.RenderControl(output);
    output.Write("</td></tr><tr><td>");
    _lblPlacePecentage.Text = SPUtility.GetLocalizedString("$Resources:webpart_lblPlacePecentage_text", "company", SPContext.Current.Web.Language);
    _lblPlacePecentage.RenderControl(output);
    output.Write("</td><td>");
    _ddlPlacePercentage.SelectedValue = Enum.GetName(typeof(EnumPlacePercentage), workLoadWebPart.UbicacionPorcentaje);
    _ddlPlacePercentage.RenderControl(output);
    output.Write("</td></tr><tr><td>");
    output.Write("</td></tr><tr><td colspan="2">");
    _lblSecurityHeader.Text = SPUtility.GetLocalizedString("$Resources:webpart_lblSecurityHeader_text", "company", SPContext.Current.Web.Language);
    _lblSecurityHeader.Font.Bold = true;
    _lblSecurityHeader.RenderControl(output);
    output.Write("<hr></td></tr><tr><td>");
    _lblProfiles.Text = SPUtility.GetLocalizedString("$Resources:webpart_lblProfiles_text", "company", SPContext.Current.Web.Language);
    _lblProfiles.RenderControl(output);
    output.Write("</td><td>");
    _ddlProfiles.SelectedValue = workLoadWebPart.PerfilPermitido;
    _ddlProfiles.RenderControl(output);
    output.Write("</td></tr></table>");
 
}

Vamos a fijarnos en esta línea:

// Establish a reference to the Web Part.
WorkLoadWebPart customWebPartEx1 =
        (WorkLoadWebPart)this.ParentToolPane.SelectedWebPart;

 

En ella lo que hacemos es obtener la referencia al WebPart que está usando la ToolPart, para acceder a sus propiedades y poder cargar los datos en los controles antes de renderizarlos.

Para finzalizar nuestra ToolPart, nos falta almacenar los valores de los controles de la ToolPart en las propiedades de nuestro WebPart. Para ellos sobreescribimos el método ApplyChanges, que será llamado cuando se pulse el botón OK o Aplicar, cuando modificamos las propiedades del WebPart:

/// <summary>
/// Called when the user clicks the OK or the Apply button in the tool pane.
/// </summary>
public override void ApplyChanges()
{
    // Establish a reference to the Web Part.
    WorkLoadWebPart workLoadWebPart =
        (WorkLoadWebPart)this.ParentToolPane.SelectedWebPart;
 
    workLoadWebPart.UbicacionPorcentaje = (EnumPlacePercentage)Enum.Parse(typeof(EnumPlacePercentage), _ddlPlacePercentage.SelectedValue);
    workLoadWebPart.TipoBarra = (EnumBarType)Enum.Parse(typeof(EnumBarType), _ddlBarType.SelectedValue);
    workLoadWebPart.PerfilPermitido = _ddlProfiles.SelectedValue;
    workLoadWebPart.TipoObjeto = (EnumObjectType)Enum.Parse(typeof(EnumObjectType), _ddlObjectType.SelectedValue);
    workLoadWebPart.MostrarEtiquetas = _chkShowLabels.Checked;
    workLoadWebPart.UsuarioActivo = _chkActiveUser.Checked;
    workLoadWebPart.DelimitadorUsurios = _txtDelimiterChar.Text.ToCharArray()[0];
 
}

Por último, falta que nuestro WebPart utilice la ToolPart que acabamos de crear. En el código de nuestro WebPart sobreescribimos el método GetToolParts:

/// <summary>
/// Determines which tool parts are displayed in the tool pane of the Web-based Web Part design user interface, and the order in which they are displayed.
/// </summary>
/// <returns>
/// An array of type <see cref="T:Microsoft.SharePoint.WebPartPages.ToolPart"></see> that determines which tool parts will be displayed in the tool pane. If a Web Part that implements one or more custom properties does not override the <see cref="M:Microsoft.SharePoint.WebPartPages.WebPart.GetToolParts"></see> method, the base class method will return an instance of the <see cref="T:Microsoft.SharePoint.WebPartPages.WebPartToolPart"></see> class and an instance of the <see cref="T:Microsoft.SharePoint.WebPartPages.CustomPropertyToolPart"></see> class. An instance of the <see cref="T:Microsoft.SharePoint.WebPartPages.WebPartToolPart"></see> class displays a tool part for working with the properties provided by the WebPart base class. An instance of the <see cref="T:Microsoft.SharePoint.WebPartPages.CustomPropertyToolPart"></see> class displays a built-in tool part for working custom Web Part properties, as long as the custom property is of one of the types supported by that tool part. The supported types are: String, Boolean, Integer, DateTime, or Enum.
/// </returns>
[SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
public override ToolPart[] GetToolParts()
{
    return new ToolPart[] { new WebPartToolPart(), new ExtendedWorkLoadToolPart() };
}

 

Añadimos a parte de la ToolPart por defecto, nuestra ToolPart (ExtendedWorkLoadToolPart).

Y esto es todo!!!

PD: He añadido un código de ejemplo aunque no es del artículo 😉

HOW TO: Obtener librerías de la GAC

Mucho ha llovido desde mi último post, pero entre trabajo y la preparación de mi boda, no he tenido mucho tiempo libre.

Hace un par de meses ayudando a un compañero con una aplicación web, tuve la necesidad de desplegar las dlls del ReportViewer en el hosting que habían contratado para que pudieran visualizar unos reports. Existe un paquete de distribución http://www.microsoft.com/downloads/details.aspx?FamilyID=8a166cac-758d-45c8-b637-dd7726e61367&DisplayLang=en, pero en nuestro caso no era nuestra opción, ya que el hosting era de terceros.

Para quién no lo sepa, las librerías se pueden encontrar en C:Program FilesMicrosoft Visual Studio X.0ReportViewer, pero una vez desplegado os encontraréis que os falta la librería Microsoft.ReportViewer.ProcessingObjectModel.dll, que concretamente se encuentra en la GAC instalada.

Para obtenerla ejecutamos el comando:

Subst <Unidad>: %windir%assembly por ejemplo Subst y: %windir%assembly

gac

Una vez hecho esto, tenemos acceso completo a las librerías, que en mi caso están en:

Y:GAC_MSILMicrosoft.ReportViewer.ProcessingObjectModel8.0.0.0__b03f5f7f11d50a3a para la versión de VS 2005

Y:GAC_MSILMicrosoft.ReportViewer.ProcessingObjectModel9.0.0.0__b03f5f7f11d50a3a para la versión de VS 2008

Esto es todo!!!