7.6: Adding PhotosController

Now, let's use our new PhotoService in a controller.

Adding PhotosController

In the .API/Controllers folder, add a new class called PhotosController.cs.

We'll inject our User and Photo Services.

We'll also add Route and Authorize attributes.

[Authorize]
[Route("Api/Users/{userId}/photos")]
public class PhotosController : Controller
{
    private readonly IUserService _userService;
    private readonly IPhotoService _photoService;

    public PhotosController(IUserService userService, IPhotoService photoService)
    {
        _userService = userService;
        _photoService = photoService;
    }

    [HttpPost]
    public async Task<IActionResult> AddPhotoForUser(int userId, PhotoForCreation photoDto)
    {
        var user = await _userService.GetUser(userId);

        if (user == null)
            return BadRequest("Could not find user");

        var currentUserId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value);

        if (currentUserId != user.Id)
        {
            return Unauthorized();
        }

        var photoForReturn = await _photoService.AddPhotoForUser(userId, photoDto);

        if (photoForReturn != null)
            return Ok();

        return BadRequest("Photo upload failed.");
    }
}

This isn't perfect yet, and we'll improve it.

Ideally, instead of just returning Ok() - we'd return CreatedAtRoute() and pass in the route it was created at.

We can't do that yet because we need an endpoint to access an individual photo. We'll work on that next.

Adding GetPhoto Method to PhotoService

First, let's add the signature to our IPhotoService.cs interface:

public interface IPhotoService
{
    Task<PhotoForReturn> GetPhoto(int id);
    Task<PhotoForReturn> AddPhotoForUser(int userId, PhotoForCreation photoDto);
}

Then, let's implement it in our PhotoService.cs class:

public async Task<PhotoForReturn> GetPhoto(int id)
{
    var photo = await _context.Photos.FirstOrDefaultAsync(p => p.Id == id);

    var photoForReturn = new PhotoForReturn
    {
        Id = photo.Id,
        Url = photo.Url,
        Description = photo.Description,
        DateAdded = photo.DateAdded,
        IsMain = photo.IsMain,
        PublicId = photo.PublicId,
    };

    return photoForReturn;
}

That was much more manageable than our last service method!

Add Method to Photos Controller

Now, we can use our new service method in a GetPhoto() method in our controller:

[HttpGet("{id}", Name = "GetPhoto")]
public async Task<IActionResult> GetPhoto(int id)
{
    var photo = _photoService.GetPhoto(id);

    return Ok(photo);
}

We're giving this endpoint a Name so we can tell CreatedAtRoute() which endpoint to use to access a photo when it's uploaded.

Adding CreatedAtRoute()

Now we have what we need to change our Ok() response for a successful image upload to a CreatedAtRoute() response instead. This adheres to RESTful API standards.

Back in our PhotosController, at the bottom of the AddPhotoForUser POST route, change the return value to use CreatedAtRoute():

var photoForReturn = await _photoService.AddPhotoForUser(userId, photoDto);

if (photoForReturn != null)
    return CreatedAtRoute("GetPhoto", new { id = photoForReturn.Id }, photoForReturn);

return BadRequest();

CreatedAtRoute() takes three parameters:

  1. The name of the controller method (that's why we added the Name attribute to our GetPhoto() controller method.

  2. The Id of the newly created photo

  3. the data to return

Testing Photo Upload in Postman

Now, let's test what we've built in Postman before trying to work with it in Angular.

First, log in to an account to make sure you have a fresh token and copy and paste the token. Refer to previous examples if you forget how to do this.

Next, we'll send a POST request to http://localhost:5000/api/users/{id}/photos. Make sure that the {id} in the above example is given the Id of the user of the token you just received. In the Headers - enter the Authorization key and Bearer token string for the value as in previous examples.

We can upload files from our computer with Postman by choosing form-data as the body of our request.

Name the key File - it must match the property of our PhotoForCreation model. In the key dropdown - change it from Text to File.

In the Value field - you should have a button that will allow you to browse to a file to upload.

PostmanUpload

After setting up Postman correctly - send the request. You should get a 201 Created response with a photo for return in the body:

Let's test scenarios when something goes wrong.

Resend the request, but change the Id in the url to a different user. You should get a 401 Unauthorized response.

PostmanUpload401

Next, resend the request with an Id that doesn't exist in the database. You should get a 400 Bad Request response and Could not find user in the body:

Last updated