Exceptions and validation in 3 Tier vb.net Solution

Hello

I am starting in vb.net from VBA and coding a 3 Tier application.
I have a Basic Application Running and need to sort out the Validation and Exception control.

I am Validation the data in the UI.
Should I validate it again in
BLL Module
DAL Module
Database Procedure
If I do validate the data at all layers then how do I deal with a validation error if one is found at the DAL module? - How can I send it back to the UI?

Does it Slow the application down if the data is validated 4 times?


Also How do I Control exceptions?
If the Database Procedure can't update the record how do I get that message back to the UI?

Many Thanks
p-platerAsked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

ste5anSenior DeveloperCommented:
Never trust user input. This rule applies to every layer. Cause from the viewpoint of the layer any input is user input and thus you cannot rely on it.

So, when you have for example a length restriction on a column in your database, then propagate this constraint by using for example DataAnnotations in your POCO's. Validate your POCO's in every layer, when you get them and before you hand them over to the next layer.

Log exceptions in the layer where they happen. Wrap them up in a HandledException and reraise them. So that they can propagate thru the correct layer.
Jacques Bourgeois (James Burger)PresidentCommented:
Ideally, you should validate at all levels.

If you validate only at the UI level, then if somebody uses your dll or go directly to the database, they might enter stuff that is not validated. You might think that this will never happen if you have only one application working with the database. But things might change in the future.

As for the way to catch the exception at the top layer, you can do something like the following:
Try
   'Your code
Catch ex As ...Exception
   'Local handling if possible
Catch
   Throw 'Bubbles up the exception to the caller
End Try

Open in new window

In order to make sure that it does not bubles up to Microsoft, you might add a global exception handling mechanism to your application.
p-platerAuthor Commented:
Thanks - I'll go through it in more depth later in the day.

Just one Question - With Data Length restrictions on a Database.
Can the vb.net Tier application get the Length Restrictions from the database or does it have to be Hard Coded in each level (I'm using stored procedures to access the data - Not Linq)?

For example the Accounting system I am currently developing an application for has a default length of 40 for the Product description but the company I am currently working with has it set to 100.
In this case can I get the length of the column on the fly or do I need to have separate DLL files for this one Customer?
Big Business Goals? Which KPIs Will Help You

The most successful MSPs rely on metrics – known as key performance indicators (KPIs) – for making informed decisions that help their businesses thrive, rather than just survive. This eBook provides an overview of the most important KPIs used by top MSPs.

Jacques Bourgeois (James Burger)PresidentCommented:
It's always possible to get information from the database about the lenght of the text, but the way to do it will vary depending on the way you access the data (DataAdapter, DataReader, Data Entities...) and the type of database in use.

Unless you have a lot of differences, you can maintain a single code base for different versions of your application by working with Configurations. You know that dropdown at the top of the code window that lets you switch between a Release and a Debug version? You can add your own configurations and control different versions of the application by working with conditional compilation.

Give a look at How to: Create and Edit Configurations and Conditional Compilation in Visual Basic.

This would enable your to have code similar to the following:

#If Customer1
     Public Const MaxChar As Integer = 100
#Else
     Public Const MaxChar As Integer = 40
#End If

Compile with the Customer1 configuration, and the constant will have a value of 100
Compile with any other configuration, and the constant will have a value of 40

This is very useful when you need to maintain different versions of the same application for different customers. It not only apply to constants definition, but also to any piece of code that might be different from one customer to another.
ste5anSenior DeveloperCommented:
When having a n-tier application, I assume you use either EF or your own POCO's. Is that assumption correct? EF can read those constraints from the database. When you use your own POCO's then create them by reverse engeniering your database with tools like T4.
p-platerAuthor Commented:
I've created a Stored Procedure that throws an Error.
This is populating up the Stack and displaying to the User.
Thanks for That

Now with Validation - If I find some Incorrect data in the BLL or DLL how do I get that message up to the UI?
Do I Create a Custom Exception Class throw a Custom Exception?
If I do create a Custom Exception Class do I need to create one at each level of the Application so the messages can travel up the Stack?
ste5anSenior DeveloperCommented:
You create the exception at the test level. The next layer which receives it logs it and either handles it or wraps it into a new exception, so that the upper layers know that it was already logged and let it bubble to the UI.
Jacques Bourgeois (James Burger)PresidentCommented:
A custom Exception is the way to go if you deal with an exception that is specific to your environment. But for standard issues, such as an invalid value or a time out error, you can simply throw a "standard" exception (InvalidValueException) or retrow the exception that you got in your class (SqlException).

In that last case, you might also want to use a custom Exception to provide a custom Message or provide extra properties, but provide the exception that you got through the InnerException property of you custom Exception.

Exceptions will automatically bubble up, but will stop doing so if trapped in a Catch. From there, if you do not handle them, you need to Throw again so that it continues going up the stack. Simply do not do the common mistake of calling Throw ex, because it resets the StackTrace to the point where you have rethrown the exception. Simply call Throw alone.
p-platerAuthor Commented:
I have added the Following to my BLL and DAL

If Activity.ActivityStatus < 1 Then
            Throw New ArgumentOutOfRangeException("ActivityStatus", Activity.ActivityStatus, "Please Select an Activity Status")
        End If

Open in new window


And the Following to the Save Button

        Try
            Activity.Save(NewActivity)
            Call ClearForm()
        Catch ex As ArgumentOutOfRangeException
            MessageBox.Show(ex.Message)

        End Try

Open in new window


Is this The correct way?
Also When it Hits this Exception the User is Presented with a Message box that contains
The Message
The Parameter
The Original Value

Even though I am specifying the Message box only show the ex.Message.
Am I doing something Wrong?
ste5anSenior DeveloperCommented:
From normal semantic understanding: No. It's wrong.

Cause a status is a restricted enumeration and not a number. Thus it should be an enumeration type.

Side-effect: Using an enumeration gives you implicit documentation of the meaning of the status instead that you need to explain in a comment, why you use a certain number.

Also take a look at DataAnnotations. They provide a built-in mechanism for such definitions and tests.
p-platerAuthor Commented:
Not Sure I follow what you mean ste5an.

The Activitystatus originates from a Combobox that is Bound to the Below Class.

Imports System
Imports System.Data
Imports System.Data.Common
Imports System.Data.SqlClient
Imports XYZDAL

Public Class ActivityStatus
    Public Property ID As Int32
    Public Property Name As String
    Public Property ShortName As String
End Class

Public Class ActivityStatusCollection

    Inherits List(Of ActivityStatus)

    Public Sub New()
        Dim drd As DbDataReader
        drd = ActivityStatusData.GetNames()
        While drd.Read
            Dim activityStatusName As New ActivityStatus
            activityStatusName.ID = drd.GetInt32(0)
            activityStatusName.Name = drd.GetString(1)
            Me.Add(activityStatusName)
        End While
    End Sub
End Class

Open in new window


When the Combobox is updated it updates an instance of an activity class (NewActivity) that is then passed to the save method of the Activity class in the BLL.
Should I be doing it different to Validate in the BLL and DAL?
p-platerAuthor Commented:
I should have said that the Data for the Activity Class is stored in a SQL database - they can be added to at anytime.
p-platerAuthor Commented:
Sorry - Spent all day attempting to get some understanding of DataAnnotations and got nowhere.

Doesn't seem to be any understandable examples for vb.net - Do you have any links to examples of using Data Annotations to Validate Data in a 3 Tier Application?
p-platerAuthor Commented:
I wasn't adding the needed References!

I now have the Following in the BLL but it is still calling the ActivityData.Save when the value of the activityStatus is 0

Imports System
Imports System.Data
Imports System.Data.Common
Imports System.Data.SqlClient
Imports System.Web.DynamicData
Imports System.ComponentModel.DataAnnotations
Imports System.Globalization

Imports XYZDAL

Public Class Activity
    Public Property ID As Int32
    Public Property StartDateTime As DateTime
    Public Property Subject As String
    Public Property Details As String
    Public Property CompanyID As String
    Public Property ContactID As Int32
    Public Property OpportunityID As Int32
    Public Property AssignedTo As Int32
    Public Property AssignedBy As Int32
    Public Property ReminderDate As DateTime
    Public Property ReminderMinutes As Int32
    Public Property ActivityType As Int32
    <Range(1, 1000,
           ErrorMessage:="Value for {0} must be between {1} and {2}.")>
    Public Property ActivityStatus As Int32
    Public Property Priority As String
    Public Property EventType As Int32
    Public Property Options As Int32
    Public Property ActivityType2 As Int32
    Public Property CompletedPercent As Decimal
    Public Property EndDateTime As DateTime
    Public Property OrderID As Integer



    Shared Sub Save(Activity As Activity)

        'If Activity.ActivityStatus < 1 Then
        '    Throw New ArgumentOutOfRangeException("ActivityStatus", Activity.ActivityStatus, "Please Select an Activity Status")
        'End If

        MsgBox(Activity.ActivityStatus)

        ActivityData.Save(Activity.ID, Activity.StartDateTime, Activity.Subject, Activity.Details, Activity.CompanyID, Activity.ContactID,
                              Activity.OpportunityID, Activity.AssignedTo, Activity.AssignedBy, Activity.ReminderDate, Activity.ReminderMinutes,
                              Activity.ActivityType, Activity.ActivityStatus, Activity.Priority, Activity.EventType, Activity.Options,
                              Activity.ActivityType2, Activity.CompletedPercent, Activity.EndDateTime)

    End Sub

End Class

Open in new window

p-platerAuthor Commented:
It seems I need to Manually call the Validation, But I can't work out how to!

Should I be using
Validator.ValidateProperty or Validator.TryValidateProperty.

I can't find Any examples online in VB!
ste5anSenior DeveloperCommented:
E.g.

 
Imports System.ComponentModel.DataAnnotations

Public Class EntityValidationResult
    Public Property Errors As IList(Of ValidationResult)
    Public ReadOnly Property HasError As Boolean
        Get
            Return Errors.Count > 0
        End Get
    End Property
    Public Sub New(ByRef Optional AErrors As IList(Of ValidationResult) = Nothing)
        If AErrors Is Nothing Then
            Errors = New List(Of ValidationResult)
        Else
            Errors = AErrors
        End If
    End Sub
End Class

Public Class EntityValidator(Of T As Class)
    Public Function Validate(AEntity As T) As EntityValidationResult
        Dim validationResults = New List(Of ValidationResult)()
        Dim validationCtx = New ValidationContext(AEntity, Nothing, Nothing)
        Dim isValid = Validator.TryValidateObject(AEntity, validationCtx, validationResults, True)
        Return New EntityValidationResult(validationResults)
    End Function
End Class

Public Class ValidationHelper
    Public Shared Function ValidateEntity(Of T As Class)(AEntity As T) As EntityValidationResult
        Return New EntityValidator(Of T)().Validate(AEntity)
    End Function
End Class

Public Class Activity
    Public Property ID As Int32

    <Required(ErrorMessage:="Subject is required.")>
    <Display(Name:="Subject")>
    <MaxLength(255)>
    Public Property Subject As String

    <Required(ErrorMessage:="Details is required.")>
    <Display(Name:="lname")>
    <MaxLength(255)>
    Public Property Details As String
End Class

Module Module1
    Sub Main()

        Dim act As Activity = New Activity
        act.Subject = "Test"

        Dim validationResults = ValidationHelper.ValidateEntity(Of Activity)(act)
        Console.WriteLine(validationResults.HasError)
        For Each validationError In validationResults.Errors
            Console.WriteLine(validationError.ErrorMessage)
        Next validationError

        Console.WriteLine("Done.")
        Console.ReadLine()

    End Sub
End Module

Open in new window

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Jacques Bourgeois (James Burger)PresidentCommented:
I come back after a few days out and find out that you are talking about System.ComponentModel.DataAnnotations. I have never worked with DataAnnotations, but I do not see where they fit in the context of the discussion.

The System.ComponentModel is used to deal with controls and components, objects for which code can be automatically generated by using the ToolBox and the Properties window. Where, in the questions, is it about controls or components?

And according to the documentation for DataAnnotations, The System.ComponentModel.DataAnnotations namespace provides attribute classes that are used to define metadata for ASP.NET MVC and ASP.NET data controls. Once again, where in the discussion is it said that the question is about controls, of that it is an ASP.NET application.

From my understanding, it is a general question about dealing with exceptions in the different layers of a 3 Tier application, nothing specific about controls and ASP.NET.
ste5anSenior DeveloperCommented:
DataAnnotations are a feature to validate data of your POCO's. While it was developed primarily for use with EF or ASP.NET MVC models, you can use it standalone. So it gives you the ability to check for valid data of your POCO's in every layer. It's a tool. So you don't need to react on invalid data reported from deeper layers, cause you can test it always when you get one POCO as parameter. It helps to create a more defensive strategy. Avoiding exceptions as control flow is imho a good architectural approach.
p-platerAuthor Commented:
Well from what I can see it seems that DataAnnotations could be the better way to go as it has built in Validation for things like Email Addresses, URL's, ect.

Now can I do this when using DataAnnotations?
Add another Field to each Property (Use something like the <Display(Name:="Display Name")>)
Have this Field included in the ValidationResults Class

If the validationResults.HasError = True
Raise an Exception and Send the validationResults Class back up the stack to the UI.
Loop through the validationResults in the UI and Mark the Textboxes that have invalid values (Based on the "Display Name" added to the validationResults)

If this is Workable then I shouldn't need to fully validate in the UI - I can just call the Save in the BLL that runs this validation then Raises an exception which returns the validationResults to the UI to deal with.
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Visual Basic.NET

From novice to tech pro — start learning today.