Skip to content

Commit

Permalink
feat: QR Code Generator #11
Browse files Browse the repository at this point in the history
Links  for SVG and PNG download on:
- Product details page
- Product export Excel
  • Loading branch information
Filipe Carneiro committed Dec 23, 2023
1 parent ae48e26 commit bef68d5
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 4 deletions.
11 changes: 9 additions & 2 deletions Areas/Admin/Controllers/ProductController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ public async Task<IActionResult> Details(Guid? id)

WineProductDetailsDto wineProductDetailsDto = _mapper.Map<WineProductDetailsDto>(product);

string baseUrl = UrlHelper.GetBaseUrl(Request);
ViewData["ShortUrl"] = UrlHelper.GetShortUrl(baseUrl,product.GetCode());
ViewData["QrCodeUrl"] = UrlHelper.GetQrCodeUrl(baseUrl, product.GetCode());
ViewData["QrCodeUrlPng"] = UrlHelper.GetQrCodeUrl(baseUrl, product.GetCode(), "png");

return View(wineProductDetailsDto);
}

Expand Down Expand Up @@ -421,10 +426,12 @@ public async Task<IActionResult> Export()


// TODO: Use UrlResolver, with Dependency Injection, to add the current Url
String currentUrl = $"{Request.Scheme}://{Request.Host}{Request.PathBase}";
string baseUrl = UrlHelper.GetBaseUrl(Request);
foreach (ProductExcelDto product in products)
{
product.ShortUrl = $"{currentUrl}/l/{product.ShortUrl}";
product.ShortUrl = "HYPERLINK(\"" + UrlHelper.GetShortUrl(baseUrl, product.Code) + "\")";
product.QRCode = "HYPERLINK(\"" + UrlHelper.GetQrCodeUrl(baseUrl, product.Code) + "\")";
product.QRCodePNG = "HYPERLINK(\"" + UrlHelper.GetQrCodeUrl(baseUrl, product.Code, "png") + "\")";
}

byte[] byteArray;
Expand Down
18 changes: 18 additions & 0 deletions Areas/Admin/Views/Product/Details.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,24 @@
</div>
}

<div class="mb-3">
<h4>QR Code</h4>
<hr />

<dl class="row">
<dt class="col-sm-2 mb-2">Short URL</dt>
<dd class="col-sm-10">
<a href="@ViewData["ShortUrl"]" target="_blank">@ViewData["ShortUrl"]</a>
</dd>
</dl>

<div>
<a href="@ViewData["QrCodeUrl"]" target="_blank">Download SVG</a> |
<a href="@ViewData["QrCodeUrlPng"]" target="_blank">Download PNG</a>
</div>

</div>

<partial name="_AuditableEntityPartial" />

<div>
Expand Down
35 changes: 35 additions & 0 deletions Controllers/LabelController.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using AutoMapper;
using ELabel.Data;
using ELabel.Extensions;
using ELabel.Models;
using ELabel.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Net.Codecrete.QrCodeGenerator;
using System.Text;

namespace ELabel.Controllers
{
Expand Down Expand Up @@ -61,6 +64,38 @@ public async Task<IActionResult> ProductCode(string code)
return View("Product", labelDto);
}

// GET: Label/QRCode/5
[Route("Label/QRCode/{code?}")]
public IActionResult QRCode([FromRoute] string code, [FromQuery] string format = "svg")
{
if (code == null || _context.Product == null)
{
return NotFound();
}

string baseUrl = UrlHelper.GetBaseUrl(Request);
var content = UrlHelper.GetQrCodeUrl(baseUrl, code, format);

// Generate QrCode
var qr = QrCode.EncodeText(content, QrCode.Ecc.Medium);

byte[] byteArray;

// PNG

if (format.ToLower() == "png")
{
byteArray = qr.ToPng(10, 4);

return File(byteArray, "image/png", $"qrcode-{code}.png");
}

// SVG

byteArray = Encoding.UTF8.GetBytes(qr.ToSvgString(4));

return File(byteArray, "text/svg", $"qrcode-{code}.svg");
}

private Guid? FindProductId(string? code)
{
Expand Down
2 changes: 1 addition & 1 deletion Data/AutoMapperProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public AutoMapperProfile()
.ForMember(dest => dest.IngredientIdList, opt => opt.MapFrom(
src => src.ProductIngredients.OrderBy(p => p.Order).Select(pi => pi.IngredientId).ToList()))
//.ForMember(dest => dest.ShortUrl, opt => opt.MapFrom<UrlResolver>())
.ForMember(dest => dest.ShortUrl, opt => opt.MapFrom(src => src.GetCode()))
.ForMember(dest => dest.Code, opt => opt.MapFrom(src => src.GetCode()))
.ReverseMap()
.ForPath(p => p.Image, opt => opt.Ignore())
.ForPath(p => p.Ingredients, opt => opt.Ignore())
Expand Down
1 change: 1 addition & 0 deletions ELabel.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.0" />
<PackageReference Include="Net.Codecrete.QrCodeGenerator" Version="2.0.4" />
<PackageReference Include="SkiaSharp" Version="2.88.6" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.6" />
</ItemGroup>
Expand Down
163 changes: 163 additions & 0 deletions Extensions/QrCodeBitmapExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
//
// QR code generator library (.NET)
// https://github.com/manuelbl/QrCodeGenerator
//
// Copyright (c) 2021 Manuel Bleichenbacher
// Licensed under MIT License
// https://opensource.org/licenses/MIT
//

using SkiaSharp;

namespace Net.Codecrete.QrCodeGenerator
{
public static class QrCodeBitmapExtensions
{
/// <inheritdoc cref="ToBitmap(QrCode, int, int)"/>
/// <param name="background">The background color.</param>
/// <param name="foreground">The foreground color.</param>
public static SKBitmap ToBitmap(this QrCode qrCode, int scale, int border, SKColor foreground, SKColor background)
{
// check arguments
if (scale <= 0)
{
throw new ArgumentOutOfRangeException(nameof(scale), "Value out of range");
}
if (border < 0)
{
throw new ArgumentOutOfRangeException(nameof(border), "Value out of range");
}

int size = qrCode.Size;
int dim = (size + border * 2) * scale;

if (dim > short.MaxValue)
{
throw new ArgumentOutOfRangeException(nameof(scale), "Scale or border too large");
}

// create bitmap
bool usesTransparency = background.Alpha != 255;
var bitmap = new SKBitmap(dim, dim,
usesTransparency ? SKColorType.Rgba8888 : SKColorType.Rgb888x,
usesTransparency ? SKAlphaType.Premul : SKAlphaType.Opaque);

using (var canvas = new SKCanvas(bitmap))
{
// draw background
canvas.Clear(background);

// draw modules
using var paint = new SKPaint { Color = foreground };
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
if (qrCode.GetModule(x, y))
{
canvas.DrawRect((x + border) * scale, (y + border) * scale, scale, scale, paint);
}
}
}
}

return bitmap;
}

/// <summary>
/// Creates a bitmap (raster image) of this QR code.
/// <para>
/// The <paramref name="scale"/> parameter specifies the scale of the image, which is
/// equivalent to the width and height of each QR code module. Additionally, the number
/// of modules to add as a border to all four sides can be specified.
/// </para>
/// <para>
/// For example, <c>ToBitmap(scale: 10, border: 4)</c> means to pad the QR code with 4 white
/// border modules on all four sides, and use 10&#xD7;10 pixels to represent each module.
/// </para>
/// <para>
/// The resulting bitmap uses the pixel format <see cref="PixelFormat.Format24bppRgb"/>.
/// If not specified, the foreground color is black (0x000000) und the background color always white (0xFFFFFF).
/// </para>
/// </summary>
/// <param name="scale">The width and height, in pixels, of each module.</param>
/// <param name="border">The number of border modules to add to each of the four sides.</param>
/// <returns>The created bitmap representing this QR code.</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="scale"/> is 0 or negative, <paramref name="border"/> is negative
/// or the resulting image is wider than 32,768 pixels.</exception>
public static SKBitmap ToBitmap(this QrCode qrCode, int scale, int border)
{
return qrCode.ToBitmap(scale, border, SKColors.Black, SKColors.White);
}

/// <inheritdoc cref="ToPng(QrCode, int, int)"/>
/// <param name="background">The background color.</param>
/// <param name="foreground">The foreground color.</param>
public static byte[] ToPng(this QrCode qrCode, int scale, int border, SKColor foreground, SKColor background)
{
using SKBitmap bitmap = qrCode.ToBitmap(scale, border, foreground, background);
using SKData data = bitmap.Encode(SKEncodedImageFormat.Png, 90);
return data.ToArray();
}

/// <summary>
/// Creates a PNG image of this QR code and returns it as a byte array.
/// <para>
/// The <paramref name="scale"/> parameter specifies the scale of the image, which is
/// equivalent to the width and height of each QR code module. Additionally, the number
/// of modules to add as a border to all four sides can be specified.
/// </para>
/// <para>
/// For example, <c>ToPng(scale: 10, border: 4)</c> means to pad the QR code with 4 white
/// border modules on all four sides, and use 10&#xD7;10 pixels to represent each module.
/// </para>
/// <para>
/// If not specified, the foreground color is black (0x000000) und the background color always white (0xFFFFFF).
/// </para>
/// </summary>
/// <param name="scale">The width and height, in pixels, of each module.</param>
/// <param name="border">The number of border modules to add to each of the four sides.</param>
/// <returns>The created bitmap representing this QR code.</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="scale"/> is 0 or negative, <paramref name="border"/> is negative
/// or the resulting image is wider than 32,768 pixels.</exception>
public static byte[] ToPng(this QrCode qrCode, int scale, int border)
{
return qrCode.ToPng(scale, border, SKColors.Black, SKColors.White);
}

/// <inheritdoc cref="SaveAsPng(QrCode, string, int, int)"/>
/// <param name="background">The background color.</param>
/// <param name="foreground">The foreground color.</param>
public static void SaveAsPng(this QrCode qrCode, string filename, int scale, int border, SKColor foreground, SKColor background)
{
using SKBitmap bitmap = qrCode.ToBitmap(scale, border, foreground, background);
using SKData data = bitmap.Encode(SKEncodedImageFormat.Png, 90);
using FileStream stream = File.OpenWrite(filename);
data.SaveTo(stream);
}

/// <summary>
/// Saves this QR code as a PNG file.
/// <para>
/// The <paramref name="scale"/> parameter specifies the scale of the image, which is
/// equivalent to the width and height of each QR code module. Additionally, the number
/// of modules to add as a border to all four sides can be specified.
/// </para>
/// <para>
/// For example, <c>SaveAsPng("qrcode.png", scale: 10, border: 4)</c> means to pad the QR code with 4 white
/// border modules on all four sides, and use 10&#xD7;10 pixels to represent each module.
/// </para>
/// <para>
/// If not specified, the foreground color is black (0x000000) und the background color always white (0xFFFFFF).
/// </para>
/// </summary>
/// <param name="scale">The width and height, in pixels, of each module.</param>
/// <param name="border">The number of border modules to add to each of the four sides.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="scale"/> is 0 or negative, <paramref name="border"/> is negative
/// or the resulting image is wider than 32,768 pixels.</exception>
public static void SaveAsPng(this QrCode qrCode, string filename, int scale, int border)
{
qrCode.SaveAsPng(filename, scale, border, SKColors.Black, SKColors.White);
}
}
}
25 changes: 25 additions & 0 deletions Extensions/UrlHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace ELabel.Extensions
{
public static class UrlHelper
{
public static string GetBaseUrl(HttpRequest request)
{
return $"{request.Scheme}://{request.Host}{request.PathBase}";
}

public static string GetShortUrl(string baseUrl, string code)
{
return $"{baseUrl}/l/{code}";
}

public static string GetQrCodeUrl(string baseUrl, string code, string format = "svg")
{
string url = $"{baseUrl}/Label/QRCode/{code}";

if(format.ToLower() != "svg")
url += $"?format={format}";

return url;
}
}
}
13 changes: 12 additions & 1 deletion ViewModels/ProductExcelDto.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ELabel.Models;
using Ganss.Excel;

namespace ELabel.ViewModels
{
Expand Down Expand Up @@ -32,8 +33,18 @@ public class ProductExcelDto : AuditableEntity

public string? Sku { get; set; }

[Ganss.Excel.DataFormat("0")]
public ulong? Ean { get; set; }

public required string ShortUrl { get; set; } = string.Empty;
// Export only properties

public string Code { get; set; } = String.Empty;

[Ganss.Excel.Formula]
public string? ShortUrl { get; set; }
[Ganss.Excel.Formula]
public string? QRCode { get; set; }
[Ganss.Excel.Formula]
public string? QRCodePNG { get; set; }
}
}

0 comments on commit bef68d5

Please sign in to comment.