Archive for August 26, 2009
Stream Transformations
Recuerdo que una de las primeras cosas que solíamos aprender con un lenguaje o framework era el cómo leer/escribir de o hacia un archivo (o por lo menos antes así era). En la .Net Framework tenemos todo un namespace dedicado a las operaciones de E/S de datos e información (System.IO). Una de las clases básicas en este namespace es la clase abstracta Stream. Cómo en la mayoría de lenguajes y frameworks modernas,Stream encapsula o abstrae las operaciones de E/S hacia diversos orígenes y/o destinos, de esa manera tenemos cosas como Network Streams, File Streams, Memory Streams, etc…
Algo de lo cual no muchos nos habíamos percatado es que las clases derivadas de Stream no solamente abstraen operaciones de E/S, sino que también pueden ser usadas para “transformar” datos sobre la marcha, de esa manera tenemos ya en la .Net framework clases Stream especiales como GzipStream(Compresión/Descompresión), CryptoStream (Encriptación) y me imagino que hay muchas más en la framework. Hace ya varios meses leí en la MSDN Magazine (una de mis magazines favoritas por cierto, les recomiendo a todos suscribirse
) un excelente artículo sobre Testing Transformation Streams (TS de aquí en adelante) y en general de que se trataba una TS y como crearla/utilizarla. Un ejemplo típico de una transformation Stream es una que elimine los espacios en una Stream básica, por ejemplo:
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace FilterSpacesStream
{
public class NoSpaceStream : Stream
{
public NoSpaceStream(Stream sink)
{
Sink = sink;
}
private Stream Sink { get; set; }
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return Sink.CanSeek; }
}
public override bool CanWrite
{
get { return true; }
}
public override long Length
{
get { return Sink.Length; }
}
public override long Position
{
get { return Sink.Position; }
set { Sink.Position = value; }
}
public override void Flush()
{
Sink.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
return Sink.Seek(offset, origin);
}
public override void SetLength(long value)
{
Sink.SetLength(value);
}
public override int Read(byte[] buffer, int offset, int count)
{
return Sink.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count)
{
string text = Encoding.UTF8.GetString(buffer);
string result = Regex.Replace(text, @"s", string.Empty);
byte[] data = Encoding.UTF8.GetBytes(result);
Sink.Write(data, 0, data.Length);
}
}
}
Y su respectivo Fact Fixture:
using System.IO;
using System.Text;
using Xunit;
namespace FilterSpacesStream
{
public class NoSpaceStreamFacts
{
private readonly MemoryStream output = new MemoryStream();
private readonly NoSpaceStream sut;
public NoSpaceStreamFacts()
{
sut = new NoSpaceStream(output);
}
[Fact]
public void It_can_read()
{
Assert.Equal(output.CanRead, sut.CanRead);
}
[Fact]
public void It_can_write()
{
Assert.True(sut.CanWrite);
}
[Fact]
public void It_can_Seek()
{
Assert.Equal(output.CanSeek, sut.CanSeek);
}
// And many other operations I really don't want to test...
[Fact]
public void When_write_a_string_with_spaces_it_returns_a_string_without_spaces()
{
string text = "This is a test ";
byte[] buffer = Encoding.UTF8.GetBytes(text);
sut.Write(buffer, 0, buffer.Length);
sut.Flush();
buffer = output.ToArray();
string converted = Encoding.UTF8.GetString(buffer);
Assert.Equal("Thisisatest", converted);
}
}
}
Uno de los usos básicos que podríamos darle a las Transformation Streams es crear una que sobre la marcha me comprima/cambie/elimine datos en la Render Stream utilizada por la ASP.NET Framework, ven ahora hacia dónde estoy orientando este post? (Rayos, creo que pensé en “voz” alta
)
Bueno, hasta la siguiente entrega!
Resolviendo problemas con tipos casi parecidos
Hace unos días un correo de la lista de correos del grupo de desarrollo .Net local preguntó algo interesante:
bien pues estoy transmitiendo documentos dentro de una clase que contiene una seccion de parametros y otra seccion de datos. La seccion de datos es variable en el tiempo, el asunto es que la seccion de datos en funcion de lo que viene es diferentes las validaciones.
La pregunta me pareció sumamente interesante, ya que muchos nos topamos con problemas similares a diario, ¿cómo lidear con tipos de datos “parecidos”? La respuesta es simple, separar la parte de la lógica que varía del resto de la lógica que no lo hace, en este caso en particular se trata no de datos, si no de la validación del conjunto de datos. Realmente creo que hay varias forma de resolver esto, pero me inclino por la más sencilla, usando un validator que obedezca a una interfaz común.
Conversando con el autor del correo, me aclara que por lo general los datos suelen enviarse de forma de texto, ya sea un XML o una larga String, y luego dependiendo del tipo de documento este debe validarse contra el contenido. En este caso es preciso hacer una diferencia entre el dato transmitido por el servicio y la lógica contenida en el servicio. Qué tal si comenzamos abstrayendo el DTO? (Data Transfer Object).
public class DocumentDto {
public int Id { get; set; }
public DocumentType Type { get; set; }
public string Data { get; set; }
}
Imaginemos que este es usado a su vez por la signature de un servicio de esta manera
public bool RegisterDocument(DocumentDto document) {
}
Digamos que la responsabilidad de validar el documento en este caso “en particular” no le pertenece al servicio ni al DTO, sino a una clase aledaña llamada “Validator” que varía según el tipo de documento, esto nos permite encapsular esas reglas de validación en alguien con el que luego cooperaremos, definamos la interface de esta simple manera
public interface IDocumentValidator {
public bool IsValid(DocumentDto document);
}
public class InvoiceValidator: IDocumentValidator {
public bool IsValid(DocumentDto document) {
// La lógica de la validación iría aquí
return true;
}
}
public class CheckValidator: IDocumentValidator {
public bool IsValid(DocumentDto document) {
return true;
}
}
Luego es cuestión de simple uso, así que nuestro Método quedaría algo así de simple:
public bool RegisterDocument(DocumentDto document) {
IDocumentValidator validator;
if (document.Type == DocumentType.Invoice) {
validator = new InvoiceValidator();
} else {
validator = new CheckValidator();
}
var isValid = validator.IsValid(document);
// El resto de la operación va aquí;
return isValid;
}
Como toque final, detesto los “if’s” en lógica anidada de este tipo, así que haré un pequeño cambio utilizando un DocumentValidatorFactory:
public bool RegisterDocument(DocumentDto document) {
var validator = DocumentValidatorFactory.Create(document.Type);
var isValid = validator.IsValid(document);
// El resto de la operación va aquí;
return isValid;
}
Encapsulando la toma de desición de la creación del validador (dependiendo del tipo de documento) a otro objeto. Les dejo como ejercicio la forma en que podría verse o quedar el DocumentValidatorFactory.
NOTA: Se que hay muchas formas diferentes de resolver este problema en particular, de hecho, me gustaría escuchar que otras maneras se les ocurre.
NOTA 2: Si necesitan más información acerca de la Factory Method Pattern pueden buscarlo en la simple guía de Head First Design Patterns
Disminuyendo el ViewState, Server Side State con Session
Creo que para estas alturas más de alguien tiene que haber pensado algo como: “Hey, si puedo controlar donde guardar el ViewState, porqué en vez de ‘comprimirlo’ y enviarlo al cliente no lo ‘guardo’ en el servidor?”
Pues claro que se puede, en la .Net Framework 2.0 se agregó una propiedad más a la System.Web.UI.Pagellamado PageStatePersister que retorna un objeto de tipo PageStatePersister, este se utiliza en la ejecución de la cascada del runtime de asp.net para retornar el objeto encargado de persistir el ViewState (o sea, guardarlo o retraerlo). Cómo los desarrolladores de ASP.net siempren andan buscando un “shortcut” y a más de alguien se le debe haber ocurrido guardar el ViewState en la sesión del usuario (que por defecto se guarda en la memoria del AppPool en el servidor) existe ya un PagePersister conocido como SessionPageStatePersister, para usarlo basta con hacer lo siguiente:
using System;
using System.Web.UI;
namespace WebApplication8
{
public partial class _Default : Page
{
protected override PageStatePersister PageStatePersister
{
get { return new SessionPageStatePersister(this); }
}
protected void Page_Load(object sender, EventArgs e)
{
}
}
}
Listo, ahora nuestro ViewState no existe en la página sino en la sesión del usuario donde quiera que esta se este guardando (recuerdan? InProc? Session Server?). Hay que tener cuidado porque por defecto la sesión tiene una duración de 20 minutos y si se recicla el Application Pool no solo perdemos los datos de la sesión, sino también el ViewState. Otro problema es que el SessionPageStatePersister guarda una cantidad “fija” de elementos dentro del historial de la sesión, por defecto es de 9 items dentro del historial, esto lo podemos cambiar en el Web.config con la entrada sessionPageState
<system.web>
<!-- otros elementos del system.web -->
<sessionPageState historySize="20" />
</system.web>
Hey, un momento, eso indica que puedo usar el SessionStatePersister para crear mi propio storage server side del ViewState…… YEAH!!! exacto!!! pero eso será algo que no tocaré en esta serie de artículos, o quizás si, pero después
Saludos!
