All posts by El Javi

Silverlight: Watermarked control

Mi experiencia con Silverlight no está siendo todo lo placentera que me gustaría. Me estoy encontrado permanentemente en la necesidad de cambiar las cosas que presume por defecto esta tecnología, para adecuarla a mis necesidades.

Una de ellas ha sido la creación de un control Textbox con marca de agua. Buscando un poco por Internet el primer enlace redirige al blog de Tim Heuer, donde tenemos un ejemplo de implementación de este control.

El problema se plantea cuando se quiere utilizar este control para usarlo como control de password. No nos ofrece la posibilidad de enmascarar los caracteres. La solución podría ser algo como esto (no recuerdo si se me ocurrió a mí, lo leí por ahí o adapté algo que hubiera encontrado):

/// <summary>
/// Changes the visual state when the control changes its text.
/// </summary>
/// <param name="sender">Sender of the event.</param>
/// <param name="e">Event arguments.</param>>
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
    if (this.IsPasswordBox == true)
    {
        TextBox textBox = sender as TextBox;
        if (textBox != null)
        {
            string newText = textBox.Text;

            // If we don't have text, clean the plain text back up.
            if (string.IsNullOrEmpty(newText) == true)
            {
                this.Plaintext = string.Empty;
                return;
            }

            // If the new text is shorter than the previous text, we simply cut the previous text
            if (newText.Length < this.Plaintext.Length)
            {
                this.Plaintext = this.Plaintext.Substring(0, newText.Length);
                return;
            }

            // We have an string longer than the previous one. We can have a new character in any place
            // inside the text, so we go along the string to find the new characters (probably only one).
            // This new character will not be the "hidden" character
            StringBuilder newBackup = new StringBuilder();

            // We will use the offset because every time we find a character in the new text that is clear,
            // it is a new character. For example, if the new character is in the 5º position, and the next
            // 6º character is hidden, it would be in the 5º position of the old text. So we use the offset
            // to count in which place the character is in the old text.
            int offset = 0;
            for (int i = 0; i < newText.Length; i++)
            {
                if (newText[i] == 'u25CF')
                {
                    // We add this character from the original plain text to the new one
                    newBackup.Append(this.Plaintext[i + offset]);
                }
                else
                {
                    // We add this character from the new text, because it's one of the new characters
                    newBackup.Append(newText[i]);

                    // We must update the offset
                    offset--;

                    // We must change this character in the new text for the hidden character.
                    newText = newText.Substring(0, i) + 'u25CF' + newText.Substring(i + 1, newText.Length - i - 1);
                }
            }

            // Set the masked text in the control
            textBox.Text = newText;

            // Move the caret to the end of the text
            textBox.SelectionStart = textBox.Text.Length;

            // Update the backup plain text
            this.Plaintext = newBackup.ToString();
        }
    }

    this.ChangeVisualState(true);
}

Por último, quedaría la validación del control. Por desgracia para los que no estamos muy informados sobre cómo funciona Silverlight, el ejemplo de Tim no incluía nada relacionado con la validación del control. Es decir, la validación en sí se realiza con mecanismos ajenos al control, como los Data Annotations. El problema es que, una vez validado el control y encontrado que no es correcta su información, es necesario que pase a un estado “No Válido” y que muestre alguna forma de error.

Después de darle un poco de vueltas y echarle un vistazo a estilos de otros controles por defecto de Silverlight, ésta es la solución que me está funcionando a las mil maravillas (esta vez, sí, cosecha propia 100%).

En primer lugar, el estilo para el control. Me voy a limitar a mostrar las partes directamente relacionadas con la validación, puesto que al final del artículo adjunto tanto el estilo del control como el código del mismo.

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup x:Name="ValidationStates">
        <VisualState x:Name="Valid"/>
        <VisualState x:Name="InvalidUnfocused">
            <Storyboard>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0">
                        <DiscreteObjectKeyFrame.Value>
                            <Visibility>Visible</Visibility>
                        </DiscreteObjectKeyFrame.Value>
                    </DiscreteObjectKeyFrame>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </VisualState>
        <VisualState x:Name="InvalidFocused">
            <Storyboard>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0">
                        <DiscreteObjectKeyFrame.Value>
                            <Visibility>Visible</Visibility>
                        </DiscreteObjectKeyFrame.Value>
                    </DiscreteObjectKeyFrame>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsOpen">
                    <DiscreteObjectKeyFrame KeyTime="0">
                        <DiscreteObjectKeyFrame.Value>
                            <System:Boolean>True</System:Boolean>
                        </DiscreteObjectKeyFrame.Value>
                    </DiscreteObjectKeyFrame>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="ValidationErrorElement"
        Visibility="Collapsed"
        BorderBrush="#FFDB000C"
        BorderThickness="1"
        >
    <ToolTipService.ToolTip>
        <ToolTip x:Name="validationTooltip" Template="{StaticResource CommonValidationToolTipTemplate}"
            DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Placement="Right"
            PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}">
            <ToolTip.Triggers>
                <EventTrigger RoutedEvent="Canvas.Loaded">
                    <BeginStoryboard>
                        <Storyboard>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsHitTestVisible">
                                <DiscreteObjectKeyFrame KeyTime="0">
                                    <DiscreteObjectKeyFrame.Value>
                                        <System:Boolean>true</System:Boolean>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </ToolTip.Triggers>
        </ToolTip>
    </ToolTipService.ToolTip>
    <Grid Height="12" HorizontalAlignment="Right"
          Margin="1,-4,-4,0" VerticalAlignment="Top"
          Width="12" Background="Transparent">
        <Path Fill="#FFDC000C" Margin="1,3,0,0" Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z"/>
        <Path Fill="#ffffff" Margin="1,3,0,0" Data="M 0,0 L2,0 L 8,6 L8,8"/>
    </Grid>
</Border>

A destacar especialmente que hay dos partes claramente diferenciadas en el estilo:

  • La parte en la que se definen el grupo de estados “ValidationStates”, formado por los estados “Valid”, “InvalidUnfocused” y “InvalidFocused”. Con estos tres cubriríamos todas las posibilidades relacionadas con el estado del control y su validación.
  • La otra parte relevante del estilo es la definición de un elemento “ValidationErrorElement”, que junto al tooltip nos permite marcar, en este caso en rojo, el control que ha fallado y mostrar un mensaje con información del error.

La segunda parte de este truco consiste en definir las transacciones correspondientes entre los estados del control así como dispararlas ante eventos concretos que acontezcan en el control. En este caso, es necesario definir una propiedad “isErroneous” para indicar cuándo el control ha pasado al estado erróneo. Esta propiedad la controlaremos conectándonos al evento BindingValidationError, tal como se muestra en el siguiente extracto del código del control:

/// <summary>
/// Indicates if the control is in an erroneous state.
/// </summary>
private bool isErroneous;

/// <summary>
/// Initializes a new instance of the <see cref="WatermarkedTextBox"/> class.
/// </summary>
public WatermarkedTextBox()
{
    this.BindingValidationError += new EventHandler<ValidationErrorEventArgs>(WatermarkedTextBox_BindingValidationError);
}

El código que vamos a ligar al evento es tan sencillo como lo que podemos ver a continuación:

/// <summary>
/// Configures the properties of the control to indicate it has an error.
/// </summary>
/// <param name="sender">Sender of the event.</param>
/// <param name="e">Event arguments.</param>
private void WatermarkedTextBox_BindingValidationError(object sender, ValidationErrorEventArgs e)
{
    this.isErroneous = e.Action == ValidationErrorEventAction.Added ? true : false;
    this.ChangeVisualState(true);
}

Controlando la acción que acompaña como argumento al evento, podremos saber si la función ha saltado porque el control ha entrado o ha salido del estado erróneo. Toda la lógica, y por tanto lo relevante, queda relegado al método ChangeVisualState. Este método controlará las transacciones entre los distintos estados en los que puede estar el control. Un extracto de este método, específico para la parte de validación de errores, en el siguiente fragmento:

/// <summary>
/// Change to the correct visual state for the textbox.
/// </summary>
/// <param name="useTransitions">
/// true to use transitions when updating the visual state, false to
/// snap directly to the new visual state.
/// </param>
private void ChangeVisualState(bool useTransitions)
{

    // Update the ValidationStates group
    if (this.hasFocus && this.isErroneous)
    {
        VisualStateHelper.GoToState(this, useTransitions, VisualStateHelper.InvalidFocused, VisualStateHelper.InvalidUnfocused);
    }
    else if (this.isErroneous)
    {
        VisualStateHelper.GoToState(this, useTransitions, VisualStateHelper.InvalidUnfocused);
    }
    else
    {
        VisualStateHelper.GoToState(this, useTransitions, VisualStateHelper.StateValid);
    }
}

Cómo se puede ver, dependiendo de si el control tiene foco o no, se pasa a los distintos estados inválidos en caso de existir errores. Caso de no haberlos, se pasa a un estado de validez que desactiva los adornos de estilo informativos.

El resultado es un control que se comporta igual que los textbox por defecto de Silverlight, a pesar de ser totalmente personalizado. En cualquier caso, cambiar el aspecto que mostrará al entrar en estado erróneo es tan sencillo como modificar el estilo.

Ejemplo

Descargas:

PD: Perdonad los comentarios en inglés en el código, pero es para un proyecto de la empresa en la que trabajo y la pereza me impide ponerme a traducirlo todo 🙂

Desarrollando con Silverlight: Data binding

Unos cuantos apuntes rápidos sobre Silverlight y Data binding, más a título personal (para no olvidarme de estos recursos y estas conclusiones) que con ánimo de salvarle la vida a nadie (no he conseguido ni arreglar mis propios problemas…).

En primer lugar, este artículo en MSDN sobre Data Binding. Algunas de las conclusiones que he podido sacar son:

  • Usar la propiedad DataContext de los objetos para enlazarlos con objetos del CLR (sources). Permite herencia hacia abajo y también se hereda de los controles padre, por lo que si no queremos que un objeto tenga el mismo valor que su padre, habrá que sobreescribirla.
  • La propiedad ElementName nos vale para enlazar a otros controles XAML en lugar de a un objeto del CLR
  • La propiedad RelativeSource para servir para enlazar a elementos en un Control Template.
  • Es necesario implementar INotifyPropertyChanged en los objetos que queramos que funcionen como sources, si queremos tener TwoWay en el binding. Si lo queremos hacer con una colección, tendrá que implementar INotifyCollectionChanged. En cualquier caso, la colección ObservableCollection<T> ya lo hace, con lo que es una buena opción para no reinventar la rueda.
  • Si queremos que nos aparezcan esos cartelitos rojos tan chulos con los mensajes de error que hayamos definido (aquí entra en juego los Data Annotations, estupendos para poder hacer validaciones más finas), tenemos que marcar a true las propiedades ValidatesOnException y NotifyOnValidationError del binding, en el fragmento XAML.

El punto en que me encuentro ahora es que tengo una validación correcta, pero al estar usando un control con su propio manejo de los estados, no es capaz de mostrar los errores (más bien, imagino que pasar a un estado “erróneo” que, por defecto en SL, tendrá asociada una transición que hace aparecer la caja con el error y demás artificios visuales).

Enlaces sobre los que trabajar:

Tan pronto resuelva el problema, publicaré puntualmente.

Bing Maps y la realidad aumentada

Por romper mi silencio reciente (cosas de la ausencia de inspiración), un vídeo espectacular que he descubierto vía el blog corporativo de Microsoft Ibérica. Trata sobre Bing Maps y, en general, sobre la realidad aumentada, la próxima revolución en cuanto a información se trata.

Lo reconozco, me encanta la realidad aumentada. Creo que la era de la información en bruto pero desordenada se termina, y el contexto va a ser la próxima revolución.

Lástima que a día de hoy los servicios que podemos ver en el vídeo no se ofrezcan sobre ciudades españolas. Aunque, al ritmo que progresamos en nuevas tecnologías en este país, lo mismo lo tenemos para… 2020.

WCF4: Activación sin fichero .svc

Hoy mismo hablaba con un compañero del trabajo sobre los ficheros svc, esos simpáticos amigos que nos permiten identificar la dirección sobre la que tiene que consumirse un servicio web en WCF. Nuestra conversación giraba en torno a la posibilidad de hostear un servicio WCF en una dirección que terminara en .asmx, para reemplazar un servicio web ASP.NET “de los antiguos” por uno de WCF sin que la URL cambiara. ¿Se podrá, pensábamos? Seguramente sí, pero a partir de WCF4 es seguramente más fácil que nunca gracias a la activación de servicios WCF sin fichero .svc.

¿Cómo hacerlo? Tirando, como no, del app.config. Como siempre, lo más fácil es verlo con un ejemplo. Vamos a partir de un proyecto WCF de ejemplo, de los que nos crea Visual Studio 2010. El contrato de servicio tendrá un método GetData y habrá una clase que implemente dicho servicio. En realidad, nos da un poco igual para lo que vamos a ver.

Vamos al tema. Nuestro fichero app.config tendrá un aspecto parecido a éste:

<configuration>
  <system.serviceModel>
    <serviceHostingEnvironment>
      <serviceActivations>
        <add relativeAddress="MyVirtualSvcFile.svc" service="WcfService.Service"/>
      </serviceActivations>
    </serviceHostingEnvironment>
  </system.serviceModel>
</configuration>

Como ya hemos comentado en post anteriores, la configuración se reduce a la mínima expresión en WCF4. En este caso estamos dejando al framework que se encargue de generarnos los bindings por defecto, y simplemente le indicamos que vamos a tener la dirección virtual con un “fichero” svc que activará el servicio indicado en el atributo “service”.

De este modo, cuando nos dirijamos a la dirección http://dominio/RutaServicio/MyVirtualSvcFile.svc veremos la clásica pantalla de WCF indicándonos que no tenemos los metadatos activados, como se muestra en la siguiente imagen:

ServicelessActivation

 

Todo esto es muy bonito, pero no responde a la pregunta original: ¿podríamos usar una URL terminada en .asmx para activar el servicio WCF? Pues, al menos con esta aproximación mediante las nuevas configuraciones de WCF4… NO. Éste es el bonito error que obtenemos cuando lo intentemos:

ServicelessActivationFailed

Una pena. En cualquier caso, esta nueva forma de activar los servicios nos ahorrará tener que andar preocupados de crear N ficheros .svc, tantos como servicios queramos activar. Mucho mejor tenerlos convenientemente descritos en el fichero app.config.

Actualización: El problema lo da con cualquier extensión que no sea .svc. De paso, hay quien dice que ya no está disponible esta característica. ¡ Para luego hacerle caso a a los de Microsoft !

WCF: OneWay y bloqueo del cliente

Cuando me estaba preparando el MCTS sobre WCF leí una afirmación sorprendente en el Training Kit oficial de Microsoft. En él venían a decir sus autores, en una traducción más o menos libre, lo siguiente sobre el patrón de intercambio de mensajes OneWay:

Dada la naturaleza OneWay del canal, uno podría pensar que, tan pronto el consumidor envía el mensaje, éste se procesa asíncronamente y el cliente es libre de hacer otras cosas. Sin embargo, la forma en que la maquinaria de WCF funciona significa que el consumidor, de hecho, se bloquea, incluso si el mensaje es OneWay, hasta que el dispatcher entrega el mensaje a una instancia del servicio, en la forma de una llamada a un método del objeto.

Teniendo en cuenta que OneWay es un patrón “Fire-and-Forget”, no encaja mucho que el cliente tenga que mantenerse bloqueado hasta que el servidor entrega el mensaje a un objeto que implemente el contrato de servicio. Es importante recordar que alguna de las particularidades de OneWay no hacen sino resaltar esta naturaleza “Fire-and-Forget”; por ejemplo, las siguientes dos propiedades:

  • Un método OneWay no soporta retornar ningún resultado, siempre será un método void. Tampoco soporta el atributo FaultContract, pues no pueden definirse errores que vaya a devolver al no tener capacidad de devolver nada.
  • Un método OneWay no soporta fluir transacciones entre el cliente y el servidor, y viceversa.

No parece que lo afirmado por los autores del Training Kit encaje mucho con estas propiedades (y otras que se quedan en el tintero) de OneWay. Así que lo mejor es salir de dudas con un pequeño ejemplo, para ver la validez de dicho comentario.

La forma más sencilla de poder comprobarlo es aprovecharse de la forma en que está definido el pipeline de WCF. Son varios los puntos de extensibilidad que tiene este framework. Si colocáramos en uno de ellos una clase que retuviera la entrega del mensaje a un objeto servidor, podríamos comprobar si efectivamente el cliente sigue bloqueado. Para comprobarlo, vamos a usar un Message Inspector.

Inyectar un Message Inspector

Vamos a partir de un nuevo proyecto de librería WCF. La estructura que nos genera Visual Studio nos vale; dejaremos únicamente un método que no devuelva nada, como se ve a continuación.

/// <summary>
/// One Way Service.
/// </summary>
[ServiceContract]
public interface IService
{
    /// <summary>
    /// OneWay method.
    /// </summary>
    /// <param name="value">Value to send.</param>
    [OperationContract(IsOneWay=true)]
    void SendData(int value);
}

La implementación de este contrato de servicio no tiene demasiada importancia, con saber que deberíamos colocar un breakpoint al comienzo del método para saber en qué momento es realmente invocado, es suficiente.

Vamos ahora a empezar a crear el Message Inspector. Crearemos una clase que implemente la interfaz IDispatchMessageInspector e implementaremos uno de sus métodos.

/// <summary>
/// My message inspector class.
/// </summary>
public class MyMessageInspector : IDispatchMessageInspector
{
    public object AfterReceiveRequest(
        ref System.ServiceModel.Channels.Message request, 
        System.ServiceModel.IClientChannel channel, 
        System.ServiceModel.InstanceContext instanceContext)
    {
        // Crear un "replicador" de mensajes y usarlo para obtener una copia del mismo
        MessageBuffer buffer = request.CreateBufferedCopy(int.MaxValue);
        string messageContent = buffer.CreateMessage().GetReaderAtBodyContents().ReadOuterXml();
        System.Diagnostics.Debug.WriteLine(messageContent);
        
        // Asignar una copia sin leer del mensaje en request, para que otros 
        // componentes del pipeline de WCF puedan leerlo sin fallar.
        request = buffer.CreateMessage();

        // Devolver null como resultado, que será lo que reciba el metodo BeforeSendReply
        // en el parámetro correlationState
        return null;
    }

    public void BeforeSendReply(
        ref System.ServiceModel.Channels.Message reply, 
        object correlationState)
    {
    }
}

En cuanto al método AfterReceiveRequest, se ejecuta en el camino de subida del mensaje desde la red hacia el objeto que va a servir esta petición. Queremos mostrarlo en la consola, para lo cual vamos a crear un buffer y generar una copia del mismo. Es importante usar el buffer en primer lugar, puesto que un mensaje no puede leerse dos veces. Si lo hiciéramos con el parámetro request, luego no podría leerse otra vez por otros componentes del pipeline de WC y cada petición fallaría irremediablemente.

En cuanto al método BeforeSendReply, no queremos hacer nada especial con él así que simplemente dejamos pasar el mensaje reply, sin leerlo para no tener el problema antes comentado de lecturas. Como curiosidad, decir que lo que devuelve el método AfterReceiveRequest lo recibe el método BeforeSendReply en su parámetro correlationState, para poder relacionar ambas llamadas entre sí.

Ahora que ya tenemos el Inspector, es el momento de modificar la configuración de WCF para usarlo. Para ello tenemos que crear una clase que modifique el comportamiento del endpoint sobre el que vamos a escuchar. Para ello, tendremos que implementar la interfaz IEndpointBehavior.

/// <summary>
/// Custom endpoint behavior.
/// </summary>
public class MyEndpointBehavior : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, 
        System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, 
        System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
    {
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, 
        System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
    {
        endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MyMessageInspector());
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }
}

De esta interfaz, como se puede observar, sólo nos interesa implementar el método ApplyDispatchBehavior, puesto que nuestro MessageInspector sólo va a actuar en el lado del servidor. Es importante no dejarnos ningún método con su implementación por defecto, puesto que tirar una NotImplementedException en cualquiera de ello tendría consecuencias indeseadas.

Ya sólo nos queda una última clase para tener todo el código listo. Este comportamiento personalizado para el endpoint necesitamos configurarlo de algún modo. La forma más limpia siempre es a través del fichero app.config, pero para ello necesitamos representar este comportamiento personalizable como un elemento de configuración. Esto podemos hacerlo creando una clase que herede de BehaviorExtensionElement, clase que por otra parte cargará nuestro comportamiento custom para el endpoint. El código sería algo así:

/// <summary>
/// Custom behavior extension element for the custom endpoint behavior.
/// </summary>
public class MyBehaviorExtensionElement : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get { return typeof(MyEndpointBehavior); }
    }

    protected override object CreateBehavior()
    {
        return new MyEndpointBehavior();
    }
}

¿Sencillo, verdad? La clase simplemente representa al comportamiento personalizado que deseamos añadir como configuración. ¿Y ahora esto cómo se usaría? Primero se cargaría la clase que acabamos de definir como una extensión de los behaviors, y posteriormente se definiría como nueva configuración para un endpoint. Finalmente se cargaría en el endpoint correspondiente esta nueva configuración. Sin embargo, con las novedades que trae WCF4 en la configuración, este último paso podemos saltárnoslo, sabiendo que a partir de ese momento todos los endpoints van a tener esa extensión cargada.

  <system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="myCustomEndpointBehavior" 
             type="OneWayService.MyBehaviorExtensionElement, OneWayService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>      
    </extensions>
    <behaviors>
      <endpointBehaviors>
        <behavior>
          <myCustomEndpointBehavior/>
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

Como se puede ver, en primer lugar se carga la extensión y, a continuación, se define como parte del comportamiento de los endpoints. Al no darle nombre al endpointBehavior, se cargará para todos los endpoints existentes. Por último, he activado la generación del fichero WSDL para poder generar un proxy.

Probando el inspector

Bien, suponiendo que ya tenemos el servicio arriba y un proyecto de cliente correctamente creado. Tras añadir la referencia al servicio, podemos escribir algo como lo siguiente para ver si realmente nuestro cliente se bloquea o no.

using (ServiceReference.ServiceClient proxy = new ServiceReference.ServiceClient())
{
    Console.WriteLine("Calling the remote server...");
    proxy.SendData(int.MaxValue);
    Console.WriteLine("Remote server called...");
    Console.ReadLine();
}

Con esto, es momento de lanzar el cliente. Deberemos tener breakpoints tanto en el método que hemos implementado del inspector, como en la clase que implementa el servicio. Si todo va bien veremos la siguiente secuencia de pasos:

  1. El cliente invoca al servicio usando el proxy.
  2. Salta el breakpoint en el MessageInspector. Si ejecutamos cualquiera de las instrucciones del inspector, con ejecución paso a paso, veremos que inmediatamente salta el breakpoint del cliente. Por lo tanto, no se está bloqueando.
  3. El servidor se queda esperando a que sigamos la ejecución, para llegar al objeto servidor, donde el otro breakpoint debería saltar.

He probado el mismo código con las mismas condiciones (el servicio desplegado en IIS 7.0), pero con el framework anterior y el resultado es el mismo. Parece, por tanto, que OneWay sí es realmente un patrón “Fire-and-Forget” y no bloquea a los clientes

 

Bibliografía:

Microsoft .NET Framework 3.5 – Windows Communication Foundation. Self-Paced Training Kit

Descargas:

Testeo unitario con Moles

Moles es una nueva virguería de la gente de Microsoft Research; en concreto, del equipo de desarrollo de PEX, una herramienta de generación de tests white-box, para quien no lo conozca.

¿Qué es Moles? Un nuevo framework que permite sustituir cualquier método de .NET por un delegado. ¿Y qué podemos hacer con esto? Pues sustituir cualquier llamada al framework por lo que queramos que se ejecute. Esto, evidentemente, tiene especial utilidad en el testeo unitario, donde necesitamos crear las condiciones adecuadas para ver cómo se comporta nuestro código. Un ejemplo sencillo, expuesto en este vídeo por uno de sus creadores, sería poder “trucar” la hora que nos devuelve DateTime.Now. Como sabemos, no se puede modificar pero… como Moles ¡sí! Si quieres saber cómo, échale un vistazo al vídeo.

Yo voy a proponer un ejemplo distinto: acceder a las appSettings del ConfigurationManager. En el pasado tuve que modificar la signatura de un método que leía, internalmente, una propiedad de appSettings, para poder testearlo. La razón es que no tenía manera de simular las distintas condiciones (ausencia de la clave buscada, valor malformado, etc) puesto que la clase ConfigurationManager no podía mockearse. La solución al final fue pasarle al método la NameValueCollection que contiene las appSettings, pudiendo así simular mis condiciones de testeo.

Pero no mola  tener que modificar un método que ya tienes codificado por culpa de las limitaciones del framework de testeo. Aquí es donde Moles va a echarnos una mano. Veamos paso a paso cómo.

Empecemos con el método que vamos a testear. Digamos que lee una variable de las appSettings y la retorna. Si no existe la clave o tiene un valor vacío, lanza una excepcion. El código podría ser algo así:

/// <summary>
/// Retrieves app setting by its key.
/// </summary>
/// <returns>Value in the settings.</returns>
public static string ExtractAppSetting()
{
    string key = "myKey";
    if (ConfigurationManager.AppSettings.AllKeys.Contains(key) == false ||
        string.IsNullOrEmpty(ConfigurationManager.AppSettings[key]) == true)
    {
        throw new ConfigurationErrorsException("Key not found");
    }

    return ConfigurationManager.AppSettings[key];
}

Fácil, ¿no? El test más típico para este código sería aquél en el que previamente hemos configurado una key en la sección AppSettings del fichero App.config y después intentamos recuperarla mediante el método testeado. Suponiendo la existencia de una setting con key “myKey” y valor “myValue”, el siguiente test nos daría un bonito verde.

/// <summary>
/// Test that checks if the method returns the 
/// appropriate value from AppSettings.
/// </summary>
[TestMethod]
public void ExtractAppSettingValidConfigurationOk()
{
    Assert.AreEqual<string>(
        "myvalue", 
        AppSettingsManager.ExtractAppSetting(), 
        "Invalid value in AppSettings");
}

Bien, ¿y cómo podemos probar ahora el comportamiento de nuestro método si no existiera la key? Quizás, a la desesperada, nos podríamos plantear intentar modificar por código el fichero app.config. Sin embargo, esto no sería una buena idea porque los tests, de tener varios, se lanzan en hilos en paralelo, por lo que podríamos tener resultados inesperados si modificamos este fichero: las clásicas condiciones de carrera.

La solución nos la aporta Moles. En primer lugar, añadimos una referencia al assembly que contiene la clase ConfigurationManager; es System.Configuration. Después, ya podemos añadir el fichero Moles a nuestro proyecto de testeo, como muestra la siguiente figura. El nombre que le daremos debería coincidir con el assembly que vamos a “molear” (¡toma palabro!).

AddingMolesFile

Una vez creado el fichero Moles, ya podemos empezar a usar su “magia” en nuestro código de testeo. Creamos otro test, pero esta vez probará que el método lanza una excepción si las AppSettings están vacías y, por tanto, no contienen la key que estamos buscando. ¿Cómo se haría esto? Sencillo, usando el mole del ConfigurationManager, que nos dará la posibilidad de modificar la colección AppSettings.

/// <summary>
/// Test that checks if the method throws an exception if 
/// the AppSettings is empty.
/// </summary>
[TestMethod]
[ExpectedException(typeof(ConfigurationErrorsException))]
public void ExtractAppSettingEmptySettingsExceptionExpected()
{
    MConfigurationManager.AppSettingsGet = () => new NameValueCollection();
    AppSettingsManager.ExtractAppSetting();
}

Aún nos quedaría un último paso, puesto que si lanzáramos el test ahora mismo nos daría una excepción en tiempo de ejecución indicándonos que tenemos que añadir el siguiente atributo para indicar que el test tiene que ejecutarse “hosteado” por Moles. El test quedaría así finalmente:

/// <summary>
/// Test that checks if the method throws an exception if 
/// the AppSettings is empty.
/// </summary>
[TestMethod]
[HostType("Moles")]
[ExpectedException(typeof(ConfigurationErrorsException))]
public void ExtractAppSettingEmptySettingsExceptionExpected()
{
    MConfigurationManager.AppSettingsGet = () => new NameValueCollection();
    AppSettingsManager.ExtractAppSetting();
}

Y éste sería el resultado:

ExceptionFromMoles

¿Cómo? ¿Una excepción en tiempo de ejecución? Pues sí, resulta que Moles no soporta la modificación del objeto System.Configuration.ConfigurationManager. En este hilo lo explica levemente uno de sus creadores. Así que en lugar de tirar la basura el post, he preferido indicar un ejemplo de su uso, aunque el final haya sido tan trágico como el de Seven.

Otro día prometo postear un resultado correcto. En cualquier caso, Moles sigue siendo una tecnología a seguir, aunque con sus limitaciones.

 

Bibliografía:

Descargas:

WCF4 – Configuración simplificada

Creo que muchos de los que hemos trabajado con Windows Communication Foundation podemos estar de acuerdo en que, en mayor o menor medida, no resulta una tecnología fácil de configurar. Es cierto que aporta una gran flexibilidad en el desarrollo, pero a la hora de hacer el despliegue hay que ser cuidado con el web.config/app.config de turno, si no queremos llorar.

Seguramente esto ya había sido identificado por Microsoft como punto negro del framework, pues muchas de las mejoras que para el lanzamiento de .NET 4 va a haber respecto a WCF, se centran en simplificar dicha configuración, tirando sobretodo de valores por defecto.

En el próximo post vamos a ver las diferencias que habría entre configurar un servicio “típico” de WCF en 3.5 y hacerlo en 4.0.

Hola Mundo en 3.5

Vamos a empezar por hacernos un servicio sencillo con .NET 3.5 y Visual Studio 2008. No me detendré mucho en ello, porque el proceso es bien conocido por todos. Empezamos por el contrato de servicio:

/// <summary>
/// Service that says hello world.
/// </summary>
[ServiceContract]
public interface IHelloWorld
{
    /// <summary>
    /// Gets "Hello Word from X", where X is the name parameter.
    /// </summary>
    /// <param name="name">Who says hello.</param>
    /// <returns>String with hello world message.</returns>
    [OperationContract]
    string GetHelloWord(string name);
}

La implementación para este servicio podría ser algo como lo siguiente:

/// <summary>
/// Implementation of the IHelloWorld service contract.
/// </summary>
public class HelloWorld : IHelloWorld
{
    /// <summary>
    /// Gets "Hello Word from X", where X is the name parameter.
    /// </summary>
    /// <param name="name">Who says hello.</param>
    /// <returns>String with hello world message.</returns>
    public string GetHelloWord(string name)
    {
        return string.Format("Hello world from {0} - Request from {1}", name, OperationContext.Current.Channel.LocalAddress);
    }
}

Por último, lo que realmente nos ocupa en este post, la configuración. Digamos que necesitamos desplegar este servicio de forma que pueda ser consumido mediante un binding lo más interoperable posible (BasicHttpBinding es nuestro hombre) y otro que sea más específico para aplicaciones .NET y que nos dé más rendimiento (vamos a tirar por NetTcpBinding). Además, todavía necesitamos información sobre errores que haya en el servidor, puesto que no está totalmente terminado, y no nos vendría mal un punto MEX para poder crear nuestros proxies.

<system.serviceModel>
  <services>
    <service behaviorConfiguration="HelloWordService.ServiceBehavior"
             name="HelloWordService.HelloWorld">
      <!-- Binding para peticiones HTTP -->
      <endpoint address=""
                binding="basicHttpBinding"
                contract="HelloWordService.IHelloWorld" />
      <!-- Binding para peticiones TCP -->
      <endpoint address=""
                binding="netTcpBinding"
                contract="HelloWordService.IHelloWorld" />
      <!-- Binding para obtener metadatos -->
      <endpoint address="mex"
                binding="mexHttpBinding"
                contract="IMetadataExchange" />
      <host>
        <baseAddresses>
          <add baseAddress="http://localhost:9999/HelloWorldService"/>
          <add baseAddress="net.tcp://localhost:9998/HelloWorldService"/>
        </baseAddresses>
      </host>
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="HelloWordService.ServiceBehavior">
        <!-- Activar la generación de metadatos para el binding MEX -->
        <serviceMetadata httpGetEnabled="True"/>
        <!-- Incluir información detallada para excepciones -->
        <serviceDebug includeExceptionDetailInFaults="False" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

Con esta configuración tendríamos a nuestro servicio escuchando en las dos direcciones base definidas, una para TCP y otra para HTTP. También tendríamos funcionando un endpoint MEX para poder generar nuestros proxies y consultar el fichero WSDL. Este servicio podríamos desplegarlo bien en IIS o bien hostearlo mediante una aplicación de consola. El resultado sería parecido, con la salvedad de que las baseAddress no son tenidas en cuenta por IIS, sino que se toma la ruta en la que se haga el despliegue de la aplicación. Recordad que es necesario configurar los bindings del Site que aloja a la aplicación web para poder tener funcionando el endpoint sobre net.tcp.

Para terminar esta parte, si nos creáramos un cliente de este servicio en su versión hosteada en IIS, en la parte de la configuración podríamos ver un fragmento como éste:

<client>
    <endpoint address=http://localhost/HelloWorldService binding="basicHttpBinding"
        bindingConfiguration="BasicHttpBinding_IHelloWorld" contract="RemoteServices.IHelloWorld"
        name="BasicHttpBinding_IHelloWorld" />
    <endpoint address="net.tcp://localhost/HelloWorldService"
        binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IHelloWorld"
        contract="RemoteServices.IHelloWorld" name="NetTcpBinding_IHelloWorld">
        <identity>
            <userPrincipalName value=j.holguera@xxx.local />
        </identity>
    </endpoint>
</client>

Con estos dos endpoints definidos, podríamos consumir el servicio mediante los dos protocolos (HTTP y TCP), como muestra la siguiente imagen.

using (RemoteServices.HelloWorldClient proxy = new RemoteServices.HelloWorldClient("BasicHttpBinding_IHelloWorld"))
{
    Console.WriteLine("HelloWorld using HTTP: " + proxy.GetHelloWord("Javier"));
}

using (RemoteServices.HelloWorldClient proxy = new RemoteServices.HelloWorldClient("NetTcpBinding_IHelloWorld"))
{
    Console.WriteLine("HelloWorld using TCP: " + proxy.GetHelloWord("Javier"));
}

El resultado sería sendas cadenas de texto en las que, además de aparecer el mensaje de saludo, se nos indica la dirección desde la que se ha recibido la petición en el servicio, útil para ver que efectivamente consumimos la funcionalidad mediante los dos protocolos.

 ConsoleClient35

Como se puede ver, la única parte que realmente existe un buen  puñado de líneas es la configuración del servicio. Veamos cómo quedaría usando WCF 4.

Hola Mundo en WCF 4

Partamos de la base de que hemos creamos un proyecto WCF con Visual Studio 2010 y hemos replicado el servicio anterior, es decir, el contrato de servicio IHelloWorld y su implementación HelloWorld. Sólo nos quedaría, por tanto, configurar el servicio en el fichero app.config. Empezamos definiendo los bindings para HTTP y TCP. La configuración necesaria sería ésta:

<configuration>
</configuration>

¿No hay configuración? ¿Cómo es posible? Pues gracias a los Default Endpoints. Son endpoints “preconfigurados” que se cargan automáticamente para cada dirección base creada. Al llamarse al método Open del ServiceHost, ya sea automáticamente por parte de IIS o manualmente cuando se hostea el servicio en una aplicación de consola, se crean estos bindings predeterminados haciendo uso del método AddDefaultEndpoints. Un par de cuestiones a tener en cuenta:

  • Si se configura un endpoint en el fichero de configuración, ya no se hace efectiva la llamada a AddDefaultEndpoints.
  • Si aún así quisiéramos tener esos endpoints por defecto, siempre es posible llamar explícitamente al método AddDefaultEndpoints y añadir, a los endpoints definidos en el fichero de configuración, los que crea él por defecto.

Los bindings traen una configuración por defecto que se considera como más habitual. Por ejemplo, con una dirección base con protocolo http, el binding por defecto es basicHttpBinding. Esta nueva técnica ha sido bautizada como Default Protocol Mapping. En cualquier caso, este mapeo también es configurable; si quisiéramos que, por defecto, la direcciones con protocolo se resolvieran con un binding de tipo wsHttpBinding, sería necesario configurar lo siguiente en el app.config/web.config:

  <system.serviceModel>
    <protocolMapping>
      <add scheme="http" binding="wsHttpBinding"/>
    </protocolMapping>
  </system.serviceModel>

En este punto, tendríamos nuestro servicio hosteado en IIS. Nos quedaría activar los metadatos del servicio para generar el fichero WSDL y enviar información detallada en caso de error. Ambas no son configuraciones por defecto del servicio, por lo que tendríamos que editar el fichero app.config/web.config e introducir lo siguiente:

  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

Si nos fijamos, veremos que no ha sido necesario ni darle un nombre al nuevo behavior del servicio, ni tampoco definir el servicio y asociarle el behavior, como hacíamos en 3.5. Esto se conoce como Default Behavior Configurations, configuraciones sin nombre que se van a aplicar a cualquier servicio que definamos. Tienen su contrapartida en los Default Binding Configurations, que realizan la misma función pero asociados a un tipo de binding.

 

Como se ha podido ver a lo largo del post, la configuración en WCF4, al menos la más habitual, se ha simplificado enormemente. Los nostálgicos de la sencillez de los servicios ASMX han perdido la principal razón para no dar el paso a WCF.

 

Bibliografía:

A Developer’s Introduction to Windows Communication Foundation 4

Descargas:

Instalar Windows 7 en Windows Virtual PC

Después de buscar un rato en Google Bing, no he visto ningún post sobre cómo instalar Windows 7 en una máquina con Windows Virtual PC. Puede resultar contradictorio puesto que Windows Virtual PC sólo funciona en Windows 7, pero ¿a quién no le ha hecho falta una máquina virtual con la que trastear? Prácticamente para eso se crearon las máquinas virtuales, ¿no? 😉

Pues bien, yo necesito una máquina con Windows 7 para instalar sobre ella Visual Studio 2010. Esto es lo que he hecho para tenerla.

Descarga e instalación

La descarga la he hecho de esta dirección: http://www.microsoft.com/windows/virtual-pc/. De los dos ficheros que indicaba que era necesario descargar, sólo lo he hecho con “Windows Virtual PC”, puesto que no me iba a hacer falta el “Windows XP Mode”.

Una vez bajado, la instalación es un simple doble click –> next –> next. Eso sí, es necesario reiniciar y tanto al apagarse como al volverse a arrancar, está un tiempo configurando esta nueva instalación.

Al volver a arrancar, podremos ver en el menú de inicio un nuevo enlace de nombre “Windows Virtual PC”. Pulsarlo nos llevará una ventana como la que aparece en la siguiente imagen.

VirtualMachinesWindow

Nueva máquina virtual

Ya tenemos la aplicación instalada, es momento de crear la primera máquina virtual. El proceso no puede ser más sencillo. Lo primero es pulsar el botón “Create Virtual Machine”, que aparece en la ventana anterior. Esto nos lanzará un cuadro de diálogo para crear una nueva máquina virtual, que comienza con darnos la opción de definir el nombre de esta nueva máquina, como se muestra en la siguiente imagen.

VirtualMachineName

El siguiente paso es indicar la cantidad de memoria RAM que queremos que tenga disponible la máquina virtual. Mucho ojo con esto porque, en el momento de lanzar la máquina virtual, tendrá que estar libre dicha cantidad de memoria RAM, pues de lo contrario no arrancará la máquina. La siguiente imagen muestra este paso en el cuadro de diálogo.

VirtualMachineMemory

Por último, debemos indicar qué clase de disco duro vamos a manejar. Las opciones principales son crear uno nuevo o elegir uno ya existente, en caso de que, por ejemplo, estuviéramos creando esta máquina virtual a partir de una ya existente. Elegimos la primera opción como se muestra en la siguiente figura.

VirtualMachineHardDisk

Con esto ya tendríamos terminada nueva máquina virtual. Para empezar con la instalación de Windows 7, bastaría con introducir en el DVD de instalación, volver a la ventana de Windows Virtual PC, seleccionar la nueva máquina “Windows7” y pulsar el botón “Open”. De este modo la máquina empieza a ejecutarse y, como cualquier máquina, arrancaría con el DVD la instalación. El final del proceso sería la pantalla con la que Windows 7 nos da la bienvenida para empezar la instalación, seleccionando la configuración de idioma como muestra la siguiente imagen.

Windows7InVirtualPC

El resto es next-> next-> next, puesto que para Windows 7 la instalación se está haciendo sobre una máquina real y los pasos a seguir son los mismos.

Para terminar, es muy recomendable instalar los Integration Components. Se puede hacer desde el menú Tools que aparece en la propia ventana de la máquina virtual y nos permitirá, entre otras cosas, tener sonido, acceso a USBs y, lo que me parece más importante, poder sacar el ratón de dentro de la máquina virtual sin tener que recurrir a la combinación ALT+CONTROL+RIGHT, lo que aumenta considerablemente la sensación de integración.

Debugger in Silverlight

Este fin de semana perdí una cantidad considerable de tiempo porque no me funcionaba el debugger en Silverlight. Básicamente, me salían esos simpáticos mensajes que aparece en Visual Studio cuando un breakpoint no se va a ejecutar:

The break point currently will not be hit , no symbols have been loaded to this document

¿Por qué? ¡¿Por qué?! Me preguntaba yo, intentando depurar una aplicación que estaba comportándose de forma incorrecta. Pues bien, la solución es tan sencilla, de esas que por suerte no se olvidan. Basta con activarlo dirigiéndose a las propiedades del proyecto ASP.NET que estará hosteando nuestro control Silverlight.

En las propiedades, bajo la pestaña Web, en la parte inferior de la pantalla se puede ver un menú como el de la siguiente imagen. Basta activar el checkbox correspondiente a Silverlight para que nuestro depurador favorito funcione de nuevo correctamente.

SilverlightDebugger

Espero que esto le sirva a alguien para perder menos tiempo del que perdí yo.

Code Coverage (II): Uso

Una vez que tenemos activado el Code Coverage para un ensamblado y tras lanzar los correspondientes tests, ver los resultados es una mera cuestión de abrir la pestaña de “Code Coverage Results”, que si no es visible se puede activar en el menú “Test – Windows – Code Coverage Results”.

CodeCoverageResults

En la ventana de ejemplo se puede ver cómo los resultados se gestionan, en primer lugar, por ensamblado ; y dentro del ensamblado , cómo se pueden ir expandiendo los diferentes espacios de nombres, clases y, por último, métodos. Sin duda, una gran granularidad que nos permite saber con precisión dónde estamos quedándonos cortos en el testeo.

Pero, si esta ayuda es buena, tener el código coloreado indicando qué partes han sido ejecutadas durante el testeo y cuáles no, sería aún mejor, ¿verdad? Basta con pulsar el botón “Show Code Coverage Coloring”, el tercero por la derecha en la ventana de “Code Coverage Results”, para obtener algo como la siguiente imagen.

ColoredCode

¡Genial! Ahora no sólo sé la cantidad de código que me falta por testear, sino que incluso sé qué para qué código necesito escribir nuevos tests. Vale, esto se salta totalmente los principios de TDD, pero la vida no siempre es maravillosa…

Un último apunte para terminar: he comprobado (en mis carnes) que algunos proyectos dan problemas al intentar configurar los ensamblados a los que se les va a calcular la cobertura de código.

Los síntomas son sencillos: abrimos el .testrunconfig, pinchamos en “Code Coverage”, y la ventana se cierra sin emitir ningún error. No soy el primero al que le pasa y aunque está reportado como bug, aparentemente sigue sin solucionarse.

La forma de evitarlo es tan chapucera como útil: simplemente hay que hacer el unload de algunos proyectos que son los que dan problemas. A mí, personalmente, me ha ocurrido exclusivamente con los proyectos de base de datos, pero parece ser que también existen problemas con proyectos WSSF. Avisados quedáis.