diff --git a/ASP_NET_CORE/Album/Album.csproj b/ASP_NET_CORE/Album/Album.csproj
index 8d61d1d..944a54d 100644
--- a/ASP_NET_CORE/Album/Album.csproj
+++ b/ASP_NET_CORE/Album/Album.csproj
@@ -22,4 +22,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/ASP_NET_CORE/Album/Album.sln b/ASP_NET_CORE/Album/Album.sln
new file mode 100644
index 0000000..b16a438
--- /dev/null
+++ b/ASP_NET_CORE/Album/Album.sln
@@ -0,0 +1,17 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Album", "Album.csproj", "{CF95F789-A60C-4810-BE50-06D12856E492}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {CF95F789-A60C-4810-BE50-06D12856E492}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CF95F789-A60C-4810-BE50-06D12856E492}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CF95F789-A60C-4810-BE50-06D12856E492}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CF95F789-A60C-4810-BE50-06D12856E492}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Add.cshtml b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Add.cshtml
new file mode 100644
index 0000000..1526aed
--- /dev/null
+++ b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Add.cshtml
@@ -0,0 +1,31 @@
+@page "/admin/role/updaterole/{handler?}/"
+@model Album.Areas.Admin.Pages.Role.AddModel
+@{
+ var btnText = Model.IsUpdate ? "Cập nhật" : "Tạo mới";
+}
+
+
+
@ViewData["Title"]
+
+
+
+
+@section Scripts {
+
+}
diff --git a/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Add.cshtml.cs b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Add.cshtml.cs
new file mode 100644
index 0000000..ae4bd7c
--- /dev/null
+++ b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Add.cshtml.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using Album.Models;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace Album.Areas.Admin.Pages.Role {
+ public class AddModel : PageModel {
+ private readonly RoleManager _roleManager;
+
+ public AddModel (RoleManager roleManager) {
+ _roleManager = roleManager;
+ }
+
+ [TempData] // Sử dụng Session
+ public string StatusMessage { get; set; }
+
+ public class InputModel {
+ public string ID { set; get; }
+
+ [Required (ErrorMessage = "Phải nhập tên role")]
+ [Display (Name = "Tên của Role")]
+ [StringLength (100, ErrorMessage = "{0} dài {2} đến {1} ký tự.", MinimumLength = 3)]
+ public string Name { set; get; }
+
+ }
+ [BindProperty]
+ public InputModel Input { set; get; }
+
+ [BindProperty]
+ public bool IsUpdate { set; get; }
+
+ public IActionResult OnGet () => NotFound ("Không thấy");
+ public IActionResult OnPost () => NotFound ("Không thấy");
+ public IActionResult OnPostStartNewRole () {
+ StatusMessage = "Hãy nhập thông tin để tạo role mới";
+ IsUpdate = false;
+ ModelState.Clear ();
+ return Page ();
+ }
+ public async Task OnPostStartUpdate () {
+ StatusMessage = null;
+ IsUpdate = true;
+ if (Input.ID == null) {
+ StatusMessage = "Error: Không có thông tin về Role";
+ return Page ();
+ }
+ var result = await _roleManager.FindByIdAsync (Input.ID);
+ if (result != null) {
+ Input.Name = result.Name;
+ ViewData["Title"] = "Cập nhật role : " + Input.Name;
+ ModelState.Clear ();
+ } else {
+ StatusMessage = "Error: Không có thông tin về Role ID = " + Input.ID;
+ }
+
+ return Page ();
+ }
+
+ public async Task OnPostAddOrUpdate () {
+
+ if (!ModelState.IsValid) {
+ StatusMessage = null;
+ return Page ();
+ }
+
+ if (IsUpdate) {
+ // CẬP NHẬT
+ if (Input.ID == null) {
+ ModelState.Clear ();
+ StatusMessage = "Error: Không có thông tin về role";
+ return Page ();
+ }
+ var result = await _roleManager.FindByIdAsync (Input.ID);
+ if (result != null) {
+ result.Name = Input.Name;
+ var roleUpdateRs = await _roleManager.UpdateAsync (result);
+ if (roleUpdateRs.Succeeded) {
+ StatusMessage = "Đã cập nhật role thành công";
+ } else {
+ StatusMessage = "Error: ";
+ foreach (var er in roleUpdateRs.Errors) {
+ StatusMessage += er.Description;
+ }
+ }
+ } else {
+ StatusMessage = "Error: Không tìm thấy Role cập nhật";
+ }
+
+ } else {
+ // TẠO MỚI
+ var newRole = new IdentityRole (Input.Name);
+ var rsNewRole = await _roleManager.CreateAsync (newRole);
+ if (rsNewRole.Succeeded) {
+ StatusMessage = $"Đã tạo role mới thành công: {newRole.Name}";
+ return RedirectToPage("./Index");
+ } else {
+ StatusMessage = "Error: ";
+ foreach (var er in rsNewRole.Errors) {
+ StatusMessage += er.Description;
+ }
+ }
+ }
+
+ return Page ();
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/AddUserRole.cshtml b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/AddUserRole.cshtml
new file mode 100644
index 0000000..3485b82
--- /dev/null
+++ b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/AddUserRole.cshtml
@@ -0,0 +1,43 @@
+@page
+@model Album.Areas.Admin.Pages.Role.AddUserRole
+@{
+ ViewData["Title"] = "Cập nhật role cho User";
+}
+
+@ViewData["Title"]
+
+
+
Chọn các role gán cho @Model.Input.Name
+
+
+
+
+@section Scripts {
+
+
+
+
+
+
+}
+
diff --git a/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/AddUserRole.cshtml.cs b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/AddUserRole.cshtml.cs
new file mode 100644
index 0000000..c36a79e
--- /dev/null
+++ b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/AddUserRole.cshtml.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using Album.Models;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+
+namespace Album.Areas.Admin.Pages.Role {
+ public class AddUserRole : PageModel {
+ private readonly RoleManager _roleManager;
+ private readonly UserManager _userManager;
+
+
+ public AddUserRole (RoleManager roleManager,
+ UserManager userManager) {
+ _roleManager = roleManager;
+ _userManager = userManager;
+ }
+
+ public class InputModel {
+ [Required]
+ public string ID { set; get; }
+ public string Name { set; get; }
+
+ public string[] RoleNames {set; get;}
+
+ }
+
+ [BindProperty]
+ public InputModel Input { set; get; }
+
+ [BindProperty]
+ public bool isConfirmed { set; get; }
+
+ [TempData] // Sử dụng Session
+ public string StatusMessage { get; set; }
+
+ public IActionResult OnGet () => NotFound ("Không thấy");
+
+ public List AllRoles {set; get;} = new List();
+
+ public async Task OnPost () {
+
+
+ var user = await _userManager.FindByIdAsync (Input.ID);
+ if (user == null) {
+ return NotFound ("Không thấy role cần xóa");
+ }
+
+ var roles = await _userManager.GetRolesAsync(user);
+ var allroles = await _roleManager.Roles.ToListAsync();
+
+ allroles.ForEach((r) => {
+ AllRoles.Add(r.Name);
+ });
+
+ if (!isConfirmed) {
+ Input.RoleNames = roles.ToArray();
+ isConfirmed = true;
+ StatusMessage = "";
+ ModelState.Clear();
+ }
+ else {
+ // Update add and remove
+ StatusMessage = "Vừa cập nhật";
+ if (Input.RoleNames == null) Input.RoleNames = new string[] {};
+ foreach (var rolename in Input.RoleNames)
+ {
+ if (roles.Contains(rolename)) continue;
+ await _userManager.AddToRoleAsync(user, rolename);
+ }
+ foreach (var rolename in roles)
+ {
+ if (Input.RoleNames.Contains(rolename)) continue;
+ await _userManager.RemoveFromRoleAsync(user, rolename);
+ }
+
+ }
+
+ Input.Name = user.UserName;
+ return Page ();
+ }
+ }
+}
\ No newline at end of file
diff --git a/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Delete.cshtml b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Delete.cshtml
new file mode 100644
index 0000000..c20ee10
--- /dev/null
+++ b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Delete.cshtml
@@ -0,0 +1,24 @@
+@page
+@model Album.Areas.Admin.Pages.Role.DeleteModel
+@{
+ ViewData["Title"] = "Xóa role";
+}
+
+@ViewData["Title"]
+
+
+
Bạn có chăc chắn xóa Role @Model.Input.Name
+
+
+
+
+@section Scripts {
+
+}
+
diff --git a/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Delete.cshtml.cs b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Delete.cshtml.cs
new file mode 100644
index 0000000..7ef4ae9
--- /dev/null
+++ b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Delete.cshtml.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using Album.Models;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace Album.Areas.Admin.Pages.Role {
+ public class DeleteModel : PageModel {
+ private readonly RoleManager _roleManager;
+
+ public DeleteModel (RoleManager roleManager) {
+ _roleManager = roleManager;
+ }
+
+ public class InputModel {
+ [Required]
+ public string ID { set; get; }
+ public string Name { set; get; }
+
+ }
+
+ [BindProperty]
+ public InputModel Input { set; get; }
+
+ [BindProperty]
+ public bool isConfirmed { set; get; }
+
+ [TempData] // Sử dụng Session
+ public string StatusMessage { get; set; }
+
+ public IActionResult OnGet () => NotFound ("Không thấy");
+
+ public async Task OnPost () {
+
+ if (!ModelState.IsValid) {
+ return NotFound ("Không xóa được");
+ }
+
+ var role = await _roleManager.FindByIdAsync (Input.ID);
+ if (role == null) {
+ return NotFound ("Không thấy role cần xóa");
+ }
+
+ ModelState.Clear ();
+
+ if (isConfirmed) {
+ //Xóa
+ await _roleManager.DeleteAsync (role);
+ StatusMessage = "Đã xóa " + role.Name;
+
+ return RedirectToPage ("Index");
+ } else {
+ Input.Name = role.Name;
+ isConfirmed = true;
+
+ }
+
+ return Page ();
+ }
+ }
+}
\ No newline at end of file
diff --git a/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Index.cshtml b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Index.cshtml
new file mode 100644
index 0000000..2cec790
--- /dev/null
+++ b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Index.cshtml
@@ -0,0 +1,36 @@
+@page "/admin/role/"
+@model IndexModel
+
+Danh sách các role
+
+
+
+Gán role cho người dùng
+
+
+
+ Role ID
+ Tên
+ Tác vụ
+
+ @foreach (var role in @Model.roles)
+ {
+
+ @role.Id
+ @role.Name
+
+
+
+
+
+ }
+
\ No newline at end of file
diff --git a/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Index.cshtml.cs b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Index.cshtml.cs
new file mode 100644
index 0000000..0f25a75
--- /dev/null
+++ b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/Index.cshtml.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Album.Models;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+
+namespace Album.Areas.Admin.Pages.Role
+{
+
+ public class IndexModel : PageModel
+ {
+ private readonly RoleManager _roleManager;
+
+ public IndexModel(RoleManager roleManager)
+ {
+ _roleManager = roleManager;
+ }
+ public List roles {set; get;}
+
+ [TempData] // Sử dụng Session lưu thông báo
+ public string StatusMessage { get; set; }
+
+ public async Task OnGet()
+ {
+ roles = await _roleManager.Roles.ToListAsync();
+ return Page();
+ }
+ }
+}
diff --git a/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/User.cshtml b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/User.cshtml
new file mode 100644
index 0000000..cef13eb
--- /dev/null
+++ b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/User.cshtml
@@ -0,0 +1,49 @@
+@page "/admin/role/users/"
+@model Album.Areas.Admin.Pages.Role.UserModel
+@{
+ ViewData["Title"] = "DANH SÁCH NGƯỜI DÙNG";
+}
+
+
+@ViewData["Title"]
+
+
+
+
+ UserName
+ Roles
+ Actions
+
+ @foreach (var user in @Model.users)
+ {
+
+ @user.UserName
+ @user.listroles
+
+
+
+
+
+ }
+
+
+@section Scripts {
+
+}
+@{
+
+ Func generateUrl = (int? _pagenumber) => {
+ return Url.Page("./User", new {pageNumber = _pagenumber});
+ };
+
+ var datapaging = new {
+ currentPage = Model.pageNumber,
+ countPages = Model.totalPages,
+ generateUrl = generateUrl
+ };
+
+}
+
diff --git a/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/User.cshtml.cs b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/User.cshtml.cs
new file mode 100644
index 0000000..7e16000
--- /dev/null
+++ b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/User.cshtml.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using Album.Models;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+using Microsoft.EntityFrameworkCore;
+
+namespace Album.Areas.Admin.Pages.Role {
+ public class UserModel : PageModel {
+ const int USER_PER_PAGE = 10;
+ private readonly RoleManager _roleManager;
+ private readonly UserManager _userManager;
+
+ public UserModel (RoleManager roleManager,
+ UserManager userManager) {
+ _roleManager = roleManager;
+ _userManager = userManager;
+ }
+
+ public class UserInList : AppUser {
+ // Liệt kê các Role của User ví dụ: "Admin,Editor" ...
+ public string listroles {set; get;}
+ }
+
+ public List users;
+ public int totalPages {set; get;}
+
+ [TempData] // Sử dụng Session
+ public string StatusMessage { get; set; }
+
+ [BindProperty(SupportsGet=true)]
+ public int pageNumber {set;get;}
+
+ public IActionResult OnPost() => NotFound("Cấm post");
+
+ public async Task OnGet() {
+
+ var cuser = await _userManager.GetUserAsync(User);
+ await _userManager.AddToRolesAsync(cuser, new string[] { "Editor"});
+
+ if (pageNumber == 0)
+ pageNumber = 1;
+
+ var lusers = (from u in _userManager.Users
+ orderby u.UserName
+ select new UserInList() {
+ Id = u.Id, UserName = u.UserName,
+ });
+
+
+ int totalUsers = await lusers.CountAsync();
+
+
+ totalPages = (int)Math.Ceiling((double)totalUsers / USER_PER_PAGE);
+
+ users = await lusers.Skip(USER_PER_PAGE * (pageNumber - 1)).Take(USER_PER_PAGE).ToListAsync();
+
+ // users.ForEach(async (user) => {
+ // var roles = await _userManager.GetRolesAsync(user);
+ // user.listroles = string.Join(",", roles.ToList());
+ // });
+
+ foreach (var user in users)
+ {
+ var roles = await _userManager.GetRolesAsync(user);
+ user.listroles = string.Join(",", roles.ToList());
+ }
+
+ return Page();
+ }
+ }
+}
\ No newline at end of file
diff --git a/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/_StatusMessage.cshtml b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/_StatusMessage.cshtml
new file mode 100644
index 0000000..208a424
--- /dev/null
+++ b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/_StatusMessage.cshtml
@@ -0,0 +1,10 @@
+@model string
+
+@if (!String.IsNullOrEmpty(Model))
+{
+ var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
+
+ ×
+ @Model
+
+}
\ No newline at end of file
diff --git a/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/_ViewImports.cshtml b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/_ViewImports.cshtml
new file mode 100644
index 0000000..9d5fdff
--- /dev/null
+++ b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/_ViewImports.cshtml
@@ -0,0 +1,2 @@
+@using Album.Areas.Admin.Pages.Role
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
diff --git a/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/_ViewStart.cshtml b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/_ViewStart.cshtml
new file mode 100644
index 0000000..6791b5b
--- /dev/null
+++ b/ASP_NET_CORE/Album/Areas/Admin/Pages/Role/_ViewStart.cshtml
@@ -0,0 +1,3 @@
+@{
+ Layout = "/Pages/Shared/_Layout.cshtml";
+}
diff --git a/ASP_NET_CORE/Album/Data/AppDbContext.cs b/ASP_NET_CORE/Album/Data/AppDbContext.cs
index 7bca196..ea71211 100644
--- a/ASP_NET_CORE/Album/Data/AppDbContext.cs
+++ b/ASP_NET_CORE/Album/Data/AppDbContext.cs
@@ -1,13 +1,25 @@
using Album.Models;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
namespace Album.Data {
// Kế thừa từ IdentityDbContext nên có sẵn các DbSet
// UserRoles Roles RoleClaimsUsers UserClaims UserLogins UserTokens
public class AppDbContext : IdentityDbContext {
+ public static readonly ILoggerFactory loggerFactory = LoggerFactory.Create (builder => {
+ builder
+ // .AddFilter (DbLoggerCategory.Database.Command.Name, LogLevel.Warning)
+ // .AddFilter (DbLoggerCategory.Query.Name, LogLevel.Debug)
+ .AddConsole ();
+ });
public AppDbContext (DbContextOptions options) : base (options) { }
+ protected override void OnConfiguring (DbContextOptionsBuilder optionsBuilder) {
+ base.OnConfiguring (optionsBuilder);
+ optionsBuilder
+ .UseLoggerFactory (loggerFactory);
+ }
protected override void OnModelCreating (ModelBuilder builder) {
diff --git a/ASP_NET_CORE/Album/Pages/Shared/_Layout.cshtml b/ASP_NET_CORE/Album/Pages/Shared/_Layout.cshtml
index b3d049d..84870b0 100644
--- a/ASP_NET_CORE/Album/Pages/Shared/_Layout.cshtml
+++ b/ASP_NET_CORE/Album/Pages/Shared/_Layout.cshtml
@@ -24,6 +24,9 @@
Privacy
+
+ Role
+
@await Html.PartialAsync("_LoginPartial")
diff --git a/ASP_NET_CORE/Album/Pages/Shared/_Paging.cshtml b/ASP_NET_CORE/Album/Pages/Shared/_Paging.cshtml
new file mode 100644
index 0000000..3c542f7
--- /dev/null
+++ b/ASP_NET_CORE/Album/Pages/Shared/_Paging.cshtml
@@ -0,0 +1,114 @@
+@model dynamic
+@{
+ int currentPage = Model.currentPage;
+ int countPages = Model.countPages;
+ var generateUrl = Model.generateUrl;
+
+ if (currentPage > countPages)
+ currentPage = countPages;
+
+ if (countPages <= 1) return;
+
+
+ int? preview = null;
+ int? next = null;
+
+ if (currentPage > 1)
+ preview = currentPage - 1;
+
+ if (currentPage < countPages)
+ next = currentPage + 1;
+
+ // Các trang hiện thị trong điều hướng
+ List pagesRanges = new List();
+
+
+ int delta = 5; // Số trang mở rộng về mỗi bên trang hiện tại
+ int remain = delta * 2; // Số trang hai bên trang hiện tại
+ pagesRanges.Add(currentPage);
+ // Các trang phát triển về hai bên trang hiện tại
+ for (int i = 1; i <= delta; i++)
+ {
+ if (currentPage + i <= countPages) {
+ pagesRanges.Add(currentPage + i);
+ remain --;
+ }
+
+ if (currentPage - i >= 1) {
+ pagesRanges.Insert(0, currentPage - i);
+ remain --;
+ }
+
+ }
+ // Xử lý thêm vào các trang cho đủ remain (xảy ra ở đầu mút của khoảng trang không đủ
+ // trang chèn vào)
+ if (remain > 0) {
+ if (pagesRanges[0] == 1) {
+ for (int i = 1; i <= remain; i++)
+ {
+ if (pagesRanges.Last() + 1 <= countPages) {
+ pagesRanges.Add(pagesRanges.Last() + 1);
+ }
+ }
+ }
+ else {
+ for (int i = 1; i <= remain; i++)
+ {
+ if (pagesRanges.First() - 1 > 1) {
+ pagesRanges.Insert(0, pagesRanges.First() - 1);
+ }
+ }
+ }
+ }
+
+}
+
+
+
\ No newline at end of file
diff --git a/ASP_NET_CORE/Album/mailssave/018c28a5-0ad4-4606-b05e-0e9e3bef82fc.eml b/ASP_NET_CORE/Album/mailssave/018c28a5-0ad4-4606-b05e-0e9e3bef82fc.eml
deleted file mode 100644
index 0532659..0000000
--- a/ASP_NET_CORE/Album/mailssave/018c28a5-0ad4-4606-b05e-0e9e3bef82fc.eml
+++ /dev/null
@@ -1,13 +0,0 @@
-From: =?utf-8?b?VMOqbiBIaeG7h24gVGjhu4sgKHbDrSBk4bul?= XUANTHULAB
-
-Date: Mon, 14 Sep 2020 09:58:22 +0700
-Subject: =?utf-8?b?WMOhYyBuaOG6rW4=?=
-Message-Id:
-Sender: =?utf-8?b?VMOqbiBIaeG7h24gVGjhu4sgKHbDrSBk4bul?= XUANTHULAB
-
-To: admin.xuanthulab.net@gmail.com
-MIME-Version: 1.0
-Content-Type: text/html; charset=utf-8
-Content-Id:
-
-Hãy xác nhận Email của bạn bằng cách bấm vào đây .
diff --git a/ASP_NET_CORE/Album/package-lock.json b/ASP_NET_CORE/Album/package-lock.json
new file mode 100644
index 0000000..6289971
--- /dev/null
+++ b/ASP_NET_CORE/Album/package-lock.json
@@ -0,0 +1,11 @@
+{
+ "requires": true,
+ "lockfileVersion": 1,
+ "dependencies": {
+ "multiple-select": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/multiple-select/-/multiple-select-1.5.2.tgz",
+ "integrity": "sha512-sTNNRrjnTtB1b1+HTKcjQ/mjWY7Gvigo9F3C/3oTQCTFEpYzwaRYFPRAOu2SogfA1hEfyJTXjyS1VAbanJMsmA=="
+ }
+ }
+}
diff --git a/ASP_NET_CORE/Album/wwwroot/lib/multiple-select/multiple-select.min.css b/ASP_NET_CORE/Album/wwwroot/lib/multiple-select/multiple-select.min.css
new file mode 100644
index 0000000..5a6f1b1
--- /dev/null
+++ b/ASP_NET_CORE/Album/wwwroot/lib/multiple-select/multiple-select.min.css
@@ -0,0 +1,10 @@
+/**
+ * multiple-select - Multiple select is a jQuery plugin to select multiple elements with checkboxes :).
+ *
+ * @version v1.5.2
+ * @homepage http://multiple-select.wenzhixin.net.cn
+ * @author wenzhixin (http://wenzhixin.net.cn/)
+ * @license MIT
+ */
+
+@charset "UTF-8";.ms-offscreen{clip:rect(0 0 0 0)!important;width:1px!important;height:1px!important;border:0!important;margin:0!important;padding:0!important;overflow:hidden!important;position:absolute!important;outline:0!important;left:auto!important;top:auto!important}.ms-parent{display:inline-block;position:relative;vertical-align:middle}.ms-choice{display:block;width:100%;height:26px;padding:0;overflow:hidden;cursor:pointer;border:1px solid #aaa;text-align:left;white-space:nowrap;line-height:26px;color:#444;text-decoration:none;border-radius:4px;background-color:#fff}.ms-choice.disabled{background-color:#f4f4f4;background-image:none;border:1px solid #ddd;cursor:default}.ms-choice>span{position:absolute;top:0;left:0;right:20px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;padding-left:8px}.ms-choice>span.placeholder{color:#999}.ms-choice>div.icon-close{position:absolute;top:0;right:16px;height:100%;width:16px}.ms-choice>div.icon-close:before{content:'×';color:#888;font-weight:bold;position:absolute;top:50%;margin-top:-14px}.ms-choice>div.icon-close:hover:before{color:#333}.ms-choice>div.icon-caret{position:absolute;width:0;height:0;top:50%;right:8px;margin-top:-2px;border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px}.ms-choice>div.icon-caret.open{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.ms-drop{width:auto;min-width:100%;overflow:hidden;display:none;margin-top:-1px;padding:0;position:absolute;z-index:1000;background:#fff;color:#000;border:1px solid #aaa;border-radius:4px}.ms-drop.bottom{top:100%;box-shadow:0 4px 5px rgba(0,0,0,0.15)}.ms-drop.top{bottom:100%;box-shadow:0 -4px 5px rgba(0,0,0,0.15)}.ms-search{display:inline-block;margin:0;min-height:26px;padding:2px;position:relative;white-space:nowrap;width:100%;z-index:10000;box-sizing:border-box}.ms-search input{width:100%;height:auto!important;min-height:24px;padding:0 5px;margin:0;outline:0;font-family:sans-serif;border:1px solid #aaa;border-radius:5px;box-shadow:none}.ms-drop ul{overflow:auto;margin:0;padding:0}.ms-drop ul>li{list-style:none;display:list-item;background-image:none;position:static;padding:.25rem 8px}.ms-drop ul>li .disabled{font-weight:normal!important;opacity:.35;filter:Alpha(Opacity=35);cursor:default}.ms-drop ul>li.multiple{display:block;float:left}.ms-drop ul>li.group{clear:both}.ms-drop ul>li.multiple label{width:100%;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ms-drop ul>li label{position:relative;padding-left:1.25rem;margin-bottom:0;font-weight:normal;display:block;white-space:nowrap;cursor:pointer}.ms-drop ul>li label.optgroup{font-weight:bold}.ms-drop ul>li.hide-radio{padding:0}.ms-drop ul>li.hide-radio:focus,.ms-drop ul>li.hide-radio:hover{background-color:#f8f9fa}.ms-drop ul>li.hide-radio.selected{color:#fff;background-color:#007bff}.ms-drop ul>li.hide-radio label{margin-bottom:0;padding:5px 8px}.ms-drop ul>li.hide-radio input{display:none}.ms-drop ul>li.option-level-1 label{padding-left:28px}.ms-drop input[type="radio"],.ms-drop input[type="checkbox"]{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.ms-drop .ms-no-results{display:none}
\ No newline at end of file
diff --git a/ASP_NET_CORE/Album/wwwroot/lib/multiple-select/multiple-select.min.js b/ASP_NET_CORE/Album/wwwroot/lib/multiple-select/multiple-select.min.js
new file mode 100644
index 0000000..7bffe99
--- /dev/null
+++ b/ASP_NET_CORE/Album/wwwroot/lib/multiple-select/multiple-select.min.js
@@ -0,0 +1,10 @@
+/**
+ * multiple-select - Multiple select is a jQuery plugin to select multiple elements with checkboxes :).
+ *
+ * @version v1.5.2
+ * @homepage http://multiple-select.wenzhixin.net.cn
+ * @author wenzhixin (http://wenzhixin.net.cn/)
+ * @license MIT
+ */
+
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],e):e((t=t||self).jQuery)}(this,(function(t){"use strict";function e(t){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){for(var n=0;n0?ct:at)(t)},ft=Math.min,pt=function(t){return t>0?ft(ht(t),9007199254740991):0},dt=Math.max,vt=Math.min,gt=function(t,e){var n=ht(t);return n<0?dt(n+e,0):vt(n,e)},yt=function(t){return function(e,n,i){var r,u=D(e),o=pt(u.length),s=gt(i,o);if(t&&n!=n){for(;o>s;)if((r=u[s++])!=r)return!0}else for(;o>s;s++)if((t||s in u)&&u[s]===n)return t||s||0;return!t&&-1}},Et={includes:yt(!0),indexOf:yt(!1)},bt=Et.indexOf,mt=function(t,e){var n,i=D(t),r=0,u=[];for(n in i)!$(Q,n)&&$(i,n)&&u.push(n);for(;e.length>r;)$(i,n=e[r++])&&(~bt(u,n)||u.push(n));return u},At=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],Ft=At.concat("length","prototype"),St={f:Object.getOwnPropertyNames||function(t){return mt(t,Ft)}},Ct={f:Object.getOwnPropertySymbols},kt=lt("Reflect","ownKeys")||function(t){var e=St.f(R(t)),n=Ct.f;return n?e.concat(n(t)):e},Dt=function(t,e){for(var n=kt(e),i=P.f,r=I.f,u=0;uu;)P.f(t,n=i[u++],e[n]);return t},Gt=lt("document","documentElement"),Ut=J("IE_PROTO"),Wt=function(){},Vt=function(){var t,e=T("iframe"),n=At.length;for(e.style.display="none",Gt.appendChild(e),e.src=String("javascript:"),(t=e.contentWindow.document).open(),t.write("