diff --git a/ComponentPractice/ComponentPractice/ComponentPractice.csproj b/ComponentPractice/ComponentPractice/ComponentPractice.csproj index 620d767..c2b1527 100644 --- a/ComponentPractice/ComponentPractice/ComponentPractice.csproj +++ b/ComponentPractice/ComponentPractice/ComponentPractice.csproj @@ -8,6 +8,7 @@ + diff --git a/ComponentPractice/ComponentPractice/Components/Layout/NavMenu.razor b/ComponentPractice/ComponentPractice/Components/Layout/NavMenu.razor index 0a654dc..f2a91dc 100644 --- a/ComponentPractice/ComponentPractice/Components/Layout/NavMenu.razor +++ b/ComponentPractice/ComponentPractice/Components/Layout/NavMenu.razor @@ -35,6 +35,7 @@ + diff --git a/ComponentPractice/ComponentPractice/Components/Pages/DataGridCsvExport.razor b/ComponentPractice/ComponentPractice/Components/Pages/DataGridCsvExport.razor new file mode 100644 index 0000000..0e183d3 --- /dev/null +++ b/ComponentPractice/ComponentPractice/Components/Pages/DataGridCsvExport.razor @@ -0,0 +1,45 @@ +@page "/datagridcsvexport"; +@using ComponentPractice.Services + +@inject InMemoryData data; +@inject ExportService exportService; + +@rendermode RenderMode.InteractiveServer + + + + + + + + + + + + + + + +@code { + RadzenDataGrid grid2; + + IEnumerable 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)) + }); + } +} diff --git a/ComponentPractice/ComponentPractice/Controllers/ExportController.cs b/ComponentPractice/ComponentPractice/Controllers/ExportController.cs new file mode 100644 index 0000000..bd99ccf --- /dev/null +++ b/ComponentPractice/ComponentPractice/Controllers/ExportController.cs @@ -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 ExportToCSV() + { + List employees = await _inMemoryData.GetFixedEmployeeDataAsync(); + IQueryable query = _toCsvService.ApplyQuery(employees.AsQueryable(), Request.Query); + return _toCsvService.ToCSV(query); + } + } +} diff --git a/ComponentPractice/ComponentPractice/Data/InMemoryData.cs b/ComponentPractice/ComponentPractice/Data/InMemoryData.cs index 8f613bb..cc86ef4 100644 --- a/ComponentPractice/ComponentPractice/Data/InMemoryData.cs +++ b/ComponentPractice/ComponentPractice/Data/InMemoryData.cs @@ -5,43 +5,60 @@ namespace ComponentPractice.Data { public class InMemoryData { + private List _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> GetEmployeeDataAsync(int count) { - var employees = new List(); - 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(); + } + + public async Task> GetFixedEmployeeDataAsync() + { + return await _fixedSample.ToDynamicListAsync(); + } + private List GenerateEmployData(int count) + { + var employees = new List(); 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(); + return employees; } } } diff --git a/ComponentPractice/ComponentPractice/Program.cs b/ComponentPractice/ComponentPractice/Program.cs index 57c2d42..eef2770 100644 --- a/ComponentPractice/ComponentPractice/Program.cs +++ b/ComponentPractice/ComponentPractice/Program.cs @@ -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(); + builder.Services.AddSingleton(); builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); 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() .AddInteractiveServerRenderMode(); + app.MapControllers(); + //app.Urls.Add("http://*:5000"); app.Run(); diff --git a/ComponentPractice/ComponentPractice/Services/ExportService.cs b/ComponentPractice/ComponentPractice/Services/ExportService.cs new file mode 100644 index 0000000..e1dcab5 --- /dev/null +++ b/ComponentPractice/ComponentPractice/Services/ExportService.cs @@ -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); + } + } +} diff --git a/ComponentPractice/ComponentPractice/Services/ToCSVService.cs b/ComponentPractice/ComponentPractice/Services/ToCSVService.cs new file mode 100644 index 0000000..ee45964 --- /dev/null +++ b/ComponentPractice/ComponentPractice/Services/ToCSVService.cs @@ -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(IQueryable 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(); + 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> GetProperties(Type type) + { + return type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.CanRead && IsSimpleType(p.PropertyType)).Select(p => new KeyValuePair(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; + } + } + } +}