Tuesday, October 6, 2020

Simple Cryptocurrency App With Blazor Server Incl Excel Export

For some reason, I started to like developing with Blazor Server, mainly because the rapidity and simplicity of development. I tend to use some free component libraries to speed up development. I still want to mention that all my experience with Blazor Server is only on tutorial/experimental level.

This is just an application to learn Blazor Server, and familiarize with Cryptocurrencies, it has no other purpose.



Prerequisites for this app:

- basic Blazor Server (See my previous tutorials)

- install Radzen Components in your app see https://blazor.radzen.com/get-started

- using the C# wrapper for CoinPaprika Api (to get cryptocurrency info) https://github.com/MSiccDev/CoinpaprikaAPI

Code repository: https://github.com/zoltanhalasz/BlazorCoins

Application is live under: https://blazorcoins.zoltanhalasz.net/

The app works the following way:

1. the route /cointable will show the top 100 crypto currencies, using the API



2. clicking the detail per each currency, the route /coinhistory will show a chart and historical data for selected currency



3. the route /coingrid will show exactly the same info as 1) only in a Radzen Grid




Bonus:

The app has export excel capabilities, which can be achieved doing the following:

1. create wwwroot/js/DownloadExcel.js

function saveAsExcel(fileName, byteContent) {

    var link = document.createElement('a');

    link.download = fileName;

    console.log(fileName);

    link.href = 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,' + byteContent;

    document.body.appendChild(link);

    link.click();

    document.body.removeChild(link);

}

2. include the file reference at the bottom of _Host.cshtml, just below the other scripts

    <script src="/js/DownloadExcel.js"></script>

3. install EPPlus, latest, via Nuget Packages

4. in page /cointable (or where you need to export from excel), insert the markup for a button

 <button class="btn btn-primary" @onclick="GenerateExcel">Download</button>

5. include the following in the top of the page you want to use Excel Export :

@inject IJSRuntime iJSRuntime;

6. Write the handlers for GenerateExcel, in the code section of the page where you have the list that you need to export in Excel

private async Task DownloadExcel(IJSRuntime myJSRuntime)

    {

        byte[] file;


        if (coinList == null) return;


        ExcelPackage.LicenseContext = LicenseContext.NonCommercial;

        using (var package = new ExcelPackage())

        {

            var worksheet = package.Workbook.Worksheets.Add("Sheet1");

            worksheet.Cells.LoadFromCollection(coinList, true);

            package.Save();

            file = package.GetAsByteArray();

        }

        await myJSRuntime.InvokeAsync<List<CoinInfo>>("saveAsExcel", "CoinList.xlsx", Convert.ToBase64String(file));

    }


    private async Task GenerateExcel()

    {

        await DownloadExcel(iJSRuntime);

    }



Monday, October 5, 2020

Upload and Download Pdf files to/from MS SQL Database using Razor Pages

As most of my project applications are business related, and I still plan to create such applications, thought about the idea to store attached pdf invoices in a database.

Below is a simple sample application, using Asp.Net Core 3.1 and Razor Pages, for an invoice management system with pdf file upload/download.

The Github repo for the application is here.

The application is also online, can be tested out: https://uploadfile.zoltanhalasz.net/

Prerequisites:

- beginner/intermediate Razor Pages for Asp.Net Core, see study material here https://mydev-journey.blogspot.com/2019/11/razor-pages-not-for-shaving.html

- intermediate C# and SQL

- some basic Entity Framework Core.

My sources for learning were:

- an MVC version for the upload: https://tutexchange.com/how-to-upload-files-and-save-in-database-in-asp-net-core-mvc/

- the download, was discovered using various C# resources.

Firstly, we create the database named UploadFile and then a table:

CREATE TABLE [dbo].[Invoices](

[Id] [int] IDENTITY(1,1) NOT NULL,

[Number] [int] NOT NULL,

[Date] [datetime] NOT NULL,

[Value] [decimal](18, 2) NOT NULL,

[Attachment] [varbinary](max) NULL,

 CONSTRAINT [PK_Invoices] PRIMARY KEY CLUSTERED ([Id] ASCWITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

Steps for creating the application:
1. Create an Asp.Net Razor Pages project
.Net Core version 3.1

2. Scaffold the database into the models using https://marketplace.visualstudio.com/items?itemName=ErikEJ.EFCorePowerTools or simply copy my Data folder that is adding the necessary data structures 
Alternatively, you can use the more traditional caffolding method: https://www.entityframeworktutorial.net/efcore/create-model-for-existing-database-in-ef-core.aspx

Make sure that the model and dbContext  (called UploadFileContext) will be stored in Data folder.

3. Add to Startup.cs, ConfigureServices method:

 services.AddDbContext<UploadfileContext>(options =>
                  options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),           
                    sqlServerOptions => sqlServerOptions.CommandTimeout(100))
                    );

4. Create and Index page, that will show the list of invoices
- the page cshtml file will contain the listing of the invoices, together with the download, upload, and delete functionalities


- the PageModel class will contain the methods to deal with:

a. download the pdf file from the database:

public async Task<IActionResult> OnPostDownloadAsync(int? id)
{
            var myInv = await _context.Invoices.FirstOrDefaultAsync(m => m.Id == id);
            if (myInv == null)
            {
                return NotFound();
            }

            if (myInv.Attachment== null)
            {
                return Page();
            }
            else
            {
                byte[] byteArr = myInv.Attachment;
                string mimeType = "application/pdf";
                return new FileContentResult(byteArr, mimeType)
                {
                    FileDownloadName = $"Invoice {myInv.Number}.pdf"
                };
            }
}

b. delete attached file from database:

public async Task<IActionResult> OnPostDeleteAsync(int? id)
{
            var myInv = await _context.Invoices.FirstOrDefaultAsync(m => m.Id == id);
            if (myInv == null)
            {
                return NotFound();
            }

            if (myInv.Attachment == null)
            {
                return Page();
            }
            else
            {
                myInv.Attachment = null;
                _context.Update(myInv);
                await _context.SaveChangesAsync();
            }

            Invoices = await _context.Invoices.ToListAsync();
            return Page();
}

5. Add a page to create Invoice Data (this can be done via Razor Pages scaffolding),

and 

6. Create an Upload Page that will help with pdf file upload

This will have the file with markup, cshtml, containing the html tags for the form:
@page
@model UploadFile.Pages.UploadModel
@{
}

<h1>Upload Invoice</h1>


<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post" enctype="multipart/form-data">
            <div class="form-group">
                <div class="col-md-10">
                    <p>Upload file</p>
                    <input type="hidden" asp-for="@Model.ID" value="@Model.myID" />
                    <input asp-for="file" class="form-control" accept=".pdf" type="file" />
                </div>
            </div>
            <div class="form-group">
                <div class="col-md-10">
                    <input class="btn btn-success" type="submit" value="Upload" />
                </div>
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

And for the PageModel class, we will have the handler to deal with the file uploaded:

public class UploadModel : PageModel
{                  

        private readonly UploadfileContext _context;

        public UploadModel(UploadfileContext context)
        {
            
            _context = context;
        }
        public int ? myID { get; set; }

        [BindProperty]
        public IFormFile file { get; set; }

        [BindProperty]
        public int ? ID { get; set; }
        public void OnGet(int? id)
        {
            myID = id;
        }

        public async Task<IActionResult> OnPostAsync()
        {
            if (file != null)
            {
                if (file.Length > 0 && file.Length < 300000)
                {
                    var myInv = _context.Invoices.FirstOrDefault(x => x.Id == ID);

                    using (var target = new MemoryStream())
                    {
                        file.CopyTo(target);
                        myInv.Attachment = target.ToArray();
                    }

                    _context.Invoices.Update(myInv);
                    await _context.SaveChangesAsync();
                }

            }

            return RedirectToPage("./Index");
        }

}


7. The end result will be like this: