Technical Questions Page
This page contains some technical questions that I've run into and would like answers to. If you fancy yourself an expert on the topic I'm asking about, I'd love to hear from you. Sign your edits with your email address. Thanks, I appreciate the help.
Question 1: In ASP.NET Web Services, how does one throw an exception from the server side to the (consuming) client side, and still maintain the server side stack trace right down to the line number of the offending line of code?
Look at this article: http://www.developer.com/net/csharp/article.php/10918_3088231_1?
The basic gist of the article is to catch the exception on the server side and log it, then wrap the exception in XML and re-throw it as a SoapException. The down side is that the client and server are now more coupled, since each has to have an awareness of the schema for the error information that is not inherent from the WSDL. This is of course quite contrary to the prinicples of SOA. --UnknownAuthor (Please come back and sign your name here)
Well that's not a bad article but it missed one of the details of my question. In fact I've already implemented much of what this article describes in my current SmartClient application. The detail that is missing is the "maintain server side stack trace". I suppose it amounts to taking the stack trace on the server side, representing that as XML that can be added to the <fault> element within the returning SOAP message and then (re)building an exception with all of that stack trace information buried in it as if it occured in the local process. If anyone runs across some code please send me a link. --CharlesMedcoff
Ok, now I've changed my mind about this. There really is no need to throw an exception with the stack trace from the server side to the client side. Instead the stack trace should be logged on the server side (in the Windows EventViewer for example) with some unique identifier (a GUID?) and the exception thrown to the client side should include that unique identifier so that the client side event can be associated with the server side exception. --CharlesMedcoff test
Question 2: When building a "business object" class, how can you implement custom attributes to provide "rules" to each member (or property) that could then bu utilized by your business tier? For example, the [First Name] and [Last Name] fields are Not Nullable. Something like:
public class Employee {
[ThisFieldIsRequired]
public string LastName {
get { return _lastName; }
set { _lastName = value; }
}
}
This is an interesting, and potentially complex answer because there is no one answer. I'm not going to get this in one take, but here is a start. First of all, the class Employee above, as is, is NOT a business object. In fact it drives me nuts when someone refers to such code as a business object. At this point, this class is nothing more than a behavior-less data transfer object or DTO as most people from the world of object-oriented modeling will tell you. Ok, apologies for the soapboxing but there it is. Now back to the problem at hand... One possible way of handling this, is to add the behavior directly to the Employee class. In the C# example here, one possible way of implementing this is to add logic to the LastName property to check for this business rule (hence it would become a real business object). For example, the setter could disallow setting of the value to null by throwing an exception. Now at least two other very interesting problems remain, what should the getter do? Well if having _lastName is truly an invalid state for the object, it must never be allowed to be set to null. We've taken care of that with the setter behavior described above, but what about construction/initialization? Well this means that the constructor must accept a parameter (among others) for the last name, and now allow a null value. Again one way of implementing this is to throw an exception out of the constructor. If we do this, then get getter implementation can remain as is. Now this is but one possible approach and there are several others. I think that the most important thing here is that whatever the implementation of this rule, that it be implemented once, in a non-presentation layer class (aka. this code needs to go into a .NET assembly/DLL and not an EXE for the presentation/GUI layer.). This will allow for several important things. 1) Testability - you can test the behavior in some automated fashion (such as NUnit) without relying on firing up the GUI to test your business rule (after all what do business rules have to do with presentation layers/GUI's). 2) Re-use - by decomposing your system in such a manner this business rule can be re-used. The first goal of such re-use is within the applications itself, the second other applications/systems. A great example of this is an application that I worked on recently that had some code for building a complete name from first name, initial, and last name fields in a database, into a single string object. In this application this "behavior" (a method) was duplicated over and over again in several WinForm classes within the app... bad, bad, bad. Again, two problems with this: had to run the GUI to test it, and duplication.
In summary A) behavior-less classes that hold data fields from the underlying data source (often databases) ARE NOT BUSINESS OBJECTS! B)The most important thing about implementing the business rules is not necessarily the details of how they are implemented, but rather the goals the implementation is to achieve and these are 1)testability and 2)re-use - minimally within the application.
I closing I strongly suggest taking a look at some books like Martin Fowlers Patterns of Enterprise Design and similar books that presents options for addressing such design problems. They don't provide specific solutions for the question presented (each situation is unique), but rather present several alternatives and advantages and disagvantages for each. --CharlesMedcoff (I may have more to say about this later).
Business Object, Schmizness Object. LOL. -Zark
I think naming is important - thus the differentiation and understanding between a business/domain object and a data transfer object etc. I didn't come up with these names, they are the names that have come about from object-oriented design luminaries such as Fowler, Martin, Booch, Larmen, etc. Note this definition on Wikipedia; especially the statement "Good business objects will encapsulate all of the data and behavior associated with the entity that it represents." --CharlesMedcoff
Chuck is right, there is no single answer. My initial thought is that having rules such as "Last Name cannot be null" seems like a validation responsibility rather than a business object responsibility. I understand this was just an example case, but I think this distinction is important. As the object model grows, it allows us to have more and more rules all in one spot (and we can refactor out common validation rules to super classes). So, we might end up with something like:
public class EmployeeValidator
{
[...code for constructor, etc...]
public void Validate(Employee employee)
{
if (employee.LastName == null)
{
throw new Exception("Last name is required");
}
}
}
public class Employee {
public string LastName {
get { return _lastName; }
set { _lastName = value; }
}
public void Save()
{
try
{
EmployeeValidator validator = new EmployeeValidator();
validator.Validate(this);
SaveToDB();
}
catch(Exception e)
{
[exception handling here]
}
}
}
The question I have now is:
Where does the line get drawn on "business rules" versus "validation rules"? My opinion is that a business rule would be something like: "If the last name begins with G, give that person a discount" whereas a validation rule would be: "Last names cannot be null". I'm sure it all depends on the context of the domain. --Rich
In reading the original question again, I realize that my comment is just a huge tangent. Sorry. --Rich
I'm no expert on this, but from what I can gather and what I've seen, you can create custom attributes such as "Required" by creating a new class that inherits from System.Attribute. In this example, it doesnt have to do anything because we're just going to end up checking whether or not a property has this attribute and whether or not that property has a value.
// AttributeUsage tells us what items in an object this attribute will apply to
[AttributeUsage(AttributeTargets.Property, AllowMultiple=true)]
public class RequiredFieldAttribute : System.Attribute
{
}
In our Employee class, we can now add the 'RequiredFieldAttribute' attribute to any fields we want.
public class Employee
{
private string _lastName;
[RequiredFieldAttribute]
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
}
Then, when we want to do the actual validation, we need to use reflection to see all of the properties that have this attribute and whether or not it has a value.
public void Save()
{
Validate();
// do insert
}
public void Validate()
{
// get all properties for this object
PropertyInfo[] properties =
this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
// loop through properties
foreach (PropertyInfo pi in properties)
{
object propertyValue = pi.GetValue(this, null);
bool isRequired = pi.GetCustomAttributes(typeof(RequiredFieldAttribute), false).Length > 0;
bool isMissing = (propertyValue == null) ? true : false;
if (isMissing)
{
throw new Exception(String.Format("Property {0} is required.", pi.Name));
}
}
}
We can test that this works by creating a unit test. The test code itself probably isnt the most ideal way to do it (ie: hardcoding an error message rather than catching a specific error type), but the idea is there.
[TestFixture]
public class RequiredFieldTests
{
[Test]
[ExpectedException(typeof(Exception), "Property LastName is required.")]
public void TestEmptyLastName()
{
Employee employee = new Employee();
// ignoring employee.LastName
// this will throw the expected exception
employee.Save();
}
}
If we put it at the top of the object model and use some fancy generics, we can easily validate required properties on anything in the domain. --Rich
This looks a lot like what I was looking for: http://msdn2.microsoft.com/en-US/library/system.componentmodel.dataobjectfieldattribute.aspx
--Greg