Ecriture d'un composant complet pour la gestion panier de commande d'un site de e-Commerce, asp.net 2.0 et c#

Presentation

L'ecriture d'un panier de commande pour un site de e-Commerce est un exercice reccurent, et on a souvent tendance a refaire les memes developpements, pour en finir une bonne fois pour toute j'ai voulu faire un composant completement réutilisable dans tous les sites que je suis amené a faire.

Cahier des charges :

- Pas de code c# à produire concernant la gestion intrinseque du panier (uniquement de la manipulation de controles asp.net).

- Le panier doit etre completement webdesignable

- Les controles suivants doivent etre implémentés :

- Ajout par Hyperlien

- Ajout par Bouton

- Ajout par Bouton Image

- Un controle d'information indiquant le nombre d'item dans le panier

- Une grille affichant tous les items du panier (colonnes parametrables)

Chacun des controles d'ajout doit etre couplé ou pas avec une textbox sensée prendre en compte la quantité du produit a mettre dans le panier.

On doit avoir le choix de la strategie de retention d'information concernant les items mis dans le panier de l'internaute.

Un item doit contenir les informations suivante :

  • Un Id unique
  • Une date de création
  • Un Id de produit
  • Un code produit
  • Une quantité
  • Un prix public
  • Une unité de vente
  • Une reduction
  • Une quantité disponible
  • Un taux de TVA
  • Une date de livraison possible
  • Un lien html vers le produit
  • Une unité de conditionnement standard

 

Conception :

Une etude du CDC m'ammene a mettre en place les classes suivantes :

 

 

Cart

Donc nous avons une classe Cart qui a pour responsabilité la gestion du panier de manière abstraite, j'ai utilisé le pattern Provider mis en avant maintenant avec le framework asp.net 2.0 il est très interessant et façile a mettre en oeuvre.

Au niveau de la configuration du site il faut mettre en place les sections suivantes dans le fichier web.config

<?xml version="1.0" encoding="utf-8" ?>
<
configuration>
    <
configSections>
      <
sectionGroup name="Serialcoder">
        <
section name="cartService" type="Serialcoder.ShoppingCart.CartServiceSection,Serialcoder.ShoppingCart" />
      </
sectionGroup>
    </
configSections>
  <
Serialcoder>
    <
cartService defaultProvider="SessionCartProvider">
      <
providers>
        <
add name="SessionCartProvider"
            type="Serialcoder.ShoppingCart.Providers.SessionCartProvider"
            minimalOrder="0"
            allowSameProducts="false"/>
      </
providers>
    </
cartService>
  </
Serialcoder>

</configuration>

Par defaut je prends un provider concret utilisant les sessions asp.net, on peut voir au passage qu'il tout a fait possible d'ecrire un autre provider qui ecrirait directement en base de données ,dans un fichier xml ou dans une file MSMQ pour les sites a très fort trafic par exemple mais je prefere rester simple.

Le provider abstrait CartProvider a la responsabilité de gerer la liste des items (CartItem) mis dans le panier de commande. Il y a un certain nombre de propriétés qui permettent de parametrer le comportement du panier, notamment AllowSameProduct (Autorise l'ajout d'un meme produit dans le panier, sinon augmente la quantité). Minimal Order, le minimum de commande.

Dans l'avenir il y aura les regles suivantes a mettre en place :

  • Integration de la gestion d'une tva adaptée a chaque pays (utilisation d'une strategie localisée).
  • Integration de regle de prix en fonction de la quantité
  • Integration de prix en fonction du client
  • Forcer la saisie d'une quantité fonction du packaging

Interface utilisateur

Le nombre de controles asp.net se divisent en 3 groupes, ceux qui permentent d'ajouter des items dans le panier, ceux qui permettent d'afficher le contenu du panier, et ceux qui permettent d'avoir des informations rapides sur le panier.

AddToCart

AddToCartWebControls

J'ai voulu mettre 3 types de controles possibles pour ajouter un item dans le panier un bouton, un lien ou une image.

Chacun de ces controles herite d'une classe abstraite AddToCart , sa responsabilité est de gerer les propriétés des items et de les faire persister dans le viewstate. Cette classe herite bien entendu de WebControl et elle implémente l'interface IPostBackEventHandler.

Elle a la responsabilité de rechercher dans l'arbre de controles si une textbox contenant la quantité est presente et le cas echéant d'affecter la quantité à cette textbox si rien n'est indiqué dans celle-ci. Cette opération est réalisée lors de l'evenement OnPreRender

 

        protected override void OnPreRender(EventArgs e)
{
Context.Trace.Write("AddToCart","OnPreRender");
// Rechercher la boite de texte
if (QuantityTextBox != null && QuantityTextBox != string.Empty)
{
Control c = Parent.FindControl(QuantityTextBox);
if (c != null && c is TextBox)
{
TextBox box = c as TextBox;
if (box.Text == null || box.Text == string.Empty)
{
box.Text = DefaultQuantity.ToString();
}
}
}
base.OnPreRender (e);
}

 


Lors d'un postback sur la page en cours, l'evenement est trappé par la methode RaisePostBackHandler car comme indiqué precedement, cette classe implemente l'interface IPostBackHandler.


 

        public void RaisePostBackEvent(string eventArgument)
{
Context.Trace.Write("AddToCartControl","RaisePostBackEvent");
Insert();
}

On peut voir que le seul travail qui doit etre réalisé a ce moment la est effectivement d'inserer l'item dans le panier.


 

        protected void Insert()
{
Context.Trace.Write("AddToCartControl","Insert");
// base.DataBind();
CartItem item = new CartItem();
if (QuantityTextBox == null)
{
item.Quantity = Quantity;
}
else
{
Control c = Parent.FindControl(QuantityTextBox);
if (c != null && c is TextBox)
{
try
{
TextBox box = c as TextBox;
int q = int.Parse(box.Text);
item.Quantity = q;
}
catch
{
Page.Validators.Add(new BadEntryValidator(BadQuantityErrorText));
return;
}
}
}
// Product
item.ProductId = ProductId;
item.Code = Code;
item.Availability = Availability;
item.Description = Description;
item.ImageUrl = ImageUrl;
item.ProductLink = ProductLink;
// Price
item.UnitSale = UnitSale;
item.Reduce = Reduce;
item.PublicPrice = PublicPrice;
item.TaxRate = TaxRate;
Cart.AddItem(item);
}

On retrouve bien a ce moment la notre service Cart , l'operation consiste a Creer une nouvelle instance de CartItem puis de renseigner chacune de ses propriétés avec celles du controle asp.net, au passage nous verifions que la textbox associé ne contient pas de caractère autre que des digits.


 


La mecanique d'insersion est commune a tous les controles d'ajout dans le panier, chacun des controles concrets se comportent en fonction de leur affichage d'une manière un peu differente.


 


Pour AddToCartButton


il suffit d'overrider la methode HtmlTextWriterTag et indiquer qu'il sagit d'un controle html de type Input


 

        [Browsable(false)]
protected override HtmlTextWriterTag TagKey
{
get
{
return HtmlTextWriterTag.Input;
}
}
 
Ce qui est visible pour un controle html de type button est le text, pour cela nous créons la propriété text que nous faisons persister dans le viewstate.
 
        [Bindable(true)]
[Category("CartItem")]
[Description("button text")]
public string Text
{
get
{
if (ViewState["Text"] == null)
{
return string.Empty;
}
return (string) ViewState["Text"];
}
set
{
ViewState["Text"] = value;
}
}

 


Ensuite pour bien forger le code html il faut ajouter quelques attributs, cette opération est réalisée en overridant la methode AddAttributesToRender


 

        protected override void AddAttributesToRender(HtmlTextWriter writer)
{
writer.AddAttribute(HtmlTextWriterAttribute.Type,"submit");
writer.AddAttribute(HtmlTextWriterAttribute.Name,this.UniqueID);
writer.AddAttribute(HtmlTextWriterAttribute.Value, Text);
}

On fait une petite verification au moment du rendu pour savoir si le controle est bien a l'interieur du tag <form/>


 

        protected override void Render(HtmlTextWriter writer)
{
if (Page != null)
{
Page.VerifyRenderingInServerForm(this);
}
base.Render (writer);
}
Voici un exemple d'utilisation dans le code d'une page aspx.
 
        <asp:TextBox ID="Textbox2" runat="server" Width="50"></asp:TextBox>
<cart:AddToCartButton ID="Addtocarthyperlink2" runat="Server" Availability="50" Code="P2"
DefaultQuantity="5" Description="Product2" ImageUrl="" ProductId="2" ProductLink=""
PublicPrice="15.0" Quantity="1" QuantityTextBox="TextBox2" Reduce="0" TaxRate="0.196"
Text="Add P2" UnitSale="1"></CART:AddToCartButton>

Concernant le controle AddToCartHyperLink


Une petite difficulté supplémentaire vient s'ajouter, en effet il faut "injecter" un peu de javascript pour effectuer le postback et non pas un GET.


Le tag utilisé pour un lien est le anchor :


 

        protected override HtmlTextWriterTag TagKey
{
get
{
return HtmlTextWriterTag.A;
}
}

Pour injecter le javascript nous overridons la methode AddAttributeToRender, et ajoutons le code javascript grace a la methode statique GetPostBackClientHyperlink


 

        protected override void AddAttributesToRender(HtmlTextWriter writer)
{
writer.AddAttribute(HtmlTextWriterAttribute.Name,this.UniqueID);
writer.AddAttribute(HtmlTextWriterAttribute.Href, Page.ClientScript.GetPostBackClientHyperlink(this, string.Empty));
}
Pour la mise en place du text, une propriété est créée a cette occasion :
 
        [Bindable(true)]
[Category("CartItem")]
[Description("text")]
public string Text
{
get
{
if (ViewState["Text"] == null)
{
return string.Empty;
}
return (string) ViewState["Text"];
}
set
{
ViewState["Text"] = value;
}
}

Comme ce controle peut etre utilisé avec un tag ouvert/fermé <a href="link">Title</a>, pour pouvoir recuper la valeur Title nous overridons la methode AddParsedSubObject


 

        protected override void AddParsedSubObject(object obj)
{
if (obj is LiteralControl)
{
Text = obj as string;
}
}

Attention pour que ça fonctionne il faut indiquer au PageParser que le contenu du tag est "parsable" via l'attribut ParseChildren(true)


 

    [ParseChildren(true)]
/// <summary>
///
/// </summary>
public class AddToCartHyperLink : AddToCart, IPostBackDataHandler

 


pour savoir quel lien a été cliqué, il suffit d'implementer l'interface IPostBackDataHandler


 

        #region IPostBackDataHandler Members

public void RaisePostDataChangedEvent()
{
// TODO: Add AddToCartHyperLink.RaisePostDataChangedEvent implementation
}

public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
{
string uniqueId = this.UniqueID;
if (postCollection[uniqueId] != null)
{
Page.RegisterRequiresRaiseEvent(this);
}
return false;
}

#endregion

Grace a la propriété UniqueID généré par le Framework nous pouvons savoir s'il s'agit bien du bon lien qui a été cliqué , et si c'est le cas, nous notifions la page avec ce controle via la commande Page.RegisterRequiresRaiseEvent, sinon le lien en cours n'a pas été cliqué.


Le fait d'appeler Page.RegisterRequiresRaiseEvent provoque l'action decrite precedement au niveau de la classe AddToCart et de son evenement RaisePostBackEvent


Un exemple d'utilisation dans une page aspx :


        <asp:TextBox ID="TextBox1" runat="server" Width="50"></asp:TextBox>
        <cart:AddToCartHyperLink ID="Addtocartimagebutton1" runat="Server" Availability="100"
            Code="P1" DefaultQuantity="1" Description="Product1" ImageUrl="" ProductId="1"
            ProductLink="" PublicPrice="10.0" Quantity="1" QuantityTextBox="TextBox1" Reduce="0"
            TaxRate="0.196" Text="Add P1" UnitSale="1"></CART:AddToCartHyperLink>


Concernant le controle AddToCartImageButton


La difficulté est de recuperer sur quelle image l'internaute a cliquer.


Le tag est de type Input :


 

        protected override HtmlTextWriterTag TagKey
{
get
{
return HtmlTextWriterTag.Input;
}
}

la propriété qui nous interesse est l'adresse source de l'image :


 

        [Bindable(true)]
[Category("CartItem")]
[Description("button image source")]
public string Src
{
get
{
if (ViewState["Src"] == null)
{
return string.Empty;
}
return (string) ViewState["Src"];
}
set
{
ViewState["Src"] = value;
}
}

Pour le rendu nous devons dans le cas ou il existe des validators, "injecter" du code javascript pour prendre en compte le traitement coté client :


 

        protected override void AddAttributesToRender(HtmlTextWriter writer)
{
if (Page != null)
{
Page.VerifyRenderingInServerForm(this);
}
writer.AddAttribute(HtmlTextWriterAttribute.Type,"image");
writer.AddAttribute(HtmlTextWriterAttribute.Name,this.UniqueID);
writer.AddAttribute(HtmlTextWriterAttribute.Src, ResolveUrl(this.Src));
if (Page != null && Page.Validators.Count > 0)
{
writer.AddAttribute(HtmlTextWriterAttribute.Onclick, "if (typeof(Page_ClientValidate) == \'function\') Page_ClientValidate();") ;
writer.AddAttribute("language", "javascript");
}
}

Pour connaitre le bouton qui a été cliqué dans la page , la classe doit implementer IPostBackDataHandler


 

        #region IPostBackDataHandler Members

public void RaisePostDataChangedEvent()
{
// TODO: Add AddToCartImageButton.RaisePostDataChangedEvent implementation
}

public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
{
string uniqueId = this.UniqueID;
string x = postCollection[uniqueId + ".x"];
string y = postCollection[uniqueId + ".y"];
if (((x != null) && (y != null)) && ((x.Length > 0) && (y.Length > 0)))
{
Page.RegisterRequiresRaiseEvent(this);
}
return false;
}

#endregion

lors d'un post sur un bouton, le protocole html impose d'ajouter un .x et un .y indiquant les coordonnées du bouton, pour le retrouver, nous verifions dans la liste des paramètre postés si notre controle a bien été cliqué, le cas echéant nous notifions la page via la methode RegisterRequiresRaiseEvent en lui passant le controle en cours.


Un exemple d'implementation dans une page aspx :


        <cart:AddToCartHyperLink ID="Addtocarthyperlink1" runat="Server" Availability="50"
            Code="P3" DefaultQuantity="5" Description="Product3" ImageUrl="" ProductId="3"
            ProductLink="" PublicPrice="15.0" Quantity="1" Reduce="0" TaxRate="0.196" Text="Add P3"
            UnitSale="1"></cart:AddToCartHyperLink>


Voila pour la partie Ajout d'item dans le panier.


 


CartGrid


CartGrid


Je me suis inspiré (merci reflector) de la datagrid pour l'affichage des items.


Une première classe "Conteneur" CartGrid dont la responsabilité est l'affichage des items dans une table html.


Cette classe contient 2 propriétés importantes, Items (Collection de CartGridItem) et Columns (Collection de CartColumn)


CartGridItem , il s'agit d'un controle qui hérite de TableRow (typiquement il s'agit du tag tr )

CartColumn , il s'agit d'un controle qui hérite de webcontrole (typiquement il s'agit du tag td )


CartGrid hérite de WebControle et implémente l'interface INamingContainer, il "hoste" un certain nombre de controles templatés HeaderTemplate, une collection de CartColumn et un FooterTemplate


Le HeaderTemplate et le FooterTemplate contiennent des informations sur le controle lui meme via l'attribut TemplateContainer :


 

        [TemplateContainer(typeof(CartGrid))]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
[Bindable(false)]
[Browsable(false)]
public ITemplate HeaderTemplate
{
get { return headerTemplate; }
set { headerTemplate = value; }
}

Cela veut dire par exemple que si Cart possede la propriété Amount qu'un ecriture du genre


<cart:grid runat="server">



<headertemplate>


<%#Container.Amount%>


</headertemplate>


</cart:grid>


Affichera le montant du panier dans le header


Comme CartItem implémente l'attribut INamingContainer, la methode CreateChildControls est appelée, c'est par elle que la table html va etre créée.


 

        protected override void CreateChildControls()
{
Context.Trace.Write("CartGrid","CreateChildControls");
Controls.Clear();
CreateControlHierarchy();
ClearChildViewState();
}
lors de cet appel on s'occupe dans un premier temps de vider la collection de controles si tant est qu'il y en ai.
Ensuite nous appelelons la methode CreateControlHierarchy (je vais y revenir) et finissons par appeler la methode ClearChildViewState.
 
        protected virtual void CreateControlHierarchy()
{
Context.Trace.Write("CartGrid","CreateChildControls");

if (Cart.Count == 0)
{
if (EmptyCartTemplate != null)
{
Control c = new Control();
EmptyCartTemplate.InstantiateIn(c);
Controls.Add(c);
}
return;
}

if (HeaderTemplate != null)
{
Control c = new Control();
HeaderTemplate.InstantiateIn(c);
Controls.Add(c);
}

Table table = new Table();

Controls.Add(table);

// Create Header
CartGridItem item = new CartGridItem(ListItemType.Header, null,Columns);
InitializeItem(item);
table.Rows.Add(item);

// Bind CartItems
for(int i=0; i < Cart.Count; i++)
{
CartGridItem cgItem = new CartGridItem(ListItemType.Item,Cart.SelectItemByIndex(i), Columns);
cgItem.ID = "CartGridItem" + i;
InitializeItem(cgItem);
table.Rows.Add(cgItem);
Items.Add(cgItem);
}

// Create Footer
if (FooterTemplate != null)
{
TableCell cell = new TableCell();
FooterTemplate.InstantiateIn(cell);
cell.ColumnSpan = Columns.Count;
TableRow row = new TableRow();
row.Cells.Add(cell);
table.Rows.Add(row);
}
}
Une premiere verification consiste a n'afficher que le template Panier vide si celui ci ne contient pas d'item.
Ensuite on Ajoute le contenu du template Header dans l'arbre de controles, vous remarquerez une petite astuce au passage pour eviter de creer une classe qui ne ferait pas plus que ça.
Creation de la table Html
Creation de la première ligne de la table Html
 
            CartGridItem item = new CartGridItem(ListItemType.Header, null,Columns);
InitializeItem(item);
table.Rows.Add(item);
L'appel de la methode InitializeItem a pour but de d'appeler la methode InitializeCell des CartColumn :
        protected virtual void InitializeItem(CartGridItem item)
{
TableCellCollection cells = item.Cells;
TableCell cell;
for(int i =0; i < Columns.Count; i++)
{
cell = new TableCell();
Columns[i].InitializeCell(cell,i,item);
cells.Add(cell);
}
}
Ensuite on ajoute chaque Item :
            for(int i=0; i < Cart.Count; i++)
{
CartGridItem cgItem = new CartGridItem(ListItemType.Item,Cart.SelectItemByIndex(i), Columns);
cgItem.ID = "CartGridItem" + i;
InitializeItem(cgItem);
table.Rows.Add(cgItem);
Items.Add(cgItem);
}
Chaque CartGridItem peut etre d'un type different , nous allons le voir plus tard.
Meme traitement pour le Footer que pour Header.
On peut noter lors du Rendu :
 
        protected override void Render(HtmlTextWriter writer)
{
if (Controls.Count > 0 && Cart.Count > 0)
{
foreach(Control c in Controls)
{
if (c is Table)
{
Table table = c as Table;
if (table.ID == null)
{
table.Attributes.Add("id", this.ID);
}
table.CopyBaseAttributes(this);
break;
}
}
}
RenderContents(writer);
}
On recopie dans les attributs de la tables tout ceux qui ne sont pas connu via CopyBaseAttributes et surtout on affecte un Id a la table, car il sera possible de mettre 2 cartGrid dans une page.
Donc pour resumer concernant CartGrid, il crée une table , ajoute la première ligne et delegue au controle CartGridItem sont propre affichage par ligne.

Passons au CartGridItem

Il s'agit d'un controle qui herite de TableRow, sont role est d'afficher le <tr></tr> avec ce qu'il y a dedans. Il implémente lui meme l'interface INamingContainer.
Pour eviter d'avoir a Databinder la page "manuellement", ceci est effectué au moment du PreRendering :
 
        protected override void OnPreRender(EventArgs e)
{
base.DataBind();
}
Ceci est très important car si cette opération n'est pas réalisée, les appels du type <%#Container.Amount%> ne retournent rien.
Nous allons avoir a traiter des evenements a l'interieur de cette grille notamment La suppression d'un item ou le changement d'une quantité, pour eviter de faire remonter les evenements a la page, nous les stoppons en overridant OnBubbleEvent :
        protected override bool OnBubbleEvent(object source, EventArgs args)
{
if (args is CommandEventArgs)
{
RaiseBubbleEvent(this,args);
return true;
}
return false;
}
Comme la classe CartGrid possede l'attribut [DefaultProperty("Columns")] et qu'elle implémente INamingContainer. une ecriture du type 
<cart:grid runat="server">

<columns>
<cart:MonControle runat="server"/>
</columns>
</cart:grid>
est possible, maintenant tout l'art consiste a matcher la liste des colonnes et les propriétés des items, comment allons nous faire ?
Nous crééons une classe abstraite de base CartColumn
Celle ci hérite de WebControl et j'ai fait de telle sorte qu'elle soit autovalidé en implémentant IValidator, ceci dans le cas de la validation d'une quantité.
Nous avons vu tout a l'heure que la classe CartItem lors de la création de son arbre de controle appelait la methode InitializeItem.
Revenons sur ceci :
 
            TableCellCollection cells = item.Cells;
TableCell cell;
for(int i =0; i < Columns.Count; i++)
{
cell = new TableCell();
Columns[i].InitializeCell(cell,i,item);
cells.Add(cell);
}
CartItem contient via sa propriété Columns une collection de CartColumn, chacune est appelée via la Methode InitializeCell, a ce moment la on passe en paramètre le controle TableCell, la position et le CartItem.
 
        public virtual void InitializeCell(TableCell cell, int index, CartGridItem item)
{
if (item.ItemType == ListItemType.Header)
{
InitializeCellHeader(cell , index);
}
else if(item.ItemType == ListItemType.Item)
{
cell.ApplyStyle(ItemStyle);
}
}
 
        private void InitializeCellHeader(TableCell cell, int index)
{
if (HeaderText.Length > 0)
{
cell.Text = HeaderText;
}
else
{
cell.Text = "&nbsp;";
}
cell.ApplyStyle(HeaderStyle);
}
il n'y a plus qu'a inserer les informations dans le "<td>".
Chaque Colonne typée est responsable de son rendu
J'ai dénombré 4 types de colonnes.

1 - DeleteCartColumnButton

son but est d'afficher un bouton supprimer la ligne, cette opération est réalisée en overridant la methode InitializeCell


 

        public override void InitializeCell(System.Web.UI.WebControls.TableCell cell, int index, CartGridItem item)
{
base.InitializeCell (cell, index, item);
if (item.ItemType == ListItemType.Item)
{
Button button = new Button();
button.CommandName = "Delete";
button.CommandArgument = item.Item.Id.ToString();
button.Command += new CommandEventHandler(button_Command);
button.Text = this.Text;
cell.Controls.Add(button);
}
}
Au passage on s'abonne a l'evenement Command de l'objet bouton que l'on vient d'ajouter
 
        private void button_Command(object sender, CommandEventArgs e)
{
System.Guid id = new Guid((string) e.CommandArgument);
CartItem item = Cart.SelectItemByGuid(id);
if (item != null)
{
Cart.RemoveItem(item);
}
}
On retrouve via le commandArgument l'item par son GUID et on le retire du panier de commande.

QuantityCartColumn et QuantityCartColumnTextBox

le role de cette classe est d'afficher une colonne avec une textbox contenant la quantité de l'item.


 

        public override void InitializeCell(System.Web.UI.WebControls.TableCell cell, int index, CartGridItem item)
{
base.InitializeCell (cell, index, item);

if (item.ItemType == ListItemType.Item)
{
box = new TextBox();
box.Text = item.Item.Quantity.ToString();
box.ID = "QuantityTextBox";
box.CopyBaseAttributes(this);
box.TextChanged += new EventHandler(box_TextChanged);
cell.Controls.Add(box);
}
}

Au passage on s'abonne a l'evenement TextChanged du controle textbox


 

        private void box_TextChanged(object sender, EventArgs e)
{
TextBox boxChange = (TextBox) sender;
// TR/TD
CartGridItem gi = (CartGridItem) boxChange.Parent.Parent;
try
{
gi.Item.Quantity = int.Parse(boxChange.Text);
}
catch
{
IsValid = false;
ErrorMessage = "Vous devez indiquer une quantite9 valide";
}
}

et on change la quantité du CartGridItem


cette classe est autovalidé, on l'ajoute a la collection des validators dans l'evenement OnInit et on retire dans OnUnLoad


 

        protected override void OnInit(EventArgs e)
{
Page.Validators.Add(this);
base.OnInit (e);
}

protected override void OnUnload(EventArgs e)
{
Page.Validators.Remove(this);
base.OnUnload (e);
}
Il suffit ensuite d'overrider la methode Validate pour tester la Textbox
 
        public override void Validate()
{
if (box.Text == null)
{
IsValid = false;
ErrorMessage = "Vous devez indiquer une quantite9";
return;
}
try
{
int.Parse(box.Text);
}
catch
{
IsValid = false;
ErrorMessage = "Vous devez indiquer une quantite9 valide";
return;
}
IsValid = true;
}

TemplateCartColumn

Cette classe a pour role d'afficher une des propriétés de cartitem.


 

        public override void InitializeCell(System.Web.UI.WebControls.TableCell cell, int index, CartGridItem item)
{
base.InitializeCell (cell, index, item);
if (item.ItemType == ListItemType.Item)
{
ITemplate content = itemTemplate;
if (content != null) // Test if Skin mode
{
content.InstantiateIn(cell);
}
}
}
Via sa propriété ItemTemplate il est possible par exemple d'ecrire 
<cart:templatecartcolumn runat="server">

<ItemTemplate>
<%#Container.Item.ProductLink%>
</ItemTemplate>
</cart:templatecartcolumn>
puisque l'on dit via l'attribut TemplateContainer que le type qui est bindé est CartGridItem
 
        private ITemplate itemTemplate;
[TemplateContainer(typeof(CartGridItem))]
public virtual ITemplate ItemTemplate
{
get { return itemTemplate; }
set { itemTemplate = value; }

}
 
Un exemple d'affichage pour la grille :
 
        <cart:cartgrid id="Cart1" runat="server" border="1" cellpadding="0" cellspacing="0"
width="100%">
<columns>
<CART:DeleteCartColumnButton runat="server" Text="Delete" type="Button"></CART:DeleteCartColumnButton>
<CART:QuantityCartColumnTextBox runat="server" HeaderText="Qty." maxlength="7" Size="4">
</CART:QuantityCartColumnTextBox>
<CART:TemplateCartColumn runat="server" HeaderText="Code">
<ITEMTEMPLATE ><A href="<%#Container.Item.ProductLink%>"><%#Container.Item.Code%></A></ITEMTEMPLATE>
</CART:TemplateCartColumn>
<CART:TemplateCartColumn runat="server" HeaderText="Description">
<HEADERSTYLE Font-Bold="True" />
<ITEMTEMPLATE ><%#Container.Item.Description%></ITEMTEMPLATE>
</CART:TemplateCartColumn>
<CART:TemplateCartColumn runat="Server" HeaderText="Dispo.">
<ITEMTEMPLATE ><%#Container.Item.Availability%></ITEMTEMPLATE>
</CART:TemplateCartColumn>
<CART:TemplateCartColumn runat="Server" HeaderText="Amount (HT)">
<ITEMSTYLE BackColor="#4A3C8C" Font-Bold="True" HorizontalAlign="Right" ForeColor="#F7F7F7" />
<ITEMTEMPLATE ><%#Container.Item.RealPrice%></ITEMTEMPLATE>
</CART:TemplateCartColumn><CART:TemplateCartColumn runat="Server" HeaderText="Total (HT)">
<ITEMTEMPLATE ><%#Container.Item.FreeTaxAmount%></ITEMTEMPLATE>
</CART:TemplateCartColumn>
</columns>
<footertemplate >
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=1><TBODY><TR><TD width="100%" rowSpan=3><asp:Button ID="Button1" Text="Recalc" Runat="server"></asp:Button> </TD><TD>Total HT :</TD><TD><%#Container.FreeTaxAmount%></TD></TR><TR><TD>TVA :</TD><TD><%#Container.TaxAmount%></TD></TR><TR><TD>Total TTC :</TD><TD><%#Container.Amount%></TD></TR></TBODY></TABLE>
<CART:EmptyCartButton ID="Emptycartbutton1" runat="Server" name="Emptycartbutton1"
Text="Empty cart"></CART:EmptyCartButton>
</footertemplate>
<EmptyCartTemplate>
your cart is empty ...
</EmptyCartTemplate>
</cart:cartgrid>
 

Informations sur le panier


Information


 


Ce projet est sur le repository CodePlex a l'adresse suivante :


http://www.codeplex.com/openshoppingcart

1 commentaire:

Stéphane a dit…

Salut Marc,

Je viens de tomber par hasard sur cet article et donc sur ton blog :)

Tu te souviendras sûrement de moi, qui suis un de tes anciens collègues, du temps de ton court passage dans la région bordelaise ;-)

A une prochaine fois sur la toile :)

SP