Showing posts with label .NET Core; Cross-platform. Show all posts
Showing posts with label .NET Core; Cross-platform. Show all posts

Wednesday, April 24, 2019

Create and Host a Cross-Platform Web Site on ASP.NET Core

This is the second post in a series on .NET Cross-Platform Support. In my first post, I covered how to create, build, and deploy a console command in .NET Core for Windows, Linux, and MacOS. In today's post, I'll cover how to create a web site with ASP.NET Core and deploy it on both Windows and Linux.

If you're going to follow along, you'll need to install the .NET Core SDK. I'm doing my development with .NET Core 2.2 and Visual Studio Code on Windows, but you could just as easily do the same on Linux or MacOS.

The web site we'll create is a simple one-page site that performs conversions between different units of measurement, such as from miles to kilometers.

Development


Creating an ASP.NET MVC Project

We'll use the dotnet new command to create a starter project. I want to use MVC, so we'll request the MVC template.We'll specify the folder and project name convert.

dotnet new mvc -o convert











This creates a folder named convert. Within that folder are our project files along with subfolders.













Coding the Site

Fire up Visual Studio Code and use Open Folder to open the convert folder that was just created.

If you're new to ASP.NET Core, let's take a moment to familiarize ourselves with the project that has been created. Keep in mind this MVC project is only one of many ASP.NET Core project tempaltes
you can use.

Project Structure

In the convert folder, there is Program.cs and Startup.cs. Program.cs contains function Main, which is where execution begins. It creates a WebHost, and references the Startup class.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace convert
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();

Startup.cs contains the initialization code for the WebHost. This refactoring of ASP.NET is extremely modular and extensible, and includes a built-in dependency injection system.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace convert
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });


            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

Since we're just building a simple demonstration web site, we dont need to make any changes to the code in Program.cs or Startup.cs.

Within the convert folder, we have Controllers, Models, and Views subfolders. These are all conceptually familiar to anyone familiar with the Model-View-Controller pattern. Controllers contains C# controller classes; Models contains C# model classes; and Views contain Razor pages (which are HTML with embedded C# code and Razor directives).

There's also a wwwroot folder, which contains static css, image, and JavaScript files.

Project Starting Point

We can build and run the project to see what we have been given out-of-the-box. In Visual Studio Code, open a new terminal window with Terminal > New Terminal. Run dotnew build to build the prjoect.

In traditional ASP.NET, launching the site locally from Visual Studio would use IIS or IIS Express to host the web site. In ASP.NET Core, all we need is the dotnet run command. Enter dotnet run in the terminal window to host a web server. Click the displayed HTTP or HTTPS link to open the site in a browser. We can see we get a simple Welcome page.








Code the HTML Page

We need to change the HTML in the default page, /Views/Home/index.cshml. We add the following code to the page:
@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Unit Converter</h1>
    <p>Use this page to convert between common units of measurement</a>.</p>
</div>

<div> </div>

<div>
    <input id="source-value" type="number" value="10" style="font-size: 14px" />
        
    <select id="source-unit">
        <option value="">-- select source units --</option>
        <option value="ft" selected>Feet (ft)</option>
        <option value="km" selected>Kilometers (km)</option>
        <option value="m">Meters (m)</option>
        <option value="mi">Miles (mi)</option>
    </select> 
        
    <button id="btn-convert" onclick="convert();">Convert</button> 
        
    <input id="dest-value" type="number" value="" style="font-size: 14px" readonly=readonly />
        
    <select id="dest-unit">
        <option value="">-- select destination units --</option>
        <option value="ft" selected>Feet (ft)</option>
        <option value="km">Kilometers (km)</option>
        <option value="m">Meters (m)</option>
        <option value="mi" selected>Miles (mi)</option>
    </select>
</div>


<script>
function convert() {
    var value = $('#source-value').val();
    var sourceUnits = $('#source-unit').val();
    var destUnits = $('#dest-unit').val();

    $('#dest-value').val('');

    if (sourceUnits==='' || destUnits==='') return;

    if (sourceUnits===destUnits)
    {
        $('#dest-value').val(value);
        return;
    }

    console.log(value);
    console.log(sourceUnits);
    console.log(destUnits);

    var url = '/home/Convert?value=' + value + '&source=' + sourceUnits + '&dest=' + destUnits;

    $.ajax({
        url: url,
        method: 'GET',
    }).done(function(data) {
        $('#dest-value').val(data);
        //$( this ).addClass( "done" );
        console.log(data);
    });
}

</script>
We can now build the site with dotnet build and run it with dotnet run so that we can test it.

dotnet build
dotnet run

Now we can enter an amount and select source and destination units. Clicking Convert converts the value.










Our site, while simple, could easily be expanded to support many more conversions over time. For our purposes of testing cross-platform compatibility, this is sufficient and we can move on to seeing things work on Linux.

Publishing to Linux

We can generate output for linux with the dotnet publish command, specifying the distribution we wish to target. We'll use Ubuntu 18.04, 64-bit (because that's an AMI available for AWS EC2).

dotnet publish -c Release --self-contained -r ubuntu.18.04-x64

To deploy, we'll need to allocate a Linux web server, for which we'll use NGINX. We'll then deploy our convert site, and finally configure NGINX to serve as a reverse proxy to our ASP.NET Core site.

1. Create EC2 instance

We first create an Ubuntu EC2 instance on AWS. We save the .pem (key) file created with the instance, which will be needed anytime we want to connect to the instance with SSH.

Configure an inbound rule allowing port 80 in the security group for the EC2 instance.

As we allocate the EC2 instance, we save the .pem (key) file, which we'll need for subsequent steps.










2. Connect with SSH

Once the instance is up and running, we connect to it with SSH, specifying our instance URL and the .pem (key) file we created with the instance:

ssh -i "dp-dev.pem" ubuntu@ec2-my-ip.us-west-2.compute.amazonaws.com

3. Set up web server

To set up a web server, we use the sudo ("SuperUser do") command to allow port 80 on the firewall.

sudo ufw allow 80/tcp

Next, we install nginx, a popular web server:

sudo apt-get install nginx
sudo service nginx start

Once installation completes, we are able to confirm that we can access the EC2 instance as a web server:

















4. Deploy the .NET Core application

Via SSH, create a convert file under /home/ubuntu. Then use a tool such as WinSCP to copy the files from the Ubuntu publish folder to /home/ubuntu/convert:















5. Configure nginx to be a Reverse Proxy for Convert.dll

Following the Microsoft instructions here, we do the following to set up NGINX as a reverse proxy for the NET Core convert site. This simply means NGINX will forward requests to our application.

a. Stop the nginx site

sudo service nginx start

b. Change directory to /etc/nginx/sites-available and replace the file default with the content below (I used Notepad++ and WinSCP for this):

cd /etc/nginx/sites-available

server {
    listen        80;
    server_name   example.com *.example.com;
    location / {
        proxy_pass         http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }
}

If you run into permission issues, try uploading your new default file to a path you have write access to, such as where you copied the convert application publish files to. Then, in cd /etc/nginx/sites-available, you can use sudo cp to copy the file:

cd /etc/nginx/sites-available
sudo cp /home/ubunto/convert/default .

c. Start the nginx service

sudo service nginx start

6. Start the Convert Site

From /home/ubuntu/convert, use the dotnet command to run convert.dll to start the Convert site

cd /home/ubuntu/convert dotnet convert.dll

7. Try to access the site in a browser:


















Our ASP.NET Core Convert site is now running on Ubuntu Linux, with NGINX serving as a reverse proxy. If you've never worked with Linux as a web server before, getting to this step with a .NET web site is a great moment.


Conclusion

In this post I've shared what my first-time experience publishing an ASP.NET Core web site to Linux. There were plenty of unknowns working with an operating system I had little experience with, but with a little persistence I got there. .NET Core is well done, and ASP.NET Core is no exception. It's very empowering to realize how broader your .NET Skills can now reach with .NET Core.


Monday, April 15, 2019

Create a Cross-Platform Console Command in .NET Core

In this post, I'm going to show how you can effortlessly create a console command in .NET Core that runs on Windows, Linux, and MacOS. I'll do this step-by-step in detail with the assumption you may be new to .NET Core.

To those diehard .NET developers who have been resisting .NET Core, let me give you some words of encouragement. You've no doubt been hearing more and more about .NET Core, and perhaps you haven't liked everything you've heard ("Config files are no longer XML?"). At this point, there's no denying that .NET Core has momentum: companies all over are making the switch. If you haven't learned .NET Core yet, you probably should: it's where Microsoft is now investing their development platform efforts, and the world is moving to it. On the plus side, there are some exciting new capabilities such as the cross-platform support we'll be focusing on in this post.

To illustrate a cross-platform .NET Core console command, we'll create an updated version of a command I wrote about last year named release. The purpose of the release command is to create and verify software release manifests in the form of directory files with hashes. Two files with the same content will have the same hash; even a one-character difference between two files will cause them to have wildly different hashes. The release command supports these actions:

release : displays help
release hash file : displays a hash code for a single file
release create manifest-file : creates a manifest file listing files and hash codes
release verify manifest-file : reads the manifest file and compares against the file system

Prerequisites

Our first step is to download and install the .NET Core SDK. At the time I'm writing this, the latest stable release of .NET Core is 2.2, so that's what we're using here.

While optional, I'll also suggest you download Visual Studio Code. You can in fact use any editor / IDE you prefer, including traditional Visual Studio, but if your interest in .NET Core is cross-platform support then it makes sense to also use Visual Studio Code. With it and the .NET Core SDK, not only can you target non-Windows platforms; you can also do the development and building itself on any of those platforms.

Creating a Console Project with the Dotnet Command

To create our starter console project, we use the dotnet command. In .NET Core, you can do lots of things from the command line, including creating new projects from templates; building them; and running them. On Windows, open a command prompt as an Administrator and set your directory to a folder where you want to be doing .NET Core development.

To create our console project, use the command dotnew new console -o project-name:

dotnet new console -o release













This generates a small number of files in a release folder. Program.cs is our started code, and release.csproj is our project file.

Create the Project Code

Open the Project in Visual Studio Code

Next, launch Visual Studio Code and open the folder we just created. If you're new to Visual Studio Code, you'll probably expext to be opening .csproj and .sln files, but it doesn't work that way in Visual Studio Code. Just open the release folder you created in the prior step, and trust that we'll be doing our building and running with the dotnet command.

Write the Console Program Code

If you examine the generated Program.cs, you'll see it just outputs Hello World! to the console. We need to replace this with our desired code. In my case, we'll paste in the Program.cs source code from my original .NET Framework implementation.

Our Program.cs in Visual Studio Code now looks like this:















We don't really need to do anything else: the original release command consisted of just Program.cs and didn't have any application configuration. We may need to make some changes later on when we test on other operating systems, but for now we can proceed as we are on Windows.

Build and Test the Project

Build the Project

Now we can build the project, for which we'll again be using the dotnet command. You can issue the build command from the command prompt as we did earlier, but for those new to Visual Studio Code you also have the option of using the integrated Terminal window in the editor. Just select Terminal > New Terminal from the menu, and you have a console window below your code.

dotnet build 

Execute the dotnet build command.















If you examine the release folder, you'll see there is a bin folder that contains a Debug folder that contains a netcoreapp2.2 folder. In /bin/Debug/netcoreapp2.2 we see a release.dll (not a .exe as you might expect), a release.pdb, and several json files.

The reason we don't see an .exe is that by default a .dll is generated, which you can execute using the dotnet command. Don't be worried: t is possible to generate an .exe, and we'll do so shortly.

Run the Project

Now that we have built our program we can try running it, using the dotnet run command. Once again you can issue this from a command prompt or a Visual Studio Code Terminal window.

dotnet run

Our command appears to run.  With no command line parameters, all release does is display help. We haven't done any real work yet but it's encouraging that our .NET Framework code has not needed any changes to work in .NET Core.

Now we can try the various actions available in the release command to test that they work.

We again use the dotnet run command to execute our command, but we can pass the same parameters we would give to release.exe.

dotnet run hash file

The hash file parameters tell the release command to display the hash code for the specified file. This works fine.

dotnet run create 0001.dir

Next we'll try the create manifest-file edition of the release command, which creates a release manifest file containing filenames and hash codes.

This again works as expected, generating the following ouput in manifest file 0001.dir:

dotnet verify 0001.dir

The last version of the release command we'll test is verify manifest-file, which reads a manifest and checks its hash codes against what it sees in the file systems, reporting any differences or missing files.

Once again, the command is working great. 

Publishing to Windows

At this point, we've rather effortlessly imported our original .NET Framework code for the release command into .NET Core via Visual Studio Code, built it, and tested it. 

We would now like to generate an actual .exe so that we can run this command on any Windows system without needing to use the dotnet run command. To do this, we use the dotnet publish command, specifying  parameters targeting a particular environment—in this case, 64-bit Windows 10:

dotnet publish -r win10-x64

If we look in our /bin/Debug/netcoreapp2.2 folder, there is now a win10-x64 subfolder which contains our release.exe. If we copy the files in this folder (release.exe, all .dlls, all .json files) to c:\Windows\System32 or better yet to a folder contained in our PATH, we can now run the release command the way it was intended to be used from any Windows command prompt:

What we just did works, but involves a larger number of files than we would like. We can reduce the number of output files by creating a Self-Contained Deployment. We can generate an SCD by specifying the -c Release option to dotnet publish. This generates a smaller number of files in a Release/Win10-x64 folder.

dotnet publish -c Release -r win10-x64

Now that we've got our .NET Core console program working in Windows, it's time to publish it to additional platforms.

Publishing to Linux

With .NET Core, we can stay right where we are on our Windows machine and generate a Linux release.

To create a Linux executable, we will again use the  dotnet publish command just as we did a moment ago, but with different parameters. The -r parameter targets the particular Linux distribution and version we have in mind. In this case, I'll target Ubuntu 18 64-bit because that's available for the AWS free tier:

dotnet publish -c Release --self-contained -r ubuntu.18.04-x64

This produces a set of files under bin/Release/netcoreapp2.2\ubuntu.18.04-x64

We can't test this without a Linux instance. I did the following to create an Ubuntu Linux instance on AWS and install .NET Core and my application:

1. Allocate a Linux instance on AWS. In my case, running Ubuntu 18.04,64-bit. Wait for it to initialize.

2. Connect to the instance from the AWS console via SSH. In my case, I used the PuTTY utility for Windows. Note that connecting from the AWS Console via PuTTY on Windows did not work until I made some permission changes on my .pem key file as per the instructions here.

3. On the Linux instance, in a browser, install the Dotnet Core Runtime. I did this by following the instructions here. Once completed verify the dotnet command works. 


4. Copy the files in the publish folder (/bin/Release/netcoreapp2.2/ubuntu.18.04-x64/publish) generated by the dotnet publish command up to the Linux instance. If you're a Linux newbie, you might struggle mightily with transfer commands like SCP and PSCP (I certainly have). I like to use the WinSCP tool for file transfers, as described here.

5. On the Linux machine (via SSH or PuTTY), from the folder containing the publish files, you can now run the command with dotnet release.dll. At first glance, things seem to be working well—but we need to be exhaustive in our testing.

dotnet release.dll

dotnet release.dll hash mscorlib.dll

dotnet release.dll create 0001.dir

dotnet release.dll verify 001.dir

Here we ran into trouble: the verify operation thought every file in the manifest directory file did not match the local file system. A quick inspection of the code revealed why: it was written for Windows, and presumes that backslashes are used to separate folders in paths.

Well, that's easy enough to correct. We modified our code to support both backslash and forward-slash paths. Here's an example of one of the areas of code that had to be augmented:

With the above change in place, we again test—on both Windows as well as Linux—to confirm the release command is now fully working. It is! Below is a a run of the verify operation on Ubuntu Linux after posting an updated release where a few of the files have changed. The release command is working just as it supposed to, confirming 193 files checked and 2 files different.

Our release command now works on Linux!

Publishing to MacOS

We can publish to MacOS by specifying an OSX release. To deploy, I'll be borrowing my wife's Macbook Air which is running OSX 10.14; the publish command we need to use therefore is:

dotnet publish -c Release -r osx.10.14-x64

The steps to deploy our application on MacOS are:

1. Run the above dotnet publish command (adjusted for the version of MacOS you are targeting) to generate a publish folder for MacOS.

2. On the Mac, in a browser, visit http://dotnet.microsoft.com/download and download the .NET Core Runtime for MacOS. Open the download to install. Test it works by opening a Terminal window and entering dotnet.

3. Transfer your application files to the Mac. First copy the application files from your Windows PC OSX publish folder (in /bin/Release/netcoreapp2.2/osx.10.14-x64/publish) to a thumbdrive. Then plug the thumbdrive into the Mac. Create a new folder under Applications and copy the publish files there. For my application that folder is Applications/Release.

























4. In a Terminal window, CD to the folder from Step 3 where the files were placed. Run the release command with dotnet release.dll followed by any command line arguments.

dotnet release.dll

The dotnet release.dll command (with no arguments) simply displays help, and tells us our program is intact and able to execute on MacOS:


















dotnet release.dll create 0001.dir

The create manifest-file operation hashes files in the current working directory and any subfolders and created a manifest directory file with has codes.


















cat 0001dir

Here's the manifest file that was created:















dotnet release.dll verify 0001.dir

Finally, here's the verify manifest-file operation, which reads the manifest file and verifies it against the file system. 

Our release command now works on MacOS!


Post-Mortem: Cross-Platform Support for .NET Core

In this post, we've shown how .NET Core code can generate output that runs not only on Windows but also on Linux and MacOS. We ported a .NET Framework console program to .NET Core and proved it could run on all three operating systems.

Our initial development was on Windows, but needn't have been. Everything that was done on Windows in this article could just as easily been done on Linux or MacOS using the .NET Core SDK and Visual Studio Code.

We demonstrated Linux support by targeting Ubuntu Linux with the dotnet publish command. On AWS, I instantiated a Linux server. On the server we ran some terminal commands to install .NET Core Runtme, then copied our files from the Ubuntu publish folder to a folder on the Linux box. Adminittedly, this took several hours: I struggled with setting up ssh and trying to copy files with scp.  There were lots of little gotchas, such as having to change permissions on the AWS .pem (key) file. Ultimately, the PuTTY and winscp tools made things easier for this longtime Windows developer. With the Linux-basics learning curve behind me, I'm confident my next time around deploying .NET Core apps to Linux will go much more rapidly.

As the console program we ported was a release utility that accesses the file system, we did need to make some changes—but only because the original code presumed Windows file pathing with backslashes. The modified code is far more useful because it can work across multiple operating systems. It only took about an hour to modify the code and test that the program still worked correctly on each OS.

To get up and running on the Mac, things went quite quickly. Rather than connecting remotly to an AWS VM, I had the Mac in front of me. I used a browser to download .NET Core Runtime, and a thumbdrive to transfer files using the Finder. In a Terminal window, testing the program on MacOS went smoothly and everything worked the very first time I tried it.

Conclusion

.NET has certainly come a long way from its debut in 2000. .NET Core does introduce some changes, not all of them pleasant, but the changes are there for good reasons. If you've been dragging your feet on .NET Core, I urge rethinking your position. .NET Core adoption is rampant. Microsoft is producing platforms like .NET Core and tools like Visual Studio Code in order to win over developers regardless of their preferred operating system, and it is working because they are very well done.

Being able to use .NET Core and C# for development yet target other operating systems is huge. Not only are many companies using .NET Core, many of them are using for non-Windows deployments: a clear sign of that, if you peruse job postings, is how many companies are advertising for .NET Core skills targeting deployment to Linux.