A DotNetNuke Reality Check

 

Original Module: VCLinks_01.02.00_Install.zip

Revised Module: VCLinks_02.00.00_Install.zip

This is a reality check not for the developer who's code is being examined but for myself, the "DotNetNuke Module Development Evangelist". The code examined here achieves the most important requirement. It works. The challenge for the "Evangelist" is to provide suggestions that offer improvements in time and efficiency. Just saying that "this is the recommended way" is not good enough. Learning new techniques is a costly process itself. This must be factored into the total cost. No developer can be expected to do anything that is more costly than the alternative.

The Module

The module being examined is called VC Links. It allows an administrator to define categories and add links to each category. 

Module Structure

The graphic below shows the module structure:

Module Navigation

The entry point for the application is "VCLinks.ascx". It implements the Dynamically Loaded Control pattern. The query string for each link is examined and the appropriate user control is injected.

            If (Request("LinkID") <> "") Then
                If (IsNumeric(Request("LinkID"))) Then
                    strControl = "ucAddLink.ascx"
                    LoadUserControl()
                End If
            Else
                If (Request("LinkDetail") <> "") Then
                    If (IsNumeric(Request("LinkDetail"))) Then
                        strControl = "ucLinkDetail.ascx"
                        LoadUserControl()
                    Else
                        strControl = "ucViewAll.ascx"
                        LoadUserControl()
                    End If
                Else
                    strControl = "ucViewAll.ascx"
                    LoadUserControl()
                End If
            End If

Recommendation

The recommended navigation is covered here: NavigateURL: How to make a link the reasons are covered here: Module Navigation Options.

Reality Check

While NavigateURL is the recommended approach it does not save any code. The primary objection to the Dynamically Loaded Control pattern that is can produce some odd behavior in certain situations because dynamically loaded controls do not save viewstate.  There are solutions. For example, Denis Bauer has created a Dynamic Placeholder that maintains the viewstate of dynamically loaded controls. A tutorial describing it's implementation can be found at this link.

Since the current code works and there is no code savings the recommendation is to leave the code as it is.

The Database Layer

The module performs the standard CRUD operations. To illustrate this the link administration will be examined and a new page that contains the same functionality will be created.

When a link is updated this method is run:

       Private Sub UpdateLink()
 
            Dim intCategory As Integer = ddlCategory.SelectedValue
            Dim strLinkName As String = tbLinkName.Text
            Dim strLinkURL As String = tbLinkURL.Text
            Dim strLinkDescription As String = tbLinkDescription.Text
 
 
            'Open Connection
            Dim strConn As String = ConfigurationManager.AppSettings("SiteSqlServer")
            Dim objConn As New SqlConnection(strConn)
            Dim cmd As New SqlCommand
 
            cmd.CommandText = "vcLinks_UpdateLink"
            cmd.CommandType = CommandType.StoredProcedure
            cmd.Connection = objConn
 
            'Declare Parameter(s)
            cmd.Parameters.AddWithValue("@intVCLinksID", VCLinksID)
            cmd.Parameters.AddWithValue("@intCategory", intCategory)
            cmd.Parameters.AddWithValue("@strLinkName", strLinkName)
            cmd.Parameters.AddWithValue("@strLinkURL", strLinkURL)
            cmd.Parameters.AddWithValue("@strLinkDescription", strLinkDescription)
            cmd.Parameters.AddWithValue("@intUpdatedBy", Me.UserId)
            cmd.Parameters.AddWithValue("@UpdatedDate", Date.Now)
 
            objConn.Open()
 
            'Get ID of Restaurant Just added 
            cmd.ExecuteNonQuery()
 
            objConn.Close()
 
        End Sub

This code is very succinct and tight. It simply gathers values and passes them to the stored procedure. There is nothing wrong with this code. The data has to be gathered and passed to the database. This method logically seems as good as any other. This code calls this stored procedure:

ALTER PROCEDURE [dbo].[VCLinks_UpdateLink]
 
    @intVCLinksID int,
    @intCategory int,
    @strLinkName varchar(100),
    @strLinkURL varchar(100),
    @strLinkDescription varchar(1000),
    @intUpdatedBy int,
    @UpdatedDate datetime
    
AS
 
 
UPDATE    VCLinks
SET              
VCLinksCategoryID = @intCategory,
LinkName = @strLinkName, 
LinkURL = @strLinkURL, 
LinkDescription = @strLinkDescription, 
UpdatedBy = @intUpdatedBy, 
UpdatedDate = @UpdatedDate
 
WHERE     (VCLinksID = @intVCLinksID)
 
SET NOCOUNT ON

Again, the stored procedure is simple and straitforward. Looking at it, there is nothing "wrong" that stands out. You could not find fault because the DotNetNuke DAL is not being used because the DotNetNuke DAL is meant to be used in situations where you need to support multiple databases. For this module that is not the case. In addition the DotNetNuke DAL would require far more code.

While the ExecuteSQL method of the DAL+ can be used to eliminate the need to create the stored procedures, it will not save very much work or time because essentially the same code that is in the stored procedure still needs to be created in the ExecuteSQL method.

Recommendation

The recommendation is to use Linq to SQL to save the amount of code needed. In addition the wizards provided by Visual Studio, when you use declarative data binding to strongly typed data sources, will also save considerable time when creating the user interface. 

Currently the functionality to administer the links is contained in the following page:

The assumption we will make is that reducing this code will reduce development time and the amount of potential coding errors that have to be debugged. The stored procedures will be eliminated also but this is a by product of implementing the solution.

Create the Linq to SQL DAL

This tutorial provides an in-depth demonstration of using Linq to SQL: Creating a DotNetNuke Module using LINQ to SQL (VB and C#).

For this module, we will simply create a Linq to SQL class and drag the two tables the module uses onto it and then save the page. The DAL is created in less than a minute. This "Linq to SQL DAL" is a class that is automatically created that contains classes that are mapped directly to the database and provide the functionality to manipulate the database and it's data. In addition the Linq to SQL class provides change tracking.

The Linq Data Source control can be used to configure automatic Creates, Reads, Updates, and Deletes.

It allows for common record selection requirements to be configured using the wizard. For example this code:

        Private Sub ReadQueryString()
            If (Request("LinkID") <> "") Then
                If (IsNumeric(Request("LinkID"))) Then
                    VCLinksID = CInt(Request.QueryString("LinkID"))
                End If
            Else
                VCLinksID = Null.NullInteger
            End If
        End Sub

Can be configured using the wizard:

When the FormView is placed on the page and connected to the Linq Data Source control, the form is automatically created.

We need only to remove unneeded fields, add JavaScript validation controls and fix the formatting.

The new page to handle the same functionality:

Most importantly it takes a lot less time to create. 

Handling Business Rules

The module also contains this code to determine if a link already exists:

        Protected Sub cvalLinkNameExist_ServerValidate(ByVal source As Object, ByVal args As System.Web.UI.WebControls.ServerValidateEventArgs) Handles cvalLinkNameExist.ServerValidate
 
            'Check to see if LinkName Already Exists
            Dim intExists As Integer
 
            'Open Connection
            Dim strConn As String = ConfigurationManager.AppSettings("SiteSqlServer")
            Dim objConn As New SqlConnection(strConn)
            Dim cmd As New SqlCommand
 
            cmd.CommandText = "vcLinks_Validate"
            cmd.CommandType = CommandType.StoredProcedure
            cmd.Connection = objConn
 
            'Declare Parameter(s)
            cmd.Parameters.AddWithValue("@ColumnNameValue", tbLinkName.Text)
 
            objConn.Open()
 
            'Get ID of Restaurant Just added 
            intExists = cmd.ExecuteScalar()
 
            objConn.Close()
 
            ReadQueryString()
            If VCLinksID > 0 Then
                args.IsValid = "True"
            Else
 
                If intExists > 0 Then
                    args.IsValid = "false"
                Else
                    args.IsValid = "true"
                End If
            End If
 
        End Sub

Again, we can use Linq to SQL to not only save a few lines of code but to also create a business rule that will be enforced no matter what UI control calls it. We simply add this partial class to the Linq to SQL class:

        Private Sub InsertVCLink(ByVal instance As VCLink)
 
            Dim results = From link In VCLinks.AsQueryable _
                          Where link.LinkName = instance.LinkName _
                          Or link.LinkURL = instance.LinkURL _
                          Select link
 
            If results.Count() > 0 Then
                Throw New Exception("Link Name or Url is already being used")
            End If
 
            Me.ExecuteDynamicInsert(instance)
        End Sub

And this code to catch a possible exception and indicate that the insert was not performed:

        Protected Sub FormView1_ItemInserted(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.FormViewInsertedEventArgs) Handles FormView1.ItemInserted
            If e.Exception Is Nothing Then
                If boolAddAnother Then
                    Response.Redirect(Globals.NavigateURL(PortalSettings.ActiveTab.TabID, Null.NullString, "LinkID=0"))
                Else
                    Response.Redirect(NavigateURL(), True)
                End If
            Else
                lblError.Text = e.Exception.Message
                e.ExceptionHandled = True
                e.KeepInInsertMode = True
            End If
        End Sub

 

Programming is Art

Programming is art much the same way constructing a building is art. We desire to follow standard conventions to construct safe buildings, yet there is plenty of room for interpretation. The artistic aspects of programming is one of the things that motivates many of us to program. Yet, as the discovery of perspective was revolutionary for art, technologies such as Linq are also revolutionary. However, it is up to the programmer, the artist, to determine when, where, and how to apply them.

Note: In order to run this module on another DotNetNuke site, you need to install ASP.NET 3.5 on the server and modify the web.config of the DotNetNuke site. Use this program: LinqPrep to add the required keys to the web.config file.

 

Back to: The ADefWebserver DotNetNuke HELP WebSite


(C) by Michael Washington - ADefWebserver.com - Webmaster@ADefWebserver.com

DotNetNuke is a registered trademark of the DotNetNuke Corporation