RSCG – RSCG_WebAPIExports
RSCG – RSCG_WebAPIExports
name | RSCG_WebAPIExports |
nuget | https://www.nuget.org/packages/RSCG_WebAPIExports/ |
link | https://github.com/ignatandrei/RSCG_WebAPIExports/ |
author | Andrei Ignat |
Generating Excel from WebAPI json array
This is how you can use RSCG_WebAPIExports .
The code that you start with is
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net7.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.10" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" /> <PackageReference Include="RSCG_WebAPIExports" Version="2023.8.16.1255" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> <PackageReference Include="ArrayToExcel" Version="2.2.2" /> </ItemGroup> <PropertyGroup> <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath> </PropertyGroup> </Project>
The code that you will use is
using RSCG_WebAPIExportsDemo; using WebApiExportToFile; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddExport(); var app = builder.Build(); app.UseExport(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(ct => { ct.DocumentTitle = "try /WeatherForecast.xlsx"; ct.HeadContent = "try /WeatherForecast.xlsx"; }); } //app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
using Microsoft.AspNetCore.Mvc; namespace RSCG_WebAPIExportsDemo.Controllers { [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; private readonly ILogger<WeatherForecastController> _logger; public WeatherForecastController(ILogger<WeatherForecastController> logger) { _logger = logger; } [HttpGet(Name = "GetWeatherForecast")] public IEnumerable<WeatherForecast> Get() { return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); } } }
The code that is generated is
using Microsoft.AspNetCore.Rewrite; namespace WebApiExportToFile; #nullable enable public static partial class Extensions { static partial void AddReturnTypesFromGenerator(); public static IServiceCollection AddExport(this IServiceCollection services, params Type[]? typesReturnedByActions) { AddReturnTypesFromGenerator(); //MiddlewareExportToFile.AddReturnType(typeof(Person[])); //MiddlewareExportToFile.AddReturnType(typeof(WeatherForecast[])); MiddlewareExportToFile.AddReturnTypes(typesReturnedByActions); return services.AddSingleton<MiddlewareExportToFile>(); } public static IApplicationBuilder UseExport(this IApplicationBuilder app) { app.UseMiddleware<MiddlewareExportToFile>(); var options = new RewriteOptions().Add(MiddlewareExportToFile.RewriteExtNeeded); app.UseRewriter(options); return app; } } #nullable disable
namespace WebApiExportToFile; public static partial class Extensions { static partial void AddReturnTypesFromGenerator(){ MiddlewareExportToFile.AddReturnType(typeof(RSCG_WebAPIExportsDemo.WeatherForecast[])); } }
using System.IO; using System.Text; using System; using Microsoft.AspNetCore.Rewrite; using System.Text.Json; using ArrayToExcel; using System.Text.Json.Serialization.Metadata; using System.Runtime.CompilerServices; #nullable enable namespace WebApiExportToFile; public class MiddlewareExportToFile : IMiddleware { private static List<Type> types = new(); static readonly string[] Extensions = new string[1] { ".xlsx" }; static string key = "Export"; public static void AddReturnTypes(params Type[]? typesReturnedByActions) { if (typesReturnedByActions?.Length > 0) { foreach (var type in typesReturnedByActions) { AddReturnType(type); } } } public static void AddReturnType(Type type) { types.Add(type); } public static void RewriteExtNeeded(RewriteContext context) { var request = context.HttpContext.Request; if (!(context.HttpContext.Items.ContainsKey(key) && context.HttpContext.Items[key]?.ToString() == "1")) { return; } var ext = Path.GetExtension(request.Path.Value); if (string.IsNullOrWhiteSpace(ext)) return; request.Path = request.Path.Value!.Substring(0, request.Path.Value.Length - ext.Length); } public bool ShouldIntercept(HttpContext context) { string path = context.Request.Path; var ext = Path.GetExtension(path); if (string.IsNullOrWhiteSpace(ext)) return false; if (!Extensions.Contains(ext, StringComparer.OrdinalIgnoreCase)) return false; return true; } //https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/migrate-from-newtonsoft static void AddMissingMemberHandling(JsonTypeInfo typeInfo) { if (typeInfo.Kind == JsonTypeInfoKind.Object && typeInfo.Properties.All(prop => !prop.IsExtensionData) && typeInfo.OnDeserialized is null) { // Dynamically attach dictionaries to deserialized objects. var cwt = new ConditionalWeakTable<object, Dictionary<string, object>>(); JsonPropertyInfo propertyInfo = typeInfo.CreateJsonPropertyInfo(typeof(Dictionary<string, object>), "__extensionDataAttribute"); propertyInfo.Get = obj => cwt.TryGetValue(obj, out Dictionary<string, object>? value) ? value : null; propertyInfo.Set = (obj, value) => cwt.Add(obj, (Dictionary<string, object>)value!); propertyInfo.IsExtensionData = true; typeInfo.Properties.Add(propertyInfo); typeInfo.OnDeserialized = obj => { if (cwt.TryGetValue(obj, out Dictionary<string, object>? dict)) { cwt.Remove(obj); throw new JsonException($"JSON properties {String.Join(", ", dict.Keys)} " + $"could not bind to any members of type {typeInfo.Type}"); } }; } } public object[]? StrongDeserialize(string responseContent) { if(types.Count() == 0) throw new Exception("please add some types"); foreach( var type in types) { try { var data = JsonSerializer.Deserialize(responseContent, type, new JsonSerializerOptions { PropertyNameCaseInsensitive = true, TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { AddMissingMemberHandling } } }) as object[]; return data; } catch(JsonException) { //do nothing } } throw new Exception("no type can deserialize " +responseContent); } public async Task InvokeAsync(HttpContext context, RequestDelegate next) { if (!ShouldIntercept(context)) { await next(context); return; } var ext=Path.GetExtension(context.Request.Path.Value); var nameFile = context.Request.Path.Value?.Replace("/", "_"); context.Items["Export"] = "1"; var originalResponseBody = context.Response.Body; using var memoryStream = new MemoryStream(); context.Response.Body = memoryStream; context.Response.Headers.Add("Content-Disposition", $"attachment; filename={nameFile}"); await next(context); memoryStream.Seek(0, SeekOrigin.Begin); var responseContent = await new StreamReader(memoryStream).ReadToEndAsync(); context.Response.Body = originalResponseBody; var data = StrongDeserialize(responseContent); ArgumentNullException.ThrowIfNull(data); using var excelStream = data.ToExcelStream(); await excelStream.CopyToAsync(context.Response.Body); // No need to call the next middleware since the generated content has been sent return; } } #nullable disable
Code and pdf at
https://ignatandrei.github.io/RSCG_Examples/v2/docs/RSCG_WebAPIExports
Leave a Reply