Wilfried Woivré & .Net

Utilisation avancée de Windows Azure Table Storage

SEPT11

Le Table Storage est selon moi l’un des composants essentiels de tout bon projet fonctionnant sur Windows Azure, en effet il permet pour un faible cout de stocker des données  utilisable par la suite au sein de votre application. De plus celui-ci est hautement scalable, il est donc parfait pour une architecture de type Cloud !

A part les cas d’usages standards du Table Storage, c’est à dire du CRUD pur et dur, il est possible de modifier son comportement de façon à nous aider dans nos différentes actions, prenons le cas simple, je veux que pour chaque entité que je sauvegarde dans mon Table Storage soit indiqué une date de création, et une date de modification, cependant je ne veux pas que ces dernières viennent polluer mon coder dans chacune de mes classes.

 

On va donc commencer par créer une entité basique pour la démonstration :

public class MyEntity : TableServiceEntity
{
}

Cette classe a donc 3 propriétés qui sont PartitionKey, RowKey et TimeStamp, or je voudrais y ajouter des propriétés pour savoir la date de création de modification de chaque ligne ce qui peut mettre utile lorsque je consulte les données via des outils tels que Cloud Storage Studio ou Azure Storage Explorer.

Pour réaliser cela, il vous faut réaliser l’opération suivante :

private static XNamespace _atomNs = "http://www.w3.org/2005/Atom";
private static XNamespace _dataNs = "http://schemas.microsoft.com/ado/2007/08/dataservices";
private static XNamespace _metadataNs = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";

public MyServiceContext(CloudStorageAccount storageAccount)
    : base(storageAccount.TableEndpoint.ToString(), storageAccount.Credentials)
{
    this.IgnoreMissingProperties = true;
    this.WritingEntity += GenericServiceContext_WritingEntity;

    var tableClient = storageAccount.CreateCloudTableClient();

    tableClient.CreateTableIfNotExist("TestTable");
}

private void GenericServiceContext_WritingEntity(object sender, System.Data.Services.Client.ReadingWritingEntityEventArgs e)
{
    MyEntity entity = e.Entity as MyEntity;

    if (entity == null)
    {
        return;
    }

    XElement properties = e.Data.Descendants(_metadataNs + "properties").First();

    XElement id = e.Data.Descendants(_atomNs + "id").First();
    if (String.IsNullOrWhiteSpace(id.Value))
    {
        var creationProperty = new XElement(_dataNs + "CreationDate", DateTime.Now);
        creationProperty.Add(new XAttribute(_metadataNs + "type", "Edm.DateTime"));
        properties.Add(creationProperty);
    }

    var modificationProperty = new XElement(_dataNs + "ModificationDate", DateTime.Now);
    modificationProperty.Add(new XAttribute(_metadataNs + "type", "Edm.DateTime"));
    properties.Add(modificationProperty);
}

 

Il vous faut avant tout dire que les propriétés manquantes de votre ServiceContext sont ignorées que ce soit lors de la lecture ou de l’écriture. Il faut par la suite s’abonner à l’évènement Writing Entity afin de modifier le XML envoyé à votre storage.

Ainsi on va pouvoir passer de ce XML :

<entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
       xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
       xmlns="http://www.w3.org/2005/Atom">
  <title />
  <author>
    <name />
  </author>
  <updated>2012-09-10T13:08:42.4981763Z</updated>
  <id />
  <content type="application/xml">
    <m:properties>
      <d:PartitionKey>42</d:PartitionKey>
      <d:RowKey>106564e8-5093-4c5b-b059-e02ac75a59d4</d:RowKey>
      <d:Timestamp m:type="Edm.DateTime">2012-09-10T15:08:42.4942455+02:00</d:Timestamp>
    </m:properties>
  </content>
</entry>

A un XML de ce type :

<entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
       xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
       xmlns="http://www.w3.org/2005/Atom">
  <title />
  <author>
    <name />
  </author>
  <updated>2012-09-10T13:08:42.4981763Z</updated>
  <id />
  <content type="application/xml">
    <m:properties>
      <d:PartitionKey>42</d:PartitionKey>
      <d:RowKey>106564e8-5093-4c5b-b059-e02ac75a59d4</d:RowKey>
      <d:Timestamp m:type="Edm.DateTime">2012-09-10T15:08:42.4942455+02:00</d:Timestamp>
      <d:CreationDate m:type="Edm.DateTime">2012-09-10T15:09:28.6219389+02:00</d:CreationDate>
      <d:ModificationDate m:type="Edm.DateTime">2012-09-10T15:09:28.6219389+02:00</d:ModificationDate>
    </m:properties>
  </content>
</entry>

 

Bien entendu, si vous vouliez réellement mettre en place cette solution, il faut avant tout vérifier que les propriétés que vous ajoutez n’existent pas déjà.

On notera par ailleurs, qu’il n’y a pas de gestion de statut dans le XML, donc pour savoir si c’est une entité que l’on créé où une entité que l’on modifie, il faut se baser sur la présence ou non de la valeur du champ id.

On peut donc voir le résultat ci-dessous au sein de mon table storage, je ne modifie que la dernière ligne dans mon application de démonstration :

image_thumb1

 

Maintenant, passons à un mode lecture avancé. Prenons le cas suivant, on vous donne un Table Storage à lire et explorer sans utiliser d’outils tierces, ni même Visual Studio et son explorateur de Table Storage ! Bref la galère à première vue …. Bon en même temps, c’est pas le scénario qui arrive tous les jours.

 

On va donc commencer par créer une entité plus complexe que la précédente qui va contenir un Tuple permettant de stocker nos différentes propriétés :

public class ExtractEntity
{
    private List<Tuple<string, object, object>> _properties = new List<Tuple<string, object, object>>();
    public List<Tuple<string, object, object>> Properties
    {
        get
        {
            return _properties;
        }
        set
        {
            _properties = value;
        }
    }
}

On notera au passage, que mon entité n’hérite pas de TableServiceEntity, et donc il est possible de créer son propre système de wrapping pour certaines entités.

Au niveau du code, il est possible de faire comme ci-dessous, c’est à dire s’abonner à l’évènement ReadingEntity et de modifier le XML d’entrée

public MyServiceContext(CloudStorageAccount storageAccount)
    : base(storageAccount.TableEndpoint.ToString(), storageAccount.Credentials)
{
    this.IgnoreMissingProperties = true;
    this.WritingEntity += GenericServiceContext_WritingEntity;
    this.ReadingEntity += GenericServiceContext_ReadingEntity;

    var tableClient = storageAccount.CreateCloudTableClient();

    tableClient.CreateTableIfNotExist("TestTable");
}

private void GenericServiceContext_ReadingEntity(object sender, System.Data.Services.Client.ReadingWritingEntityEventArgs e)
{
    ExtractEntity entity = e.Entity as ExtractEntity;
    if (entity == null)
    {
        return;
    }

    var q = from p in e.Data.Element(_atomNs + "content")
                            .Element(_metadataNs + "properties")
                            .Elements()
            select new
            {
                Name = p.Name.LocalName,
                IsNull = string.Equals("true", p.Attribute(_dataNs + "null") == null
                        ? null
                        : p.Attribute(_metadataNs + "null").Value, StringComparison.OrdinalIgnoreCase),
                TypeName = p.Attribute(_dataNs + "type") == null ? null : p.Attribute(_metadataNs + "type").Value,
                p.Value
            };

    foreach (var dp in q)
    {
        entity.Properties.Add(new Tuple<string, object, object>(dp.Name, dp.TypeName ?? "Edm.String", dp.Value));
    }
}

 

Le XML que l’on récupère en entrée, ressemble à celui ci-dessous, il est donc possible de retrouver l’ensemble des informations nécessaires pour connaitre l’entité et sa table d’origine

<entry m:etag="W/&quot;datetime'2012-09-10T13%3A34%3A20.327Z'&quot;"
       xmlns="http://www.w3.org/2005/Atom"
       xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
  <id>http://127.0.0.1:10002/devstoreaccount1/TestTable(PartitionKey='42',RowKey='365aa1c5-ac6b-42ea-b674-749dfdc7b514')</id>
  <title type="text"></title>
  <updated>2012-09-10T14:52:49Z</updated>
  <author>
    <name />
  </author>
  <link rel="edit" title="TestTable" href="TestTable(PartitionKey='42',RowKey='365aa1c5-ac6b-42ea-b674-749dfdc7b514')" />
  <category term="devstoreaccount1.TestTable" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  <content type="application/xml">
    <m:properties>
      <d:PartitionKey xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">42</d:PartitionKey>
      <d:RowKey xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">365aa1c5-ac6b-42ea-b674-749dfdc7b514</d:RowKey>
      <d:Timestamp m:type="Edm.DateTime" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">2012-09-10T13:34:20.327Z</d:Timestamp>
      <d:CreationDate m:type="Edm.DateTime" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">2012-09-07T11:54:29Z</d:CreationDate>
      <d:ModificationDate m:type="Edm.DateTime" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">2012-09-07T11:54:29Z</d:ModificationDate>
    </m:properties>
  </content>
</entry>

 

On va donc se retrouver avec une entité comprenant toutes les propriétés de notre table, comme on peut le voir ci-dessous :

image_thumb4

 

Voilà en espérant que ça puisse vous donner quelques idées pour vos développements futurs ou passés !

Remonter

Créer des urls propres pour vos sites Web

AVRI29

Si comme moi, vous aimez voir des urls lisibles quand vous naviguez sur des sites, vous vous êtes déjà penchez sur la question sur comment formater votre url, afin qu’elle soit facilement lisible pour vos utilisateurs, et pour les moteurs de recherche.

 

Alors comme vous devez le savoir il y a la méthode UrlEncode disponible dans le Framework, mais personnellement je n’aime pas trop cette méthode, puisqu’elle vous traduit les caractères spéciaux en caractères encodés, ce que je ne trouve pas très lisible.

L’autre solution est de remplacer tous les caractères spéciaux par un autre caractère associé, en créant 2 listes, et en réalisant un mapping, mais il suffit que vous oubliez un caractère spécial, et c’est le drame !

 

La dernière solution, c’est de réaliser une normalisation de votre chaîne de caractères, et de garder uniquement les caractères qui vous intéressent, voici donc la méthode que j’utilise pour mon blog :

 

public static string GenerateShortName(this string valueToConvert)
{
    StringBuilder sb = new StringBuilder();
    string st = valueToConvert.ToLower().Normalize(NormalizationForm.FormD);

    foreach (char t in st)
    {
        System.Globalization.UnicodeCategory uc = System.Globalization.CharUnicodeInfo.GetUnicodeCategory(t);
        switch (uc)
        {
            case System.Globalization.UnicodeCategory.LowercaseLetter:
            case System.Globalization.UnicodeCategory.DecimalDigitNumber:
                sb.Append(t);
                break;
            case System.Globalization.UnicodeCategory.SpaceSeparator:
                if (sb.ToString().LastOrDefault() != '-')
                    sb.Append('-');
                break;
            default:
                break;
        }
    }

    string value = sb.ToString().Normalize(NormalizationForm.FormC);

    return value;
}

Alors si on prend comme exemple le texte suivant : “Unity : Gestion des paramètres primitifs”, regardons ce que ça donne lorsqu’on l’encode, où que l’on utilise cette méthode.

image

A mon sens, le résultat est sans appel, mais je ne suis pas  à 100% objectif !

Et voilà, même près tout ce temps, le Framework .Net peut vous apprendre énormément de choses !

Remonter

Unity : Gestion des paramètres primitifs

AVRI28

On a vu précédemment qu’avec de l’IoC comme Unity, il est facilement possible d’instancier des objets avec des paramètres complexes qui ont été au préalable enregistrés dans Unity. Cependant, il est aussi possible de résoudre des objets qui contiennent des types primitifs

Pour un cas concret, prenons ce code qui contient des paramètres optionnels.

public class MyClass : IMyInterface
{
    public MyClass(string param1, int param2, string param3 = "test", int param4 = 42)
    {
        Console.WriteLine("Params : {0}, {1}, {2}, {3}", param1, param2, param3, param4);
    }
}

Si l’on souhaite résoudre via Unity cette classe, il nous faut donc définir les paramètres, pour cela nous allons utiliser la classe ParametersOverride pour passer nos paramètres lors de la résolution de notre classe.

UnityRoot.Container.Resolve<IMyInterface>(new ParameterOverrides()
                                              {
                                                  {"param1", "val"},
                                                  {"param2", 3},
                                                  {"param3", "toto"},
                                                  {"param4", 12}
                                              });

 

Ainsi lors de la résolution de notre container, nous aurons bien la valeur de nos 4 paramètres.

image

 

Alors, comme vous avez pu le voir, dans mon exemple j’ai indiqué des paramètres optionnels, que j’ai néanmoins voulu résoudre lors de l’instanciation de ma classe. Parce qu’en effet à ce jour, Unity ne gère pas les paramètres optionnels, c’est à dire que si je souhaite résoudre ma classe de cette façon :

UnityRoot.Container.Resolve<IMyInterface>(new ParameterOverrides()
                                              {
                                                  {"param1", "val"},
                                                  {"param2", 3}
                                              });

Il se produira une erreur de type Microsoft.Practices.Unity.ResolutionFailedException car celui-ci n’arrive pas à résoudre les paramètres optionnels, comme on peut le voir ci-dessous

image

 

Donc mon conseil, soit vous passez par des propriétés que vous injectez, ce qui vous évite d’avoir des paramètres optionnels que vous devez absolument saisir. Soit vous pouvez définir tous vos paramètres de façon non optionnel.

Remonter

Utiliser RIA Services & le Table Storage d’Azure

MAI29

Lors d’un Azure Camp organisé par ZeCloud, j’ai montré comment exposer le Table Storage de Windows Azure via un WCF Data Services, cela nous permettait d’avoir une exposition de nos données via OData. Vous pouvez retrouver la démonstration sur le codeplex de ZeCloud, et me demander plus d’infos au prochain Azure Camp

 

Dans la même idée, je me suis aperçu que la dernière version de RIA Services proposait quelque chose du même genre, via son toolkit, on va donc voir comment le mettre en place !

 

Commençons déjà par créer un projet de type Cloud, ainsi qu’une application Silverlight avec un site web et WCF RIA Services. Il nous faut ensuite ajouter les références, par NuGet c’est plus facile

image

 

 

Maintenant, il nous faut créer notre Model, pour cela, on va prendre un cas très simple :

 

public class Person : TableEntity
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    /// <summary>
    /// The property is set to be mentioned explicitly in the DataForm ...
    /// ONLY FOR THE DEMO
    /// </summary>
    public string MyPartitionKey
    {
        get
        {
            return base.PartitionKey;
        }
        set
        {
            base.PartitionKey = value;
        }
    }
}

On peut voir déjà quelques différences, premièrement on n’hérite pas de TableStorageEntity, mais de TableEntity qui hérite lui même de TableServiceEntity, et la deuxième c’est que pour le cas de la démo, j’ai voulu tester plusieurs PartitionKey, j’ai donc réexposé via une autre propriété celle ci afin qu’elle apparaisse dans mon DataForm Silverlight.

 

Maintenant, voyons notre contexte de données pour notre Table Storage

public class AzureServiceContext : TableEntityContext
{      
    public AzureServiceContext() : 
        base(RoleEnvironment
            .GetConfigurationSettingValue("Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString"))
    {
    }

    public TableEntitySet<Person> People
    {
        get { return base.GetEntitySet<Person>(); }
    }
}

 

Donc de même ici, on peut voir quelques différences, déjà au niveau de l’héritage, ici on hérite de TableEntityContext qui hérite lui même de TableServiceEntity.

De plus, on peut voir que l’on ne gère pas non plus la création des tables dans le Table Storage, vu que le toolkit de RIA Services s’en occupe pour nous.

Il ne vous reste plus qu’à créer votre Domain Service de façon classique, il faut juste renseigner aucun contexte.

image

 

Maintenant, implémentons notre DomainService

[EnableClientAccess()]
public class TSDomainService : TableDomainService<AzureServiceContext>
{

    protected override string PartitionKey
    {
        get
        {
            return null;
        }
    }

    public IQueryable<Person> GetPeople()
    {
        return EntityContext.People;
    }

    public void AddPerson(Person person)
    {
        EntityContext.People.Add(person);
    }

    public void DeletePerson(Person person)
    {
        EntityContext.People.Delete(person);
    }

    public void UpdatePerson(Person person)
    {
        EntityContext.People.Update(person);
    }
}

On a dorénavant la possibilité de faire un TableDomainService pour englober notre contexte Azure, de même les méthodes standards de CRUD sont facilitées.

Voyons maintenant la PartitionKey, par défaut  le toolkit met la PartitionKey à la valeur du nom du Domain Service, pour éviter qu’elle soit définit ainsi, il suffit de surcharger la PartitionKey, cependant cela veut dire qu’il vous faudra la spécifier à chaque fois, ce qui est mieux si vous voulez une bonne structure de donnée dans votre Table Storage

 

Et voilà le résultat dans un DataForm Silverlight

image

 

Vous pouvez retrouver les sources de la solution ici

Remonter

WPF : Utiliser plusieurs templates dans un ItemControls

JANV14

Dans certains cas d’utilisation, il peut vous arriver de vouloir utiliser différents templates dans un ItemControls tel qu’une listbox. Prenons par exemple, un cas concret, soit twitter, si vous voulez réaliser un client WPF pour ce célèbre réseau sociaux, vous pouvez avoir envie de définir différents templates pour les messages que vous avez envoyé, ou ceux que vous recevez (comme on peut le voir dans de nombreux clients).

Le soucis c’est que pour réaliser cela, on voudrait afficher une listbox avec les dernières activités, et donc mixer les templates dans cet ItemControl. Ce qu’on peut faire c’est donc d’utiliser des DataTemplate que l’on va typer selon l’objet. Comment cela marche.

Prenons un cas plus simple d’un model objet de ce type :

image

On a donc une classe Figure, dont tous nos éléments que l’on souhaite afficher vont dériver de cette classe.

Après il nous suffit de déclarer en XAML nos différents DataTemplate en ressources de cette façon :

    <Window.Resources>
        <DataTemplate DataType="{x:Type Model:Rectangle}">
            <Rectangle Height="20" Width="60" Fill="Blue" Margin="2" />
        </DataTemplate>
        <DataTemplate DataType="{x:Type Model:Ellipse}">
            <Ellipse Height="20" Width="40" Fill="Red" Margin="2" />
        </DataTemplate>
    </Window.Resources>

On va donc typer nos différents DataTemplate grâce à la propriété DataType. Il nous suffit donc de créer notre jeu de données de façon on ne peut plus classique.

private void InitData()
{
    var figures = new List<Model.Figure>();
    figures.Add(new Model.Rectangle());
    figures.Add(new Model.Rectangle());
    figures.Add(new Model.Ellipse());
    figures.Add(new Model.Ellipse());
    figures.Add(new Model.Rectangle());
    figures.Add(new Model.Ellipse());

    this.DataContext = figures;
}

Et ensuite de les lier à notre ItemControls de la même façon :

<ListBox ItemsSource="{Binding}" />

Et voilà, ces quelques lignes vous donnent le rendu souhaité

image

 

Tout le code est dans l’article, donc pas besoin que je fournisse le projet de démo!

Merci à Pascal Louveau de m’avoir posé la question.

Remonter