[Fiddler] Scratchpad es tu aliado

Si eres un enamorado de Fiddler (Sino lo conoces ya estas tardando en descargarlo e instarlo) como yo y te pasas el día lanzando llamadas a tu API REST y esas llamadas son muy repetitivas, te recomiendo que uses Scratchpad.

¿Que es Scratchpad?

Es una opción que viene por defecto en Fiddler que nos permite mantener una lista de nuestras llamadas HTTP.

¿Como funciona?

En la tab de Composer (Básicamente nos permite crear nuestras Request HTTP) creamos nuestra Request, en este ejemplo estoy llamando a WebAPI para que me devuelva un listado ordenado y paginado de preguntas:

image

Ejecutamos la Request, pinchamos en la pestaña de Scratchpad y arrastramos la respuesta:

image

A partir de ahora y aunque cerremos Fiddler, tendremos nuestra petición lista para lanzarla las veces que queramos con tan solo seleccionarla y ejecutarla:

image

Buen fin de semana a todos!!!

Autenticación con DNIe en aplicaciones ASP.NET

Con este post quiero estrenar mi nuevo blog http://luisruizpavon.com, esto no es un adios a mi blog en Geeks, ni mucho menos, haré crossposting de lo que vaya con la temática de Geeks, pero hace tiempo que tenía en la cabeza crear un blog propio y al fin lo he hecho.

Lo primero es dejar claro que todo lo que voy a contar aquí es fruto de mi propia investigación, quién se haya pegado con esto o este en este momento investigando, sabrá que la información al respecto es bastante escasa. Me gustaría que si algo de lo que digo es erroneo o hay una forma mejor de hacerlo, me deje un comentario que será muy bien recibido.

La idea es poder acceder al portal de 2 maneras diferentes:

Sin DNie: El usuario accederá al portal introduciendo su DNI y su contraseña, que fueron introducidos cuando se produjo el alta en el mismo. Con este método, los trámites que necesiten DNIe no estarán disponibles hasta que no acceda con su DNIe.

Con DNie: El usuario introducirá su DNIe en el lector, accederá al portal y este, sí previamente realizó el registro manual introduciendo todos sus datos, automaticamente será identificado.

Yo he encontrado 2 formas de hacer esto y trataré de explicar un poco por encima como funcionan:

IIS 7 + Desarrollo de componentes

Una manera de poder permitir al usuario acceder al portal con su DNIe es configurar IIS 7 para que acepte certificado de cliente (El DNIe lleva 2 certificados, el de autenticación y el de firma).

Para ello lo primero es crearnos un certificado de prueba y habilitar el SSL en nuestro sitio. Luego en la configuración SSL del sitio marcamos que acepta certificado de cliente:

imageAccedemos con el DNIe en el lector y nos pedirá que seleccionemos el certificado:

image

Uno de los problemas que me he encontrado con IIS 7, tiene que ver con la comprobación del estado de revocación del cetificado. He tratado de configurarlo tal cual explica en muchos artículo pero simpre recibo el mismo error:

403.13 (Client Certificate Revoked)

Aquí se explica detalladamente y aunque habla de IIS 6 a mí en IIS 7 introduciendo el ocsp  de la policía no me funciona:

http://tiagoe.blogspot.com/2010/01/error-40313-client-certificate-revoked.html

Esto me lleva a realizar la comprobación del estado de revocación del certificado del DNIe mediante código c#, utilizando el protocolo OCSP (Online Certificate Status Protocol) qué es un método para determinar el estado de revocación de un certificado digital, en este caso para el DNIe está en esta dirección:

http://ocsp.dnie.es/

Aqúi tenéis código de ejemplo:

https://zonatic.usatudni.es/es/aprendizaje/aprende-sobre-el-dnie/58-desarrolla-con-el-dni-electronico/226-internet-information-services-iis.html?start=2

Una vez comprobado el estado de revocación del certificado y visto que es válido, necesitamos realizar la autenticación del usuario por FBA basado en los datos del certificado de autenticación del DNIe.

Usando el API de Tractis

He encontrado por casualidad esta página https://www.tractis.com de la cual no tenía ni idea de su existencia, pero la verdad es que me alegro de haberla encontrado.

Tractis Identity nos permite integrar en nuestras aplicaciones web verificaciones de identidad y permite a tus usuarios acceder a tu sitio web con cetificados electrónicos y todo esto de manera gratuita.

Lo primero que necesitamos es crearnos una cuenta para obtener un API Key:

image

Una vez creada, iniciamos sesión:

image 

Una vez dentro, en la parte superior de la pantalla pinchamos sobre Identity

image

Vamos a crear nuestra API Key, para ello introducimos la dirección de nuestro sitio web, sí estamos en un entorno de desarrollo, para realizar pruebas, podemos añadir el nombre de nuestro sitio web al fichero hosts de nuestro sevidor y funcionará (Lo he probado), pulsamos sobre el botón Obtener API Key:image

Y ya tenemos nuestra API Key para nuestro sitio web:

image

Para probarla basta con crearte una pequeña página HTML tal cual viene en el ejemplo que ellos ponen en su página (Lectura recomendada):

https://www.tractis.com/help/?p=3537&language=es

Para probarlo con SharePoint, podemos añadir la página al directorio _layouts:

image

image

image

Y nos retorna la página que le hemos indicado con la información de la validación:

image

Por último faltaría verificar los datos que nos han enviado desde la pasarela de Tractis para verificar que alguien no se está haciendo pasar por Tractis y nos ha metido la url a pelo en el navegador (Todo esto está en el enlace que puse anteriomente) y validarlo con nuestro sistema de autenticación, en mi caso con un FBA.

 

Un saludo y espero comentarios :)

[ASP.NET] Mantenimiento de sitios web: App_Offline.htm

Todavía me sorprendo cuando la gente se pone a hacer mantenimientos de aplicaciones sin informar de que se están realizando tareas de mantenimiento.

A partir de ASP.NET 2.0 hay una forma sencillísima de hacer esto, que es poner un fichero llamado App_Offline.htm en el directorio virtual de nuestra aplicación web, sí tenemos más de un servidor podemos crearnos un script que copie este fichero a todos los directorios y otro para que una vez que se hayamos terminado las tareas de mantenimiento borre dicho fichero.

El contenido del fichero puede ser algo como esto:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

    <title>Estamos realizando tareas de mantenimiento web</title>

</head>

<body>

    <h1>Estamos realizando tareas de mantenimiento web.</h1>

</body>

</html>

Al copiar el fichero App_Offline.htm en nuestro directorio virtual, el runtime de ASP.NET se encargará de cerrar la aplicación web, descargar el app domain del servidor y no procesará nuevas peticiones que se hagan a la aplicación web.

Pues ya no hay excusas para que los usuarios que esten visitando una web se encuentren con alguna que otra sorpresa :)

Un saludo

[VS2010] Attach al proceso w3wp.exe en una aplicación ASP.NET no carga los símbolos de depuración

Es un post cortito de algo que me ha ocurrido hoy.

Problema:

Depurando una aplicación ASP.NET, haciendo un attach al w3wp.exe no se me cargaban los símbolos de depuración.

Solución:

Cuando publiques el sitio web en tu IIS de desarrollo, asegurate de marcar el check “Emit debug information” para que se generen los .pdb 

image

Saludos

[ASP.NET] Simular un EmptyDataTemplate en un DataList

Como sabréis, este tipo de template no está disponible en el control DataList (El GridView sí dispone de esta plantilla EmptyDataTemplate) para mostrar un mensaje informandole al usuario que no hay registros. Así que vamos a ver como con unas lineas de código podemos simularla:

Yo en mi caso voy a utilizar el FooterTemplate del DataList como sí se tratase de mi EmptyDataTemplate:

<FooterTemplate>

    <asp:Label ID="lblNoRecords" 

               runat="server" 

               Visible="false" 

               Text="No hay comentarios. Sea usted el primero."></asp:Label>

</FooterTemplate>

Dentro del FooterTemplate, añado un Label que será el que muestre el mensaje en caso de no haber registros (En este caso concreto, sino hay comentarios).

Una vez hecho esto, vamos al evento ItemDataBound del control y controlamos que el ItemType es del tipo Footer. Sí es, comprobamos que el número de registros es 0 y accedemos al label para ponerlo visible:

/// <summary>

/// Handles the ItemDataBound event of the dlComments control.

/// </summary>

/// <param name="sender">The source of the event.</param>

/// <param name="e">The <see cref="System.Web.UI.WebControls.DataListItemEventArgs"/> 

/// instance containing the event data.</param>

protected void dlComments_ItemDataBound(object sender, DataListItemEventArgs e)

{

    if (e.Item.ItemType == ListItemType.Footer)

    {

        if (dlComments.Items.Count == 0)

        {

            Label lblNoRecords = e.Item.FindControl(ControlNames.LABEL_NORECORDS) as Label;

 

            if (lblNoRecords != null)

            {

                lblNoRecords.Visible = true;

            }

        }

    }

}

Con esto hemos conseguido simular dicha template.

Un saludo

[ASP.NET + VS 2008] Web Deployment Projects nos facilita los despliegues de nuestras aplicaciones web

Como dije en mi anterior post, estas 3 últimas semanas, he estado desplegando una solución bastante completa en un cliente y pensaba que no me iba a dar para escribir ningún post, pero olvidé hacer mención de la herramienta:

Visual Studio® 2008 Web Deployment Projects – RTW

Seguro que tienes aplicaciones Web con varios config y settings para entornos diferentes DEV, PRE, PRO y muchas veces tienes que estar cambiando a mano dichos configs, settings… con el consiguiente riesgo de pasar settings de DEV a PRE, PRE a PRO, etc… bueno, pues esta herramienta nos va a facilitar bastante la labor de los despligues, ya que dada una configuración que nosotros mismos podemos crearnos, nos va a generar una solución lista para desplegar con sus settings correspondientes.

Vamos a ver como funciona:

Una vez descargada e instalada, vamos a generar un proyecto de prueba, en mi caso en Servicio Web sencillo y nos vamos al fichero Web.config y añadimos una cadena de conexión:

<connectionStrings>

    <add name="eShopConnectionString" 

         connectionString="Data Source=dev-sql; Initial Catalog=dev-db; Integrated Security=True"/>

  </connectionStrings>

En este caso, estaría apuntando al servidor Sql de Desarrollo. A continuación vamos a añadir a nuestra solución un Web Deployment project:

wdp

A continuación vamos a crear 3 tipos de configuración a nuestro proyecto WDP:

  1. Desarrollo (Debug)
  2. Pre-Producción (Release)
  3. Producción (Release)

wdp2

wdp3

Desarrollo

wdp4

Pre-Producción

wdp5

Producción

wdp6

Una vez creadas las 3 configuraciones, vamos a crear 3 ficheros config con las cadenas de conexión (En este caso solo contendrán una, pero podrían ser más) de cada entorno:

wdp7

Desarrollo

<connectionStrings>

  <add name="eShopConnectionString" 

       connectionString="Data Source=dev-sql; Initial Catalog=dev-db; Integrated Security=True"/>

</connectionStrings>

Pre-Producción

<connectionStrings>

  <add name="eShopConnectionString" 

       connectionString="Data Source=pre-sql; Initial Catalog=pre-db; Integrated Security=True"/>

</connectionStrings>

Producción

<connectionStrings>

  <add name="eShopConnectionString" 

       connectionString="Data Source=pro-sql; Initial Catalog=pro-db; Integrated Security=True"/>

</connectionStrings>

Ahora, solo nos queda especificar para cada configuración de las que hicimos enteriormente, de que fichero de cadenas de conexión obtendrá la configuración:

  1. Seleccionamos la configuración (Desarrollo)
  2. En la opción Deployment
  3. Activamos el check Enable Web.config file section replacement
  4. A la sección connectionStrings de nuestro Web.config lo reemplazamos con lo que contiene el fichero de configuraciones del entorno de desarrollo.
  5. Marcamos el check Enforce matching section replacements

wdp8

Y a las otras 2:

wdp7

wdp10

 

 

Una vez hecho todo el trabajo solo nos queda probar, compilamos la solcuión para cada configuración y nos vamos al directorio de nuestro proyecto y:

wdp11

cada carpeta:

wdp12

lista para desplegar con su configuración correspondiente.

Podemos hacer lo mismo con otras secciones de nuestro Web.config, como por ejemplo las appSettings 😉

La verdad es que esta herramienta me ha gustado bastante, facilita mucho el trabajo a la hora de desplegar, nosotros usabamos otras técnicas pero hemos adoptado a esta nueva herramienta.

Salu2 y a disfrutar!!!

[Twitter] Error: 417 Expectation Failed.

Realizando una prueba de concepto con un minicliente de Twitter que estoy desarrollando con WPF para una demo, me encontrado con el error del título a la hora de hacer un update de mi status.

Googleando, he podido encontrar el porque de dicho error:

La clase System.HttpWebRequest y WebClient añaden la cabecera HTTP "Expect: 100-Continue" a menos que se le especifique lo contrario y eso no le gusta a Twitter:

<html>
    <head>
        <title>417 Expectation Failed</title>
    </head>
    <body>
        <h1>Expectation Failed</h1>
        <p>The expectation given in the Expect request-header
        field could not be met by this server.</p>
        <p>The client sent<pre>
        Expect: 100-continue
        </pre>
        but we only allow the 100-continue expectation.</p>
    </body>
</html>

Con el siguiente código he conseguido Twittear desde mi aplicación:

using (WebClient client = new WebClient())
{
    client.Credentials = request.Credentials;
    ServicePoint sp = ServicePointManager.FindServicePoint(request.TwitterUri);
    sp.Expect100Continue = false;
    byte[] updateMessageBytes = System.Text.Encoding.UTF8.GetBytes("status=" + request.Message);
    byte[] responseMessageBytes = client.UploadData(request.TwitterUri, updateMessageBytes);
    response.Response = XDocument.Parse(System.Text.Encoding.UTF8.GetString(responseMessageBytes));
}

Donde:

request.TwitterUri contiene http://twitter.com/statuses/update.xml

Espero que os sirva 😉

Salu2

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!!!

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 😉