AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=345&pId=-1
The Basics of Creating Server Controls
page
by Justin Lovell
Feedback
Average Rating: 
Views (Total / Last 10 Days): 51621/ 96

Foreword

Download the C# source used in this article.
Download the VB.NET source in this article.

In classic ASP, our code was a mix of in-line scripts and HTML. It was very easy for anyone, with logical thinking, to write the code for a dynamic web page. The reason was simple – the steps of the code that was ran was from top to bottom. Wherever there was HTML, it outputted the HTML. When the page parser found a script block, it would then translate it into a small program/applet and then it would be executed. If any output was required from these script blocks, it was a matter of using the Response.Write method. As ASP developers got familiar with their technology, they wanted to reuse repetitive code and scale out their application. There was workarounds such as having common file includes with methods and functions but did not address a true scalable solution. There was the ability to create server/ActiveX components but this was only an option to the people who were not on shared hosting plans or for the people with hosts that did allowed the installation of the components for a fee.

This area is where ASP.NET excels in over its predesor. Server controls is the answer to a scalable design and reusable code for outputting common user interfaces. Microsoft ships with built-in server controls that will do almost any general task from input fields, to buttons, to displaying data in a table (DataGrid). Likewise, with classic ASP, as time progresses and as your knowledge of the .NET framework begins to mature, you will start to demand more control of those server controls from doing repetitive tasks such as data binding.

Fortunately, we are not stuck completely as we found ourselves in classic ASP. We are able to extend the built-in server controls at a very easy level and with a great deal of comfort. We can achieve this by inheriting from the server control that we want to extend its functionality or to change the behaviour of. Furthermore, we can create a whole new server control for new functionality and interaction that could not be provided by the built-in server controls.

Over a series of articles, I will be writing about different aspects of creating server controls for ASP.NET - the articles will span over a few months. This article series is not aimed for beginners but is aimed for intermediate programmers and beyond. As far as this article is concerned, we will be dealing with:

  • The basics of server controls.
  • Cover very briefly of the essential internal workings of ASP.NET pages.
The Basic Concept of the User Interface in ASP.NET

One of the main critera is for a "server control" to qualify as a server control, the class must inherit from System.Web.UI.Control class, directly or indirectly. Another main critera for one to qualify as a server control is the ability to output some type of text into the HTTP request; either or all of the following:

  • Client side scripts.
  • Html mark-up.
  • Contain other server controls as children.

With that said, time for you to swallow the big red pill – a page (the one at System.Web.UI.Page) is a server control as well. It does qualify for both of the main criterion. Think that you got a fright when you read that a page is a server control? Then just read on...

Lets add more complexity - every ounce of text and “static” HTML is a server control as well. Here is the interesting part – text can defy this behaviour (get converted to a server control) can be manipulated so that the text can become part of the control (known as extended or advanced properties). With the text out of the way (either a server control or part of a server control), the page parser finalizes the page.

When the page is being finalized the following will occur - all controls that contain other controls are then sorted into the parent-children relationship (the parents are also known as container controls). To accomplish this, AddParsedSubObject method is then used by the page parser. By default, the method will take the instance of the control (that was found "underneath" the current control) and then cast it as a Control class. This method will then take the control and then place it under a ControlCollection class - namely, the Controls property.

While the AddParsedSubObject method is being executed (adding the controls from the page parser), the ControlCollection class is doing what its constructor asked for the instaintation to create the ControlCollection – telling the new controls who their “parent” is. This is required by unique identification of the controls for various reasons. The page parser works from the top of the page hierarchy and works “deeper” into the control tree - moving in a left to right and a top to bottom fashion.

When those controls gets its content "underneath" themselves, the process started all over again until the entire page has been parsed. Now the Page is ready to begin its life cycle.

As we go across more articles in the series, we will pick up other essential bits and pieces that makes a control, a control (no pun intended). Although I did explain this section in a very brief manner, there is nothing really to lure on about the internal workings of the user interface in ASP.NET.

The Life Cycle of a Page and its Controls

In classic ASP, there was only one stage to the life cycle of the execution in the page. Basically, the page's life would start at the top of a script file (after IIS got all the server include files) and end at the bottom of the script file.

Because .NET is object orientated, there is a thing called events. With the aid of these events, the page/control life cycle is a bit more "eventful". Here is the basic order of events (without post back):

  1. Initialize (in your code, it is called the Init event) – Initialize settings needed during the lifetime of the incoming web request. If the server control is a composite control, this will load the default items based in the CreateChildControls method.

  2. Load – Perform actions common to all requests. This is the usual event that the page developer does his/her processing.

  3. PreRender – Perform any updates before the output is rendered. This is the usual event that control developers does his/her processing. This event is the last one that can do the last major modifications by the developer.

  4. Save View State - The ViewState property of a control is automatically persisted to a string object after this stage. Normally, this is stored to a hidden field on the page but can be persisted to another medium if need be.

  5. Render – Generates output to be rendered to the client. Note that not much functionality is able to be done here.

  6. Dispose – Perform any final clean-up before the control is torn down. Database connections, files and such should be closed at this point.

  7. Unload – Perform any final clean-up before the control is torn down.

This is the life cycle or order of events that the page undergoes during post back (note that some of these events do not have a description due to the fact that it was described in the above listing):

  1. Initialize – After the normal initializing is done, as described above, it will then do the following:

    1. Load View State – Loads the ViewState from the persistent medium. It then searches for the controls to load its ViewState.

    2. Process Post Back Data – Processes incoming data. This will only fire on controls that implement the IPostBackDataHandler interface.

  2. Load  – After the normal Load event is done, as described above, it will then do the following.

    1. Sends post back notifications – Raise change events in response to state changes between the current and previous postbacks. This will only fire on controls that implement the IPostBackDataHandler interface.

    2. Handle Post Events – Raises the event that the client-side triggered the post back for. First, it searches for the control that the event was targeted for. Once found, it determines if the control implements IPostBackEventHandler interface. If the interface is implemented, the RaisePostBackEvent method is called with a string argument which will then determine what event to raise.

  3. PreRender

  4. Save View State

  5. Render

  6. Dispose

  7. Unload

There is some behaviour that some of us is not consciously aware of but we use it to our advantage. The one behaviour is the first control to be alive will be the Page. Then its children starts to live, and their children starts to live and so on down until all the descendants are alive. The last control(s) that become alive is the first control(s) to die; therefore, we can conclude the page is the last one to die. To exagurate on this fact, we will then talk in lame-man's terms:

When the page begins its life, it would start with the Init event. Once the page has lived through the Init event, it will call the child(rens) Init event. Once those childrens' Init event has fired, their child(rens) starts to live through Init event and so it carries on down the hierarchy chain; for every, individual event as well. Therefore, the page starts life earlier (the parent control will live first).

I mentioned that the first control to live is the last to die. The reason is that all the controls in the page will execute the Unload event. When the Unload event is finished on the control that is located the furthest down the hierarchy, the control is finished. Its parent will then do the final finishing ups and then will finish its self. This bubble effect will continue until the Page gets the go ahead to do the finishing touches - therefore, the page is the last to die (the last control to "die").

Although many of us do not notice the following described behaviour now, whenever a control is dynamically added to a control collection, it will be given life... a rather speedy one! The control that was dynamically added plays catch up with the life cycle to the current point of the parent's point in its life cycle. For example, if the parent control is in its PreRender event and a control is dynamically added to the control collection of this parent, the child control will catch up to, and including the PreRender event.

Guidelines and Trends for Developing Controls

Before we can begin at looking at some code for creating server controls, I have added a few guidelines and trends for developing server controls. Note that I am not calling it rules; although I would like to. These are just some general guidelines and trends and I am not including every single one here because of the following reasons.

  • I am bound to forget to mention every single guideline or trend.

  • No one will remember all the rules for later on in the series.

  • I do not want to reference back to this article later in the series.

Without further a due, here is the general guidelines and trends:

The control class should be able to be inherited from

Whoever wants to extend your control, should be freely able to do so. No matter who your target audience is or what functionality and features that your control provides, you will always find that one developer who will want to extend it for his/her own extra mile.

The most common scenario is that the developer wants to create a control that they do not want to do common, repetitive data binding for every page. Sure they can drop your server control into a user control and do their data binding there and use that across their pages... but do you really want to do that if you spent an investment of your time for a rich design time (and possibly, to mention your customers investment of money into your product). If your “clients” really wanted to steal your control and publish it as their own, they would have to be forced to do one of the two things:

  • Attach your assembly to their distribution of their control (that is that their control cannot go solo without the help from your control).

  • Reverse engineer the byte-code that .NET Framework assemblies.

Other partial reasons are discussed below.

Rather override the appropriate methods than to attach to events when inheriting a control.

If you care about performance, this is one “rule” that you must keep in mind. Basically, you are saving extra memory being consumed which events point to delegated methods in your code. Obviously, you can avoid this by overriding the method name that is associated with the desired event – your code will then be inline with the execution of the event.

But most all, you might want to dominate how your code is going to run. You might want to do some processing before the actual event is fired or you might even want to dictate if the control fires its event at all. In other words, it puts you in the god role that you have absolute power. If you want the event to fire, ensure that its base's method that is associated with the event is called.

If you create custom events, create virtual (or overridable) methods to be associated with the events

On your quest around the .NET SDK, you might have noticed for every event, there is a method that is associated with that event, namely, OnEventName. You will also notice that the method's visibility is “protected”. The only reason why the accessor is protected is because it is not for use outside the control – instead, it should be used by controls that derive from it. Remember to mark it virtual, so the previous guideline “Rather override the appropriate methods than to attach to events when inheriting a control” can be obeyed by other control developers.

If overriding a method that is associated with an event, ensure that you call the base's method

This is not a hard-enforced rule as such but more of a fore-warning. If you want the event to be fired, then the base's method that is associated with the event must be called. The explanation is in both of the previously discussed guidelines and trends.

Always do your processing during the PreRender event of the page life cycle – not during the Load event.

This must be declared as a golden rule. The page developer do their processing during the Load event. You, as the control developer, is to take those changes that the page developer possibly did to your control during the Load event and start processing. To ensure that no “concurrency” error of order of events will occur, the last event that any major changes that can be done is during the PreRender event – straight after the Load event. It is just a precaution, just in case the page developer creates a page framework that may throw your processing off.

If you need to do some processing before the PreRender event, you can consider doing processing during the Init event.

Deliver data to view state and to persist during post backs

Pretty simple trend and should become a rule because so many control developers do not follow this. The usual excuse is that the page developer should not be lazy by setting those few properties or that the page developer should populate the data every time the page is executed. This just breaks the normal flow of developing pages and some might want to keep their 233MHz SQL Server machine a bit “airy” from unwanted requests. Although a database server is hardly found on a low resource as that, that might as well be the resources available if a web farm gets a sudden influx of traffic.

If you, as the control developer, insist that the page developer caches the DataTable, you might lose a valuable customer. The reason why you will lose him is because he will see your workmanship as lazy. Do not be a lazy developer yourself.

Expose Properties, not fields

There is one sole reason for doing this – keeping consistency with the current OOP model of the .NET Framework. It will also offer you more flexibility to do some changes between two versions of your controls without breaking them.


As I said before, we will analyse other guidelines and trends as we deal with the subject when we are examining it under great detail.

Post Back - Raising Events

Most of the built-in ASP.NET controls can post back to the server to create some changes. Lets take a button for an example - you can listen to the Click event and you will be notified when the button has been clicked by the user. But the event and code resides on the server - so how does ASP.NET execute the code/listeners if HTTP is stateless? The answer is simple - with the aid of JavaScript. If you had to view the source of this very page, you will see a JavaScript function like this:

function __doPostBack(eventTarget, eventArgument) {
   var theform;
   if (window.navigator.appName.toLowerCase().indexOf("microsoft") > -1) {
      theform = document.Form1;
   }
   else {
      theform = document.forms["Form1"];
   }
   theform.__EVENTTARGET.value = eventTarget.split("$").join(":");
   theform.__EVENTARGUMENT.value = eventArgument;
   theform.submit();
}

It tells the server side form (which should be found on every ASP.NET page) to send a POST data request back to the server. But in the process, it assigns two hidden fields with some values:

<INPUT type=hidden name="__EVENTTARGET">
<INPUT type="hidden" name="__EVENTARGUMENT">

Those values contain the server control's identification that ASP.NET has to find to raise the event and a parameter to pass to the server control. The identification is guarantees to be unique - that identification contains the hierarchy location of where the server control is located on the ASP.NET page... or should be. If the server control does not exist, no event is raised on the server control. This may occur if you add controls dynamically to the control collection. Once the server control is located (must be by the end of the Load event of the page), the parameter is passed to the server control. If that server control contains multiple events, that parameter will inform the server control which event to fire.

All server controls that wants to raise events must do three things first (at least):

1. Create an event field

// [C#]
public event EventHander Click;
' [VB.NET]
Event Click As EventHander

2. Create methods to relate to the event field.

It is convenient to name it "OnEventName". Please read the Guidelines page to see all the guidelines.

// [C#]
protected void OnClick(EventArgs e) {
if (Click != null)
Click(e);
}
' [VB.NET]
Protected Sub OnClick(e As EventArgs)
   RaiseEvent Click(Me, e)
End Sub

3. Implement the IPostBackEventHandler interface.

You will also implement its only member to get that parameter that is passed for post back.

// [C#]
public class MyControl : Control, IPostBackEventHandler {
   public void RaisePostBackEvent(string eventArgument) {
      OnClick(EventArgs.Empty);
   }
}
' [VB.NET]
Public Class MyControl
   Inherits Control
   Implements IPostBackEventHandler
  
   Public Sub RaisePostBackEvent(eventArgument As String) _
Implements IPostBackEventHandler.RaisePostBackEvent
      OnClick(EventArgs.Empty)
   End Sub
End Class


Now, ASP.NET is very convenient about creating the JavaScript to call/use that JavaScript function that I discussed briefly about earlier on this page. Here is the two methods that can be utilized to get that client callee statement (used mainly during Rendering):

For demonstration purposes, we will create a control that will render a HTML <a> tag and will post back to the server. We are going to pretend that the Hyperlink control does not exist nor does the LinkButton control; but we know that the Label control does exist (so we can have those styling stuff built-in to Label control). I will put all the control's source down first before explaining what each part does.

// [C#]
public class MyOwnLinkButton : Label, IPostBackEventHandler {
private string clientPostBackScript;
   public event EventHandler Click;
   protected virtual void OnClick(EventArgs e) {
      if (Click != null)
         Click(this, e);
   }
   protected override HtmlTextWriterTag TagKey {
get { return HtmlTextWriterTag.A; }
}
   protected override void OnPreRender(EventArgs e) {
clientPostBackScript = Page.GetPostBackClientHyperlink(this,
String.Empty);
base.OnPreRender(e);
}
   protected override void AddAttributesToRender(HtmlTextWriter writer) {
    writer.AddAttribute(HtmlTextWriterAttribute.Href, clientPostBackScript);
    base.AddAttributesToRender(writer);
   }
   public virtual void RaisePostBackEvent(string eventArgument) {
OnClick(EventArgs.Empty);
}
}
' [VB.NET]
Public Class MyOwnLinkButton
   Inherits Label
   Implements IPostBackEventHandler
   Private clientPostBackScript As String
   Public Event Click As EventHandler
  
   Protected Overridable Sub OnClick(e As EventArgs)
      RaiseEvent Click(Me, e)

   End Sub
  
   Protected Overrides ReadOnly Property TagKey() As HtmlTextWriterTag
      Get
         Return HtmlTextWriterTag.A
      End Get
   End Property
   
   Protected Overrides Sub OnPreRender(e As EventArgs)
      clientPostBackScript = Page.GetPostBackClientHyperlink(Me, [String].Empty)
      MyBase.OnPreRender(e)
   End Sub
  
   Protected Overrides Sub AddAttributesToRender(writer As HtmlTextWriter)
      writer.AddAttribute(HtmlTextWriterAttribute.Href, clientPostBackScript)
      MyBase.AddAttributesToRender(writer)
   End Sub
  
   Public Overridable Sub RaisePostBackEvent(eventArgument As String) _
      Implements IPostBackEventHandler.RaisePostBackEvent
      OnClick(EventArgs.Empty)
   End Sub
End Class

I created my event called “Click”. I then associated a protected, virtual (overridable) method that is associated with the “Click” event, called “OnClick” method. I then implemented the interface, IPostBackEventHandler and its only member, the RaisePostBackEvent method. I made the RaisePostBackEvent method virtual (overridable) to make way for control developers the ability to add further post back functionality if they decide to extend our control. Since that the RaisePostBackEvent method will be called if our control does the post back and since we only have one event, we pass execution flow over to the method associated with the only event called “OnClick”.

In the C# code, the “OnClick” method will then check if the event has any delegates attached to its associated event. The “Click” event will be null, if no delegates are attached. If the event has delegates attached, then the “Click” event is then called as if it was a method. The parameters that are parsed in this calling is defined in the delegate's declaration. I will cover the basics at a latter article in the series.

In the VB.NET code, there is only one way to raise the event and that has to be with the RaiseEvent statement. This statement checks if any delegated methods are attached to the even, and if any are found, the delegated methods will be called.

I also overrode the OnPreRender method so I can do two specific tasks with one line of code (with the GetPostBackClientHyperlink method). These are the tasks that this one-liner does.

  • ASP.NET checks if the post back script block exists in the page. If not, it will register the script block (the script block that is shown at the top of this page).
  • To get the client side script to invoke the script block that was just registered to the page.

You cannot do the above operation completely during the Render event in the life cycle because ASP.NET cannot insert the script blocks as described in the first thing that the one-liner does. We simply save the client side invocation to a private variable and render it out to the client as a Href attribute.

The rest of the code is for the purpose of rendering. The TagKey property determines the enclosing tags of the control. From the Label control, this would default to be a <span> tag. I just changed the enclosing tag to an anchor (the formal word; in other words, a hyper link). The rest of the rendering is automatically done by the Label control itself (and the WebControl class).

View State - Persisting data during Post Backs

In this article, I would only cover the basics of view state. If you do not know what view state is, it is the medium of containing data to persist when the user posts back to the same page. This concept is not new to classic ASP at all. In classic ASP, persisting data for reuse on the same page was a very tedious task - never mind the fact it was normally in clear text. ASP.NET simplifies that process of persisting data so it is as simple to use as the Application state. View state is used by server controls to persist its data across different requests - it is not designed to persist server controls itself, nor its sub controls.

I am not going to write a long talk about "What is view state?" Instead, you can read a very well explained article by Paul Wilson, title “View State: All You Wanted to Know”.

Just a recap of the only rule of view state - what view state is optimized for:

  • Objects (that are one of the following).

  • Integers.

  • Strings.

  • Floats.

  • Decimals.

  • Objects.

  • Pair class containing the above data types or Triplet class.

  • Triplet class containing the above data types.

  • Arrays of the data types stated above.

  • ArrayList with the values being a type of data types listed above.

  • A StateBag containing the values of the data types listed above.

You can create custom optimized classes for ViewState by implementing IStateManager interface but essentially, you will have to drill down your basic string data type. Essentially, a normal XYZ class is not optimal to be put into ViewState without the implementation of the IStateManager interface - it can be done but heavy work will be required to be completed by ASP.NET to be serialized.

Also note that if you are planning to persist a structure, you will have to add the SerializableAttribute attribute to the actual structure.

The normal suggestion of making your properties persist in view state is by making your properties follow the same type of format:

// [C#]
public string FooString {
   get {
      string value = (string)ViewState["FooString"];
      return (value != null) ? (string)value : String.Empty;
   }
   set { ViewState["FooString"] = value; }
}
' [VB.NET]
Public Property FooString() As String
   Get
      Dim value As String = CStr(ViewState("FooString"))
      If Not (value Is Nothing) Then
         Return CStr(value)
      Else
         Return [String].Empty
      End If
   End Get
   Set
      ViewState("FooString") = value
   End Set
End Property

Tiresome isn't it!? But I have a better solution because I am just as lazy like you. Here is the basic "magic" code that will needed by all controls that is going to follow our lazy route:

(Edit: Marvin Slayton gave some feedback on the original source. He managed to pick up a few bugs that the original source allowed to slip through for posting back to the server. To reproduce the error, view state keys that were set before the Init event caused the keys that were later added via the LoadViewState method to throw an exception. He was even kind enough to provide the fix and I have further modified the fix to provide consinstency.)

// [C#]
private const string BaseStateKey = "DefaultViewState";
private Hashtable StateTable = new Hashtable();
protected internal void SaveToStateTable(string key, object value) {
if (StateTable.ContainsKey(key)) StateTable[key] = value.ToString();
else StateTable.Add(key, value.ToString());
}
protected internal object LoadFromStateTable(string key, object defaultValue) {
   if (StateTable.ContainsKey(key))
      return (string)StateTable[key];
   else {
      if ((defaultValue.GetType() != typeof(string)) &&
(defaultValue.GetType() != typeof(ValueType))) {
StateTable.Add(key, defaultValue);
}
      return defaultValue;
}
}
protected override object SaveViewState() {
string[] keys = new string[StateTable.Count];
ArrayList indices = new ArrayList();
ArrayList state = new ArrayList();
StateTable.Keys.CopyTo(keys, 0);
indices.Add(BaseStateKey);
state.Add(base.SaveViewState());
   foreach (string key in keys) {
indices.Add(key);
state.Add(StateTable[key]);
}
   return new Pair(indices, state);
}
protected override void LoadViewState(object state) {
if (state == null) return;
   ArrayList indices = ((Pair)state).First as ArrayList;
ArrayList values = ((Pair)state).Second as ArrayList;
   for (int i = 0; i < indices.Count; i++) {
if (indices[i].ToString() == BaseStateKey)
base.LoadViewState(values[i]);
else
SaveToStateTable(indices[i].ToString(), values[i]);
}
}
' [VB.NET]
Private Const BaseStateKey As String = "DefaultViewState"
Private StateTable As New Hashtable()
Friend Protected Sub SaveToStateTable(key As String, value As Object)
   If StateTable.ContainsKey(key) Then
      StateTable(key) = value.ToString()
   Else
      StateTable.Add(key, value.ToString())
   End If
End Sub
 
Friend Protected Function LoadFromStateTable(key As String, _
defaultValue As Object) As Object
   If StateTable.ContainsKey(key) Then
      Return CStr(StateTable(key))
   Else
      If defaultValue.GetType() <> GetType(String) And _
defaultValue.GetType() <> GetType(ValueType) Then
         StateTable.Add(key, defaultValue)
      End If
     
      Return defaultValue
   End If
End Function
Protected Overrides Function SaveViewState() As Object
   Dim keys(StateTable.Count) As String
   Dim indices As New ArrayList()
   Dim state As New ArrayList()
   StateTable.Keys.CopyTo(keys, 0)
   indices.Add(BaseStateKey)
   state.Add(MyBase.SaveViewState())
  
   For Each key As String In  keys
      indices.Add(key)
      state.Add(StateTable(key))
   Next
  
   Return New Pair(indices, state)
End Function
Protected Overrides Sub LoadViewState(state As Object)
   If state Is Nothing Then
      Return
   End If
   Dim indices As ArrayList = CType(CType(state, Pair).First, ArrayList)
   Dim values As ArrayList = CType(CType(state, Pair).Second, ArrayList)
  
   For i As Integer = 0 To indices.Count - 1
      If indices(i).ToString() = BaseStateKey Then
         MyBase.LoadViewState(values(i))
      Else
         SaveToStateTable(indices(i).ToString(), values(i))
      End If
   Next
End Sub

With this code, we will save the default view state with the custom view state. We also added the feature that advanced data types will be automatically added to the view state bin if it does not exist in the bin. If it does, any changes to the data inside itself will remain persistent. Now I am going to demonstrate the same functionality that the code before my magic hand was used with the new, improved, lazy format:

// [C#]
public string FooString {
   get { return (string)LoadFromStateTable("FooString", String.Empty); }
   set { SaveToStateTable("FooString", value); }
}
' [VB.NET]
Public Property FooString() As String
   Get
      Return CStr(LoadFromStateTable("FooString", [String].Empty))
   End Get
   Set
      SaveToStateTable("FooString", value)
   End Set
End Property

For more advanced data types such as classes, you can still use this lazy method. Instead of defining a default value such as a "0", you would create a new instance of that data type (by its constructor). Please remember, for optimal performance, ensure that the class implements IStateManager interface for the view state to deserialize the object a lot quicker.

I will discuss further into view state and handling it at a later article in the series. Depending on the feedback I receive and the topics and content of the threads at the www.asp.net/forums/, will determine the depth that it will be discussed in the future.

An Ending Note
I briefly went over server controls at the basic level. Although there is other fundamental basics, I cannot include them all in this article or else this article will start to span over a chapter in a book. I must admit that this article was more of a theoretical approach than code displayed but it will be fundamental to know this basic knowledge. If there is anything that I missed out because I just barely scraped over it, and you want to dig deeper into something that I mentioned, please be patient – I did say that more articles will continue from this series.

By the way, if you have any suggestions or comments about the way I am structuring the series of articles please give me feedback via the feedback link at the top of the page.


Product Spotlight
Product Spotlight 

©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-26 6:38:48 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search