WCF service contract and error handling

949 views Asked by At

I'm trying to host a service that can be used as a proxy/gateway to invoke various other services in a generic manner. This service will take parameters that identifies a service to invoke and a payload string it simply passes on to "the implementation". The "gateway" takes care of authentication of the external client and provides the identity of the user and other "context" information to "the implementation".

I want external clients that provide an invalid request to get an error indicating as much. But since the "gateway" service has no idea what the requests (or responses) should look like, this has to rely on the "real" service giving a response that reliably indicates "bad request". An error message (human-readable description of what's wrong with the request) also needs to be provided. There are some cases where "the implementation" has it's own authorization logic, so similarly it must also be able to indicate "not authorized", something I thought HttpException with status 403 could nicely represent.

All implementators will be written in .net as WCF services.

I thought I could get away with specifying the service contract and have the implementations throw HttpException with status code 400 or 403 to provide error information back to the "gateway" - that it would not be necessary to specify a fault contract (isn't there some generic SOAP fault, and doesn't this allow at least an error code and a message string??).

Now I've tried to build a POC. The "normal" case where the request is valid and the implementation returns normally it works fine. But I can't see any way to obtain the error information when things go wrong.

In a console app to test one service implementation:

private string GetResponse(string req)
{
    try
    {
        using (var proxy = new MapperProxy())
        {
            return proxy.Map(null, req);
        }
    }
    catch (FaultException fe)
    {
        int status = GetStatus(fe);
        switch (status)
        {
            case 400:
                return "400 BAD REQUEST - " + fe.Message;

            case 403:
                return "403 FORBIDDEN - " + fe.Message;

            default:
                return "501 INTERNAL SERVER ERROR";
        }
    }
    catch (Exception ex)
    {
        return "Exception caught: " + ex.ToString();
    }
}

Assume the WCF service threw HttpException with status code 400. (The service implementation is configured to "include exception details in fault".) How can GetStatus(FaultException ex) be implemented?

1

There are 1 answers

0
The Dag On

I found the answer. I can use the generic SOAP faults by having the implementors throw SoapFaultException indicating a FaultCode and a message. I should probably set Action and the properties to indicate if it was a client or server fault, but this works:

Server-side:

public string Map(MapperContext context, string payLoad)
{
    string verb = GetFirstWord(payLoad).ToUpper();

    switch (verb)
    {
        case "ECHO":
            return payLoad.Substring(verb.Length).TrimStart();

        case "COP":
            throw new FaultException("Only cops may use this service.", new FaultCode("FORBIDDEN"));

        case "BUG":
            throw new NullReferenceException();
    }

    throw new FaultException("Invalid request; first word must be 'ECHO', 'COP' or 'BUG'.", new FaultCode("BAD REQUEST"));
}

private string GetFirstWord(string request)
{
    for (int i = 0; i < request.Length; i++)
    {
        if (char.IsWhiteSpace(request[i]))
        {
            return request.Substring(0, i);
        }
    }

    return request;
}

Modified client (my console app to test it):

private string GetResponse(string req)
{
    try
    {
        using (var proxy = new MapperProxy())
        {
            return proxy.Map(null, req);
        }
    }
    catch (FaultException fe)
    {
        switch (fe.Code.Name)
        {
            case "BAD REQUEST":
                return "400 BAD REQUEST - " + fe.Message;

            case "FORBIDDEN":
                return "403 FORBIDDEN - " + fe.Message;

            default:
                return "501 INTERNAL SERVER ERROR: " + fe.Message;
        }
    }
    catch (Exception ex)
    {
        return "Exception caught: " + ex.ToString();
    }
}

I think this is enough for my purposes.