ASP.NET_Custom Server Control的模組化開發練習


由於日常升級網頁表單會時常一直寫一樣的html code
為了節省時間通常可進行所謂的Custom Server Control的封裝


這樣也會使維護變得複雜,會讓其他程式開發者不曉得哪段是重點(變動頻率較高的程式段落)


本次就會以如下控件模組來設計一個可以重複使用的套版

新增好一個Web專案後
在自行新增加一個WebForm Server Control項目
(我比較習慣稱呼為Custom Server Control 關鍵字比較好搜尋)



Default 會產生的程式碼如下


RenderContents可以串HTML的內容
主要是在此事件中渲染到瀏覽器的

經改寫(大量Google 跟 Try & Error)
程式碼:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Drawing
Imports System.Text
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls

#Region "Reference Link"
' Custom Web Server Controls
' https://dotblogs.com.tw/shadow/archive/2011/11/28/59816.aspx
' https://dotblogs.com.tw/jeff377/2008/10/04/5578
' http://vito-note.blogspot.com/2012/10/custom-web-server-controls.html
' http://www.webapp.com.tw/EBook5/view.aspx?a=1&TreeNodeID=50&id=1151
' https://www.codeproject.com/Articles/37814/How-to-Create-a-Date-Picker-Composite-Control-in-A
' creating-a-composite-server-control
' http://www.joe-stevens.com/2010/04/16/creating-a-composite-server-control-with-asp-net/
' HOW TO: Register an Assembly in a WebForm to Use a Custom Control
' https://support.microsoft.com/en-hk/help/321749/how-to-register-an-assembly-in-a-webform-to-use-a-custom-control
' Delegates (Visual Basic)
' https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/delegates/index
#End Region

'請將下列此行複製貼至aspx檔案頂部
'<%@ Register assembly="Foci.NET.Web.ServerControl" namespace="Foci.NET.Web.ServerControl.CustomWebControl" tagprefix="FociWebControl" %>
<Assembly: TagPrefix("CustomWebControl", "FociWebControl")>
Namespace CustomWebControl
    <DefaultProperty("Text")>
    <ToolboxBitmap("C:\img\FociControl.bmp")>
    <Description("WisdomGrid 控制項")>
    <ToolboxData("<FociWebControl:TimePeriodCusControl runat=""server"" TopicLabel="" ""/>")>
    Public Class TimePeriodCusControl
        Inherits WebControls.WebControl
        Implements IPostBackEventHandler
        Dim sbYearList As New StringBuilder
        Dim sbMonthList As New StringBuilder
        Dim sbDayList As New StringBuilder
#Region "Property"

        <Description("設置標題")>
        Public Property TopicLabel As String = ""

        <Description("獲取或設置下拉單選項_起始年")>
        Public Property DDLBeginYear As String = ""

        <Description("獲取或設置下拉單選項_結束年")>
        Public Property DDLEndYear As String = ""

        <Description("獲取或設置下拉單選項_起始月")>
        Public Property DDLBeginMonth As String = ""


        <Description("獲取或設置下拉單選項_結束月")>
        Public Property DDLEndMonth As String = ""

        <Description("獲取或設置下拉單選項_起始日")>
        Public Property DDLBeginDay As String = ""

        <Description("獲取或設置下拉單選項_結束日")>
        Public Property DDLEndDay As String = ""
#End Region

        '外部註冊事件用的程式區塊,不要更動
        '------------------------------------------------------------------------------------------------------------------------------------------------------------
        <Category("Behavior")>
        Public Event Click As EventHandler
        Public Sub RaisePostBackEvent(eventArgument As String) Implements IPostBackEventHandler.RaisePostBackEvent
            Me.DDLBeginYear = Page.Request("ddlBeginYear")
            Me.DDLBeginMonth = Page.Request("ddlBeginMonth")
            Me.DDLBeginDay = Page.Request("ddlBeginDay")

            Me.DDLEndYear = Page.Request("ddlEndYear")
            Me.DDLEndMonth = Page.Request("ddlEndMonth")
            Me.DDLEndDay = Page.Request("ddlEndDay")

            If Not IsDateTimeFormatCheck() Then
                Page.ClientScript.RegisterStartupScript(Page.ClientScript.GetType(), "OpenSubPage", "<script language='javascript'>alert('日期格式有誤!(例如4月沒有31日)');</script>")
                Exit Sub
            End If
            OnClick(EventArgs.Empty)
        End Sub

        Private Sub OnClick(ByVal e As EventArgs)
            RaiseEvent Click(Me, e)
        End Sub
        '------------------------------------------------------------------------------------------------------------------------------------------------------------

        Private Function IsDateTimeFormatCheck() As Boolean
            Dim dtBegin As DateTime = DateTime.ParseExact(DDLBeginYear & "/" & DDLBeginMonth, "yyyy/M", Nothing)
            Dim validBeginDays As Integer = DateTime.DaysInMonth(Convert.ToInt32(Year(dtBegin)), Convert.ToInt32(Month(dtBegin)))

            Dim dtEnd As DateTime = DateTime.ParseExact(DDLEndYear & "/" & DDLEndMonth, "yyyy/M", Nothing)
            Dim validEndDays As Integer = DateTime.DaysInMonth(Convert.ToInt32(Year(dtEnd)), Convert.ToInt32(Month(dtEnd)))

            If (Integer.Parse(DDLBeginDay) < 1 Or Integer.Parse(DDLBeginDay) > validBeginDays) Or
               (Integer.Parse(DDLEndDay) < 1 Or Integer.Parse(DDLEndDay) > validEndDays) Then
                'Page.ClientScript.RegisterStartupScript(Page.ClientScript.GetType(), "OpenSubPage", "<script language='javascript'>alert('日期格式有誤!(例如4月沒有31日)');</script>")
                Return False
            Else
                Return True
            End If
        End Function

        ''' <summary>
        ''' 如何在ASP.NET下取得一般HTML標籤的值?
        ''' https://ithelp.ithome.com.tw/questions/10122409
        ''' [ASP.NET]取得WebControl的Html字串
        ''' https://dotblogs.com.tw/rainmaker/2013/04/08/101008
        ''' calling server side event from html button control
        ''' https://stackoverflow.com/questions/7189037/calling-server-side-event-from-html-button-control
        ''' [asp.net,javascript]如何從前端利用__doPostBack還有LinkButton 回到後端code behind (UniqueID)
        ''' https://dotblogs.com.tw/kevinya/2012/05/25/72407
        ''' HtmlButton.OnServerClick(EventArgs) Method
        ''' https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.htmlcontrols.htmlbutton.onserverclick?redirectedfrom=MSDN&view=netframework-4.8#System_Web_UI_HtmlControls_HtmlButton_OnServerClick_System_EventArgs_
        ''' *** [ASP.NET 控制項實作 Day6] 事件與 PostBack
        ''' https://ithelp.ithome.com.tw/articles/10011861
        ''' How to call a server-side method from client-side JavaScript
        ''' https://decoding.wordpress.com/2008/11/14/aspnet-how-to-call-a-server-side-method-from-client-side-javascript/
        ''' PDF Reference
        ''' http://www.aspalliance.com/cookbook/downloads/pdfs/0672325241_ch03.pdf
        ''' 引發 PreRender 事件,並註冊用戶端指令碼,以產生回傳。
        ''' https://www.jasongaylord.com/blog/embed-javascript-in-custom-aspnet-server-controls
        ''' Embed JavaScript in Custom ASP.NET Server Controls
        ''' https://docs.microsoft.com/zh-tw/dotnet/api/system.web.ui.htmlcontrols.htmlbutton.onprerender?view=netframework-4.8
        ''' </summary>
        ''' <param name="writer"></param>
        Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)
            Dim strHTML As String = ""
            If Page.IsPostBack = False Then 'Page.IsPostBack = False:頁面是第一次加載時候
                InitControlSetting()
            Else 'Page.IsPostBack = true:頁面是重新加載或返回時
                '.....do nothing
            End If

            '控件樣式
            strHTML = strHTML & "<style type=""text/css"">"
            strHTML = strHTML & "    body {"
            strHTML = strHTML & "       margin: 0;"
            strHTML = strHTML & "    }"
            strHTML = strHTML & "    #table0 {"
            strHTML = strHTML & "            background-color: yellow;"
            strHTML = strHTML & "            width: 100%;"
            strHTML = strHTML & "            table-layout: fixed; /*Table欄位寬度一致*/"
            strHTML = strHTML & "            text-align: center; /*Table欄位內容置中*/"
            strHTML = strHTML & "        }"
            strHTML = strHTML & "     #table1 {"
            strHTML = strHTML & "            /*background-color:yellow;*/"
            strHTML = strHTML & "            width: 100%;"
            strHTML = strHTML & "            table-layout: fixed; /*Table欄位寬度一致*/"
            strHTML = strHTML & "            text-align: left; /*Table欄位內容置中*/"
            strHTML = strHTML & "        }"
            strHTML = strHTML & "     .auto-style1 {"
            strHTML = strHTML & "            height: 26px;"
            strHTML = strHTML & "        }"
            strHTML = strHTML & "</style>"

            '控件主體
            strHTML = strHTML & "<table id=""table0"">"
            strHTML = strHTML & "    <tr>"
            strHTML = strHTML & "        <td>&nbsp;</td>"
            strHTML = strHTML & "        <td>"
            strHTML = strHTML & "            <span id=""Label1"" style=""color: blue; font-weight: bold;"">"
            strHTML = strHTML & "            " & TopicLabel
            strHTML = strHTML & "            </span>"
            strHTML = strHTML & "        </td>"
            strHTML = strHTML & "        <td>&nbsp;</td>"
            strHTML = strHTML & "    </tr>"
            strHTML = strHTML & "</table>"
            strHTML = strHTML & "<table id=""table1"" style=""background-color: lightskyblue;"">"
            strHTML = strHTML & "    <tr>"
            strHTML = strHTML & "        <td class=""auto-style1"">"
            strHTML = strHTML & "            <span id=""Label2"" style=""font-size: small;"">選擇日期:</span>"
            strHTML = strHTML & "        </td>"
            strHTML = strHTML & "        <td class=""auto-style1"">"
            strHTML = strHTML & "           <select name=""ddlBeginYear"" id=""ddlBeginYear"" style=""width: 50px; height: 19px;"" >"
            strHTML = strHTML & "               " & If(Page.IsPostBack = False, GetInitYearVals(), RestoreYear(Me.DDLBeginYear))
            strHTML = strHTML & "            </select>"
            strHTML = strHTML & "            <span id=""Label3"" style=""font-size: xx-small;"">年</span>"
            strHTML = strHTML & "        </td>"
            strHTML = strHTML & "        <td class=""auto-style1"">"
            strHTML = strHTML & "           <select name=""ddlBeginMonth"" id=""ddlBeginMonth"" style=""width: 40px; height: 19px;"" >"
            strHTML = strHTML & "           " & If(Page.IsPostBack = False, GetInitMonthVals(), RestoreMonth(Me.DDLBeginMonth))
            strHTML = strHTML & "            </select>"
            strHTML = strHTML & "            <span id=""Label4"" style=""font-size: xx-small;"">月</span>"
            strHTML = strHTML & "        </td>"
            strHTML = strHTML & "        <td class=""auto-style1"">"
            strHTML = strHTML & "            <select name=""ddlBeginDay"" id=""ddlBeginDay"" style=""width: 40px; height: 19px;"">"
            strHTML = strHTML & "            " & If(Page.IsPostBack = False, GetInitDayVals(), RestoreDay(Me.DDLBeginDay))
            strHTML = strHTML & "            </select>"
            strHTML = strHTML & "            <span id=""Label5"" style=""font-size: xx-small;"">日</span>"
            strHTML = strHTML & "            <span id=""Label6"" style=""font-size: xx-small;"">~</span>"
            strHTML = strHTML & "        </td>"
            strHTML = strHTML & "        <td class=""auto-style1"">"
            strHTML = strHTML & "            <select name=""ddlEndYear"" id=""ddlEndYear"" style=""width: 50px; height: 19px;"" >"
            strHTML = strHTML & "           " & If(Page.IsPostBack = False, GetInitYearVals(), RestoreYear(Me.DDLEndYear))
            strHTML = strHTML & "            </select>"
            strHTML = strHTML & "            <span id=""Label7"" style=""font-size: xx-small;"">年</span>"
            strHTML = strHTML & "        </td>"
            strHTML = strHTML & "        <td class=""auto-style1"">"
            strHTML = strHTML & "            <select name=""ddlEndMonth"" id=""ddlEndMonth"" style=""width: 40px; height: 19px;"" >"
            strHTML = strHTML & "           " & If(Page.IsPostBack = False, GetInitMonthVals(), RestoreMonth(Me.DDLEndMonth))
            strHTML = strHTML & "            </select>"
            strHTML = strHTML & "            <span id=""Label8"" style=""font-size: xx-small;"">月</span>"
            strHTML = strHTML & "        </td>"
            strHTML = strHTML & "        <td class=""auto-style1"">"
            strHTML = strHTML & "            <select name=""ddlEndDay"" id=""ddlEndDay"" style=""width: 40px; height: 19px;"">"
            strHTML = strHTML & "            " & If(Page.IsPostBack = False, GetInitDayVals(), RestoreDay(Me.DDLEndDay))
            strHTML = strHTML & "            </select>"
            strHTML = strHTML & "            <span id=""Label9"" style=""font-size: xx-small;"">日</span>"
            strHTML = strHTML & "        </td>"
            strHTML = strHTML & "        <td class=""auto-style1"">"
            strHTML = strHTML & String.Format("   <input name={0} type='submit'  value='查詢'  />  ", Me.UniqueID)
            strHTML = strHTML & "        </td>"
            strHTML = strHTML & "    </tr>"
            strHTML = strHTML & "</table>"
            writer.Write(strHTML)
        End Sub
        Sub InitControlSetting()
            For idxYear As Integer = Year(Now()) To 2000 Step -1
                If idxYear = Year(Now) Then
                    sbYearList.Append("<option selected=""selected"" value=" & idxYear.ToString() & "> " & idxYear.ToString() & "</option>")
                Else
                    sbYearList.Append("<option  value=" & idxYear.ToString() & ">" & idxYear.ToString() & "</option>")
                End If
            Next

            For idxMonth As Integer = 1 To 12
                sbMonthList.Append("<option value=" & idxMonth.ToString() & "> " & idxMonth.ToString() & "</option>")
            Next

            For idxDay As Integer = 1 To 31
                sbDayList.Append("<option value=" & idxDay.ToString() & "> " & idxDay.ToString() & "</option>")
            Next
        End Sub

        Function RestoreYear(strYear As String) As String
            sbYearList.Clear()
            For idxYear As Integer = Year(Now()) To 2000 Step -1
                If idxYear = Integer.Parse(strYear) Then
                    sbYearList.Append("<option selected=""selected"" value=" & idxYear.ToString() & "> " & idxYear.ToString() & "</option>")
                Else
                    sbYearList.Append("<option  value=" & idxYear.ToString() & ">" & idxYear.ToString() & "</option>")
                End If
            Next
            Return sbYearList.ToString()
        End Function

        Function RestoreMonth(strMonth As String) As String
            sbMonthList.Clear()
            For idxMonth As Integer = 1 To 12
                If idxMonth = Integer.Parse(strMonth) Then
                    sbMonthList.Append("<option selected=""selected"" value=" & idxMonth.ToString() & "> " & idxMonth.ToString() & "</option>")
                Else
                    sbMonthList.Append("<option value=" & idxMonth.ToString() & "> " & idxMonth.ToString() & "</option>")
                End If
            Next
            Return sbMonthList.ToString()
        End Function

        Function RestoreDay(strDay As String) As String
            sbDayList.Clear()
            For idxDay As Integer = 1 To 31
                If idxDay = Integer.Parse(strDay) Then
                    sbDayList.Append("<option selected=""selected"" value=" & idxDay.ToString() & "> " & idxDay.ToString() & "</option>")
                Else
                    sbDayList.Append("<option value=" & idxDay.ToString() & "> " & idxDay.ToString() & "</option>")
                End If
            Next
            Return sbDayList.ToString()
        End Function

        Private Function GetInitYearVals() As String
            Return sbYearList.ToString()
        End Function

        Private Function GetInitMonthVals() As String
            Return sbMonthList.ToString()
        End Function

        Private Function GetInitDayVals() As String
            Return sbDayList.ToString()
        End Function
    End Class
End Namespace

大概講解一下
經過compile後會生成一個dll可以添加參考

要兩部加入參考
Step1.對專案右鍵Add Reference(一般)
Step2.預設ToolBox並不會有Foci.NET.Web.ServerControl的控件èRight Click è選擇項目





這裡會產生一段說明文字
是我在組件編輯的(利於版本確認)
可以自行依據任職單位和開發時間點撰寫





Step3.請在預配置的aspx(front-end)文件頂部複製貼上此行再拖曳



Step4.可以讓你設置標題






Step5.Button事件註冊
由於我在我自行設計的Custom Server Control中有添加一個Click的 EventHandler委派
是透過實作IPostBackEventHandler 這個Interface去RaiseEvent的


因此後續其他工程師就可以直接外部去使用


這裡主要遇到的一個測試問題就是當發生PostBack(第一次之後的閃爍)就會遺失年月日資料
因此要做判斷處裡來保留



Reference:
ASP.NET - Custom Controls
https://www.tutorialspoint.com/asp.net/asp.net_custom_controls.htm

Custom server control in asp net
https://www.youtube.com/watch?v=Yb8SqkP2WWo

Asp net custom server controls Part 112
https://www.youtube.com/watch?v=UZkc-4R5lCw

留言

這個網誌中的熱門文章

何謂淨重(Net Weight)、皮重(Tare Weight)與毛重(Gross Weight)

Architecture(架構) 和 Framework(框架) 有何不同?_軟體設計前的事前規劃的藍圖概念

經得起原始碼資安弱點掃描的程式設計習慣培養(五)_Missing HSTS Header