First off, let me say that this article pretty much changed how I program, for the better. Until I read this article, I was a spaghetti programming master --- if there were awards for crappiest, least organized, and impossible to read programming, I would have been world champion. But that article taught me to utilize classes (mind-blowing, I know), rather than just copy/paste the same logic around to the necessary pages. With that in mind, I modeled my application (described below) on the examples found in that article. I highly recommend that article for anyone else who's looking to make the jump from newb coding via facerolling on the keyboard to newb coding with some thought behind it.
I've been simultaneously building upon and maintaining this web application for a year now, and I feel that while the layered approach has helped me to stay more organized, I'm not utilizing very much (if any) extra power from having layers.
In my web application Project, I have a folder I created called Classes. The structure looks like this:
/Classes
/ClientManagement
/DataAccess
ClientDB.cs
/Logic
ClientManager.cs
/Objects
Client.cs
The above is extremely abbreviated (for example, ClientManagement actually has about 20 Objects, 20 Managers, and 20 DBs), and there are other base folders like EmployeeManagement, InventoryManagement, etc. Each "Management" base folder corresponds to a schema in the database, so that for example, in my SQL Server database, I have [myDatabase].[ClientManagement].[Clients].
Each class in the Objects folder looks something like:
public class Client
{
//fields
private int? _clientID = null;
private string _clientName = null;
//properties
public int? ClientID
{
get { return _clientID; }
set { _clientID = value; }
}
public int? ClientName
{
get { return _clientName; }
set { _clientName = value; }
}
//constructor
public Client()
{
}
}
Once again, very abbreviated here. I am aware of auto-implemented properties in C#, I just chose to manually implement them.
Upon Object instantiation, I set everything to null. It is easier to check for null than to check for "empty" or "zero" values, in my opinion. This way when it comes time to validate an object before saving it to a database, it's very black and white --- if any field that does not allow nulls in the database is found to be null, throw an error, else continue on to save.
Next up, here's what a Manager (Logic) class looks like:
public static class ClientManager
{
public static Client GetItem(int? clientID)
{
return ClientDB.GetItem(clientID);
}
public static ClientList GetList()
{
return ClientDB.GetList();
}
public static int? Save(Client incomingClient)
{
Validate(incomingClient);
return ClientDB.Save(incomingClient);
}
public static void Validate(Client incomingClient)
{
if (incomingClient.ClientName == null)
{
throw new Exception(@"Cannot save a Client without entering a Client Name.");
}
}
}
As you can see, most of the calls to the manager/logic class are simply forwarded to the DB class. This is where I feel I could use improvement by actually doing something useful in the Manager class, I just don't know what.
The Validate() method is a last resort check. There is already validation checking in each page's code behind. There really is no apparent way that you could ever reach the Validate() method in the Manager class, but it's there anyway because you never know. I've purposely chosen to have it throw a hard exception, because if somehow the user reaches that point, something is seriously wrong somewhere and needs to be addressed immediately.
A DB/DataAccess class looks like this:
public static class ClientDB
{
public static Client GetItem(int? clientID)
{
Client selectedClient = null;
//make db connection, call stored procedure, fill selectedClient.
return selectedClient;
}
public static ClientList GetList()
{
ClientList list = null;
//make db connection, call stored procedure, fill list.
return list;
}
public static int Save(Client incomingClient)
{
int result;
//make db connection, call stored procedure, get result code
return result;
}
private static Client FillDataRecord(IDataReader datareader)
{
Client theclient = new Client();
theclient.ClientID = datareader.GetInt32(datareader.GetOrdinal("ClientID"));
theclient.ClientName = datareader.GetString(datareader.GetOrdinal("ClientName"));
return theclient;
}
}
As you can see, my web application is broken up into different layers which makes organization a breeze. But, I feel that currently I'm not utilizing the power of layers --- the logic layer does nothing at all but just forward the calls to the DB layer. I think part of my problem is that I simply don't know what I CAN do that is useful.
Currently my system works and my goals are achieved. Some people might say, "then what's the problem?" And while that makes me happy (it means I've done my job with some degree of success, horray), I want to know more advanced techniques.
In the article I originally linked to above, it uses the example of checking roles in the logic layer. In my case, I don't need to do that. We're checking roles pretty much on every action the user takes at the page level.
If I can't figure out something else useful to do at the logic layer, I am thinking about cutting out the logic layer all together, since all it does is forward calls anyway. This would eliminate some .cs files and make it even easier to navigate/maintain.
Ideas, thoughts? What can I do better? What has worked for you? Are there any things related to layered web applications that you know now that you wish you knew when you first started?
null
is the default for those types. It's unnecessary (re) initialization and only serves to decrease performance. \$\endgroup\$