Just knowing MVC as defined in the books and arguing over it does not make any sense, does it?
It is when you know how to put the pattern to practise that will satisfy you that you know MVC or any design pattern/architecture and this is where most of the patterns example fail to do (believe me, most of the examples that abound 'are meant' to make sense to only similar, like-minded people (not necessarily to mean technically oriented)...;P here is a simple step-by-step example on how to simplify your UI notifications (not necessarily the heavyweight, 'technical' way).
Simply put, a front-end (since this is in C# - a Windows Form), a business layer (BusinessClass.cs) and a database class. (DataClass.cs)
Let us call it as an Orders utility with one of the specs, "Log as error and display on the user's error log so that the user can copy/paste it to a text editor, if no order is present in the results returned from the database query.
Now, let us implement this spec.
What are the mechanisms that you have? An exception? But this is no exception, in the true sense of the word. So? Return a resultset that if contains no rows satisfies our need.
Before we go further, let me tell you that you should have - knowledge of creating custom exceptions, how to use/create custom event handlers, the 3.5 way. To continue...
Exceptions
Exceptions, in programming, are any deviations, during the normal execution of a program, that need to be either taken care of or dealt with in some way to enable graceful execution of the software program.
An exception, in C#, is implemented as a base class called System.Exception and when it occurs is handled by the CLR (Common Language Runtime). This means that any object, let us say the BusinessClass or DataClass, in our example scenario, if does not handle the exception through the "try...catch..finally" mechanism, will throw the exception all the way up the stack. So, if the DataClass was to throw an exception, it will be handled by the preceding class and since the preceding objects cannot handle exceptions of other objects can only handle it through the base Exception class or not at all, in which case the program will terminate with a runtime exception!
But, if handled as a base System.Exception, our requirement of having to inform the user as to what kind of an exception it is cannot be satisfied because, as mentioned in the previous paragraph above, other objects cannot handle other objects exception. In simple terms, this means if "Object A" throws a "SQLConnection error", Object B can handle it as
SqlConnection conn=new SQLConnection(connectionString);
try{
conn.Open();
}
catch (SQLConnection sqlc){
}
finally{
conn.Close();
}
only if SQLConnection is a type of an Exception or, in programming terms, derives from System.Exception as shown below,
public class SQLConnection :
Exception{
public SQLConnection(string exceptionMessage):base(exceptionMessage){
}
}
Hark back to the beginning of this post. Is "
Log as error and display on the user's error log so ..." an exception ? No. It is a custom exception. So, we handle it by defining a custom exception class called "NoOrdersException" as below.
using System;
// The class
public class NoOrdersException: Exception{
// the data that he exception will return as a string
private string stringMessage="Default message";
// the constructor of the exception class that will pas the message to the base exception class
// and initialize this class' message
public NoOrdersException(string exceptionMessage):base(exceptionMessage){
stringMessage=exceptionMessage;
}
// the property of the class that will return this exception message that will override the base Message
// property
public override readonly string Message{ get{return stringMessage;}}
}
Does this solve our requirement completely? No. because the program still has to notify the user. This can be done only through the UI (the Window on which the user awaits the result).
But, the front-end, the user input screen has nothing to do with the database class that interacts with the database. All it needs to know is how to pass the input from the user and receive the result.
Another problem that you will tackle is to understand what this custom exception is meant for; as you know, exceptions can be thrown and there is an inbuilt mechanism to handle exceptions but "NoOrders" is
not really an exception in the execution process of the application rather, it is an exception in the data flow. You can understand it as, when you dip your hand inside a Popcorn bag but find the bag is empty or "NoPopcorns".
As of now, the above translates into a design like
with the BusinessObject class not doing anything. Do we really need the class in the design?
Yes, because if we define business rules in either the view or the data object, it is a deviation of OO principles - a View's purpose is just to enable correct user interaction and the data class to enable data related transactions or actions. Another reason why a separate Business Object class makes sense is because business rules may change as per market or client or customer or other needs and keeping it as separate logical unit makes sense.
All this is common knowledge so what does MVC or this post aim to provide as a value add-on?
As per the previous analysis on why we need the
BusinessObject, let us modify the code to use this class to to establish the business rules with the design representing the code as below.
This design represents our requirement to notify the UI in the event of "NoOrders" correctly but is not a correct representation of the code.
It ia contrived because as per programmatic rules, a return value of a called method can only be returned to the calling object so the design representing the code should actually look like below, which it does.
This design represents the code (
DataClass and the
FormClass) correctly but is there a way to verify that it is correct and not contrived? Yes, by verifying it through tests, requirements and performance but right now, let us re-look at the problem - throwing a custom exception that is not an exception. Why do we need to do it? Because it is the simplest mechanism to notify the UI from an object that is not known to it, as shown above. And to accomplish this in the OO way, we will use an event.
Event and Event Handlers
private void OnNoOrdersErrorEvent(object sender,ErrorEvent ev)
{
txtErrorLog.Text=ev.errorMessage;
}
public event myEventHandler<ErrorEvent> ErrorLogEvent;
delegate void myEventHandler<T>(object sender, ErrorEvent ev);
with the ErrorEvent defined as below:
public class ErrorEvent : EventArgs
{
string eventMessage;
public ErrorEvent(string evMsg)
{
eventMessage = evMsg;
}
public string errorMessage
{
get
{
return eventMessage;
}
}
}
The parameter is the 3.5 version that is simpler than the previous way of handling events though you still need to wire up the event with the event handler as earlier:
private void Form1_Load(object sender, EventArgs e)
{
ErrorLogEvent += new myEventHandler(OnNoOrdersErrorEvent);
}
Now, let us look at how the data class notifies the "NoOrdersException."
public class DatabaseActions
{
private static SqlConnection sqlConnection;
private static SqlCommand sqlCommand;
private static SqlTransaction sqlTransaction;
private static DataSet dSet;
private static int businessRule;
private static DataView dView;
public static void FetchOrderTransaction(string sqlQuery)
{
dSet = new DataSet();
SqlDataAdapter da = new SqlDataAdapter(sqlQuery, sqlConnection);
da.SelectCommand.Transaction = sqlTransaction;
da.Fill(dSet, "OrderDetails");
EnumerableRowCollection<DataRow> ordersQuery = from row in dSet.Tables["OrderDetails"].AsEnumerable()
orderby row.Field("OrderNumber")
select row;
DataView orderDetailsView = ordersQuery.AsDataView();
if (orderDetailsView.Count <= 0)
{
sqlConnection.Close();
throw new NoOrdersException("No order exists!");
}
else
{
dView = orderDetailsView;
}
}
private class NoOrdersException : Exception
{
private string msg;
public override string Message
{
get
{
return msg;
}
}
internal NoOrdersException(string msgException)
: base(msgException)
{
msg = msgException;
}
}
...
}
Much of the code above is meant for the example purpose and can be used with your own database as is
The question now is, why can we not raise the noOrdersEvent directly from here?
The reason is - design.
The exception or the event of no orders occurs in the data class and the result is subscribed by the UI class. If we raise the event from the data class, the design will look like
Not very elegant because there is another flow for the orders present, which will mean that the data class becomes attached to two objects. What is wrong in doing so? It will mean that testing the data class will be difficult.
So, either we declare the event in the UI class and raise the event from the data class but for which we need a handle to the UI in the data class (totally unnecessary) or just throw the exception from the data class, not handle it anywhere till it reaches up the stack to the UI class and then raise the event!
But why raise an event, when we already have the "catch..." mechanism? Because there could be another data flow exception, "MissingOrders" and because, the exception caught in the UI class will be of the base Exception type, the UI class will have no way of knowing which kind of exception occurred, plus because
neither being exceptions may need to be handled in their own way.
But more importantly, other factors like thread safety, testability and simplicity in design are the real reasons!
|
An artist's impression of how it looks from the outside - only representative. :D |
More to follow...and
Webbased MVC and the challenges that MVC solves