RazorMovieEFCore ใช้ MySQL

  1. สร้างโปรเจ็กส์ Razor
  2. เพิ่มโมเดล Movie
  3. Migration
  4. เปลี่ยนมาใช้ MySQL
  5. รันดูหน้า Movies
  6. Seed the database
  7. Update pages
  8. Add search
  9. Add a new field
  10. Validation

1. สร้างโปรเจ็กส์ Razor

เปิด VS 2019 สร้างโปรเจ็กส์ใหม่แบบ ASP.NET Core Web Application

เลือก Web Application

เพิ่มไฟล์ .gitignore

.vs/
bin/
obj/
*.user
artifacts/
.vscode/
.dotnet/
*.binlog

รันจะเจอหน้า Home

และหน้า Privacy

2.เพิ่มโมเดล Movie

สร้างโฟลเดอร์ชื่อ Models

เพิ่มคลาส Movie

using System;
using System.ComponentModel.DataAnnotations;

namespace RazorMovieEFCore.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }

        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }
}

สร้างโฟลเดอร์ Pages > Movies

Scaffold the movie model

เลือก Razor Pages using Entity Framework (CRUD)

เลือกโมเดล Movie และเพิ่มคลาส context

3.Migration

ใช้ Package Manager Console (PMC)

PM> Add-Migration Initial
PM> Update-Database

ตอนนี้จะได้ดาต้าเบสละ ลองเปิดดูด้วย SQL Server Object Explorer

4.เปลี่ยนมาใช้ MySQL

สามารถเปลี่ยนไปใช้ MySQL ได้ด้วยการติดตั้ง Pomelo.EntityFrameworkCore.MySql

PM> Install-Package Pomelo.EntityFrameworkCore.MySql

ที่ไฟล์ appsettings.json เปลี่ยน ConnectionStrings จาก

  "ConnectionStrings": {
    "RazorMovieEFCoreContext": "Server=(localdb)\\mssqllocaldb;Database=RazorMovieEFCoreContext-c746480b-4d3e-40f2-abf5-3bd2302f1542;Trusted_Connection=True;MultipleActiveResultSets=true"
  }


เป็น

"ConnectionStrings": {
  "RazorMovieEFCoreContext": "Server=localhost;User Id=user1;Password=pass1;Database=demo"
}

ที่ไฟล์ Startup.cs ใน.ConfigureServices เปลี่ยนจาก UseSqlServer

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddDbContext<RazorMovieEFCoreContext>(options =>                    options.UseSqlServer(Configuration.GetConnectionString("RazorMovieEFCoreContext")));
}

เป็น UseMySql

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddDbContext<RazorMovieEFCoreContext>(options =>                    options.UseMySql(Configuration.GetConnectionString("RazorMovieEFCoreContext")));
}

ลบไฟล์ในโฟลเดอร์ Migrations ที่ได้จากการทำ Migraion ครั้งก่อน

ทำ Migration ใหม่ (สำหรับ MySQL)

PM> Add-Migration Initial
PM> Update-Database

เปิดด้วย phpMyAdmin จะเห็นตาราง movie ละ

ลอง export ออกมาดูจะได้

CREATE TABLE `movie` (
  `ID` int(11) NOT NULL,
  `Title` longtext DEFAULT NULL,
  `ReleaseDate` datetime(6) NOT NULL,
  `Genre` longtext DEFAULT NULL,
  `Price` decimal(65,30) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

5.รันดูหน้า Movies

เรียกหน้า Movies ที่ https://localhost:44333/Movies

หน้าเพิ่มรายการ
หน้าหลัก – แสดงรายการ
หน้า Detail

6.Seed the database

เพิ่มคลาส SeedData

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorMovieEFCore.Data;
using System;
using System.Linq;

namespace RazorMovieEFCore.Models
{
    public static class SeedData
    {
        public static void Initialize(IServiceProvider serviceProvider)
        {
            using (var context = new RazorMovieEFCoreContext(
                serviceProvider.GetRequiredService<
                    DbContextOptions<RazorMovieEFCoreContext>>()))
            {
                // Look for any movies.
                if (context.Movie.Any())
                {
                    return;   // DB has been seeded
                }

                context.Movie.AddRange(
                    new Movie
                    {
                        Title = "When Harry Met Sally",
                        ReleaseDate = DateTime.Parse("1989-2-12"),
                        Genre = "Romantic Comedy",
                        Price = 7.99M
                    },

                    new Movie
                    {
                        Title = "Ghostbusters ",
                        ReleaseDate = DateTime.Parse("1984-3-13"),
                        Genre = "Comedy",
                        Price = 8.99M
                    },

                    new Movie
                    {
                        Title = "Ghostbusters 2",
                        ReleaseDate = DateTime.Parse("1986-2-23"),
                        Genre = "Comedy",
                        Price = 9.99M
                    },

                    new Movie
                    {
                        Title = "Rio Bravo",
                        ReleaseDate = DateTime.Parse("1959-4-15"),
                        Genre = "Western",
                        Price = 3.99M
                    }
                );
                context.SaveChanges();
            }
        }
    }
}

แก้ Program.cs

public static void Main(string[] args)
{
    //CreateHostBuilder(args).Build().Run();
            
    var host = CreateHostBuilder(args).Build();

    using (var scope = host.Services.CreateScope())
    {
        var services = scope.ServiceProvider;

        try
        {
            SeedData.Initialize(services);
        }
        catch (Exception ex)
        {
            var logger = services.GetRequiredService<ILogger<Program>>();
            logger.LogError(ex, "An error occurred seeding the DB.");
        }
    }

    host.Run();
}

ลบรายการในตาราง Movie

รันโปรแกรม จะเห็นรายการเพิ่มเข้ามาจากการ Seed

7.Update pages

เปลี่ยนคำว่า ReleaseDate เป็น Release Date

แก้ไข Movie.cs

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorMovieEFCore.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        [Column(TypeName = "decimal(18, 2)")] 
        public decimal Price { get; set; }
    }
}

แก้ไข link ของ Edit , Detail , Delete จาก

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

เป็น

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

ด้วยการเปลี่ยนจาก @page เป็น @page "{id:int}" ที่ไฟล์ Edit, Details, Delete ในโฟลเดอร์ Movies (ไฟล์ Index ไม่ต้องแก้)

8.Add search

ทำการ search จาก title

แก้ไขไฟล์ Pages/Movies/Index.cshtml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using RazorMovieEFCore.Data;
using RazorMovieEFCore.Models;

namespace RazorMovieEFCore
{
    public class IndexModel : PageModel
    {
        private readonly RazorMovieEFCore.Data.RazorMovieEFCoreContext _context;

        public IndexModel(RazorMovieEFCore.Data.RazorMovieEFCoreContext context)
        {
            _context = context;
        }

        public IList<Movie> Movie { get;set; }

        [BindProperty(SupportsGet = true)]
        public string SearchString { get; set; }
        // Requires using Microsoft.AspNetCore.Mvc.Rendering;
        public SelectList Genres { get; set; }
        [BindProperty(SupportsGet = true)]
        public string MovieGenre { get; set; }

        public async Task OnGetAsync()
        {
            //Movie = await _context.Movie.ToListAsync();
            
            var movies = from m in _context.Movie
                         select m;
            if (!string.IsNullOrEmpty(SearchString))
            {
                movies = movies.Where(s => s.Title.Contains(SearchString));
            }

            Movie = await movies.ToListAsync();
        }
    }
}

ลองค้นหาแบบนี้ https://localhost:44333/Movies?searchString=Ghost

เปลี่ยนเป็นค้นหาแบบนี้ https://localhost:44333/Movies/Ghost โดยการแก้ไขไฟล์ Index

@page "{searchString?}"

ใส่กล่อง Search ที่ไฟล์ Pages/Movies/Index.cshtml

@page "{searchString?}"
@model RazorMovieEFCore.IndexModel

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

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        Title: <input type="text" asp-for="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
...

Search by genre

ไฟล์ Pages/Movies/Index.cshtml.cs

public async Task OnGetAsync()
{
    // Use LINQ to get list of genres.
    IQueryable<string> genreQuery = from m in _context.Movie
                                    orderby m.Genre
                                    select m.Genre;

    //Movie = await _context.Movie.ToListAsync();

    var movies = from m in _context.Movie
                    select m;
    if (!string.IsNullOrEmpty(SearchString))
    {
        movies = movies.Where(s => s.Title.Contains(SearchString));
    }

    if (!string.IsNullOrEmpty(MovieGenre))
    {
        movies = movies.Where(x => x.Genre == MovieGenre);
    }

    Genres = new SelectList(await genreQuery.Distinct().ToListAsync());

    Movie = await movies.ToListAsync();
}

ไฟล์ Pages/Movies/Index.cshtml

@page "{searchString?}"
@model RazorMovieEFCore.IndexModel

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

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <select asp-for="MovieGenre" asp-items="Model.Genres">
            <option value="">All</option>
        </select>
        Title: <input type="text" asp-for="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
...

9.Add a new field

ที่ไฟล์ Movie.cs เพิ่ม Rating

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorMovieEFCore.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        [Column(TypeName = "decimal(18, 2)")] 
        public decimal Price { get; set; }
        public string Rating { get; set; }
    }
}

เพิ่มคอลัมน์ Rating ที่ Pages/Movies/Index.cshtml

@page "{searchString?}"
@model RazorMovieEFCore.IndexModel

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

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <select asp-for="MovieGenre" asp-items="Model.Genres">
            <option value="">All</option>
        </select>
        Title: <input type="text" asp-for="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Price)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Rating)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Movie)
        {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Rating)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
        }
    </tbody>
</table>

แก้ไข SeedData.cs (ทุกอันที่ new Movie)

context.Movie.AddRange(
    new Movie
    {
        Title = "When Harry Met Sally",
        ReleaseDate = DateTime.Parse("1989-2-12"),
        Genre = "Romantic Comedy",
        Price = 7.99M,
        Rating = "R"
    },

อัพเดทดาต้าเบส

PM> Add-Migration Rating
PM> Update-Database

ถ้าดูที่ตาราง Movie ตอนนี้จะเห็น Rating มีค่า null ให้ทำการลบข้อมูลในตาราง Movie ให้หมด แล้วค่อยรันโปรแกรมใหม่ เพื่อให้เกิดการ Seed

10.Validation

Update the Movie class to take advantage of the built-in RequiredStringLengthRegularExpression, and Range validation attributes.

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorMovieEFCore.Models
{
    public class Movie
    {
        public int ID { get; set; }

        [StringLength(60, MinimumLength = 3)]
        [Required]
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        //[DisplayFormat(DataFormatString = "{0:dd-MMM-yyyy}", ApplyFormatInEditMode = false)]
        public DateTime ReleaseDate { get; set; }

        [Range(1, 100)]
        [DataType(DataType.Currency)]
        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }

        [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
        [Required]
        [StringLength(30)]
        public string Genre { get; set; }

        [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
        [StringLength(5)]
        [Required]
        public string Rating { get; set; }
    }
}
PM> Add-Migration New_DataAnnotations
PM> Update-Database