3
\$\begingroup\$

I'm creating a Web Api in .NET Framework that is connected to an ASMX service. No own logic, just pass through data.

Everything works as expected. But not sure if it's good. Have to repeat the same try/catch block in all controller actions.

SecurityController - send username and password and retrieve a session Id to use in subsequent calls:

public class SecurityController : ApiController
{
    public SecurityController(ILogger logger)
    {
        Logger = logger;
    }

    ILogger Logger { get; }

    [HttpPost]
    public IHttpActionResult StartSession([FromBody] StartSessionRequestModel requestModel)
    {
        var client = new SecurityFacade(); // from proxy class
        var requestMessage = new StartSessionRequest
        {
            Username = requestModel.Username,
            Password = requestModel.Password
        };

        try
        {
            var startSessionResponse = client.StartSession(requestMessage);

            return Ok(new
            {
                startSessionResponse.SessionId
            });
        }
        catch (SoapException ex)
        {
            return BadRequest(ex.Code.Name);
        }
        catch (Exception ex)
        {
            Logger.Error(ex);
            return InternalServerError(ex);
        }
    }
}

Output when entered wrong user/password

{
    "Message": "InvalidUsernameAndPasswordPair"
}

On the client (Xamarin), using RestSharp.

var request = new RestRequest("security/startsession");
request.AddParameter("Username", Username);
request.AddParameter("Password", Password);
restClient.PostAsync<StartSessionResponse>(request, (response, handle) =>
{
    if (response.IsSuccessful)
    {
        var sessionId = response.Data.SessionId;

        // todo
    }
    else
    {
        var responseMessage = JsonConvert.DeserializeAnonymousType(response.Content, new { Message = "" });

        if (responseMessage.Message == "InvalidUsernameAndPasswordPair")
        {
            // todo: show "invalid user/password" error
        }
        else
        {
            // todo: show other nicely worded error
        }
    }
});
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Handling exceptions within the action is a cross-cutting concern that can be extracted and placed into one of the available extensiblity points. This will greatly reduce the repeated exception handling in all controller actions.

Reference Global Error Handling in ASP.NET Web API 2

Exception handlers are the solution for customizing all possible responses to unhandled exceptions caught by Web API.

With that the above controller in your example can be refactored to something as simple as

public class SecurityController : ApiController {
    private readonly ISecurityProxy client; // abstraction of proxy class

    public SecurityController(ISecurityProxy client) {
        this.client = client;
    }

    [HttpPost]
    public IHttpActionResult StartSession([FromBody] StartSessionRequestModel requestModel) {
        if (ModelState.IsValid) {
            var requestMessage = new StartSessionRequest {
                Username = requestModel.Username,
                Password = requestModel.Password
            };
            StartSessionResponse result = client.StartSession(requestMessage);
            return Ok(result);
        }
        return BadRequest(ModelState);
    }
}

Why so lean?

The SecurityFacade has been abstracted out as classes should depend on abstractions and not be tightly coupled to concretions as the original example implies that dependency injection is already being used.

The ILogger and exception handling is no longer needed in the controller as that is a cross-cutting concern that can be moved to an ExceptionHandler

public class WebApiExceptionHandler : ExceptionHandler {
    private readonly ILogger logger;

    public WebApiExceptionHandler(ILogger logger)
        : base() {
        this.logger = logger;
    }

    public override void Handle(ExceptionHandlerContext context) {
        var innerException = context.ExceptionContext.Exception;

        // Ignore HTTP errors.
        if (innerException.GetType().IsAssignableFrom(typeof(System.Web.HttpException))) {
            return;
        }

        //Handle SOAP errors as bad requests
        IHttpActionResult result = null;
        if (innerException.GetType().IsAssignableFrom(typeof(SoapException))) {
            var soapException = innerException as SoapException;
            result = new BadRequestResult(soapException.Code.Name, context.Request);
            context.Result = result;
            return;
        }

        //Handle and log general errors
        logger.Error(innerException);
        result = new InternalServerErrorResult(context.Request);
        context.Result = result;
    }

    private class BadRequestResult : IHttpActionResult {
        public BadRequestResult(string message, HttpRequestMessage request) {
            Message = message;
            Request = request;
        }

        public HttpRequestMessage Request { get; private set; }
        public string Message { get; private set; }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken) {
            var model = new {
                Message = Message
            };
            var response = Request.CreateResponse(HttpStatusCode.BadRequest, model);
            return Task.FromResult(response);
        }
    }
}

An exception handler indicates that it has handled an exception by setting the Result property to an action result (for example, an ExceptionResult, InternalServerErrorResult, StatusCodeResult, or a custom result). If the Result property is null, the exception is unhandled and the original exception will be re-thrown.

the handler can now be added as a global error handler for API calls.

The following extension method is added to simplify adding the handler when configuring the Web API at start up.

public static class WebApiConfig {

    public static void Register(HttpConfiguration config) {

        //... code removed for brevity

        config.ReplaceExceptionHandler();
    }

    /// <summary>
    /// Global Exception handling
    /// </summary>
    /// <remarks>
    /// There must be exactly one exception handler. 
    /// (There is a default one that may be replaced.)
    /// </remarks>
    public static HttpConfiguration ReplaceExceptionHandler(this HttpConfiguration config) {
        var errorHandler = config.Services.GetExceptionHandler();
        if (!(errorHandler is WebApiExceptionHandler)) {
            var service = config.Services.GetService(typeof(WebApiExceptionHandler));
            config.Services.Replace(typeof(IExceptionHandler), service);
        }
        return config;
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.