Sam Gentile

Sponsors

The Lounge

Wicked Cool Jobs

Syndication

News

  • This Blog has moved to samgentile.com. If you have subscribed via FeedBurner, you do not have to do a thing, feed has been re-pointed

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
How to use the Service BAT, Exception Handling and Logging Blocks with WCF and CAB

So, in the last post, I talked about our problems identified during the CTP. This second part is an instructive solution-oriented post about what we did to fix things.

So, as I went off on vacation for a week, I pretty much obbessed on the issues during the CTP and proper exception handling! -) Truth be told, we had done some correct things with FaultContracts, but we had not systematically done proper SOA design with our Indigo Services. The question is what are the right practices in an emerging technology like WCF? I knew some of them like FaultContracts but where do you turn to? You turn to what is emerging to be the number one group in Microsoft, Patterns and Practices for Architectural and Development Guidance on this.

The particular piece of guidance is the Service Factory BAT which just offically shipped (See the link for more info). Unfortunately, it shipped with guidance for ancient technologies like ASMX and WSE-)). But fortunately, there are betas of versions for WCF. The Service BAT has excellent guidance on things like the Exception Shielding Pattern and applying it in the context of WCF:

Context:

A client is accessing a Web service. The Web service is designed according to the principals of service orientation, which ensures that the boundaries of the service are explicit, and requires that exception information related to the internal implementation of the service is managed within the service.

Solution

Use the Exception Shielding pattern to sanitize unsafe exceptions by replacing them with exceptions that are safe by design. Return only those exceptions to the client that have been sanitized or exceptions that are safe by design. Exceptions that are safe by design do not contain sensitive information in the exception message, and they do not contain a detailed stack trace, either of which might reveal sensitive information about the Web service's inner workings.

The Service BAT also talks about the following for "Unexpected exceptions and errors:":

To extend control over error handling and error reporting in WCF so that your services can intercept errors, perform processing, and affect how errors are reported to the client, implement the IErrorHandler and IServiceBehavior interfaces:

  • The IErrorHandler interface in the System.ServiceModel.Dispatcher namespace allows you to explicitly control the SOAP fault generated and whether to send it back to the client, and optionally perform custom error processing such as logging. It consists of the following two methods:
    • The ProvideFault method allows the fault reported for an error to be replaced or suppressed.
    • The HandleError method allows error processing to take place in the event of an error and controls whether additional error handling can execute.
  • The IServiceBehavior interface in the System.ServiceModel.Description namespace provides a mechanism to modify or insert custom extensions across an entire service. It consists of the following methods:
    • The ApplyDispatchBehavior method provides the ability to change run-time property values or insert custom extension objects such as error handlers.
    • The AddBindingParameters method provides the ability to pass custom data to binding elements to support the contract implementation.
    • The Validate method provides the ability to inspect the service host and the service description to confirm that the service can run successfully. For example, you can enforce that the contract have a SOAP fault with a specific DetailType."

So, in the team's Retrospective, which was going on while I was on vacation after the CTP thing, everyone knew we had screwed up and we needed to do better about errors and logging. So some time was allocated for "investigating logging solutions." But as I mentioned before, I was obbessed with this issue and doing a full comprehensive solution with SO principles as I knew them and advocated in the Service BAT and incorporating a full slotted Architecture on the orthogonal, cross-cutting concerns like Logging, Exception Management, etc along with our adoption of CAB and SCBAT. I had a few phone conversations with Steve while I was on vacation (poor Steve!) and we came to agreement on what needed to be done.

I came back Thursday, and Steve and I sat down immedietly to pair for the day and as quick as possible make our ideas come to friuition without taking down the main velocity of the rest of the team which was on an very important business value Iteration. One of the things I most appreciate about Steve is that not only his he one of the best Code Developers I have ever seen in my 23 years in the business, but he is so quick with major architectural patterns so we move so wicked fast together! We always get in that pairing zone and we can make amazing things happen like what we did throughout the whole product in less than 8 hours of work!!

So, the first step was to bring in the Logging Block out of the Enterprise Library and incorporate into into our large overall solution. All of the EntLib blocks come with a dynamite Ent Lib Configuration tool that enables you to configure the neccesary configuration settings. The only hick-up was that we needed to strong-name the EntLib assemblies and while that is encouraged and documented in the documentation, it broke the Configuration tool which was set-up to work with the "raw" EntLib assemblies that were partially specified. After googling, and talking to my friend Peter Provost, we tried to strong-name the assemblies in the directory that the Config tool resided. This then crashed the tool. The solution was we had to strong-name every single one of the EntLib assemblies in that directory and then the tool worked correctly. Tom Hollander blogged somewhere that they are looking at making the strong-name experience much easier.

The killer feature of the Logging and Exception Management Blocks, that NLog and Log4net do not have, is Policies. Policies are really useful and cool . An exception policy has a name and is made up of a set of exception types to be processed by that policy. Each exception type has a list of handlers that are executed sequentially. An application can have multiple policies. This lets different parts of the application to handle the same exception types differently. You then, as an Architect, can specify different Exception Hadling policies for each of the layers. For instance, we wanted our WCF Services layer to have a very different policy than our CAB Smart Client layer.

Logging as simple as this example:

LogEntry log = new LogEntry();
log.EventId = 300;
log.Message = "Sample message";
log.Categories.Add("UI Events");
log.Severity = TraceEventType.Information;
log.Priority = 5;

There are many different features of the Logging Block that I don't have time to go through here. Once, we had basic logging, we created Exception Polocies and then implemented those, while using a version of this for a Generic Global Handler for anything that fell through:

public static void HandleException(Exception ex, string policy)
{
    Boolean rethrow = false;
    try
    {
        rethrow = ExceptionPolicy.HandleException(ex, policy);
    }
    catch (Exception innerEx)
    {
        string errorMsg = "An unexpected exception occured while " +
            "calling HandleException with policy '" + policy + "'. ";
        errorMsg += Environment.NewLine + innerEx.ToString();
        MessageBox.Show(errorMsg, "Application Error",
            MessageBoxButtons.OK, MessageBoxIcon.Stop);
        throw ex;
    }
    if (rethrow)
    {
        // WARNING: This will truncate the stack of the exception
        throw ex;
    }
    else
    {
        MessageBox.Show("An unhandled exception occurred and has " +
            "been logged. Please contact support.");
    }
}
static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
    HandleException(e.Exception, "UI Policy");
}
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    if (e.ExceptionObject is System.Exception)
    {
        HandleException((System.Exception)e.ExceptionObject, "Unhandled Policy");
    }
}
Then, onto WCF. We used the ExceptionShielding attribute on our contracts similar to what they do here (example shown here is ASMX from the BAT):

[WebService(Namespace = "http://CustomerProducts/GlobalBank.CustomerProducts.ServiceContracts/2006/06", Name = "CustomerProducts")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1, EmitConformanceClaims = true)]

[ExceptionShielding("CustomerProductsPolicy")]

public class CustomerProducts : ICustomerProducts

public class CustomerProducts : ICustomerProducts

[ExceptionShielding("CustomerProductsPolicy")]

public class CustomerProducts : ICustomerProducts

public class CustomerProducts : ICustomerProducts

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1, EmitConformanceClaims = true)]

[ExceptionShielding("CustomerProductsPolicy")]

public class CustomerProducts : ICustomerProducts

public class CustomerProducts : ICustomerProducts

[ExceptionShielding("CustomerProductsPolicy")]

public class CustomerProducts : ICustomerProducts

public class CustomerProducts : ICustomerProducts

WebService(Namespace = "http://CustomerProducts/GlobalBank.CustomerProducts.ServiceContracts/2006/06", Name = "CustomerProducts")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1, EmitConformanceClaims = true)]

[ExceptionShielding("CustomerProductsPolicy")]

public class CustomerProducts : ICustomerProducts

public class CustomerProducts : ICustomerProducts

[ExceptionShielding("CustomerProductsPolicy")]

public class CustomerProducts : ICustomerProducts

public class CustomerProducts : ICustomerProducts

WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1, EmitConformanceClaims = true)]

[ExceptionShielding("CustomerProductsPolicy")]

public class CustomerProducts : ICustomerProducts

public class CustomerProducts : ICustomerProducts

ExceptionShielding("CustomerProductsPolicy")]

public class CustomerProducts : ICustomerProducts

public class CustomerProducts : ICustomerProducts

{

#region ICustomerProducts Members

public GetAccountSummaryResponse GetAccountSummary(GetAccountSummaryRequest request)

{

CustomerProductsAdapter adapter = new CustomerProductsAdapter();

return adapter.GetAccountSummary(request);

}

}

return adapter.GetAccountSummary(request);

}

}

{

CustomerProductsAdapter adapter = new CustomerProductsAdapter();

return adapter.GetAccountSummary(request);

}

}

return adapter.GetAccountSummary(request);

}

}

public GetAccountSummaryResponse GetAccountSummary(GetAccountSummaryRequest request)

{

CustomerProductsAdapter adapter = new CustomerProductsAdapter();

return adapter.GetAccountSummary(request);

}

}

return adapter.GetAccountSummary(request);

}

}

{

CustomerProductsAdapter adapter = new CustomerProductsAdapter();

return adapter.GetAccountSummary(request);

}

}

return adapter.GetAccountSummary(request);

}

}

ICustomerProducts Members

public GetAccountSummaryResponse GetAccountSummary(GetAccountSummaryRequest request)

{

CustomerProductsAdapter adapter = new CustomerProductsAdapter();

return adapter.GetAccountSummary(request);

}

}

return adapter.GetAccountSummary(request);

}

}

{

CustomerProductsAdapter adapter = new CustomerProductsAdapter();

return adapter.GetAccountSummary(request);

}

}

return adapter.GetAccountSummary(request);

}

}

public GetAccountSummaryResponse GetAccountSummary(GetAccountSummaryRequest request)

{

CustomerProductsAdapter adapter = new CustomerProductsAdapter();

return adapter.GetAccountSummary(request);

}

}

return adapter.GetAccountSummary(request);

}

}

CustomerProductsAdapter adapter = new CustomerProductsAdapter();

return adapter.GetAccountSummary(request);

}

}

return adapter.GetAccountSummary(request);

}

#endregion

}

This enabled us to tie into Exception Policies and use priorities as I talked about earlier.

Well, I have been at this 2 and 1/2 hours and I did want to run, so there will be a Part 3, to go deeper-).

Technorati Tags: , , , , , , , ,
 

kick it on DotNetKicks.com

Posted Sat, Aug 5 2006 8:50 AM by Sam Gentile

[Advertisement]

Devlicio.us