4개 API 컨트롤러 구현: ✅ AuthController: POST /api/auth/login ✅ BlogController: GET/POST/PUT/DELETE /api/blog ✅ CategoryController: GET/POST/PUT/DELETE /api/category ✅ InquiryController: POST/GET/PUT /api/inquiry 아키텍처 개선: - Application 서비스 레이어 확장 (CategoryService 추가) - Repository 인터페이스 CRUD 지원 추가 - Program.cs에 MapControllers() 추가 - 비즈니스 로직과 UI 완전 분리 장점: - 향후 UI 리뉴얼 시 API 변경 불필요 - 모바일 앱, 데스크톱 클라이언트 추가 가능 - 테스트 가능한 API 엔드포인트 테스트 결과: ✅ 블로그 API: 5개 포스트 조회 ✅ 카테고리 API: 5개 카테고리 조회 ✅ 문의 API: 문의 제출 성공 ⚠️ 인증 API: 예정된 수정 대기 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1 @@
|
|||||||
{"sessionId":"49d9d3ba-3c81-4fe7-ab85-f0734baf92f6","pid":42320,"acquiredAt":1782462035015}
|
{"sessionId":"c3cb93c0-7adf-4d3a-817d-6c01e0e0f09f","pid":26816,"acquiredAt":1782481349474}
|
||||||
@@ -9,6 +9,7 @@ public static class DependencyInjection
|
|||||||
{
|
{
|
||||||
services.AddScoped<BlogService>();
|
services.AddScoped<BlogService>();
|
||||||
services.AddScoped<InquiryService>();
|
services.AddScoped<InquiryService>();
|
||||||
|
services.AddScoped<CategoryService>();
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace TaxBaik.Application.Services;
|
namespace TaxBaik.Application.Services;
|
||||||
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using TaxBaik.Application.DTOs;
|
||||||
using TaxBaik.Domain.Entities;
|
using TaxBaik.Domain.Entities;
|
||||||
using TaxBaik.Domain.Interfaces;
|
using TaxBaik.Domain.Interfaces;
|
||||||
|
|
||||||
@@ -13,6 +14,9 @@ public class BlogService(IBlogPostRepository repository)
|
|||||||
int page, int pageSize, int? categoryId = null, CancellationToken ct = default) =>
|
int page, int pageSize, int? categoryId = null, CancellationToken ct = default) =>
|
||||||
await repository.GetPublishedPagedAsync(page, pageSize, categoryId, ct);
|
await repository.GetPublishedPagedAsync(page, pageSize, categoryId, ct);
|
||||||
|
|
||||||
|
public async Task<IEnumerable<BlogPost>> GetAllAsync(CancellationToken ct = default) =>
|
||||||
|
await repository.GetAllForAdminAsync(ct);
|
||||||
|
|
||||||
public async Task<IEnumerable<BlogPost>> GetAllForAdminAsync(CancellationToken ct = default) =>
|
public async Task<IEnumerable<BlogPost>> GetAllForAdminAsync(CancellationToken ct = default) =>
|
||||||
await repository.GetAllForAdminAsync(ct);
|
await repository.GetAllForAdminAsync(ct);
|
||||||
|
|
||||||
@@ -23,9 +27,49 @@ public class BlogService(IBlogPostRepository repository)
|
|||||||
return await repository.CreateAsync(post, ct);
|
return await repository.CreateAsync(post, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<BlogPost> CreateAsync(CreateBlogPostDto dto, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var post = new BlogPost
|
||||||
|
{
|
||||||
|
Title = dto.Title,
|
||||||
|
Content = dto.Content,
|
||||||
|
CategoryId = dto.CategoryId,
|
||||||
|
Tags = dto.Tags,
|
||||||
|
SeoTitle = dto.SeoTitle,
|
||||||
|
SeoDescription = dto.SeoDescription,
|
||||||
|
ThumbnailUrl = dto.ThumbnailUrl,
|
||||||
|
IsPublished = dto.IsPublished,
|
||||||
|
AuthorId = dto.AuthorId,
|
||||||
|
CreatedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
var id = await CreateAsync(post, ct);
|
||||||
|
post.Id = id;
|
||||||
|
return post;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task UpdateAsync(BlogPost post, CancellationToken ct = default) =>
|
public async Task UpdateAsync(BlogPost post, CancellationToken ct = default) =>
|
||||||
await repository.UpdateAsync(post, ct);
|
await repository.UpdateAsync(post, ct);
|
||||||
|
|
||||||
|
public async Task<BlogPost?> UpdateAsync(int id, CreateBlogPostDto dto, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var post = await repository.GetByIdAsync(id, ct);
|
||||||
|
if (post == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
post.Title = dto.Title;
|
||||||
|
post.Content = dto.Content;
|
||||||
|
post.CategoryId = dto.CategoryId;
|
||||||
|
post.Tags = dto.Tags;
|
||||||
|
post.SeoTitle = dto.SeoTitle;
|
||||||
|
post.SeoDescription = dto.SeoDescription;
|
||||||
|
post.ThumbnailUrl = dto.ThumbnailUrl;
|
||||||
|
post.IsPublished = dto.IsPublished;
|
||||||
|
|
||||||
|
await UpdateAsync(post, ct);
|
||||||
|
return post;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task DeleteAsync(int id, CancellationToken ct = default) =>
|
public async Task DeleteAsync(int id, CancellationToken ct = default) =>
|
||||||
await repository.DeleteAsync(id, ct);
|
await repository.DeleteAsync(id, ct);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
namespace TaxBaik.Application.Services;
|
||||||
|
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using TaxBaik.Domain.Entities;
|
||||||
|
using TaxBaik.Domain.Interfaces;
|
||||||
|
|
||||||
|
public class CategoryService(ICategoryRepository repository)
|
||||||
|
{
|
||||||
|
public async Task<IEnumerable<Category>> GetAllAsync(CancellationToken ct = default) =>
|
||||||
|
await repository.GetAllAsync(ct);
|
||||||
|
|
||||||
|
public async Task<Category?> GetBySlugAsync(string slug, CancellationToken ct = default) =>
|
||||||
|
await repository.GetBySlugAsync(slug, ct);
|
||||||
|
|
||||||
|
public async Task<Category?> GetByIdAsync(int id, CancellationToken ct = default) =>
|
||||||
|
await repository.GetByIdAsync(id, ct);
|
||||||
|
|
||||||
|
public async Task<Category> CreateAsync(string name, string? description, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var slug = GenerateSlug(name);
|
||||||
|
var category = new Category
|
||||||
|
{
|
||||||
|
Name = name.Trim(),
|
||||||
|
Slug = slug,
|
||||||
|
SortOrder = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
var id = await repository.CreateAsync(category, ct);
|
||||||
|
return new Category { Id = id, Name = category.Name, Slug = category.Slug, SortOrder = category.SortOrder };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Category?> UpdateAsync(int id, string name, string? description, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var category = await repository.GetByIdAsync(id, ct);
|
||||||
|
if (category == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
category.Name = name.Trim();
|
||||||
|
category.Slug = GenerateSlug(name);
|
||||||
|
await repository.UpdateAsync(category, ct);
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAsync(int id, CancellationToken ct = default) =>
|
||||||
|
await repository.DeleteAsync(id, ct);
|
||||||
|
|
||||||
|
private static string GenerateSlug(string name)
|
||||||
|
{
|
||||||
|
var slug = Regex.Replace(name.ToLowerInvariant(), @"[^\w\s-]", "");
|
||||||
|
slug = Regex.Replace(slug, @"\s+", "-");
|
||||||
|
slug = Regex.Replace(slug, @"-+", "-").Trim('-');
|
||||||
|
return slug.Length > 100 ? slug[..100] : slug;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,4 +6,8 @@ public interface ICategoryRepository
|
|||||||
{
|
{
|
||||||
Task<IEnumerable<Category>> GetAllAsync(CancellationToken cancellationToken = default);
|
Task<IEnumerable<Category>> GetAllAsync(CancellationToken cancellationToken = default);
|
||||||
Task<Category?> GetBySlugAsync(string slug, CancellationToken cancellationToken = default);
|
Task<Category?> GetBySlugAsync(string slug, CancellationToken cancellationToken = default);
|
||||||
|
Task<Category?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||||
|
Task<int> CreateAsync(Category category, CancellationToken cancellationToken = default);
|
||||||
|
Task UpdateAsync(Category category, CancellationToken cancellationToken = default);
|
||||||
|
Task DeleteAsync(int id, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,4 +20,36 @@ public class CategoryRepository(IDbConnectionFactory connectionFactory) : BaseRe
|
|||||||
"SELECT id, name, slug, sort_order FROM categories WHERE slug = @Slug",
|
"SELECT id, name, slug, sort_order FROM categories WHERE slug = @Slug",
|
||||||
new { Slug = slug });
|
new { Slug = slug });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Category?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var conn = Conn();
|
||||||
|
return await conn.QueryFirstOrDefaultAsync<Category>(
|
||||||
|
"SELECT id, name, slug, sort_order FROM categories WHERE id = @Id",
|
||||||
|
new { Id = id });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> CreateAsync(Category category, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var conn = Conn();
|
||||||
|
return await conn.QueryFirstAsync<int>(
|
||||||
|
@"INSERT INTO categories (name, slug, sort_order)
|
||||||
|
VALUES (@Name, @Slug, @SortOrder)
|
||||||
|
RETURNING id",
|
||||||
|
category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAsync(Category category, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var conn = Conn();
|
||||||
|
await conn.ExecuteAsync(
|
||||||
|
"UPDATE categories SET name = @Name, slug = @Slug, sort_order = @SortOrder WHERE id = @Id",
|
||||||
|
category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAsync(int id, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var conn = Conn();
|
||||||
|
await conn.ExecuteAsync("DELETE FROM categories WHERE id = @Id", new { Id = id });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using TaxBaik.Web.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class AuthController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AuthService _authService;
|
||||||
|
|
||||||
|
public AuthController(AuthService authService)
|
||||||
|
{
|
||||||
|
_authService = authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("login")]
|
||||||
|
public async Task<IActionResult> Login([FromBody] LoginRequest request)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
|
||||||
|
return BadRequest(new { message = "Username and password are required" });
|
||||||
|
|
||||||
|
var token = await _authService.AuthenticateAndGenerateTokenAsync(request.Username, request.Password);
|
||||||
|
if (token == null)
|
||||||
|
return Unauthorized(new { message = "Invalid username or password" });
|
||||||
|
|
||||||
|
return Ok(new { token, expiresIn = 28800 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LoginRequest
|
||||||
|
{
|
||||||
|
public string Username { get; set; } = string.Empty;
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
using TaxBaik.Application.DTOs;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class BlogController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly BlogService _blogService;
|
||||||
|
|
||||||
|
public BlogController(BlogService blogService)
|
||||||
|
{
|
||||||
|
_blogService = blogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetPublished([FromQuery] int page = 1, [FromQuery] int pageSize = 12)
|
||||||
|
{
|
||||||
|
var (items, total) = await _blogService.GetPublishedPagedAsync(page, pageSize);
|
||||||
|
return Ok(new { data = items, total, page, pageSize });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{slug}")]
|
||||||
|
public async Task<IActionResult> GetBySlug(string slug)
|
||||||
|
{
|
||||||
|
var post = await _blogService.GetBySlugAsync(slug);
|
||||||
|
if (post == null)
|
||||||
|
return NotFound(new { message = "Post not found" });
|
||||||
|
return Ok(post);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("admin/all")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> GetAll()
|
||||||
|
{
|
||||||
|
var posts = await _blogService.GetAllAsync();
|
||||||
|
return Ok(posts);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> Create([FromBody] CreateBlogPostDto dto)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(dto.Title) || string.IsNullOrWhiteSpace(dto.Content))
|
||||||
|
return BadRequest(new { message = "Title and content are required" });
|
||||||
|
|
||||||
|
var result = await _blogService.CreateAsync(dto);
|
||||||
|
return CreatedAtAction(nameof(GetBySlug), new { slug = result.Slug }, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> Update(int id, [FromBody] CreateBlogPostDto dto)
|
||||||
|
{
|
||||||
|
var result = await _blogService.UpdateAsync(id, dto);
|
||||||
|
if (result == null)
|
||||||
|
return NotFound(new { message = "Post not found" });
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> Delete(int id)
|
||||||
|
{
|
||||||
|
await _blogService.DeleteAsync(id);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class CategoryController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly CategoryService _categoryService;
|
||||||
|
|
||||||
|
public CategoryController(CategoryService categoryService)
|
||||||
|
{
|
||||||
|
_categoryService = categoryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetAll()
|
||||||
|
{
|
||||||
|
var categories = await _categoryService.GetAllAsync();
|
||||||
|
return Ok(categories);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> Create([FromBody] CreateCategoryRequest request)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(request.Name))
|
||||||
|
return BadRequest(new { message = "Category name is required" });
|
||||||
|
|
||||||
|
var category = await _categoryService.CreateAsync(request.Name, request.Description);
|
||||||
|
return CreatedAtAction(nameof(GetAll), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> Update(int id, [FromBody] CreateCategoryRequest request)
|
||||||
|
{
|
||||||
|
var category = await _categoryService.UpdateAsync(id, request.Name, request.Description);
|
||||||
|
if (category == null)
|
||||||
|
return NotFound(new { message = "Category not found" });
|
||||||
|
return Ok(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> Delete(int id)
|
||||||
|
{
|
||||||
|
await _categoryService.DeleteAsync(id);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CreateCategoryRequest
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string? Description { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
using TaxBaik.Domain.Interfaces;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class InquiryController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly InquiryService _inquiryService;
|
||||||
|
private readonly IInquiryRepository _inquiryRepository;
|
||||||
|
|
||||||
|
public InquiryController(InquiryService inquiryService, IInquiryRepository inquiryRepository)
|
||||||
|
{
|
||||||
|
_inquiryService = inquiryService;
|
||||||
|
_inquiryRepository = inquiryRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Submit([FromBody] SubmitInquiryRequest request)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(request.Name) || string.IsNullOrWhiteSpace(request.Phone))
|
||||||
|
return BadRequest(new { message = "Name and phone are required" });
|
||||||
|
|
||||||
|
await _inquiryService.SubmitAsync(request.Name, request.Phone, request.ServiceType, request.Message);
|
||||||
|
return Ok(new { message = "Inquiry submitted successfully" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> GetPaged([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
|
||||||
|
{
|
||||||
|
var (inquiries, total) = await _inquiryRepository.GetPagedAsync(page, pageSize);
|
||||||
|
return Ok(new { data = inquiries, total, page, pageSize });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> GetById(int id)
|
||||||
|
{
|
||||||
|
var inquiry = await _inquiryRepository.GetByIdAsync(id);
|
||||||
|
if (inquiry == null)
|
||||||
|
return NotFound(new { message = "Inquiry not found" });
|
||||||
|
return Ok(inquiry);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id}/status")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> UpdateStatus(int id, [FromBody] UpdateStatusRequest request)
|
||||||
|
{
|
||||||
|
var inquiry = await _inquiryRepository.GetByIdAsync(id);
|
||||||
|
if (inquiry == null)
|
||||||
|
return NotFound(new { message = "Inquiry not found" });
|
||||||
|
|
||||||
|
await _inquiryRepository.UpdateStatusAsync(id, request.Status);
|
||||||
|
return Ok(new { message = "Status updated" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubmitInquiryRequest
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Phone { get; set; } = string.Empty;
|
||||||
|
public string? Email { get; set; }
|
||||||
|
public string ServiceType { get; set; } = string.Empty;
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UpdateStatusRequest
|
||||||
|
{
|
||||||
|
public string Status { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -13,6 +13,9 @@ using TaxBaik.Web.Services;
|
|||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Controllers (API)
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
// Razor Pages + Blazor Server 통합
|
// Razor Pages + Blazor Server 통합
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
|
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
|
||||||
@@ -107,7 +110,8 @@ if (!app.Environment.IsDevelopment())
|
|||||||
app.UseHsts();
|
app.UseHsts();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Razor Pages + Blazor 매핑
|
// API + Razor Pages + Blazor 매핑
|
||||||
|
app.MapControllers();
|
||||||
app.MapRazorPages();
|
app.MapRazorPages();
|
||||||
app.MapRazorComponents<TaxBaik.Web.Components.Admin.App>().AddInteractiveServerRenderMode();
|
app.MapRazorComponents<TaxBaik.Web.Components.Admin.App>().AddInteractiveServerRenderMode();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user