Malaysia Covid-19 Daily New Cases using Vue and Chart.js

Vue.js and Chart.js are two popular javascript libraries, today we are going to learn how to draw a chart for Covid-19 using Open data on COVID-19 in Malaysia.

For beginners to learn Vue.js, sometimes it is simpler to code directly using an HTML with javascript file and a browser instead of using Node.js andĀ  Vue CLI, so this post will use this approach. To add all the javascript libraries to theĀ  HTML, we will use external CDN and script tags.

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script src="https://unpkg.com/vue@^2/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<script src="https://unpkg.com/vue-chartjs/dist/vue-chartjs.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.1/papaparse.min.js"></script>

Above are the libraries we will use, they are:

  1. Vue.js – A Progressive JavaScript Framework.
  2. Chart.js – A simple, clean and engaging HTML5 based JavaScript charts.
  3. Vue-Chartjs – A wrapper for Chart.js in Vue.
  4. PapaParse – A library to parse CSV in Javascript.
  5. Bootstrap – A frontend framework.
  6. jQuery – A fast, small, and feature-rich JavaScript library, but mainly due to coming with Bootstrap for this tutorial.
  7. Proper.js – Position tooltips and popovers in modern web applications, come with Bootstrap.

You do not always need to use the CDN above, you can choose to download to your local folder or use other CDN servers.

To make browsers support the import keyword, we need to make sure the script is module type and please do note this is support by modern browsers only, if you want to use it in IE, you quite likely need to polyfill.

<script type="module">
    import { } from 'https://unpkg.com/vue-chartjs/dist/vue-chartjs.js';
    //Other codes
</script>

Then, we define a Vue component for the bar chart we want to create. The bar chart component will accept a chartdata property which is the data from the chart. We also define some chart properties here for e.g. responsive or not, maintain aspect ratio, chart legend, title and etc.

Vue.component('barchart', {
    extends: VueChartJs.Bar,
    props: { chartdata: {
        type: Object,
        default: null
    }},
    mounted() {
        this.renderChart(this.chartdata, {
            responsive: true, 
            maintainAspectRatio: false, 
            legend: {
                position: 'top',
            },
            title: {
                display: true,
                text: 'Malaysia Covid 19 Daily New Cases using Vue and Chart.js'
            }
        });
    }
});

After having the bar chart component, we need to initialize a Vue and load the data from the external server. The this.loaded will temporary hide the chart if the data not yet loaded. After fetch the CSV data from MoH-Malaysia GitHub, we will use the Papa.parse to load the CSV with defined first row of CSV is the header. It will store in the response variable, we extract the daily new cases and date using Javascript map function. Then, we assign the values to chartdata.

var vm = new Vue({
    el: '#mychartjs',
    data: () => ({ 
        loaded: false,
        chartdata: null
    }),
    async mounted() {
        this.loaded = false;
        const response = await fetch('https://raw.githubusercontent.com/MoH-Malaysia/covid19-public/main/epidemic/cases_malaysia.csv')
            .then(response => response.text())
            .then(v => Papa.parse(v, { header: true }))
            .catch(err => console.log(err));

        let cases = response.data.map(a => a.cases_new);
        let date = response.data.map(a => a.date);
        this.chartdata = {
            labels: date,
            datasets: [{ label: "Covid 19 cases", backgroundColor: "#f87979", data: cases }]
        };
        this.loaded = true;
    }
})

Finally, we need to define the HTML element for the Vue and chart. mychartjs is the main entrance for our Vue, barchart is our Vue component, v-if is not a property for bar chart component but use to show or hide the chart, and :chartdata is the property for component use to send data into bar chart component.

<div class="container">
    <div id="mychartjs">
        <div class="row">
            <barchart class="col-md-12" v-if="loaded" :chartdata="chartdata"></barchart>
        </div>
    </div>
</div>

Below is the result of the page:

Bonus:
If you don’t want to use the fetch method, you can use the promise method to fetch the data.

//Reference: https://github.com/mholt/PapaParse/issues/752#issuecomment-567294386
const papaPromise = (url) => new Promise((resolve, reject) => {
    Papa.parse(url, {
        header: true,
        download: true,
        complete: function(results) {
            resolve(results);
        },
        error: function(error) {
            reject(error);
        }
    });
})

const response = await papaPromise("https://raw.githubusercontent.com/MoH-Malaysia/covid19-public/main/epidemic/cases_malaysia.csv")
    .then(response => response)
    .catch(err => console.log(err));

Below is the complete code:

<!doctype html>
<html lang="en">
  <head>
    <title>Malaysia Covid 19 Daily New Cases using Vue and Chart.js</title>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  </head>
  <body>

    <div class="container">
        <div id="mychartjs">
            <div class="row">
                <barchart class="col-md-12" v-if="loaded" :chartdata="chartdata"></barchart>
            </div>
        </div>
    </div>
      
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
    <script src="https://unpkg.com/vue@^2/dist/vue.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
    <script src="https://unpkg.com/vue-chartjs/dist/vue-chartjs.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.1/papaparse.min.js"></script>
    <script type="module">
        import { } from 'https://unpkg.com/vue-chartjs/dist/vue-chartjs.js';
        Vue.component('barchart', {
            extends: VueChartJs.Bar,
            props: { chartdata: {
                type: Object,
                default: null
            }},
            mounted() {
                this.renderChart(this.chartdata, {
                    responsive: true, 
                    maintainAspectRatio: false, 
                    legend: {
                        position: 'top',
                    },
                    title: {
                        display: true,
                        text: 'Malaysia Covid 19 Daily New Cases using Vue and Chart.js'
                    }
                });
            }
        });

        //Reference: https://github.com/mholt/PapaParse/issues/752#issuecomment-567294386
        const papaPromise = (url) => new Promise((resolve, reject) => {
            Papa.parse(url, {
                header: true,
                download: true,
                complete: function(results) {
                    resolve(results);
                },
                error: function(error) {
                    reject(error);
                }
            });
        })

        var vm = new Vue({
            el: '#mychartjs',
            data: () => ({ 
                loaded: false,
                chartdata: null
            }),
            async mounted() {
                this.loaded = false;
                const response = await fetch('https://raw.githubusercontent.com/MoH-Malaysia/covid19-public/main/epidemic/cases_malaysia.csv')
                    .then(response => response.text())
                    .then(v => Papa.parse(v, { header: true }))
                    .catch(err => console.log(err));

                /*const response = await papaPromise("https://raw.githubusercontent.com/MoH-Malaysia/covid19-public/main/epidemic/cases_malaysia.csv")
                    .then(response => response)
                    .catch(err => console.log(err));*/

                let cases = response.data.map(a => a.cases_new);
                let date = response.data.map(a => a.date);
                this.chartdata = {
                    labels: date,
                    datasets: [{ label: "Covid 19 cases", backgroundColor: "#f87979", data: cases }]
                };
                this.loaded = true;
            }
        })
    </script>
  </body>
</html>

The State of .NET MAUI using CLI

What is .NET MAUI?

.NET MAUI or .NET Multi-platform App UI is a cross-platform framework to create Android, iOS, macOS, and Windows apps using one shared code-base.

.NET MAUI supported platforms. credit: https://docs.microsoft.com/en-us/dotnet/maui/what-is-maui

But wait, from the sentence and the picture above, you found something is missing. Yes, the Linux desktop app supports is not on the list, at least it is not first-class supports for the Linux desktop app. It is not always a bad idea to not include Linux support since do remember the former of MAUI, Xamarin was coming from the Mono community. Hopefully, in the future, Microsoft or the community will add in the support for the Linux desktop app. Anyway, this not going to stop anyone to take a look at the .NET MAUI since it has supported most of the popular platforms.

Installation

To develop .NET MAUI using CLI, first, you must install .NET 6, you can get it from the Microsoft .NET website, and currently, .NET 6 is still in preview release. Then, install the maui-check utility. It will help you to check any tools and SDKs required by .NET MAUI, if missing, it will prompt you to install it or ignore it.

dotnet tool install -g redth.net.maui.check

After installed, execute the command below to start the MAUI check.

maui-check
.NET MAUI CHECK

Then, we use .NET CLI to create a new MAUI project named as MauiState and switch to the directory. Execute the dotnet restore to restore the NuGet.

dotnet new maui -o MauiState; cd MauiState
dotnet restore

Once done, open your favourite edit e.g. Visual Studio Code. A modification has been done to add an alert message box. The first code below is MainPage.xaml and the second is MainPage.xaml.cs.

<ScrollView Padding="{OnPlatform iOS='30,60,30,30', Default='30'}">
    <Grid RowSpacing="25" RowDefinitions="Auto,Auto,Auto,Auto,Auto,*">
        <!--Ommited code-->
        <Button Text="Click me"
            Grid.Row="3"
            SemanticProperties.Hint="Counts the number of times you click"
            Clicked="OnCounterClicked"
            HorizontalOptions="Center" />
        <Button Text="Hello"
            Grid.Row="4"
            SemanticProperties.Hint="Hello .NET"
            Clicked="OnHelloClicked"
            HorizontalOptions="Center" />
        <!--Ommited code-->
    </Grid>
</ScrollView>
private void OnHelloClicked(object sender, EventArgs e)
{
	DisplayAlert ("Alert", "Hello .NET!", "OK");
}

After you have saved the file, you can run the app already. We will try to run using Android, and it is recommended to start your android emulator first. For this post scenario, it failed to start the emulator automatically, but once the emulator started, it can deploy and run without issue.

#Use the two commands below if you want to run Android emulator first
emulator -list-avds
#Pixel_2_API_30 is one of the result of the list above
emulator -avd Pixel_2_API_30
dotnet build -t:RUN -f net6.0-android
Result:
.NET MAUI Hello World

One thing to note, the .NET MAUI CLI was unable to run the WinUI app. Guess it is still in the preview version, and if using Visual Studio, it can run without issue.

Finally, the .NET MAUI looks quite pretty, impressive and simple. Hopefully, for the final release, it will be more integrated with the CLI.

Migrating IE ActiveXObject XMLHTTP XMLDOM to Chrome

Internet Explorer lifeline is set to unplug by Microsoft starting from June 15, 2022, so it already entering the countdown stage and everyone should need to full force and rushing to migrate legacy IE-only web applications to Chrome?

Credit: Pixabay

Actually, Microsoft still will provide backward compatibility for IE until at least 2029 inside the Edge browser via Microsoft Edge with IE mode. Detail information about the timeline can be found here > https://docs.microsoft.com/en-us/lifecycle/faq/internet-explorer-microsoft-edge

Although it still has some years to go before the actual unplug, it is no reason for us to continue procrastination about the migration. Today let us take a look at how to migrate some of the common use IE ActiveXObject Microsoft.XMLHTTP and Microsoft.XMLDOM to Chrome/Firefox…browser.

To simulate the Web API, let use .NET 6 Web API template project WeatherForecaset as our API. Please run the command below to create a new Web API project.

dotnet new webapi -o webapi

To enable XML format for that API, we need to AddXmlSerializerFormatters to the controller. We disabled the https redirection to make the test project simple and then add allowed server static files from the webserver.

using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers().AddXmlSerializerFormatters();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new() { Title = "webapi", Version = "v1" });
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (builder.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseSwagger();
    app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "webapi v1"));
}

//app.UseHttpsRedirection();

app.UseAuthorization();

app.UseDefaultFiles();
app.UseStaticFiles();

app.MapControllers();

app.Run();

For Javascript, the ActiveXObject Microsoft.XMLHTTP and Microsoft.XMLDOM actually have their equivalent object in Chrome liked browser, let have some of the comparisons on the table below. Noted: the Chrome supported code below are built-in code, you can consider using other libraries like jQuery too.

IE supported code Chrome supported code
xhr = new ActiveXObject(“Microsoft.XMLHTTP”); xhr = new XMLHttpRequest();
var domDoc = new ActiveXObject(“Microsoft.XMLDOM”); domDoc = new DOMParser().parseFromString(xhr.responseText, “text/xml”);
var node = root.selectSingleNode (“/ArrayOfWeatherForecast/WeatherForecast/Summary”); No equivalent, but it can code manually. Refer to funcSelectSingleNode.
var nodes = root.selectNodes(“WeatherForecast”); var nodes = root.getElementsByTagName(“WeatherForecast”);
element.text element.innerHTML
nodes[i].selectNodes(“Date”)[0].childNodes[0].text nodes[i].getElementsByTagName(“Date”)[0].childNodes[0].nodeValue
<!DOCTYPE html>
<html>
    <head>
        <title>XML - IE vs Chrome</title>
        <style>
            table,
            th,
            td {
                border: 1px solid black;
                border-collapse: collapse;
                padding: 5px;
            }
        </style>
    </head>
    <body>
        <div id="browser"></div>
        <div id="xsd"></div>
        <table id="result"></table>
        <div id="xml"></div>
        <script>
            var xhr;
            if (!window.document.documentMode) {
                xhr = new XMLHttpRequest();
                document.getElementById("browser").innerText = "Chrome";
            } else {
                xhr = new ActiveXObject("Microsoft.XMLHTTP");
                document.getElementById("browser").innerText = "IE";
            }
            xhr.onreadystatechange = function () {
                if (xhr.readyState == XMLHttpRequest.DONE) {
                    console.log(xhr.responseText);
                    if (window.document.documentMode) {
                        var domDoc = new ActiveXObject("Microsoft.XMLDOM");
                        domDoc.loadXML(xhr.responseText);

                        funcIE(domDoc);
                    }
                    else {
                        domDoc = new DOMParser().parseFromString(xhr.responseText, "text/xml");

                        funcChrome(domDoc);
                    }
                }
            }
            xhr.open('GET', './WeatherForecast?random=' + Math.random(), true);
            xhr.setRequestHeader("Accept", "application/xml")
            xhr.send(null);

            function funcIE(xmlDoc) {
                var root = xmlDoc.documentElement;

                var xsd = root.getAttribute("xmlns:xsd");
                document.getElementById("xsd").innerText = xsd;

                var node = root.selectSingleNode("/ArrayOfWeatherForecast/WeatherForecast/Summary");
                node.text = node.text + "+";

                var firstNode = root.selectSingleNode("WeatherForecast");
                var newNode = firstNode.cloneNode(true);
                root.appendChild(newNode);

                var newElem = xmlDoc.createElement("WeatherForecast");
                var tempElem = xmlDoc.createElement("Date");
                tempElem.text = new Date().toISOString();
                newElem.appendChild(tempElem);
                tempElem = xmlDoc.createElement("TemperatureC");
                tempElem.text = "32";
                newElem.appendChild(tempElem);
                tempElem = xmlDoc.createElement("Summary");
                tempElem.text = "Hot";
                newElem.appendChild(tempElem);
                root.appendChild(newElem);

                var n = root.selectSingleNode('WeatherForecast[2]');
                root.removeChild(n);

                var table = "<tr><th>Date</th><th>TemperatureC</th><th>Summary</th></tr>";
                var nodes = root.selectNodes("WeatherForecast");
                for (var i = 0; i < nodes.length; i++) {
                    table += "<tr><td>" +
                        nodes[i].selectNodes("Date")[0].childNodes[0].text +
                        "</td><td>" +
                        nodes[i].selectNodes("TemperatureC")[0].childNodes[0].nodeValue +
                        "</td><td>" +
                        nodes[i].selectNodes("Summary")[0].childNodes[0].nodeValue +
                        "</td></tr>";
                }
                document.getElementById("result").innerHTML = table;
                document.getElementById("xml").innerText = xmlDoc.xml;
            }

            function funcChrome(xmlDoc) {
                var root = xmlDoc.documentElement;

                var xsd = root.getAttribute("xmlns:xsd");
                document.getElementById("xsd").innerText = xsd;

                var node2 = funcSelectSingleNode(xmlDoc, "/ArrayOfWeatherForecast/WeatherForecast/Summary");
                node2.childNodes[0].nodeValue = node2.childNodes[0].nodeValue + "+";

                var firstNode = root.getElementsByTagName("WeatherForecast");
                var newNode = firstNode[0].cloneNode(true);
                root.appendChild(newNode);

                var newElem = xmlDoc.createElement("WeatherForecast");
                var tempElem = xmlDoc.createElement("Date");
                tempElem.innerHTML = new Date().toISOString();
                newElem.appendChild(tempElem);
                tempElem = xmlDoc.createElement("TemperatureC");
                tempElem.innerHTML = "32";
                newElem.appendChild(tempElem);
                tempElem = xmlDoc.createElement("Summary");
                tempElem.innerHTML = "Hot";
                newElem.appendChild(tempElem);
                root.appendChild(newElem);

                var n = root.getElementsByTagName('WeatherForecast')[2];
                root.removeChild(n);

                var table = "<tr><th>Date</th><th>TemperatureC</th><th>Summary</th></tr>";
                var nodes = root.getElementsByTagName("WeatherForecast");
                for (var i = 0; i < nodes.length; i++) {
                    table += "<tr><td>" +
                        nodes[i].getElementsByTagName("Date")[0].childNodes[0].nodeValue +
                        "</td><td>" +
                        nodes[i].getElementsByTagName("TemperatureC")[0].childNodes[0].nodeValue +
                        "</td><td>" +
                        nodes[i].getElementsByTagName("Summary")[0].childNodes[0].nodeValue +
                        "</td></tr>";
                }
                document.getElementById("result").innerHTML = table;
                document.getElementById("xml").innerText = xmlDoc.documentElement.outerHTML;
            }

            function funcSelectSingleNode(xmlDoc, elementPath) {   
                if (document.implementation && document.implementation.createDocument) {         
                    var nodes = document.evaluate(elementPath, xmlDoc, null, XPathResult.ANY_TYPE, null);
                    return nodes.iterateNext();
                }
            }
        </script>
    </body>
</html>

Below are the results:

GitHub Source Code:
https://github.com/sanme98/Migrating_ActiveXObject_XMLHTTP_XMLDOM

Reference:
https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms757828(v=vs.85)
https://forums.asp.net/t/1227814.aspx?xml+SelectNodes+not+working+in+firefox+in+javascript+
https://blog.darkthread.net/blog/migrate-xml-data-island/

How to use AX2012 R2 ERD to get a field relationship?

A few beginners found it is not easy to get a field relationship in AX2012 R2 Entity Relationship Diagrams. This may be due to the AxErd was linked and shown by all the fields link to the table (foreign keys) first instead of list all the fields of the table itself. But this is not an issue once you know how to find it. Let has an example.

In SALESTABLE there is a field called WORKERSALESRESPONSIBLE, this field shows a long type 10 digit numbers. I want to find the table to show me the Name of the worker.

In order to search the field, let go to the Alphabetical list of all tables, locate the S and click the link.

After that, Ctrl+F to try to locate the SalesTable. Apparently, it is not on this page. Then, we scroll down to the bottom and click to navigate to the next page and try to find it again. And then, we found the SalesTable, click it to continue.

On this page (image below), although we found we are in a list of fields of SalesTable, we can’t see the field we are looking for. This is due to the page only display fields that are linked to the SalesTable, not the SalesTable fields. To go to the SalesTable fields, we just need to click any SalesTable link on this page.
Ctrl+F again to find the WorkerSalesResponsible and we located the field we needed. From that, we can see it is linked with the HcmWorker table. Click the HcmWorker link and we will inside the ParentChilds page. Click again the HcmWorker table to list all the fields of the table. The process continues for DirPerson, DirPartyTable and from the Dir tables, you will be able to get the name.

This is one of the ways to use the AxErd, hope this will help readers to use it more effectively.

Different ways to consume/call Web API in .NET

Did you know there have different ways to consume a Web API in .NET? In this post, it will show different methods to call an API.

1) Using WebClient

string GetApiByWebClient(string byQuery, string byHeader)
{
    using WebClient webClient = new WebClient();
    if (byHeader.Length > 0)
    {
        webClient.Headers.Add("name", byHeader);
    }
    webClient.Headers.Add("Accept", "application/json");
    webClient.Headers.Add("Content-Type", "application/json");
    return webClient.DownloadString(myApiUrl + (byQuery.Length == 0 ? "" : "?name=" + byQuery));
}

string PostApiByWebClient(string data)
{
    using WebClient webClient = new WebClient();
    webClient.Headers.Add("Accept", "application/json");
    webClient.Headers.Add("Content-Type", "application/json");
    return webClient.UploadString(myApiUrl, WebRequestMethods.Http.Post, data);
}

2) Using HttpWebRequest

string PostApiByHttpWebRequest(string data)
{
    HttpWebRequest webClient = (HttpWebRequest)WebRequest.Create(myApiUrl);
    webClient.Accept = "application/json";
    webClient.ContentType = "application/json";
    webClient.Method = WebRequestMethods.Http.Post;
    webClient.ContentLength = data.Length;
    using (var writer = new System.IO.StreamWriter(webClient.GetRequestStream()))
    {
        writer.Write(data);
    }
    var response = webClient.GetResponse() as HttpWebResponse;
    using (var streamReader = new StreamReader(response.GetResponseStream()))
    {
        return streamReader.ReadToEnd();
    }
}

3) Using HttpClient

async Task<string> PostApiByHttpClientAsync(string data)
{
    string result = "";

    using (var httpClient = new HttpClient())
    {
        httpClient.DefaultRequestHeaders.Add("Accept", "application/json");

        HttpContent httpContent = new StringContent(data);
        httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

        using (HttpResponseMessage response = await httpClient.PostAsync(myApiUrl, httpContent))
        {
            if (response.Content != null && response.IsSuccessStatusCode)
            {
                result = await response.Content.ReadAsStringAsync();
            }
            else
            {
                if (response.Content != null)
                {
                    result = await response.Content.ReadAsStringAsync();
                    string message = "POST Response Fail : " + result;
                    throw new Exception(message);
                }
                else
                {
                    string message = "POST Response Fail : No response";
                    throw new Exception(message);
                }
            }
        }
    }

    return result;
}

Apparently, the third method is longer, but as you can see from the code, it is an asynchronous method, which means it is an efficient approach towards activities blocked or access is delayed e.g. Web API. Therefore, it is a recommended method if responsive is important for the application you want to develop.

Bonus:
4) Using Powershell
Lastly, how about after you deployed to an environment and it is not working? You not sure is it due to a firewall or network issue and you have no tools like POSTMAN….? Then, PowerShell can come to help.

PS D:\Temp\dotnetcore\ConsumeAPI> Invoke-WebRequest -Uri http://localhost:7071/api/HttpExample `
>>     -ContentType 'application/json' `
>>     -Method POST `
>>     -Body '{name: "POST API request from PowerShell"}'


StatusCode        : 200
StatusDescription : OK
Content           : Hello, POST API request from PowerShell.
RawContent        : HTTP/1.1 200 OK
                    Transfer-Encoding: chunked
                    Content-Type: text/plain; charset=utf-8
                    Date: Sat, 14 Aug 2021 09:16:38 GMT
                    Server: Kestrel

                    Hello, POST API request from PowerShell.
Forms             : {}
Headers           : {[Transfer-Encoding, chunked], [Content-Type, text/plain; charset=utf-8], [Date, Sat, 14 Aug 2021
                    09:16:38 GMT], [Server, Kestrel]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 40

.NET Interactive as C#/F#/PowerShell scratchpad/jupyter in VS Code

Are you still using a Visual Studio Console Application to write some simple testing code for C#/F#? Are you still looking for some alternative that is similar to Python Jupyter Notebook in .NET world? Are you still looking for a scratchpad application to write some simple .NET code? Are you want to learn and try .NET programming in a simple way but don’t have a simple learning tool?

Now you don’t need to continue the search for the alternative, .NET Interactive is the one you need. It is exactly similar to Python Jupyter Notebook and you can install it in Visual Studio Code.

Getting Started

  1. Install the latest Visual Studio Code.
  2. Install the latest .NET 5 SDK
  3. Install the .NET Interactive Notebooks extension from the marketplace.

After you have installed it, to start it, just follow the steps below:

  1. Press Ctrl+Shift+P to open the Command Palette.
  2. Select .NET Interactive: Create new blank notebook.
  3. Create as ‘.dib’ if you prefer the latest notebook format.
  4. Choose your preferred programming language from C#/F#/PowerShell.
  5. And you are done. Just start your coding :).

As you can see from the screenshot above, it supports IntelliSense (autocomplete) though it does not support debugging now.

FAQ:
Q: How to add reference for a library?
A: You can use something like below:

#r "System.Data.SqlClient"
#r "nuget:System.Text.Json,4.7.2"

Q: How about using keyword? Supported?
A: Yes.

using System.Net;

Q: How to execute the code?
A: Press either Ctrl+Alt+Enter or Alt+Enter (this will add a new cell below too).

How to update Node.js in WSL/Ubuntu/Raspberry Pi?

If you try to check the version of node.js in either WSL/Ubuntu/Raspberry Pi, you normally found the version outdated even though you have run all the updates. E.g. in WSL Ubuntu 18.04, after ran all the updates, it is still version v8.10.0.

sudo apt-get update
sudo apt-get upgrade
node -v

For this, you can consider removing the system existing and update it manually. If you want to uninstall, run the command below.

sudo apt-get remove nodejs npm node-semver

Then go to nodejs.org to download the update > https://nodejs.org/en/download/.
Beware of the version you need to download either is WSL/Ubuntu x64 or Raspberry PI ARMv7 (32-bit) or ARMv8 (64-bit). You can check your version using the command below.

uname -a
# WSL Ubuntu will be something like below
# Linux ComputerName 5.4.72-microsoft-standard-WSL2 #1 SMP Wed Oct 28 23:40:43 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
# Raspberry Pi ARMv7
# Linux raspberrypi 5.10.17-v7l+ #1421 SMP Thu May 27 14:00:13 BST 2021 armv7l GNU/Linux

Then, run the command below to install manually.

# Replace the URL copy from above for correct installer and newer version
wget https://nodejs.org/dist/v14.17.3/node-v14.17.3-linux-x64.tar.xz
sudo mv node-v14.17.3-linux-x64.tar.xz /opt
cd /opt
sudo tar -xf node-v14.17.3-linux-x64.tar.xz
sudo mv node-v14.17.3-linux-x64 nodejs
sudo rm node-v14.17.3-linux-x64.tar.xz
sudo ln -s /opt/nodejs/bin/node /usr/bin/node
sudo ln -s /opt/nodejs/bin/npm /usr/bin/npm
sudo ln -s /opt/nodejs/bin/npx /usr/bin/npx

Lastly, run the node -v, you will get v14.17.3 and npm -v 6.14.13.

CRUD DataTables.js using ASPNET WebForm

It is common for us to use asp.net DataGrid to do CRUD operations, but how about we want to use inline edit DataTables.js since it reduces some round trips needed to the server? So, today we will learn how to create an example project with CRUD DataTables.js using ASP.NET WebForm.

To be able to CRUD DataTable.js using asp.net webform, in this tutorial, we are not using AJAX (though with some further modifications, you can, since AJAX is supported by .NET 2 and above.). Instead, we use few hidden fields to store the JSON values.

<form id="form1" runat="server">
    <asp:HiddenField ID="HiddenFieldUser" runat="server" />
    <asp:HiddenField ID="HiddenFieldDeletedUser" runat="server" />
    <asp:HiddenField ID="HiddenFieldCity" runat="server" />

After declared the hidden fields, in the Page_Load, we serialize the JSON object and assign it to the hidden field.

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        List<User> deletedUserList = new List<User>();
        HiddenFieldDeletedUser.Value = JsonConvert.SerializeObject(deletedUserList);
        
        List<City> cityList = new List<City>();
        cityList.Add(new City { Name = "Kuala Lumpur", Value = "Kuala Lumpur" });
        cityList.Add(new City { Name = "Petaling Jaya", Value = "Petaling Java" });
        cityList.Add(new City { Name = "Subang", Value = "Subang" });
        cityList.Add(new City { Name = "Penang", Value = "Penang" });
        cityList.Add(new City { Name = "Melaka", Value = "Melaka" });
        cityList.Add(new City { Name = "Johor Bahru", Value = "Johor Bahru" });
        HiddenFieldCity.Value = JsonConvert.SerializeObject(cityList);

        DataTable userTable = InMemorySqlite.Instance.GetDataTable("SELECT RowID, * FROM User");
        List<User> userList = new List<User>();
        foreach (DataRow row in userTable.Rows)
        {
            User newUser = new User();
            newUser.RowID = Convert.ToInt64(row["RowID"]);
            newUser.Name = row["Name"].ToString();
            newUser.Age = row["Age"] == DBNull.Value ? 0 : Convert.ToInt32(row["Age"]);
            newUser.City = row["City"].ToString();
            newUser.Email = row["Email"].ToString();
            newUser.JoinedDate = row["JoinedDate"] == DBNull.Value ? new DateTime(1900, 1, 1) : Convert.ToDateTime(row["JoinedDate"]);
            newUser.ModifiedDate = row["ModifiedDate"] == DBNull.Value ? new DateTime(1900, 1, 1) : Convert.ToDateTime(row["ModifiedDate"]);
            userList.Add(newUser);
        }
        HiddenFieldUser.Value = JsonConvert.SerializeObject(userList);
    }
}

Inside the Javascript, we assign the value after DOM is loaded from the hidden field to populate the data for datatables.js.

//After document ready
var dataSource = JSON.parse($("#HiddenFieldUser").val());

Then, we assign it back to the hidden field during we save and before post it back to the server.

<asp:Button class="btn btn-danger" ID="btnSave" runat="server" Text="Save" OnClick="btnSave_Click" OnClientClick="fnSave();" />
//Before submit back to the server
function fnSave() {
    var jsonData = JSON.stringify($('#dtUser').DataTable().rows().data().toArray());
    $('input#HiddenFieldUser').val(jsonData);
}

Finally, in the C# code, we deserialize the hidden field value to get the JSON objects.

List<User> userList = JsonConvert.DeserializeObject<List<User>>(HiddenFieldUser.Value);    

Below is the result of the code.

Example project on Github:
https://github.com/sanme98/CRUD_DataTables_ASPNET_WebForm

Reference:
https://www.c-sharpcorner.com/article/incell-edting-using-datatable/

IIS Application Pool crash abruptly if enabled Oracle Transaction

If your IIS Application Pool crash abruptly without any detail error messages if you enabled Oracle Transaction in ASP.NET using one of the two ways below:
Method 1:

<%@ Page Transaction="RequiresNew" Language="vb" AutoEventWireup="false" ....

Method 2:

using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions(), EnterpriseServicesInteropOption.Full))

Please try to check either you are using Oracle Connection String by omitting tnsnames.ora or not.

SERVER=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=MyHost)(PORT=MyPort))(CONNECT_DATA=(SERVICE_NAME=MyOracleSID)));uid=myUsername;pwd=myPassword;

Instead, you should be using the standard Oracle Connection String which needs to define in tnsname.ora.

Data Source=MyOracleDB;Integrated Security=yes;

Besides that, you might get the error below:

at System.Data.Common.UnsafeNativeMethods.OraMTSEnlCtxGet(Byte[] lpUname, Byte[] lpPsswd, Byte[] lpDbnam, OciHandle pOCISvc, OciHandle pOCIErr, UInt32 dwFlags, IntPtr&amp; pCtxt)
at System.Data.OracleClient.TracedNativeMethods.OraMTSEnlCtxGet(Byte[] userName, Byte[] password, Byte[] serverName, OciHandle pOCISvc, OciHandle pOCIErr, IntPtr&amp; pCtxt)
at System.Data.OracleClient.OciEnlistContext..ctor(Byte[] userName, Byte[] password, Byte[] serverName, OciServiceContextHandle serviceContextHandle, OciErrorHandle errorHandle)
at System.Data.OracleClient.OracleInternalConnection.Enlist(String userName, String password, String serverName, Transaction transaction, Boolean manualEnlistment)
at System.Data.OracleClient.OracleInternalConnection.EnlistTransaction(Transaction transaction)
at System.Data.OracleClient.OracleConnection.EnlistTransaction(Transaction transaction)

According to a forum user replied, it was due to the string buffer of 40 bytes limitation. You can check the detail on the reference links below.

References:
https://www.connectionstrings.com/oracle/
http://www.databaseforum.info/25/548397.aspx

How to enable WordPress.com Classic Editor and use the Keyboard Shortcuts?

Although WordPress.com Block Editor is the default and recommended editor, but one of the disadvantages is the editor not supported by Grammarly. So in this blog, we going to enable back the Classic Editor.

First, go to your profile page which you can locate at the top-right header profile icon there. At the settings page, turn on the “Show wp-admin pages if available”.

After enabled, in your Posts there, you will able to see Classic Editor when you hover over your blog post title.

Click the classic editor and then switch to Text instead of Visual editor, you should be able to see Grammarly enabled at the right bottom.

Below is the shortcut keys command for the classic editor, which should be helpful during you editing your blog post.

Just press the Ctrl + key on Windows for the default shortcuts.

Key Action Key Action
c Copy x Cut
v Paste a Select all
z Undo y Redo
b Bold i Italic
u Underline k Insert/edit link

Press Ctrl + Alt + key on Windows for the additional shortcuts.

Key Action Key Action
1 Heading 1 2 Heading 2
3 Heading 3 4 Heading 4
5 Heading 5 6 Heading 6
l Align left c Align center
r Align right j Justify
d Strikethrough q Blockquote
u Bullet list o Numbered list
a Insert/edit link s Remove link
m Insert/edit image t Insert Read More tag
h Keyboard Shortcuts x Code
p Insert Page Break tag