<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shane Kunz</title>
    <description>The latest articles on DEV Community by Shane Kunz (@shanemlk).</description>
    <link>https://dev.to/shanemlk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F322339%2F9281e5c2-c10b-4c9b-944b-fad7cf748200.jpeg</url>
      <title>DEV Community: Shane Kunz</title>
      <link>https://dev.to/shanemlk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shanemlk"/>
    <language>en</language>
    <item>
      <title>On Having a Clean Digital Workspace</title>
      <dc:creator>Shane Kunz</dc:creator>
      <pubDate>Sun, 05 Apr 2020 19:39:09 +0000</pubDate>
      <link>https://dev.to/shanemlk/on-having-a-clean-digital-workspace-lma</link>
      <guid>https://dev.to/shanemlk/on-having-a-clean-digital-workspace-lma</guid>
      <description>&lt;p&gt;We often put a lot of focus on keeping our physical workspaces clean. We remove clutter, tidy up cords, remove dust/spills, etc, but we often don't put a lot of emphasis on cleaning our digital lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Importance of Digital Cleanliness
&lt;/h2&gt;

&lt;p&gt;For anyone in an IT position, having a clean digital workspace can save you incalculable time and stress. The less files you have on you desktop, the more clear minded you can make yourself. You might not realize it, but looking at those files a few hundred times over the course of a few weeks can stress you out.&lt;/p&gt;

&lt;p&gt;When I start new jobs I create a new &lt;code&gt;/projects&lt;/code&gt; folder near the root. Any requirements docs, important emails, or notes get filed away in the correct folder. So when business analysts, project managers, or other developers ask questions about a specific project, I often have the information to help them.&lt;/p&gt;

&lt;p&gt;Take time to narrow done the tools and bookmarks you rely on. Your bookmark tool should only have the links you use regularly. You can often delete programs you no longer use, startup less processes, or consolidate multiple old programs into one new multi-use program. Also, do you really need to have 25 browser tabs open? It may be time to let go, and feel the thrill of permanently deleting your junk and clutter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Digital Minimalism
&lt;/h2&gt;

&lt;p&gt;Realize that life is more about saying no than saying yes. Your life will consist of countless opportunities, but what really matters is saying no to the garbage, and saying yes to the diamonds. Organizing your digital life can help you focus on the stuff that really matters.&lt;/p&gt;

</description>
      <category>codenewbie</category>
      <category>computerscience</category>
      <category>clean</category>
      <category>code</category>
    </item>
    <item>
      <title>I Finally Found The Perfect Linux Distro</title>
      <dc:creator>Shane Kunz</dc:creator>
      <pubDate>Mon, 03 Feb 2020 08:39:05 +0000</pubDate>
      <link>https://dev.to/shanemlk/i-finally-found-the-perfect-linux-distro-13ll</link>
      <guid>https://dev.to/shanemlk/i-finally-found-the-perfect-linux-distro-13ll</guid>
      <description>&lt;h2&gt;
  
  
  I've used MAC and Windows a lot, but I never got into Linux. That all changed 2 days ago.
&lt;/h2&gt;

&lt;h4&gt;
  
  
  I installed &lt;a href="https://system76.com/pop"&gt;Pop!_OS&lt;/a&gt; overwriting my Windows installation, and have no regrets so far.
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5PAaIyPy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8g8e6c1i8dnqj4wuby8o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5PAaIyPy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8g8e6c1i8dnqj4wuby8o.png" alt="Pop!_OS Landing Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://system76.com/pop"&gt;Pop!_OS&lt;/a&gt; was made with developers in mind. Many popular development tools are pre installed. Downloading and installing other popular tools is easy using the &lt;a href="https://github.com/pop-os/shop"&gt;Pop!_Shop&lt;/a&gt;, or by following along with simple Ubuntu installation instructions.
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MHXnaQDT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dpocyp02ihwwxqx646hq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MHXnaQDT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dpocyp02ihwwxqx646hq.png" alt="Pop!_OS Landing Page - Developer Section"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Here are some of the reasons it's awesome:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It comes pre installed with AMD/Nvidia GPU drivers that have &lt;a href="https://en.wikipedia.org/wiki/Vulkan_(API)"&gt;Vulkan&lt;/a&gt; &amp;amp; &lt;a href="https://en.wikipedia.org/wiki/OpenGL"&gt;OpenGL&lt;/a&gt; support.&lt;/li&gt;
&lt;li&gt;All Windows games are now available on Linux because of new easy to install tools. The &lt;a href="https://github.com/pop-os/shop"&gt;Pop!_Shop&lt;/a&gt; allows you to download apps like &lt;a href="https://store.steampowered.com/"&gt;Steam&lt;/a&gt; with &lt;a href="https://www.protondb.com/"&gt;Proton&lt;/a&gt;, &lt;a href="https://lutris.net/"&gt;Lutris&lt;/a&gt;, and &lt;a href="https://www.winehq.org/"&gt;Wine&lt;/a&gt; in one click.&lt;/li&gt;
&lt;li&gt;The Linux Terminal is better than &lt;code&gt;cmd&lt;/code&gt; for coding. VS Code works perfectly and is available in one click using the &lt;a href="https://github.com/pop-os/shop"&gt;Pop!_Shop&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;All the Windows apps you use are likely already available (like Steam, Spotify, and Discord) via the &lt;a href="https://github.com/pop-os/shop"&gt;Pop!_Shop&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The UI is much simpler than Windows, but more powerful than Windows.&lt;/li&gt;
&lt;li&gt;It's much faster and less bloated than Windows.&lt;/li&gt;
&lt;li&gt;It takes up less hard drive space than Windows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;You can install &lt;a href="https://system76.com/pop"&gt;Pop!_OS&lt;/a&gt; onto a flash drive with &lt;a href="https://www.balena.io/etcher/"&gt;balenaEtcher&lt;/a&gt; in just a few clicks.&lt;/strong&gt;
&lt;/h3&gt;

</description>
      <category>linux</category>
    </item>
    <item>
      <title>Scraping Yelp and Facebook with Node. Displaying data with ASP.NET Core</title>
      <dc:creator>Shane Kunz</dc:creator>
      <pubDate>Mon, 27 Jan 2020 06:30:12 +0000</pubDate>
      <link>https://dev.to/shanemlk/scraping-yelp-and-facebook-with-node-displaying-data-with-asp-net-core-5fad</link>
      <guid>https://dev.to/shanemlk/scraping-yelp-and-facebook-with-node-displaying-data-with-asp-net-core-5fad</guid>
      <description>&lt;p&gt;I spent today adding a feature to &lt;a href="https://stunodspizza.com/"&gt;StunodsPizza.com&lt;/a&gt;'s homepage to show all their positive customer reviews. We wanted to fix up their web branding today and add the reviews feature in expectation for the slow winter season.&lt;/p&gt;

&lt;p&gt;You can find the scraping code here: &lt;a href="https://github.com/shaneMLK/scrape-facebook-and-yelp-reviews"&gt;https://github.com/shaneMLK/scrape-facebook-and-yelp-reviews&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Scrape all positive reviews from a Yelp and Facebook page with a &lt;a href="https://nodejs.org/en/"&gt;Node.js&lt;/a&gt; script.

&lt;ul&gt;
&lt;li&gt;This includes grabbing the reviewer's name, avatar image, review text, and source of the review (Yelp or Facebook) and generating a .sql insert script.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Create a database schema and insert all the data into a &lt;a href="https://azure.microsoft.com/en-us/"&gt;Azure&lt;/a&gt; database.&lt;/li&gt;
&lt;li&gt;On the front end, show all the reviews in a &lt;a href="https://swiperjs.com/"&gt;Swiper.js&lt;/a&gt; carousel on the homepage using &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-3.1&amp;amp;tabs=visual-studio"&gt;Razor&lt;/a&gt;.

&lt;ul&gt;
&lt;li&gt;I want them to be randomly shuffled on page load.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Scrape the data with &lt;a href="https://nodejs.org/en/"&gt;Node.js&lt;/a&gt;.
&lt;/h3&gt;

&lt;p&gt;I started with a git repo I had used recently &lt;a href="https://github.com/hacker-DOM"&gt;hacker-DOM&lt;/a&gt;'s &lt;a href="https://github.com/hacker-DOM/github-by-stars"&gt;github-by-stars&lt;/a&gt; project.&lt;/p&gt;

&lt;p&gt;The result was this: &lt;a href="https://github.com/shaneMLK/scrape-facebook-and-yelp-reviews"&gt;scrape-facebook-and-yelp-reviews&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You have to download the pages you want to scrap using browser dev tools. The reason for this is to grab any data dynamically loaded on the client side. You then run the program against the HTML files (&lt;code&gt;npx nodemon index.js&lt;/code&gt;), and out will come SQL insert statements you can put in a database. You can also upload the avatar images to something like Azure storage or S3 AWS buckets to grab the images on a production site.&lt;/p&gt;

&lt;p&gt;For example, I visited the company's Facebook page, inspect the page with the inspector, right clicked on the root &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; tag and clicked "copy" -&amp;gt; "Outer HTML". I pasted that into a file named &lt;code&gt;FacebookReviews_1-26-2020.html&lt;/code&gt; in a folder &lt;code&gt;/html_scr&lt;/code&gt;. I made sure the file was referenced correctly in the &lt;code&gt;/src/retreiveFacebookReviews.js&lt;/code&gt; file on line 7. The project uses a library called &lt;code&gt;cherrio&lt;/code&gt;, that allows us to access the DOM of the html file as if we were using jQuery. Line 8 sets this up &lt;code&gt;const $ = cheerio.load(res)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I ran &lt;code&gt;npx nodemon index.js&lt;/code&gt; to generate .sql insert scripts I need to setup the database schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Setup your reviews database schema with Entity Framework and a Azure database.
&lt;/h3&gt;

&lt;p&gt;In my &lt;a href="https://dotnet.microsoft.com/apps/aspnet"&gt;ASP.NET Core&lt;/a&gt; project within a &lt;code&gt;/Models/ReviewContext.cs&lt;/code&gt; file, I put the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace MyProject.Models
{
    public class ReviewContext : DbContext
    {
        public ReviewContext (DbContextOptions&amp;lt;ReviewContext&amp;gt; options)
            : base(options)
        { }
        public DbSet&amp;lt;Review&amp;gt; Reviews { get; set; }

    }

    public class Review
    {
        public int Id { get; set; }
        public int UserId { get; set; }
        // UserId turned out unnecessary
        public string ReviewText { get; set; }
        public string UserName { get; set; }
        public string Source{ get; set; }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Side note that within the &lt;code&gt;Startup.cs&lt;/code&gt; in the &lt;code&gt;ConfigureServices&lt;/code&gt; method, I have the following line...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services.AddDbContext&amp;lt;ReviewContext&amp;gt;(options =&amp;gt; 
options.UseSqlServer(
Configuration.GetValue&amp;lt;string&amp;gt;("AppSettings:StorageConnectionString")));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;... which allows me to keep my Azure database connection string in my appSettings.json as &lt;code&gt;StorageConnectionString&lt;/code&gt;. This string will now be what &lt;a href="https://docs.microsoft.com/en-us/ef/"&gt;entity framework&lt;/a&gt; uses to update the database schema.&lt;/p&gt;

&lt;p&gt;I run &lt;code&gt;dotnet ef migrations add "ReviewsMigration"&lt;/code&gt; to create a migration. A migration is just a list of un-run steps to update a database.&lt;/p&gt;

&lt;p&gt;Then I run &lt;code&gt;dotnet ef database update&lt;/code&gt; to actually update the database's schema. Note that if you have a &lt;code&gt;appSettings.Development.json&lt;/code&gt;, the update will run on that file's &lt;code&gt;StorageConnectionString&lt;/code&gt;, not &lt;code&gt;appSettings.json&lt;/code&gt;'s &lt;code&gt;StorageConnectionString&lt;/code&gt; field.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Display the reviews on the front end using &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-3.1&amp;amp;tabs=visual-studio"&gt;Razor&lt;/a&gt;.
&lt;/h3&gt;

&lt;p&gt;Within &lt;code&gt;/Views/Shared/_Layout.cshtml&lt;/code&gt; I include the &lt;a href="https://swiperjs.com/"&gt;Swiper.js&lt;/a&gt; javascript and styles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="stylesheet" href="https://unpkg.com/swiper/css/swiper.min.css"&amp;gt;
&amp;lt;script src="https://unpkg.com/swiper/js/swiper.min.js"&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;_Layout.cshtml&lt;/code&gt; file is what wraps all my views. The method &lt;code&gt;@RenderBody()&lt;/code&gt; is where my inner views will render.&lt;/p&gt;

&lt;p&gt;I edited my &lt;code&gt;Index&lt;/code&gt; function in the &lt;code&gt;HomeController&lt;/code&gt; to pass all the reviews to the &lt;code&gt;Views/Home/Index.cshtml&lt;/code&gt; view by using &lt;code&gt;return View(_context.Reviews.ToList().Shuffle());&lt;/code&gt;. But in order to have access to the database context, we need to use dependency injection. At the top of the &lt;code&gt;HomeController&lt;/code&gt; class we use the following code to tell &lt;a href="https://dotnet.microsoft.com/apps/aspnet"&gt;ASP.NET&lt;/a&gt; to pass the database context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        private readonly ReviewContext _context;

        public HomeController(ReviewContext context)
        {
            _context = context;
        }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The shuffle method is an static extension method to the IList type which is declared outside the &lt;code&gt;HomeController&lt;/code&gt; class but within the same file. It simply randomizes the order of the reviews:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   public static class ShuffleExtension{
        public static IList&amp;lt;T&amp;gt; Shuffle&amp;lt;T&amp;gt;(this IList&amp;lt;T&amp;gt; list)  
        {  
            Random rng = new Random();
            int n = list.Count;  
            while (n &amp;gt; 1) {  
                n--;  
                int k = rng.Next(n + 1);  
                T value = list[k];  
                list[k] = list[n];  
                list[n] = value;  
            }
            return list;
        }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;At the top of the homepage view (&lt;code&gt;/Views/Home/Index.cshtml&lt;/code&gt;) I write &lt;code&gt;@model List&amp;lt;Review&amp;gt;&lt;/code&gt; to declare that the view is expecting a list of reviews. Our reviews carousel is going to be a separate partial view block, so we render it using &lt;code&gt;@await Html.PartialAsync("_ReviewsBlock", Model)&lt;/code&gt; within the &lt;code&gt;/Views/Home/Index.cshtml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Within the &lt;code&gt;/Views/_Shared/_ReviewsBlock.cshtml&lt;/code&gt;, I grab some AppSettings values and declare that the block is expecting a list of reviews as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@model List&amp;lt;Review&amp;gt;
@{
    var AzureBlobStorageAccountName = Configuration.GetSection("AppSettings")["AzureBlobStorageAccountName"];
    var AzureBlobStorageContainer_Users = Configuration.GetSection("AppSettings")["AzureBlobStorageContainer_Users"];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;appSettings.json&lt;/code&gt; values are just from &lt;a href="https://azure.microsoft.com/en-us/services/storage/blobs/"&gt;Azure's blob storage service&lt;/a&gt;. I have a container just for user avatar images on the reviews. I've allowed the blob storage container to be accessed anonymously. I upload the images straight from the node project's &lt;code&gt;/output&lt;/code&gt; folder to the Azure container. I can then access them all from the view like so... &lt;code&gt;https://@(AzureBlobStorageAccountName).blob.core.windows.net/@(AzureBlobStorageContainer_Users)/@("user_review_img_" + review.UserName.Replace(" ", "_") + ".jpg")"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I used the &lt;a href="https://swiperjs.com/get-started/"&gt;Swiper.js get started guide&lt;/a&gt; to craft the carousel.&lt;/p&gt;

&lt;p&gt;The main html structure is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- Slider main container --&amp;gt;
&amp;lt;div class="swiper-container"&amp;gt;
    &amp;lt;!-- Additional required wrapper --&amp;gt;
    &amp;lt;div class="swiper-wrapper"&amp;gt;
        &amp;lt;!-- Slides --&amp;gt;
        &amp;lt;div class="swiper-slide"&amp;gt;Slide 1&amp;lt;/div&amp;gt;
        &amp;lt;div class="swiper-slide"&amp;gt;Slide 2&amp;lt;/div&amp;gt;
        &amp;lt;div class="swiper-slide"&amp;gt;Slide 3&amp;lt;/div&amp;gt;
        ...
    &amp;lt;/div&amp;gt;

    &amp;lt;!-- If we need navigation buttons --&amp;gt;
    &amp;lt;div class="swiper-button-prev"&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class="swiper-button-next"&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;... and Swiper handles a lot of the styling for us with these classes.&lt;/p&gt;

&lt;p&gt;I loop through the reviews and render carousel slides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@foreach (var review in Model)
{
    &amp;lt;div class="swiper-slide"&amp;gt;
    . . . 
    &amp;lt;/div&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Within the carousel I can display the review data using &lt;code&gt;@review.UserName&lt;/code&gt;, &lt;code&gt;@review.Source&lt;/code&gt;, and &lt;code&gt;@review.ReviewText&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Lastly, there's a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag to initialize the carousel after the page is done loading...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
    $(document).ready(function(){
        var mySwiper = new Swiper ('.image-slide .swiper-container', {
            direction: 'horizontal',
            loop: true,
            slidesPerView: 1,
            autoplay: {
                delay: 3000,
            },
        });
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I specify &lt;code&gt;.image-slide .swiper-container&lt;/code&gt; as the selector to make sure it doesn't conflict with other &lt;code&gt;.swiper-container&lt;/code&gt;s on the page.&lt;/p&gt;

&lt;p&gt;After some styling with some heavy use of &lt;a href="https://www.w3schools.com/css/css3_flexbox.asp"&gt;CSS Flexbox&lt;/a&gt;, I think the result turned out simple and effective.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aEqzotxw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/xs583twv76a7ird7c8d4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aEqzotxw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/xs583twv76a7ird7c8d4.gif" alt="Carousel Reviews Swiping"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>csharp</category>
      <category>webdev</category>
      <category>sql</category>
    </item>
    <item>
      <title>Setting up an ASP.NET Core Restaurant Website with a Vue.js Online Ordering Page.</title>
      <dc:creator>Shane Kunz</dc:creator>
      <pubDate>Sun, 26 Jan 2020 12:30:55 +0000</pubDate>
      <link>https://dev.to/shanemlk/setting-up-an-asp-net-core-restaurant-website-with-a-vue-js-online-ordering-page-5hba</link>
      <guid>https://dev.to/shanemlk/setting-up-an-asp-net-core-restaurant-website-with-a-vue-js-online-ordering-page-5hba</guid>
      <description>&lt;p&gt;I went ahead and installed &lt;a href="https://dotnet.microsoft.com/download"&gt;.NET Core&lt;/a&gt; 2.2. I used a starter pizza related bootstrap 4 theme from &lt;a href="https://colorlib.com/"&gt;colorlib&lt;/a&gt; for base &lt;a href="https://www.w3schools.com/css/"&gt;CSS&lt;/a&gt;, with the only license requirement being a link to &lt;a href="https://colorlib.com/"&gt;colorlib&lt;/a&gt; in the footer.&lt;/p&gt;

&lt;p&gt;Once I had customized the pages and stylings, I started to build a &lt;a href="https://vuejs.org/"&gt;Vue.js&lt;/a&gt; powered online ordering page. I used &lt;a href="https://docs.microsoft.com/en-us/ef/core/get-started/?tabs=netcore-cli"&gt;entity framework&lt;/a&gt; to build a database schema in an &lt;a href="https://www.microsoft.com/en-us/sql-server/sql-server-2019"&gt;MS SQL&lt;/a&gt; &lt;a href="https://azure.microsoft.com/en-us/"&gt;Azure&lt;/a&gt; database.&lt;/p&gt;

&lt;p&gt;I downloaded the &lt;a href="https://vuejs.org/"&gt;Vue.js&lt;/a&gt; library file and put it in the &lt;code&gt;/wwwroot/js&lt;/code&gt; which I include in the &lt;code&gt;/Views/Order/index.cshtml&lt;/code&gt; file at the bottom as:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script  src="~/js/vue.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script  src="~/js/orderpage.js"&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;Views/Order/index.cshtml&lt;/code&gt; is the root &lt;a href="https://vuejs.org/v2/guide/syntax.html"&gt;Vue.js Template&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I initialize the root &lt;a href="https://vuejs.org/"&gt;Vue.js&lt;/a&gt; instance in the &lt;code&gt;/wwwroot/js/orderpage.js&lt;/code&gt; file with a function call similar to this:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var  app  =  new  Vue({
    el:  '#app'
)};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the &lt;a href="https://vuejs.org/v2/guide/index.html"&gt;Vue.js initialization&lt;/a&gt; function call above, I set the &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;computed&lt;/code&gt;, &lt;code&gt;filters&lt;/code&gt;, &lt;code&gt;methods&lt;/code&gt;, and a &lt;code&gt;mounted&lt;/code&gt; life-cycle hook.&lt;/p&gt;

&lt;p&gt;This is how the order page Vue.js app looks with the styling and after the Vue initialization has finished:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uisvg7-i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/d5glzzt27yoyu99vnfk0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uisvg7-i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/d5glzzt27yoyu99vnfk0.jpg" alt="Image of the front-end ordering page with categories, items in the selected category, and cart information shown"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I include &lt;br&gt;
&lt;code&gt;&amp;lt;script  src="https://js.stripe.com/v3/"&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;br&gt;
in the &lt;code&gt;/Views/Shared/_Layout.cshtml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On the second step, checkout, if a user has selected credit as payment, a stripe credit card input appears because of the inclusion of following logic at the bottom of &lt;code&gt;/wwwroot/js/orderpage.js&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$(document).ready(function () {
    stripe  =  Stripe($('#StripeApiKey').val());
    var  elements  =  stripe.elements();
    card  =  elements.create('card');
    card.mount('#card-element');
    card.addEventListener('change', function (event) {
        var  displayError  =  document.getElementById('card-errors');
        if (event.error) {
            displayError.textContent  =  event.error.message;
        } else {
            displayError.textContent  =  '';
        }
    });
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The checkout step looks like this:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6IMzMq5H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/k1u9y7rkfbqai82yqogo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6IMzMq5H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/k1u9y7rkfbqai82yqogo.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Controllers/OrderController.cs&lt;/code&gt; houses all of the Ecommerce logic and APIs. I should probably split it into smaller files, but I haven't felt the need to yet.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;public  IActionResult PlaceOrder(OrderParam  order, bool  pos  =  false)&lt;/code&gt; method is the entry point to placing an order. This method calls a lot of private methods, because if all the code was in one method, it would be too long. It does data validation based on certain options the customer selected. If the customer selected delivery, it will ask &lt;a href="https://developers.google.com/maps/documentation"&gt;Google Map's API&lt;/a&gt; if the distance between the customer and the restaurant is less than a certain amount of miles.&lt;/p&gt;

&lt;p&gt;The method &lt;code&gt;private  bool  ValidateOrderPrices(OrderParam  order, bool  isPOS)&lt;/code&gt; validates the user's supplied cart prices to see if they match the prices in the database. &lt;code&gt;isPOS&lt;/code&gt; tells the method if it should expect a delivery fee.&lt;/p&gt;

&lt;p&gt;I charge the customer via &lt;a href="https://stripe.com/"&gt;Stripe&lt;/a&gt; if the customer selected the credit payment type and they are on the customer facing website online ordering system (&lt;a href="http://stunodspizza.com/order"&gt;http://stunodspizza.com/order&lt;/a&gt;) &lt;/p&gt;

&lt;p&gt;The following code creates a &lt;a href="https://stripe.com/"&gt;Stripe&lt;/a&gt; charge:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var  options  =  new  ChargeCreateOptions
{
    Amount  =  Convert.ToInt32(order.total*100),
    Currency  =  "usd",
    Description  =  "Stunod's Charge for "+order.email,
    Source  =  order.stripeToken,
    ReceiptEmail  =  order.email,
    Metadata  =  metadata
};
var  service  =  new  ChargeService();
try{
    Charge  charge  =  service.Create(options);
}catch(Exception  exception){
    return  Json(new{Success  =  false, Message="Unable to process credit/debit card."});
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If the user did not select credit as a payment method, or the &lt;a href="https://stripe.com/"&gt;Stripe&lt;/a&gt; payment was successful, the order is saved to the database using the method &lt;code&gt;private  int  SaveOrderToDatabase(OrderParam  order, bool  placedOnPOS)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;PlaceOrder&lt;/code&gt; method also generates emails by using replace string methods on text files for dynamic data. If the business has emails listed in the &lt;code&gt;/appSettings.json&lt;/code&gt;, they will receive receipt emails. The customer will also receive an email receipt if a customer email was supplied in the order.&lt;/p&gt;

&lt;p&gt;Text messages are also sent to business and/or customers via a simple &lt;a href="https://www.twilio.com/"&gt;Twilio API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Within the &lt;code&gt;PlaceOrder&lt;/code&gt; method, a PDF is generated with certain thin width dimensions using the library &lt;a href="https://www.nuget.org/packages/PdfSharpCore/"&gt;PdfSharp&lt;/a&gt;. This PDF gets sent via &lt;code&gt;HTTPS&lt;/code&gt; to a &lt;a href="https://www.python.org/"&gt;python&lt;/a&gt; &lt;a href="https://flask.palletsprojects.com/en/1.1.x/"&gt;development flask server&lt;/a&gt;. The server gets booted by the client by double clicking a &lt;code&gt;.sh&lt;/code&gt; file on a &lt;a href="https://www.raspberrypi.org/"&gt;raspberry pi&lt;/a&gt; desktop. The &lt;code&gt;.sh&lt;/code&gt; script also runs a &lt;a href="https://www.noip.com/"&gt;noip&lt;/a&gt; library for use as a DDNS service. This allows the web app on &lt;a href="https://azure.microsoft.com/en-us/"&gt;Azure&lt;/a&gt; to send the PDF receipt to a URL that stays aware of the printer's router's IP address. The &lt;a href="https://www.python.org/"&gt;python&lt;/a&gt; &lt;a href="https://flask.palletsprojects.com/en/1.1.x/"&gt;development flask server&lt;/a&gt; receives, downloads, and prints the PDF receipt using CUPS and a &lt;a href="https://github.com/klirichek/zj-58"&gt;nice print driver&lt;/a&gt; for the restaurant's &lt;code&gt;TM-T20II POS Thermal Receipt Printer&lt;/code&gt; connected to the &lt;code&gt;raspberry pi&lt;/code&gt; via USB. If you're interested in the printer setup, please check out the &lt;code&gt;raspberry-pi/README.md&lt;/code&gt; when I release the source code at the end of this &lt;a href="https://dev.to/"&gt;dev.to&lt;/a&gt; series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming up next: a Vue.js CLI App walkthrough and tutorial
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;I walk you through building a web app that's a full point of sale, a&lt;br&gt;
order history management app. a sales reporting app, and a Ecommerce CMS with&lt;br&gt;
functionality for editing hours and menu items. The whole projects is in&lt;br&gt;
a nested folder setup with the &lt;a href="https://cli.vuejs.org/guide/"&gt;Vue&lt;br&gt;
CLI&lt;/a&gt;. When we run &lt;code&gt;yarn build&lt;/code&gt; it will&lt;br&gt;
place our Vue app in the &lt;code&gt;/Views/POS/index.cshtml&lt;/code&gt; with its assets in&lt;br&gt;
the &lt;code&gt;/wwwroot/js&lt;/code&gt; and &lt;code&gt;/wwwroot/css&lt;/code&gt; folders. Both our&lt;br&gt;
development and production builds will be able to hit API functions in&lt;br&gt;
the &lt;code&gt;/Controllers/OrderController.cs&lt;/code&gt; by use of the&lt;br&gt;
&lt;code&gt;/pizza-pos/vue.config.js&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Note:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  I'll be releasing the open source code at the end of the series.
&lt;/h3&gt;

</description>
      <category>vue</category>
      <category>csharp</category>
      <category>webdev</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
