API에서 Dapper 결과를 그대로 반환하고, 클라이언트 측에서 이를 `Dictionary<string, object>` 형태로 변환하는 방법을 설명해 드리겠습니다.
## 1. API 서버 코드
서버에서는 Dapper 결과를 JSON으로 직렬화하여 반환합니다.
```csharp
using Dapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using System.Collections.Generic;
using System.Threading.Tasks;
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IDbConnectionFactory _connectionFactory;
public UsersController(IDbConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}
[HttpGet]
public async Task<IActionResult> GetAll()
{
using (var connection = _connectionFactory.CreateConnection())
{
await connection.OpenAsync();
// Dapper 쿼리 결과를 그대로 반환
var users = await connection.QueryAsync("SELECT * FROM Users");
// ASP.NET Core가 자동으로 dynamic 객체를 JSON으로 직렬화
return Ok(users);
}
}
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
using (var connection = _connectionFactory.CreateConnection())
{
await connection.OpenAsync();
var user = await connection.QueryFirstOrDefaultAsync(
"SELECT * FROM Users WHERE Id = @Id",
new { Id = id }
);
if (user == null)
return NotFound();
return Ok(user);
}
}
// 기타 CRUD 메서드
}
```
## 2. 클라이언트 코드
클라이언트에서는 API 응답을 받아 `Dictionary<string, object>` 형태로 변환합니다.
```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using System.Text.Json;
public class UserApiClient
{
private readonly HttpClient _httpClient;
public UserApiClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
// Dictionary<string, object> 형태로 변환
public async Task<IEnumerable<Dictionary<string, object>>> GetUsersAsDictionaryAsync()
{
var response = await _httpClient.GetAsync("api/users");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
// JSON 문자열을 Dictionary<string, object>의 리스트로 변환
// System.Text.Json 사용 방식
using var document = JsonDocument.Parse(content);
var array = document.RootElement;
List<Dictionary<string, object>> result = new List<Dictionary<string, object>>();
foreach (var element in array.EnumerateArray())
{
var dict = new Dictionary<string, object>();
foreach (var property in element.EnumerateObject())
{
dict[property.Name] = ExtractValue(property.Value);
}
result.Add(dict);
}
return result;
}
// JSON 값을 적절한 .NET 타입으로 변환
private object ExtractValue(JsonElement element)
{
switch (element.ValueKind)
{
case JsonValueKind.String:
return element.GetString();
case JsonValueKind.Number:
if (element.TryGetInt32(out int intValue))
return intValue;
if (element.TryGetInt64(out long longValue))
return longValue;
return element.GetDouble();
case JsonValueKind.True:
return true;
case JsonValueKind.False:
return false;
case JsonValueKind.Null:
return null;
default:
return element.ToString();
}
}
// IEnumerable<dynamic>으로 변환하는 방법
public async Task<IEnumerable<dynamic>> GetUsersAsDynamicAsync()
{
var dictionaries = await GetUsersAsDictionaryAsync();
// Dictionary를 ExpandoObject로 변환
return dictionaries.Select(dict => {
dynamic expando = new System.Dynamic.ExpandoObject();
var expandoDict = (IDictionary<string, object>)expando;
foreach (var kvp in dict)
{
expandoDict[kvp.Key] = kvp.Value;
}
return expando;
});
}
// LINQ 쿼리를 적용하는 예시
public async Task<IEnumerable<Dictionary<string, object>>> GetFilteredUsersAsync(int minAge)
{
var users = await GetUsersAsDictionaryAsync();
return users.Where(user =>
user.ContainsKey("Age") &&
user["Age"] is int age &&
age >= minAge
);
}
}
```
## 3. 클라이언트 사용 예시 (Blazor WASM)
```csharp
@page "/users"
@using System.Collections.Generic
@inject UserApiClient UserApi
<h1>사용자 목록</h1>
@if (users == null)
{
<p>로딩 중...</p>
}
else
{
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>이름</th>
<th>이메일</th>
<th>나이</th>
</tr>
</thead>
<tbody>
@foreach (var user in users)
{
<tr>
<td>@user["Id"]</td>
<td>@user["Name"]</td>
<td>@user["Email"]</td>
<td>@user["Age"]</td>
</tr>
}
</tbody>
</table>
<button class="btn btn-primary" @onclick="FilterUsers">30세 이상 필터링</button>
}
@code {
private IEnumerable<Dictionary<string, object>> users;
protected override async Task OnInitializedAsync()
{
users = await UserApi.GetUsersAsDictionaryAsync();
}
private async Task FilterUsers()
{
users = await UserApi.GetFilteredUsersAsync(30);
}
}
```
## 4. Newtonsoft.Json을 사용하는 방법 (대안)
많은 경우 Newtonsoft.Json이 더 간편하게 사용할 수 있습니다:
```csharp
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public async Task<IEnumerable<Dictionary<string, object>>> GetUsersAsDictionaryAsync()
{
var response = await _httpClient.GetAsync("api/users");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
// Newtonsoft.Json을 사용한 변환
var jArray = JArray.Parse(content);
var result = new List<Dictionary<string, object>>();
foreach (var jObject in jArray)
{
var dict = new Dictionary<string, object>();
foreach (var prop in (JObject)jObject)
{
dict[prop.Key] = ConvertJTokenToNative(prop.Value);
}
result.Add(dict);
}
return result;
}
private object ConvertJTokenToNative(JToken token)
{
switch (token.Type)
{
case JTokenType.Integer:
return token.Value<long>();
case JTokenType.Float:
return token.Value<double>();
case JTokenType.String:
return token.Value<string>();
case JTokenType.Boolean:
return token.Value<bool>();
case JTokenType.Date:
return token.Value<DateTime>();
case JTokenType.Null:
return null;
default:
return token.ToString();
}
}
```
## 5. 성능 최적화
대량의 데이터를 처리하는 경우 성능을 최적화하는 방법:
1. **페이징 구현**:
```csharp
// API 서버
[HttpGet]
public async Task<IActionResult> GetPaged(int page = 1, int pageSize = 20)
{
using (var connection = _connectionFactory.CreateConnection())
{
await connection.OpenAsync();
var offset = (page - 1) * pageSize;
var users = await connection.QueryAsync(
"SELECT * FROM Users LIMIT @PageSize OFFSET @Offset",
new { PageSize = pageSize, Offset = offset }
);
// 전체 레코드 수를 함께 반환
var totalCount = await connection.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM Users");
return Ok(new {
Items = users,
TotalCount = totalCount,
Page = page,
PageSize = pageSize,
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
});
}
}
// 클라이언트
public async Task<(IEnumerable<Dictionary<string, object>> Items, int TotalCount, int Page, int TotalPages)>
GetPagedUsersAsync(int page = 1, int pageSize = 20)
{
var response = await _httpClient.GetAsync($"api/users?page={page}&pageSize={pageSize}");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
using var document = JsonDocument.Parse(content);
var root = document.RootElement;
// 페이징 정보 추출
var totalCount = root.GetProperty("totalCount").GetInt32();
var currentPage = root.GetProperty("page").GetInt32();
var totalPages = root.GetProperty("totalPages").GetInt32();
// 아이템 배열 처리
var items = root.GetProperty("items").EnumerateArray()
.Select(element => {
var dict = new Dictionary<string, object>();
foreach (var property in element.EnumerateObject())
{
dict[property.Name] = ExtractValue(property.Value);
}
return dict;
})
.ToList();
return (items, totalCount, currentPage, totalPages);
}
```
2. **선택적 필드 요청**:
```csharp
// API 서버
[HttpGet("fields")]
public async Task<IActionResult> GetWithFields([FromQuery] string fields)
{
if (string.IsNullOrEmpty(fields))
{
fields = "*"; // 기본값은 모든 필드
}
using (var connection = _connectionFactory.CreateConnection())
{
await connection.OpenAsync();
var users = await connection.QueryAsync($"SELECT {fields} FROM Users");
return Ok(users);
}
}
// 클라이언트
public async Task<IEnumerable<Dictionary<string, object>>> GetUsersWithFieldsAsync(string[] fields)
{
var fieldsParam = string.Join(",", fields);
var response = await _httpClient.GetAsync($"api/users/fields?fields={fieldsParam}");
// 이하 처리 로직은 동일
}
```
이러한 방식으로 서버에서는 Dapper 결과를 그대로 반환하고, 클라이언트에서 필요에 따라 `Dictionary<string, object>` 또는 `dynamic` 형태로 변환하여 LINQ 등으로 처리할 수 있습니다.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ
C# API에서 Dapper 결과를 반환하고 클라이언트 단에서 ExpandoObject를 이용해 List<dynamic>으로 처리하는 방법을 설명해 드리겠습니다.
서버 측(API)에서는 Dapper를 사용하여 데이터를 조회하고, 클라이언트 측에서는 ExpandoObject를 활용하여 동적 데이터를 처리하는 과정을 구현해 보겠습니다.
## 서버 측 구현 (C# API)
```csharp
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Dapper;
using System.Data.SqlClient;
[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
private readonly string _connectionString = "your_connection_string";
[HttpGet]
public IActionResult GetData()
{
using (var connection = new SqlConnection(_connectionString))
{
connection.Open();
// Dapper로 쿼리 실행 - dynamic 타입으로 반환
var result = connection.Query("SELECT Id, Name, Value FROM YourTable").ToList();
// JSON으로 반환 (ASP.NET Core가 자동으로 직렬화)
return Ok(result);
}
}
}
```
## 클라이언트 측 구현 (List<dynamic>과 ExpandoObject 활용)
```csharp
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
public class Program
{
public static async Task Main()
{
// HTTP 클라이언트 생성
using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://your-api-url/");
// API 호출 및 결과 받기
var response = await httpClient.GetAsync("api/data");
response.EnsureSuccessStatusCode();
// 응답을 List<ExpandoObject>로 변환
var jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var expandoList = await response.Content.ReadFromJsonAsync<List<ExpandoObject>>(jsonOptions);
// List<dynamic>으로 처리
List<dynamic> dynamicList = expandoList.Cast<dynamic>().ToList();
// 동적 객체 처리 예시
foreach (dynamic item in dynamicList)
{
Console.WriteLine($"Id: {item.Id}, Name: {item.Name}, Value: {item.Value}");
// 동적으로 속성 추가
((IDictionary<string, object>)item)["NewProperty"] = "새 값";
// 속성 존재 여부 확인
var expandoDict = (IDictionary<string, object>)item;
if (expandoDict.ContainsKey("Name"))
{
Console.WriteLine($"Name 속성 값: {expandoDict["Name"]}");
}
}
}
}
```
## ExpandoObject 활용 방법
ExpandoObject는 System.Dynamic 네임스페이스에 있는 클래스로, 런타임에 동적으로 속성을 추가하거나 제거할 수 있습니다.
1. **속성 추가/수정**:
```csharp
((IDictionary<string, object>)dynamicObj)["PropertyName"] = value;
```
2. **속성 접근**:
```csharp
var value = dynamicObj.PropertyName; // 동적 접근
// 또는
var value = ((IDictionary<string, object>)dynamicObj)["PropertyName"]; // 딕셔너리 접근
```
3. **속성 존재 여부 확인**:
```csharp
bool exists = ((IDictionary<string, object>)dynamicObj).ContainsKey("PropertyName");
```
4. **속성 제거**:
```csharp
((IDictionary<string, object>)dynamicObj).Remove("PropertyName");
```
이러한 방식으로 서버에서 Dapper로 조회한 데이터를 클라이언트에서 동적으로 처리할 수 있습니다. 특히 스키마가 자주 변경되거나 다양한 형태의 데이터를 처리해야 할 때 유용합니다.
카테고리 없음