Should it be a user control or a server control?

You must have heard this question in the developer community or in online forums, or at least at your work place. Though, the concept of user and server controls in .NET is pretty old, some developers still get confused when it comes to making a choice between both of them. I myself used to go wrong with the selection and would pick one over the other, mainly because of a very thin line (ok ok..I heard you..a little thick) between them.

Up until now, I was fond of using UserControl for all of my projects. The ratio of picking a user control over server control always used to be somewhere close to 10:1 (probabilistically, 1.0 to 0.1) for me. However, it was then when I received a comment for my experimental work on Code Project article MultiSelect Dropdown Control about my decision being an unthoughtful over choosing a UserControl as the parent class, I decided to enlighten myself more about both the control types and find out why I feel it a thin line what is a very clear distinction, and why I fail to take their design guidelines into account when it comes to authoring them.

I believe one of the important reasons to go for a user control is its simplistic nature. It can be created as easily as a web page and Its design time development support for authoring makes things a lot easier. We don’t need to override the Render() method as we get out-of-the-box support for rendering. A user control is more suitable choice when target control is composite (a collection of other intrinsic controls) in nature and has a lot of static data. On the downside, it is less convenient for advanced scenarios due to its tight coupling with the application it is built for. If the same user control needs to be used in more than one application, it introduces redundancy and maintenance problems as its source form (.ascx) needs be copied and its reference must be provided on the hosting web page. We don’t get to see this kind of problem while dealing with server controls. A server control can be a best choice in the following scenarios:

  • Redistributable
  • Dynamically generated content
  • zero maintenance ― single DLL which can also be added in the GAC
  • Support to add the control in Visual Studio Toolbox.

A server control helps reduce redundancy as it is only a single DLL which can very easily be consumed in more than one application. According to Microsoft Support

"A server control is more suited for when an application requires dynamic content to be displayed; can be reused across an application, for example, for a data bound table control with dynamic rows”

Server controls provide full design-time support when used in a design-time host. It can be added to the toolbox of a visual designer and dragged and dropped onto a page. However, I believe the biggest disadvantage of using a server control is that, it requires to be written from the scratch for which one has to have a good understanding of page lifecycle and the order in which events execute which is normally taken care of in user controls.

Now, coming back to the title of my post "what should we choose", a user control or a server control? I would say such a decision should be a thoughtful one. If you want rapid application development (RAD) with full design time support without understanding the page life cycle, user control is the way to go. On the other hand, If ease of deployment with no redundancy and maintenance headaches is more important for you, think about custom server control.

HTH,

Creating User Controls ― a few good practices

In my previous post, Dealing with GAC atrocities, I talked about code reusability being an important aspect of rapid application development (RAD). User Controls in .NET support code reusability out of the box. Just to remind ourselves, a user control is a control that is created using the same technique we use for creating ASP.NET web pages. We create user controls every now and then in our everyday programming and, at times, we forget to entertain some of its important aspects during the development. For example exposing properties that define its behavior and layout , taking care of the situation where a web page can have multiple instances of a user control etc.

As you probably know that I love to follow the guidelines and like to stick to the checklist whenever possible. I decided to create one for myself containing good practices for user control development. The idea is to come back to this list to see what should go in the user control and what should not. Though, these are not industry standard best practices, I find them very useful in most of the scenarios. Please have a look and let me know what do you have to say about it. Moreover, If there is anything, a procedure, a guideline or even a practice that you think helps you in anyway while writing a user control and saves your time, share it with the world in the comments below.

  1. Every single behavior of a user control should be represented by a public property. More the properties it has, the more it is customizable for the end user. I never forget to write properties that define the important characteristics of my list-type control e.g. AutoPostBack.
  2. Private _AutoPostback As Boolean
    Public Property AutoPostback() As Boolean
    Get
    Return _AutoPostback
    End Get
    Set(ByVal value As Boolean)
    _AutoPostback = value
    End Set
    End Property

    Private _AllowParentSelect As Boolean
    Public Property AllowParentSelect() As Boolean
    Get
    Return _AllowParentSelect
    End Get
    Set(ByVal value As Boolean)
    _AllowParentSelect = value
    End Set
    End Property

  3. There will always be only one instance of a user control on a web page is a blind assumption. If the user control looks good and is designed to keep the user friendliness and intuitiveness in mind, people would love to use it than once, sometimes on the same page which might lead to a conflict between their child control IDs. To deal with it, ClientID property can be used in the HTML as well as in the back end.
  4. If AutoPostback = True AndAlso Request.Form("__EVENTTARGET") IsNot _
    Nothing AndAlso Request.Form("__EVENTTARGET").Equals(Me.ClientID + "_categoryMenu") Then

    RaiseEvent OnSelectedIndexChanged(Me, Nothing)

    End If

  5. All the external script files (JavaScript etc.) should always be registered using Page.ClientScript.RegisterClientScriptInclude and Page.ClientScript.IsClientScriptIncludeRegistered method to avoid registering duplicate scripts resources for multiple instances of user control on a page.
  6. Private Sub RegisterClientScriptIncludes()

    Dim clientScriptMgr As ClientScriptManager = Me.Page.ClientScript

    If clientScriptMgr.IsClientScriptIncludeRegistered(Me.GetType(), "mcDropdown") = False Then
    clientScriptMgr.RegisterClientScriptInclude(Me.GetType(), "mcDropdown", ClientScriptIncludePath)
    End If

    End Sub

  7. The user control should have no knowledge (no hard-coding), unless required, of any project/application-wide configurations e.g. resource (image/script) file path, connection strings, database column names of the supplied data source, default values etc. We can maintain the abstractness of a control by passing all these values from the page as properties.
  8. Private _DropdownListStyles As String
    Public Property DropdownListStyles() As String
    Get
    Return _DropdownListStyles
    End Get
    Set(ByVal value As String)
    _DropdownListStyles = value
    End Set
    End Property

    Private _DataTextField As String
    Public Property DataTextField() As String
    Get
    Return _DataTextField
    End Get
    Set(ByVal value As String)
    _DataTextField = value
    End Set
    End Property

    Private _DataValueField As String
    Public Property DataValueField() As String
    Get
    Return _DataValueField
    End Get
    Set(ByVal value As String)
    _DataValueField = value
    End Set
    End Property

  9. There is a possibility of a user control being used on a page which does not contain any .NET default server control, hence no sign of __doPostBack JavaScript function definition which can be a serious problem if the user control has to do a PostBack. We can harness GetPostBackEventReference() under ClientScriptManager which returns a string that can be used in a client event to cause Postback to the server.

  10. There is a consistency in the property names e.g. DataSource, DataTextField, DataValueField for all ASP.NET data bound controls because of their support to the standard Windows Forms data-binding model. This helps developers gain familiarity with the control very quickly. We can try wherever possible to maintain this level of friendliness in our user control as well by keeping the property and method names as close to that of default controls. The same goes for event names as well. For example for a user control that is going to behave like a list-type control, we can think of having properties such as DataTextField, DataValueField, DataSource, SelectedIndex etc. and events like SelectedIndexChanged.
  11. Private Sub ddlApplicationStatus_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles ddlApplicationStatus.OnSelectedIndexChanged
    Try

    Dim ctlDropdown As mcDropdown = CType(sender, mcDropdown)
    Dim selectedVal As Integer = ctlDropdown.SelectedValue
    Dim selectedText As String = ctlDropdown.SelectedText

    Catch ex As Exception
    'log exception here...
    End Try

    End Sub

  12. In addition to method and events, a list-type user control should also expose ListItemCollection that gives complete control of the items and facilitates adding, inserting, removing and finding items.

  13. We need to make sure the control is able to maintain its state on PostBacks. It goes in line with the best practice of loading controls when the page is being rendered for the first time.
  14.  If Not Me.IsPostBack Then
    InitializeUserControls()
    End If

  15. Instead of inheriting our control class directly from System.Web.UI.UserControl class every time, we should first look for an abstract class if it is available that defines the same properties, methods, and events common that we need for our user control. For example ListControl class can be served as the abstract class for all list-type controls and DataBoundControl class for the controls that display their data in list of tabular form.

  16. The default values should be specified for the control properties, hence allowing the user to get the control up and running in very little time.
  17. Private _AllowParentSelect As Boolean
    Public Property AllowParentSelect() As Boolean
    Get
    Return _AllowParentSelect
    End Get
    Set(ByVal value As Boolean)
    _AllowParentSelect = value
    End Set
    End Property

HTH,

Dealing with GAC atrocities

Code reusability is one of the important pillars of Object Oriented programming (OOP). I like this feature a lot as It doesn't make sense to write a crucial piece of code again and again and in multiple places, which is actually being shared across applications. Why not to write it once and reuse it everywhere. Classes in OOP can be a very good example of this, which can be written once and used as many times as we want by initializing them as objects.

Anyone, who works with Visual Studio knows that referencing an assembly in the project paves the way towards code reusability. On the other hand, in order to achieve this same thing while in the production environment, we need to install assemblies in the Global Assembly Cache(GAC) . There are a lot of advantages of using GAC as a shared repository, but this post is not an effort towards that as there are a bunch of articles already available on the internet. What I want to share here are the concerns which I have listed below, which may come to minds before deciding on to go down this route of using GAC for our projects.

  1. Let's consider a scenario where we want to share a DLL say ExceptionHandler.dll between two applications HelloWorld and HelloUniverse. We have signed the assembly (DLL) using a strong name key and installed it in the GAC on the production server so that it would be utilized by both the applications. We have deployed the applications where they seem to be accessing the DLL from GAC. So far so good. Now assuming, the ClassLibrary project (ExceptionHandler.dll) is part of a solution which also contains either of the applications let's say HelloWorld, there is a possibility while building the application someone from the team can unintentionally do a FULL Debug of the solution, leaving the version number (build + revision) changed. Now, a week later our HelloUniverse application which is also pointing to the same DLL project undergoes a change and is deployed alone to the server assuming it will pick the same DLL that was deployed earlier and eventually he gets this:

    Could not load file or assembly 'ExceptionHandler, Version=3.1.65.66, Culture=neutral, PublicKeyToken=a2e77a5f9a0ce598' or one of its dependencies. The system cannot find the file specified.

  2. Let's assume for the time being that the situation above will never happen as there is no room for an unintentional behavior in your team. But, You will agree with me on the fact that using GAC as a shared repository motivates one towards having multiple versions of the same DLL as there is a native support for that. When it was designed to overcome problems like DLL HELL, then why not to use it. It is a very valid point to support applications still relying on the older version. One of the concerns is that if I come back to my GAC after a year, I will find it cluttered with multiple copies of the DLL with same name but with different version numbers.


  3. If the above scenario proves to be true, will it not become a manageability issue to keep a track of assembly versions on both production as well as development ends considering we have a reasonably big number of applications.

I am in no way trying to discourage anyone from using GAC, rather giving my point of view on the issues that get in our way whenever we decide to use it as a shared folder for assemblies. I may be wrong and may be these issues will never come for someone or they may not be a big concern for a few, but I believe sometimes it is good to be paranoid of implementing new technology or suggesting it over an existing one. This may give us an opportunity to think left, right and center before we get our hands dirty with it.

Before I finish, I would like to briefly comment on the way I believe above issues can be addressed. For the first point, each application or set of applications can point to their own copy of the shared DLL leaving no room for errors. The second and the third issues can be tackled by building applications targeting a single stable version of DLL at some point in future, which then can be replaced with all of the older DLLs with multiple versions in the GAC.

HTH,