4.6: Adding User Service

Adding Abstract Interface

In the Contracts project, add a new interface called IUserService.cs where we'll define the methods we'll be using in our concrete implementation.

public interface IUserService
{
    void Add<T>(T entity) where T: class;
    void Delete<T>(T entity) where T: class;
    Task<bool> SaveAll();
    Task<IEnumerable<UserForList>> GetUsers();
    Task<UserForDetail> GetUser(int id);
}

The Add() and Delete() methods are using generics. We're using generics here so we can use the same methods for different types - if we want to add a User - but, also if we want to add Photos or other entities we may add later.

Adding Concrete Implementation

Next, in the .Services project - add a new class called UserService.cs.

Indicate that it will implement the IUserService (: IUserService).

Add a constructor with the DbContext as a parameter:

public class UserService : IUserService
{
    private readonly EFConnectContext _context;
    public UserService(EFConnectContext context)
    {
        _context = context;
    }

    //  ...

Next, ctrl + . on IUserService and select Implement Interface.

Now we'll implement the methods

Add and Delete Methods

public void Add<T>(T entity) where T : class
{
    _context.Add(entity);
}

public void Delete<T>(T entity) where T : class
{
    _context.Remove(entity);
}

These look confusing because they're using Generics. They allow us to use these methods for any type (that we supply). Instead of writing Add() and Delete() methods for Users and Photos - we can just use these methods for both. Generics are a great way to keep it [DRY](https://en.wikipedia.org/wiki/Don't_repeat_yourself)!

Read up on Generics here.

GetUser(id) Method

Next we'll implement the GetUser(id) method.

public async Task<UserForDetail> GetUser(int id)
{
    var user = await _context.Users
                    .Include(p => p.Photos)
                    .FirstOrDefaultAsync(u => u.Id == id);              // 1

    if (user == null)
        return null;

    var photosToReturn = new List<PhotoForDetail>();                    // 2

    foreach (var photo in user.Photos)                                  // 3
    {
        var userPhoto = new PhotoForDetail
        {
            Id = photo.Id,
            Url = photo.Url,
            Description = photo.Description,
            DateAdded = photo.DateAdded,
            IsMain = photo.IsMain
        };

        photosToReturn.Add(userPhoto);
    }

    var userToReturn = new UserForDetail                                // 4
    {
        Id = user.Id,
        Username = user.Username,
        Specialty = user.Specialty,
        Age = user.DateOfBirth.CalculateAge(),                          //  (extension method)
        KnownAs = user.KnownAs,
        Created = user.Created,
        LastActive = user.LastActive,
        Introduction = user.Introduction,
        LookingFor = user.LookingFor,
        Interests = user.Interests,
        City = user.City,
        State = user.State,
        PhotoUrl = user.Photos.FirstOrDefault(p => p.IsMain).Url,
        Photos = photosToReturn
    };

    return userToReturn;                                                // 5
}

There is a lot going on here! We can break it down into 5 parts. Refer to the steps are commented above.

  1. We're getting all of the users from the context and including the Photos along with them.

  2. Next, we're declaring a variable to hold a list of PhotoForDetail DTOs

  3. Here we're looping through the photos attached to the user, translating them into PhotoForDetail DTOs and pushing them onto the list we created in step 2.

  4. Now we're creating the UserForDetail DTO. We're using our extension method to calculate the Age property. We're using our List of PhotoForDetails as our collection of Photos.

  5. Finally, we're returning the new UserForDetail we just created

GetUsers() Method

public async Task<IEnumerable<UserForList>> GetUsers()
{
    var users = await _context.Users
                    .Include(p => p.Photos)
                    .Select(
                        e => new UserForList
                        {
                            Id = e.Id,
                            Username = e.Username,
                            Specialty = e.Specialty,
                            Age = e.DateOfBirth.CalculateAge(),
                            KnownAs = e.KnownAs,
                            Created = e.Created,
                            LastActive = e.LastActive,
                            City = e.City,
                            State = e.State,
                            PhotoUrl = e.Photos.FirstOrDefault(p => p.IsMain).Url
                        }
                    )
                    .ToListAsync();

    return users;
}

There is less going on in this method. If the previous method makes sense to you, this one should be pretty straightforward.

SaveAll() Method

We won't be using this yet, but we'll go ahead and add it since we added it to the interface.

This will be used for saving changes to the database. Right now we're just querying the database, not making any changes.

public async Task<bool> SaveAll()
{
    return await _context.SaveChangesAsync() > 0;
}

Register Service in Startup.cs

In the Startup.cs file in the .API project, register the new UserService for dependency injection as we previously added the AuthService:

//  ...
services.AddScoped<IAuthService, AuthService>();
services.AddScoped<IUserService, UserService>();                //  <--- Added
//  ...

Last updated