Skip to content

9. کامپوننت‌های سرور ASP – ۳

9.1. Introduction

ما با توسعه بیشتر قابلیت‌های کامپوننت‌های [DataList] و [DataGrid]، به‌ویژه در زمینه به‌روزرسانی داده‌هایی که نمایش می‌دهند، به کار خود بر روی رابط کاربری ادامه می‌دهیم

9.2. پردازش رویدادهای مرتبط با داده‌ها در کامپوننت‌های داده‌بسته

9.2.1. مثال

صفحه زیر را در نظر بگیرید:

این صفحه شامل سه کامپوننت مرتبط با یک لیست داده است:

  • یک کامپوننت [DataList] با نام [DataList1]
  • یک کامپوننت [DataGrid] با نام [DataGrid1]
  • یک کامپوننت [Repeater] به نام [Repeater1]

فهرست داده‌های مرتبط آرایهٔ {"zero", "one", "two", "three"} است. هر یک از این موارد داده با جفت دکمه‌ای با برچسب‌های [Infos1] و [Infos2] مرتبط است. وقتی کاربر روی یکی از دکمه‌ها کلیک می‌کند، یک کادر متن نام دکمهٔ کلیک‌شده را نمایش می‌دهد. هدف در اینجا نشان دادن نحوهٔ مدیریت یک فهرست دکمه‌ها یا پیوندها است.

9.2.2. پیکربندی کامپوننت‌ها

کامپوننت [DataList1] به شرح زیر پیکربندی شده است:


                        <asp:datalist id="DataList1" ... runat="server">
                            <SelectedItemStyle ...</SelectedItemStyle>
                            <HeaderTemplate>
                                [début]
                            </HeaderTemplate>
                            <FooterTemplate>
                                [fin]
                            </FooterTemplate>
                            <ItemStyle ...></ItemStyle>
                            <ItemTemplate>
                                <P><%# Container.DataItem %>
                                    <asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
                                    <asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
                            </ItemTemplate>
                            <FooterStyle ...></FooterStyle>
                            <HeaderStyle ...></HeaderStyle>
                        </asp:datalist>

ما همه چیز مربوط به ظاهر [DataList] را حذف کرده‌ایم تا صرفاً بر محتوای آن تمرکز کنیم:

  • بخش <HeaderTemplate> سربرگ [DataList] را تعریف می‌کند و بخش <FooterTemplate> پاورقی آن را تعریف می‌کند.
  • بخش <ItemTemplate> قالب نمایشی است که برای هر آیتم در فهرست داده‌های مرتبط استفاده می‌شود. این بخش شامل عناصر زیر است:
    • مقدار آیتم داده فعلی از لیست داده‌های مرتبط با کامپوننت: <%# Container.DataItem %>
    • دو دکمه با برچسب‌های [Infos1] و [Infos2] به ترتیب. کلاس [Button] دارای یک ویژگی [CommandName] است که در اینجا استفاده می‌شود. این به ما امکان می‌دهد تا تشخیص دهیم کدام دکمه رویدادی را در [DataList] ایجاد کرده است. برای رسیدگی به کلیک‌ها روی دکمه‌ها، ما یک رویدادپرداز واحد خواهیم داشت که به خود [DataList] متصل خواهد بود، نه به دکمه‌ها. این رویدادگیر اطلاعات مربوط به خطی که کلیک در [DataList] روی آن رخ داده است را دریافت می‌کند. ویژگی [CommandName] به ما امکان می‌دهد تا تشخیص دهیم کلیک روی کدام دکمه در آن خط انجام شده است.

کامپوننت [Repeater1] به روشی بسیار مشابه پیکربندی می‌شود:


                        <asp:repeater id="Repeater1" runat="server">
                            <HeaderTemplate>
                                [début]<hr />
                            </HeaderTemplate>
                            <FooterTemplate>
                                <hr />
                                [fin]
                            </FooterTemplate>
                            <SeparatorTemplate>
                                <hr />
                            </SeparatorTemplate>
                            <ItemTemplate>
                                <%# Container.DataItem %>
                                <asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
                                <asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
                            </ItemTemplate>
                        </asp:repeater>

ما به سادگی یک بخش <SeparatorTemplate> اضافه کرده‌ایم تا داده‌های متوالی نمایش داده شده توسط کامپوننت با یک خط افقی از هم جدا شوند.

در نهایت، کامپوننت [DataGrid1] به شرح زیر پیکربندی شده است:


                        <asp:datagrid id="DataGrid1" ... runat="server" PageSize="2" AllowPaging="True">
                            <SelectedItemStyle ...></SelectedItemStyle>
                            <AlternatingItemStyle ...></AlternatingItemStyle>
                            <ItemStyle ...></ItemStyle>
                            <HeaderStyle ...></HeaderStyle>
                            <FooterStyle ....></FooterStyle>
                            <Columns>
                                <asp:ButtonColumn Text="Infos1" ButtonType="PushButton" CommandName="Infos1">
                              </asp:ButtonColumn>
                                <asp:ButtonColumn Text="Infos2" ButtonType="PushButton" CommandName="Infos2">
                              </asp:ButtonColumn>
                            </Columns>
                            <PagerStyle .... Mode="NumericPages"></PagerStyle>
                        </asp:datagrid>

در اینجا نیز، اطلاعات مربوط به استایل (رنگ‌ها، عرض‌ها و غیره) را حذف کرده‌ایم. ما در حالت تولید خودکار ستون هستیم که حالت پیش‌فرض برای [DataGrid] است. این بدان معناست که تعداد ستون‌ها برابر با تعداد ستون‌های موجود در منبع داده خواهد بود. در اینجا، یکی وجود دارد. ما دو ستون دیگر را با تگ <asp:ButtonColumn> اضافه کرده‌ایم. در این ستون‌ها، اطلاعاتی مشابه آنچه برای دو کامپوننت دیگر تعریف شده است، و همچنین نوع دکمه، که در این مورد [PushButton] است، تعریف می‌کنیم. نوع پیش‌فرض [LinkButtonc.a.d است. یک لینک. علاوه بر این، داده‌ها با [AllowPaging=true] صفحه‌بندی خواهند شد با اندازه صفحه دو آیتم [PageSize=2].

9.2.3. کد چیدمان صفحه

کد چیدمان برای صفحهٔ مثال ما در فایلی به نام [main.aspx] قرار داده شده است:


<%@ page codebehind="main.aspx.vb" inherits="vs.main" autoeventwireup="false" %>
<HTML>
    <HEAD>
    </HEAD>
    <body>
        <form runat="server">
            <P>Gestion d'événements de composants associés à des listes de données</P>
            <HR width="100%" SIZE="1">
            <table cellSpacing="1" cellPadding="1" bgColor="#ffcc00" border="1">
                <tr>
                    <td ...>DataList</td>
                    <td ...>DataGrid</td>
                    <td ...>Repeater</td>
                </tr>
                <tr>
                    <td ...>

                      <asp:datalist id="DataList1" ... runat="server">
                            <HeaderTemplate>
                                [début]
                            </HeaderTemplate>
                            <FooterTemplate>
                                [fin]
                            </FooterTemplate>
                            <ItemStyle ...></ItemStyle>
                            <ItemTemplate>
                                <P><%# Container.DataItem %>
                                    <asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
                                    <asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
                            </ItemTemplate>
                            <FooterStyle ...></FooterStyle>
                            <HeaderStyle ....></HeaderStyle>
                        </asp:datalist>

                        <P></P>
                    </td>
                    <td ...>

                       <asp:datagrid id="DataGrid1" ... runat="server" PageSize="2" AllowPaging="True">
                            <AlternatingItemStyle ...></AlternatingItemStyle>
                            <ItemStyle ...></ItemStyle>
                            <HeaderStyle ...></HeaderStyle>
                            <FooterStyle ...></FooterStyle>
                            <Columns>
                                <asp:ButtonColumn Text="Infos1" ButtonType="PushButton" CommandName="Infos1"> 
                              </asp:ButtonColumn>
                                <asp:ButtonColumn Text="Infos2" ButtonType="PushButton" CommandName="Infos2">
                               </asp:ButtonColumn>
                            </Columns>
                            <PagerStyle ... Mode="NumericPages"></PagerStyle>
                        </asp:datagrid>

                  </td>
                    <td ...>

                       <asp:repeater id="Repeater1" runat="server">
                            <HeaderTemplate>
                                [début]<hr />
                            </HeaderTemplate>
                            <FooterTemplate>
                                <hr />
                                [fin]
                            </FooterTemplate>
                            <SeparatorTemplate>
                                <hr />
                            </SeparatorTemplate>
                            <ItemTemplate>
                                <%# Container.DataItem %>
                                <asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
                                <asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
                            </ItemTemplate>
                        </asp:repeater>

                  </td>
                </tr>
            </table>
            <P><asp:label id="lblInfo" runat="server"></asp:label></P>
            <P></P>
        </form>
    </body>
</HTML>

در کد بالا، کدهای قالب‌بندی (رنگ‌ها، خطوط، اندازه‌ها و غیره) را حذف کرده‌ایم.

9.2.4. کد کنترل صفحه

کد کنترل برنامه در فایل [main.aspx.vb] قرار داده شده است:

Public Class main
    Inherits System.Web.UI.Page

     'اجزای صفحه
    Protected WithEvents DataList1 As System.Web.UI.WebControls.DataList
    Protected WithEvents lblInfo As System.Web.UI.WebControls.Label
    Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
    Protected WithEvents Repeater1 As System.Web.UI.WebControls.Repeater

     'منبع داده
    Protected textes() As String = {"zéro", "un", "deux", "trois"}

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        If Not IsPostBack Then
             'پیوند به منبع داده
            DataList1.DataSource = textes
            DataGrid1.DataSource = textes
            Repeater1.DataSource = textes
            Page.DataBind()
        End If
    End Sub

    Private Sub DataList1_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles DataList1.ItemCommand
         ' یک رویداد در یکی از خطوط در [datalist] رخ داد
        lblInfo.Text = "Vous avez cliqué sur le bouton [" + e.CommandName + "] de l'élément [" + e.Item.ItemIndex.ToString + "] du composant [DataList]"
    End Sub

    Private Sub Repeater1_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.RepeaterCommandEventArgs) Handles Repeater1.ItemCommand
         ' یک رویداد در یکی از خطوط [repeater] رخ داد
        lblInfo.Text = "Vous avez cliqué sur le bouton [" + e.CommandName + "] de l'élément [" + e.Item.ItemIndex.ToString + "] du composant [Repeater]"
    End Sub

    Private Sub DataGrid1_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.ItemCommand
         'یک رویداد در یکی از خطوط در [datagrid] رخ داده است
        lblInfo.Text = "Vous avez cliqué sur le bouton [" + e.CommandName + "] de l'élément [" + e.Item.ItemIndex.ToString + "] de la page [" + DataGrid1.CurrentPageIndex.ToString() + "] du composant [DataGrid]"
    End Sub

    Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
         'تغییر صفحه
        With DataGrid1
            .CurrentPageIndex = e.NewPageIndex
            .DataSource = textes
            .DataBind()
        End With
    End Sub
End Class

نظرات:

  • منبع داده [textes] یک آرایه ساده از رشته‌های متنی است. این منبع به سه مؤلفه روی صفحه متصل خواهد شد
  • این ارتباط در رویه [Page_Load] در طول اولین پرس‌وجو برقرار می‌شود. برای پرس‌وجوهای بعدی، سه مؤلفه مقادیر خود را از طریق مکانیزمی که در [VIEW_STATE] تعریف شده است، بازیابی خواهند کرد.
  • این سه مؤلفه دارای یک دست‌پرداز برای رویداد [ItemCommand] هستند. این رویداد زمانی رخ می‌دهد که دکمه‌ای یا پیوندی در یکی از سطرهای مؤلفه کلیک شود. دست‌پرداز دو قطعه اطلاعات دریافت می‌کند:
    • source: مرجع شیء (دکمه یا لینک) که رویداد را فعال کرده است
    • a: اطلاعاتی درباره رویداد از نوع [DataListCommandEventArgs]، [RepeaterCommandEventArgs] یا [DataGridCommandEventArgs]، بسته به مورد. آرگومان «a» اطلاعات مختلفی را در خود جای می‌دهد. در اینجا، دو مورد از این اطلاعات برای ما از اهمیت ویژه‌ای برخوردارند:
      • a.Item: نمایانگر خطی است که رویداد در آن رخ داده است، از نوع [DataListItem]، [DataGridItem] یا [RepeaterItem]. صرف‌نظر از نوع دقیق، عنصر [Item] دارای یک ویژگی [ItemIndex] است که شماره خط [Item] کانتینری را که به آن تعلق دارد، نشان می‌دهد. ما در اینجا این شماره خط را نمایش می‌دهیم
      • a.CommandName: این ویژگی [CommandName] دکمه (Button, LinkButton, ImageButton) است که رویداد را فعال کرده است. این اطلاعات، در ترکیب با مورد قبلی، به ما امکان می‌دهد تا تشخیص دهیم کدام دکمه در کانتینر رویداد [ItemCommand] را فعال کرده است.

9.3. برنامه – مدیریت فهرست اشتراک‌ها

اکنون که می‌دانیم چگونه رویدادهای رخ‌داده در یک محفظه داده را رهگیری کنیم، مثالی ارائه می‌دهیم که نشان می‌دهد چگونه می‌توان آن‌ها را مدیریت کرد.

9.3.1. مقدمه

برنامه‌ای که در اینجا ارائه شده، شبیه‌سازی یک برنامهٔ اشتراک‌گذاری لیست پستی را انجام می‌دهد. این موارد توسط یک شی سه‌ستونی [DataTable] تعریف شده‌اند:

نام
نوع
نقش
id
رشته
کلید اصلی
thème
رشته
نام تم لیست
description
رشته
توضیحی از موضوعات پوشش‌داده‌شده توسط فهرست

برای سادگی مثال، شیء [DataTable] بالا به‌طور دلخواه از طریق کد ساخته خواهد شد. در یک کاربرد واقعی، احتمالاً این توسط متد یک کلاس دسترسی به داده فراهم می‌شود. جدول لیست در رویه [Application_Start] ساخته خواهد شد و جدول حاصل در برنامه قرار داده خواهد شد. ما این جدول را [dtThèmes] می‌نامیم. کاربر به موضوعات خاصی در این جدول مشترک خواهد شد. فهرست اشتراکات او نیز، مجدداً، در یک شیء به نام [dtAbonnements] که ساختاری به شرح زیر دارد، ذخیره خواهد شد:

name
نوع
نقش
id
رشته
کلید اصلی
thème
رشته
نام تم لیست

اپلیکیشن تک‌صفحه‌ای به شرح زیر است:

خیر.
نام
نوع
ویژگی‌ها
نقش
1
dgThèmes
DataGrid
 
فهرست‌های پستی برای اشتراک‌پذیری در دسترس هستند
2
dlAbonnements
DataList
 
فهرست اشتراک‌های کاربر در لیست‌های فوق
3
panelInfos
پنل
 
پنل اطلاعات در مورد موضوع انتخاب‌شده توسط کاربر، با یک لینک [Plus d'informations]
4
lblThème
برچسب
بخشی از [panelInfos] است
نام تم
5
lblDescription
برچسب
بخشی از [panelInfos] است
توضیحات تم
6
lblInfo
برچسب
 
پیام اطلاعات برنامه

مثال ما با هدف نشان دادن نحوه استفاده از کامپوننت‌های [DataGrid] و [DataList]، به‌ویژه مدیریت رویدادهایی که در سطح سطر در این محفظه‌های داده رخ می‌دهند، ارائه شده است. بنابراین، این برنامه دکمه‌ی ارسال ندارد که برای مثال انتخاب‌های کاربر را در پایگاه داده ذخیره کند. با این حال، این برنامه واقع‌گرایانه است. این برنامه شبیه یک اپلیکیشن تجارت الکترونیک است که در آن کاربر محصولات (اشتراک‌ها) را به سبد خرید خود اضافه می‌کند.

9.3.2. نحوه کار

اولین نمایی که کاربر می‌بیند به شرح زیر است:

کاربر برای مشاهده اطلاعات یک موضوع روی لینک‌های [Plus d'informations] کلیک می‌کند. این اطلاعات در [panelInfos] نمایش داده می‌شود:

Image

آنها برای اشتراک در یک موضوع روی لینک‌های [S'abonner] کلیک می‌کنند. موضوعات انتخاب‌شده به کامپوننت [dlAbonnements] اضافه می‌شوند:

Image

کاربر ممکن است بخواهد به فهرستی که قبلاً در آن مشترک شده است، دوباره مشترک شود. پیامی این موضوع را به او اطلاع می‌دهد:

Image

در نهایت، آنها می‌توانند با استفاده از دکمه‌های [Retirer] در بالا، از هر یک از موضوعات لغو اشتراک کنند. نیازی به تأیید نیست. این کار در اینجا ضروری نیست زیرا کاربر می‌تواند به راحتی دوباره اشتراک را فعال کند. در اینجا نما پس از لغو اشتراک از [thème1] آمده است:

Image

9.3.3. پیکربندی کانتینرهای داده

کامپوننت [dgThèmes]، از نوع [DataGrid]، به منبعی از نوع [DataTable] متصل است. قالب‌بندی آن با استفاده از لینک [Mise en forme automatique] در پنل ویژگی‌های [DataGrid] تعریف شده است. ویژگی‌های آن با استفاده از لینک [Générateur de proprités] در همان پنل تعریف شده است. کد تولید شده به شرح زیر است (کد قالب‌بندی حذف شده است):


                        <asp:datagrid id="dgThèmes" AutoGenerateColumns="False" AllowPaging="True" PageSize="5" 
                            runat="server">
                            <ItemStyle ...></ItemStyle>
                            <HeaderStyle ...></HeaderStyle>
                            <FooterStyle ...></FooterStyle>
                            <Columns>
                                <asp:BoundColumn DataField="th&#232;me" HeaderText="Th&#232;me"></asp:BoundColumn>
                                <asp:ButtonColumn Text="Plus d'informations" CommandName="infos"></asp:ButtonColumn>
                                <asp:ButtonColumn Text="S'abonner" CommandName="abonner"></asp:ButtonColumn>
                            </Columns>
                            <PagerStyle HorizontalAlign="Center" ... Mode="NumericPages"></PagerStyle>
                        </asp:datagrid>

لطفاً به نکات زیر توجه کنید:

AutoGenerateColumns=false
ما خودمان ستون‌های قابل نمایش را در بخش <columns>...</columns> تعریف می‌کنیم
AllowPaging=true
PageSize=5
برای صفحه‌بندی داده‌ها
<asp:BoundColumn>
ستون [thème] را تعریف می‌کند (HeaderText) از [DataGrid]، که به ستون [thème] در منبع داده (DataField) پیوند داده خواهد شد
<asp:ButtonColumn>
دو ستون دکمه (یا لینک) را تعریف می‌کند. برای تمایز بین دو لینک در یک ردیف، از خاصیت [CommandName] آن‌ها استفاده خواهد شد.

کامپوننت [DataGrid] به طور کامل پیکربندی نشده است. این کامپوننت در کد کنترل‌کننده پیکربندی خواهد شد.

کامپوننت [dlAbonnements]، از نوع [DataList]، به منبعی از نوع [DataTable] متصل است. قالب‌بندی آن با استفاده از لینک [Mise en forme automatique] در پنل ویژگی‌های [DataList] تعریف شده است. ویژگی‌های آن مستقیماً در کد ارائه تعریف شده‌اند. این کد به شرح زیر است (کد قالب‌بندی حذف شده است):


                        <asp:DataList id="dlAbonnements" ... runat="server" >
                            <HeaderTemplate>
                                <div align="center">
                                    Vos abonnements</div>
                            </HeaderTemplate>
                            <ItemStyle ...></ItemStyle>
                            <ItemTemplate>
                                <TABLE>
                                    <TR>
                                        <TD><%#Container.DataItem("theme")%></TD>
                                        <TD>
                                            <asp:Button id="lnkRetirer" CommandName="retirer" runat="server" Text="Retirer" /></TD>
                                    </TR>
                                </TABLE>
                            </ItemTemplate>
                            <HeaderStyle ...></HeaderStyle>
                        </asp:DataList>
<HeaderTemplate>
متن سربرگ را برای [DataList] تعریف می‌کند
<ItemTemplate>
عنصر فعلی [DataList] را تعریف می‌کند – در اینجا یک جدول با دو ستون و یک سطر قرار گرفته است. سلول اول نام تمّی را که کاربر مایل به اشتراک آن است در خود جای می‌دهد، در حالی که سلول دیگر حاوی دکمه [Retirer] است که به کاربر امکان لغو انتخابش را می‌دهد.

9.3.4. صفحه نمایش

کد نمایش [main.aspx] به شرح زیر است:


<%@ page src="main.aspx.vb" inherits="main" autoeventwireup="false" %>
<HTML>
    <HEAD>
        <title></title>
    </HEAD>
    <body>
        <P>Indiquez les thèmes auxquels vous voulez vous abonner :</P>
        <HR width="100%" SIZE="1">
        <form runat="server">
            <table>
                <tr>
                    <td vAlign="top">
                        <asp:datagrid id="dgThèmes" ... runat="server">
...
                        </asp:datagrid>
                  </td>
                    <td vAlign="top">
                        <asp:DataList id="dlAbonnements" ... runat="server" GridLines="Horizontal" ShowFooter="False">
....
                        </asp:DataList>
                   </td>
                    <td vAlign="top">
                        <asp:Panel ID="panelInfo" Runat="server" EnableViewState="False">
                            <TABLE>
                                <TR>
                                    <TD bgColor="#99cccc">
                                        <asp:Label id="lblThème" runat="server"></asp:Label></TD>
                                </TR>
                                <TR>
                                    <TD bgColor="#ffff99">
                                        <asp:Label id="lblDescription" runat="server"></asp:Label></TD>
                                </TR>
                            </TABLE>
                        </asp:Panel>
                    </td>
                </tr>
            </table>
            <P>
                <asp:Label id="lblInfo" runat="server" EnableViewState="False"></asp:Label></P>
        </form>
    </body>
</HTML>

9.3.5. کنترل‌کننده‌ها

کنترل بین فایل‌های [global.asax] و [main.aspx] تقسیم شده است. فایل [global.asax] به شرح زیر است:

<%@ Application src="global.asax.vb" inherits="global" %>

فایل مرتبط [global.asax.vb] حاوی کد زیر است:


Imports System.Web
Imports System.Web.SessionState
Imports System.Data
Imports System

Public Class global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        'منبع داده راه‌اندازی شد
        Dim thèmes As New DataTable
        ' ستون‌ها
        With thèmes.Columns
            .Add("id", GetType(System.Int32))
            .Add("thème", GetType(System.String))
            .Add("description", GetType(System.String))
        End With
        ' ستون 'id' کلید اصلی خواهد بود
        thèmes.Constraints.Add("cléprimaire", thèmes.Columns("id"), True)
        ' رده‌ها
        Dim ligne As DataRow
        For i As Integer = 0 To 10
            ligne = thèmes.NewRow
            ligne.Item("id") = i.ToString
            ligne.Item("thème") = "thème" + i.ToString
            ligne.Item("description") = "description du thème " + i.ToString
            thèmes.Rows.Add(ligne)
        Next
        'منبع داده به برنامه اضافه می‌شود
        Application("thèmes") = thèmes
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' آغاز جلسه – ایجاد یک جدول اشتراک خالی
        Dim dtAbonnements As New DataTable
        With dtAbonnements
            'ستون‌ها
            .Columns.Add("id", GetType(String))
            .Columns.Add("thème", GetType(String))
            'کلید اصلی
            .PrimaryKey = New DataColumn() {.Columns("id")}
        End With
        'جدول به جلسه اضافه می‌شود
        Session.Item("abonnements") = dtAbonnements
    End Sub

End Class

روال [Application_Start]، هنگامی که برنامه اولین درخواست خود را دریافت می‌کند، [DataTable] را که حاوی تم‌هایی است که می‌توان به آن‌ها مشترک شد، می‌سازد. این به صورت دلخواه با استفاده از کد ساخته می‌شود. تکنیکی را که قبلاً با آن مواجه شده‌ایم به یاد بیاورید. موارد زیر به ترتیب ساخته می‌شوند:

  • یک شیء خالی [DataTable]، بدون ساختار و بدون داده
  • ساختار جدول با تعریف ستون‌های آن (نام و نوع داده)
  • رده‌های جدول که نمایانگر داده‌های مرتبط هستند

در اینجا یک کلید اصلی اضافه کرده‌ایم. ستون «id» به‌عنوان کلید اصلی عمل می‌کند. راه‌های مختلفی برای بیان این موضوع وجود دارد. در اینجا از یک قید استفاده کرده‌ایم. در SQL، قید قانونی است که داده‌های یک سطر باید آن را برآورده کنند تا آن سطر به جدول اضافه شود. انواع مختلفی از قیدهای ممکن وجود دارد. محدودیت «کلید اصلی» تضمین می‌کند که ستونی که این محدودیت به آن اعمال شده است، شامل مقادیر منحصربه‌فرد و غیرخالی باشد. در واقع، یک کلید اصلی می‌تواند شامل یک عبارت باشد که از مقادیر چندین ستون تشکیل شده باشد. [DataTable].Constraints مجموعه‌ای از محدودیت‌ها برای یک جدول مشخص است. برای افزودن یک محدودیت، از متد [DataTable.Constraints.Add] استفاده می‌شود. این متد چندین امضا دارد. در اینجا، از متد [Add(Byval nom as String, Byval colonne as DataColumn, Byval cléPrimaire as Boolean)] استفاده شده است:

nom
نام قید – می‌تواند هر چیزی باشد
colonne
ستونی که باید به عنوان کلید اصلی استفاده شود – از نوع [DataColumn]
cléPrimaire
باید روی [vrai] تنظیم شود تا [colonne] کلید اصلی شود. اگر روی [cléPrimaire=false] تنظیم شود، تنها یک محدودیت یکتایی روی [colonne] وجود دارد.

برای اینکه ستون با نام «id» را به کلید اصلی جدول [dtAbonnements] تبدیل کنیم، بنابراین می‌نویسیم:

dtAbonnements.Constraints.Add("xxx",dtAbonnements.Columns("id"),true)

روال [Session_Start] زمانی اجرا می‌شود که برنامه اولین درخواست را از یک کلاینت دریافت می‌کند. از این روال برای ایجاد اشیاء مخصوص به هر کلاینت استفاده می‌شود که باید در درخواست‌های مختلف کلاینت پایدار بمانند. این روال جدول [DataTable] را که شامل اشتراک‌های کلاینت است، ایجاد می‌کند. فقط ساختار آن ایجاد می‌شود، زیرا این جدول در ابتدا خالی است. با ارسال درخواست‌ها، داده‌ها در آن بارگذاری خواهند شد. در اینجا نیز ستون «id» به‌عنوان کلید اصلی عمل می‌کند. برای تعریف این قید از تکنیک متفاوتی استفاده شده است:

[DataTable].PrimaryKey
مجموعه ستون‌هایی است که کلید اصلی را تشکیل می‌دهند – در اینجا، ما یک آرایه تک‌عنصری را اعلام کرده‌ایم: ستونی به نام «id»

وقتی درخواست مشتری به کنترلر [main.aspx] می‌رسد، دو شیء [DataTable] در برنامه برای جدول تم‌ها و در جلسه برای جدول اشتراک‌ها در دسترس هستند. کنترلر [main.aspx.vb] به شرح زیر است:


Imports System.Data

Public Class main
    Inherits System.Web.UI.Page

    Protected WithEvents dgThèmes As System.Web.UI.WebControls.DataGrid
    Protected WithEvents lblThème As System.Web.UI.WebControls.Label
    Protected WithEvents lblDescription As System.Web.UI.WebControls.Label
    Protected WithEvents dlAbonnements As System.Web.UI.WebControls.DataList
    Protected WithEvents lblInfo As System.Web.UI.WebControls.Label
    Protected WithEvents panelInfo As System.Web.UI.WebControls.Panel

    Protected dtThèmes As DataTable
    Protected dtAbonnements As DataTable

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
    End Sub

    Private Sub liaisons()
...
    End Sub

    Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
...
    End Sub

    Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
...
    End Sub

    Private Sub infos(ByVal id As String)
...
    End Sub

    Private Sub abonner(ByVal id As String)
...
    End Sub

    Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
...
    End Sub
End Class

هدف اصلی رویه [Page_Load] عبارت است از:

  • بازیابی دو جدول [dtThèmes] و [dtAbonnements] که به ترتیب در لایهٔ برنامه (application) و لایهٔ جلسه (session) قرار دارند، به منظور در دسترس قرار دادن آن‌ها برای تمامی متدهای صفحه
  • داده‌های این دو منبع را به کانتینرهای مربوطه پیوند دهد. این کار فقط در اولین درخواست انجام می‌شود. برای درخواست‌های بعدی، لزومی ندارد که پیوند به‌طور سیستماتیک برقرار شود؛ و زمانی که نیاز به برقراری آن باشد، گاهی لازم است قبل از انجام این کار منتظر رویدادی که پس از [Page_Load] رخ می‌دهد، بمانند.

کد برای [Page_Load] به شرح زیر است:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' بازیابی منابع داده
        dtThèmes = CType(Application("thèmes"), DataTable)
        dtAbonnements = CType(Session("abonnements"), DataTable)
        ' پیوند داده
        If Not IsPostBack Then
            liaisons()
        End If
        'برخی اطلاعات پنهان شده‌اند
        panelInfo.Visible = False
    End Sub

    Private Sub liaisons()
        ': منبع داده به مؤلفه [datagrid] متصل است
        With dgThèmes
            .DataSource = dtThèmes
            .DataKeyField = "id"
        End With
        'منبع داده به مؤلفه [datalist] متصل است
        With dlAbonnements
            .DataSource = dtAbonnements
            .DataKeyField = "id"
        End With
        ' داده‌ها را به کامپوننت‌ها اختصاص دهید
        Page.DataBind()
    End Sub

در رویه [liaisons]، ما از ویژگی [DataKeyField] اجزای [DataList] و [DataGrid] برای تعریف ستونی در منبع داده استفاده می‌کنیم که برای شناسایی منحصربه‌فرد سطرها در محفظه‌ها به کار می‌رود. معمولاً این ستون کلید اصلی منبع داده است، اما این امر اجباری نیست. کافی است که این ستون فاقد مقادیر تکراری یا خالی باشد. برای کانتینر [dgThèmes]، این ستون «id» از منبع [dtThèmes] است که به عنوان کلید اصلی عمل خواهد کرد، و برای کانتینر [dlAbonnements]، این ستون «id» از منبع [dtAbonnements] خواهد بود. الزامی نیست که ستونی که به عنوان کلید اصلی کانتینر عمل می‌کند، توسط خود کانتینر نمایش داده شود. در این مورد، هیچ‌کدام از دو کانتینر، ستون کلید اصلی را نمایش نمی‌دهند. مزیت داشتن کلید اصلی برای یک کانتینر این است که به شما امکان می‌دهد به راحتی اطلاعات مربوط به سطر کانتینری را که یک رویداد در آن رخ داده است، از منبع داده بازیابی کنید. در واقع، اغلب پیش می‌آید که بر اساس یک سطر از یک کانتینر که رویدادی در آن رخ داده است، نیاز باشد اقدامی روی سطر متناظر در منبع داده مرتبط انجام شود. کلید اصلی این کار را تسهیل می‌کند.

صفحه‌بندی برای [DataGrid] به روش استاندارد انجام می‌شود:


    Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
        'تغییر صفحه
        dgThèmes.CurrentPageIndex = e.NewPageIndex
        ' لینک
        liaisons()
    End Sub

اقدامات روی لینک‌های [Plus d'informations] و [S'abonner] توسط رویه [dgThèmes_ItemCommand] مدیریت می‌شوند:


    Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
        'رویداد روی یک ردیف از [datagrid]
        Dim commande As String = e.CommandName
        Select Case commande
            Case "infos"
                infos(dgThèmes.DataKeys(e.Item.ItemIndex))
            Case "abonner"
                abonner(dgThèmes.DataKeys(e.Item.ItemIndex))
        End Select
        ' لینک
        liaisons()
    End Sub

ما از این واقعیت که هر دو لینک دارای ویژگی [CommandName] هستند استفاده می‌کنیم تا بین آن‌ها تمایز قائل شویم. بسته به مقدار این ویژگی، یا رویه [infos] یا [abonner] فراخوانی می‌شود و در هر دو مورد کلید «id» مرتبط با عنصری که رویداد در [DataGrid] در آن رخ داده است، ارسال می‌گردد. با در اختیار داشتن این اطلاعات، رویه [info] جزئیات تم انتخاب‌شده توسط کاربر را نمایش خواهد داد:


    Private Sub infos(ByVal id As String)
        ' اطلاعات درباره شناسه کلید
        ' ردیف را از [datatable] که با کلید مطابقت دارد بازیابی می‌کند
        Dim ligne As DataRow
        ligne = dtThèmes.Rows.Find(id)
        If Not ligne Is Nothing Then
            ' نمایش اطلاعات
            lblThème.Text = CType(ligne("thème"), String)
            lblDescription.Text = CType(ligne("description"), String)
            panelInfo.Visible = True
        End If
    End Sub

از آنجا که جدول [dtThèmes] دارای کلید اصلی است، از متد [dtThèmes.Rows.Find("P")] برای یافتن سطر با کلید اصلی P استفاده می‌شود. اگر یافت شود، یک شیء [DataRow] بازگردانده می‌شود. در اینجا، باید ردیف دارای کلید اصلی [id] را جستجو کنیم، که در آن [id] به‌عنوان پارامتر ارسال می‌شود. اگر سطر پیدا شود، اطلاعات [thème] و [description] از آن سطر در پنل اطلاعات قرار می‌گیرد که سپس قابل مشاهده می‌شود.

روند [abonner(id)] باید کلید موضوع [id] را به فهرست اشتراک‌ها اضافه کند. کد آن به شرح زیر است:


    Private Sub abonner(ByVal id As String)
         ' اشتراک در موضوع شناسه کلید
         ' ردیف مربوط به [datatable] را که با کلید مطابقت دارد بازیابی می‌کند
        Dim ligne As DataRow
        ligne = dtThèmes.Rows.Find(id)
        If Not ligne Is Nothing Then
             'بررسی می‌کند که آیا کاربر قبلاً مشترک شده است
            Dim abonnement As DataRow
            abonnement = dtAbonnements.Rows.Find(id)
            If Not abonnement Is Nothing Then
                 ' گزارش خطا
                lblInfo.Text = "Vous êtes déjà abonné au thème [" + ligne("thème") + "]"
            Else
                 ' افزودن تم به اشتراک‌ها
                abonnement = dtAbonnements.NewRow
                abonnement("id") = id
                abonnement("thème") = ligne("thème")
                dtAbonnements.Rows.Add(abonnement)
                 ' لینک‌ها ایجاد شده‌اند
                liaisons()
            End If
        End If
    End Sub

در لیست تم‌ها [dtThèmes]، ابتدا به دنبال خط کلیدی [id] می‌گردیم. اگر این مورد یافت شود، سیستم بررسی می‌کند که این موضوع قبلاً در لیست اشتراک‌ها وجود نداشته باشد تا از اضافه شدن دوباره‌ی آن جلوگیری شود. اگر چنین باشد، یک پیام خطا نمایش داده می‌شود. در غیر این صورت، یک اشتراک جدید به جدول [dtAbonnements] اضافه می‌شود و اجزای لیست داده‌ها به منابع مربوطه خود متصل می‌شوند.

وقتی کاربر روی دکمه [Retirer] کلیک می‌کند، یک عنصر باید از جدول [dtAbonnements] حذف شود. این کار با رویه زیر انجام می‌شود:


    Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
        ' حذف اشتراک
        Dim commande As String = e.CommandName
        If commande = "retirer" Then
            'اشتراک از [datatable] حذف شد
            With dtAbonnements.Rows
                .Remove(.Find(dlAbonnements.DataKeys(e.Item.ItemIndex)))
            End With
            ' پیوندها
            liaisons()
        End If
    End Sub

ابتدا، ویژگی [CommandName] عنصری را که رویداد را ایجاد کرده است بررسی می‌کنیم. این در واقع نسبتاً بی‌فایده است، زیرا دکمه [Retirer] تنها کنترل موجود در کامپوننت [DataList] است که قادر به تولید رویداد است. بنابراین هیچ ابهامی وجود ندارد. برای حذف یک سطر از یک شیء [DataTable]، از متد [DataList.Remove(DataRow)] استفاده می‌کنیم که سطر با نوع [DataRow] را که به‌عنوان پارامتر ارسال شده، از جدول حذف می‌کند. این سطر توسط متد [DataList.Find] که کلید اصلی سطر مورد نظر به آن پاس شده است، مکان‌یابی می‌شود. پس از حذف سطر، داده‌ها به کامپوننت‌ها لینک می‌شوند

9.4. مدیریت [DataList] صفحه‌بندی‌شده

ما از مثال قبلی برای صفحه‌بندی کامپوننت [DataList] که فهرست اشتراک‌های کاربر را نمایش می‌دهد، استفاده خواهیم کرد. برخلاف کامپوننت [DataGrid]، کامپوننت [DataList] هیچ قابلیت صفحه‌بندی ارائه نمی‌دهد. در ادامه خواهیم دید که پیاده‌سازی صفحه‌بندی پیچیده است، و این موضوع باعث می‌شود که بیش از پیش از قابلیت صفحه‌بندی خودکار [DataGrid] قدردانی کنیم.

9.4.1. نحوه کار

تنها تفاوت در صفحه‌بندی [DataList] است. صفحات شامل دو اشتراک خواهند بود. اگر کاربر پنج اشتراک داشته باشد، سه صفحه وجود خواهد داشت. صفحه اول به شرح زیر خواهد بود:

Image

به صفحه دوم از طریق لینک [Suivant] دسترسی پیدا می‌شود:

Image

صفحه سوم:

Image

توجه داشته باشید که لینک‌های [Précédent] و [Suivant] تنها در صورتی قابل مشاهده هستند که به ترتیب یک صفحه قبلی و یک صفحه بعدی پس از صفحه جاری وجود داشته باشد.

9.4.2. کد ارائه

لینک‌های [Précédent] و [Suivant] با افزودن یک تگ <FooterTemplate> به [DataList] ایجاد می‌شوند:


                        <asp:datalist id="dlAbonnements" runat="server" ...>
....
                            <FooterTemplate>
                                <asp:LinkButton id="lnkPrecedent" runat="server" CommandName="precedent">Précédent</asp:LinkButton>
                                <asp:LinkButton id="lnkSuivant" runat="server" CommandName="suivant">Suivant</asp:LinkButton>
                            </FooterTemplate>
....
                        </asp:datalist>

9.4.3. رقم کنترل

فایل مرتبط [global.asax.vb] به شرح زیر تغییر می‌کند:

...

Public Class global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
...
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
         ' آغاز جلسه  ایجاد یک جدول اشتراک خالی
        Dim dtAbonnements As New DataTable
        With dtAbonnements
             'ستون‌ها
            .Columns.Add("id", GetType(String))
            .Columns.Add("thème", GetType(String))
             'کلید اصلی
            .PrimaryKey = New DataColumn() {.Columns("id")}
        End With
         'جدول به جلسه اضافه می‌شود
        Session.Item("abonnements") = dtAbonnements
         ' صفحهٔ جاری صفحهٔ 0 است
        Session.Item("pAC") = 0
         ' تعداد اشتراک‌ها در این صفحه 0 است
        Session.Item("nbAC") = 0
    End Sub

علاوه بر جدول اشتراک [dtAbonnements]، دو اطلاعات دیگر در جلسه گنجانده شده‌اند:

pAC
از نوع [Integer] – این شماره صفحهٔ فعلی نمایش‌داده‌شده در طول درخواست اخیر است
nbAC
از نوع [Integer] – تعداد خطوط نمایش داده شده در صفحه جاری قبلی

در ابتدای یک جلسه، هم شماره صفحه فعلی و هم تعداد خطوط آن صفحه صفر هستند.

کنترل‌کننده [main.aspx.vb] به شرح زیر تغییر می‌کند:

....

Public Class main
    Inherits System.Web.UI.Page

....

     ' داده‌های برنامه
    Protected dtThèmes As DataTable
    Protected dtAbonnements As DataTable
    Protected dtPA As DataTable    ' la page d'abonnements affichée
    Protected Const nbAP As Integer = 2    ' nbre abonnements par page
    Protected pAC As Integer    ' page abonnement courant
    Protected nbAC As Integer    ' nombre d'abonnements dans page courante

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
    End Sub

    Private Sub terminer()
...
    End Sub

    Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
...
    End Sub

    Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
...
    End Sub

    Private Sub infos(ByVal id As String)
...
    End Sub

    Private Sub abonner(ByVal id As String)
...
    End Sub

    Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
...
    End Sub

    Private Sub changePAC()
...
    End Sub

    Private Sub setLiens(ByVal ctl As Control, ByVal blPrec As Boolean, ByVal blSuivant As Boolean)
...
    End Sub
End Class

داده‌های جدید مربوط به صفحه‌بندی اشتراک در اینجا تعریف شده است:


    Protected dtPA As DataTable    ' la page d'abonnements affichée
    Protected Const nbAP As Integer = 2    ' nbre abonnements par page
    Protected pAC As Integer    ' page abonnement courant
    Protected nbAC As Integer    ' nombre d'abonnements dans page courante

بخشی از این اطلاعات در جلسه ذخیره شده و در هر درخواست در رویه [Page_Load] بازیابی می‌شود:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'بازیابی منابع داده
        dtThèmes = CType(Application("thèmes"), DataTable)
        dtAbonnements = CType(Session("abonnements"), DataTable)
        ' و اطلاعات نمایش اشتراک
        pAC = CType(Session("pAC"), Integer)
        nbAC = CType(Session("nbAC"), Integer)
        ' پیوند داده
        If Not IsPostBack Then
            'نمایش لیست خالی اشتراک‌ها
            terminer()
        End If
        ' اطلاعات خاصی پنهان شده است
        panelInfo.Visible = False
    End Sub

اطلاعات استخراج‌شده از [pAC] و [nbAC] مربوط به صفحهٔ اشتراک‌ها است که در درخواست قبلی نمایش داده شد:

pAC
این شماره صفحهٔ جاری است که در درخواست قبلی نمایش داده شده است
nbAC
تعداد سطرهای نمایش‌داده‌شده در این صفحهٔ جاری

متد [terminer]، اجزا را به منابع داده‌ی آن‌ها متصل می‌کند، درست همان‌طور که متد [liaisons] در اپلیکیشن قبلی این کار را انجام می‌داد. ویژگی جدید در اینجا پیوند بین [DataList] و جدول [dtPA] است که صفحه اشتراک‌نامه‌ها برای نمایش می‌باشد:


    Private Sub terminer()
        'پیوند دادن منبع داده به کامپوننت [datagrid]
        With dgThèmes
            .DataSource = dtThèmes
            .DataKeyField = "id"
        End With
        ' لینک صفحهٔ اشتراک‌ها به کامپوننت [datalist]، با در نظر گرفتن صفحهٔ فعلی pAC
        changePAC()
        'صفحه p نمایش داده می‌شود
        With dlAbonnements
            .DataSource = dtPA
            .DataKeyField = "id"
        End With
        ' داده‌ها را به کامپوننت‌ها اختصاص می‌دهد
        Page.DataBind()
        'مدیریت لینک‌های [précédent] و [suivant] برای [datalist]
        Dim blprec As Boolean = pAC <> 0
        Dim blsuivant As Boolean = pAC <> (dtAbonnements.Rows.Count - 1) \ nbAP
        Dim nbLiensTrouvés As Integer = 0
        setLiens(dlAbonnements, blprec, blsuivant, nbLiensTrouvés)
        'اطلاعات صفحهٔ جاری را در جلسه ذخیره می‌کند
        Session("pAC") = pAC
        Session("nbAC") = dtPA.Rows.Count
    End Sub

لطفاً به نکات زیر توجه کنید:

  • منبع [dtPA] به شماره صفحه فعلی [pAC] که باید نمایش داده شود، وابسته است. متغیر [pAC] یک متغیر سراسری در کلاس است که توسط متدهای مسئول به‌روزرسانی شماره صفحه فعلی مدیریت می‌شود. این متد [changePAC] است که جدول [dtPA] را می‌سازد، که به کامپوننت [dlAbonnements] لینک خواهد شد.
  • نقش متد [setLiens] این است که لینک‌های [Précédent] و [Suivant] را بسته به اینکه آیا صفحه جاری [pAC] که نمایش داده می‌شود، قبل و بعد از آن صفحه دیگری وجود دارد یا خیر، نشان دهد یا پنهان کند. این متد چهار پارامتر دارد:
    • [dlAbonnements]: کنترل [DataList] که درخت کنترل آن را برای یافتن دو لینک بررسی خواهیم کرد. اگرچه این دو لینک در مکانی مشخص – یعنی در پاورقی [DataList] – قرار دارند، به نظر نمی‌رسد راه ساده‌ای برای ارجاع مستقیم به آن‌ها وجود داشته باشد. در هر صورت، هیچ روش مشابهی در اینجا یافت نشده است.
    • [blPrecedent]: یک مقدار بولی که باید به ویژگی [visible] لینک [Precedent] اختصاص یابد – اگر صفحهٔ جاری 0 نباشد، روی true تنظیم می‌شود
    • [blSuivant]: یک مقدار بولی که باید به خصوصیت [visible] لینک [Suivant] اختصاص داده شود – زمانی که صفحهٔ جاری آخرین صفحه در لیست اشتراک‌ها نباشد، مقدار true را می‌گیرد
    • [nbLiensTrouvés]: یک پارامتر خروجی که تعداد لینک‌های یافت‌شده را شمارش می‌کند. به محض اینکه این شمارش به دو برسد، متد پایان می‌یابد.
  • اطلاعات [pAC] و [nbAC] برای درخواست بعدی در جلسه (session) ذخیره می‌شود.

روش [changePAC] جدول [dtPA] را ایجاد می‌کند که به کامپوننت [dlAbonnements] متصل خواهد شد. این کار بر اساس شماره [pAC] صفحهٔ جاری برای نمایش انجام می‌شود. جدول [dtPA] باید ردیف‌های مشخصی را از جدول اشتراک‌ها [dtAbonnements] نمایش دهد. شایان ذکر است که این جدول در جلسه (session) ذخیره شده و با هر پرس‌وجو به‌روزرسانی (افزایش یا کاهش) می‌شود. ما با تعیین محدوده [premier,dernier] برای شماره‌های سطر در جدول [dtAbonnements] که جدول [dtPA] قرار است نمایش دهد، شروع می‌کنیم:


    Private Sub changePAC()
        'صفحه pAC را به‌عنوان صفحهٔ جاری برای اشتراک‌گذاری‌ها تنظیم می‌کند
        'مدیریت صفحات [datalist] را بر عهده دارد
        Dim nbAbonnements = dtAbonnements.Rows.Count
        Dim dernièrePage = (nbAbonnements - 1) \ nbAP
        ' اولین و آخرین اشتراک
        If pAC < 0 Then pAC = 0
        If pAC > dernièrePage Then pAC = dernièrePage
        Dim premier As Integer = pAC * nbAP
        Dim dernier As Integer = (pAC + 1) * nbAP - 1
        If dernier > nbAbonnements - 1 Then dernier = nbAbonnements - 1

پس از انجام این کار، می‌توانیم جدول [dtPA] را ایجاد کنیم. ابتدا ساختار آن را در [id,thème] تعریف می‌کنیم، سپس با کپی کردن سطرهای [dtAbonnements] که شماره‌هایشان در بازهٔ [premier,dernier] که قبلاً محاسبه شده است قرار دارند، آن را پر می‌کنیم.


         ' ایجاد جدول داده dtpa
        dtPA = New DataTable
        With dtPA
            ' ستون‌ها
            .Columns.Add("id", GetType(String))
            .Columns.Add("thème", GetType(String))
            'کلید اصلی
            .PrimaryKey = New DataColumn() {.Columns("id")}
        End With
        Dim abonnement As DataRow
        For i As Integer = premier To dernier
            abonnement = dtPA.NewRow
            With abonnement
                .Item("id") = dtAbonnements.Rows(i).Item("id")
                .Item("thème") = dtAbonnements.Rows(i).Item("thème")
            End With
            dtPA.Rows.Add(abonnement)
        Next
    End Sub

در پایان متد [changePAC]، جدول [dtPA] ایجاد شده و می‌توان آن را به کامپوننت [DataList] متصل کرد. این کار در متد [terminer] انجام می‌شود. در همین متد، رویه [setLiens] برای تنظیم وضعیت لینک‌های [Précédent] و [Suivant] در [DataList] استفاده می‌شود. کد این رویه به شرح زیر است:


    Private Sub setLiens(ByVal ctl As Control, ByVal blPrec As Boolean, ByVal blSuivant As Boolean, ByRef nbLiensTrouvés As Integer)
        'جستجو برای لینک‌های [précédent] و [suivant]
        'در درخت کنترل برای [datalist]
        'آیا همهٔ پیوندها را پیدا کرده‌ایم؟
        If nbLiensTrouvés = 2 Then Exit Sub
        'بررسی کنترل‌های فرعی
        Dim c As Control
        For Each c In ctl.Controls
            ' ما اول به صورت عمیق کار می‌کنیم – لینک‌ها در پایین درخت قرار دارند
            setLiens(c, blPrec, blSuivant, nbLiensTrouvés)
            ' لینک [Précédent]؟
            If c.ID = "lnkPrecedent" Then
                CType(c, LinkButton).Visible = blPrec
                nbLiensTrouvés += 1
            End If
            ' لینک [Suivant]؟
            If c.ID = "lnkSuivant" Then
                CType(c, LinkButton).Visible = blSuivant
                nbLiensTrouvés += 1
            End If
        Next
    End Sub

این رویه بازگشتی است. ابتدا جستجو می‌کند، در میان کنترل‌های فرزند کامپوننت [dlAbonnements] برای کامپوننت‌هایی با نام‌های [lnkPrecedent] و [lnkSuivant]، که شناسه‌های (ویژگی ID) دو لینک پیجینیشن هستند. این جستجو را از پایین درخت کنترل آغاز می‌کند، زیرا آن‌ها در آنجا قرار دارند. به محض یافتن یک لینک، شمارنده [nbLiensTrouvés] افزایش می‌یابد و ویژگی [visible] لینک به مقداری که به عنوان پارامتر به رویه ارسال شده است، تنظیم می‌شود. پس از یافتن هر دو لینک، دیگر در درخت کنترل جستجو انجام نمی‌شود و رویه بازگشتی متوقف می‌گردد.

ما اشاره کردیم که متد [changePAC]، که منبع داده [dtPA] را برای کامپوننت [dlAbonnements] تنظیم می‌کند، با شماره [pAC] صفحه فعلی که باید نمایش داده شود، کار می‌کند. چندین رویه این عدد را تغییر می‌دهند:


    Private Sub abonner(ByVal id As String)
        ' با استفاده از شناسه کلید به تم مشترک شوید
..
            ' قالب به اشتراک‌ها اضافه شد
..
             ' به‌روزرسانی شماره صفحه فعلی – اکنون این آخرین صفحه است
            pAC = (dtAbonnements.Rows.Count - 1) \ nbAP
             'لینک‌های داده
            terminer()
        End If
    End Sub

پس از افزودن یک اشتراک، آن در پایین فهرست اشتراک‌ها ظاهر می‌شود. بنابراین صفحه به آخرین صفحهٔ اشتراک‌ها هدایت می‌شود تا کاربر بتواند افزوده‌شده را ببیند.


    Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
        ' حذف اشتراک
        Dim commande As String = e.CommandName
        Select Case commande
            Case "retirer"
                ' حذف اشتراک از [datatable]
                With dtAbonnements.Rows
                    .Remove(.Find(dlAbonnements.DataKeys(e.Item.ItemIndex)))
                End With
                ' آیا باید صفحهٔ فعلی تغییر کند؟
                nbAC -= 1
                If nbAC = 0 Then pAC -= 1
            Case "precedent"
                ' تغییر صفحهٔ جاری
                pAC -= 1
            Case "suivant"
                ' تغییر صفحهٔ جاری
                pAC += 1
        End Select
        ' پیوندهای داده
        terminer()
    End Sub
  • [nbAC] تعداد خطوط نمایش‌داده‌شده در صفحهٔ جاری قبل از لغو اشتراک است. اگر تعداد جدید خطوط در صفحه 0 باشد، شمارهٔ صفحهٔ جاری [pAC] یک واحد کاهش می‌یابد.
  • اگر لینک [Precedent] کلیک شود، شماره صفحه فعلی [pAC] یک واحد کاهش می‌یابد.
  • اگر لینک [Suivant] کلیک شود، شماره صفحه فعلی [pAC] یک واحد افزایش می‌یابد.

سایر رویه‌ها مانند قبل باقی می‌مانند.

9.4.4. نتیجه‌گیری

ما در این مثال نشان دادیم که می‌توانیم یک کامپوننت [DataList] را صفحه‌بندی کنیم. این صفحه‌بندی پیچیده است و ترجیح داده می‌شود تا در صورت امکان به صفحه‌بندی خودکار کامپوننت [DataGrid] تکیه شود. این مثال همچنین نشان داده است که چگونه به کامپوننت‌های پاورقی در [DataList] دسترسی پیدا کنیم.

9.5. دسترسی کلاس برای پایگاه داده محصول

ما بار دیگر در حال بررسی پایگاه داده ACCESS [produits] هستیم که قبلاً از آن استفاده کرده‌ایم. به یاد داشته باشید که این پایگاه داده شامل یک جدول واحد به نام [liste] است که ساختار آن به شرح زیر است:

ما قصد داریم یک کلاس دسترسی برای جدول [liste] ایجاد کنیم که به ما امکان خواندن و به‌روزرسانی آن را می‌دهد. همچنین یک کلاینت کنسول خواهیم ساخت که از کلاس مذکور برای به‌روزرسانی جدول استفاده می‌کند. در ادامه، یک کلاینت وب برای انجام همان کار ایجاد خواهیم کرد.

9.5.1. کلاس ExceptionProduits

کلاس [Exception] دارای یک سازنده است که یک پیام خطا را به عنوان پارامتر می‌پذیرد. در اینجا، ما می‌خواهیم یک کلاس استثنا داشته باشیم که سازنده‌ای با قابلیت پذیرش یک لیست از پیام‌های خطا به جای یک پیام خطای واحد داشته باشد. این کلاس [ExceptionProduits] خواهد بود که در زیر نشان داده شده است:


    Public Class ExceptionProduits
        Inherits Exception

        ' پیام‌های خطا مرتبط با استثنا
        Private _erreurs As ArrayList

        ' سازنده
        Public Sub New(ByVal erreurs As ArrayList)
            Me._erreurs = erreurs
        End Sub

        ' ویژگی
        Public ReadOnly Property erreurs() As ArrayList
            Get
                Return _erreurs
            End Get
        End Property
    End Class

9.5.2. ساختار [sProduit]

ساختار [produit] نماینده محصول [id, nom, prix] خواهد بود:


     'ساختار sProduit
    Public Structure sProduit
         ' fields
        Private _id As Integer
        Private _nom As String
        Private _prix As Double

         ' خاصیت شناسه
        Public Property id() As Integer
            Get
                Return _id
            End Get
            Set(ByVal Value As Integer)
                _id = Value
            End Set
        End Property

        ' نام ویژگی
        Public Property nom() As String
            Get
                Return _nom
            End Get
            Set(ByVal Value As String)
                If IsNothing(Value) OrElse Value.Trim = String.Empty Then Throw New Exception
                _nom = Value
            End Set
        End Property

        ' ویژگی قیمت
        Public Property prix() As Double
            Get
                Return _prix
            End Get
            Set(ByVal Value As Double)
                If IsNothing(Value) OrElse Value < 0 Then Throw New Exception
                _prix = Value
            End Set
        End Property
    End Structure

این ساختار تنها داده‌های معتبر را برای فیلدهای [nom] و [prix] می‌پذیرد.

9.5.3. کلاس محصولات

کلاس [Produits] کلاسی است که به ما امکان می‌دهد جدول [liste] را در پایگاه داده محصول به‌روزرسانی کنیم. ساختار آن به شرح زیر است:

    Public Class produits
         'داده‌های نمونه

        Public Sub New(ByVal chaineConnexionOLEDB As String)
....
        End Sub

        Public Function getProduits() As DataTable
....
        End Function

        Public Sub ajouterProduit(ByVal produit As sProduit)
...
        End Sub

        Public Sub modifierProduit(ByVal produit As sProduit)
...
        End Sub

        Public Sub supprimerProduit(ByVal id As Integer)
...
        End Sub

    End Class

داده‌های نمونه

داده‌های به اشتراک گذاشته شده توسط متدهای مختلف کلاس به شرح زیر است:


        Private connexion As OleDbConnection
        Private Const selectText As String = "select id,nom,prix from liste"
        Private Const insertText As String = "insert into liste(nom,prix) values(?,?)"
        Private Const updateText As String = "update liste set nom=?,prix=? where id=?"
        Private Const deleteText As String = "delete from liste where id=?"
        Private selectCommand As New OleDbCommand
        Dim insertCommand As New OleDbCommand
        Dim updateCommand As New OleDbCommand
        Dim deleteCommand As New OleDbCommand
        Dim adaptateur As New OleDbDataAdapter
connexion
اتصال به پایگاه داده – برای اجرای دستور SQL باز شده و بلافاصله پس از آن بسته خواهد شد
selectText
پرس‌وجوی SQL [select] کل جدول [liste] را بازیابی می‌کند
insertText
پرس‌وجوی اجازهٔ درج یک سطر (نام، قیمت) در جدول [liste]. توجه داشته باشید که فیلد [id] مشخص نشده است. این به این دلیل است که این فیلد توسط SGBD به‌صورت خودکار افزایش می‌یابد، بنابراین نیازی به مشخص کردن آن نداریم.
updateText
پرس‌وجو برای به‌روزرسانی فیلدهای (name, price) در سطر جدول [liste] با کلید [id]
deleteText
پرس‌وجو برای حذف سطر از جدول [liste] با کلید [id]
selectCommand
شیء [OleDbCommand] در حال اجرای پرس‌وجوی [selectText] بر روی اتصال [connexion]
updateCommand
شیء [OleDbCommand] در حال اجرای پرس‌وجوی [updateText] بر روی اتصال [connexion]
insertCommand
شیء [OleDbCommand] در حال اجرای پرس‌وجوی [insertText] بر روی اتصال [connexion]
deleteCommand
شیء [OleDbCommand] در حال اجرای پرس‌وجوی [deleteText] بر روی اتصال [connexion]
adaptateur
ابجکتی که برای بازیابی نتیجه اجرای [selectCommand] در یک شیء [DataSet] استفاده می‌شود

سازنده

سازنده یک پارامتر واحد، [chaineConnexionOLEDB]، را می‌پذیرد که رشته اتصال مشخص‌کننده پایگاه داده مورد پرس‌وجو است. با استفاده از این، چهار دستور برای پرس‌وجو و به‌روزرسانی جدول، و همچنین آداپتور، آماده می‌شوند. این صرفاً یک مرحله آماده‌سازی است؛ در واقع هیچ اتصالی برقرار نمی‌شود.


        Public Sub New(ByVal chaineConnexionOLEDB As String)
            ' آماده‌سازی اتصال
            connexion = New OleDbConnection(chaineConnexionOLEDB)
            ' آماده‌سازی دستورات پرس‌وجو
            Dim commandes() As OleDbCommand = {selectCommand, insertCommand, updateCommand, deleteCommand}
            Dim textes() As String = {selectText, insertText, updateText, deleteText}
            For i As Integer = 0 To commandes.Length - 1
                With commandes(i)
                    .CommandText = textes(i)
                    .Connection = connexion
                End With
            Next
            'آماده‌سازی آداپتور دسترسی به داده‌ها
            adaptateur.SelectCommand = selectCommand
        End Sub

روش getProduits

این روش محتویات جدول [Liste] را در یک شیء [DataTable] بازیابی می‌کند. کد آن به شرح زیر است:


        Public Function getProduits() As DataTable
            'قرار دادن جدول [liste] در [dataset]
            Dim contenu As New DataSet
            'ایجاد یک شیء DataAdapter برای خواندن داده‌ها از منبع OLEDB
            Try
                With adaptateur
                    .FillSchema(contenu, SchemaType.Source)
                    .Fill(contenu)
                End With
            Catch e As Exception
                ' pb
                Dim erreursCommande As New ArrayList
                erreursCommande.Add(String.Format("Erreur d'accès à la base de données : {0}", e.Message))
                Throw New ExceptionProduits(erreursCommande)
            End Try
            'نتیجه بازگردانده می‌شود
            Return contenu.Tables(0)
        End Function

این کار با دو دستورالعمل زیر انجام می‌شود:


                With adaptateur
                    .FillSchema(contenu, SchemaType.Source)
                    .Fill(contenu)
                End With

متد [FillSchema] ساختار (ستون‌ها، محدودیت‌ها، روابط) [DataSet] و contenu را بر اساس ساختار پایگاه داده‌ای که در [adaptateur.Connexion] به آن ارجاع شده است، تعریف می‌کند. این امکان را برای ما فراهم می‌کند تا ساختار جدول [liste]، از جمله کلید اصلی آن، را بازیابی کنیم. عملیات بعدی [Fill]، جدول‌های [Dataset] و contenu را با سطرهای جدول [liste] پر می‌کند. فقط با این عملیات، داده‌ها و ساختار را به دست می‌آوردیم، اما نه کلید اصلی را. با این حال، برای به‌روزرسانی جدول [liste] در حافظه به آن نیاز خواهیم داشت. در اینجا، مانند سایر روش‌ها، ما با استفاده از کلاس [ExceptionProduits] هرگونه خطا را مدیریت می‌کنیم تا به جای یک خطای واحد، فهرستی (ArrayList) از خطاها به دست آوریم. متد [getProduits] جدول [liste] را به صورت یک شیء [DataTable] برمی‌گرداند.

متد ajouterProduits

این متد امکان افزودن یک سطر (id, name, price) به جدول [liste] را فراهم می‌کند. این اطلاعات در قالب یک ساختار [sProduit] شامل فیلدهای [id, nom, prix] به آن ارائه می‌شود. کد متد به شرح زیر است:


        Public Sub ajouterProduit(ByVal produit As sProduit)
            'یک محصول اضافه می‌شود: [nom,prix]
            ' ما پارامترها را برای افزودن آماده می‌کنیم
            With insertCommand.Parameters
                .Clear()
                .Add(New OleDbParameter("nom", produit.nom))
                .Add(New OleDbParameter("prix", produit.prix))
            End With
            ' افزوده شده است
            Try
                'اتصال برقرار شد
                connexion.Open()
                ' اجرای فرمان
                insertCommand.ExecuteNonQuery()
            Catch ex As Exception
                'مشکل
                Dim erreursCommande As New ArrayList
                erreursCommande.Add(String.Format("Erreur lors de l'ajout : {0}", ex.Message))
                Throw New ExceptionProduits(erreursCommande)
            Finally
                ' اتصال بسته شد
                connexion.Close()
            End Try
        End Sub

میدان‌های ساختار [produit] در پارامترهای فرمان [insertCommand] درج می‌شوند. به عنوان یادآوری، پیکربندی فعلی این فرمان به شرح زیر است (به سازنده مراجعه کنید):


 Private Const insertText As String = "insert into liste(nom,prix) values(?,?)"
 insertCommand.Connexion=connexion

آیا متن دستور SQL [insert] حاوی جای‌گرینه‌هایی است که باید با پارامترهای واقعی جایگزین شوند؟ این کار با استفاده از مجموعه [Parameters] از کلاس [OleDbCommand] انجام می‌شود. این مجموعه شامل عناصری از نوع [OleDbParameter] است که پارامترهای واقعی را که باید جایگزین پارامترهای شکلی «؟» شوند، تعریف می‌کنند. از آنجایی که پارامترهای واقعی نام‌گذاری نشده‌اند، از شاخص پارامترهای واقعی برای تعیین اینکه کدام پارامتر صوری با کدام پارامتر واقعی مطابقت دارد، استفاده می‌شود. در اینجا، پارامتر واقعی شماره i در مجموعه [Parameters]، پارامتر صوری ؟ شماره i را جایگزین خواهد کرد. برای ایجاد یک پارامتر حقیقی از نوع [OleDbParameter]، از سازنده [OleDbParameter (Byval nom as String, Byval valeur as Object)] استفاده می‌کنیم که نام و مقدار پارامتر حقیقی را تعریف می‌کند. نام می‌تواند هر چیزی باشد. علاوه بر این، از آن در اینجا استفاده نخواهد شد. دو پارامتر دستور SQL ([insert]) با مقادیر فیلدهای [nom, prix] از ساختار [produit] مقداردهی می‌شوند. پس از انجام این کار، درج با عبارت [insertCommand.ExecuteNonQuery] انجام می‌شود.

روش modifierProduits

این روش برای اصلاح یک سطر در جدول [liste] استفاده می‌شود. اطلاعات مورد نیاز برای این کار در ساختار [sProduit] که شامل فیلدهای [id, nom, prix] است، ارائه شده است.


        Public Sub modifierProduit(ByVal produit As sProduit)
            'محصول در حال تغییر است [id,nom,prix]
            ' آماده‌سازی پارامترهای به‌روزرسانی
            With updateCommand.Parameters
                .Clear()
                .Add(New OleDbParameter("nom", produit.nom))
                .Add(New OleDbParameter("prix", produit.prix))
                .Add(New OleDbParameter("id", produit.id))
            End With
            ' انجام تغییر
            Try
                'اتصال برقرار شد
                connexion.Open()
                ' اجرای فرمان
                Dim nbLignes As Integer = updateCommand.ExecuteNonQuery()
                If nbLignes = 0 Then Throw New Exception(String.Format("Le produit de clé [{0}] n'existe pas dans la table des données", produit.id))
            Catch ex As Exception
                ' مشکل
                Dim erreursCommande As New ArrayList
                erreursCommande.Add(String.Format("Erreur lors de la modification : {0}", ex.Message))
                Throw New ExceptionProduits(erreursCommande)
            Finally
                ' اتصال بسته شد
                connexion.Close()
            End Try
        End Sub

این کد تقریباً با کد متد [ajouterProduits] یکسان است، با این تفاوت که فرمان مربوطه [OleDbCommand] به جای [insertCommand]، [updateCommand] است.

روش supprimerProduits

این متد با استفاده از کلید [id] که به عنوان پارامتر ارسال می‌شود، ردیف را از جدول [liste] حذف می‌کند. کد به شرح زیر است:


        Public Sub supprimerProduit(ByVal id As Integer)
            'محصول را با کلید [id] حذف می‌کند
            ' آماده‌سازی پارامترهای حذف
            With deleteCommand.Parameters
                .Clear()
                .Add(New OleDbParameter("id", id))
            End With
            ' حذف در حال انجام
            Try
                'اتصال باز شد
                connexion.Open()
                ' اجرای فرمان
                Dim nbLignes As Integer = deleteCommand.ExecuteNonQuery()
                If nbLignes = 0 Then Throw New Exception(String.Format("Le produit de clé [{0}] n'existe pas dans la table des données", id))
            Catch ex As Exception
                ' خطا
                Dim erreursCommande As New ArrayList
                erreursCommande.Add(String.Format("Erreur lors de la suppression : {0}", ex.Message))
                Throw New ExceptionProduits(erreursCommande)
            Finally
                ' بستن اتصال
                connexion.Close()
            End Try
        End Sub

رویکرد همانند روش‌های قبلی است.

9.5.4. آزمایش‌ها برای کلاس [produits]

یک برنامهٔ آزمون کنسولی با نام [testproduits.vb] ممکن است به این شکل باشد:

Option Explicit On 
Option Strict On

' فضاهای نام
Imports System
Imports System.Data
Imports Microsoft.VisualBasic
Imports System.Collections

Namespace st.istia.univangers.fr

     ' صفحه آزمایشی
    Module testproduits
        Dim contenu As DataTable

        Sub Main(ByVal arguments() As String)
             ' محتویات جدول محصول را نمایش می‌دهد
             'جدول در پایگاه‌دادهٔ ACCESS قرار دارد که pg نام فایل خود را از آن می‌گیرد
            Const syntaxe1 As String = "pg bdACCESS"

             'بررسی پارامترهای برنامه
            If arguments.Length <> 1 Then
                 ' پیام خطا
                Console.Error.WriteLine(syntaxe1)
                 ' پایان
                Environment.Exit(1)
            End If
             ' آماده‌سازی رشته اتصال
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + arguments(0)
             ' ایجاد یک شیء محصول
            Dim objProduits As produits = New produits(chaineConnexion)
             ' نمایش تمام محصولات
            afficheProduits(objProduits)
             ' افزودن یک محصول
            Dim produit As New sProduit
            With produit
                .nom = "xxx"
                .prix = 1
            End With
            Try
                objProduits.ajouterProduit(produit)
            Catch ex As ExceptionProduits
                afficheErreurs(ex.erreurs)
            End Try
             ' نمایش تمام محصولات
            afficheProduits(objProduits)
             'بازیابی شناسه محصول اضافه شده
            produit.id = CType(contenu.Rows(contenu.Rows.Count - 1)("id"), Integer)
             ' ویرایش محصول اضافه شده
            produit.prix = 200
            Try
                objProduits.modifierProduit(produit)
            Catch ex As ExceptionProduits
                afficheErreurs(ex.erreurs)
            End Try
             ' نمایش همه محصولات
            afficheProduits(objProduits)
             ' محصول افزوده‌شده را حذف می‌کند
            Try
                objProduits.supprimerProduit(produit.id)
            Catch ex As ExceptionProduits
                afficheErreurs(ex.erreurs)
            End Try
             ' تمام محصولات را نمایش می‌دهد
            afficheProduits(objProduits)
        End Sub

        Sub afficheProduits(ByRef objProduits As produits)
             ' جدول محصولات را در یک DataTable فراخوانی می‌کند
            Try
                contenu = objProduits.getProduits()
            Catch ex As ExceptionProduits
                afficheErreurs(ex.erreurs)
                Environment.Exit(2)
            End Try
            Dim lignes As DataRowCollection = contenu.Rows
            For i As Integer = 0 To lignes.Count - 1
                 ' ردیف i از جدول
                Console.Out.WriteLine(lignes(i).Item("id").ToString + "," + lignes(i).Item("nom").ToString + _
                "," + lignes(i).Item("prix").ToString)
            Next
             'خروجی کنسول را متوقف می‌کند
            Console.WriteLine("...")
            Console.ReadLine()
        End Sub

        Sub afficheErreurs(ByRef erreurs As ArrayList)
             'خطاها را در کنسول نمایش می‌دهد
            If erreurs.Count <> 0 Then
                Console.WriteLine("Les erreurs suivantes se sont produites :")
                For i As Integer = 0 To erreurs.Count - 1
                    Console.WriteLine(String.Format("-- {0}", CType(erreurs(i), String)))
                Next
            End If
             'خروجی کنسول را متوقف می‌کند
            Console.WriteLine("...")
            Console.ReadLine()
        End Sub
    End Module
End Namespace

ما دو فایل منبع را کامپایل می‌کنیم:

dos>vbc /t:library /r:system.dll /r:system.data.dll /r:system.xml.dll produits.vb

dos >vbc /r:produits.dll /r:system.dll /r:system.data.dll /r:system.xml.dll testproduits.vb

dos>dir
07/04/2004  08:40             7 168 produits.dll
04/04/2004  16:38           118 784 produits.mdb
07/04/2004  08:31             6 209 produits.vb
07/04/2004  08:40             5 120 testproduits.exe
03/04/2004  19:02             3 312 testproduits.vb

سپس آزمایش می‌کنیم:

dos>testproduits produits.mdb
1,produit1,10
2,produit2,20
3,produit3,30
...

1,produit1,10
2,produit2,20
3,produit3,30
8,xxx,1
...

1,produit1,10
2,produit2,20
3,produit3,30
8,xxx,200
...

1,produit1,10
2,produit2,20
3,produit3,30
...

از خوانندگان دعوت می‌شود خروجی صفحه بالا را با کد برنامه آزمایشی مقایسه کنند.

9.6. برنامه وب برای به‌روزرسانی جدول محصولات کش‌شده

9.6.1. مقدمه

ما اکنون در حال نوشتن یک برنامه وب برای به‌روزرسانی جدول محصولات (افزودن، حذف، تغییر) هستیم. جدول به‌روزرسانی‌شده در حافظه یک شیء [DataTable] باقی می‌ماند و توسط همه کاربران به اشتراک گذاشته می‌شود. ما می‌خواهیم دو نکته را برجسته کنیم:

  • مدیریت یک شیء [DataTable]
  • مسائل مربوط به به‌روزرسانی همزمان یک جدول توسط چندین کاربر.

معماری MVC برنامه به شرح زیر خواهد بود:

9.6.2. کارکرد و نماها

صفحه اصلی برنامه به شرح زیر است:

Image

 

این نما، با نام [formulaire]، به کاربر امکان می‌دهد تا یک شرط فیلتر را روی محصولات اعمال کند و تعداد محصولات در هر صفحه را که مایل است ببیند، تنظیم نماید.

خیر.
نام
نوع
نقش
1
lnkFiltre
LinkButton
نما [Formulaire] را نمایش می‌دهد که برای تنظیم شرط فیلتر استفاده می‌شود
2
lnkMisaJour
LinkButton
نما [Produits] را نمایش می‌دهد که برای مشاهده و به‌روزرسانی جدول محصولات (اصلاح و حذف) استفاده می‌شود
3
lnkAjout
LinkButton
نما [Ajout] را نمایش می‌دهد که برای افزودن یک محصول استفاده می‌شود
4
panel
vueFormulaire
نما [Formulaire]
5
txtFiltre
TextBox
شرط فیلتر
6
txtPages
TextBox
تعداد محصولات در هر صفحه
7
rfvLignes
RequiredFieldValidator
بررسی می‌کند که آیا یک مقدار در [txtPages] موجود است
8
rvLignes
RangeValidator
بررسی می‌کند که آیا txtPages در محدوده [3,10] قرار دارد
9
btnExécuter
 
دکمه [submit]، که نمای [produits] را بر اساس شرط (۵) فیلتر می‌کند
10
lblInfo1
برچسب
متن اطلاعاتی در صورت بروز خطا

برای مثال، اگر نما [formulaire] به صورت زیر پر شود:

Image

نتیجه زیر به دست می‌آید:

خیر.
نام
نوع
نقش
1
vueProduits
پنل
 
2
rdCroissant
rdDécroissant
RadioButton
به کاربر امکان می‌دهد ترتیب مرتب‌سازی دلخواه را هنگام کلیک بر روی سرستون [nom] یا [prix] تنظیم کند. هر دو دکمه بخشی از گروه [rdTri] هستند.
3
DataGrid1
DataGrid
نمایش شبکه برای نمای فیلترشده جدول محصولات. فیلتر همان فیلتری است که توسط نما [formulaire] تنظیم شده است. ما همچنین داریم .AllowPaging=true, .AllowSorting=true
4
DataGrid2
DataGrid
کل جدول محصولات را نمایش می‌دهد – به شما امکان می‌دهد به‌روزرسانی‌ها را پیگیری کنید
5
LblInfo2
برچسب
متن آموزنده، به‌ویژه در صورت بروز خطا
6
DataGrid3
DataGrid
محصولات حذف‌شده را در جدول محصولات نمایش می‌دهد
7
DataGrid4
DataGrid
محصولات اصلاح‌شده را در جدول محصولات نمایش می‌دهد
8
DataGrid5
DataGrid
محصولات اضافه شده را در جدول محصولات نمایش خواهد داد

در این صفحه پنج مخزن داده وجود دارد. همه آن‌ها یک جدول یکسان، [dtProduits]، را از طریق یک نمای متفاوت، [DataView]، نمایش می‌دهند. یک نما، زیرمجموعه‌ای از سطرهای جدول منبع را نشان می‌دهد. این زیرمجموعه با استفاده از خواص [RowFilter] و [RowStateFilter] از کلاس [DataView] ایجاد می‌شود:

  • [RowFilter] به شما امکان می‌دهد فیلتری را روی سطرها اعمال کنید، مانند [prix>30] که در بالا نشان داده شده است. این نوع فیلترسازی توسط [DataGrid1] استفاده خواهد شد.
  • [RowStateFilter] به شما امکان می‌دهد فیلتری بر اساس وضعیت سطر جدول تنظیم کنید. این وضعیت، وضعیت سطر را نسبت به وضعیت اصلی آن در زمان ایجاد نمای جدول نشان می‌دهد. در اینجا، جدول [dtProduits] از یک پایگاه داده استخراج شده است. در ابتدا، تمام سطرهای آن وضعیت مساوی با [Original] خواهند داشت تا نشان دهند که این‌ها سطرهای اصلی جدول هستند. این وضعیت ممکن است بعداً تغییر کرده و مقادیر مختلفی به خود بگیرد که برخی از آن‌ها در زیر فهرست شده است:
    • [Added]: سطر اضافه شده است – این سطر پیش از این جزئی از جدول اصلی نبوده است
    • [Deleted]: سطر حذف شده است – این سطر هنوز در جدول موجود است اما به‌عنوان «برای حذف» علامت‌گذاری شده است
    • [Modified]: سطر اصلاح شده است

[RowStateFilter] سطرها را در جدول با وضعیت خاصی نمایش می‌دهد:

  • (ادامه)
    • [DataViewRowState.Added]: فقط ردیف‌های اضافه شده نمایش داده می‌شوند. آنها با مقادیر فعلی خود نشان داده می‌شوند.
    • [DataViewRowState.ModifiedOriginal]: فقط سطرهای اصلاح‌شده نمایش داده می‌شوند. آنها با مقادیر اصلی خود نشان داده می‌شوند.
    • [DataViewRowState.ModifiedCurrent]: فقط سطرهای اصلاح‌شده نمایش داده می‌شوند. آنها با مقادیر فعلی خود نشان داده می‌شوند.
    • [DataViewRowState.Deleted]: فقط سطرهای حذف‌شده نمایش داده می‌شوند. آنها با مقادیر اصلی خود نشان داده می‌شوند.
    • [DataViewRowState.CurrentRows]: سطرهایی که حذف نشده‌اند نمایش داده می‌شوند. آنها با مقادیر فعلی خود نشان داده می‌شوند.

بنابراین، فیلتر [RowStateFilter] مقادیر زیر را خواهد داشت:

DataGrid1
نداشتن فیلتر برای وضعیت خط
 
DataGrid2
DataViewRowState.CurrentRows
وضعیت فعلی جدول محصولات را نمایش می‌دهد
DataGrid3
DataViewRowState.Deleted
رده‌های حذف‌شده از جدول محصول را نمایش می‌دهد
DataGrid4
DataViewRowState.ModifiedOriginal
رده‌های اصلاح‌شده در جدول محصولات را به همراه مقادیر اصلی‌شان نمایش می‌دهد
DataGrid5
DataViewRowState.Added
رده‌های افزوده شده به جدول اصلی محصولات را نمایش می‌دهد

کانتینرهای [DataGrid1-5] به ما امکان می‌دهند تا به‌روزرسانی‌های جدول [dtProduits] را پیگیری کنیم. کامپوننت [DataGrid1] امکان ویرایش یا حذف یک محصول را فراهم می‌کند. خواهیم دید که پیکربندی این کامپوننت چگونه این به‌روزرسانی را ممکن می‌سازد. این کامپوننت همچنین صفحه‌بندی و مرتب‌سازی شده است. این دو جنبه قبلاً در یک مثال قبلی پوشش داده شده‌اند.

لینک [Ajout] دسترسی به فرمی برای افزودن محصول را فراهم می‌کند:

خیر.
نام
نوع
نقش
1
vueAjout
پنل
 
2
txtNom
TextBox
نام محصول
3
rfvNom
RequiredFieldValidator
بررسی وجود مقدار در [txtNom]
4
txtPrix
TextBox
قیمت محصول
5
rfvPrix
RequiredFieldValidator
بررسی می‌کند که آیا یک مقدار در [txtPrix] موجود است
6
cvPrix
CompareValidator
بررسی می‌کند که قیمت ≥ ۰ باشد
7
btnAjouter
دکمه
[submit] دکمه «افزودن محصول»
8
lblInfo3
برچسب
متن اطلاعاتی مربوط به نتیجه عملیات افزودن

پایگاه داده [produits] ممکن است هنگام راه‌اندازی برنامه در دسترس نباشد. در این صورت، نما [erreurs] به کاربر نمایش داده می‌شود:

خیر.
نام
نوع
نقش
1
vueErreurs
پنل
 
2
rptErreurs
تکرارکننده
فهرست خطاها

9.6.3. پیکربندی کانتینر داده

پنج کانتینر [DataGrid] زیر [WebMatrix] پیکربندی شده‌اند. آن‌ها با استفاده از لینک [Configuration automatique] در پنل ویژگی‌هایشان قالب‌بندی شده‌اند (رنگ‌ها و حاشیه‌ها). کانتینر [DataGrid1] با استفاده از لینک [Générateur de propriétés] در همان پنل پیکربندی شده است. مرتب‌سازی فعال شده است (زبانه‌ی [Général]):

Image

برخلاف چهار کانتینر دیگر، ستون‌ها به‌طور خودکار ایجاد نمی‌شوند. آن‌ها به‌صورت دستی با استفاده از ویزارد تعریف شده‌اند:

Image

ابتدا، دو ستون از نوع [Colonne connexe] با نام‌های [nom] و [prix] ایجاد شدند. این‌ها به ترتیب به فیلدهای [nom] و [prix] در منبع داده‌ای که کانتینرها نمایش خواهند داد، متصل شدند. در اینجا، برای مثال، پیکربندی ستون [nom] آمده است:

Image

بیان مرتب‌سازی، عبارتی است که باید پس از عبارت [order by] در دستور SQL [select] قرار گیرد، که هنگام ... اجرا خواهد شد.کاربر روی نام ستون سربرگ [nom] که با فیلد [nom] در [DataGrid] مرتبط است، کلیک می‌کند. در اینجا [nom] را تنظیم کرده‌ایم تا عبارت مرتب‌سازی [order by nom] باشد. خواهیم دید که این را طوری تغییر می‌دهیم که بسته به ترتیب مرتب‌سازی انتخاب‌شده توسط کاربر، به [order by nom asc] یا [order by nom desc] تبدیل شود.

ما همچنین دو ستون دکمه ایجاد کرده‌ایم:

Image

ستون [Modifier, Mettre à jour, Annuler] به ما امکان ویرایش یک محصول را می‌دهد و ستون [Supprimer] امکان حذف آن را فراهم می‌کند. هر یک از این ستون‌ها قابل پیکربندی هستند. ستون [Modifier, Mettre à jour, Annuler] پیکربندی‌های زیر را ارائه می‌دهد:

Image

می‌توانیم ببینیم که متن دکمه قابل ویرایش است. در مورد خود دکمه‌ها، می‌توانیم بین لینک‌ها و دکمه‌ها (لیست کشویی بالا) انتخاب کنیم. در اینجا لینک‌ها انتخاب شده‌اند. پیکربندی ستون [Supprimer] مشابه است. علاوه بر این جادوگر پیکربندی، ما مستقیماً از پنجرهٔ ویژگی‌ها برای [DataGrid] استفاده کردیم تا ویژگی [DataKeyField] را تنظیم کنیم، که مشخص می‌کند کدام فیلد در منبع داده برای نمایه کردن سطرها در [DataGrid] استفاده می‌شود. در این مورد، از کلید اصلی جدول محصولات استفاده می‌شود:

Image

در نهایت، این پیکربندی کد ارائه زیر را تولید می‌کند:


<asp:DataGrid id="DataGrid1" runat="server" AllowSorting="True" PageSize="4" AllowPaging="True" AutoGenerateColumns="False" DataKeyField="id">
<SelectedItemStyle ...></SelectedItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<Columns>
         <asp:BoundColumn DataField="nom" SortExpression="nom" HeaderText="nom"></asp:BoundColumn>
          <asp:BoundColumn DataField="prix" SortExpression="prix" HeaderText="prix"></asp:BoundColumn>
           <asp:EditCommandColumn ButtonType="LinkButton" UpdateText="Mettre &#224; jour" CancelText="Annuler" EditText="Modifier"></asp:EditCommandColumn>
          <asp:ButtonColumn Text="Supprimer" CommandName="Delete"></asp:ButtonColumn>
  </Columns>
<PagerStyle NextPageText="Suivant" PrevPageText="Pr&#233;c&#233;dent" ...></PagerStyle>
</asp:DataGrid>

همان‌طور که همیشه، پس از کسب مقداری تجربه، می‌توانید تمام یا بخشی از کد بالا را مستقیماً بنویسید.

سایر کانتینرهای [DataGrid] دارای پیکربندی پیش‌فرض هستند که با تولید خودکار ستون‌های [DataGrid] از ستون‌های منبع داده‌ای که با آن مرتبط است، به‌دست می‌آید.

کانتینر [Repeater] برای نمایش فهرستی از خطاها استفاده می‌شود. پیکربندی آن مستقیماً در کد ارائه تعریف می‌شود:


                            <asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
                                <HeaderTemplate>
                                    Les erreurs suivantes se sont produites :
                                    <ul>
                                </HeaderTemplate>
                                <ItemTemplate>
                                    <li>
                                        <%# Container.DataItem %>
                                    </li>
                                </ItemTemplate>
                                <FooterTemplate>
                                    </ul>
                                </FooterTemplate>
                            </asp:Repeater>

هر خط از کامپوننت مقدار [Container.DataItemc.a.d را نمایش می‌دهد. مقدار متناظر در فهرست داده‌ها از نوع [ArrayList] خواهد بود و نمایانگر فهرستی از خطاها است.

9.6.4. کد ارائهٔ برنامه

این در فایل [main.aspx] ذخیره می‌شود:


<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
    </HEAD>
    <body>
        <form id="Form1" runat="server">
            <P>
                <table>
                    <tr>
                        <td><FONT size="6">Options :</FONT></td>
                        <td>
                          <asp:linkbutton id="lnkFiltre" runat="server" CausesValidation="False">
                                  Filtrage
                          </asp:linkbutton>
                    </td>
                        <td>
                        <asp:linkbutton id="lnkMisajour" runat="server" CausesValidation="False">
                            Mise à jour
                           </asp:linkbutton>
                    </td>
                        <td>
                          <asp:linkbutton id="lnkAjout" runat="server" CausesValidation="False">
                               Ajout
                           </asp:linkbutton>
                       </td>
                    </tr>
                </table>
            </P>
            <HR width="100%" SIZE="1">
            <table>
                <tr>
                    <td>

                  <asp:panel id="vueFormulaire" runat="server">
                            <P>Condition de filtrage sur&nbsp;la table LISTE.&nbsp;Exemple : prix&lt;100 and 
                                prix&gt;50</P>
                            <P>
                                <asp:TextBox id="txtFiltre" runat="server" Columns="60"></asp:TextBox></P>
                            <P>Nombre de lignes par page :
                                <asp:TextBox id="txtPages" runat="server" Columns="3">5</asp:TextBox>
                                <asp:RequiredFieldValidator id="rfvLignes" runat="server" Display="Dynamic"
                            ControlToValidate="txtPages" ErrorMessage="Indiquez le nombre de lignes par page"
                                    EnableClientScript="False">
                     </asp:RequiredFieldValidator></P>
                            <P>
                                <asp:RangeValidator id="rvLignes" runat="server" Display="Dynamic"
                        ControlToValidate="txtPages" ErrorMessage="Vous devez indiquer un nombre entre 3 et 10"
                                    EnableClientScript="False" MaximumValue="10" MinimumValue="3" Type="Integer"
                             EnableViewState="False">
                             </asp:RangeValidator></P>
                            <P>
                                <asp:Label id="lblinfo1" runat="server"></asp:Label></P>
                            <P>
                                <asp:Button id="btnExécuter" runat="server" CausesValidation="False"
                              EnableViewState="False" Text="Exécuter">
                             </asp:Button></P>
                        </asp:panel>

                      <asp:panel id="vueProduits" runat="server">
                            <TABLE>
                                <TR>
                                    <TD align="center" bgColor="#ff9966">
                                        <P>Tri
                                            <asp:RadioButton id="rdCroissant" runat="server" Text="croissant"
                                     GroupName="rdTri" Checked="True">
                                 </asp:RadioButton>
                                            <asp:RadioButton id="rdDécroissant" runat="server" Text="décroissant"
                                                 GroupName="rdTri">
                                    </asp:RadioButton></P>
                                    </TD>
                                    <TD align="center" bgColor="#ff9966">Tous les produits
                                    </TD>
                                    <TD align="center" bgColor="#ff9966">Suppressions
                                    </TD>
                                    <TD align="center" bgColor="#ff9966">Modifications
                                    </TD>
                                    <TD align="center" bgColor="#ff9966">Ajouts
                                    </TD>
                                <TR>
                                    <TD vAlign="top">
                                        <P>
                                            <asp:DataGrid id="DataGrid1" runat="server" AllowSorting="True" PageSize="4"
                                                 AllowPaging="True" ....     AutoGenerateColumns="False" DataKeyField="id">
                                                <ItemStyle ...></ItemStyle>
                                                <HeaderStyle ...></HeaderStyle>
                                                <FooterStyle ...></FooterStyle>
                                                <Columns>
                                                    <asp:BoundColumn DataField="nom" SortExpression="nom" HeaderText="nom">
                                               </asp:BoundColumn>
                                                    <asp:BoundColumn DataField="prix" SortExpression="prix" HeaderText="prix">
                                          </asp:BoundColumn>
                                                    <asp:EditCommandColumn ButtonType="LinkButton" UpdateText="Mettre &#224; jour"
                                                         CancelText="Annuler" EditText="Modifier">
                                       </asp:EditCommandColumn>
                                                    <asp:ButtonColumn Text="Supprimer" CommandName="Delete">
                                     </asp:ButtonColumn>
                                                </Columns>
                                                <PagerStyle NextPageText="Suivant" PrevPageText="Pr&#233;c&#233;dent" ....>
                                           </PagerStyle>
                                            </asp:DataGrid>
                                       </P>
                                    </TD>
                                    <TD vAlign="top">
                                        <asp:DataGrid id="DataGrid2" runat="server" ...>
                                            <AlternatingItemStyle ...></AlternatingItemStyle>
                                            <ItemStyle ...></ItemStyle>
                                            <HeaderStyle ....></HeaderStyle>
                                            <FooterStyle ...></FooterStyle>
                                            <PagerStyle ... Mode="NumericPages"></PagerStyle>
                                        </asp:DataGrid>
                            </TD>
                                    <TD vAlign="top">
                                        <asp:DataGrid id="DataGrid3" runat="server" ...>
                                            <ItemStyle ...></ItemStyle>
                                            <HeaderStyle ...></HeaderStyle>
                                            <FooterStyle ...></FooterStyle>
                                            <PagerStyle ...></PagerStyle>
                                        </asp:DataGrid>
                                   </TD>
                                    <TD vAlign="top">
                                        <asp:DataGrid id="DataGrid4" runat="server" ...>
                                            <ItemStyle ....></ItemStyle>
                                            <HeaderStyle ....></HeaderStyle>
                                            <FooterStyle ...></FooterStyle>
                                            <PagerStyle .... Mode="NumericPages"></PagerStyle>
                                        </asp:DataGrid>
                       </TD>
                                    <TD vAlign="top">
                                        <asp:DataGrid id="DataGrid5" runat="server....>
                                            <AlternatingItemStyle ...></AlternatingItemStyle>
                                            <HeaderStyle ..></HeaderStyle>
                                            <FooterStyle ...></FooterStyle>
                                            <PagerStyle ....></PagerStyle>
                                        </asp:DataGrid>
                         </TD>
                                </TR>
                            </TABLE>
                            <P></P>
                            <P>
                                <asp:Label id="lblInfo2" runat="server"></asp:Label></P>
                    </asp:panel>

                   <asp:panel id="vueErreurs" runat="server">
                            <asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
                                <HeaderTemplate>
                                    Les erreurs suivantes se sont produites :
                                    <ul>
                                </HeaderTemplate>
                                <ItemTemplate>
                                    <li>
                                        <%# Container.DataItem %>
                                    </li>
                                </ItemTemplate>
                                <FooterTemplate>
                                    </ul>
                                </FooterTemplate>
                            </asp:Repeater>
                        </asp:panel>

                  <asp:panel id="vueAjout" EnableViewState="False" Runat="server">
                            <P>Ajout d'un produit</P>
                            <P>
                                <TABLE ... border="1">
                                    <TR>
                                        <TD>nom</TD>
                                        <TD>
                                            <asp:TextBox id="txtNom" runat="server" Columns="30"></asp:TextBox>
                                            <asp:RequiredFieldValidator id="rfvNom" runat="server" ControlToValidate="txtNom"
                                                     ErrorMessage="Vous devez indiquer un nom">
                               </asp:RequiredFieldValidator>
                                      </TD>
                                    </TR>
                                    <TR>
                                        <TD>prix</TD>
                                        <TD>
                                            <asp:TextBox id="txtPrix" runat="server" Columns="10"></asp:TextBox>
                                            <asp:RequiredFieldValidator id="rfvPrix" runat="server" ControlToValidate="txtPrix"
                                                 ErrorMessage="Vous devez indiquer un prix">
                                           </asp:RequiredFieldValidator>
                                            <asp:CompareValidator id="cvPrix" runat="server" ControlToValidate="txtPrix"
                                                     ErrorMessage="CompareValidator" Type="Double" Operator="GreaterThanEqual"
                                                     ValueToCompare="0">
                             </asp:CompareValidator>
                                   </TD>
                                    </TR>
                                </TABLE>
                            </P>
                            <P>
                                <asp:Button id="btnAjouter" runat="server" CausesValidation="False"
                                     Text="Ajouter">
                               </asp:Button></P>
                            <P>
                                <asp:Label id="lblInfo3" runat="server"></asp:Label></P>
                        </asp:panel>

                </td>
                </tr>
            </table>
        </form>
    </body>
</HTML>

لطفاً به نکات زیر توجه کنید:

  • صفحه از چهار کانتینر (پنل) از نوع [vueFormulaire, vueProduits, vueAjout, vueErreurs] تشکیل شده است که نماهای چهارگانهٔ برنامه را تشکیل خواهند داد.
  • دکمه‌ها یا لینک‌هایی با نوع [submit] دارای ویژگی [CausesValidation=false] هستند. ویژگی [causesValidation=true] اجرای تمام بررسی‌های اعتبارسنجی صفحه را راه‌اندازی می‌کند. با این حال، در این مورد، لازم نیست همه بررسی‌های اعتبارسنجی همزمان انجام شوند. برای مثال، هنگام افزودن یک ورودی جدید، ما نمی‌خواهیم بررسی‌های مربوط به تعداد خطوط در هر صفحه انجام شوند. بنابراین، خودمان مشخص خواهیم کرد که کدام بررسی‌های اعتبارسنجی باید انجام شوند.

9.6.5. کد اعتبارسنجی [global.asax]

اعتبارسنج [global.asax] به شرح زیر است:

<%@ Application src="global.asax.vb" inherits="Global" %>

کد مرتبط [global.asax.vb]:


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Data
Imports Microsoft.VisualBasic
Imports System.Collections

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' بازیابی اطلاعات پیکربندی
        Dim chaînedeConnexion As String = ConfigurationSettings.AppSettings("OLEDBStringConnection")
        Dim defaultProduitsPage As String = ConfigurationSettings.AppSettings("defaultProduitsPage")
        Dim erreurs As New ArrayList
        If IsNothing(chaînedeConnexion) Then erreurs.Add("Le paramètre [OLEDBStringConnection] n'a pas été initialisé")
        If IsNothing(defaultProduitsPage) Then
            erreurs.Add("Le paramètre [defaultProduitsPage] n'a pas été initialisé")
        Else
            Try
                Dim defProduitsPage As Integer = CType(defaultProduitsPage, Integer)
                If defProduitsPage <= 0 Then Throw New Exception
            Catch ex As Exception
                erreurs.Add("Le paramètre [defaultProduitsPage] a une valeur incorrecte")
            End Try
        End If
        ' آیا خطاهای پیکربندی وجود دارد؟
        If erreurs.Count <> 0 Then
            ' اشتباهات را یادداشت کنید
            Application("erreurs") = erreurs
            'خروج
            Exit Sub
        End If
        ' در اینجا هیچ خطای پیکربندی وجود ندارد
        ' شیء محصول ایجاد کنید
        Dim dtProduits As DataTable
        Try
            dtProduits = New produits(chaînedeConnexion).getProduits
        Catch ex As ExceptionProduits
            'خطایی هنگام دسترسی به محصولات رخ داد؛ این در برنامه ثبت می‌شود
            Application("erreurs") = ex.erreurs
            Exit Sub
        Catch ex As Exception
            ' خطای رسیدگی‌نشده
            erreurs.Add(ex.Message)
            Application("erreurs") = erreurs
            'خروج از زیرروال
        End Try
        ' در اینجا هیچ خطای инициализация وجود ندارد
        'تعداد محصولات در هر صفحه ذخیره می‌شود
        Application("defaultProduitsPage") = defaultProduitsPage
        'ذخیره جدول محصولات
        Application("dtProduits") = dtProduits
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' ابتدایی‌سازی متغیرهای جلسه
        If IsNothing(Application("erreurs")) Then
            ' نمایش جدول محصولات
            Session("dvProduits") = CType(Application("dtProduits"), DataTable).DefaultView
            'تعداد محصولات در هر صفحه
            Session("nbProduitsPage") = Application("defaultProduitsPage")
            ' صفحهٔ فعلی نمایش داده شده
            Session("pageCourante") = 0
        End If
    End Sub
End Class

در [Application_Start]، ما با بازیابی دو مورد اطلاعات از فایل پیکربندی برنامه، [web.config]: شروع می‌کنیم:

  • OLEDBStringConnection: رشته OLEDB برای اتصال به پایگاه داده محصولات
  • defaultProduitsPage: تعداد پیش‌فرض محصولات نمایش‌داده‌شده در هر صفحه

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="OLEDBStringConnection" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\devel\aspnet\poly\webforms3\vs\majproduits1\produits.mdb" />
        <add key="defaultProduitsPage" value="5" />
    </appSettings>
</configuration>

اگر هر یک از این دو مورد اطلاعات ناقص باشد، یک لیست خطا ایجاد شده و به برنامه اضافه می‌شود. همین امر در صورتی که پارامتر [defaultProduitsPage] وجود داشته اما نادرست باشد نیز صدق می‌کند. اگر هر دو پارامتر مورد انتظار موجود و صحیح باشند، جدولی با نام [dtProduits] ایجاد و به برنامه اضافه می‌شود. این جدول است که توسط کلاینت‌های مختلف استفاده و به‌روزرسانی خواهد شد. خود پایگاه داده بدون تغییر باقی می‌ماند. ما در یک برنامه آینده به به‌روزرسانی پایگاه داده خواهیم پرداخت. این جدول با استفاده از یک نمونه از کلاس [produits] که قبلاً مورد بحث قرار گرفت و متد [getProduits] آن ایجاد می‌شود. ایجاد جدول [dtProduits] ممکن است با شکست مواجه شود. در این مورد، می‌دانیم که کلاس [produits] یک استثنا از نوع [ExceptionProduits] را پرتاب می‌کند. این استثنا در اینجا دریافت می‌شود و لیست خطاهای مرتبط در برنامه‌ای که با کلید [erreurs] مرتبط است، قرار داده می‌شود. وجود این کلید در اطلاعات ذخیره‌شده در برنامه، هر بار که یک درخواست پردازش می‌شود، بررسی خواهد شد. اگر یافت شود، نما [erreurs] به کلاینت ارسال می‌شود.

اگرچه جدول [dtProduits] بین همه کلاینت‌های وب مشترک است، با این حال هر کلاینت نمای [dvProduits] مخصوص به خود را از این جدول خواهد داشت. این امر به این دلیل است که هر کلاینت وب می‌تواند فیلتری روی جدول [dtProduits] و همچنین یک ترتیب مرتب‌سازی تنظیم کند. این اطلاعات که مختص هر کلاینت وب است، در نمای کلاینت ذخیره می‌شود. در نتیجه، این نما در [Session_Start] ایجاد می‌شود تا بتواند در جلسه فردی هر کلاینت قرار گیرد. ویژگی [DefaultView] در جدول [dtProduits] برای ارائه یک نمای پیش‌فرض از جدول استفاده می‌شود. در ابتدا، نه فیلتری وجود دارد و نه ترتیب مرتب‌سازی. علاوه بر این، دو مورد اطلاعات نیز در جلسه ذخیره می‌شوند:

  • تعداد محصولات در هر صفحه، که با کلید [nbProduitsPage] مشخص می‌شود. هنگامی که جلسه آغاز می‌شود، این عدد برابر با مقدار پیش‌فرض تعریف‌شده در فایل پیکربندی است.
  • شماره صفحه فعلی محصولات. در ابتدا، این صفحه اول است.

9.6.6. کد کنترل‌کننده [main.aspx.vb]

قالب کنترل‌کننده به شرح زیر است:


Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Data
Imports st.istia.univangers.fr
Imports System
Imports System.Xml
Imports System.Web.UI.WebControls

Public Class main
    Inherits System.Web.UI.Page

    ' اجزای صفحه
    Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
    Protected WithEvents btnExécuter As System.Web.UI.WebControls.Button
    Protected WithEvents vueFormulaire As System.Web.UI.WebControls.Panel
    Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
    Protected WithEvents lnkErreurs As System.Web.UI.WebControls.LinkButton
    Protected WithEvents vueErreurs As System.Web.UI.WebControls.Panel
    Protected WithEvents rdCroissant As System.Web.UI.WebControls.RadioButton
    Protected WithEvents rdDécroissant As System.Web.UI.WebControls.RadioButton
    Protected WithEvents txtFiltre As System.Web.UI.WebControls.TextBox
    Protected WithEvents lblInfo2 As System.Web.UI.WebControls.Label
    Protected WithEvents lblinfo1 As System.Web.UI.WebControls.Label
    Protected WithEvents lblErreurs As System.Web.UI.WebControls.Label
    Protected WithEvents DataGrid2 As System.Web.UI.WebControls.DataGrid
    Protected WithEvents vueProduits As System.Web.UI.WebControls.Panel
    Protected WithEvents vueAjout As System.Web.UI.WebControls.Panel
    Protected WithEvents lnkMisajour As System.Web.UI.WebControls.LinkButton
    Protected WithEvents lnkFiltre As System.Web.UI.WebControls.LinkButton
    Protected WithEvents txtNom As System.Web.UI.WebControls.TextBox
    Protected WithEvents txtPrix As System.Web.UI.WebControls.TextBox
    Protected WithEvents btnAjouter As System.Web.UI.WebControls.Button
    Protected WithEvents lblInfo3 As System.Web.UI.WebControls.Label
    Protected WithEvents rfvLignes As System.Web.UI.WebControls.RequiredFieldValidator
    Protected WithEvents rvLignes As System.Web.UI.WebControls.RangeValidator
    Protected WithEvents rfvNom As System.Web.UI.WebControls.RequiredFieldValidator
    Protected WithEvents rfvPrix As System.Web.UI.WebControls.RequiredFieldValidator
    Protected WithEvents cvPrix As System.Web.UI.WebControls.CompareValidator
    Protected WithEvents lnkAjout As System.Web.UI.WebControls.LinkButton
    Protected WithEvents DataGrid3 As System.Web.UI.WebControls.DataGrid
    Protected WithEvents DataGrid4 As System.Web.UI.WebControls.DataGrid
    Protected WithEvents DataGrid5 As System.Web.UI.WebControls.DataGrid

    ' داده‌های صفحه
    Protected dtProduits As DataTable
    Protected dvProduits As DataView
    Protected defaultProduitsPage As Integer
    Protected erreur As Boolean = False

    ' بارگذاری صفحه
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
    End Sub

    Private Sub afficheErreurs()
...
    End Sub

    Private Sub afficheFormulaire()
...
    End Sub

    Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
....
    End Sub

    Private Sub afficheProduits(ByVal page As Integer, ByVal taillePage As Integer)
...
    End Sub

    Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
...
    End Sub

    Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
....
    End Sub

    Private Sub DataGrid1_DeleteCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.DeleteCommand
....
    End Sub

    Private Sub DataGrid1_EditCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.EditCommand
...
    End Sub

    Private Sub DataGrid1_CancelCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.CancelCommand
...
    End Sub

    Private Sub DataGrid1_UpdateCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.UpdateCommand
...
    End Sub

    Private Sub supprimerProduit(ByVal idProduit As Integer)
...
    End Sub

    Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
....
    End Sub

    Private Sub lnkMisajour_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkMisajour.Click
...
    End Sub

    Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
...
    End Sub

    Private Sub afficheAjout()
...
    End Sub

    Private Sub lnkFiltre_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkFiltre.Click
....
    End Sub

    Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
....
    End Sub
End Class

9.6.7. داده‌های نمونه

کلاس [main] از داده‌های نمونه زیر استفاده می‌کند:

Public Class main
    Inherits System.Web.UI.Page

     ' اجزای صفحه
    Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
...

     ' داده‌های صفحه
    Protected dtProduits As DataTable
    Protected dvProduits As DataView
    Protected defaultProduitsPage As Integer
dtProduits
جدول محصول [DataTable] – مشترک برای همه مشتریان
dvProduits
نما [DataView] محصولات – مختص هر مشتری
defaultProduitsPage
تعداد پیش‌فرض محصولات در هر صفحه

9.6.8. رویه [Page_Load] برای بارگذاری صفحه

کد مربوط به رویه [page_Load] به شرح زیر است:


    'بارگذاری صفحه
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'بررسی اینکه آیا برنامه با خطایی مواجه شده است
        If Not IsNothing(Application("erreurs")) Then
            ' برنامه به درستی راه‌اندازی نشده است
            afficheErreurs(CType(Application("erreurs"), ArrayList))
            Exit Sub
        End If
        ' استخراج یک مرجع از جدول محصولات
        dtProduits = CType(Application("dtProduits"), DataTable)
         ' بازیابی نمای محصول
        dvProduits = CType(Session("dvProduits"), DataView)
         ' بازیابی تعداد محصولات در هر صفحه
        defaultProduitsPage = CType(Application("defaultProduitsPage"), Integer)
         'لغو هر به‌روزرسانی در حال انجام
        DataGrid1.EditItemIndex = -1
        'پرس‌وجوی اول
        If Not IsPostBack Then
            'نمایش فرم اولیه
            txtPages.Text = defaultProduitsPage.ToString
            afficheFormulaire()
        End If
    End Sub
  • ما کار را با بررسی اینکه آیا جدول محصول هنگام شروع برنامه با موفقیت بارگذاری شده است، آغاز می‌کنیم. اگر اینطور نباشد، نمای [erreurs] را همراه با پیام‌های خطای مناسب نمایش می‌دهیم، سپس پس از تنظیم مقدار بولین [erreur] به true، از رویه خارج می‌شویم. این شاخص توسط رویه‌های خاصی بررسی می‌شود.
  • جدول محصولات [dtProduits] را از برنامه فراخوانی کرده و آن را در متغیر نمونه [dtProduits] ذخیره می‌کنیم تا بتوان از آن توسط تمام متدهای صفحه استفاده شود.
  • ما همین کار را با ویو [dvProduits] که از جلسه (session) بازیابی شده است، و تعداد پیش‌فرض محصولات در هر صفحه که از برنامه بازیابی شده است، انجام می‌دهیم.
  • اگر این اولین درخواست مشتری باشد، فرم تعریف شرایط فیلترینگ و صفحه‌بندی را نمایش می‌دهیم، با صفحه‌بندی پیش‌فرض [defaultProduitsPage] محصول در هر صفحه.
  • ما حالت ویرایش کامپوننت [dataGrid1] را لغو می‌کنیم. این کامپوننت دارای ویژگی [EditItemIndex] است که نمایه عنصر در حال ویرایش فعلی است. اگر [EditItemIndex]=-1 باشد، در حال حاضر هیچ عنصری در حال ویرایش نیست. اگر [EditItemIndex]=i باشد، عنصر شماره i از کامپوننت [DataGrid] در حال ویرایش است. سپس به شکلی متفاوت از سایر عناصر [dataGrid] نمایش داده می‌شود:

Image

در بالا، عنصر [Produit8, 80] در حالت [édition] قرار دارد. همانطور که در بالا نشان داده شده است، کاربر می‌تواند به‌روزرسانی خود را از طریق لینک [Mettre à jour] تأیید کند، یا آن را از طریق لینک [Annuler] لغو نماید. آنها همچنین می‌توانند از لینک‌های دیگری که به عنصر در حال ویرایش ارتباط ندارند، استفاده کنند. با تنظیم [DataGrid1.EditItemIndex=-1] در هر بار بارگذاری صفحه، اطمینان حاصل می‌کنیم که حالت ویرایش برای [DataGrid1] به‌طور سیستماتیک لغو شود. مقدار متفاوتی تنها در صورتی به آن اختصاص داده می‌شود که لینک [Modifier] کلیک شده باشد. این کار در رویه‌ای که این رویداد را مدیریت می‌کند انجام خواهد شد. حالات زیر ممکن است پیش بیاید:

  1. لینک [Modifier] در عنصر شمارهٔ ۸ از [DataGrid1] کلیک شده است. [Page_Load] ابتدا مقدار [DataGrid1.EditItemIndex] را روی -1 تنظیم می‌کند. سپس رویه مسئول رویداد [Modifier] مقدار 8 را در [DataGrid1.EditItemIndex] تنظیم خواهد کرد. در نهایت، هنگامی که صفحه به کلاینت بازگردانده می‌شود، عنصر شماره 8 واقعاً در حالت [édition] خواهد بود و همانطور که در بالا نشان داده شده است، ظاهر خواهد شد.
  2. کاربر محصول را که در حالت [édition] است، تغییر می‌دهد و تغییر خود را از طریق لینک [Mettre à jour] تأیید می‌کند. [Page_Load] مقدار -1 را در [DataGrid1.EditItemIndex] قرار می‌دهد. سپس رویهٔ رسیدگی به رویداد [Mettre à jour] اجرا می‌شود و عنصر شمارهٔ ۸ از [dtProduits] به‌روزرسانی می‌گردد. هنگامی که صفحه به کلاینت بازگردانده می‌شود، هیچ عنصری در [DataGrid1] در حالت ویرایش نخواهد بود (DataGrid.EditItemIndex=-1).
  3. اگر کاربر به‌روزرسانی محصول را وارد کرده باشد، منطقی است که این به‌روزرسانی را از طریق [Annuler] لغو کند. با این حال، هیچ چیزی مانع از کلیک او روی لینک دیگری نمی‌شود. بنابراین باید برای این سناریو نیز پیش‌بینی لازم را انجام دهیم. در این حالت، مانند موارد قبلی، [Page_Load] ابتدا [DataGrid1.EditItemIndex] را روی -1 تنظیم می‌کند، و پس از آن، رویه‌ای که رویداد رخ‌داده را مدیریت می‌کند، اجرا خواهد شد. این کار خاصیت [DataGrid1.EditItemIndex] را تغییر نخواهد داد و بنابراین در مقدار -1 باقی خواهد ماند. هنگامی که صفحه به کلاینت بازگردانده می‌شود، هیچ عنصری در [DataGrid1] در حالت ویرایش نخواهد بود.

می‌توانیم ببینیم که با تنظیم [EditItemIndex] روی -1 هنگام بارگذاری صفحه، دیگر نیازی نیست نگران این باشیم که آیا کاربر هنگام کلیک روی یک لینک در حالت ویرایش بوده است یا خیر.

9.6.9. نمایش ویوهای [erreurs]، [formulaire] و [ajout]

نما [erreurs] زمانی که صفحه [Page_Load] بارگذاری می‌شود نمایش داده می‌شود، اگر تشخیص داده شود که برنامه به درستی راه‌اندازی نشده است. این امر با مرتبط ساختن مؤلفه داده [rptErreurs] با فهرست خطاهایی که به‌عنوان پارامتر ارسال شده‌اند و با قابل‌مشاهده کردن محفظهٔ مناسب انجام می‌شود. در نهایت، سه پیوند [Filtre, Mise à jour, Ajout] پنهان می‌شوند زیرا در صورت بروز خطا، نمی‌توان از برنامه استفاده کرد.


    Private Sub afficheErreurs(ByVal erreurs As ArrayList)
        'خطاها را به تکرارکننده rptErreurs مرتبط می‌کند
        With rptErreurs
            .DataSource = erreurs
            .DataBind()
        End With
        'گزینه‌ها غیرفعال هستند
        lnkAjout.Visible = False
        lnkMisajour.Visible = False
        lnkFiltre.Visible = False
        'نما نمایش داده می‌شود [erreurs]
        vueErreurs.Visible = True
        vueFormulaire.Visible = False
        vueProduits.Visible = False
        vueAjout.Visible = False
    End Sub

نمای‌های دیگر از میان گزینه‌های ارائه‌شده به کاربر درخواست می‌شوند:

Image

نما [Ajout] زمانی درخواست می‌شود که کاربر روی پیوند [Ajout] در صفحه کلیک کند:


    Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
        'نمای [ajout] را نمایش می‌دهد
        afficheAjout()
    End Sub

    Private Sub afficheAjout()
        'نمای افزودن را نمایش می‌دهد
        vueAjout.Visible = True
        vueFormulaire.Visible = False
        vueProduits.Visible = False
        vueErreurs.Visible = False
    End Sub

نما [Formulaire] زمانی نمایش داده می‌شود که کاربر روی پیوند [Filtrage] در صفحه کلیک کند. این نما به کاربر اجازه می‌دهد تا تعریف کند

  • فیلتر برای جدول محصولات
  • تعداد محصولات نمایش داده شده در هر صفحه وب

یک فرم نمایش داده می‌شود که مقادیر ذخیره‌شده در جلسه را نشان می‌دهد:


    Private Sub lnkFiltre_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkFiltre.Click
        ' معیارهای فیلتر را تنظیم می‌کند
        txtFiltre.Text = dvProduits.RowFilter
        ' تنظیم صفحه‌بندی
        txtPages.Text = CType(Session("nbProduitsPage"), String)
        'فرم فیلتر را نمایش می‌دهد
        afficheFormulaire()
    End Sub

شرایط فیلتر برای یک نما در ویژگی [RowFilter] آن تعریف می‌شود. به یاد داشته باشید که نمای فیلتر شده و صفحه‌بندی شده [dvProduits] نامیده می‌شود و در جلسه زمانی که صفحه [Page_Load] بارگذاری شد، بازیابی شده است. بنابراین شرط فیلتر از [dvProduits.RowFilter] بازیابی می‌شود. تعداد محصولات در هر صفحه نیز از جلسه بازیابی می‌شود. اگر کاربر هرگز این اطلاعات را مشخص نکرده باشد، این عدد با تعداد پیش‌فرض محصولات در هر صفحه برابر است. پس از انجام این کار، فرم مربوط به مشخص کردن این دو مورد اطلاعات نمایش داده می‌شود:


    Private Sub afficheFormulaire()
        'نمای [formulaire] را نمایش می‌دهد
        vueFormulaire.Visible = True
        vueErreurs.Visible = False
        vueProduits.Visible = False
        vueAjout.Visible = False
    End Sub

Image

9.6.10. اعتبارسنجی نما [formulaire]

کلیک روی دکمه [Exécuter] در بالا توسط رویه زیر انجام می‌شود:


    Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
        ' آیا صفحه معتبر است؟
        rfvLignes.Validate()
        rvLignes.Validate()
        If Not rfvLignes.IsValid Or Not rvLignes.IsValid Then
            afficheFormulaire()
            Exit Sub
        End If
        ' پیوست کردن داده‌های فیلترشده به جدول
        Try
            dvProduits.RowFilter = txtFiltre.Text.Trim
        Catch ex As Exception
            lblinfo1.Text = "Erreur de filtrage (" + ex.Message + ")"
            afficheFormulaire()
            Exit Sub
        End Try
        'تعداد محصولات در هر صفحه ذخیره می‌شود
        Session("nbProduitsPage") = txtPages.Text
        'و صفحهٔ فعلی
        Session("pageCourante") = 0
        'همه چیز درست است – داده‌ها نمایش داده می‌شوند
        afficheProduits(0, CType(txtPages.Text, Integer))
    End Sub

ابتدا، شایان ذکر است که این صفحه دارای دو مؤلفه اعتبارسنجی است:

خیر.
name
نوع
نقش
7
rfvLignes
RequiredFieldValidator
بررسی می‌کند که آیا یک مقدار در [txtPages] موجود است
8
rvLignes
RangeValidator
بررسی می‌کند که آیا txtPages در محدوده [3,10] قرار دارد

این رویه با اجرای کد اعتبارسنجی برای دو مؤلفهٔ فوق با استفاده از متد [Validate] آنها آغاز می‌شود، سپس مقدار ویژگی [IsValid] آنها را بررسی می‌کند. این مقدار تنها در صورتی روی [true] تنظیم می‌شود که داده‌های در حال بررسی معتبر تشخیص داده شوند. اگر بررسی‌های اعتبارسنجی برای هر یک از این دو مؤلفه ناموفق باشد، نمای [formulaire] مجدداً نمایش داده می‌شود تا کاربر بتواند خطا(های) خود را اصلاح کند. شرایط فیلتر بر ویژگی [dvProduits.RowFilter] اعمال می‌شود. در اینجا ممکن است یک استثنا رخ دهد اگر کاربر معیار فیلتر نادرستی وارد کرده باشد، همانطور که در زیر نشان داده شده است:

Image

در این حالت، نما [formulaire] دوباره نمایش داده می‌شود. اگر هر دو اطلاعات وارد شده صحیح باشند، دو مقدار داده در جلسه ذخیره می‌شوند:

  • تعداد محصولات در هر صفحه که توسط کاربر انتخاب شده است
  • شماره صفحه‌ای که باید نمایش داده شود – در ابتدا صفحه 0

این دو مورد اطلاعات هر بار که نما [produits] نمایش داده می‌شود، استفاده می‌شوند.

9.6.11. نمایش نما [produits]

نما [produits] به شرح زیر است:

Image

ما ۵ کامپوننت داریم، [DataGrid]، که هر کدام نمای مشخصی از جدول محصول [dtProduits] را نمایش می‌دهند. از سمت چپ شروع می‌کنیم:

نام
نقش
DataGrid1
جدول نمایش‌دهنده نمای فیلترشده از جدول محصولات. فیلتر همان فیلتری است که توسط نمای [formulaire] تنظیم شده است. همچنین داریم .AllowPaging=true, .AllowSorting=true
DataGrid2
کل جدول محصولات را نمایش می‌دهد – امکان پیگیری به‌روزرسانی‌ها را برای شما فراهم می‌کند
DataGrid3
محصولات حذف‌شده را در جدول محصولات نمایش می‌دهد
DataGrid4
محصولاتی را که در جدول محصولات اصلاح شده‌اند نمایش می‌دهد
DataGrid5
محصولات اضافه شده به جدول محصولات را نمایش خواهد داد

روال [afficheProduits] مسئول نمایش نمای قبلی است:


    Private Sub afficheProduits(ByVal page As Integer, ByVal taillePage As Integer)
        ' داده‌ها را به هر دو مؤلفه متصل کنید [DataGrid]
        Application.Lock()
        With DataGrid1
            .DataSource = dvProduits
            .PageSize = taillePage
            .CurrentPageIndex = page
            .DataBind()
        End With
        Dim dvCurrent As New DataView(dtProduits)
        dvCurrent.RowStateFilter = DataViewRowState.CurrentRows
        With DataGrid2
            .DataSource = dvCurrent
            .DataBind()
        End With
        Dim dvSupp As DataView = New DataView(dtProduits)
        dvSupp.RowStateFilter = DataViewRowState.Deleted
        With DataGrid3
            .DataSource = dvSupp
            .DataBind()
        End With
        Dim dvModif As DataView = New DataView(dtProduits)
        dvModif.RowStateFilter = DataViewRowState.ModifiedOriginal
        With DataGrid4
            .DataSource = dvModif
            .DataBind()
        End With
        Dim dvAjout As DataView = New DataView(dtProduits)
        dvAjout.RowStateFilter = DataViewRowState.Added
        With DataGrid5
            .DataSource = dvAjout
            .DataBind()
        End With
        Application.UnLock()
        'نما نمایش داده می‌شود [produits]
        vueProduits.Visible = True
        vueFormulaire.Visible = False
        vueErreurs.Visible = False
        vueAjout.Visible = False
        'ذخیرهٔ صفحهٔ جاری
        Session("pageCourante") = page
    End Sub

این رویه دو پارامتر را می‌پذیرد:

  • شماره صفحه‌ای که باید در [DataGrid1] نمایش داده شود [page]
  • تعداد محصولات [taillePage] در هر صفحه

ارتباط بین [DataGrid] و جدول محصولات از طریق پنج نمای مختلف برقرار می‌شود.

  • [DataGrid1] به نمای صفحه‌بندی‌شده و مرتب‌شده [dvProduits] متصل است.

        With DataGrid1
            .DataSource = dvProduits
            .PageSize = taillePage
            .CurrentPageIndex = page
            .DataBind()
        End With

این زمانی است که هنگام لینک کردن [DataGrid1] به منبع داده‌های آن، دو مورد اطلاعات که به‌عنوان پارامتر به رویه ارسال می‌شوند، مورد نیاز هستند.

  • [DataGrid2] به یک نما متصل است که تمام رکوردهای فعلی جدول [dtProduits] را نمایش می‌دهد. مانند [DataGrid1]، این نما دید به‌روز از جدول [dtProduits] را ارائه می‌کند، اما بدون صفحه‌بندی یا مرتب‌سازی.

        Dim dvCurrent As New DataView(dtProduits)
        dvCurrent.RowStateFilter = DataViewRowState.CurrentRows
        With DataGrid2
            .DataSource = dvCurrent
            .DataBind()
        End With

در اینجا از ویژگی [RowStateFilter] از کلاس [DataView] استفاده می‌کنیم. یک سطر در یک جدول – یا در این مورد، یک نما – دارای ویژگی [RowState] است که وضعیت سطر را نشان می‌دهد. در اینجا چند مثال آورده شده است:

  • (ادامه)
    • [Added]: سطر اضافه شده است
    • [Modified]: سطر اصلاح شده است
    • [Deleted]: سطر حذف شده است
    • [Unchanged]: سطر تغییر نکرده است

وقتی جدول [dtProduits] در [Application_Start] ایجاد شد، همه سطرهای آن در وضعیت [Unchanged] بودند. این وضعیت با به‌روزرسانی داده‌ها توسط کلاینت‌های وب تغییر خواهد کرد:

  • (ادامه)
    • وقتی یک کلاینت یک محصول جدید ایجاد می‌کند، یک سطر به جدول [dtProduits] اضافه می‌شود. این سطر در وضعیت [Added] قرار خواهد داشت.
    • هنگامی که یک محصول اصلاح می‌شود، وضعیت آن از [Unchanged] به [Modified] تغییر می‌کند.
    • هنگامی که یک محصول حذف می‌شود، سطر به صورت فیزیکی حذف نمی‌گردد. در عوض، برای حذف علامت‌گذاری شده و وضعیت آن به [Deleted] تغییر می‌یابد. امکان لغو این حذف وجود دارد.

ویژگی [RowStateFilter] از کلاس [DataView] به یک نما اجازه می‌دهد تا بر اساس وضعیت [RowState] ردیف‌هایش فیلتر شود. مقادیر ممکن آن عبارتند از مقادیر شمارش [DataRowViewState]:

  • (ادامه)
    • DataRowViewState.CurrentRows: سطرهایی که حذف نشده‌اند با مقادیر فعلی خود نمایش داده می‌شوند
    • DataRowViewState.Added: سطرهای اضافه شده با مقادیر فعلی خود نمایش داده می‌شوند
    • DataRowViewState.Deleted: سطرهای حذف‌شده با مقادیر اصلی خود نمایش داده می‌شوند
    • DataRowViewState.ModifiedOriginal: سطرهای اصلاح‌شده با مقادیر اصلی خود نمایش داده می‌شوند
    • DataRowViewState.ModifiedCurrent: سطرهای اصلاح‌شده با مقادیر فعلی خود نمایش داده می‌شوند

یک سطر اصلاح‌شده دو مقدار دارد: مقدار اصلی، که مقداری است که سطر قبل از اولین اصلاح داشت، و مقدار فعلی، که مقداری است که پس از یک یا چند اصلاح به دست آمده است. هر دو مقدار به طور همزمان حفظ می‌شوند.

DataGrid 2 تا 5 نمایی از جدول [dtProduits] را که توسط ویژگی [RowStateFilter] فیلتر شده است، نمایش می‌دهند

DataGrid
فیلتر
DataGrid2
RowStateFilter= DataRowViewState.CurrentRows
DataGrid3
RowStateFilter= DataRowViewState.Deleted
DataGrid4
RowStateFilter = DataRowViewState.ModifiedOriginal
DataGrid5
RowStateFilter= DataRowViewState.Added

جدول [dtPoduits] به‌طور همزمان توسط کلاینت‌های وب مختلف به‌روزرسانی می‌شود که می‌تواند به تضادهای دسترسی روی جدول منجر شود. وقتی یک کلاینت نماهای جدول [dtProduits] را نمایش می‌دهد، می‌خواهیم از وقوع این وضعیت جلوگیری کنیم در حالی که جدول در حالت ناپایدار قرار دارد زیرا توسط کلاینت وب دیگری در حال تغییر است. بنابراین، هرگاه یک کلاینت نیاز به دسترسی به جدول [dtProduits] برای عملیات خواندن طبق توضیحات فوق، یا برای عملیات نوشتن هنگام افزودن، اصلاح یا حذف محصولات داشته باشد، با استفاده از توالی زیر با سایر کلاینت‌ها همگام‌سازی خواهد کرد:

Application.Lock
... section critique 1
Application.Unlock

ممکن است چندین بخش بحرانی در کد برنامه وجود داشته باشد:

Application.Lock
... section critique 2
Application.Unlock

مکانیزم به شرح زیر است:

  1. یک یا چند کلاینت به دستور [Application.Lock] در بخش بحرانی ۱ می‌رسند. این یک دستگاه توزیع‌کننده توکن با ورود یک‌تایی است. یک کلاینت این توکن را دریافت می‌کند. بیایید آن را C1 بنامیم.
  2. تا زمانی که مشتری C1 توکن را از طریق [Application.Unlock] بازنگردانده است، هیچ مشتری دیگری مجاز به ورود به بخش بحرانی تحت کنترل [Application.Lock] نیست. بنابراین در مثال بالا، هیچ کلاینتی نمی‌تواند وارد بخش‌های بحرانی ۱ و ۲ شود.
  3. کلاینت C1، [Application.Unlock] را اجرا می‌کند و بدین ترتیب توکن ورود را بازمی‌گرداند. این توکن سپس می‌تواند به کلاینت دیگری اختصاص یابد. مراحل ۱ تا ۳ تکرار می‌شوند.

با این مکانیزم، ما تضمین می‌کنیم که تنها یک کلاینت به جدول [dtProduits]، چه برای خواندن و چه برای نوشتن، دسترسی داشته باشد.

لینک [Mise à jour] همچنین نما [Produits] را نمایش می‌دهد:

Image

روال مربوطه به شرح زیر است:


    Private Sub lnkMisajour_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkMisajour.Click
        'نمایش نمای محصولات
        afficheProduits(CType(Session("pageCourante"), Integer), CType(Session("nbProduitsPage"), Integer))
    End Sub

ویوی [produits] باید نمایش داده شود. این کار با استفاده از رویه [afficheProduits] انجام می‌شود که دو پارامتر می‌گیرد: شماره صفحه فعلی برای نمایش و تعداد محصولات در هر صفحه برای نمایش. هر دو اطلاعات در جلسه ذخیره می‌شوند، احتمالاً با مقادیر پیش‌فرض خود اگر کاربر هرگز آن‌ها را خودش تنظیم نکرده باشد.

9.6.12. صفحه‌بندی و مرتب‌سازی در [DataGrid1]

ما پیش از این در یک مثال دیگر با رویه‌هایی که صفحه‌بندی و مرتب‌سازی را برای [DataGrid] انجام می‌دهند، آشنا شده‌ایم. هنگام تغییر صفحات، نما [produits] با ارسال شماره صفحه جدید به عنوان پارامتر به رویه [afficheProduits] نمایش داده می‌شود.


    Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
        ' تغییر صفحه
        afficheProduits(e.NewPageIndex, DataGrid1.PageSize)
    End Sub

روند [DataGrid1_SortCommand] زمانی اجرا می‌شود که کاربر روی سربرگ یکی از ستون‌های [DataGrid1] کلیک کند. سپس عبارت مرتب‌سازی باید به ویژگی [Sort]ِ نما [dvProduits] که توسط [DataGrid1] نمایش داده می‌شود، اختصاص داده شود. این از نظر نحوی معادل عبارت مرتب‌سازی پس از عبارت [order by] در دستور SQL SELECT است. آرگومان [e] این رویه دارای یک ویژگی [SortExpression] است که عبارت مرتب‌سازی مرتبط با ستونی را که سربرگ آن کلیک شده است، فراهم می‌کند. هنگامی که مؤلفه [DataGrid1] ایجاد شد، این عبارت مرتب‌سازی تعریف شده بود. در زیر، عبارت مرتب‌سازی تعریف‌شده برای ستون [nom] از [DataGrid1] آمده است:

Image

اگر عبارت مرتب‌سازی هنگام طراحی [DataGrid] تعریف نشده باشد، از نام فیلد داده مرتبط با ستون در [DataGrid] استفاده می‌شود. ترتیب مرتب‌سازی (صعودی، نزولی) توسط کاربر با استفاده از دکمه‌های رادیویی [rdCroissant, rd Décroissant] در اینجا تنظیم می‌شود:

Image

روند مرتب‌سازی به شرح زیر است:


    Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
        ' نمایش داده‌ها را مرتب می‌کند
        With dvProduits
            .Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
        End With
        ' نمایش آن
        afficheProduits(0, DataGrid1.PageSize)
    End Sub

9.6.13. حذف یک محصول

برای حذف یک محصول، کاربر روی لینک [Supprimer] در ردیف محصول کلیک می‌کند:

Image

سپس رویه [DataGrid1_DeleteCommand] اجرا می‌شود:


    Private Sub DataGrid1_DeleteCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.DeleteCommand
        ' حذف یک محصول
        'شناسه محصولی که باید حذف شود
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
        ' حذف محصول
        Dim erreur As Boolean = False
        Try
            supprimerProduit(idProduit)
        Catch ex As Exception
            ' مشکل
            lblInfo2.Text = ex.Message
            erreur = True
        End Try
        ' صفحه را تغییر دهید؟
        Dim page As Integer = DataGrid1.CurrentPageIndex
        If Not erreur AndAlso DataGrid1.Items.Count = 1 Then
            page = DataGrid1.CurrentPageIndex - 1
            If page < 0 Then page = 0
        End If
        'نمایش محصول
        afficheProduits(page, DataGrid1.PageSize)
    End Sub

محصولات با استفاده از کلید محصول [id] به‌روزرسانی خواهند شد. این به این دلیل است که ستون [id] در جدول [dtProduits] کلید اصلی است و با استفاده از آن، می‌توانیم در جدول [dtProduits] ردیف محصولی را که در کامپوننت [DataGrid1] در حال به‌روزرسانی است، پیدا کنیم. بنابراین، این رویه با بازیابی کلید محصولی که لینک آن ([Supprimer]) کلیک شده است، آغاز می‌شود:


         'کلید محصول برای حذف
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)

ما می‌دانیم که [e.Item] عنصری در [DataGrid1] است که رویداد را فعال کرده است. به‌طور کلی، این عنصر یک سطر در [DataGrid] است. [e.Item.ItemIndex] شماره سطری است که رویداد را فعال کرده است. این شاخص به صفحهٔ جاری نمایش‌داده‌شده مربوط می‌شود. بنابراین، اولین خط صفحه نمایش‌داده‌شده دارای ویژگی [ItemIndex=0] است، هرچند شماره ۱۷ در جدول محصول است. [DataGrid1.DataKeys] فهرست کلیدهای مربوط به [DataGrid] است. از آنجایی که ما [DataGrid1.DataKey=id] را در حین طراحی مشخص کردیم، کلیدهای [DataGrid1] شامل مقادیر ستون [id] در جدول [dtProduits] هستند، که این ستون همچنین کلید اصلی است. بنابراین، [DataGrid1.DataKeys(e.Item.ItemIndex)] کلید محصولی است که باید حذف شود، [id]. پس از به‌دست آوردن آن، با استفاده از رویه [supprimerProduit] درخواست حذف آن را می‌دهیم:


         'حذف محصول
        Dim erreur As Boolean = False
        Try
            supprimerProduit(idProduit)
        Catch ex As Exception
            ' خطا
            lblInfo2.Text = ex.Message
            erreur = True
        End Try

این حذف ممکن است در برخی موارد با شکست مواجه شود. خواهیم دید چرا. سپس یک استثنا پرتاب می‌شود. این مورد در اینجا مدیریت می‌شود. اگر خطایی رخ دهد، نیازی به تغییر [DataGrid1] نیست. فقط یک پیام خطا به نمای [produits] اضافه می‌شود. اگر خطایی رخ ندهد و کاربر به‌تازگی تنها محصول را از صفحهٔ جاری حذف کرده باشد، صفحهٔ قبلی نمایش داده می‌شود.


         ' صفحه را تغییر دهید؟
        Dim page As Integer = DataGrid1.CurrentPageIndex
        If Not erreur AndAlso DataGrid1.Items.Count = 1 Then
            page = DataGrid1.CurrentPageIndex - 1
            If page < 0 Then page = 0
        End If

در همه موارد، نما [produits] با استفاده از رویه [afficheProduits] مجدداً نمایش داده می‌شود:

         ' نمایش محصول
        afficheProduits(page, DataGrid1.PageSize)

رویه [supprimerProduit] به شرح زیر است:


    Private Sub supprimerProduit(ByVal idProduit As Integer)
        Dim erreur As String
        Try
            'همگام‌سازی
            Application.Lock()
            ' جستجو برای خطی که باید حذف شود
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' حذف سطر
                ligne.Delete()
            End If
        Catch ex As Exception
            erreur = String.Format("Erreur de suppression : {0}", ex.Message)
        Finally
            ' پایان همگام‌سازی
            Application.UnLock()
        End Try
        ' در صورت رخ دادن خطا، استثنا پرتاب کنید
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

این رویه کلید محصولی را که باید حذف شود به‌عنوان پارامتر می‌پذیرد. اگر تنها یک مشتری به‌روزرسانی‌ها را انجام می‌داد، این حذف می‌توانست به‌صورت زیر انجام شود:

             ' حذف خط [idProduit]
            dtProduits.Rows.Find(idProduit).Delete()

با این حال، هنگامی که چندین کلاینت همزمان در حال انجام به‌روزرسانی‌ها هستند، وضعیت پیچیده‌تر می‌شود. توالی زیر رویدادها را در نظر بگیرید:

زمان
اقدام
T1
کلاینت A جدول [dtProduits] را می‌خواند – یک محصول با کلید 20 وجود دارد
T2
مشتری B جدول [dtProduits] را می‌خواند – یک محصول با کلید 20 وجود دارد
T3
کاربر A محصول با کلید 20 را حذف می‌کند
T4
کلاینت B محصول با کلید 20 را حذف می‌کند

وقتی مشتریان A و B جدول محصولات را می‌خوانند و محتویات آن را در یک صفحه وب نمایش می‌دهند، جدول شامل محصول با کلید 20 است. بنابراین ممکن است بخواهند عملی روی این محصول انجام دهند، مانند حذف آن. یکی از آن‌ها حتماً زودتر این کار را انجام خواهد داد. فردی که بعداً به آن دسترسی پیدا می‌کند، سپس تلاش خواهد کرد محصولی را که دیگر وجود ندارد، حذف کند. این سناریو توسط رویه [supprimerProduits] به چند روش مدیریت می‌شود:

  • اولاً، همگام‌سازی با [Application.Lock] وجود دارد. این بدان معناست که وقتی یک کلاینت از این مانع عبور می‌کند، هیچ کلاینت دیگری نمی‌تواند جدول محصول را تغییر دهد یا بخواند. در واقع، تمام عملیات از این دست به این روش همگام‌سازی می‌شوند. ما قبلاً این را برای عملیات خواندن دیده‌ایم.
  • سپس بررسی می‌کنیم که آیا محصولی که می‌خواهیم حذف کنیم وجود دارد یا خیر. در صورت وجود، حذف می‌شود؛ در غیر این صورت، یک پیام خطا تولید می‌شود.

             ' خطی را که باید حذف شود جستجو کنید
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                 ' حذف سطر
                ligne.Delete()
            End If
  • ترتیب بحرانی از طریق [Application.Unlock] خارج می‌شود تا به سایر کلاینت‌ها اجازه دهد به‌روزرسانی‌های خود را انجام دهند.
  • اگر خط قابل حذف نبود، رویه یک استثنا به همراه یک پیام خطا پرتاب می‌کند.

بیایید یک مثال را بررسی کنیم. ما یک کلاینت [Mozilla] را راه‌اندازی می‌کنیم و بلافاصله گزینه [Mise à jour] (نمایش جزئی) را انتخاب می‌کنیم:

Image

ما همین کار را با کلاینت [Internet Explorer] (نمایش جزئی) انجام می‌دهیم:

Image

با مشتری [Mozilla]، محصول [produit2] را حذف می‌کنیم. صفحهٔ جدید زیر به ما نمایش داده می‌شود:

Image

عملیات حذف با موفقیت انجام شد. محصول حذف‌شده به درستی در [DataGrid] (محصولات حذف‌شده) نمایش داده می‌شود و دیگر در لیست محصولات برای اجزای [DataGrid1] و [DataGrid2]، که محصولات موجود در جدول را نمایش می‌دهند، دیده نمی‌شود. بیایید همین کار را با [Interbet Explorer] انجام دهیم. عنصر [produit2] را حذف خواهیم کرد:

Image

خروجی حاصل به شرح زیر است:

Image

یک پیام خطا به کاربر اطلاع می‌دهد که محصول با کلید [2] وجود ندارد. پاسخ یک نمای جدید را به کلاینت بازمی‌گرداند که وضعیت فعلی جدول را منعکس می‌کند. می‌توانیم ببینیم که محصول با کلید [2] واقعاً در لیست محصولات حذف‌شده قرار دارد. بنابراین نما [produits] به‌روزرسانی‌های انجام‌شده توسط همه کلاینت‌ها را منعکس می‌کند، نه فقط یک کلاینت.

9.6.14. افزودن محصول

برای افزودن یک محصول، کاربر روی لینک [Ajout] در گزینه‌ها کلیک می‌کند. این کار صرفاً نمای [Ajout] را نمایش می‌دهد:

Image

Image

دو رویه‌ای که در این اقدام دخیل هستند به شرح زیر است:


    Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
        'نما نمایش داده می‌شود [ajout]
        afficheAjout()
    End Sub

    Private Sub afficheAjout()
        'نمای «افزودن» را نمایش می‌دهد
        vueAjout.Visible = True
        vueFormulaire.Visible = False
        vueProduits.Visible = False
        vueErreurs.Visible = False
    End Sub

توجه داشته باشید که نما [Ajout] دارای اجزای اعتبارسنجی است:

name
نوع
نقش
rfvNom
RequiredFieldValidator
بررسی می‌کند که آیا یک مقدار در [txtNom] موجود است
rfvPrix
RequiredFieldValidator
بررسی می‌کند که آیا مقداری در [txtPrix] موجود است
cvPrix
CompareValidator
بررسی می‌کند که price >= 0

روند رسیدگی به کلیک روی دکمه [Ajouter] به شرح زیر است:


    Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
        ' افزودن یک آیتم جدید به جدول محصولات
        'داده‌ها باید ابتدا معتبر باشند
        rfvNom.Validate()
        rfvPrix.Validate()
        cvPrix.Validate()
        If Not rfvNom.IsValid Or Not rfvPrix.IsValid Or Not cvPrix.IsValid Then
            ' فرم ورود داده دوباره
            afficheAjout()
            Exit Sub
        End If
        ' ایجاد یک سطر
        Dim produit As DataRow = dtProduits.NewRow
        produit("nom") = txtNom.Text.Trim
        produit("prix") = txtPrix.Text.Trim
        ' اضافه کردن سطر به جدول
        Application.Lock()
        Try
            dtProduits.Rows.Add(produit)
            lblInfo3.Text = "Ajout réussi"
            'پاکسازی
            txtNom.Text = ""
            txtPrix.Text = ""
        Catch ex As Exception
            lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
        End Try
        Application.UnLock()
    End Sub

ابتدا، بررسی‌های اعتبارسنجی برای سه مؤلفه [rfvNom, rfvPrix, cvPrix] انجام می‌شوند. اگر هر یک از بررسی‌ها ناموفق باشد، نما [Ajout] همراه با پیام‌های خطا برای مؤلفه‌های اعتبارسنجی دوباره نمایش داده می‌شود. در اینجا یک مثال آورده شده است:

Image

اگر داده‌ها معتبر باشند، سطر مورد نظر برای درج در جدول [dtProduits] آماده می‌شود.


         'یک سطر ایجاد کنید
        Dim produit As DataRow = dtProduits.NewRow
        produit("nom") = txtNom.Text.Trim
        produit("prix") = txtPrix.Text.Trim

ابتدا یک ردیف جدید در جدول [dtProduits] با استفاده از متد [DataTable.NewRow] ایجاد می‌شود. این ردیف شامل سه ستون [id, nom, prix] از جدول [dtProduits] خواهد بود. ستون‌های [nom, prix] با مقادیر واردشده در فرم افزودن پر می‌شوند. ستون [id] پر نمی‌شود. این ستون از نوع [AutoIncrementc.a.d است. SGBD به عنوان کلید محصول جدید، برابر با حداکثر کلید موجود به اضافه ۱ خواهد بود. ردیف [produit] که در اینجا ایجاد شده، از جدول [dtProduits] جدا شده است. اکنون باید آن را در این جدول درج کنیم. از آنجا که قصد داریم جدول [dtProduits] را به‌روزرسانی کنیم، همگام‌سازی بین کلاینت‌ها را فعال می‌کنیم:

        Application.Lock()

پس از انجام این کار، ما سعی می‌کنیم این سطر را به جدول [dtProduits] اضافه کنیم. در صورت موفقیت، یک پیام موفقیت را نمایش می‌دهیم؛ در غیر این صورت، یک پیام خطا.

        Try
            dtProduits.Rows.Add(produit)
            lblInfo3.Text = "Ajout réussi"
             'پاکسازی
            txtNom.Text = ""
            txtPrix.Text = ""
        Catch ex As Exception
            lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
        End Try

به طور معمول، هیچ استثنایی ممکن نیست. با این حال، یک مکانیزم رسیدگی به استثنا به عنوان اقدامی احتیاطی ایجاد شده است. پس از انجام افزودن، بخش بحرانی از طریق [Application.Unlock] ترک می‌شود.

9.6.15. ویرایش یک محصول

برای ویرایش یک محصول، کاربر روی لینک [Modifier] در ردیف محصول کلیک می‌کند:

Image

این شما را به حالت ویرایش می‌برد:

Image

به لطف کامپوننت [DataGrid]، این نتیجه با کدنویسی بسیار اندک حاصل می‌شود:


    Private Sub DataGrid1_EditCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.EditCommand
        ' مورد فعلی را در حالت ویرایش قرار دهید
        DataGrid1.EditItemIndex = e.Item.ItemIndex
        ' نمایش مجدد محصولات
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
    End Sub

کلیک بر روی لینک [Modifier] باعث اجرای سمت سرور رویه [DataGrid1_EditCommand] می‌شود. کامپوننت [DataGrid1] دارای فیلدی به نام [EditItemIndex] است. رده‌ای که نمایه‌اش با مقدار [EditItemIndex] مطابقت دارد، به حالت ویرایش تنظیم می‌شود. هر مقدار در این ردیف که به این صورت به‌روزرسانی شده باشد، می‌تواند در یک کادر ورودی مانند آنچه در تصویر بالا نشان داده شده است، تغییر یابد. بنابراین، ما نمای [produits] را دوباره نمایش می‌دهیم. این نما دقیقاً مانند قبل به نظر می‌رسد، اما با یک خط در حالت ویرایش. تنها کاری که باقی مانده، تازه کردن نمای [produits] است. این نما دقیقاً مانند قبل به نظر می‌رسد، اما با یک سطر در حالت ویرایش.

لینک [Annuler] برای محصولی که در حال ویرایش است، به کاربر اجازه می‌دهد به‌روزرسانی را لغو کند. رویه‌ای که با این لینک مرتبط است به شرح زیر است:


    Private Sub DataGrid1_CancelCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.CancelCommand
        ' نمایش مجدد محصولات
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
    End Sub

این به سادگی نما [produits] را مجدداً نمایش می‌دهد. ممکن است وسوسه شوید که مقدار -1 را در [DataGrid1.EditItemIndex] وارد کنید تا حالت به‌روزرسانی را لغو کنید. در واقع، می‌دانیم که رویه [Page_Load] این کار را به‌طور خودکار انجام می‌دهد. بنابراین نیازی به انجام مجدد آن نیست.

این تغییر از طریق لینک [Mettre à jour] در سطری که در حال ویرایش است تأیید می‌شود. سپس رویه زیر اجرا می‌شود:


    Private Sub DataGrid1_UpdateCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.UpdateCommand
        ' کلید محصولی که قرار است اصلاح شود
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
        ' موارد اصلاح‌شده
        Dim nom As String = CType(e.Item.Cells(0).Controls(0), TextBox).Text.Trim
        Dim prix As String = CType(e.Item.Cells(1).Controls(0), TextBox).Text.Trim
        'آیا تغییرات معتبر هستند؟
        lblInfo2.Text = ""
        If nom = String.Empty Then lblInfo2.Text += "[Indiquez un nom]"
        If prix = String.Empty Then
            lblInfo2.Text += "[Indiquez un prix]"
        Else
            Dim nouveauPrix As Double
            Try
                nouveauPrix = CType(prix, Double)
                If nouveauPrix < 0 Then Throw New Exception
            Catch ex As Exception
                lblInfo2.Text += "[prix invalide]"
            End Try
        End If
        '  اگر خطایی رخ دهد، صفحهٔ به‌روزرسانی دوباره نمایش داده می‌شود
        If lblInfo2.Text <> String.Empty Then
            ' ردیف را به حالت به‌روزرسانی بازمی‌گرداند
            DataGrid1.EditItemIndex = e.Item.ItemIndex
            'نمایش محصول
            afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
            ' پایان
            Exit Sub
        End If
        ' اگر خطایی رخ ندهد – جدول را به‌روزرسانی کن
        Try
            modifierProduit(idProduit, e.Item)
        Catch ex As Exception
            ' مشکل
            lblInfo2.Text = ex.Message
        End Try
        ' نمایش محصولات
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
    End Sub

مانند حذف، برای یافتن ردیف آن در جدول محصولات [dtProduits]، باید کلید محصولی را که قرار است اصلاح شود، بازیابی کنیم:


         'کلید محصولی که قرار است اصلاح شود
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)

سپس باید مقادیر جدید را که قرار است به خط اختصاص داده شوند، بازیابی کنیم. این مقادیر در کامپوننت [DataGrid1] قرار دارند. آرگومان رویه [e] برای کمک به ما در آنجا قرار دارد. [e.Item] نمایانگر ردیفی از [DataGrid1] است که رویداد را تحریک کرده است. بنابراین این همان ردیفی است که در حال حاضر در حال به‌روزرسانی است، زیرا لینک [Mettre à jour] تنها در این ردیف وجود دارد. این سطر شامل ستون‌هایی است که توسط مجموعه سطر [Cells] مشخص شده‌اند. بنابراین، [e.Item.Cells(0)] نمایانگر ستون 0 از سطری است که در حال به‌روزرسانی است. ما می‌دانیم که مقادیر جدید در فیلدهای ورودی قرار دارند. مجموعه [e.Item.Cells(i).Controls] نمایانگر مجموعه کنترل‌ها برای ستون i از ردیف [e.Item] است. دو عبارت زیر مقادیر را از فیلدهای ورودی ردیف به‌روز شده در [DataGrid1] بازیابی می‌کنند:


         ' موارد اصلاح‌شده
        Dim nom As String = CType(e.Item.Cells(0).Controls(0), TextBox).Text.Trim
        Dim prix As String = CType(e.Item.Cells(1).Controls(0), TextBox).Text.Trim

بنابراین ما مقادیر جدید برای سطر اصلاح‌شده را به صورت رشته‌های کاراکتری در اختیار داریم. سپس بررسی می‌کنیم که آیا این داده‌ها معتبر هستند یا خیر. نام نباید خالی باشد و قیمت باید یک عدد مثبت یا صفر باشد:


         'آیا تغییرات معتبر هستند؟
        lblInfo2.Text = ""
        If nom = String.Empty Then lblInfo2.Text += "[Indiquez un nom]"
        If prix = String.Empty Then
            lblInfo2.Text += "[Indiquez un prix]"
        Else
            Dim nouveauPrix As Double
            Try
                nouveauPrix = CType(prix, Double)
                If nouveauPrix < 0 Then Throw New Exception
            Catch ex As Exception
                lblInfo2.Text += "[prix invalide]"
            End Try
        End If

در صورت بروز خطا، تگ [lblInfo2] یک پیام خطا نمایش داده و به سادگی همان صفحه را مجدداً بارگذاری می‌کند:


          ' اگر خطایی رخ دهد، صفحهٔ به‌روزرسانی مجدداً نمایش داده می‌شود
        If lblInfo2.Text <> String.Empty Then
            ' ردیف را به حالت به‌روزرسانی بازمی‌گرداند
            DataGrid1.EditItemIndex = e.Item.ItemIndex
            ' نمایش محصول
            afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
             'پایان
            Exit Sub
        End If

آنچه کد بالا نشان نمی‌دهد این است که مقادیر وارد شده از بین می‌روند. این به این دلیل است که [DataGrid1] به داده‌های جدول [dtProduits] متصل است که شامل مقادیر اصلی برای سطر اصلاح‌شده می‌باشد. در اینجا یک مثال آمده است.

Image

پاسخ به‌دست‌آمده به شرح زیر است:

Image

می‌توانیم ببینیم که مقادیر وارد شده در ابتدا از بین رفته‌اند. در یک برنامه کاربردی تجاری، این امر احتمالاً قابل قبول نخواهد بود. این موضوع محدودیت‌های خاصی را در حالت به‌روزرسانی استاندارد برای مؤلفه [DataGrid] برجسته می‌کند. ترجیح داده می‌شود یک نمای [Modification] مشابه نمای [Ajout] وجود داشته باشد.

اگر داده‌ها معتبر باشند، برای به‌روزرسانی جدول [dtProduits] استفاده می‌شوند:


         ' اگر خطایی رخ ندهد – جدول را به‌روزرسانی کن
        Try
            modifierProduit(idProduit, e.Item)
        Catch ex As Exception
            ' مشکل
            lblInfo2.Text = ex.Message
        End Try
        ' نمایش محصول
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)

به همان دلیلی که هنگام حذف یک محصول ذکر شد، ممکن است به‌روزرسانی با شکست مواجه شود. این به آن دلیل است که بین زمانی که مشتری جدول [dtProduits] را خوانده و زمانی که تلاش می‌کند یکی از محصولات خود را به‌روزرسانی کند، ممکن است آن محصول توسط مشتری دیگری حذف شده باشد. روال [modifierProduit] این سناریو را با ایجاد یک استثنا مدیریت می‌کند. این استثنا در اینجا رسیدگی می‌شود. پس از اینکه به‌روزرسانی با موفقیت یا عدم موفقیت انجام شد، برنامه نما [produits] را به کلاینت بازمی‌گرداند. اکنون باید ببینیم که رویه [modifierProduit] چگونه به‌روزرسانی را انجام می‌دهد:


    Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
        Dim erreur As String
        Try
            ' همگام‌سازی
            Application.Lock()
            ' جستجو برای سطری که قرار است اصلاح شود
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' ویرایش سطر
                With ligne
                    .Item("nom") = CType(item.Cells(0).Controls(0), TextBox).Text.Trim
                    .Item("prix") = CType(item.Cells(1).Controls(0), TextBox).Text.Trim
                End With
            End If
        Catch ex As Exception
            erreur = String.Format("Erreur lors de la modification : {0}", ex.Message)
        Finally
            'پایان همگام‌سازی
            Application.UnLock()
        End Try
        ' در صورت رخ دادن خطا، یک استثنا پرتاب می‌کند
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

ما وارد جزئیات این رویه نمی‌شویم، زیرا کد آن مشابه رویه [supprimerProduit] است که قبلاً به تفصیل توضیح داده شده است. بیایید صرفاً به دو مثال نگاه کنیم. اول، قیمت محصول [produit1] را اصلاح خواهیم کرد:

Image

استفاده از لینک [Mettre à jour] بالا پاسخ زیر را تولید می‌کند:

Image

این تغییر در کامپوننت‌های [DataGrid] 1 و 2 که وضعیت فعلی جدول [dtProduits] را منعکس می‌کنند، به وضوح قابل مشاهده است. این تغییر همچنین در کامپوننت [DataGrid4] وجود دارد که نمایی از سطرهای اصلاح‌شده است و با مقادیر اصلی آن‌ها نمایش داده می‌شود. اکنون بیایید به موردی از تعارض دسترسی نگاهی بیندازیم. همانند مثال حذف، از دو کلاینت وب مختلف استفاده خواهیم کرد. یک کلاینت [Mozilla] جدول [dtProduits] را می‌خواند و برای محصول [produit1] وارد حالت ویرایش می‌شود:

Image

مشتری با شناسه [Internet Explorer] در شرف حذف محصول [produit1] است:

Image

کلاینت [Internet Explorer] در حال حذف [produit1] است:

Image

توجه کنید که [produit1] دیگر در جدول ردیف‌های اصلاح‌شده نیست، بلکه در جدول ردیف‌های حذف‌شده قرار دارد. سپس کلاینت [Mozilla] به‌روزرسانی خود را اعتبارسنجی می‌کند:

Image

کلاینت [Mozilla] پاسخ زیر را برای به‌روزرسانی خود دریافت می‌کند:

Image

او می‌تواند ببیند که [produit1] حذف شده است، زیرا در لیست محصولات حذف‌شده ظاهر می‌شود.

9.7. برنامه وب برای به‌روزرسانی جدول محصول فیزیکی

9.7.1. راه‌حل‌های پیشنهادی

برنامه قبلی بیشتر یک مثال کتاب درسی بود که برای نمایش مدیریت یک شی [DataTable] کش‌شده طراحی شده بود تا یک سناریوی دنیای واقعی. در واقع، در مقطعی، منبع داده واقعی باید به‌روزرسانی شود. دو استراتژی متفاوت می‌توان اتخاذ کرد:

  1. از کش درون‌حافظه‌ای [dtProduits] برای به‌روزرسانی منبع داده استفاده کنید. به طور جایگزین، می‌توان یک صفحه در ساختار درختی وب برنامه قبلی ایجاد کرد تا دسترسی به کش [dtProduits] آن فراهم شود. این صفحه به یک مدیر اجازه می‌دهد تا تغییرات اعمال‌شده بر روی کش [dtProduits] را با منبع داده فیزیکی همگام‌سازی کند. برای این کار، می‌توان یک متد جدید به کلاس دسترسی [produits] اضافه کرد که کش [dtProduits] را به عنوان پارامتر دریافت کرده و از این کش برای به‌روزرسانی منبع داده فیزیکی استفاده کند.
  2. منبع داده فیزیکی همزمان با کش به‌روزرسانی می‌شود.

استراتژی شمارهٔ ۱ اجازه می‌دهد تنها یک اتصال به منبع دادهٔ فیزیکی باز شود. استراتژی شمارهٔ ۲ برای هر به‌روزرسانی به یک اتصال نیاز دارد. بسته به در دسترس بودن اتصال، ممکن است یکی از استراتژی‌ها بر دیگری ترجیح داده شود. از آنجایی که ابزارهای لازم برای پیاده‌سازی آن (کلاس [produits]) را در اختیار داریم، استراتژی شماره ۲ را انتخاب کرده‌ایم.

9.7.2. راه حل ۱

به منظور حفظ پیوستگی با برنامه قبلی، استراتژی زیر را انتخاب کرده‌ایم:

  • منبع فیزیکی همزمان با کش به‌روزرسانی می‌شود
  • پیش‌حافظه فقط یک بار، هنگام راه‌اندازی برنامه در [global.asax.vb]، ساخته می‌شود. این بدان معناست که اگر منبع داده فیزیکی توسط مشتریانی غیر از مشتریان وب به‌روزرسانی شود، مشتریان وب این تغییرات را مشاهده نخواهند کرد. آنها فقط تغییراتی را که خودشان در جدول پیش‌حافظه ایجاد می‌کنند، می‌بینند.

برای به‌روزرسانی منبع داده فیزیکی، به یک نمونه از کلاس دسترسی به محصول نیاز داریم. هر کلاینت می‌تواند نمونه خاص خود را داشته باشد. به طور جایگزین، می‌توان یک نمونه واحد را به اشتراک گذاشت که توسط برنامه در زمان راه‌اندازی ایجاد می‌شود. این راه‌حلی است که ما در اینجا انتخاب کرده‌ایم. کد کنترل [global.asax.vb] به شرح زیر اصلاح می‌شود:


Imports System
Imports System.Web
...

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        'بازیابی اطلاعات پیکربندی
        Dim chaînedeConnexion As String = ConfigurationSettings.AppSettings("OLEDBStringConnection")
        Dim defaultProduitsPage As String = ConfigurationSettings.AppSettings("defaultProduitsPage")
        Dim erreurs As New ArrayList
...
        ' در اینجا هیچ خطای پیکربندی وجود ندارد
        ' یک شیء محصول ایجاد می‌شود
        Dim objProduits As New produits(chaînedeConnexion)
        Dim dtProduits As DataTable
        Try
            dtProduits = objProduits.getProduits
        Catch ex As ExceptionProduits
            'خطایی هنگام دسترسی به محصولات رخ داده است؛ این موضوع در برنامه ثبت می‌شود
            Application("erreurs") = ex.erreurs
            Exit Sub
        Catch ex As Exception
            'خطای رسیدگی‌نشده
            erreurs.Add(ex.Message)
            Application("erreurs") = erreurs
            'خروج از زیرروال
        End Try
        ' در اینجا هیچ خطای инициализация وجود ندارد
...
        ' مثال دسترسی به داده‌ها ذخیره شد
        Application("objProduits") = objProduits
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
...
    End Sub
End Class

یک نمونه از کلاس دسترسی به داده‌ها در برنامه ذخیره شده است که با کلید [objProduits] مرتبط است. هر کلاینت از این نمونه برای دسترسی به منبع داده فیزیکی استفاده خواهد کرد. این نمونه در رویه [Page_Load] از [main.aspx.vb] بازیابی خواهد شد:


    Protected objProduits As produits
....
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
        ' بازیابی نمونه دسترسی به محصول
        objProduits = CType(Application("objProduits"), produits)
...
    End Sub

نمونه دسترسی به داده‌ها [objProduits] برای همه متدهای صفحه قابل دسترسی است. این نمونه برای سه عملیات به‌روزرسانی: درج، حذف و به‌روزرسانی استفاده خواهد شد.

روند add به شرح زیر اصلاح شده است:


    Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
...
        ' افزودن سطر
        Application.Lock()
        Try
            ' اضافه کردن سطر به جدول پُرکننده‌ای
            dtProduits.Rows.Add(produit)
             ' ردیف به منبع فیزیکی اضافه می‌شود
            Dim nouveauProduit As sProduit
            With nouveauProduit
                .nom = CType(produit("nom"), String)
                .prix = CType(produit("prix"), Double)
            End With
            objProduits.ajouterProduit(nouveauProduit)
            ' نظارت
            lblInfo3.Text = "Ajout réussi"
            'پاک‌سازی
            txtNom.Text = ""
            txtPrix.Text = ""
        Catch ex As Exception
            'خطا
            lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
        End Try
        Application.UnLock()
    End Sub

رویه اصلاح به شرح زیر اصلاح می‌شود:


    Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
        Dim erreur As String
        Try
            'همگام‌سازی
            Application.Lock()
            ' جستجو برای خطی که قرار است اصلاح شود
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' تغییر خط در کش
                With ligne
                    .Item("nom") = CType(item.Cells(0).Controls(0), TextBox).Text.Trim
                    .Item("prix") = CType(item.Cells(1).Controls(0), TextBox).Text.Trim
                End With
                 ' ردیف در منبع فیزیکی به‌روزرسانی می‌شود
                Dim nouveauProduit As sProduit
                With nouveauProduit
                    .id = idProduit
                    .nom = CType(ligne.Item("nom"), String)
                    .prix = CType(ligne.Item("prix"), Double)
                End With
                objProduits.modifierProduit(nouveauProduit)
            End If
        Catch ex As Exception
            erreur = String.Format("Erreur lors de la modification : {0}", ex.Message)
        Finally
            'پایان همگام‌سازی
            Application.UnLock()
        End Try
        ' در صورت رخ دادن خطا، استثنا پرتاب می‌کند
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

رویه حذف به شرح زیر اصلاح می‌شود:


    Private Sub supprimerProduit(ByVal idProduit As Integer)
        Dim erreur As String
        Try
            'همگام‌سازی
            Application.Lock()
            ' جستجو برای سطر جهت حذف
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' حذف سطر از کش
                ligne.Delete()
                 ' حذف سطر از منبع فیزیکی
                objProduits.supprimerProduit(idProduit)
            End If
        Catch ex As Exception
            erreur = String.Format("Erreur de suppression : {0}", ex.Message)
        Finally
            'پایان همگام‌سازی
            Application.UnLock()
        End Try
        ' در صورت رخ دادن خطا، یک استثنا پرتاب می‌شود
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

9.7.3. آزمایش‌ها

ما با جدول داده زیر در یک فایل ACCESS شروع می‌کنیم:

Image

یک کلاینت وب راه‌اندازی می‌شود:

Image

ما یک محصول اضافه می‌کنیم:

Image

ما [produit1] را حذف می‌کنیم:

Image

ما قیمت [produit2] را تغییر می‌دهیم:

Image

پس از این تغییرات، محتویات جدول [liste] را در پایگاه داده ACCESS بررسی می‌کنیم:

Image

سه تغییر به‌درستی در جدول فیزیکی منعکس شده‌اند. اکنون بیایید یک سناریوی تداخل را بررسی کنیم. ما ردیف مربوط به [produit2] را که درست زیر ACCESS قرار دارد، حذف می‌کنیم:

Image

بیایید به کلاینت وب خود بازگردیم. آنها نمی‌توانند ببینند که حذف انجام شده و می‌خواهند [produit2] را خودشان حذف کنند:

Image

آنها پاسخ زیر را دریافت می‌کنند:

Image

خط [produit2] در واقع از کش حذف شده است، همانطور که در لیست محصولات حذف شده نشان داده شده است. با این حال، حذف [produit2] از منبع فیزیکی با شکست مواجه شده است، همانطور که از پیام خطا مشخص است.

9.7.4. راه حل ۲

در راه‌حل قبلی، کلاینت‌های وب به‌طور همزمان منبع داده فیزیکی را به‌روزرسانی می‌کنند اما تغییرات اعمال‌شده توسط سایر کلاینت‌ها را نمی‌بینند. آنها تنها تغییرات خود را مشاهده می‌کنند. اکنون می‌خواهیم کلاینت بتواند منبع داده فیزیکی را همان‌طور که در حال حاضر است ببیند، نه همان‌طور که هنگام راه‌اندازی برنامه بود. برای دستیابی به این هدف، گزینه‌ای جدید در اختیار کلاینت قرار می‌دهیم:

Image

با گزینه [Rafraîchir]، کلاینت خواندن مجدد منبع داده فیزیکی را اجباری می‌کند. برای اطمینان از اینکه این کار بر سایر کلاینت‌ها تأثیر نگذارد، جدولی که از این خواندن حاصل می‌شود باید متعلق به کلاینتی باشد که عملیات تازه‌سازی را انجام می‌دهد و نباید با سایر کلاینت‌ها به اشتراک گذاشته شود. این اولین تفاوت با برنامه قبلی است. پیش‌حافظه [dtProduits] برای منبع داده توسط هر کلاینت ساخته می‌شود، نه توسط خود برنامه. این تغییر در [global.asax.vb] انجام شده است:


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Data
Imports Microsoft.VisualBasic
Imports System.Collections

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        'بازیابی اطلاعات پیکربندی
...
        ' در اینجا هیچ خطای پیکربندی وجود ندارد
        ' ایجاد یک شیء محصولات
        Dim objProduits As New produits(chaînedeConnexion)
        ' تعداد محصولات در هر صفحه ذخیره می‌شود
        Application("defaultProduitsPage") = defaultProduitsPage
        ' نمونه دسترسی به داده‌ها ذخیره می‌شود
        Application("objProduits") = objProduits
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' ابتدایی‌سازی متغیرهای جلسه
        If IsNothing(Application("erreurs")) Then
            'منبع داده در حافظه پنهان ذخیره می‌شود
            Dim dtProduits As DataTable
            Try
                Application.Lock()
                dtProduits = CType(Application("objProduits"), produits).getProduits
            Catch ex As ExceptionProduits
                'خطایی هنگام دسترسی به محصولات رخ داده است؛ این خطا در جلسه ثبت می‌شود
                Session("erreurs") = ex.erreurs
                Exit Sub
            Finally
                Application.UnLock()
            End Try
            ' کش [dtProduits] در جلسه ذخیره می‌شود
            Session("dtProduits") = dtProduits
            ' نمایش جدول محصولات
            Session("dvProduits") = dtProduits.DefaultView
            'تعداد محصولات در هر صفحه
            Session("nbProduitsPage") = Application("defaultProduitsPage")
            ' صفحهٔ فعلی نمایش داده شده
            Session("pageCourante") = 0
        End If
    End Sub

End Class

داده‌های جلسه با هر درخواست در رویه [Page_Load] بازیابی می‌شوند:


     ' داده‌های صفحه
    Protected dtProduits As DataTable
    Protected dvProduits As DataView
    Protected objProduits As produits
    Protected nbProduitsPage As Integer
    Protected pageCourante As Integer

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'بررسی می‌کند که آیا برنامه با خطایی مواجه شده است
        If Not IsNothing(Application("erreurs")) Then
            ' برنامه به درستی راه‌اندازی نشده است
            afficheErreurs(CType(Application("erreurs"), ArrayList))
            Exit Sub
        End If
        'بررسی اینکه آیا جلسه با خطایی مواجه شده است
        If Not IsNothing(Session("erreurs")) Then
            'جلسه به‌درستی راه‌اندازی نشده است
            afficheErreurs(CType(Session("erreurs"), ArrayList))
            Exit Sub
        End If
        'بازیابی یک مرجع از جدول محصولات
        dtProduits = CType(Session("dtProduits"), DataTable)
        ' بازیابی نمای محصول
        dvProduits = CType(Session("dvProduits"), DataView)
        ' بازیابی تعداد محصولات در هر صفحه
        nbProduitsPage = CType(Session("nbProduitsPage"), Integer)
        ' بازیابی صفحهٔ جاری
        pageCourante = CType(Session("pageCourante"), Integer)
        ' نمونه دسترسی به محصول را بازیابی می‌کند
        objProduits = CType(Application("objProduits"), produits)
        ' هر به‌روزرسانی در حال انجام را لغو می‌کند
        DataGrid1.EditItemIndex = -1
        'اولین درخواست
        If Not IsPostBack Then
            ' نمایش فرم اولیه
            txtPages.Text = nbProduitsPage.ToString
            afficheFormulaire()
        End If
    End Sub

اطلاعات به‌دست‌آمده در طول جلسه پس از هر درخواست در پایان جلسه ذخیره خواهد شد:


    Private Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.PreRender
        ' ذخیره برخی اطلاعات در جلسه
        Session("dtProduits") = dtProduits
        Session("dvProduits") = dvProduits
        Session("nbProduitsPage") = nbProduitsPage
        Session("pageCourante") = pageCourante
    End Sub

رویداد [PreRender] نشان می‌دهد که پاسخ در شرف ارسال به کلاینت است. ما از این فرصت استفاده می‌کنیم تا تمام داده‌هایی را که باید در جلسه حفظ شوند، ذخیره کنیم. این کار افراطی است، زیرا اغلب تنها بخشی از داده‌ها تغییر کرده‌اند. این ذخیرهٔ سیستماتیک مزیت آن را دارد که ما را از بار مدیریت جلسه در سایر متدهای صفحه رها می‌کند.

عملیات تازه‌سازی کش توسط رویه زیر انجام می‌شود:


    Private Sub lnkRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkRefresh.Click
        ' کش [dtProduits] باید با منبع داده فیزیکی همگام‌سازی شود
        'همگام‌سازی آغاز می‌شود
        Application.Lock()
        Try
            ' جدول محصول
            dtProduits = CType(Application("objProduits"), produits).getProduits
            ' فیلتر فعلی ذخیره می‌شود
            Dim filtre As String = dvProduits.RowFilter
            ' ایجاد نمای فیلترشده جدید
            dvProduits = New DataView(dtProduits)
            ' بازیابی فیلتر
            dvProduits.RowFilter = filtre
        Catch ex As ExceptionProduits
            'خطایی هنگام دسترسی به محصولات رخ داده است؛ این خطا در جلسه ثبت می‌شود
            Session("erreurs") = ex.erreurs
            ' نمایش نما [erreurs]
            afficheErreurs(ex.erreurs)
            ' انجام شد
            Exit Sub
        Finally
            'همگام‌سازی کامل شد
            Application.UnLock()
        End Try
        ' موفقیت‌آمیز بود – نمایش محصولات از صفحه اول شروع می‌شود
        afficheProduits(0, nbProduitsPage)
    End Sub

این رویه مقادیر جدیدی را برای کش [dtProduits] و نما [dvProduits] تولید می‌کند. این مقادیر توسط رویه [Page_PreRender] که در بالا توضیح داده شد، در جلسه قرار داده می‌شوند. پس از بازسازی کش [dtProduits]، محصولات از صفحه اول نمایش داده می‌شوند.

در اینجا مثالی از اجرای آن آورده شده است. یک کلاینت [mozilla] راه‌اندازی شده و محصولات را نمایش می‌دهد:

Image

یک کلاینت به نام [Internet Explorer] همین کار را انجام می‌دهد:

Image

کلاینت [Mozilla]، [produit1] را حذف می‌کند، [produit2] را اصلاح می‌کند و یک محصول جدید اضافه می‌کند. این کار صفحه جدید زیر را تولید می‌کند:

Image

کلاینت [Internet Explorer] می‌خواهد [produit1] را حذف کند.

Image

پاسخ زیر را دریافت می‌کند:

Image

به آنها اطلاع داده شد که [produit1] دیگر وجود ندارد. کاربر سپس تصمیم می‌گیرد با استفاده از لینک [Rafraîchir] بالا، کش خود را تازه کند. آنها پاسخ زیر را دریافت می‌کنند:

Image

آنها اکنون همان منبع داده را مانند مشتری [Mozilla] دارند.

9.8. Conclusion

در این فصل، زمان قابل توجهی را به کانتینرهای داده و ارتباط آن‌ها با منابع داده اختصاص دادیم. در پایان، با نمایش نحوه به‌روزرسانی یک منبع داده با استفاده از کامپوننت [DataGrid]، به کار خود پایان دادیم. ما از یک جدول پایگاه داده به‌عنوان منبع داده خود استفاده کردیم. اگرچه کامپوننت [DataGrid] ارائه داده‌ها را کمی آسان‌تر می‌کند، چالش واقعی نه در خود ارائه، بلکه در مدیریت به‌روزرسانی‌های منبع داده است که توسط کلاینت‌های مختلف انجام می‌شود. ممکن است تعارضات دسترسی پیش بیاید و باید مدیریت شوند. در اینجا، ما آن‌ها را در کنترل‌کننده با استفاده از [Application.Lock] مدیریت کرده‌ایم. احتمالاً عاقلانه‌تر است که دسترسی به منبع داده را در کلاس دسترسی به داده همگام‌سازی کنیم تا کنترل‌کننده مجبور نباشد به چنین جزئیاتی که خارج از حوزه وظایف آن است، بپردازد.

در عمل، جداول در یک پایگاه داده از طریق روابط به یکدیگر متصل هستند و به‌روزرسانی‌ها باید این روابط را در نظر بگیرند. اولاً، این موضوع بر کلاس دسترسی به داده تأثیر می‌گذارد، زیرا این کلاس برای یک جدول مستقل، پیچیده‌تر از حد لازم می‌شود. همچنین این موضوع عموماً بر لایه ارائه (presentation layer) وب‌اپلیکیشن نیز تأثیر می‌گذارد، زیرا اغلب لازم است نه فقط یک جدول، بلکه جداول متعددی که از طریق روابط به یکدیگر متصل هستند، نمایش داده شوند.

این فصل همچنین برای معرفی ساختارهای داده مختلف مانند [DataTable, DataView] به کار رفته است.