Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I pass service layer validation messages back to the caller?

I've done alot of research, including here on SO, and I can't seem to find clear direction. I currently have an ASP.NET MVC3 application, with a service layer that sits on top of a repository.

In my service layer, I have functions such as:

public class MyService{

    public void CreateDebitRequest(int userId, int cardId, decimal Amount, .... )
    {
    //perform some sort of validation on parameters, save to database
    }

    public void CreateCreditRequest(.....)
    }
        //perform some sort of validation on parameters, save to database
    }

    public void CreateBatchFile()
    {
        //construct a file using a semi-complex process which could fail
        //write the file to the server, which could fail
    }


    public PaymentTransaction ChargePaymentCard(int paymentCardId, decimal amount)
    {
        //validate customer is eligible for amount, call 3rd party payments api call,
        //...save to database, other potential failures, etc.
    }

}

I've seen people say that parameter validation isn't very exceptional, and so throwing an exception is not very fitting. I also don't love the idea of passing in an out paramater, such as a string, and checking for an empty value. I've considered implementing a ValidationDictionary class, and making it a property of any given service class (it would contain an IsValid boolean, and a List of error messages, and could be checked after any given function call in the service layer to see how things went). I could check the ValidationDictionary status after running any given function:

var svc = new MyService();
svc.CreateBatchFile();
if (svc.ValidationDictionary.IsValid)
    //proceed
else
   //display values from svc.ValidationDictionary.Messages...

The thing I don't like about this is that I would have to update it for every service layer function call, to avoid having it retain old values (if I chose not to use it for many or most functions, one would still expect it to have a meaningful or null value after running any given function). Another thing I've considered is passing in the ValidationDictionary for each function call that might have detailed validation information, but then I am back to using an out parameter...

Do any of you have recommendations? I can't seem to figure out any clean way of doing this. Sometimes returning null for a function is enough information, but sometimes I'd like a little more validation information passed back to the caller. Any advice would be appreciated!

Edit to clarify: My service layer is not aware that it is an MVC application that is consuming it. The service layer just has certain public functions such as CreateBatchFile() or AddDebitRequest(). Sometimes returning null is enough for the consumer (in this case a controller, but could be something else) to know what happened, and sometimes the consumer would like some more information from the service layer (maybe to pass along to ModelState if the consumer is a controller). How do I bubble this up from the service layer itself?

like image 475
Josh Avatar asked Apr 11 '12 16:04

Josh


1 Answers

This is what I do. Have a class for your validation, and instead of passing parameters pass a view model. So in your case something like this, where ValidationResult is just a simple class w/ MemberName and ErrorMessage properties:

public class DebitRequestValidator{

  public IEnumerable<ValidationResult> Validate(DebitRequestModel model){

    //do some validation
    yield return new ValidationResult {
      MemberName = "cardId",
      ErrorMessage = "Invalid CardId."
    }
  }  

}

Then create a controller extension method to copy these validation results to the model state.

public static class ControllerExtensions
{
    public static void AddModelErrors(this ModelStateDictionary modelState, IEnumerable<ValidationResult> validationResults)
    {
        if (validationResults == null) return;

        foreach (var validationResult in validationResults)
        {
            modelState.AddModelError(validationResult.MemberName, validationResult.ErrorMessage);
        }
    }
}

Then in your controller do something like

[HttpPost]
public ActionResult DebitRequest(DebitRequestModel model) {
  var validator = new DebitRequestValidator();
  var results = validator.Validate(model);
  ModelState.AddModelErrors(results);
  if (!ModelState.IsValid)
    return View(model)

  //else do other stuff here
}

Then in your view you can display errors like normal.

@Html.ValidationMessageFor(m => m.CardId)
like image 64
Chad Avatar answered Sep 28 '22 05:09

Chad