Securely Handle User Uploads With Service Injection (MVC) | Dear Coder
Tags: postgres, csharp, tutorial, dotnet
Published: 2021-05-17 12:57:36

Dear Coder,

In my last letter, I wrote about [four rules](https://dev.to/piaomu/four-rules-for-accepting-secure-user-uploads-to-a-postgressql-database-mvc-dear-coder-o9a) to keep in mind while handling uploads from users in a web application. Today I want to give some step-by-step instructions on how to handle uploads while satisfying those rules.

You might recall that I'm building an application called [ToonSpace](https://dev.to/piaomu/create-a-relational-database-using-asp-net-core-mvc-dear-coder-3jj0) for users to share their webtoons with each other. Today, I'll build the service that will accomplish that goal.



## Create Image Service Interface

I create a new folder on the top level of my Solution Explorer named _Services_ and add a new public Interface _IImageService_.

![Services Folder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x8y6f0nma5m1pq6j3e7k.png)

This Interface will hold a task that Encodes the image from a user upload, a task that encodes an image from a url, a string that decodes the image, and a string that records the content type of the image.

I name my service IImageService and add these four fields, making sure to declare the Interface is public.

![IImageService](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jyscjyn40iqx7v8y4qt8.png)

I also add my AspNetCore.Http using directive:

```
using Microsoft.AspCore.Http;
```

## Create Basic Image Service Concrete Class

Once the interface is finished, I create a corresponding concrete class called BasicImageService that holds executing code for the interface.

Inside my _Services_ folder, I create a class called BasicImageService.

I add three using directives:

```
using Microsoft.AspNetCore.Http;
using System.IO;
using System.Net.Http;
```


#### Let's look at what each of these methods actually allow me to do in the concrete class:


```
        public async Task<byte[]> EncodeImageAsync(IFormFile image)
        {
            if (image == null)
            {
                return null;
            }
            using var ms = new MemoryStream();
            await image.CopyToAsync(ms);
            return ms.ToArray();

        }
```

_EncodeImageAsync_ encodes a user-submitted image from an upload control and converts the file to a byte array to be stored in my database. Later, I'll leverage the multi-part form-data feature to allow users to submit an image file within a form, which is why _EncodeImageAsync_ takes the IFormFile interface type.


```
        public async Task<byte[]> EncodeImageURLAsync(string imageURL)
        {
            var client = new HttpClient();

            var response = await client.GetAsync(imageURL);

            Stream stream = await response.Content.ReadAsStreamAsync();

            var ms = new MemoryStream();
            await stream.CopyToAsync(ms);

            return ms.ToArray();

        }
```

_EncodeImageURLAsync_ takes the _path_ to an image and turns it into a byte array to be used in a similar fashion to _EncodeImageAsync_, storing an image in the database as a byte array.


```
        public string DecodeImage(byte[] image, string contentType)
        {
            if (image == null)
            {
                return null;
            }
            var convertedImage = Convert.ToBase64String(image);
            return $"data:{contentType};base64,{convertedImage}";
        }
```

The first two methods dealt with encoding an image to store in my database. _DecodeImage_ allows me to convert the stored data into a base64 string and pull it from the database to display as an image.



```
        public string RecordContentType(IFormFile image)
        {
            if (image == null)
            {
                return null;
            }
            return image.ContentType;
        }
```

Finally, I need to record the _ContentType_ (png, jpeg, etc.) so I can properly formulate the string to display the image.

The finished Concrete Class looks like this:
![BasicImageService](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bsx4sovp8c1vifqn5yh3.png)

## Register Basic Image Service

Now that I've built my interface and corresponding concrete class for my Basic Image Service, I have to register it as a service in my Startup so I can inject it across the project. 

I simply access my Startup file in my Solution Explorer and scroll to the _ConfigureServices method:
![Startup.cs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9vp4p3jz11x3i5me651m.png)

I add my Basic Image Service to my list of services by adding `using ToonSpace.Services` to the top of the document and adding my service to the list of services like with the IImageService interface and BasicImageService concrete class:
```
services.AddScoped<IImageService, BasicImageService>();
```
Now my service is ready to be used throughout the project.

## Inject Service into Controller

I've registered an image service that will allow users to securely upload their webtoons to my application, so now comes the fun part! I inject my service into the controller by adding a using directive, constructor, and parameters:

![Inject Image Service](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mra3s6nuxhmjtva1imj5.png)

## Add the Image Upload to the Form

I need to jump over to the View where the user is going to upload so I can instruct the controller what to do when the user uploads.

I access the Create scaffolded View in my Uploads Views folder to access the form that has already been built for me.

The first thing I need to do is change the encode type to `enctype="multipart/form-data"` which is an HTML spec that allows the form to now accept uploads instead of just fields.

Then, scroll to the Image div and add an Image Id, a type of "file", and a "form-control-file" class (for styling).
```
            <div class="form-group">
                <label asp-for="Image" class="control-label"></label>
                <input id="Image" type="file" asp-for="Image" class="form-control-file" />
                <span asp-validation-for="Image" class="text-danger"></span>
            </div>
```

## Code Instructions in Create Action of Controller

I open my UploadsController to the Create Action and remove _Image_ and _ContentType_ from the Bind. I add add IFormFile Image as an argument in the Create Method, which Microsoft.AspNetCore.Http allows me to do. I add `Microsoft.AspNetCore.Http` to my using directives.

The Bind now looks like this:

```
public async Task<IActionResult> Create([Bind("Id,GenreId,Title,Artist,Created,Image,ContentType")] Upload upload, IFormFile Image)
```
I then set the upload image and content type equal to the data coming in from the image service if the model state is valid.

```
                upload.ContentType = _imageService.ContentType(Image);
                upload.Image = await _imageService.EncodeImageAsync(Image);
```

The user's upload will now save to the database after I've written these lines of code in the Create Action.

## Display The User's Image in the View

The only thing I have left to do is spin up the user's image from the database and display it in the View. To do this, I access my Views folder and find the "Details" view under "Uploads."

I write some quick markup for illustration purposes:

```
@model ToonSpace.Models.Upload
@using ToonSpace.Services
@inject IImageService _imageService

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Upload</h4>
    <hr />

    <div class="card">
        <div class="col-8">
            <img id="Profile" class="img-fluid" src="@_imageService.DecodeImage(Model.Image, Model.ContentType)" />
        </div>
    </div>
```

Now, all the user needs to do is upload an image to the database and they will be able to see the image displayed in the Details View.

There's much more to be done to make this platform functional and secure, but today's letter is already longer than usual, and so I'll leave you to rest.

Godspeed in your keystrokes.

Clickity Clacks,

Kasey