Return inline dynamic SVG from MVC Controller

1.6k views Asked by At

I'm building a method on my Controller to generate an SVG QR Code (QRCoder) and I'm trying to inline the resultant SVG from the controller into the View using a Method on the Controller.

When I attempt to view the method directly, I see the SVG XML coming back successfully, but it's getting an exception. I'm not quite sure what I need to make my controller return so that I can do this in my view.

<img src="~/Redirect/QRCode/{code}/svg"/>

the equivalent PNG version works perfectly.

<img src="~/Redirect/QRCode/{code}/png"/>
[Route("[controller]/QRCode/{code}/{format?}")]
public IActionResult QRCodeImage(string code, BarcodeService.Format format = BarcodeService.Format.Png)
{
    //Database call to get the real Uri...
    var uri = "https://github.com/paulfarry/";
    switch (format)
    {
        case BarcodeService.Format.Svg:
            {
                var data = barcodeService.GenerateQRCodeSvg(uri);
                return File(data, "image/svg+xml; charset=utf-8");
            }
        case BarcodeService.Format.Png:
        default:
            {
                var data = barcodeService.GenerateQRCode(uri);
                return File(data, "image/png");
            }
    }
}

When I view the page directly I would have expected to see the SVG rendered but I get this exception information plus the SVG data.

https://localhost:5001/Redirect/QRCode/a/svg
An unhandled exception occurred while processing the request.
FileNotFoundException: Could not find file: 
<svg version="1.1" baseProfile="full" shape-rendering="crispEdges" width="740" height="740" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect x="0" y="0" width="740" height="740" fill="#FFFFFF" />
<!--Remaining SVG data is here-->
</svg>
    
    Microsoft.AspNetCore.Mvc.Infrastructure.VirtualFileResultExecutor.ExecuteAsync(ActionContext context, VirtualFileResult result)
    Microsoft.AspNetCore.Mvc.VirtualFileResult.ExecuteResultAsync(ActionContext context)
    Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultAsync(IActionResult result)
    Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
    Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeNextResultFilterAsync<TFilter, TFilterAsync>()
    Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
    Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
    Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
    Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
    Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
    Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
    Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
    Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
    Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
    Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
    Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
    Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context)
    Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
2

There are 2 answers

0
Paul Farry On BEST ANSWER

Turns out to be very simple to solve. Just needed to return Content instead of File

But took a lot of experimenting.

        case BarcodeService.Format.Svg:
            {
                var data = barcodeService.GenerateQRCodeSvg(uri);
                return Content(data, "image/svg+xml);
            }
0
Fanetic On

Here is an example of an Asp.Net Core Action returning a check-circle svg with dynamic colours.

        [Route("/demo/dynamic-svg")]
        public IActionResult DynamicSvg()
        {
            var green = "#008000";
            var orange = "#ffc862";

            var data = @$"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<svg width=""510.774"" height=""512"" viewBox=""0 0 135.142 135.467"" xml:space=""preserve"" xmlns=""http://www.w3.org/2000/svg"">
    <path style=""fill:{green};stroke-width:.264583"" d=""M129.845 25.892c-1.085 0-2.17.417-2.99 1.25L95.978 58.02 82.034 44.075a4.2 4.2 0 0 0-5.98 0 4.2 4.2 0 0 0 0 5.98l16.934 16.933a4.2 4.2 0 0 0 5.98 0l33.866-33.866a4.2 4.2 0 0 0 0-5.98 4.18 4.18 0 0 0-2.99-1.25z"" transform=""translate(-37.573 20.686)""/>
    <path style=""fill:{orange};stroke-width:.264583"" d=""M37.573 40.683h12.779a54.456 54.456 0 0 1 11.536-27.808l-9.049-9.048c-8.44 10.213-13.97 22.912-15.266 36.856zM70.857 3.88C78.69-2.312 88.24-6.44 98.665-7.656V-20.41C84.72-19.113 72.02-13.583 61.808-5.143l9.023 9.023zm40.508 97.816v12.78c13.943-1.297 26.643-6.827 36.856-15.267l-9.022-9.022c-7.832 6.19-17.383 10.318-27.808 11.535zm36.803-20.505 9.023 9.022c8.44-10.186 13.97-22.913 15.266-36.856h-12.78a54.456 54.456 0 0 1-11.535 27.807zm-86.36 18.018c10.187 8.44 22.913 13.97 36.857 15.266v-12.78A54.456 54.456 0 0 1 70.857 90.16Zm-8.969-8.97 9.022-9.022C55.67 73.386 51.543 63.834 50.325 53.41H37.573c1.296 13.918 6.826 26.618 15.266 36.83zm95.382-95.382c-10.213-8.44-22.913-13.97-36.856-15.266v12.78a54.456 54.456 0 0 1 27.808 11.535l9.022-9.022zm-.053 18.018c6.192 7.832 10.32 17.383 11.536 27.808h12.78c-1.297-13.944-6.827-26.643-15.267-36.856l-9.022 9.022z"" transform=""translate(-37.573 20.686)""/>
    <path style=""fill:{green};stroke-width:.264583"" d=""M104.982-20.686c-.194 0-.387.006-.58.007v12.7c.193-.002.386-.007.58-.007 30.4 0 55.033 24.632 55.033 55.033 0 30.4-24.633 55.033-55.033 55.033-.194 0-.387-.005-.58-.007v12.7c.193.002.386.007.58.007 37.412 0 67.733-30.32 67.733-67.733 0-37.412-30.321-67.733-67.733-67.733z"" transform=""translate(-37.573 20.686)""/>
</svg>";

            return Content(data, "image/svg+xml");
        }