첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
본 블로그는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 블로그 콘텐츠 향상을 위해 쓰여집니다.

728x90
반응형
728x170

TestProject.zip
다운로드

▶ Controllers/TodoController.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Threading.Tasks;

using TestProject.Data;
using TestProject.Models;

namespace TestProject.Controllers
{
    /// <summary>
    /// 할일 컨트롤러
    /// </summary>
    [ApiController]
    [Route("api/[controller]")]
    public class TodoController : ControllerBase
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 데이터베이스 컨텍스트
        /// </summary>
        private readonly DatabaseContext context;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - TodoController(context)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="context">데이터베이스 컨텍스트</param>
        public TodoController(DatabaseContext context)
        {
            this.context = context;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region GET 처리하기 - Get()

        /// <summary>
        /// GET 처리하기
        /// </summary>
        /// <returns>할일 리스트 태스크</returns>
        [HttpGet]
        public async Task<List<TodoModel>> Get()
        {
             return await this.context.Todo.AsNoTracking().ToListAsync();
        }

        #endregion
        #region GET 처리하기 - Get(id)

        /// <summary>
        /// GET 처리하기
        /// </summary>
        /// <param name="id">ID</param>
        /// <returns>할일 액션 결과 태스크</returns>
        [HttpGet("{id}")]
        public async Task<ActionResult<TodoModel>> Get(long id)
        {
            TodoModel todo = await this.context.Todo.FindAsync(id);

            if(todo == null)
            {
                return NotFound();
            }

            return todo;
        }

        #endregion
        #region POST 처리하기 - Post(todo)

        /// <summary>
        /// POST 처리하기
        /// </summary>
        /// <param name="todo">할일</param>
        /// <returns>액션 결과 태스크</returns>
        [HttpPost]
        public async Task<IActionResult> Post(TodoModel todo)
        {
            this.context.Todo.Add(todo);

            await this.context.SaveChangesAsync();

            return CreatedAtAction(nameof(Get), new { id = todo.ID, todo });
        }

        #endregion
        #region PUT 처리하기 - Put(id, todo)

        /// <summary>
        /// PUT 처리하기
        /// </summary>
        /// <param name="id">ID</param>
        /// <param name="todo">할일</param>
        /// <returns>액션 결과 태스크</returns>
        [HttpPut("{id}")]
        public async Task<IActionResult> Put(long id, TodoModel todo)
        {
            if(todo.ID != id)
            {
                return BadRequest();
            }

            this.context.Update(todo);

            await this.context.SaveChangesAsync();

            return NoContent();
        }

        #endregion
        #region DELETE 처리하기 - Delete(id)

        /// <summary>
        /// DELETE 처리하기
        /// </summary>
        /// <param name="id">ID</param>
        /// <returns>액션 결과 태스크</returns>
        [HttpDelete("{id}")]
        public async Task<IActionResult> Delete(long id)
        {
            TodoModel todo = await this.context.Todo.FindAsync(id);

            if(todo == null)
            {
                return NotFound();
            }

            this.context.Todo.Remove(todo);

            await this.context.SaveChangesAsync();

            return NoContent();
        }

        #endregion
    }
}

 

728x90

 

▶ TodoClient.cs

using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

using TestProject.Models;

namespace TestProject
{
    /// <summary>
    /// 할일 클라이언트
    /// </summary>
    public class TodoClient
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// JSON 직렬화기 옵션
        /// </summary>
        private static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions
        {
            IgnoreNullValues     = true,
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        };

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// HTTP 클라이언트
        /// </summary>
        private readonly HttpClient httpClient;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - TodoClient(httpClient)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="httpClient">HTTP 클라이언트</param>
        public TodoClient(HttpClient httpClient)
        {
            this.httpClient = httpClient;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Metohd
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 리스트 구하기 (비동기) - GetListAsync()

        /// <summary>
        /// 리스트 구하기 (비동기)
        /// </summary>
        /// <returns>할일 리스트 태스크</returns>
        public async Task<List<TodoModel>> GetListAsync()
        {
            using HttpResponseMessage httpResponseMessage = await this.httpClient.GetAsync("/api/todo");

            httpResponseMessage.EnsureSuccessStatusCode();

            using Stream stream = await httpResponseMessage.Content.ReadAsStreamAsync();

            return await JsonSerializer.DeserializeAsync<List<TodoModel>>(stream, _jsonSerializerOptions);
        }

        #endregion
        #region 항목 구하기 (비동기) - GetItemAsync(id)

        /// <summary>
        /// 항목 구하기 (비동기)
        /// </summary>
        /// <param name="id">ID</param>
        /// <returns>할일 태스크</returns>
        public async Task<TodoModel> GetItemAsync(long id)
        {
            using HttpResponseMessage httpResponseMessage = await this.httpClient.GetAsync($"/api/todo/{id}");

            if(httpResponseMessage.StatusCode == HttpStatusCode.NotFound)
            {
                return null;
            }

            httpResponseMessage.EnsureSuccessStatusCode();

            using Stream stream = await httpResponseMessage.Content.ReadAsStreamAsync();

            return await JsonSerializer.DeserializeAsync<TodoModel>(stream, _jsonSerializerOptions);
        }

        #endregion
        #region 항목 생성하기 (비동기) - CreateItemAsync(todo)

        /// <summary>
        /// 항목 생성하기 (비동기)
        /// </summary>
        /// <param name="todo">할일</param>
        /// <returns>태스크</returns>
        public async Task CreateItemAsync(TodoModel todo)
        {
            StringContent stringContent = new StringContent
            (
                JsonSerializer.Serialize(todo, _jsonSerializerOptions),
                Encoding.UTF8,
                "application/json"
            );

            using HttpResponseMessage httpResponseMessage = await this.httpClient.PostAsync
            (
                "/api/todo",
                stringContent
            );

            httpResponseMessage.EnsureSuccessStatusCode();
        }

        #endregion
        #region 항목 저장하기 (비동기) - SaveItemAsync(todo)

        /// <summary>
        /// 항목 저장하기 (비동기)
        /// </summary>
        /// <param name="todo">할일</param>
        /// <returns>태스크</returns>
        public async Task SaveItemAsync(TodoModel todo)
        {
            StringContent stringContent = new StringContent
            (
                JsonSerializer.Serialize(todo),
                Encoding.UTF8,
                "application/json"
            );

            using HttpResponseMessage httpResponseMessage = await this.httpClient.PutAsync
            (
                $"/api/todo/{todo.ID}",
                stringContent
            );

            httpResponseMessage.EnsureSuccessStatusCode();
        }

        #endregion
        #region 항목 삭제하기 (비동기) - DeleteItemAsync(id)

        /// <summary>
        /// 항목 삭제하기 (비동기)
        /// </summary>
        /// <param name="id">ID</param>
        /// <returns>태스크</returns>
        public async Task DeleteItemAsync(long id)
        {
            using HttpResponseMessage httpResponseMessage = await this.httpClient.DeleteAsync($"/api/todo/{id}");

            httpResponseMessage.EnsureSuccessStatusCode();
        }

        #endregion
    }
}

 

300x250

 

▶ Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;

using TestProject.Data;

namespace TestProject
{
    /// <summary>
    /// 시작
    /// </summary>
    public class Startup
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 서비스 컬렉션 구성하기 - ConfigureServices(services)

        /// <summary>
        /// 서비스 컬렉션 구성하기
        /// </summary>
        /// <param name="services">서비스 컬렉션</param>
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<DatabaseContext>
            (
                options => options.UseInMemoryDatabase("TestDB")
            );

            services.AddHttpContextAccessor();

            services.AddHttpClient<TodoClient>
            (
                (serviceProvider, httpClient) =>
                {
                    HttpRequest httpRequest = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;

                    httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme, httpRequest.Host, httpRequest.PathBase));

                    httpClient.Timeout = TimeSpan.FromSeconds(5);
                }
            );

            services.AddControllers();

            services.AddRazorPages();
        }

        #endregion
        #region 구성하기 - Configure(app, environment)

        /// <summary>
        /// 구성하기
        /// </summary>
        /// <param name="app">애플리케이션 빌더</param>
        /// <param name="environment">웹 호스트 환경</param>
        public void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
        {
            if(environment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints
            (
                endpoints =>
                {
                    endpoints.MapControllers();

                    endpoints.MapRazorPages();
                }
            );
        }

        #endregion
    }
}

 

▶ Pages/Index.cshtml

@page
@using TestProject.Models
@model IndexModel
@{
    ViewData["Title"] = "Home";
}
<div class="row justify-content-center">
    <div class="col-md-6">
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <form asp-page-handler="Create" method="post">
            <div class="input-group input-group-sm">
                <input name="name"
                    class="form-control form-control-sm"
                    placeholder="Something I need to do">
                <div class="input-group-append">
                    <button class="btn btn-primary">Add to List</button>
                </div>
            </div>
        </form>
    </div>
</div>
@if(Model.IncompleteTodoList.Any())
{
    <hr>
    <div class="row justify-content-center">
        <div class="col-md-6">
            <ul class="list-group mb-2">
                @foreach(TodoModel todo in Model.IncompleteTodoList)
                {
                    <a asp-page="/Item" asp-route-id="@todo.ID"
                        class="list-group-item list-group-item-action">
                        @todo.Name
                    </a>
                }
            </ul>
        </div>
    </div>
}
@if(Model.CompleteTodoList.Any())
{
    <div class="row justify-content-center">
        <div class="col-md-6">
            <p class="text-center">
                <small>@Model.CompleteTodoList.Count completed</small>
            </p>
            <ul class="list-group">
                @foreach(TodoModel todo in Model.CompleteTodoList)
                {
                    <a asp-page="/Item" asp-route-id="@todo.ID"
                        class="list-group-item list-group-item-action text-muted">
                        @todo.Name
                    </a>
                }
            </ul>
        </div>
    </div>
}
@section Styles {
    <style>.list-group-item.text-muted{text-decoration:line-through}</style>
}

 

▶ Pages/Index.cshtml.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

using TestProject.Models;

namespace TestProject.Pages
{
    /// <summary>
    /// 인덱스 모델
    /// </summary>
    public class IndexModel : PageModel
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 할일 클라이언트
        /// </summary>
        private readonly TodoClient client;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 완료 할일 리스트 - CompleteTodoList

        /// <summary>
        /// 완료 할일 리스트
        /// </summary>
        public List<TodoModel> CompleteTodoList { get; private set; }

        #endregion
        #region 미완료 할일 리스트 - IncompleteTodoList

        /// <summary>
        /// 미완료 할일 리스트
        /// </summary>
        public List<TodoModel> IncompleteTodoList { get; private set; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - IndexModel(client)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="client">할일 클라이언트</param>
        public IndexModel(TodoClient client)
        {
            this.client = client;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region GET 요청시 처리하기 (비동기) - OnGetAsync()

        /// <summary>
        /// GET 요청시 처리하기 (비동기)
        /// </summary>
        /// <returns>태스크</returns>
        public async Task OnGetAsync()
        {
            List<TodoModel> todoList = await this.client.GetListAsync();

            CompleteTodoList   = todoList.Where(x => x.IsComplete).ToList();
            IncompleteTodoList = todoList.Except(CompleteTodoList).ToList();
        }

        #endregion
        #region POST 생성 요청시 처리하기 (비동기) - OnPostCreateAsync(name)

        /// <summary>
        /// POST 생성 요청시 처리하기 (비동기)
        /// </summary>
        /// <param name="name">명칭</param>
        /// <returns>액션 결과 태스크</returns>
        public async Task<IActionResult> OnPostCreateAsync([Required] string name)
        {
            if(!ModelState.IsValid)
            {
                return RedirectToPage();
            }

            TodoModel newTodo = new TodoModel
            {
                Name = name
            };

            await this.client.CreateItemAsync(newTodo);

            return RedirectToPage();
        }

        #endregion
    }
}

 

▶ Pages/Item.cshtml

@page "/Item/{id}"
@model ItemModel
@{
    ViewData["Title"] = "Item";
}
<div class="row justify-content-center">
    <div class="col-md-6">
        <form asp-page-handler="Save" method="POST">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="TodoInput.Name"></label>
                <input asp-for="TodoInput.Name" class="form-control">
                <span asp-validation-for="TodoInput.Name" class="text-danger"></span>
            </div>
            <div class="form-group form-check">
                <input asp-for="TodoInput.IsComplete" class="form-check-input">
                <label asp-for="TodoInput.IsComplete" class="form-check-label">Completed</label>
            </div>
            <div class="form-group">
                <button class="btn btn-primary">Save</button>
                <button asp-page-handler="Delete" class="btn btn-danger">Delete</button>
            </div>
        </form>
        <div>
            <a asp-page="/Index">Back to List</a>
        </div>
    </div>
</div>

 

▶ Pages/Item.cshtml.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Threading.Tasks;

using TestProject.Models;

namespace TestProject.Pages
{
    /// <summary>
    /// 항목 모델
    /// </summary>
    public class ItemModel : PageModel
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 할일 클라이언트
        /// </summary>
        private readonly TodoClient client;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 할일 입력 - TodoInput

        /// <summary>
        /// 할일 입력
        /// </summary>
        [BindProperty]
        public TodoInputModel TodoInput { get; set; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - ItemModel(client)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="client">할일 클라이언트</param>
        public ItemModel(TodoClient client)
        {
            this.client = client;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region GET 요청시 처리하기 (비동기) - OnGetAsync(id)

        /// <summary>
        /// GET 요청시 처리하기 (비동기)
        /// </summary>
        /// <param name="id">ID</param>
        /// <returns>액션 결과 태스크</returns>
        public async Task<IActionResult> OnGetAsync(int id)
        {
            TodoModel todo = await this.client.GetItemAsync(id);

            if(todo == null)
            {
                return RedirectToPage("/Index");
            }

            TodoInput = new TodoInputModel
            {
                Name       = todo.Name,
                IsComplete = todo.IsComplete
            };

            return Page();
        }

        #endregion
        #region POST 저장 요청시 처리하기 (비동기) - OnPostSaveAsync(id)

        /// <summary>
        /// POST 저장 요청시 처리하기 (비동기)
        /// </summary>
        /// <param name="id">ID</param>
        /// <returns>액션 결과 태스크</returns>
        public async Task<IActionResult> OnPostSaveAsync(int id)
        {
            if(!ModelState.IsValid)
            {
                return Page();
            }

            TodoModel updateTodo = new TodoModel
            {
                ID         = id,
                Name       = TodoInput.Name,
                IsComplete = TodoInput.IsComplete
            };

            await this.client.SaveItemAsync(updateTodo);

            return RedirectToPage("/Index");
        }

        #endregion
        #region POST 삭제 요청시 처리하기 (비동기) - OnPostDeleteAsync(id)

        /// <summary>
        /// POST 삭제 요청시 처리하기 (비동기)
        /// </summary>
        /// <param name="id">ID</param>
        /// <returns>액션 결과 태스크</returns>
        public async Task<IActionResult> OnPostDeleteAsync(int id)
        {
            await this.client.DeleteItemAsync(id);

            return RedirectToPage("/Index");
        }

        #endregion
    }
}
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요