Category: webApi

What I should return from WebAPI ?

 

There are several approaches when returning code from WebAPI .  Let’s frame the problem: say we have a Person controller with 2 actions:

– a GET {id}  – that retrieves the Person with id

– a POST {Peron}  – that tries to verify the validity of the Person and then saves to database.

We will answer to some questions:

1.What we will return if the person with id does not exists ?

2. What happens with the validation about the Person  ?
Let’s see the Person class.

public class Person: IValidatableObject
{
    public int ID { get; set; }
    public string  Name { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        yield return new ValidationResult("not valid not matter what");
    }
}

 

First approach: Do nothing

What I mean is just return the Person , even if it is null.

1.1. The GET

[HttpGet("{id}")]
public Person GetPerson(int id)
{
var p = RetrieveFromDatabase(id);
return p;
}

The problem is that when we return null from an action, there are no response data from the HTTP call-  and the javascript should handle it .

 

1.2 The POST – no problem . [ApiController] should handle the validation and return BadRequest if not validate

 

Second approach:  Return standard HttpCodes

2.1 The GET


[HttpGet("{id}")]
public ActionResult<Person> GetPerson404(int id)
{
    var p = RetrieveFromDatabase(id);
    if (p == null)
        return NotFound($"{nameof(Person)} with {id} are not found");

    return p;

}

We intercept the null and return standard 404 HttpCode . In that case, I strongly suggest to add a message to the end-caller – to know that the reason is not that the http call was a problem, but the Person was not found

2.2 The POST – no problem . [ApiController] should handle the validation and return BadRequest if not validate

 

Third approach: Return OK with a custom ReplyData class

The ReplyData can look this way

public class ReplyData<T>
{
    public bool Success { get; set; }
    public string Message { get; set; }
    public T ReturnObject { get; set; }
}

2.1 The GET

This action is over simplified – the code just returns the ReplyData from the database.

[HttpGet("{id}")]
public ReplyData<Person> GetWithReply(int id) {

    return RetrieveWithReplyFromDatabase(id);
}

private ReplyData<Person> RetrieveWithReplyFromDatabase(int id)
{
    try
    {
        Person p = null;//retrieve somehow
        if (p == null)
        {
            var r = new ReplyData<Person>();
            r.Success = false;
            r.Message = "Cannot find person with id " + id;
            return r;
        }
        else
        {
            var r = new ReplyData<Person>();
            r.Success = true;
            r.ReturnObject = p;

            return r;
        }
    }
    catch(Exception ex)
    {
        var r = new ReplyData<Person>();
        r.Success = false;
        r.Message = ex.Message;
        return r;
    }
}

2.2 The POST

This is somehow complicated – the [ApiController] , if not valid, return a 400 BadRequest . So we should modify this with a custom middleware to return the same ReplyData

public class From400ValidationToReply : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
    try
    {
        var originalStream = context.Response.Body;
        var bufferStream = new MemoryStream();
        context.Response.Body = bufferStream;
        await next(context);
        bufferStream.Seek(0, SeekOrigin.Begin);
        if (context.Response.StatusCode != 400)
        {
            await bufferStream.CopyToAsync(originalStream);
            return;
        }
        var reader = new StreamReader(bufferStream);
        var response = await reader.ReadToEndAsync();
        if (!response.Contains("errors"))
        {
            await bufferStream.CopyToAsync(originalStream);
            return;
        }

        context.Response.StatusCode = 200;

        var r = new ReplyData<Person>();
        r.Success = false;
        r.Message=response;
        var text = JsonSerializer.Serialize(r);
        var bit = Encoding.UTF8.GetBytes(text);
        context.Response.ContentLength = bit.Length;
        await originalStream.WriteAsync(bit);

    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw;
    }
}

And the problem is that will affect all the request pipeline….

Conclusion:
I would choose 2 . But … your choice… ( and , for an advanced discussion, please read https://blog.ploeh.dk/2013/05/01/rest-lesson-learned-avoid-hackable-urls/ )

Thinking of the project–part 2

WebAPI2CLI

This is a part of the series where about how I made the WebAPI2CLI - Execute ASP.NET Core WebAPI from Command Line
Source code on https://github.com/ignatandrei/webAPI2CLI/
1WebAPI2CLI - Description
2WebAPI2CLI- Organization
3WebAPI2CLI - implementing
4WebAPI2CLI - tests
5WebAPI2CLI - Devops and CI/CD
6WebAPI2CLI - documentation
7WebAPI2CLI - Conclusions
8WebAPI2CLI - Zip application

The questions that I have to solve is :

1.  How to run something in an ASP.NET Core project, when we have just WEBAPI controllers ?

The answer was pretty simple: Hosted Services: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio . It runs unattended and can be started by the project

2. Which version of .NET Core should I use ?

Another simple answer, looking at https://dotnet.microsoft.com/platform/support/policy/dotnet-core – the only one that has LTS now is .NET Core 3.1 . Although, if someone requires , I can do a .NET Core 2.1

3. How can I serialize the HTTP arguments to send to the  commands?

There are 2 means of a program to read arguments: by Command Line arguments and by file on disk.

Now, let’s see how many options we have to configure a HTTP call : Verb, Arguments, ContentType, URL, Headers…. hard to fit into a command line. Not mentions that I want to have the option to run many HTTP calls / commands . So , as usual , it will be a mix: comand line for activating the file with the commands.

4. What do I do with the result of HTTP calls ?

The output of the commands will be written to the console. It is the easy way  – and you can redirect this output to a file to persist.

5. Where do I store the sources ?

Easy – GitHub – https://github.com/ignatandrei/WebAPI2CLI/

6. How do I find the the project works ?

Azure DevOps to the rescue – it can build the project and run tests. And it is free for Github

7. Where and how I deploy it ?

Where : easy – it will be hosted on NuGet https://www.nuget.org/packages/ExtensionNetCore3

How –easy  -Azure DevOps again.  https://dev.azure.com/ignatandrei0674/WebAPI2CLI/_build?definitionId=7&_a=summary

8. Where and how I can make the help for this project ?

Again –easy – GitHub has docs – https://ignatandrei.github.io/WebAPI2CLI/

 

Now let’s do the implementation!

 

New side project- WebAPI To CLI–part 1

WebAPI2CLI

This is a part of the series where about how I made the WebAPI2CLI - Execute ASP.NET Core WebAPI from Command Line
Source code on https://github.com/ignatandrei/webAPI2CLI/
1WebAPI2CLI - Description
2WebAPI2CLI- Organization
3WebAPI2CLI - implementing
4WebAPI2CLI - tests
5WebAPI2CLI - Devops and CI/CD
6WebAPI2CLI - documentation
7WebAPI2CLI - Conclusions
8WebAPI2CLI - Zip application

I have had an idea; What if the WebAPI application can be called from the console application itself  ?

This will permit just having the DI running  – and all the stuff that WebAPI can work with when calling a controller.

Will be good in a CI / CD workflow – testing the full API instead of relying to test just the dll ( Yes, I know about skinny controllers )

And will be good in Jenkins / any other tool / in order to execute directly the WebAPI instead of recurring to curl / powershell

 

So – seems to have a good piece of software to start – how we do this ?

First, how all the commands should be passed ? If we pass via command line, there is pretty much possible for someone to miss things.

Second , if the user does not pass via command line, the WebAPI should work as usual.

So , 5 days and 88 commits later , this is the result: https://github.com/ignatandrei/webAPI2CLI/ .

It has the documentation about how to install and use it. Enjoy !

 

Modifying from MVC to WebAPI

I have to make a private personal site to work with Android  / IPhone /Windows Phone natively. So I have to open the data from the site.

But what is better then http://en.wikipedia.org/wiki/Eating_your_own_dog_food ? So I decide to do modifications for this

The site was a simple listing of steps to solve a dangerous situation –see the below image:

image

 

The code was realize with a ViewModel and some ajax call( cascading drop downs) .

The ViewModel was this

public class ChooseDangerSituation
    {
        public List<KVD> Danger;
        public List<KVD> Situation;
        public List<Tuple<int, int>> DangerSituation;

So I list here the steps:

1.  Returning the same ViewModel class from the WebAPI:

public class SurvivingController : ApiController
  {
      public ChooseDangerSituation DangerSituations()

 

2. Modifying view controls and loading from javascript:

In my example  instead of DropDownList :

var danger = Model.Danger.Select(item => new SelectListItem() { Text = item.Value, Value = item.Key.ToString() }).ToArray();

@Html.DropDownList("idDanger",danger, "Danger")

I had to modify into select :

<select id="idDanger" name="idDanger">
    <option value="">Danger</option>
</select>

3. Modify all JsonResult to HttpResponseMessage

This

return Json(new { ok = true, data = q });

will become this

return Request.CreateResponse(HttpStatusCode.OK, new { ok = true, data = q });               

 

 

Side note:
For a real application you should generate unobtrusive validation. Also, you may want to create Validationmessages. Also WebApi let’s you define routes more easily: [Route("GetSteps/{idDanger}/{idSituation}")]

 

 

That it will be all.

Andrei Ignat weekly software news(mostly .NET)

* indicates required

Please select all the ways you would like to hear from me:

You can unsubscribe at any time by clicking the link in the footer of our emails. For information about our privacy practices, please visit our website.

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.