OdeToCode IC Logo

Byte Arrays and ASP.NET Core Web APIs

Thursday, February 22, 2018

I’ve decided to write down some of the steps I just went through in showing someone how to create and debug an ASP.NET Core controller. The controller is for an API that needs to accept a few pieces of data, including one piece of data as a byte array. The question asked specifically was how to format data for the incoming byte array.

Instead of only showing the final solution, which you can find if you read various pieces of documentation, I want to show the evolution of the code and a thought process to use when trying to figure out the solution. While the details of this post are specific to sending byte arrays to an API, I think the general process is one to follow when trying to figure what works for an API, and what doesn’t work.

To start, collect all the information you want to receive into a single class. The class will represent the input model for the API endpoint.

public class CreateDocumentModel
{
    public byte[] Document { get; set; }
    public string Name { get; set; }
    public DateTime CreationDate { get; set; }
}

Before we use the model as an input to an API, we’ll use the model as an output. Getting output from an API is usually easy. Sending input to an API can be a little bit trickier, because we need to know how to format the data appropriately and fight through some generic error messages. With that in mind, we’ll create a simple controller action to respond to a GET request and send back some mock data.

[HttpGet]
public IActionResult Get()
{
    var model = new CreateDocumentModel()
    {
        Document = new byte[] { 0x03, 0x10, 0xFF, 0xFF },
        Name = "Test",
        CreationDate = new DateTime(2017, 12, 27)
    };

    return new ObjectResult(model);
}

Now we can use any tool to see what our data looks like in a response. The following image is from Postman.

Simple GET Request ASP.NET Core Byte Array

What we see in the response is a string of characters for the “byte array” named document. This is one of those situations where having a few years of experience can help. To someone new, the characters look random. To someone who has worked with this type of data before, the trailing “=” on the string is a clue that the byte array was base64 encoded into the response. I’d like to say this part is easy, but there is no substitute for experience. For beginners, one also has to see how C# properties in PascalCase map to JSON properties in camelCase, which is another non-obvious hurdle to formatting the input correctly. 

Once you’ve figured out to use base64 encoding, it’s time to try to send this data back into the API. Before we add any logic, we’ll create a simple echo endpoint we can experiment with.

[HttpPost]
public IActionResult CreateDocument([FromBody] CreateDocumentModel model)
{
    return new ObjectResult(model);
}

With the endpoint in place, we can use Postman to send data to the API and inspect the response. We’ll make sure to set a Content-Type header to application/json, and then fire off a POST request by copying data from the previous response.

Posting byte array to asp.net core

Voilà!

The model the API returns looks just like the model we sent to the API. Being able to roundtrip the model is a good sign, but we are only halfway through the journey. We now have a piece of code we can experiment with interactively to understand how the code will behave in different circumstances. We want a deeper understanding of how the code behaves because our clients might not always send the model we expect, and we want to know what can go wrong before the wrong things happen.

Here are some questions to ask.

Q: Is a base64 encoded string the only format we can use for the byte array?

A: No. The ASP.NET Core model binder for byte[] also understands how to process a JSON array.

{
    "document": [1, 2, 3, 254],
    "name": "Test input",
    "creationDate": "2017-12-27T00:00:00"
}

Q: What happens if the document property is missing in the POST request?

A: The Document property on the input model will be null.

Q: What happens if the base64 encoding is corrupt, or when using an array, a value is outside the range of a byte?

A: The model input parameter itself will be null

I’m sure you can think of other interesting questions.

Summary

There are two points I’m making in this post:

1. When trying to figure out how to get some code to work, take small steps that are easy to verify.

2. Once the code is working, it is often worthwhile to spend a little more time to understand why the code works and how the code will behave when the inputs aren’t what you expect.


Comments
Gravatar Thomas Thursday, February 22, 2018
Scott, thank you. I have another question immediately popping up: how to tell the byte[]-formatter to create a json-array instead of base64? Cheers, Thomas
Gravatar scott Thursday, February 22, 2018
I wondered this too, but didn't find an immediate answer. Might require diffing into JSON.net settings.
Gravatar Mladen Mihajlović Friday, February 23, 2018
Use an array instead of base64: https://stackoverflow.com/questions/15226921/how-to-serialize-byte-as-simple-json-array-and-not-as-base64-in-json-net
Gravatar scott Friday, February 23, 2018
Thanks!
Gravatar Mike Friday, February 23, 2018
I would recommend moving the property of byte[] type to the bottom of the class, so it is serialized last. Been bitten before, when the maximum Http request size is exceeded, the rest of the props in the model will be null. And you will be chasing your own tail trying to figure out what the heck is wrong with the model binder when all of your prop names match up with the model of the API method. Once it is the dead-last prop, you will figure it out much faster that the length of received array is less than what you sent in.
Gravatar scott Saturday, February 24, 2018
Great tip!
Gravatar johnny Tuesday, February 27, 2018
My question is not about byte array but i still need help on this problem. i tried to use your "https://odetocode.com/blogs/scott/archive/2008/03/25/inner-outer-lets-all-join-together-with-linq.aspx" tutorial to solve my problem but i failed. Am having a problem with this Linq to Sql query. var query = from bs in this.mDbContext.tblboxs join ld in this.pdbContext.tblDicts on bs.BID equals ld.ID join mbs in this.crmDbContext.tblStatuses on bs.ID equals mbs.Id into mbsGrps from mbs in mbsGrps.DefaultIfEmpty() select new XRecord { BsID_Archiv = bs.ID, .......................... }; return await query.ToListAsync(); I always get an internal server error when testing with Postman. And after I debugging i found out that the method throws a "The value can not be NULL. Parameter name: entityType ", I am using EntityFrameworkCore' s fluent api to map my entities to database tables. Any help is greatly appreciated.
Gravatar scott Tuesday, February 27, 2018
@johnny - I don't see anything wrong with the query, so I would suspect something is missing in the mapping.
Gravatar johnny Wednesday, February 28, 2018
Allen thanks for the reply. I don't understand when you say something is missing in the mapping. - Is it the mapping of an Entity to Database table that I implement in DbContex's OnModelCreating() method - Is it the mapping if it's there with my DTO thus XReport() object . I first did the mapping myself using EF-fluent Api in the OnModelCreating() of my Dbcontexts , that didn't solve the error. Then after your reply i decided to use "EF Core Power Tools" to do the Reverse Engineering and mapping for me and I still have the same error.
Gravatar scott Wednesday, February 28, 2018
@johnny: I'm not sure. I think I've seen that error before, but not having the project nor the schema to look at, I can only offer a vague direction, sorry. I'd try to reproduce the problem in a small console application by adding pieces bit by bit and see where the problem starts.
Gravatar scott Friday, March 2, 2018
@johnny: Sorry for deleting your last comment, but all the DDL statements made for a loooong comment and this blog isn't designed for technical support. If you can reproduce the problem in a small program, perhaps a console application, you can email me and I'll try to take a look. You might also try to post in Microsoft support forms or stackoverflow for a quicker answer.
Gravatar johnny Monday, March 5, 2018
Allen sorry about my last post. I have now created console application that reproduces the same problem. You can find the project here: https://github.com/devcodeprobs/EfCoreTesting.
Gravatar scott Friday, March 16, 2018
Will look soon - been jammed up the last couple weeks.
Gravatar scott Sunday, March 18, 2018
@johnny: I don't think EF Core 2.0 will do joins across different DbContexts. You'll either have to do the joins in memory (use ToList or AsEnumerable on your DbSets), or use a view, stored procedure, or ad-hoc SQL.
Comments are closed.