Click here to Skip to main content
12,406,687 members (51,526 online)
Click here to Skip to main content
Add your own
alternative version

Stats

12.6K views
129 downloads
16 bookmarked
Posted

Validating Incoming JSON using Upida/Jeneva.Net

, 16 Jan 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
This article describes how to implement incoming JSON data validation

Introduction

In the previous article, I described how Jeneva.Net helps to solve common problems in implementing WebAPI-based web applications. This article shows how Jeneva.Net can also greatly simplify your validation routines.

*Note, this article assumes that you have gone through my previous article.

Background

If you apply the techniques from my previous article in your web application, it can greatly reduce the amount of custom coding. But there is still one very important and useful feature in Jeneva.Net - validation.

The implementation is pretty simple. First of all, you have to identify classes that require validation, usually these are domain classes. Secondly, you have to identify validation groups for each class - for example: class Client has two groups - validation before save and validation before update. This means that the same class - Client can be validated two different ways - for save and for update. Sometimes, you may require different validation groups - for example, Assign or Merge or whatever. And the last step is implementing a validator class. For example, the Client class will have validator - ClientValidator.

Implementing

Let's create these validation methods for the Client class. In order to follow all the SOLID principles, I will create a separate class - ClientValidator which will contain these validation methods. The main idea of the Jeneva-based validation is the following - You must create a new instance of the JenevaValidationContext class everytime you need to validate something. Everytime you find an error, you must register it in the context instance using its methods. Using the context instance ensures that each error message is tied with the corresponding property path. By the way, the context class already contains several simple validation routines, for example, it can check whether a particular field is null, or if it is present in JSON, it can check text length or collection's size, it can check regular expressions, etc. As you must know, Jeneva manages data deserialization, and it stores information about each JSON field, therefore later you can validate if a field was present in JSON, if it was null or if it was correctly parsed during deserialization. This information is accessible through the JenevaValidationContext class methods.

One of the main goals of the JenevaValidationContext class is to keep track of the property path. For example, when you validate an object and then validate its nested children, the context class ensures that all error messages are connected with corresponding property paths. The result of validation is a list of failures, where a failure is a property path text and a message. This failure structure is serializaed to JSON and sent back to browser, where it is parsed and displayed correctly in correct places in HTML.

The best practice would be deriving from theJenevaValidationContext class, extending some additional application-specific validation routines there and then using the subclass in the validation methods. Below is the example of how to extend the context class:

public class ValidationContext : JenevaValidationContext
{
    public ValidationContext(IJenevaContext jenevaContext)
        : base(jenevaContext)
    {
    }

    public void Required()
    {
        this.Assigned("is required");
        if (this.IsFieldValid && this.IsValidFormat())
        {
            this.NotNull("is required");
        }
    }

    public void Missing()
    {
        this.NotAssigned("must be empty");
    }

    public void Number()
    {
        this.ValidFormat("must be valid number");
    }

    public void Date()
    {
        this.ValidFormat("must be valid date");
    }

    public void Float()
    {
        this.ValidFormat("must be valid floating point number");
    }

    public void Email()
    {
        const string expr = @"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b";
        this.Regex(expr, "must be valid Email");
    }

    public void Text()
    {
        this.StringLengthBetween(3, 20, "must be between 3 and 20 characters");
    }

    public void TrueFalse()
    {
        this.ValidFormat("must be 'yes' or 'no'");
    }
}

The logic above is extremely helpful and will make the validation methods extremely simple and readable. For example, I will no longer have to write redundant error messages for strings between 3 and 20 characters, I will use Text(). The same is for dates, doubles, numbers, required fields, etc.

In the following piece of code, you can see how validation method for Save is implemented ValidateForSave():

public class ClientValidator : IClientValidator
{
    public ILoginValidator LoginValidator { get; set; }

    public void ValidateForSave(Client target, IValidationContext context)
    {
        context.SetField("id", target.Id);   // validate id property now
        context.Missing();  // it must be missing - not present in json

        context.SetField("name", target.Name);   // validate name property now
        context.Required();    // it is required - present in json and not null
        context.Text();    // it also must be between 3 and 20 characters length

        context.SetField("lastname", target.Lastname);
        context.Required();
        context.Text();

        context.SetField("age", target.Age);
        context.Required();
        context.Number();
        context.MustBeGreaterThan(0, "must be greater than zero");

        context.SetField("logins", target.Logins);
        context.Required();
        context.MustHaveCountBetween(1, 5, "must be at least one login");

        context.AddNested();   //  let us validate child properties - logins
        int index = 0;
        foreach (Login login in target.Logins)
        {
            context.SetIndex(index++);    // the logins - is indexed property - property path will have index - logins[4].name
            context.SetTarget(login);     // validate login class
            this.LoginValidator.ValidateForSave(login, context);
        }

        context.RemoveNested();   // switch back - up in property path - back to Client's properties
    }
}

The SetField() method tells the context what field is currently validated, i.e., if you register a failure in the context, it will have property path of the current field. Validation routines must go after the SetFiled() method call. TheJenevaValidationContext class contains numerous validation routines. These routines usually do two actions: first - they check a condition (for example, check if field value is null), and second - they register a failure if the condition is not true (using the Fail() method). For example, Assigned() - checks if field is assigned (is present in JSON) and if false - it registers a failure using the current property path; NotNull(), Null() are selfdescriptive; ValidFormat() - registers a failure if a field value is not correctly parsed (integer or double) from JSON.

As you have noticed, the JenevaValidationContext class, besides the validation routines methods, also contains other important methods: SetTarget() sets up a current validated object, this method is important and should always be called before any validation routine; AddNested() - this method propagates current property in the property path as nested object, all subsequent calls to SetField() will result in concatenating property name with the nested object name; the RemoveNested() method does the inverse; the SetIndex() method also adds indexing to the current property path - "[" + index + "]".

Here you can see validation methods for the Login class.

public class LoginValidator : ILoginValidator
{
    public void ValidateForSave(Login target, IValidationContext context)
    {
        context.SetField("id", target.Id);
        context.Missing();

        context.SetField("name", target.Name);
        context.Required();
        context.Text();

        context.SetField("password", target.Password);
        context.Required();
        context.Text();

        context.SetField("enabled", target.Enabled);
        context.TrueFalse();

        context.SetField("client", target.Client);
        context.Missing();
    }
}

When the validators for all the domain and DTO classes are done, we are free to define a facade class which will be injected into our services or controllers and which will be used to fire validation. Here is an example of the validation facade:

public class ValidationFacade : IValidationFacade
{
    public IValidationContextFactory ContextFactory { get; set; }
    public IClientValidator ClientValidator { get; set; }

    public void AssertClientForSave(Client target)
    {
        IValidationContext context = this.ContextFactory.GetNew();
        context.SetTarget(target);
        this.ClientValidator.ValidateForSave(target, context);
        context.Assert();
    }

    public void AssertClientForUpdate(Client target)
    {
        IValidationContext context = this.ContextFactory.GetNew();
        context.SetTarget(target);
        this.ClientValidator.ValidateForUpdate(target, context);
        context.Assert();
    }
}

The class looks pretty simple, here I use simple factory method to get a new instance of the validation context. The most important is the last line of each method - Assert() - this method throws ValidationException if at least one error is registered in the context, otherwise it does nothing.

Here you can see how the facade is injected and used in the service layer:

public class ClientService : IClientService
{
    public IMapper Mapper { get; set; }
    public IValidationFacade Validator { get; set; }
    public IClientDao ClientDao { get; set; }

    public Client GetById(int id)
    {
        Client item = this.ClientDao.GetById(id);
        return this.Mapper.Filter(item, Levels.DEEP);
    }

    public IList<client> GetAll()
    {
        IList<client> items = this.ClientDao.GetAll();
        return this.Mapper.FilterList(items, Levels.GRID);
    }

    public void Save(Client item)
    {
        this.Validator.AssertClientForSave(item);
        using (ITransaction tx = this.ClientDao.BeginTransaction())
        {
            this.Mapper.Map(item);
            this.ClientDao.Save(item);
            tx.Commit();
        }
    }

    public void Update(Client item)
    {
        this.Validator.AssertClientForUpdate(item);
        using (ITransaction tx = this.ClientDao.BeginTransaction())
        {
            Client existing = this.ClientDao.GetById(item.Id.Value);
            this.Mapper.MapTo(item, existing);
            this.ClientDao.Merge(existing);
            tx.Commit();
        }
    }
}

By the way, the validation context class is quite handy and easy to extend and use it differently. You can always play with its methods and get a different behavior. In the example code, you will see how it is used for validation in different circumstances - for example, validation before Delete. Please, see the AssertClientExists() and the AssertMoreThanOneClient() methods in the ValidationFacade class.

public void AssertClientExists(Client item)
{
    if (item == null)
    {
        IValidationContext context = this.ContextFactory.GetNew();
        context.Fail("Client does not exist");
        context.Assert();
    }
}

public void AssertMoreThanOneClient(long count)
{
    if (count == 1)
    {
        IValidationContext context = this.ContextFactory.GetNew();
        context.Fail("Cannot delete the only client");
        context.Assert();
    }
}

In these methods, you don't rely on a target validated object. You just have to assert that some condition is met. In this case, a ValidationException is thrown with jsut on failure in it, and property path is empty.

And that is it. Now validation must work. If you call the AssertValid() method from your business layer, it will identify which type validator class to use, based on the provided group. Then it will invoke implementation of the abstract Validate() method. If validation succeeds, nothing happens. If validation fails, ValidationException is thrown. ValidationException will contain a list of name-value pairs - property path and error message. In order to process this exception properly in ASP.NET MVC, I will create and register a custom ExceptionFilterAttribute. This technique is common in ASP.NET MVC, you can find how to do it in the internet. So, here is the implementation of the custom Exception filter.

public class ErrorFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        FailResponse response;
        if (context.Exception is ValidationException)
        {
            response = (context.Exception as ValidationException).BuildFailResponse();
        }
        else
        {
            response = new FailResponse(context.Exception.ToString());
        }

        context.Response = 
          context.Request.CreateResponse(HttpStatusCode.InternalServerError, response);
    }
}

I replace the HTTP response content with my JSON array of (property path - error message) pairs - FailResponse class.

Now I have to register ErrorFilterAttribute. I have to add this line in the Global.asax Application_Start event.

GlobalConfiguration.Configuration.Filters.Add(new ErrorFilterAttribute());

Now, every time the ValidationException is thrown, it will be handled by ErrorHandlerAttribute handler, and a list of property paths and error messages will be sent back as JSON in the HTTP response.

Front-end

The last step is displaying those messages in the correct place in HTML. You are free to write your custom JavaScript for AngularJS or KnockoutJS, or anything else. Jeneva.Net comes with a small JavaScript library for AngularJS validation. These libraries make it simpler displaying those errors. For example, if you use angular, your HTML must look like this:

<label>Name:</label>
<input name="name" type="text" ng-model="name" jv-path="name" />
<span class="error" ng-if="form.name.$error.jvpath" ng-repeat="msg in form.name.$jvlist">{{msg}}</span>

The jVpath directive works the same way as any AngularJS validation directives i.e. it tells that the name textbox is responsible for the "name" property path. The span below will repeatedly display the validation messages assigned to the "name" property path.

You can find out how to create a Single-Page Application (SPA) using AngularJS in my next article: AngularJS single-page app and Upida/Jeneva.Net.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

vladimir husnullin
Software Developer
United States United States
No Biography provided

You may also be interested in...

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160730.1 | Last Updated 16 Jan 2014
Article Copyright 2013 by vladimir husnullin
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid