Sunday, October 31, 2010

Multi-Tier Applications

Reading the MSDN forums, I can’t count the number of times I see posters putting all their eggs in one basket, so to speak. Their UI forms contain their Business logic AND their Data Access logic. Their DataSets are defined in the same UI project. Microsoft and Visual Studio, unfortunately, makes this too easy to do … what with all the drag-and-drop stuff from the Server Explorer directly onto a Form designer. I’m sorry, but this is really a great big no-no!!!

These activities should be broken up into multiple tiers, or layers. To simplify, think of three different projects/dlls in your solution. MyUI, MyBiz, and MyDataAccess (I'd actually throw in a 4th one, MyWebService, but let's not complicate matters at this point. Oh wait, I'd have a separate DataSet project too, but as I said, let's keep it simple for now). In your MyUI project, you'd have all the different forms that you plan to use in your UI. The same for MyBiz and MyDataAccess projects, all the different Biz classes and DataAccess classes.

So, to start, your form would be similar to this (to get your data when the form first opens):

using MyCompany.MyApp.Business.MyBiz;

namespace MyCompany.MyApp.WinUI.MyUI
{
public class MyForm : MyBaseForm
{
private long CustomerKey;
private MyDataSet dsData;
private CustomerBiz oBiz;

public MyForm(long key)
{
this.CustomerKey = key;
InitializeComponent();
this.FillData();
}

public void FillData()
{
// To simplify, I'm directly calling a Biz class.
// In reality, I use a Web Service here instead
// which in turn calls the Biz class.

oBiz = new CustomerBiz();
dsData = oBiz.GetCustomer(this.CustomerKey);
}
}
}

Now in your MyBiz project, you'd have a Biz class:

using MyCompany.MyApp.DataAccess.MyDataAccess

namespace MyCompany.MyApp.Business.MyBiz
{
public class CustomerBiz
{
private MyDataSet dsData;

public MyDataSet GetCustomer(long CustomerKey)
{
CustomerAccess oDA = new CustomerAccess();
this.dsData = oDA.GetCustomer(CustomerKey);

// if you have other Biz things to do to this customer
// do it here before returning the DataSet

return this.dsData;
}
}
}

And, lastly, in your MyDataAccess project, you'd have this class:

namespace MyCompany.MyApp.DataAccess.MyDataAccess
{
public class CustomerAccess
{
public MyDataSet GetCustomer(long CustomerKey)
{
// Here's where you'd put all the SqlCommand and DataAdapter stuff
// and fill your DataSet.

return dsData;
}
}
}

Now, that's the "simple" version, just to get the concept. Let's take it a step further:

We have 4 "layers" ... UI, Web Services, Business, Data Access.

These are more than 4 projects, because each layer is further broken down by module. Let's take the DataAccess layer as an example:

As in all our layers, there are DataAccess parent classes from which all DataAccess classes inherit. These parent classes have all the basic functionality needed for DataAccess and we consider it part of our "framework" ... it has it's own project. See my 3-part DataAccess series for more info about this:

http://geek-goddess-bonnie.blogspot.com/2009/09/dataaccess-part-i.html
http://geek-goddess-bonnie.blogspot.com/2009/10/dataaccess-part-ii.html
http://geek-goddess-bonnie.blogspot.com/2009/10/dataaccess-part-iii.html

Each module in our app has a separate DataAccess project. So, we'll have a DataAccess.Personnel project and a DataAccess.Inspection project, etc. and the classes in those projects inherit from the parent classes in the "framework" project. (As you probably know, these separate projects become separate .DLLs).

The Business layer got a little more complicated, but the architecture of it is the same. We actually have 2 Business layers ... server-side and client-side. The server-side classes remain on the server where they are accessed from the Web Services. The client-side classes are brought down to the client from the server to be used by the UI classes, but they can also be used on the server.

So, that's it in a nutshell. I think it's a good start, to get you thinking about how you should structure your application.

Saturday, October 23, 2010

Exception Handling

Today’s post is about having “global” Exception handling at the Application Level. What I mean by that is handling an exception at the very "top" of an application, in your MainForm, in case exception handling as been missed by the developer in other modules, forms or whatever. Something like this will do it:

[STAThread]
static void Main(string[] args)
{
// Creates an instance of the methods that will handle the exception.
CustomExceptionHandler eh = new CustomExceptionHandler();

// Adds the event handler to to the event.
Application.ThreadException += new ThreadExceptionEventHandler(eh.OnThreadException);
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

Application.Run(new MainForm());
}

// Creates a class to handle the exception event.
internal class CustomExceptionHandler
{
// Handles the exception event.
public void OnThreadException(object sender, ThreadExceptionEventArgs t)
{
DialogResult result = this.ShowThreadExceptionDialog(t.Exception);

// Exits the program after displaying message to the user.
// In Development mode, the developer will have more options (Abort/Retry/Ignore).
if (result == DialogResult.OK || result == DialogResult.Abort)
Application.Exit();
}

// Creates the error message and displays it.
private DialogResult ShowThreadExceptionDialog(Exception e)
{
DialogResult retval;
string msgUser = "An error occurred please contact the adminstrator with the following information:\n\n";
string msgDev = "An unhandled exception occurred (Abort/Retry/Ignore buttons are only displayed to Developers) \n\n";

string msgTrace = "Error: " + e.Message + "\n\n";
if (e.InnerException != null)
msgTrace += " " + e.InnerException.Message + "\n\n";
msgTrace += "Error Method: " + e.TargetSite + "\n\n" +
"Stack Trace: " + e.StackTrace;

if (System.Diagnostics.Debugger.IsAttached)
retval = MessageBox.Show(msgDev + msgTrace, "Application Error", MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Exclamation);
else
retval = MessageBox.Show(msgUser + msgTrace, "Application Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);

return retval;
}
}

You'll notice that in my example, I only allow the user to Retry or Ignore if they're the developer and debugging. I assume that in such a case, the developer will want to see right off the bat what went wrong and where, but if this happens to a real user, it's typically NOT a good idea to allow them to continue, as it may easily lead to further corruption of data. You can also expand on this class to add error-logging or whatever you wish.