add api controller

add swagger
datagrid export function
main
Peace 10 months ago
parent 3ed28e3b3a
commit 109b3b6d74
  1. 1
      ComponentPractice/ComponentPractice/ComponentPractice.csproj
  2. 1
      ComponentPractice/ComponentPractice/Components/Layout/NavMenu.razor
  3. 45
      ComponentPractice/ComponentPractice/Components/Pages/DataGridCsvExport.razor
  4. 30
      ComponentPractice/ComponentPractice/Controllers/ExportController.cs
  5. 63
      ComponentPractice/ComponentPractice/Data/InMemoryData.cs
  6. 21
      ComponentPractice/ComponentPractice/Program.cs
  7. 20
      ComponentPractice/ComponentPractice/Services/ExportService.cs
  8. 126
      ComponentPractice/ComponentPractice/Services/ToCSVService.cs

@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="Radzen.Blazor" Version="4.32.10" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
</Project>

@ -35,6 +35,7 @@
<RadzenPanelMenuItem Text="Advanced" Icon="view_headline" Path="datagridadv" />
<RadzenPanelMenuItem Text="REST" Icon="view_headline" Path="datagridrest" />
<RadzenPanelMenuItem Text="Editing" Icon="view_headline" Path="datagridedit" />
<RadzenPanelMenuItem Text="CSV Export" Icon="view_headline" Path="datagridcsvexport" />
</div>
</RadzenPanelMenuItem>
</RadzenPanelMenu>

@ -0,0 +1,45 @@
@page "/datagridcsvexport";
@using ComponentPractice.Services
@inject InMemoryData data;
@inject ExportService exportService;
@rendermode RenderMode.InteractiveServer
<RadzenCard class="rz-p-12">
<RadzenButton class="mb-4" Text="Export CSV" Icon="wrap_text" Click="@(args => Export("csv"))" />
<RadzenDataGrid @ref="grid2" Data="@employees2"
AllowColumnPicking="true" AllowFiltering="true" AllowPaging="true" AllowSorting="true" ShowPagingSummary="true">
<Columns>
<RadzenDataGridColumn Property="@(nameof(Employee.ID))" Title="ID" />
<RadzenDataGridColumn Property="@(nameof(Employee.FirstName))" Title="First Name" />
<RadzenDataGridColumn Property="@(nameof(Employee.LastName))" Title="Last Name" />
<RadzenDataGridColumn Property="@(nameof(Employee.Title))" Title="Title" />
<RadzenDataGridColumn Property="@(nameof(Employee.HireDate))" Title="Hire Date" FormatString="{0:yyyy-MM-dd}"/>
<RadzenDataGridColumn Property="@(nameof(Employee.Phone))" Title="Phone" />
</Columns>
</RadzenDataGrid>
</RadzenCard>
@code {
RadzenDataGrid<Employee> grid2;
IEnumerable<Employee> employees2;
protected override async Task OnInitializedAsync()
{
employees2 = await data.GetFixedEmployeeDataAsync();
}
// table: FixedEmployees
public void Export(string type)
{
exportService.Export("FixedEmployees", type, new Query()
{
OrderBy = grid2.Query.OrderBy,
Filter = grid2.Query.Filter,
Select = string.Join(",", grid2.ColumnsCollection.Where(c => c.GetVisible() && !string.IsNullOrEmpty(c.Property))
.Select(c => c.Property.Contains(".") ? $"{c.Property} as {c.Property.Replace(".", "_")}" : c.Property))
});
}
}

@ -0,0 +1,30 @@
using ComponentPractice.Data;
using ComponentPractice.Models;
using ComponentPractice.Services;
using Microsoft.AspNetCore.Mvc;
namespace ComponentPractice.Controllers
{
[ApiController]
[Route("[controller]")]
public class ExportController : ControllerBase
{
private InMemoryData _inMemoryData;
private ToCSVService _toCsvService;
public ExportController(InMemoryData inMemoryData, ToCSVService toCsvService)
{
_inMemoryData = inMemoryData;
_toCsvService = toCsvService;
}
//[HttpGet("/InMemoryData/FixedEmployees/csv")] // 절대경로 주의!
[HttpGet("InMemoryData/FixedEmployees/csv")]
public async Task<FileStreamResult> ExportToCSV()
{
List<Employee> employees = await _inMemoryData.GetFixedEmployeeDataAsync();
IQueryable query = _toCsvService.ApplyQuery(employees.AsQueryable(), Request.Query);
return _toCsvService.ToCSV(query);
}
}
}

@ -5,43 +5,60 @@ namespace ComponentPractice.Data
{
public class InMemoryData
{
private List<Employee> _fixedSample;
Random _random = new Random();
string[] _firstNames = new[] { "John", "Jane", "Michael", "Emily", "Robert", "Linda", "William", "Jessica", "David", "Sarah" };
string[] _lastNames = new[] { "Smith", "Johnson", "Brown", "Williams", "Jones", "Garcia", "Miller", "Davis", "Martinez", "Hernandez" };
string[] _jobs = new[] { "Developer", "Designer", "Manager", "Analyst", "Consultant", "Administrator", "Engineer", "Specialist", "Technician", "Coordinator" };
string[] _titles = new[] { "Junior", "Mid", "Senior", "Lead", "Chief" };
string[] _cities = new[] { "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia", "San Antonio", "San Diego", "Dallas", "San Jose" };
string[] _regions = new[] { "NY", "CA", "IL", "TX", "AZ", "PA", "TX", "CA", "TX", "CA" };
string[] _counties = new[] { "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA" };
public InMemoryData()
{
_fixedSample = GenerateEmployData(100);
}
public async Task<List<Employee>> GetEmployeeDataAsync(int count)
{
var employees = new List<Employee>();
var random = new Random();
var firstNames = new[] { "John", "Jane", "Michael", "Emily", "Robert", "Linda", "William", "Jessica", "David", "Sarah" };
var lastNames = new[] { "Smith", "Johnson", "Brown", "Williams", "Jones", "Garcia", "Miller", "Davis", "Martinez", "Hernandez" };
var jobs = new[] { "Developer", "Designer", "Manager", "Analyst", "Consultant", "Administrator", "Engineer", "Specialist", "Technician", "Coordinator" };
var titles = new[] { "Junior", "Mid", "Senior", "Lead", "Chief" };
var cities = new[] { "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia", "San Antonio", "San Diego", "Dallas", "San Jose" };
var regions = new[] { "NY", "CA", "IL", "TX", "AZ", "PA", "TX", "CA", "TX", "CA" };
var counties = new[] { "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA" };
return await GenerateEmployData(count).ToDynamicListAsync<Employee>();
}
public async Task<List<Employee>> GetFixedEmployeeDataAsync()
{
return await _fixedSample.ToDynamicListAsync<Employee>();
}
private List<Employee> GenerateEmployData(int count)
{
var employees = new List<Employee>();
for (int i = 0; i < count; i++)
{
var employee = new Employee
{
ID = i + 1,
Photo = $"https://randomuser.me/api/portraits/med/{(i % 2 == 0 ? "men" : "women")}/{i}.jpg",
FirstName = firstNames[random.Next(firstNames.Length)],
LastName = lastNames[random.Next(lastNames.Length)],
Job = jobs[random.Next(jobs.Length)],
Title = titles[random.Next(titles.Length)],
BirthDate = new DateTime(random.Next(1950, 2000), random.Next(1, 13), random.Next(1, 29)),
HireDate = DateTime.Today.AddDays(-random.Next(1, 3650)),
Address = $"{random.Next(1, 9999)} Main St",
City = cities[random.Next(cities.Length)],
Region = regions[random.Next(regions.Length)],
PostalCode = $"{random.Next(10000, 99999)}",
Country = counties[random.Next(counties.Length)],
Phone = $"{random.Next(100, 999)}-555-{random.Next(1000, 9999)}",
Extension = $"{random.Next(100, 9999)}",
FirstName = _firstNames[_random.Next(_firstNames.Length)],
LastName = _lastNames[_random.Next(_lastNames.Length)],
Job = _jobs[_random.Next(_jobs.Length)],
Title = _titles[_random.Next(_titles.Length)],
BirthDate = new DateTime(_random.Next(1950, 2000), _random.Next(1, 13), _random.Next(1, 29)),
HireDate = DateTime.Today.AddDays(-_random.Next(1, 3650)),
Address = $"{_random.Next(1, 9999)} Main St",
City = _cities[_random.Next(_cities.Length)],
Region = _regions[_random.Next(_regions.Length)],
PostalCode = $"{_random.Next(10000, 99999)}",
Country = _counties[_random.Next(_counties.Length)],
Phone = $"{_random.Next(100, 999)}-555-{_random.Next(1000, 9999)}",
Extension = $"{_random.Next(100, 9999)}",
Notes = "Generated demo data"
};
employees.Add(employee);
}
return await employees.ToDynamicListAsync<Employee>();
return employees;
}
}
}

@ -1,6 +1,7 @@
using ComponentPractice.Components;
using ComponentPractice.Data;
using ComponentPractice.Services;
using Microsoft.AspNetCore.Builder;
using Radzen;
namespace ComponentPractice
@ -15,10 +16,19 @@ namespace ComponentPractice
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
// Add controllers
builder.Services.AddControllers();
// Add swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddRadzenComponents();
builder.Services.AddScoped<InMemoryData>();
builder.Services.AddSingleton<InMemoryData>();
builder.Services.AddScoped<NorthwindDataService>();
builder.Services.AddScoped<ToCSVService>();
builder.Services.AddScoped<ExportService>();
var app = builder.Build();
@ -30,6 +40,13 @@ namespace ComponentPractice
app.UseHsts();
}
// Active swagger on Development Env.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
@ -38,6 +55,8 @@ namespace ComponentPractice
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.MapControllers();
//app.Urls.Add("http://*:5000");
app.Run();

@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Components;
using Radzen;
namespace ComponentPractice.Services
{
public class ExportService
{
private readonly NavigationManager _navigationManager;
public ExportService(NavigationManager navigationManager)
{
_navigationManager = navigationManager;
}
public void Export(string table, string type, Query query = null)
{
_navigationManager.NavigateTo(query != null ? query.ToUrl($"/export/InMemoryData/{table}/{type}") : $"/export/InMemoryData/{table}/{type}", true);
}
}
}

@ -0,0 +1,126 @@
using Microsoft.AspNetCore.Mvc;
using System.Globalization;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Reflection;
using System.Text;
namespace ComponentPractice.Services
{
public class ToCSVService
{
public IQueryable ApplyQuery<T>(IQueryable<T> items, IQueryCollection query = null) where T: class
{
if (query == null)
return items;
//// For EF
//if (query.ContainsKey("$expand"))
//{
// var propertiesToExpand = query["$expand"].ToString().Split(',');
// foreach (var p in propertiesToExpand)
// {
// items = items.Include(p);
// }
//}
var filter = query.ContainsKey("$filter") ? query["$filter"].ToString() : null;
if (!string.IsNullOrEmpty(filter))
{
items = items.Where(filter);
}
if (query.ContainsKey("$orderBy"))
{
items = items.OrderBy(query["$orderBy"].ToString());
}
if (query.ContainsKey("$skip"))
{
items = items.Skip(int.Parse(query["$skip"].ToString()));
}
if (query.ContainsKey("$top"))
{
items = items.Take(int.Parse(query["$top"].ToString()));
}
if (query.ContainsKey("$select"))
{
return items.Select($"new ({query["$select"].ToString()})");
}
return items;
}
public FileStreamResult ToCSV(IQueryable query, string fileName = null)
{
var columns = GetProperties(query.ElementType);
var sb = new StringBuilder();
foreach (var item in query)
{
var row = new List<string>();
foreach (var col in columns)
{
if (col.Value == typeof(DateTime))
row.Add(((DateTime)GetValue(item, col.Key)).ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture));
else
row.Add($"{GetValue(item, col.Key)}".Trim());
}
sb.AppendLine(string.Join(",", row.ToArray()));
}
var result = new FileStreamResult(new MemoryStream(Encoding.UTF8.GetBytes($"{string.Join(",", columns.Select(c => c.Key))}{System.Environment.NewLine}{sb.ToString()}")), "text/csv");
result.FileDownloadName = $"{(!string.IsNullOrEmpty(fileName) ? fileName : "Export")}.csv";
return result;
}
public static object GetValue(object target, string name)
{
return target.GetType().GetProperty(name).GetValue(target);
}
public static IEnumerable<KeyValuePair<string, Type>> GetProperties(Type type)
{
return type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead && IsSimpleType(p.PropertyType)).Select(p => new KeyValuePair<string, Type>(p.Name, p.PropertyType));
}
public static bool IsSimpleType(Type type)
{
var underlyingType = type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof(Nullable<>) ?
Nullable.GetUnderlyingType(type) : type;
if (underlyingType == typeof(System.Guid))
return true;
var typeCode = Type.GetTypeCode(underlyingType);
switch (typeCode)
{
case TypeCode.Boolean:
case TypeCode.Byte:
case TypeCode.Char:
case TypeCode.DateTime:
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.SByte:
case TypeCode.Single:
case TypeCode.String:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
return true;
default:
return false;
}
}
}
}
Loading…
Cancel
Save