Daniel Donbavand

Documenting my Development Journey

ASP.NET Core 2.0: Input Validation - Creating a Problem Factory

When creating Web APIs we create endpoints that people can use to interact with our Web Service. Often these endpoints will have a parameter that we can enter as part of the request. These parameters that someone can enter are what we want to make sure we validate on. We don’t want to be passing around input values that are not what we are expecting. We want to gracefully handle invalid values.


Note: I am adding the problem factory to the Giphy API Web Service that was created in a previous blog post. If you want to see how to create this head along to ASP.NET Core 2.0: Implementing a 3rd Party Web API

Handling Invalid Input

We could easily handle invalid inputs inside our controller by adding an if statement that checks for a null or empty then simply returning a 400 Bad Request. While this is perfectly acceptable for certain projects, often when we create Web APIs, we will have more then one endpoint that will need validating. If this is the case, then creating a Problem Factory could be the way to go.

Problem Factory

What is a Factory

I will be writing a blog series on design patterns that will include factories, what they are and how they are used. In the meantime if you are unsure on what a factory is and want to know more, head along to the following link. https://www.dotnetperls.com/factory

What is a Problem Factory

A Problem Factory holds a set of problems with each problem assigned different details about the problem.

The details can include but are not limited to Title, Status, Detail. When we encounter a problem that we are checking for, the person making the request is returned these details back.

It gives us an opportunity to return more detail back to the person making the request then simply returning a Bad Request.

While we could return all this detail inside our controller, as you can imagine, our controller will start to get quite large. The controller is also doing to many things. Our aim when writing code is to keep it clean, readable and to make sure we are seperating out each concern.

Implementing the Problem Factory

The first thing we need to do when implementing our problem factory is to

  • Add a question mark (?) to {searchCritera} this will make the searchCritera nullable.

If we didn't do this, then when we try and hit our endpoint without any searchCritera, it wouldn't find our route.

We need to make it nullable in order to hit our route without searchCritera.

[HttpGet]
[Route("v1/giphy/random/{searchCritera?}")]

We then add an if statement to our GetRandomGif Method. This if statement will check for IsNullorEmpty.

if (string.IsNullOrEmpty(searchCritera))
{
   return FromProblem(_problemFactory.MissingPayLoad("searchCritera"));
}

We need to create a method FromProblem, that takes the problem that we are about to create, and combines it with a status code before returning as an IActionResult.

public IActionResult FromProblem(Problem.Problem problem)
{
   return StatusCode((int) problem.StatusCode, problem);
}

Then we will create a field named _problemFactory with the type IProblemFactory to the top of our class and initialize this inside our controllers constructor.

private readonly IProblemFactory _problemFactory;

public GiphyController(IProblemFactory problemFactory)
{
    _problemFactory = problemFactory;
}

This is what our full GiphyController class looks like now

public class GiphyController : Controller
{
    private readonly IGiphyServices _giphyServices;
    private readonly IProblemFactory _problemFactory;

    public GiphyController(IGiphyServices giphyServices, IProblemFactory problemFactory)
    {
        _giphyServices = giphyServices;
        _problemFactory = problemFactory;
    }

    [HttpGet]
    [Route("v1/giphy/random/{searchCritera?}")]
    public async Task<IActionResult> GetRandomGif(string searchCritera)
    {
        if (string.IsNullOrEmpty(searchCritera))
        {
            return FromProblem(_problemFactory.MissingPayLoad("searchCritera"));
        }

        var result = await _giphyServices.GetRandomGifBasedOnSearchCritera(searchCritera);

        return Ok(result);
    }

    public IActionResult FromProblem(Problem.Problem problem)
    {
        return StatusCode((int) problem.StatusCode, problem);
    }
 }

We will now create a folder named Problems and add the following 3 files.

  • IProblemFactory
  • ProblemFactory
  • Problem

Inside the problem class we will add the following properties. This is the information that we will be sending back to our users that make the requests.

public string Title { get; set; }
public string Details { get; set; }
public HttpStatusCode StatusCode { get; set; }

In the ProblemFactory we will add our first problem that we want to return back to the user, MissingPayLoad.

public Problem MissingPayLoad(string value)
{
    return new Problem
    {
        Title = "Payload is not well formed or was not provided",
        Details = $"Missing Data {value}",
        StatusCode = HttpStatusCode.BadRequest
    };
}

The ProblemFactory is where we would add all of our problems, while we have one at the moment, often you would have more then one, such as UnexpectedError, that would return a status code of 500

We will then add the public method to our interface

public interface IProblemFactory
{
   Problem MissingPayLoad(string value);
}

Heading back to the GiphyController, make sure you add the reference the ProblemFactory.

Lastly we need to make sure that our IProblemFactory and ProblemFactory are added to the ConfigureServices method in the startup.cs, so we are able to use dependency injection and inject our interfaces in the classes that use them.

Head to startup.cs and add the following.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IProblemFactory, ProblemFactory>();
}

Now build your solution and run it.

When you hit the endpoint with a search criteria, you should be return the gif url


2018-01-24_20-16-32


and when you hit the endpoint without any search criteria, you should be return our problem that includes the title, status and details of the issue.


2018-01-24_20-17-43


A video tutorial on how this is done is also on my YouTube channel

If you have any questions, comments or thoughts, you can connect with me on one of the following social media accounts.


Connect with me on social media:

Twitter: https://twitter.com/Donbavand
GitHub: https://github.com/donbavand
Facebook: https://www.facebook.com/dandonbavand/

1883487-1

You can find the code dicussed above on my GitHub repo