[ตอนที่ 8] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 เพิ่มข้อมูลของ Users และสร้างความสัมพันธ์ของข้อมูล

Line
Facebook
Twitter
Google

สำหรับบทความนี้จะใช้ MacOS ทำงานต่อจากตอนที่แล้ว โดยเมื่อ Clone Project. มาแล้ว สำหรับ API ก็ต้องทำการ dotnet restore เพื่อติดตั้ง Package ที่เกี่ยวข้อง และต้องใช้ใน ASP.NET Core และสำหรับ SPA ก็ต้องทำการ npm install เพื่อติดตั้ง Dependencies ทั้งหมดที่ต้องใช้ใน Angular Project

สารบัญเนื้อหา

  1. เพิ่มข้อมูลของ Users ใน User Model
  2. สร้าง Migrations และอัพเดต Database
  3. ความสัมพันธ์ของข้อมูลใน Entity Framework Relationship
  4. สร้างข้อมูลตั้งต้น (Seeding Data)
  5. สร้าง Repository สำหรับข้อมูลใหม่
  6. สร้าง Users Controller
  7. สร้าง DTOs สำหรับ Users เพื่อให้แสดงข้อมูลเฉพาะตามต้องการ
  8. การใช้งาน AutoMapper เพื่อจับคู่ข้อมูลใน Model กับ DTOs

1. เพิ่มข้อมูลของ Users ใน User Model

ทำการเพิ่มข้อมูลของ Users ใน User Model และสร้าง Photo Model ดังนี้

Models/Photo.ts

using System;

namespace SocialApp_API.Models
{
    public class Photo
    {
        public int Id { get; set; }
        public string Url { get; set; }
        public string Description { get; set; }
        public DateTime DateAdded { get; set; }
        public bool isMain { get; set; }
        public User User { get; set; }
        public int UserId { get; set; }
    }
}

Models/User.cs

using System;
using System.Collections.Generic;

namespace SocialApp_API.Models
{
    public class User
    {
        public int Id { get; set; }
        public string Username { get; set; }
        public byte[] PasswordHash { get; set; }
        public byte[] PasswordSalt { get; set; }
        public string Gender { get; set; }
        public DateTime DateOfBirth { get; set; }
        public string KnownAs { get; set; }
        public DateTime Created { get; set; }
        public DateTime LastActive { get; set; }
        public string Introduction { get; set; }
        public string LookingFor { get; set; }
        public string Interests { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
        public ICollection<Photo> Photos { get; set; }
    }
}

และปรับปรุง DataContext.cs

using SocialApp_API.Models;
using Microsoft.EntityFrameworkCore;

namespace SocialApp_API.Data
{
    public class DataContext : DbContext
    {
        public DataContext(DbContextOptions options)
            : base(options)
        {
        }

        public DbSet<Value> Values { get; set; }
        public DbSet<User> Users { get; set; }
        public DbSet<Photo> Photos { get; set; }
    }
}

2. สร้าง Migrations และอัพเดต Database

ทำการสร้าง Migrations และอัพเดต Database ด้วยคำสั่ง

dotnet ef migrations add ExtendedUserClass
dotnet ef database update

***ถ้าใช้ SQLite จะไม่รองรับการลบคอลัมภ์ (Drop Column) นะ ดังนั้น ถ้าเรา update database ไปแล้ว ต้องการลบฟิลด์ออก อาจต้องใช้วิธีการลบ Database แล้วลบ Migrations ล่าสุด ทำการอัพเดต Model ให้ถูกต้อง แล้ว add migrations และทำกการสร้าง database ใหม่ หรือหากต้องการย้อนกลับ migration ล่าสุดก็ทำการอัพเดต database หลังจาก remove migrations ได้

คำสั่งที่เกี่ยวข้อง

dotnet ef database drop
dotnet ef migrations remove
dotnet ef migrations add EditEntity
dotnet ef database update

3. ความสัมพันธ์ของข้อมูลใน Entity Framework Relationship

ใน Photo.cs ที่เรามี

public User User { get; set; }
public int UserId { get; set; }

เพื่อเป็นการระบุว่าในเราต้องการให้ Table Photo มีความสัมพันธ์กับ Table User โดยให้เป็นความสัมพันธ์แบบ 1:1 คือ 1 Photo ต้องมี 1 User เป็นเจ้าของ

ส่วน User.cs ที่เรามี

public ICollection<Photo> Photos { get; set; }

เพื่อเป็นการระบุความสัมพันธ์ระหว่าง User กับ Photo ว่าเป็นความสัมพันธ์แบบ 1:N คือ 1 User สามารถมีหลาย Photo ได้

4. สร้างข้อมูลตั้งต้น (Seeding Data)

สร้างไฟล์ UserSeedData.json ภายใต้ Data

***ไฟล์ JSON กรณีมี Id ที่เป็น Primary Key ไม่ต้องใส่ในไฟล์ JSON***

[
  {
    "Username": "Freda",
    "Gender": "female",
    "DateOfBirth": "1980-03-12",
    "Password": "password",
    "KnownAs": "Freda",
    "Created": "2017-02-13",
    "LastActive": "2017-02-13",
    "Introduction": "Veniam ex id elit et id. Ullamco proident laborum irure fugiat laborum enim exercitation reprehenderit proident tempor dolor. Sit do consectetur amet tempor eu exercitation labore in reprehenderit laborum. Tempor velit laborum qui deserunt dolore proident amet laboris pariatur aliqua. Excepteur mollit elit esse ut laborum ad ut nisi anim. Reprehenderit amet id tempor adipisicing occaecat Lorem pariatur nisi non consequat id duis nisi. Pariatur non dolor magna cupidatat duis elit quis.\r\n",
    "LookingFor": "Eiusmod consequat nulla nulla cupidatat adipisicing commodo ullamco reprehenderit fugiat ad labore. Nulla commodo minim sint non consectetur deserunt do veniam aliqua amet consectetur et reprehenderit. Dolor dolor eu reprehenderit ex laborum magna adipisicing ad nisi laborum velit.\r\n",
    "Interests": "Ullamco nulla laboris pariatur aute culpa laboris consequat adipisicing dolore.",
    "City": "Hamilton",
    "Country": "Finland",
    "Photos": [
      {
        "url": "https://randomuser.me/api/portraits/women/47.jpg",
        "isMain": true,
        "description": "Ut dolore minim ipsum minim exercitation anim aliqua esse."
      }
    ]
  },
  {
    "Username": "Aida",
    "Gender": "female",
    "DateOfBirth": "1989-10-18",
    "Password": "password",
    "KnownAs": "Aida",
    "Created": "2017-06-19",
    "LastActive": "2017-06-19",
    "Introduction": "Ea eu enim mollit deserunt mollit velit Lorem. Qui ea reprehenderit consequat nulla. Velit elit proident reprehenderit officia ut eu. Ullamco consequat amet ipsum proident voluptate. Magna irure aliqua nulla cupidatat laborum aute tempor consequat. Minim amet in dolor enim velit qui. Ut ad est fugiat nostrud qui cupidatat.\r\n",
    "LookingFor": "Est ad do cillum anim exercitation pariatur ea pariatur aliqua duis consectetur ut ullamco veniam. Eu ea et ipsum amet pariatur dolor nostrud sunt tempor ipsum veniam fugiat proident. In aliquip laboris fugiat anim duis. Proident exercitation exercitation cillum aliquip aliquip ullamco nostrud magna enim. Ea Lorem aute et consequat nulla ullamco aliqua proident magna aliquip nulla. Voluptate officia ex sit tempor eiusmod qui nulla eiusmod sit. Consectetur duis pariatur dolore anim ut ad cupidatat proident incididunt excepteur ad.\r\n",
    "Interests": "Sunt voluptate deserunt duis nostrud aute do voluptate culpa magna anim sit.",
    "City": "Ypsilanti",
    "Country": "Latvia",
    "Photos": [
      {
        "url": "https://randomuser.me/api/portraits/women/33.jpg",
        "isMain": true,
        "description": "Culpa sint consectetur in deserunt occaecat nostrud in commodo ad deserunt in minim."
      }
    ]
  },
  {
    "Username": "Susie",
    "Gender": "female",
    "DateOfBirth": "1972-05-21",
    "Password": "password",
    "KnownAs": "Susie",
    "Created": "2017-12-03",
    "LastActive": "2017-12-03",
    "Introduction": "Officia ex adipisicing laboris commodo. Ullamco culpa laborum tempor consectetur adipisicing adipisicing et aliqua reprehenderit aliquip Lorem eu elit. In reprehenderit occaecat consequat excepteur fugiat nostrud. Sit commodo minim id pariatur aute labore irure quis Lorem aliqua in aute do et. Veniam nulla pariatur officia esse commodo enim labore veniam irure do.\r\n",
    "LookingFor": "Pariatur consequat enim ad ex fugiat ex aliquip anim quis sit laborum ex. Anim ex ut enim velit minim enim labore et veniam ea voluptate. Ad amet dolore et laborum ad cillum id ad non sint do non non nisi. Excepteur labore labore ea ut aliqua qui magna nulla.\r\n",
    "Interests": "Nulla incididunt excepteur aute nostrud commodo aute anim consectetur sit consequat quis.",
    "City": "Starks",
    "Country": "Greenland",
    "Photos": [
      {
        "url": "https://randomuser.me/api/portraits/women/65.jpg",
        "isMain": true,
        "description": "Esse aliquip enim nisi culpa sunt."
      }
    ]
  },
  {
    "Username": "Nichole",
    "Gender": "female",
    "DateOfBirth": "1968-03-10",
    "Password": "password",
    "KnownAs": "Nichole",
    "Created": "2017-08-30",
    "LastActive": "2017-08-30",
    "Introduction": "Ipsum consectetur dolor nisi mollit velit id do voluptate irure ea nostrud exercitation excepteur mollit. Duis sunt consectetur proident ullamco ipsum excepteur consequat do duis. Proident consequat ut laborum magna aliquip. Esse culpa tempor ullamco sunt officia officia ea occaecat elit occaecat mollit consectetur in exercitation. Laborum cillum ad laboris fugiat incididunt occaecat. Dolore laborum cillum quis esse adipisicing non Lorem eu.\r\n",
    "LookingFor": "Cupidatat cillum laborum labore esse cillum culpa irure quis ad eiusmod quis nulla. Dolor nulla deserunt laborum officia consectetur officia sunt labore quis labore consectetur aute. Qui irure labore dolore in irure consequat exercitation elit proident eiusmod fugiat do. In est irure commodo ad cillum ut id cillum sit cupidatat elit. Consectetur cillum exercitation voluptate aliqua proident adipisicing ex. Officia qui est eiusmod commodo aliqua et.\r\n",
    "Interests": "Culpa consequat in id fugiat do quis magna culpa minim.",
    "City": "Idamay",
    "Country": "Jamaica",
    "Photos": [
      {
        "url": "https://randomuser.me/api/portraits/women/38.jpg",
        "isMain": true,
        "description": "Adipisicing consequat consequat sit aute officia commodo officia esse dolore dolore anim consequat nisi excepteur."
      }
    ]
  },
  {
    "Username": "Bridget",
    "Gender": "female",
    "DateOfBirth": "1965-07-31",
    "Password": "password",
    "KnownAs": "Bridget",
    "Created": "2017-06-23",
    "LastActive": "2017-06-23",
    "Introduction": "Dolore magna ad aute elit eiusmod pariatur in. Consectetur proident sint cillum quis ad quis. Laboris adipisicing consequat minim eiusmod irure amet. Veniam et do in voluptate nulla reprehenderit in sint. Do adipisicing quis occaecat officia non. Est aute ad cillum eiusmod velit anim esse ea dolore.\r\n",
    "LookingFor": "Reprehenderit amet id elit elit enim veniam sint esse deserunt. Consectetur sit qui reprehenderit in nulla anim laboris ea. Et ad ad pariatur adipisicing enim dolor. Exercitation mollit dolore incididunt irure dolore mollit enim do qui elit. Tempor dolore consequat veniam culpa commodo amet non excepteur consequat deserunt dolore.\r\n",
    "Interests": "Exercitation consequat labore proident id nisi non aliquip exercitation.",
    "City": "Ona",
    "Country": "Turks and Caicos Islands",
    "Photos": [
      {
        "url": "https://randomuser.me/api/portraits/women/6.jpg",
        "isMain": true,
        "description": "Proident reprehenderit est enim proident ad exercitation irure eu laboris exercitation aute voluptate dolore adipisicing."
      }
    ]
  },
  {
    "Username": "Fisher",
    "Gender": "male",
    "DateOfBirth": "1990-04-02",
    "Password": "password",
    "KnownAs": "Fisher",
    "Created": "2017-10-15",
    "LastActive": "2017-10-15",
    "Introduction": "Ex sit do magna amet dolor aliquip veniam qui cillum fugiat dolore. Reprehenderit et non ipsum id non aute enim exercitation fugiat dolor fugiat eiusmod. Culpa consectetur sunt sit labore. Veniam ullamco ut cillum sit labore cupidatat consectetur dolor Lorem. Nisi nulla commodo minim aliquip aliqua anim eu eiusmod ex et nulla est ullamco. Et enim consequat fugiat consequat id id deserunt veniam aliqua nostrud nostrud.\r\n",
    "LookingFor": "Pariatur qui nostrud eu excepteur aliquip aliquip non. Ipsum eu commodo cillum minim labore elit sint velit laboris mollit fugiat minim tempor. Consequat ex consequat sunt officia. Anim exercitation do dolore ut do excepteur eu duis ut voluptate dolor eiusmod aute. Amet veniam ad in mollit duis magna esse eu amet nulla elit non elit ea. Officia excepteur et commodo cupidatat amet non aliqua laborum consectetur nisi nostrud anim adipisicing. Sint non duis ex amet voluptate ex.\r\n",
    "Interests": "Ullamco sint deserunt enim dolor cupidatat mollit.",
    "City": "Bennett",
    "Country": "Dominican Republic",
    "Photos": [
      {
        "url": "https://randomuser.me/api/portraits/men/44.jpg",
        "isMain": true,
        "description": "Culpa commodo proident do dolor cillum pariatur eiusmod cillum reprehenderit do Lorem nulla do."
      }
    ]
  },
  {
    "Username": "Simon",
    "Gender": "male",
    "DateOfBirth": "1964-08-27",
    "Password": "password",
    "KnownAs": "Simon",
    "Created": "2017-06-22",
    "LastActive": "2017-06-22",
    "Introduction": "Esse sunt esse cupidatat enim nulla et nisi labore commodo dolor dolore laboris non. Id non Lorem cillum exercitation aliquip ut tempor. Deserunt commodo laborum proident reprehenderit eiusmod pariatur incididunt. Sit officia cupidatat adipisicing esse sunt cupidatat amet irure occaecat do est. Lorem officia magna culpa ex sit in veniam proident.\r\n",
    "LookingFor": "Ullamco ad elit ex ullamco consectetur sint laborum occaecat. Qui sunt aute est culpa irure incididunt culpa sunt et in. Aute esse id ex commodo elit. Exercitation cupidatat laboris officia elit aliquip exercitation aliqua. Sunt sit ea ea sint consequat consectetur eu proident aliquip anim veniam. Ipsum esse do reprehenderit sint veniam dolore proident dolor do. Cupidatat laborum sunt enim voluptate in laboris ipsum ea laborum in eu.\r\n",
    "Interests": "Occaecat culpa commodo tempor ullamco mollit nostrud pariatur reprehenderit officia dolore proident aliquip.",
    "City": "Chamizal",
    "Country": "Guadeloupe",
    "Photos": [
      {
        "url": "https://randomuser.me/api/portraits/men/72.jpg",
        "isMain": true,
        "description": "Magna ea dolore eu incididunt eu commodo culpa sunt magna in magna est."
      }
    ]
  },
  {
    "Username": "Rivers",
    "Gender": "male",
    "DateOfBirth": "1952-12-16",
    "Password": "password",
    "KnownAs": "Rivers",
    "Created": "2017-04-16",
    "LastActive": "2017-04-16",
    "Introduction": "Anim incididunt eu eiusmod ut excepteur esse quis deserunt adipisicing enim ut culpa. Eiusmod dolor proident dolor pariatur enim do. Duis dolor cupidatat aute minim consectetur voluptate esse labore.\r\n",
    "LookingFor": "Deserunt amet ad duis anim non velit. Occaecat adipisicing consectetur aute commodo cillum aliquip magna laborum irure eu. Velit aliquip incididunt pariatur fugiat amet eiusmod consequat Lorem et ipsum Lorem amet.\r\n",
    "Interests": "Exercitation sit ut labore esse culpa sit.",
    "City": "Rockbridge",
    "Country": "Taiwan",
    "Photos": [
      {
        "url": "https://randomuser.me/api/portraits/men/23.jpg",
        "isMain": true,
        "description": "Duis esse irure nulla ullamco est id consequat Lorem."
      }
    ]
  },
  {
    "Username": "Boyer",
    "Gender": "male",
    "DateOfBirth": "1974-12-02",
    "Password": "password",
    "KnownAs": "Boyer",
    "Created": "2017-09-17",
    "LastActive": "2017-09-17",
    "Introduction": "In ex in incididunt esse aute sit ex anim dolore laborum nisi id ea. Consequat incididunt aute eiusmod amet ut. Esse ullamco qui anim sit adipisicing ea consectetur adipisicing sunt est duis. Esse Lorem mollit est aute quis aliquip exercitation labore voluptate. Id aute elit est Lorem.\r\n",
    "LookingFor": "Ipsum sint esse do amet minim ipsum id non nulla nostrud eiusmod deserunt. Magna Lorem deserunt aliqua aliquip aliquip tempor culpa nulla occaecat pariatur. Voluptate minim commodo sint ad commodo nostrud. Ullamco fugiat occaecat sint laboris sunt aliqua fugiat in enim. Exercitation excepteur aliqua commodo fugiat proident mollit.\r\n",
    "Interests": "Est laborum quis ullamco nulla cupidatat anim enim aliqua et veniam et consectetur dolore ea.",
    "City": "Katonah",
    "Country": "Georgia",
    "Photos": [
      {
        "url": "https://randomuser.me/api/portraits/men/36.jpg",
        "isMain": true,
        "description": "Sint aliqua ut culpa ipsum deserunt elit anim voluptate fugiat."
      }
    ]
  },
  {
    "Username": "Johnson",
    "Gender": "male",
    "DateOfBirth": "1957-07-17",
    "Password": "password",
    "KnownAs": "Johnson",
    "Created": "2017-06-28",
    "LastActive": "2017-06-28",
    "Introduction": "Occaecat veniam quis culpa minim ullamco sunt sint veniam elit ad magna. Sit est quis duis commodo tempor mollit duis incididunt sint. Incididunt exercitation fugiat elit Lorem culpa elit.\r\n",
    "LookingFor": "Anim consequat ut nostrud veniam ea ipsum duis ea nisi ullamco quis. Anim anim elit duis commodo occaecat quis quis esse. Excepteur occaecat ipsum mollit mollit amet velit occaecat fugiat nostrud. Proident anim sunt officia ex elit ea culpa est ut officia adipisicing quis. Nulla non cupidatat commodo ullamco amet voluptate elit consectetur.\r\n",
    "Interests": "Sunt quis laboris culpa est mollit nulla aliquip dolor labore non.",
    "City": "Barronett",
    "Country": "Botswana",
    "Photos": [
      {
        "url": "https://randomuser.me/api/portraits/men/52.jpg",
        "isMain": true,
        "description": "Laboris magna mollit consequat deserunt consectetur culpa commodo anim est ea sunt."
      }
    ]
  }
]

สร้าง Seed.cs ใน Data

using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Internal;
using Newtonsoft.Json;
using SocialApp_API.Models;

namespace SocialApp_API.Data
{
    public class Seed
    {
        public static void SeedUsers(DataContext context){
            if (!context.Users.Any()){
                var userData = System.IO.File.ReadAllText("Data/UserSeedData.json");
                var users = JsonConvert.DeserializeObject<List<User>>(userData);

                foreach (var user in users){
                    byte[] passwordHash, passwordSalt;
                    CreatePasswordHash("password",out passwordHash, out passwordSalt);

                    user.PasswordHash = passwordHash;
                    user.PasswordSalt = passwordSalt;
                    user.Username = user.Username.ToLower();

                    context.Users.Add(user);
                }

                context.SaveChanges();
            }
        }

        private static void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
        {
            using (var hmac = new System.Security.Cryptography.HMACSHA512())
            {
                passwordSalt = hmac.Key;
                passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
            }
        }

    }
}

ปรับปรุง Method Main ใน Program.cs

public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();
    using (var scope = host.Services.CreateScope())
    {
        var services = scope.ServiceProvider;
        try
        {
            var context = services.GetRequiredService<DataContext>();
            context.Database.Migrate();
            Seed.SeedUsers(context);
        } catch (Exception ex)
        {
            var logger = services.GetRequiredService<ILogger<Program>>();
            logger.LogError(ex, "An error occured during migration");
        }
    }

    host.Run();
}

ลบ Database เพื่อให้ Method Main ทำการสร้าง Database ใหม่ พร้อมข้อมูลตั้งต้น และทำการรัน API ใหม่ ด้วยคำสั่ง

dotnet ef database drop
dotnet watch run

หลังจากรันแล้ว ตรวจสอบข้อมูลในตาราง User ใน Database จะพบว่ามีข้อมูลตั้งต้นอยู่ในนั้นเรียบร้อยแล้ว

5. สร้าง Repository สำหรับข้อมูลใหม่

เพิ่ม ISocialRepository.cs ภายใต้ Data

using System.Collections.Generic;
using System.Threading.Tasks;
using SocialApp_API.Models;

namespace SocialApp_API.Data
{
    public interface ISocialRepository
    {
         void Add<T>(T entity) where T: class;
         void Delete<T>(T entity) where T: class;
         Task<bool> SaveAll();
         Task<IEnumerable<User>> GetUsers();
         Task<User> GetUser(int id);
    }
}

เพิ่ม SocialRepository.cs ภายใต้ Data และทำการ Implement Interface ISocialRepository

ซึ่ง Repository นี้เราจะใช้งานกับ Model อื่น ๆ ด้วย เราก็เลยต้องสร้าง Repository แบบกลาง ๆ สำหรับการดำเนินการต่าง ๆ เกี่ยวกับข้อมูลจาก Model ที่ส่งเข้ามา

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using SocialApp_API.Models;

namespace SocialApp_API.Data
{
    public class SocialRepository : ISocialRepository
    {
        private readonly DataContext _context;
        public SocialRepository(DataContext context)
        {
            _context = context;

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

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

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

            return user;
        }
        public async Task<IEnumerable<User>> GetUsers()
        {
            var users = await _context.Users.Include(p => p.Photos).ToListAsync();

            return users;
        }

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

เพิ่ม Services ใน Startup.cs ในส่วนของ ConfigureServices

services.AddScoped<ISocialRepository, SocialRepository>();

6. สร้าง Users Controller

ติดตั้ง Package Microsoft.AspNetCore.Mvc.NewtonsoftJson

เพิ่ม services.AddControllers().AddNewtonsoftJson(); ใน Startup.cs

services.AddControllers().AddNewtonsoftJson(opt =>
{
      opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});

สร้าง UsersController.cs

using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SocialApp_API.Data;

namespace SocialApp_API.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class UsersController : ControllerBase
    {
        private readonly ISocialRepository _repo;
        public UsersController(ISocialRepository repo)
        {
            _repo = repo;
        }

        [HttpGet]
        public async Task<IActionResult> GetUsers()
        {
            var users = await _repo.GetUsers();

            return Ok(users);
        }

        [HttpGet("{id}")]
        public async Task<IActionResult> GetUser(int id)
        {
            var user = await _repo.GetUser(id);

            return Ok(user);
        }
    }
}

ปรับ API ให้รันในโหมด Development และทดสอบรัน แล้วเรียก API ผ่าน UsersController

จะเห็นว่ามี passwordHash และ passwordSalt ติดออกมาใน JSON ด้วย ซึ่งเราไม่ต้องการให้เป็นเช่นนั้น

7. สร้าง DTOs สำหรับ Users เพื่อให้แสดงข้อมูลเฉพาะตามต้องการ

สร้าง UserForListDto.cs ภายใต้ Dtos

using System;

namespace SocialApp_API.Dtos
{
    public class UserForListDto
    {
        public int Id { get; set; }
        public string Username { get; set; }
        public string Genger { get; set; }
        public int Age { get; set; }
        public string KnowAs { get; set; }
        public DateTime Created { get; set; }
        public DateTime LastActive { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
        public string PhotoUrl { get; set; }
    }
}

สร้าง UserForDetailedDto.cs ภายใต้ Dtos

using System;
using System.Collections.Generic;
using SocialApp_API.Models;

namespace SocialApp_API.Dtos
{
    public class UserForDetailedDto
    {
        public int Id { get; set; }
        public string Username { get; set; }
        public string Genger { get; set; }
        public int Age { get; set; }
        public string KnowAs { get; set; }
        public DateTime Created { get; set; }
        public DateTime LastActive { get; set; }
        public string Introduction { get; set; }
        public string LookingFor { get; set; }
        public string Interests { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
        public string PhotoUrl { get; set; }
        public ICollection<Photo> Photos { get; set; } 
    }
}

8. การใช้งาน AutoMapper เพื่อจับคู่ข้อมูลใน Model กับ DTOs

ติดตั้ง Package AutoMapper.Extensions.Microsoft.DependencyInjection เพิ่มเติม

เพิ่ม Services ใน Startup.cs หลัง services.AddCors();

services.AddAutoMapper(typeof(SocialRepository).Assembly);

เรียกใช้งาน Auto Mapper โดยปรับปรุงโค้ดใน UsersController ดังนี้

using System.Collections.Generic;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SocialApp_API.Data;
using SocialApp_API.Dtos;

namespace SocialApp_API.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class UsersController : ControllerBase
    {
        private readonly ISocialRepository _repo;
        private readonly IMapper _mapper;
        public UsersController(ISocialRepository repo, IMapper mapper)
        {
            _mapper = mapper;
            _repo = repo;
        }

        [HttpGet]
        public async Task<IActionResult> GetUsers()
        {
            var users = await _repo.GetUsers();

            var usersToReturn = _mapper.Map<IEnumerable<UserForListDto>>(users);

            return Ok(usersToReturn);
        }

        [HttpGet("{id}")]
        public async Task<IActionResult> GetUser(int id)
        {
            var user = await _repo.GetUser(id);

            var userToReturn = _mapper.Map<UserForDetailedDto>(user);

            return Ok(userToReturn);
        }
    }
}

สร้าง Auto Mapper Helper สำหรับช่วยทำการ Map Data โดยสร้าง AutoMapperProfiles.cs ภายใต้ Helpers

using AutoMapper;
using SocialApp_API.Dtos;
using SocialApp_API.Models;

namespace SocialApp_API.Helpers
{
    public class AutoMapperProfiles : Profile
    {
        public AutoMapperProfiles()
        {
            CreateMap<User, UserForListDto>();
            CreateMap<User, UserForDetailedDto>();
        }
    }
}

ลองรันดู จะพบว่าข้อมูลถูก Map ตามที่กำหนดแล้ว แต่ยังเหลือเฉพาะ Age และ PhotoUrl ซึ่งเป็นข้อมูลพิเศษที่กำหนดเอาไว้ก่อนหน้านี้

และสำหรับ API ดึงข้อมูลรายละเอียดของ User ก็จะพบว่าข้อมูลยังไม่ครบเช่นกัน แถมยังมีการ Include ข้อมูล User มาใน Photo อีกชั้นหนึ่ง ทำให้ passwordHash, passwordSalt ติดมาด้วยอีก

มาแก้ปัญหานี้ต่อ

สร้าง DTOs สำหรับ Photo เพื่อนำไป Include กับ User เป็นไฟล์ชื่อ PhotoForDetailedDto.cs

using System;

namespace SocialApp_API.Dtos
{
    public class PhotoForDetailedDto
    {
        public int Id { get; set; }
        public string Url { get; set; }
        public string Description { get; set; }
        public DateTime DateAdded { get; set; }
        public bool isMain { get; set; }
    }
}

และกำหนดให้ UserForDetailedDto ดึง Photo จาก DTOs นี้แทน

public ICollection<PhotoForDetailedDto> Photos { get; set; }

เพิ่ม Auto Mapper Profile ใน AutoMapperProfiles.cs

using AutoMapper;
using SocialApp_API.Dtos;
using SocialApp_API.Models;

namespace SocialApp_API.Helpers
{
    public class AutoMapperProfiles : Profile
    {
        public AutoMapperProfiles()
        {
            CreateMap<User, UserForListDto>();
            CreateMap<User, UserForDetailedDto>();
            CreateMap<Photo, PhotoForDetailedDto>();
        }
    }
}

ทดสอบรันและเรียก API อีกครั้ง จะพบว่า Photos ใน Detail มาถูกต้องตามที่ต้องการแล้ว

คราวนี้ก็เหลือ Age, PhotoUrl

ทำการเพิ่มการดัดแปลงข้อมูลใน AutoMapperProfiles.cs

using System.Linq;
using AutoMapper;
using SocialApp_API.Dtos;
using SocialApp_API.Models;

namespace SocialApp_API.Helpers
{
    public class AutoMapperProfiles : Profile
    {
        public AutoMapperProfiles()
        {
            CreateMap<User, UserForListDto>()
                .ForMember(dest => dest.PhotoUrl, opt => opt.MapFrom(src =>
                    src.Photos.FirstOrDefault(p => p.isMain).Url));
            CreateMap<User, UserForDetailedDto>()
                .ForMember(dest => dest.PhotoUrl, opt => opt.MapFrom(src =>
                    src.Photos.FirstOrDefault(p => p.isMain).Url));
            CreateMap<Photo, PhotoForDetailedDto>();
        }
    }
}

ลองรันและทดสอบเรียก API จะพบว่ามี PhotoUrl มาแล้ว

ต่อไปก็เหลือเฉพาะ Age แล้ว

เพิ่มฟังก์ชันการคำนวณอายุไว้ใน Extensions.cs

using System;
using Microsoft.AspNetCore.Http;

namespace SocialApp_API.Helpers
{
    public static class Extensions
    {
        public static void AddApplicationError(this HttpResponse response, string message)
        {
            response.Headers.Add("Application-Error", message);
            response.Headers.Add("Access-Control-Expose-Headers", "Application-Error");
            response.Headers.Add("Access-Control-Allow-Origin", "*");
        }

        public static int CalculateAge(this DateTime theDateTime)
        {
            var age = DateTime.Today.Year - theDateTime.Year;
            if (theDateTime.AddYears(age) > DateTime.Today)
                age--;
            
            return age;
        }
    }

}

เพิ่มการคำนวณอายุใน AutoMapperProfiles.cs

using System.Linq;
using AutoMapper;
using SocialApp_API.Dtos;
using SocialApp_API.Models;

namespace SocialApp_API.Helpers
{
    public class AutoMapperProfiles : Profile
    {
        public AutoMapperProfiles()
        {
            CreateMap<User, UserForListDto>()
                .ForMember(dest => dest.PhotoUrl, opt => opt.MapFrom(src =>
                    src.Photos.FirstOrDefault(p => p.isMain).Url))
                .ForMember(dest => dest.Age, opt =>
                    opt.MapFrom(src => src.DateOfBirth.CalculateAge()));
            CreateMap<User, UserForDetailedDto>()
                .ForMember(dest => dest.PhotoUrl, opt => opt.MapFrom(src =>
                    src.Photos.FirstOrDefault(p => p.isMain).Url))
                .ForMember(dest => dest.Age, opt =>
                    opt.MapFrom(src => src.DateOfBirth.CalculateAge()));
            CreateMap<Photo, PhotoForDetailedDto>();
        }
    }
}

ทดสอบรันและตรวจสอบผลที่ได้จาก API คราวนี้ข้อมูลก็จะครบถ้วนสมบูรณ์แล้ว

โปรดติดตามตอนต่อไป…

Line
Facebook
Twitter
Google
การติดตั้งและใช้งาน Datatables ร่วมกับ Angular
[ตอนที่ 16] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การทำ Sorting
[ตอนที่ 15] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การทำ Filtering
[ตอนที่ 14] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การใช้งาน Paging
เตรียม Atom สำหรับ React Native #3
เตรียม Visual Studio Code สำหรับ React Native #2
การติดตั้ง React Native บน macOs #1
การกำหนดค่า TF_MIN_GPU_MULTIPROCESSOR_COUNT เพื่อให้ TensorFlow ใช้งาน GPU ทุกตัว
ติดตั้ง Ubuntu 17.04 ใช้งานร่วมกับ Windows 10
การติดตั้ง TensorFlow & Caffe บน Ubuntu 16.04