DESIGNING EDITING FUNCTIONALITY

SAVE A FILE UPLOADED BY THE USER

NET CORE

We will see how it is possible to save an ‘image so that it is usable by all users of the application. To save the image, I created a new interface in the IImagePersister.cs infrastructure service. I’ll show you the code.

Interfaccia

It returns a Task<string> because this infrastructure service knows where to persist the image and how to get to it. The class that implements the interface is called InsecureImagePersister.cs, and you will understand why it is insecure in a moment. The method will use a Data Stream, so let’s see what is meant. Regardless of where the image binaries are located-in RAM, on a temporary file on disk, on a Web Services-the beauty of the Stream is that it provides an interface to access the file sequentially. CopyToAsync method of IFormFile will copy the binary file from the input stream to the output stream that we decide which one it is.

Stream

The method copies the files as is without any verification; therefore, even if the user uploaded a virus this virus would be transferred to disk, hence why this class is insecure. This is the code.

Implementazione

RESIZE A USER UPLOADED IMAGE

Sometimes it may happen that the image in the Detail Form does not update. This is because since the path to the image has not been changed but only overwritten, the Browser thinks it can serve the image from its local cache instead of reading it again from the server. Let’s see how to solve it.

asp-append-version

As you can see a Hash code of the image was added by making the path change and forcing the Browser to load the new image. Let us now deal with a very important issue. Image validation, performed via an external library downloaded from NuGet.

Libreria

With the library we download from NuGet we can do validation and crop the image, however, it does not secure us from all possible abuses such as uploading a pornographic image. In this case you could upload the image and with the File object move it to another location so that it could be screened by the administrator staff. Or using an AI-based Web Service. The library we are going to download from NuGet is Magick.NET-Q8-AnyCPU. We are going to create a new class in the infrastructure service with this library and call it MagickNetImagePersister.cs. In the class we make use of the OpenReadStream method of IFormFile. Let’s look at an image.

Magick net

Let’s look at various types of resizing. If the original image has the same proportions as the one we want to get we simply have to resize it with the values given in the code, 300px in width and 300px in height.

Ridimensionamento

Let us see the case where the image is not square, and therefore its proportions cannot be maintained. There are various strategies, the first not advisable is to have the image occupy the entire surface.

Ridimensionamento

The second strategy allows the proportions to be left unchanged, but leaving white bands on the sides.

Ridimensionamento

The third strategy maintains proportions by cutting the upper and lower bands. In these cases you can inform the user by saying you didn’t upload a square image so it might be cropped. Let’s see how our library performs.

Ridimensionamento
Ridimensionamento
Ridimensionamento

The second parameter MagickColor.FromRgb indicates the color of the side bands, white in this case. The image below illustrates the strategy we will use.

Ridimensionamento

SHOW A PREVIEW OF THE RESIZING RESULT

There is not much to say on this topic since you only have to create CSS rules.

Anteprima

PROTECT THEMSELVES FROM ABUSE RELATED TO IMAGE UPLOADING

When a library like Magick.Net ingests image bytes it has to process them somehow because those are the bytes of a compressed format like jpeg or png. Compressed format means that bytes manage to represent a high amount of pixels, so Magick.Net must first decompress the image to its original format in order to work. Decompressing it means that every single pixel must be represented in RAM memory. This means that the RAM memory occupancy is proportional to the number of pixels with which the image is formed. Let’s take an example. Suppose the user loads an image of 4000px by 3000px, being channels 3 would come to occupy in RAM about 36MB.

Decompressione

But if the user uploads an image of 40000px by 30000px occupancy becomes 36GB. The number of pixels with which the image is formed can be easily set in the constructor of the Magick.NET class. When this limit is exceeded, the library throws an exception. But what if the user sends many requests? How do we protect the application? In this case we can use an object from the .NET Core, the SemaphoreSlim.

Limitare il consumo di ram

With the last instruction, it is as if we are setting up a two-lane road, in which passage is regulated by a traffic light. The machines(The ASP.NET Core threads that go to execute the image resizing code) are forced to pass one at a time, when they have reached the finish line (end of processing) the light turns green again. So with such a mechanism we are able to limit the number of Threads to two.

SemaphoreSlim
Limitare la ram

A Thread that arrives to execute this code by finding the await operator will put itself on hold on one of the two lanes we have available, if it was already green so much the better, it means that WaitAsync() will complete immediately and the Thread will begin its processing. It is important after the await statement to put a try finally block, this is because if an exception were to occur the finally block would still be executed and the traffic light will turn green again. Otherwise, Thread queues would form and our application would stop working. If we did not invoke the Release method, the light would always stay on and Threads would be formed waiting. Instead of having the library process everything and then generate an exception you can set a limit in the configuration file, in which case it is the Kestrel Web Server that will immediately block execution.

Limitare il peso delle immagini

LINK TO CODE ON GITHUB

GITHUB

Download the section code15 or master branch or clone the GITHUB repository to have all sections available in your favorite editor.