Search code examples
c#blazorblazor-server-side

Pass variable amount of parameters to local API in a Blazor Server app


I have an API controller set up in my Blazor Server app to handle report generation via FastReport:

ReportController.cs

[Route("api/[controller]")]
[ApiController]
public class ReportController : ControllerBase
{
    [HttpGet("{reportName}")]
    public IActionResult GetReport(string reportName)
    {
        string reportPath = String.Format(@"pathtofilehere.frx", reportName);

        //TODO: different export type handling
        string exportType = "application/pdf";
        string exportFileName = String.Format("{0}.pdf", reportName);

        WebReport web = new();
        web.Report.Load(reportPath);

        web.Report.SetParameterValue("CONN", @"connstringhere");

        //handle parameters here

        web.Report.Prepare();

        MemoryStream stream = new();
        web.Report.Export(new FastReport.Export.PdfSimple.PDFSimpleExport(), stream);

        stream.Position = 0;

        return File(stream, exportType, exportFileName);
    }
}

I currently have calls to this handling some minor parameters via query strings in one of my pages, making a GET call by navigating to the API route:

Reporting.razor

<div class="content-container">
    <input type="text" @bind-value="@_startDate" />
    <button @onclick="DoSomething" type="submit">Download File</button>
</div>

@code {
    private async Task DoSomething()
    {
        string report = "report-2";
        string name = _startDate;

        NavManager.NavigateTo(string.Format("api/Report/{0}?name={1}", report, name), true);
    }
}

I have a varying amount of parameters these reports will need to accept, such as start date, end date, user id, etc. There's about 40ish parameters total that we currently have built into our existing legacy site that will be built into this. I know I'll be able to generate a query string dynamically in my DoSomething, but I would prefer to be able to pass an enumerable object so I don't need to mess with the query string at all. I know I could pass in a JSON object if I was doing a POST, but since I need to actually navigate to the URL to download the file, I don't think I can move that direction.

My initial thoughts were to have an App State singleton that can just read the parameters used and then the controller can pull them out, but the singleton would be shared across all users, which would cause some interference if multiple users are trying to generate reports.

I am trying to avoid generating the PDF on the server and then downloading, which is the reason I'm using the API call to download directly from a stream.

FastReport is the easiest thing I've found to actual build and then generate PDFs, but I'm open to other freeware as well.


Solution

  • Store the parameters for your report generator in an object and stash it in an injected IMemoryCache using a unique Guid as the key, then pass that key to your controller to retreive the object.

    Blazor component:

    <div class="content-container">
    <input type="text" @bind-value="@_startDate" />
    <button @onclick="DoSomething" type="submit">Download File</button>
    
    @code {
    [Inject]
    IMemoryCache cache;
    
    private async Task DoSomething()
    {
        ReportCreationParameters parameters = new ReportCreationParameters
        {
            Report = "report-2",
            Name = "_startDate"
        };
    
        Guid key = Guid.NewGuid();
    
        cache.Set(key, parameters);
    
        NavManager.NavigateTo(string.Format("api/Report/{0}", key), true);
    }
    }
    

    Controller:

    [Route("api/[controller]")]
    [ApiController]
    public class ReportController : ControllerBase
    {
        private readonly IMemoryCache cache;
    
        public ReportController(IMemoryCache cache)
        {
            this.cache = cache;
        }
    
        [HttpGet("{key}")]
        public IActionResult GetReport(Guid key)
        {
            // Fetch saved parameters from cache.
            ReportCreationParameters parameters = cache.Get<ReportCreationParameters>(key);
    
            // Remove saved parameters from cache.
            cache.Remove(key);
    
            string reportPath = String.Format(@"pathtofilehere.frx", parameters.Name);
    
            //TODO: different export type handling
            string exportType = "application/pdf";
            string exportFileName = String.Format("{0}.pdf", parameters.Name);
    
            WebReport web = new();
            web.Report.Load(reportPath);
    
            web.Report.SetParameterValue("CONN", @"connstringhere");
    
            //handle parameters here
    
            web.Report.Prepare();
    
            MemoryStream stream = new();
            web.Report.Export(new FastReport.Export.PdfSimple.PDFSimpleExport(), stream);
    
            stream.Position = 0;
    
            return File(stream, exportType, exportFileName);
        }
    }