Bon je continue mon petit bonhomme de chemin. Aujourd’hui je vais tacher de rendre ma liste de groupe un peu plus sexy en lui rajoutant des informations intéressantes.

  1. le nom du groupe dans lequel je me trouve
  2. le chemin du groupe dans lequel je me trouve

pour le point 1 pas de gros souci, mais cela va me permettre de détailler un peu le fonctionnement d’accès aux données que j’ai changer depuis mon dernier billet. Maintenant mon contrôleur n’accède plus directement au repository mais plutôt à une couche de service. cela me permet d’écrire le code suivant dans mon contrôleur.

// GET: /Localisation/ListGroup/id
public ActionResult ListGroup(int? ParentGroupId)
{
    Localisation localisation = CreateLocalisation();
    localisation.Groups = servicelocalisation.GetLocalisationGroups(ParentGroupId);
    if (ParentGroupId.HasValue)
    {
        localisation.CurrentGroup = servicelocalisation.GetLocalisationGroup(ParentGroupId.Value);
        localisation.GroupParents = servicelocalisation.GetLocalisationGroupParents(ParentGroupId.Value);
    }
    return View("ListGroup", localisation);
}

localisation.CurrentGroup va contenir le groupe parent de ma liste, celui dont j’explore les groupes enfants. Et pour le récupérer je vais faire un appel à la fonction GetLocalisationGroup du service servicelocalisation. Cette fonction renvoie un localisation group et interroge le repository :

public LocalisationGroup GetLocalisationGroup(int LocalisationGroupId)
{
    LocalisationGroup group = _repository.FindLocalisationGroup(LocalisationGroupId);
    return group;
}

Au vu de cet exemple on pourrait se poser la question du bien fondé d’avoir une couche service, mais celle ci prend tout son sens pour par exemple pour les fonctions GetLocalisationGroups() et GetLocalisationGroups(int? GroupParentId) qui servent respectivement à récupérer tous les groupes et à récupérer tous les groupes possédant un certain groupe parent. On verrait alors deux traitements différents d’un appel à la même fonction de notre repository FindAllLocalisationGroup.

//get all the groups in the database
public IList<LocalisationGroup> GetLocalisationGroups()
{
    IQueryable<LocalisationGroup> groups = _repository.FindAllLocalisationGroup();
}

et

//get the groups whom parent id is given
public IList<LocalisationGroup> GetLocalisationGroups(int? GroupParentId)
{
    IQueryable<LocalisationGroup> groups = _repository.FindAllLocalisationGroup().Where(x => (x.ParentGroup.Id == GroupParentId) || (x.ParentGroup==null && !GroupParentId.HasValue));
    return groups.ToList();
}

L’intérêt ici est que ce qui sort de notre repository n’est qu’un IQueryable, ce qui signifie que l’appel à la base de données n’a pas été fait. Il ne sera fait que dans notre couche service, et donc la requête SQL sera appropriée a la demande initiale. Il n’y aura pas de requête du style : “ Select * from ma table”, pour ensuite faire un filtrage sur les résultats, ce qui serait un gâchis énorme.

Trêve de digression, je reviens à mon sujet initial. Et j’aborde le point 2. Beaucoup plus intéressant. Je veux connaitre pour un groupe donné tous les parents  de celui ci. Pour ce faire, j’ai de la chance j’ai une procédure stockée qui me renvoie l’information sous forme de liste. Parfait, il n’y a plus qu’à extraire ces informations de ma procédure stockée!

La première chose à faire est d’écrire ma proc. stock dans ma base de données. Elle a pour nom [SP_LocalisationGroup_GetParents] et prend en argument un entier nullable : @localisationgroup_childid int=null   

Maintenant tâche à nous de faire comprendre à EF ce que nous voulons. Je me place donc dans mon modèle de données.

ef2-1

et je fais un clic droit pour actualiser mon modèle depuis ma base de données.

je choisis ma procédure stockée à ajouter.

ef2-2

Et je valide.

Ensuite je me place dans le model Browser et dans localisation.Store, je vois ma procédure stockée qui a été rajouté. Afin d’avoir un accès facile à cette dernière, je clic droit sur elle et je choisis add function import.

ef2-3

Cela va me rajouter une fonction d’import dans mon conteneur et ainsi me permettre de prendre en main l’appel à cette procédure stockée de cette façon :

public IList<LocalisationGroup> GetLocalisationGroupParents(int groupid)
{
   IList<LocalisationGroup> parentgroups = container.SP_LocalisationGroup_GetParents(groupid).ToList();
    return parentgroups;
}

Vous remarquerez ici que l’accès à la base de données est immédiat. cela signifie donc que lorsque j’appellerais cette fonction, je ferais un appel à ma base, en renvoyant un IQueryable ce n’est pas le cas. Mais ici ce n’est pas important, car je récupère directement ce qui m’intéresse sans avoir à faire un traitement particulier dessus.

Voilà, je pense avoir rajouter une petite brique de plus. Je vais continuer à travailler sur ce petit projet au rythme de mon temps disponible… donc ne vous étonnez pas d’avoir des nouvelles dans 48 heures ou dans 3 mois!

Merci de votre lecture, et comme d’habitude si vous avez des suggestions, des commentaires, ou autres, surtout n’hésitez pas.

Bon ca fait un petit moment que je devais revenir. J’avais prévu une semaine, au final, cela aura fait deux mois. Entre temps beaucoup de tafs et d#autres choses mais on est pas la pour parler de ma vie, enfin pas que.. ;)

Je me suis beaucoup intéresse au développement sous AJAX ces derniers temps, et surtout ce qui est faisable avec MVC ASP.net. Le résultat n’est pas encore probant, je n’ai pas encore réussi a faire vraiment ce que je voulais, mais je vais vous livrer une première ébauche de ce vers quoi je tends, avec un peu de chance, j’aurais peut être même deux ou trois commentaires qui me guideront vers la sortie…

L’objectif est pour le moment de faire apparaitre  un tableau avec les valeurs de ma table de LocalisationGroup et de pouvoir une popup pour éditer un élément de ma table.

Pour cela j’ai le code suivant :

<h2>Groups</h2>
    <div id="GroupList"></div>
    <div id="GroupDetail" title="Detail Group">
</div>

Comme vous pouvez le voir ridiculeusement simple. Ma div grouplist sera peuplé par un appel javascript :

function RefreshListGroup(id) {
        //load the list of group
        $("#GroupList").load("/Localisation/ListGroup", { ParentGroupId: id });
    }

    //Intialisation of the page
    RefreshListGroup(null);

Cet appel JavaScript va charger le contenu Html qui sera renvoyé lors de l’appel à l’action ListGroup du contrôleur Localisation, et elle prend en paramètre le parent group id car je le rappelle, ma liste de données possède une structure hiérarchique.

tableau

De quoi est composé cette action :

// GET: /Localisation/ListGroup/id
  public ActionResult ListGroup(int? ParentGroupId)
  {
      Localisation localisation = CreateLocalisation();
      localisation.Groups = servicelocalisation.GetLocalisationGroups(ParentGroupId);
      localisation.AllGroups = servicelocalisation.GetLocalisationGroups();
      return View("ListGroup", localisation);
  }

Elle renvoie une vue nommé List group qui correspond à un usercontrol qui contient mon tableau. J’aurais pu associer a ce usercontrol une banale liste de localisationgroup, mais je vais avoir besoin de plus d’information ensuite, donc j’ai un DTO qui m’assure le transport de toutes mes informations vers ma vue.

Ce contrôle qui contient mon code Html est le suivant:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Localisation>" %>

       <table class="list" width="100%" cellspacing="0" border="1" rules="all">
                        <tr class="headerStyle">
                            <td>
                                &nbsp;
                            </td>
                            <td>
                                <input type="checkbox" enabled="false" />
                            </td>
                            <td>
                                <asp:Image ID="Image2" runat="server" ImageUrl="~/App_Themes/dynamiq/true.gif" />
                            </td>
                            <td width="100%">
                                Name
                            </td>
                            <td >
                                &nbsp;
                            </td>
                        </tr>
<% foreach (Data.LocalisationGroup group in Model.Groups )
   {%>
   <tr class="itemStyle">
                            <td>
                            <%
                            if (Model.CurrentGroup.Id>0)
                            {
                                Response.Write(Html.Image(this.ResolveUrl("~/App_Themes/dynamiq/attention.gif"),
                                               "The dates of this group are not in the boundaries of its parent",
                                              !group.MatchParentKeyDates(Model.CurrentGroup.StartDate, Model.CurrentGroup.EndDate)));
                            }
                             %>
                            </td>
                            <td>
                                &nbsp;
                            </td>
                            <td>
                                <%= group.IsReleased %>
                            </td>
                            <td >
                                   <a href="Javascript:RefreshListGroup(<%= group.Id %>);"><%= group.Name %></a>
                            </td>
                            <td >
                               <%= Ajax.ActionLink("Edit", "DetailLocalisationGroup", new { id = group.Id }, new AjaxOptions() { UpdateTargetId = "DetailLocalisationGroup", OnSuccess = "InitialisationDetailGroup" })%>
                            </td>
                        </tr>
<%  }%>
</table>

Rien de bien compliqué ici, je me contente d’afficher les informations dans différentes lignes. La seule dynamicité est là :

<a href="Javascript:RefreshListGroup(<%= group.Id %>);"><%= group.Name %></a>

Cet appel me permet de recharger ma list de localisationGroup, au cas ou je désire naviguer dans ma liste hiérarchique.

et le deuxième point est là :

<%= Ajax.ActionLink("Edit", "DetailLocalisationGroup", new { id = group.Id }, new AjaxOptions() { UpdateTargetId = "DetailLocalisationGroup", OnSuccess = "InitialisationDetailGroup" })%>

Ici j’utilise une propriété du framework MVC . Il s’agit là d’appeler l’action DetailLocalisationGroup du contrôleur Localisation, et de rendre l’html produit dans la div DetailLocalisationGroup.

Ainsi en cliquant sur le lien  , le contenu du div groupdetail contiendra le formulaire de modification de mon objet localisationgroup

<h2>Groups</h2>
    <div id="GroupList"></div>
    <div id="GroupDetail" title="Detail Group">
</div>

l’action du controleur Localisation est la suivante :

[AcceptVerbs("POST")]
public ActionResult DetailLocalisationGroup(int id)
{
    LocalisationGroup group = servicelocalisation.GetLocalisationGroup(id);
    return View("DetailGroup", group);
}

Elle renvoie un usercontrol nommé DetailGroup, avec le group demandé :

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Data.LocalisationGroup>" %> 
  <form id="EditLocalisationGroupForm" >
        <p>
            Id:
            <%= Html.Encode(Model.Id)%>
             <%= Html.TextBox("Id", Html.Encode(Model.Id), new { @readonly = "true" })%>
        </p>
    <p>
            Name:
            <%= Html.TextBox("Name", Html.Encode(Model.Name))%>
            <%= Html.ValidationMessage("Name", "*")%>
        </p>
        <p>
            ParentGroupId:  <%= Html.TextBox("ParentGroupId", Html.Encode(Model.ParentGroupId))%>
        </p>
</form>

A ce stade, je me retrouverait donc avec mes deux div de départ chargés du Html qui va bien. Et pour mettre un peu de bonhommie dans tout ca, j’aurais pris soin d’utiliser un peu de Jquery pour mettre en forme mon formulaire d’edition, et ce JQuery provoquera aussi la soumission de mes informations :

   $(function () {

        $("#DetailLocalisationGroup").dialog({
            bgiframe: true,
            autoOpen: false,
            height: 500,
            width: 300,
            modal: true,
            buttons: {
                ‘Save’: function () {
                    var bValid = true;

                    //Validation du contenu : bValid=true or false

                    if (bValid) {
                        dataString = $("#EditLocalisationGroupForm").serialize();

           

                        $.ajax(
                        {
                            type: "POST",
                            url: "/Localisation/EditLocalisationGroup",
                            data: dataString,
                            dataType: "html",
                            success: function (data) {
                                $("#GroupList").html(data);
                            },
                            error: function (httpRequest, textStatus, errorThrown) {
                                alert("status=" + textStatus + ",error=" + errorThrown);
                            }
                        }
                        );

                        $(this).dialog(‘close’);
                    }
                },
                Cancel: function () {
                    $(this).dialog(‘close’);
                }
            },
            close: function () {
                allFields.val( »).removeClass(‘ui-state-error’);
            }
        });

        $(‘#create-user’).click(function () {
            $(‘#DetailLocalisationGroup’).dialog(‘open’);
        })
        $(‘#edit-group’).click(function () {
            $(‘#DetailLocalisationGroup’).dialog(‘open’);
        });

    });

Edit

J’utilise ceci pour envoyer mes données à l’action EditLocalisationGroup du controleur Localisation :

                        dataString = $("#EditLocalisationGroupForm").serialize();

                        $.ajax(
                        {
                            type: "POST",
                            url: "/Localisation/EditLocalisationGroup",
                            data: dataString,
                            dataType: "html",
                            success: function (data) {
                                $("#GroupList").html(data);
                            },
                            error: function (httpRequest, textStatus, errorThrown) {
                                alert("status=" + textStatus + ",error=" + errorThrown);
                            }
                        }
                        );

Le contrôleur récupère un groupe qu’il tachera de sauvegarder dans la base de données :

[AcceptVerbs("POST")]
public ActionResult EditLocalisationGroup(LocalisationGroup group)
       {
           servicelocalisation.SaveLocalisationGroup(group);
           return ListGroup(group.ParentGroupId);
       }

En cas de succès, je récupère le contenu de mon tableau que je rempalce dans ma div. je referme ensuitema popup:

$(this).dialog(‘close’);

Voilà pour l’instant , où j’en suis rendu. Les choses avancent tout doucement. Il y a plusieurs choses que je n’aime pas trop comme le fait de faire appel a des postback via Jquery . J’aurais préféré quelque chose de plus intégré dans le Framework asp.net MVC. Mais peut être m’y suis je mal pris, je compte continuer un peu mes investigations dans ce domaine.

Si vous avez des idées, des commentaires ou des remarques a faire sur la façon que j’ai de procéder n’hésiter surtout pas, je suis dans une phase d’exploration en ce moment…

le projet est téléchargeable en l’état, c’est à dire en construction ici.

merci de votre lecture,

Avant de se lancer dans des développements un peu trop compliqué, je voudrais savoir un peu comment marche la chose, au moins dans ses grandes lignes, et comment je vais pouvoir en tirer parti.

Entity Framework

 

Le modele edmx ( il va falloir que je me renseigne sur les termes avant la fin, moi…) me génère mes objets tout seul. ca j’apprécie beaucoup. je me retrouve donc avec un objet comme suit ( en fait c’est beaucoup plus long, mais j’ai décidé de me retreindre à l’essentiel) :

[EdmEntityTypeAttribute(NamespaceName="Localisation", Name="Culture")]
[Serializable()]
[DataContractAttribute(IsReference=true)]
public partial class Culture : EntityObject
{
    public global::System.Int32 Id {get; set;}
    public global::System.String Language {get; set;}
    public global::System.String Country {get; set;}
}

Pour l’instant, je ne regarderais pas plus avant ce qu’ il y a dedans.

Et je ne m’attarderais pas non plus sur toutes les fonctions possibles contenues le modele edmx, mais par contre je vais les utiliser dans un classe RepositoryCulture.

public class CultureRepository: ICultureRepository
   {

       LocalisationContainer container ;

       public CultureRepository(string connectionstring)
       {
           container = new LocalisationContainer(connectionstring);
       }

       // Query Methods
       public IQueryable<Culture> FindAllCulture()
       {
           return  container.T_Culture;
       }
       public Culture FindCulture(int CultureId)
       {
           var culture=  from c in FindAllCulture()
                          where c.Id == CultureId
                           select c;
           return culture.SingleOrDefault();
       }

       // Insert/Delete
       public void AddCulture(Culture c)
       {
           container.T_Culture.AddObject(c);
           container.SaveChanges();
       }
       public void DeleteCulture(Culture c)
       {
           container.T_Culture.DeleteObject(c);
           container.SaveChanges();
       }

       // Persistence
       public void Save()
       {
           container.SaveChanges();
       }
   }

Cette dernière va me permettre de manipuler mon edmx, de façon sereine. Mais cela veut donc dire que je vais utiliser une interface afin de me préserver d’un éventuel changement d’humeur de ma part ( je pourrais avoir envie d’essayer Nhibernate par exemple, plus tard…  grrr…)

public interface ICultureRepository
    {
    // Query Methods
    IQueryable<Culture> FindAllCulture();
    Culture FindCulture(int CultureId);

    // Insert/Delete
    void AddCulture(Culture c);
    void DeleteCulture(Culture c);
    // Persistence
    void Save();

    }

 

Je dispose donc maintenant d’une méthode conviviale pour jouer avec mes données de Culture. Passons au coté MVC…

MVC

l’acces aux donnés

De la même façon, que je suis parti sur un concept simple pour le coté Entity Framework, je ferais de même pour ma première approche de MVC.

La première chose que je veux faire, c’est de pouvoir accéder aux donnés facilement dans mon application MVC, je vais donc poursuivre le travail effectué plus avant, et généraliser ma façon d’accéder aux infos. Pour ce faire, je vais utilser un composant d’injection de dépendance / Inversion de Contrôle  nommé StructureMap

Je ne susi réellement pas un expert de ce genre de composé, j ai donc repiqué beaucoup d’idée a un tutorial MVC de microsoft. Mais voilà ce que j’ai pu en tirer pour mon cas présent.

dans mon application_start , j’ai rajouté

        BootStrapper.ConfigureStructureMap();

           ControllerBuilder.Current.SetControllerFactory(
               new StructureMapControllerFactory()
               );

Cela me permet d’initialiser le composant. la classe BootStrapper consiste en deux lignes :

public class BootStrapper
   {
       public static void ConfigureStructureMap()
       {
           StructureMapConfiguration.AddRegistry(new DBServiceRegistry()); 
           StructureMapConfiguration.AddRegistry(new StorefrontRegistry());
       }
   }

Et la classe DBServiceReigistry, va me permettre d’instancier ma conenction à la bonne base pour entity :

public class DBServiceRegistry : Registry
    {
        public string ConnectionString
        {
            get
            {
                if (WebConfigurationManager.ConnectionStrings["Application"]==null)
                    throw new InvalidOperationException(« Conenction string Application cannot be null. »);
                if (string.IsNullOrEmpty(WebConfigurationManager.ConnectionStrings["Application"].ToString()))
                    throw new InvalidOperationException(« Connection string Application cannot be empty. »);

                return WebConfigurationManager.ConnectionStrings["Application"].ToString();
            }
        }

        protected override void configure()
        {

            ForRequestedType<System.Data.EntityClient.EntityConnection>()
                .TheDefaultIs(() => new System.Data.EntityClient.EntityConnection(ConnectionString))
                .CacheBy(InstanceScope.Hybrid);

        }
    }

tandis que StoreFrontRegistryx lui cataloguera, les différents repository que je peux avoir et instanciera la bonne classe.

public class StorefrontRegistry : Registry
    {
        protected override void configure()
        {

            #region repository
            ForRequestedType<ICultureRepository>()
              .TheDefaultIsConcreteType<CultureRepository>();

            ForRequestedType<ILocalisationRepository>()
              .TheDefaultIsConcreteType<LocalisationRepository>();
            #endregion
        }
    }

Pour l’instant, j’utilise une autre classe StructureMapControllerFactory, mais je n’ai pas encore pris le temps de changer quelque chose à l’intérieur, le besoin ne s’en est pas fait sentir tout du moins.

les routes

Pour mes routes je ne me suis pas trop embêté pour l’instant, et j ‘ai tapé dans le simple et efficace. J’ai codé en dur Localisation/Culture afin de faire un différence significative avec ce qui pourrait arrivé ensuite :

 

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute(« {resource}.axd/{*pathInfo} »);

            routes.MapRoute(
                « home »,                                                  // Route name
                « home/{controller}/{action}/{id} »,                       // URL with parameters
                new { controller = « Home », action = « Index », id = «  » }   // Parameter defaults
            );

            routes.MapRoute(
               « localisation »,                                              // Route name
               « localisation/culture/{action}/{id} »,                        // URL with parameters
                new { controller = « Culture », action = « Index », id = «  » }   // Parameter defaults
           );

        }

Par ailleurs avant d’aller plus loin , j ai installé un autre composant RouteDebug.dll qui me permet de debugger mes routes, je pense que tôt ou tard, j’en aurais besoin.

Pour ce faire, j’ai rajouter mais commenté pour le moment l’instruction suivante :

          // ROUTES
           RegisterRoutes(RouteTable.Routes);
           //uncomment this line for route debugging
           //RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);

On peut voir dans ma route que j’instancie :

       new { controller = « Culture », action = « Index », id = «  » } 

Ce faisant, j’instancie un contrôle nommé Culture.

le controller

Rien de plus simple que ce controller. Mais tout d’abord, faire attention à son vrai nom CultureController. C’est important afin que MVC ne se mélange pas les pinceaux.

Ensuite détaillons le controller :

son constructeur :

#region constructor

       public CultureController(ICultureRepository repository)
       {
           _repository = repository;
           if (_repository == null)
               throw new Exception(« Repository cannot be null »);
       }

       #endregion

Il prend en entrée un ICultureRepository  qui sera instancié par StructureMap avec la bonne classe.

et ensuite correspondant à l’ {action} de ma route :

      // GET: /Localisation/Culture/Index ou /Localisation/Culture
        public ActionResult Index()
        {
            IQueryable<Culture> cultures = _repository.FindAllCulture();
            return View(« Index », cultures);
        }

Suivant l’url /Localisation/Culture/Index ou /Localisation/Culture, j’appellerais le Controller Culture et j’effectuerais l’action Index, qui me renverra la vue Index.

La vue Index prend en parametre un IQueryable<Cultures>, que je vais chercher au moment ou j’en ai besoin.

Cela fonctionne de la même facon pour les autres actions, avec un petit détail à voir, à savoir la méthode utilisé pour appeler l’url qui permettra sur la même Url De procéder à plusieurs actions différentes :

public ActionResult Details(int id)
public ActionResult Create()

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection)

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Edit(int id)

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection collection)

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Delete(int id)

Sur ce , il n’ y a plus qu’à créer les vues correspondantes

les Views

Personnellement, et pour le début, je ne me suis pas embêté, et j’ai pris l’assistant de visual studio pour les créer.  J’ai donc créé un répertoire Culture dans Views. Ceci est aussi important pour MVC.

Je pense qu il doit y avoir un moyen de changer ce paramètre par défaut , mais ce sera l’occasion de plus amples investigations.

Voilà ,c’est tout pour aujourd’hui. Bon c’est bien évidemment hyper débutant comme approche, mais c’Est ce que je suis, et je me disais que d’autres pourraient être intéressé par mon cheminement personnel, si jamais il y avait des amateurs de MVC et d’entity framework dans les parages!

La suite la semaine prochaine,

L’objectif que je me fixe dans les jours qui viennent est de réaliser une petite application de localisation en ASP.net et MVC. Il s’agit pour moi surtout de me familiariser avec ces techniques, et peut être que je pourrais vous emmener avec moi sur un petit bout de chemin, qui sait?

Tout d’abord le but, je vais m’exprimer avec les mots que je connais a savoir ceux de l’univers webform: Avoir une librairie de contrôle qui accepteront un clé (localisationKey) et un langage. Cette clé possède une date de départ et une date de fin. Et associé à cette clé, je veux avoir un article (LocalisationItem) qui lui contiendra le texte a afficher en fonction du langage. Cela permettra de pouvoir aficher un texte en plusieurs langues et qui pourra changer aussi en fonction de la date.

Bref, rien de bien compliqué, surtout que j’ai déjà fait tout ca en webform. Il s’agit aussi pour moi, de voir l’effort à fournir pour transoposer vers le support MVC.net du travail déjà fait en webform.

Dernière petite précision, l’intérêt va être aussi d’utiliser Entity framework de façon à générer ma base de données à partir du modèle.

Les outils à ma disposition :

  • Visual Studio 2010
  • Visual Studio 2005 expresse édition

Tout d’abord mon premier schéma de données :

MVC1

Comme vous pouvez le constater, je pars du principe que je vais avoir des groupes qui contiendront des  groupes enfants, ou et des clés.

Et à chacune de ces clés, j’associe un item.

Lors de la création de mon schéma j’ai pris soin de spécifier pour chacun des id , la propriété StoreGeneratedPAttern=Identity. Cela me permet d’avoir un champ auto incrémenté du coté de ma base de données.

Du coté de ma solution, je m’oriente vers différents projets :

  • site MVC
  • Data
  • Services
  • Test

Tout ce ci n’est évidemment qu’une première approche, au fur et á mesure de l’avancée du projet j’affinerais un peu tout ca.

Voici le projet utilisé actuellement : solution VS 2010

Suite dans l’étape 2…

Bonjour,

Dans les applications ASP.net, on doit comme dans la plupart des application stocker  nos données lors de l’exécution du programme. En ASP.net, cela peut ête dans des variables d’application, de session, dans le cache, dans le viewstate. Mais on peut imaginer aussi un conteneur exprès pour accueillir des objets un certain laps de temps, ou d’autres choses encore plus fantaisistes mais pourtant tellement quotidienne…

Par ailleurs, dans les applications asp.net, on peut etre confronté aussi à un autre problème: Comment arriver à obtenir de la souplesse avec le mode d’hébergement? je m’explique par un exemple, comment faire dans un site de e-commerce pour passer d’un caddie hébergé en variable de session à un caddie hébergé dans le viewstate ou dans le cache, etc..

Dans mes applications, j’utilise une seule classe avec des méthodes static . Et des lors que je veux quelque chose entreposé dans un container c’est à cette classe que je m’adresse. Un petit exemple avant de continuer :

/// <summary>

/// this class defines where to get all the items used to make the web site work

/// This is where to define the storage of every item

/// </summary>

public class MonContext

{

/// <summary>

/// Stored in the session

/// </summary>

/// <returns></returns>

public static UserProvider GetCurrentUser()

{

StoringContainerProvider container = StoringContainerFactory.GetInstance(_
StoringContainerFactory.E_StoringContainerType.Session);

if (container.LoadObject(« User ») == null)

{

UserProvider user = new UserProvider();

container.SaveObject(user, « user »);

}

return (UserProvider)container.LoadObject(« User »);

}

}

Cette méthode GetCurrentUser() va être responsable du container pour entreposer ma variable. Ici nous voyons un container de type simple : une session. Rien de bien excitant.
Mais de la même facon, je pourrais appeler un autre type de conteneur. Maintenant , un petit tour sur la fabrication d’un conteneur. Celui ci va devoir implémenter l’interface suivante :

public interface IContainer {

string GetContainerKey(params Object[] listparams);

List<string> GetKeys();

bool HasObject(string ContainerKey);

object LoadObject(string ContainerKey);

void SaveObject(Object objtoBeSaved, string ContainerKey);

void DeleteObject(string ContainerKey);

}

Et j’aurais alors une factory qui me permettra de choisir le type du conteneur qui m’interesse :

public sealed class StoringContainerFactory

{

/// <summary>

/// Type of container available

/// </summary>

public enum E_StoringContainerType{

none,

Session,

Application,

RawCache,

MonWebContainer

}

private E_StoringContainerType _StoringContainerType;

public E_StoringContainerType StoringContainerType{

get{ return _StoringContainerType; }

set{_StoringContainerType = value; }

}

public static StoringContainerProvider GetInstance(_

E_StoringContainerType containertype){

StoringContainerProvider Instance = null;

switch (containertype)

{

case E_StoringContainerType.none:

Instance = new ConcreteProvider.StoringContainerNone();

break;

case E_StoringContainerType.Session:

Instance = new ConcreteProvider.StoringContainerSession();

break;

case E_StoringContainerType.Application:

Instance = new ConcreteProvider.StoringContainerApplication();

break;

case E_StoringContainerType.RawCache:

Instance = new ConcreteProvider.StoringContainerRawCache();

break;

case E_StoringContainerType.MonWebContainer:

Instance = new ConcreteProvider.StoringContainerMonWebContainer();

break;

}

return Instance;

}

}

J’utilise un énuméré E_StoringContainerType dans ma factory car je trouve cela plus tranquille de connaitre les possibilités que j’ai d’entreposer des choses.

Cette factory nous renvoie des objets abstraits  de type StoringContainerProvider :

public abstract class StoringContainerProvider : IContainer,IDisposable{

public string GetContainerKey(params Object[] listparams){

string returnvalue=string.Empty;

for (int i = 0; i < listparams.GetLength(0); i++)

{

if (listparams[i] != null)

{

returnvalue += listparams[i].ToString();

}

else

{

returnvalue += « null »;

}

}

return returnvalue;

}

public abstract void DeleteObject(string ContainerKey);

public abstract List<string> GetKeys();

public abstract bool HasObject(string ContainerKey);

public abstract Object LoadObject(string ContainerKey);

public abstract void SaveObject(Object ObjToBeSaved, string ContainerKey);

}

Ces objets abstraits ne sont rien tant qu’ils ne sont pas instanciés avec un objet plus concret. Prenons l’exemple de rawCache:

public class StoringContainerRawCache : StoringContainerProvider{

public override bool HasObject(string ContainerKey)

{

return (HttpContext.Current.Cache[ContainerKey.ToLower()] != null);

}

public override object LoadObject(string ContainerKey)

{

return HttpContext.Current.Cache[ContainerKey.ToLower()];

}

public override void SaveObject(Object ObjToBeSaved, string ContainerKey)

{

if (ObjToBeSaved == null)

{

this.DeleteObject(ContainerKey.ToLower());

}

else

{

HttpContext.Current.Cache[ContainerKey.ToLower()] = ObjToBeSaved;

}

}

public override void DeleteObject(string ContainerKey)

{

HttpContext.Current.Cache.Remove(ContainerKey.ToLower());

}

public override List<string> GetKeys()

{

List<string> ls = new List<string>();

IDictionaryEnumerator d = HttpContext.Current.Cache.GetEnumerator();

d.MoveNext();

for (int i = 0; i < HttpContext.Current.Cache.Count;i++ )

{

if (d.Current != null)

{

ls.Add(d.Key.ToString());

d.MoveNext();

}

}

return ls;

}

}

Ici la gestion est aussi très simple, mais on pourrait imaginer , une gestion plus complexe qui aurait lieu dans cette classe. Ensuite Tout dépend des besoins d l’application à vrai dire. J’espère  vous avoir introduit à un système de gestion de vos conteneurs dans vos applications.

C’est agréable de travailler de la sorte ensuite, car vous ne vous poser plus aucune question sur l’endroit ou sont stockées vos objets, et vous pouvez ainsi disposer d’un vrai tableau de bord pour assigner tel ou tel objet dans tel ou tel conteneur afin d’optimiser vos sites et vos applications web.

Merci de votre lecture, si vous êtes arrivés jusque là…


this class defines where to get all the items used to make the web site work

/// This is where to define the storage of every item

/// </summary>

public class MonContext

{

/// <summary>

/// Stored in the session

/// </summary>

/// <returns></returns>

public static UserProvider GetCurrentUser()

{

StoringContainerProvider container = StoringContainerFactory.GetInstance(StoringContainerFactory.E_StoringContainerType.Session);

if (container.LoadObject(« User ») == null)

{

UserProvider user = new UserProvider();

container.SaveObject(user, « user »);

}

return (UserProvider)container.LoadObject(« User »);

}

}

Publié par : yoannr | 6 juillet 2009

Les repository et l’utilisation de procédures stockées

Dans mes précédents posts, j’ai décrit ce qu’était un repository de manière générale et comment implémenter un système dans un environnement web afin de choisir de manière simple son repository.

Je vais aujourd’hui détailler une méthode afin de faire rimer les repository avec  procédures stockées. Pour ce faire rien de tel qu’un exemple, et celui ci sera la gestion de cours de danse, et de salles de cours y afférent. Nous aurons donc besoin de connaitre la listes des endroits où se passent les cours, de pouvoir récupérer le détail d’un endroit, de sauver un endroit ou de l’effacer. le processus sera le même pour les cours.

l’interface de notre repository

Tout ceci va nous amener à développer l’interface suivante pour discuter avec notre base de données :

public interface IRepositoryCourses
{
List<Place> GetPlaces(FilterPlace filter);
Place GetPlace(FilterPlace filter);
void SavePlace(Place p);
void DeletePlace(Place p);
List<Course> GetCourses(FilterCourse filter);
Course GetCourse(FilterCourse filter);
void SaveCourse(Course c);
void DeleteCourse(Course c);
}

Avant de continuer le code c#, allons faire un petit tour du coté de notre base. Pour cet exemple je prendrais un script de transact-sql pour sql server 2005 mais , je crois que n’importe quelle base pourrait faire l’affaire .

L’idée va être de regrouper toutes nos commande en une seule procédure stockées, qui va ainsi, elle aussi, posséder les fonctions suivantes

–    —————————————–
–      | @id_op |  opération                   |
–    —————————————–
–      |    1           |  Get Places  |
–    —————————————–
–      |    2
|  Get Place   |
–    —————————————–
–      |    3
|  Save Place |
–    —————————————–
–      |    4           |  Delete Place  |
–    —————————————–
–      |    5           |  Get Courses   |
–    —————————————–
–      |    6           |  Get Course  |
–    —————————————–
–      |    7          |  Save Course |
–    —————————————–
–      |    8          |  Delete Course           |
–    —————————————–
–      |                 |                                         |
–    —————————————–

Comme vous le pouvez le constater chaque fonction est associée à un id opération. C’est qui va nous permettre de différencier la commande à effectuer lors de l’appel de notre procédure.

Quel est l’intérêt de pratiquer de la sorte?
Il est très simple :

  1. Cela groupe les fonctionnalités par bloc de code dans votre base de données plus simple à migrer si besoin était.
  2. Pour plusieurs fonctions, on nécessite parfois les mêmes informations. De la sorte, on ne les déclare qu’une seule fois.
  3. cela va nous permettre de factoriser notre code c# et par la même

La procédure stockée

 


Dans la pratique comment ca fonctione? Voice le code complet de la procedure stockée SP_COURSES

Le parametre @id_op va nous permettre de naviguer dans la procédure en jouant sur des bloc if comme suit :

– Get Places

if @id_op=1
begin

Select * from V_places where

PlaceIsDeleted=isnull(@filter_place_IsDeleted,PlaceIsDeleted)

and PlaceIsReleased=isnull(@filter_place_IsReleased,PlaceIsReleased)

IF @@ERROR <> 0
BEGIN
GOTO
LBL_ERROR RETURN
END

end

la définition des paramétres hors cet id_op obéit toujours à la même regle :

–getting, saving and deleting places
@place_id
int=null, – id of the place

@place_name varchar(50)=null, – name of the place

@place_description varchar(255)=null, – description of the place

@place_Released bit=null, – true of false, indicates if the place has been released

@place_Deleted bit=null, – true of false, indicates if the place has been deleted

@place_startdate datetime=null, – start date of the place

@place_enddate datetime=null, – end date of the place


à savoir que chaque parametre est nullable. Avoir des parametres nullable ne résoudra pas tous les soucis et ne permettra pas toutes les factorisations possibles mais dans le cas du select Get Places détaillé plus haut, travailer de la sorte nous permettra d’avoir suffisemment de latitudes pour combler notre besoin.

Par ailleurs, on peut remarquer que la procédure stockées s’articule à l’interieur d’une transaction :

SET NOCOUNT ON
– starting transaction

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

BEGIN TRANSACTION TRANSACTION_COURSES

COMMIT TRANSACTION TRANSACTION_COURSES

RETURN

LBL_ERROR:

ROLLBACK TRANSACTION TRANSACTION_COURSES

RETURN


L’implementation du repository coté c#

Maintenant que les règles pour écrire notre procédure stockée ont été détaillé, on va pouvoir passer au code c# qui va se charger de communiquer avec elle.

L’idée serait d’avoir une seule classe capable de cette communication. Cette classe devra donc implémenter l’interface décrite ci dessus. Par ailleurs, il faudrait aussi que cette classe soit indépendante de la base de donnée auquelle elle accede. Et donc que seul l’interface de la base de données ( son nom et ses parametres d’entrée et de sortie) soit le parametre déterminant qui compose à sa création.

Les contraintes étantes fixées, je vais m’appuyer sur une DAL comme décrite dans cet article de developpez.com écrit par SaumonAgile (erf j ai paumé le lien…) afin de rendre mon code indépendant de ma base.

public class SqlServerCoursesRepository : SQLRepository, IRepositoryCourses

{

}



Ma classe va aussi vous le voyer hériter de SQLRepository. En effet, toute une partie du traitement sera commune á ce repository comme à d’autres.

/// <summary>

/// Description résumée de ProckStock

/// </summary>

public abstract class SQLRepository : ISQLRepository

{

public class CastValeur : ExceptionLibrary

{

public CastValeur(string parametername, DbType casttype, object parametervalue)

:

base(string.Format(« The parameter {0} has not been casted in {1}. The parameter value was {2} », parametername,
casttype.ToString(), parametervalue.ToString()))

{

}

}

protected IDataParameterCollection ParamCollection;

protected IDbConnection ApplicationConnection;

public abstract Object Execute();

public void HttpTrace(string storedProcedure, string operation)

{

System.Web.HttpContext.Current.Trace.Warn(string.Format(« SP name={0}, Operation ={1} », storedProcedure, operation));

foreach (IDataParameter p in ParamCollection)

{

System.Web.HttpContext.Current.Trace.Warn(string.Format(« Pname={0};Value={1} », p.ParameterName, p.Value));

}

}

public SQLRepository(IDbConnection connection)

{

if (connection == null)

throw new Exception(« The connection is null »);

ApplicationConnection = connection;

ParamCollection = DataProviderFactory.GetInstance().GetParameterCollection();

}


Comme vous pouvez le voir, le comportement géneral  est  de

  1. fournir un méthode pour tracer nos appels
  2. Définir une exception pour des problemes de cast, ceci provenant de souci avec ma DAL.
  3. définit un constructeur qui accepte une connection ouverte afin de pouvoir executer la procédure stockée
  4. oblige les classe héritantes à définir une fonction execute qui retournera un objet.

Cela étant dit, nous allons pouvoir nous immerger dans ce qui nous tient plus á coeur, à savoir l’implémentation de notre repository DBCoursesRepository.cs, dont vous trovuerez le code ici.

Cette classe fonctionne de la sorte :  On l’instancie et on appelle une des fonctions qui implémente son interface, et cette fonction appelle une fonction nommée Execute  qui elle fera le contact avec la base de données.

illustrons ceci par un exemple  avec la selection des endroits pour les cours de danse, getPlaces:

public List<Place> GetPlaces(FilterPlace filter)

{

List<Place> l = new List<Place>();

Place p;

IDataReader dr;

this.Id_op = E_Operation.GetPlaces;

this._FilterPlace = filter;

dr = (IDataReader)this.Execute();

while (dr.Read())

{

p = createPlace(dr);

l.Add(p);

}

dr.Dispose();

return l;

}

Comme défini dans l’interface,  elle  nous renvoie une List<Place> . En l’inspectant un peu plus, on se rend compte qu’elle va associé à une propriété Id_op l’enuméré GetPlaces.

En regardant cette énuméré, on se rend compte d’une coincidence troublante :

public enum E_Operation

{

GetPlaces = 1,

GetPlace = 2,

SavePlace = 3,

DeletePlace = 4,

GetCourses = 5,

GetCourse = 6,

SaveCourse = 7,

DeleteCourse = 8

}


GetPlaces est associée à la valeur 1 tout comme dans notre procédure stockée.  Par ailleurs, l’obujet filter qui étati passé en paramétre vient se loger au creux d’une propriété de cette classe.

La fonction execute est appelée et renvoie un datareader. Tout le mystere va donc avoir lieu dans cette fonction execute :

public override object Execute()

{

//déclaration des variables

Object o = null;

string course_dates=string.Empty;

string storedProcedure = « SP_COURSES »;


Tout d’abord dans cette fonction, on définit à quelle procédure stockée cette fonction va devoir s’adresser, ici SP_COURSES.

Ensuite,  elle va converser avec la procédure stockée afin d’attribuer à chaque paramétre, une valeur:

ParamCollection.Clear();

//Insertion of enter parameters

using (dp = DataProviderFactory.GetInstance())

{

ParamCollection.Add(dp.GetParameterInput(« @Id_op », (int)this.Id_op));

//Place

ParamCollection.Add(dp.GetParameterInput(« @place_id », this._Place.ID));

ParamCollection.Add(dp.GetParameterInput(« @place_name », this._Place.Name));

ParamCollection.Add(dp.GetParameterInput(« @place_description », this._Place.Description));

ParamCollection.Add(dp.GetParameterInput(« @place_Released », this._Place.IsReleased));

ParamCollection.Add(dp.GetParameterInput(« @place_Deleted », this._Place.IsDeleted));

ParamCollection.Add(dp.GetParameterInput(« @place_startdate », this._Place.StartDate));

ParamCollection.Add(dp.GetParameterInput(« @place_enddate », this._Place.EndDate));

//Place filter

ParamCollection.Add(dp.GetParameterInput(« @filter_place_id », this._FilterPlace.ID));

ParamCollection.Add(dp.GetParameterInput(« @filter_place_IsReleased », this._FilterPlace.IsReleased));

ParamCollection.Add(dp.GetParameterInput(« @filter_place_IsDeleted », this._FilterPlace.IsDeleted));

ParamCollection.Add(dp.GetParameterInput(« @filter_place_StartDate », this._FilterPlace.StartDate, DbType.DateTime));

ParamCollection.Add(dp.GetParameterInput(« @filter_place_EndDate », this._FilterPlace.EndDate, DbType.DateTime));

…ect…


Le premier parametre défini et l’id_op que l’on avait stockée avant et on remarquera que le filtre stockée dans notre fonction appelante est retransmis dans cette collection de parametres. Il ne reste plus alors qu à appeler notre procédure stockée de la facon qui nous interesse :

//gives an output in the trace of the execution of this stored procedure

HttpTrace(storedProcedure, this.Id_op.ToString());

//execution of the code

switch (Id_op)

{

case E_Operation.SaveCourse:

case E_Operation.SavePlace:

case E_Operation.DeleteCourse:

case E_Operation.DeletePlace:

o = dp.ExecuteNonQuery(ApplicationConnection, storedProcedure, ParamCollection);

break;

case E_Operation.GetCourse:

case E_Operation.GetCourses:

case E_Operation.GetPlace:

case E_Operation.GetPlaces:

o = dp.ExecuteReader(ApplicationConnection, storedProcedure, ParamCollection);

break;

default:

break;

}

return o;

}



Notre DAL nous permettant au choix : ExecuteNonQuery,ExecuteReader,ExecuteDataset,ExecuteQueryParamsOut,ExecuteScalar.

Dans le cas de notre exmple, ce qui est retourné est un Idatareader. Sur lequel nous allons tourner, afin de créer notre liste d’endroits, la fonction create Place permettant de décortiquer ce datareader est de créer un objet Place directement à partir de celui ci :

private Place createPlace(IDataReader dr)

{

Place p = new Place();

p.ID = int.Parse(dr["PlaceId"].ToString());

p.Name = dr["PlaceName"].ToString();

p.Description = dr["PlaceDescription"].ToString();

p.IsDeleted = bool.Parse(dr["PlaceIsDeleted"].ToString());

p.IsReleased = bool.Parse(dr["PlaceIsReleased"].ToString());

p.StartDate = DateTime.Parse(dr["PlaceStartDate"].ToString());

p.EndDate = DateTime.Parse(dr["PlaceEndDate"].ToString());

return p;

}


Si des traitements devaient se faire sur les données provenant de notre base de données c’est ici qu’ils devraient avoir lieu.

Conclusion

On a aborder ici de maniére trés générale la construction d’un repository pour une procédure stockée.  Il y a evidemment beaucoup de variantes possibles en fonction des besoins et de l’architecture. L’intérêt de cette méthode est je trouve de ne plus avoir qu’une seule procédure stockée en entrée de base de données, ce qui permet d’unifier aussi l’appel à la base et ainsi de restreindre le nombre de fois ou on aura à réutiliser  le même interfacage pour les le même pool de données. Tout ceci ne vient que de mon experience personnelle et des discussions que j’ai pu avoi ici et lá. Je serais donc ravi de connaitre votre avis sur cette méthode ( si vous en avez un ;) )…

Dans de prochains articles j’essaierais de détailler un peu plus le fonctionnement de ma DAL, cela permettra en autres d’expliquer la présence de cete exception CastValeur dans mon code.

public class SqlServerCoursesRepository : SQLRepository, IRepositoryCourses

{

}

Publié par : yoannr | 2 juillet 2009

Un BirthDate user controle

Je fais des sites webs à longeur de journée, et il n’y en a pas un qui échappe à la régle. Ils possédent tous, à un endroit ou à un autre, trois listes déroulantes afin d’indiquer la date de naissance ou la date de peremption, de creation, de fin etc… Pour la plupart de mes dates j utilise un calendriere gratuit extremement bien fait  d‘Eworld. Mais avec ce calendrier, on ne peut pas aisément choisir une date 37 ans dans le passé. Pour cela rien ne vaut trois liste deroulantes.

Donc Je me suis mis en tête de créer un user controle afin de satisfaire à la tâche. Pour l’instant il n’est pas encore fini, mais les fonctionnalités de base sont lá. Je peaufinerais le tout plus tard. Mais tréve de bavardage, allons dans le concret:

Tout a été réalisé en code behind afin de pouvoir integrer ce contrôle dans n’importe lequel de mes sites webs via ma bibliothèque de code.

Ce user control comprend 3 dropdwnlist (C_DDL_Day,C_DDL_Month,C_DDL_Year). Le code c# ne sera responsable que de trois choses :

1)Remplir ces listes
2)Recupérer la valeur selectionner afin de fournir directement une datetime nullable.
3) fournir une interface avec le validateur de ce user controle

le point 3 est discutable, mais comme le validateur a éte écrit spécialement pour ce user controle, je ne pense pas faire une mauvaise opération, pour le moment, en l’integrant directement.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Library.WebSite.WebControls.Validator;

namespace Library.WebSite.WebControls
{
public class BirthDate : Library.WebSite.WebControls.BaseWebControl
{
private bool HasUndergoneDatabinding;
private int _DeltaYear = 10;
private int _MinimalYear = 1930;
private string _Separator = « / »;
private string _ValidatorErrorMessage = string.Empty;
private ValidatorDisplay _ValidatorDisplay =  ValidatorDisplay.None;

private PlaceHolder C_PH_BirthDate;
private DropDownList C_DDL_Day;
private DropDownList C_DDL_Month;
private DropDownList C_DDL_Year;
private BirthdateValidator C_VAL_BirthdateValidator;

public DateTime? SelectedValue
{
get
{
return (DateTime?)ViewState["BirthDateSelectedValue"];
}
set
{
ViewState["BirthDateSelectedValue"] = value;
}
}
public int DeltaYear
{
get { return _DeltaYear; }
set { _DeltaYear = value; }
}
public int MinimalYear
{
get { return _MinimalYear; }
set { _MinimalYear = value; }
}
public string Separator
{
get { return _Separator; }
set { _Separator = value; }
}
public string ValidatorErrorMessage
{
get { return _ValidatorErrorMessage; }
set { _ValidatorErrorMessage = value; }
}
public ValidatorDisplay ValidatorDisplay
{
get { return _ValidatorDisplay; }
set { _ValidatorDisplay = value; }
}

protected override void OnInit(EventArgs e)
{
//initialisation of the  control
C_PH_BirthDate = new PlaceHolder();

C_DDL_Day = new DropDownList();
C_DDL_Day.ID = « C_DDL_Day »;
C_DDL_Month = new DropDownList();
C_DDL_Month.ID = « C_DDL_Month »;
C_DDL_Year = new DropDownList();
C_DDL_Year.ID = « C_DDL_Year »;

C_VAL_BirthdateValidator = new BirthdateValidator();

//building the control
Literal l = new Literal();
Literal l2 = new Literal();
l.Text = _Separator;
l2.Text = _Separator;
C_PH_BirthDate.Controls.Add(C_DDL_Day);
C_PH_BirthDate.Controls.Add(l);
C_PH_BirthDate.Controls.Add(C_DDL_Month);
C_PH_BirthDate.Controls.Add(l2);
C_PH_BirthDate.Controls.Add(C_DDL_Year);
C_PH_BirthDate.Controls.Add(C_VAL_BirthdateValidator);

this.Controls.Add(C_PH_BirthDate);

C_VAL_BirthdateValidator.BirthDateDayId = « C_DDL_Day »;
C_VAL_BirthdateValidator.BirthDateMonthId = « C_DDL_Month »;
C_VAL_BirthdateValidator.BirthDateYearId = « C_DDL_Year »;

C_VAL_BirthdateValidator.ErrorMessage = _ValidatorErrorMessage;
C_VAL_BirthdateValidator.Display = _ValidatorDisplay;

//initialisation of the dropdownList
//for the days
for (int i = 1; i < 32; i++)
{
C_DDL_Day.Items.Add(new ListItem(i.ToString(), i.ToString()));
}
//for the months
for (int i = 1; i < 13; i++)
{
C_DDL_Month.Items.Add(new ListItem(i.ToString(), i.ToString()));
}
//for the year
//todo this has te be interfaced
for (int i = DateTime.Today.Year – DeltaYear; i > MinimalYear; i–)
{
C_DDL_Year.Items.Add(new ListItem(i.ToString(), i.ToString()));
}

base.OnInit(e);
}

protected void Page_Load(object sender, EventArgs e)
{
DateTime outdate;
if (DateTime.TryParse(C_DDL_Day.SelectedValue + « . » + C_DDL_Month.SelectedValue + « . » + C_DDL_Year.SelectedValue,out outdate))
this.SelectedValue = outdate;
}

protected override void OnPreRender(EventArgs e)
{
if (SelectedValue.HasValue)
{
if (SelectedValue.Value.Year > this.MinimalYear && SelectedValue.Value.Year <= DateTime.Today.Year – DeltaYear)
{
C_DDL_Day.SelectedValue = SelectedValue.Value.Day.ToString();
C_DDL_Month.SelectedValue = SelectedValue.Value.Month.ToString();
C_DDL_Year.SelectedValue = SelectedValue.Value.Year.ToString();
}
else
{
throw new Exception(« The selected value for the birth date is not in the range allowed. »);
}
}

base.OnPreRender(e);

}

}
}

En complément,  j’ai codé un validator exprès pour ce user controle grâce à l’excellent tutorial de Nico-pyright(c).

Je vous en présente ici le code :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

namespace Library.WebSite.WebControls.Validator
{
public class BirthdateValidator: BaseValidator
{
[Bindable(true), IDReferenceProperty(typeof(DropDownList)), TypeConverter(typeof(DropDownIDConverter)), Category("Controles"),
DefaultValue(""), Description("ID of the DropDownList for the days")]
public string BirthDateDayId
{
get
{
string birthDateDayId = (string)ViewState["BirthDateDayId"];
return birthDateDayId ?? String.Empty;
}
set { ViewState["BirthDateDayId"] = value; }
}
[Bindable(true), IDReferenceProperty(typeof(DropDownList)), TypeConverter(typeof(DropDownIDConverter)), Category("Controles"),
DefaultValue(""), Description("ID of the DropDownList for the months")]
public string BirthDateMonthId
{
get
{
string birthDateMonthId = (string)ViewState["BirthDateMonthId"];
return birthDateMonthId ?? String.Empty;
}
set { ViewState["BirthDateMonthId"] = value; }
}
[Bindable(true), IDReferenceProperty(typeof(DropDownList)), TypeConverter(typeof(DropDownIDConverter)), Category("Controles"),
DefaultValue(""), Description("ID of the DropDownList for the dropdownlist for the year")]
public string BirthDateYearId
{
get
{
string birthDateYearId = (string)ViewState["BirthDateYearId"];
return birthDateYearId ?? String.Empty;
}
set { ViewState["BirthDateYearId"] = value; }
}

//overloading of the ControltoValidate in order not to be used
public new string ControlToValidate
{
get { return string.Empty; }
set { throw new NotSupportedException(« Do not use ControlToValidate, use instead BirthDateDayId,BirthDateMonthId,BirthDateYearId »); }
}

protected override bool ControlPropertiesValid()
{
// check that Id Are filled
if (BirthDateDayId.Length == 0)
throw new HttpException(string.Format(« The BirthDateDayId of the validator ‘{0}’ must be filled out. », ID));
if (BirthDateMonthId.Length == 0)
throw new HttpException(string.Format(« The BirthDateMonthId of the validator ‘{0}’ must be filled out. », ID));
if (BirthDateYearId.Length == 0)
throw new HttpException(string.Format(« The BirthDateYearId of the validator ‘{0}’ must be filled out. », ID));

// check that the associated cotnrol are dropdownlist
if (!(FindControl(BirthDateDayId) is DropDownList))
throw new HttpException(string.Format(« The BirthDateDay control must be a dropdownList », BirthDateDayId));
if (!(FindControl(BirthDateMonthId) is DropDownList))
throw new HttpException(string.Format(« The BirthDateMonth control must be a dropdownList », BirthDateMonthId));
if (!(FindControl(BirthDateYearId) is DropDownList))
throw new HttpException(string.Format(« The BirthDateYear control must be a dropdownList », BirthDateYearId));

return true; // everything is good
}

protected override bool EvaluateIsValid()
{
DateTime outdate;
string validateDate;
bool isValid = true;
StringBuilder sb = new StringBuilder();

//we create the date as string
validateDate = string.Format(« {0}.{1}.{2} », GetControlValidationValue(BirthDateDayId), GetControlValidationValue(BirthDateMonthId), GetControlValidationValue(BirthDateYearId));

//we try to parse it
if (DateTime.TryParse(validateDate, out outdate))
{
isValid = true;
}
else
{
isValid = false;
}

return isValid;
}

protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
if (RenderUplevel)
{
Page.ClientScript.RegisterExpandoAttribute(ClientID, « evaluationfunction », « BirthdateValidatorEvaluateIsValid », false);
Page.ClientScript.RegisterExpandoAttribute(ClientID, « birthDateDayId », FindControl(BirthDateDayId).ClientID, false);
Page.ClientScript.RegisterExpandoAttribute(ClientID, « birthDateMonthId », FindControl(BirthDateMonthId).ClientID, false);
Page.ClientScript.RegisterExpandoAttribute(ClientID, « birthDateYearId », FindControl(BirthDateYearId).ClientID, false);

Page.ClientScript.RegisterExpandoAttribute(ClientID, « errorMessage », this.ErrorMessage, true);

}
}

protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (RenderUplevel)
{
Page.ClientScript.RegisterClientScriptInclude(« BirthDateValidatorJavaScript »,
Page.ClientScript.GetWebResourceUrl(GetType(), « Library.WebSite.WebControls.Validator.BirthDateValidator.js »));
string birthDateDayId = FindControl(BirthDateDayId) == null ? string.Empty : FindControl(BirthDateDayId).ClientID;
string birthDateMonthId = FindControl(BirthDateMonthId) == null ? string.Empty : FindControl(BirthDateMonthId).ClientID;
string birthDateYearId = FindControl(BirthDateYearId) == null ? string.Empty : FindControl(BirthDateYearId).ClientID;

Page.ClientScript.RegisterStartupScript(GetType(), « hookup1″, string.Format(« ValidatorHookupControlID(‘{0}’, document.all ? document.all['{1}'] : document.getElementById(‘{1}’)); », birthDateDayId, ClientID), true);
Page.ClientScript.RegisterStartupScript(GetType(), « hookup2″, string.Format(« ValidatorHookupControlID(‘{0}’, document.all ? document.all['{1}'] : document.getElementById(‘{1}’)); », birthDateMonthId, ClientID), true);
Page.ClientScript.RegisterStartupScript(GetType(), « hookup3″, string.Format(« ValidatorHookupControlID(‘{0}’, document.all ? document.all['{1}'] : document.getElementById(‘{1}’)); », birthDateYearId, ClientID), true);
}
}

}
}

Ce  validator fait appel à un fichier javascript afin de réaliser le test sur la date :
function TestDatum(nTag, nMaxTag)
{
if (nTag >= 1 && nTag <= nMaxTag)
return true
else
return false
}

function TestJahr(nJahr)
{
return (isNaN(nJahr)) ? false : true;
}

function IstSchaltjahr(nJahr)
{
if ((nJahr % 100 != 0) && (nJahr % 4 == 0) && (nJahr % 400 == 0)) {
return true;
}
return false;
}

function DatumGueltigEx(nTag, nMonat, nJahr)
{
var ok;
ok = false;

if (!TestJahr(nJahr)) return false;

switch (nMonat) {

case 1:
ok = TestDatum(nTag, 31);
break;
case 2:
if (IstSchaltjahr(nJahr)) {
ok = TestDatum(nTag, 29);
} else {
ok = TestDatum(nTag, 28);
}
break;

case 3:
ok = TestDatum(nTag, 31);
break;
case 4:
ok = TestDatum(nTag, 30);
break;
case 5:
ok = TestDatum(nTag, 31);
break;
case 6:
ok = TestDatum(nTag, 30);
break;
case 7:
ok = TestDatum(nTag, 31);
break;
case 8:
ok = TestDatum(nTag, 31);
break;
case 9:
ok = TestDatum(nTag, 30);
break;
case 10:
ok = TestDatum(nTag, 31);
break;
case 11:
ok = TestDatum(nTag, 30);
break;
case 12:
ok = TestDatum(nTag, 31);
break;
}

return ok;
}

function BirthdateValidatorEvaluateIsValid(sender)
{
var birthDateDay = ValidatorTrim(ValidatorGetValue(sender.birthDateDayId));
var birthDateMonth = ValidatorTrim(ValidatorGetValue(sender.birthDateMonthId));
var birthDateYear = ValidatorTrim(ValidatorGetValue(sender.birthDateYearId));

var Errormessage = ValidatorTrim(sender.errorMessage);

var isValid = true;

isValid = DatumGueltigEx(parseInt(birthDateDay), parseInt(birthDateMonth),parseInt( birthDateYear));

if (!isValid)
{
sender.innerHTML = Errormessage;
sender.errormessage = Errormessage;
}
return isValid;
}

Je ne m’attarderait pas trop sur le code ici écrit car, j’avoue en avoir piqué les principaux fonctionnements au tutoriel dont je parle plus haut.Il me reste qu’à rendre le tout un peu plus sexy en ne proposant pas, par exemple, des dates impossibles, je fais en effet mon test à la fin et donc propose des chausse trappes à mes utilisateurs. Mais bon, pour l’instant cela fonctionne, reste plus qu ‘à l’améliorer…

Publié par : yoannr | 22 juin 2009

Les repository et leur utilisation en ASP.net

Dnas mes précédents post , je vous parlais de l’endroit ou stocker sa connection et du pattern des repository. Dans celui ci, je vais mixer les deux afin d’obtenir un début de réponse à ces problematiques.

Le monde du web pourrait se résumer à des appels à des url. Si l’on pratique ce raccourci, on pourrait se figurer qu’en appelant une url,  on appelle une mini application que l’on pourrait imaginer être  comme un conteneur. Il y aurait alors le conteneur page webform, le conteneur webservice, etc..

L’idée serait alors d’associer à ces conteneurs un systeme dont le but serait d’initialiser la conjugaison des données. Quand je parle de conjugaison, je veux dire par lá qu si j’ai un repository pour aller chercher des informations concernant des utilisateurs nommé IRepositoryUser, je vais dans mon application utiliser un repository de mode sql server afin d’implementer l’interface IRepositoryUser.

Ce faisant il me faudra pour ce type de repository bien particulier procéder à certaines choses comme l’ouverture d’une connection à la base de données. D’autres types de repository pourraient évoquer d’autres besoins.

Pour cela, je vais donc créer une classe que je vais appeler repositorystore dans laquelle je vais référencer les repository présent dans mon application et dire de quel type, ils devront être:

public class RepositoryStore : IDisposable
{
IRepositoryUser _repositoryUser;
IRepositoryCourses _repositoryCourses;

public IRepositoryUser RepositoryUser
{
get
{
if (_repositoryUser == null)
{
_repositoryUser = new Library.Data.SqlServer.SqlServerUserRepository(ApplicationConnection);
}
return _repositoryUser;
}
}

public IRepositoryCourses RepositoryCourses
{
get
{
if (_repositoryCourses == null)
{
_repositoryCourses = new Library.Data.SqlServer.SqlServerCoursesRepository(ApplicationConnection);
}
return _repositoryCourses;
}
}
}

Comme vous pouvez le constater ci dessus, je vais avoir deux accesseurs publics qui me renverront un interface de repository. A l’interieur de ces accesseurs je m’occuperais d’instancier le repository qui m’interessera.

Maintenant, comme vous avez pu le constater, ces repository de type sql server prennent en argument une connection ouverte à une base de données. Je me retrouve donc au même point que précedemment. Sauf que j’ai ici déclaré ma classe comme implémentant l’interface Idisposable, ce qui va me permettre d’agir lors de la destruction de celle ci.

Je vais ainsi pouvoir créer ma connection et l’ouvrir lors du premier besoin grâce à :

IDbConnection _ApplicationConnection;

public IDbConnection ApplicationConnection
{
get
{
if (_ApplicationConnection == null)
_ApplicationConnection = LoadApplicationConnection();
return _ApplicationConnection;
}
}

private IDbConnection LoadApplicationConnection()
{
IDbConnection conn;
//retrieve the connection string to use
if (WebConfigurationManager.ConnectionStrings["Application"]==null)
throw new Exception( » A connection string  should be defined in the connections strings of the web config with the name ‘Application’ »);

//create the connection to the Application Database and open it
conn = DataProviderFactory.GetInstance().GetConnection();
conn.Open();
return conn;
}

public void Dispose()
{
if (_ApplicationConnection != null)
{
_ApplicationConnection.Close();
_ApplicationConnection.Dispose();
}

}

La connection à la base de données ne sera appelée qu’une seule fois. car elle sera hébergée dans la variable privée et donc lors d’un appel au repository pour ressortir quelque chose de ma base de données, celle ci aura une connection toute fraiche qui l’attendra.
Et donc voila, j’ai une classe qui hébergera des blocs d’informations sous forme de repository et qui s’occupera des obligations liées à chaque type de repository. Maintenant, il va s’agir de trouver comment faire pour utiliser cette classe dans le cadre d’une application web. Pour cela rien de plus simple: il suffit de faire hériter ses conteneurs web ( Page, WebService, etc..) d’une même interface qui contiendra un accesseur vers notre repository store :

interface IWebContainer
{
//repository storage
RepositoryStore Store {get;}
}

public class BasePage : System.Web.UI.Page, IKjuWebContainer
{
RepositoryStore  _Store;
//repository storage
public RepositoryStore Store
{
get
{
if (_Store==null) _Store= new RepositoryStore ();
return _Store;
}
}
}

La page lors de sa destruction appelera la méthode Dispose() de notre RepositoryStore ce qui amenera la fermeture de notre connection à la base de données.

Le prochain post traitera d’un exemple de repository : les procédures stockée sous sql server 2005.

public IDbConnection ApplicationConnection

{

get

{

if (_ApplicationConnection == null)

_ApplicationConnection = LoadApplicationConnection();

return _ApplicationConnection;

}

}

Publié par : yoannr | 21 juin 2009

Un repository, qu’est ce que c’est…

Peu importe, l’architecture considérée,  un moment ou à un autre, on aura affaire aux données. Celles ci sont une partie indispensable de toute application. Mais ce bloc de donnée possède généralement ses caractéristiques propres. On n’accède pas de la même façon à une base de données, ou à un ensemble de webservice ou encore un fichier xml. Il faudra donc développer des mécanismes propres à chaque type de stockage.

Par contre, ce qu’une application fera avec sera invariant.Si une application doit gérer une liste de participant, celle ci devra demander à son bloc de données : « donne moi la liste des participants ». Apres peu importe pour l’application que le bloc de données aille faire appel a une procédure stockée ou appelle un adresse web pour récupérer ses informations.

Plus important encore, pour l’application peu importe sous quelle forme le bloc de données rapporte initialement les données.  Lorsqu’elle vont être utilisées, les informations devront être sous une seul type de format : celui que l’application comprend, à savoir un composite de ses objets métiers.

cette introduction faite , on peut convenir qu’un repository permet de définir un type de stockage.  Il peut y avoir plusieurs types de stockage possible pour une application (XML, BDD, etc..) . Il convient donc d’instaurer une interface sur notre stockage de données afin de pouvoir permuter simplement si besoin était notre façon d’accéder à nos données.

Voici un exemple :

public interface IFestivalVilleRepository
{

List<Participant> GetParticipants(FestivalFilter filter);
Participant GetParticipant(int Participantid, Festival f);
void SaveParticipant(Participant p, Festival f);
void DeleteParticipant(Participant p);
}

Cette interface commandera à notre dépôt de données (quel qu’il soit) des comportements. Nous savons donc que si je prends l’exemple ci dessus en appelant la fonction GetParticipants(FestivalFilter filter) nous obtiendrons une liste d’objets Participant.

Cela veut donc dire que l’appel à ce repository va nous permettre d’accéder directement à un composite d’objet de notre couche métier. Peu importe d’où provient l’information de base, que ce soit d’un appel à une base de données  ou un appel à un webservice, cette interface nous assurera que ce qui en sortira sera quelque chose de connu par l’application.

Schéma Basique de Repository Pattern

Schéma Basique de Repository Pattern

Ceci nous amene au prochain post : Les repository et leur utilisation en ASP.net

Publié par : yoannr | 17 juin 2009

Mais où aller chercher sa connection à sa base….

Au tout début ou je développais en asp.net, j’allais chercher ma connexion directement dans mon code behind de ma page, je l’instanciais et ensuite, je la disposais et ce autant de fois que je pouvais faire de requête dans cette page.

Bien évidemment, ce schéma d’action ne dura qu un temps. Je suis ensuite passer à un autre mode que j’avais trouvé à ce moment là bien plus judicieux et correspondant à tous mes besoins.

Je faisais hériter toutes mes pages web d’une même classe Base Page, et ainsi je créais une variable de page qui contiendrait un seul objet connexion pour toutes mes pages.

Cela se résumait au code suivant :

public class BasePage : System.Web.UI.Page
{

/// <summary>
/// Connection to the database of this application
/// </summary>
public IDbConnection ApplicationConnection
{
get
{
return MonContext.GetConnection();
}
set
{
MonContext.SetConnection(value);
}
}

public BasePage()
{

}

protected override void InitializeCulture()
{

//retrieve the connection string to use
string connectionstring = WebConfigurationManager.ConnectionStrings[MonAppli.DataBaseName.ToString()].ToString();

//create the connection to the Application Database and open it
ApplicationConnection = DataProviderFactory.GetInstance().GetConnection(connectionstring);
ApplicationConnection.Open();

base.InitializeCulture();
}

protected override void OnUnload(EventArgs e)
{
if (ApplicationConnection != null)
{
//Close the connection to the Application Database and destroy it
ApplicationConnection.Close();
ApplicationConnection.Dispose();
ApplicationConnection = null;
}
base.OnUnload(e);
}

}

Tout allait très bien tant que mes sites web se résumait à un ensemble de pages. Mais quid ensuite des webcontroles et des webservices?

Et bien ma première inspiration fut de travailler de la même façon dans un premier temps, en créant et détruisant un objet connexion dans des classe baseWebControl et BaseWebservice. Malheureusement, comme vous pouvez le penser ce système n’est pas la panacée. On va multiplier le nombre de connexion actuellement ouverte par le nombre de contrôle présent sur la page.

Alors comment faire?

Jusqu’à présent , je suis parti du postulat que ma couche web devait être l’instigatrice de l’ouverture de ma connexion à la base de données. La demande créant l’objet. Mais plus j’avance dans la modélisation de mes applications, plus je me rends compte du manque de pérennité d’une telle solution. Par contre,  ce système permettait de s’extraire complètement de la création destruction d’un objet Connection ce qui était plutôt pratique.

Afin de continuer la reflexion un petit point sur la notion des repository

Messages Plus Anciens »

Catégories