ASP.NET Core

Deploying an ASP.NET Core Application to Amazon Web Services

This is the second post in a series on deploying a very simple ASP.NET Core application to the major cloud provides. This post is going to be dealing with setup and deployment to Amazon Web Services (AWS).

The following is are the other posts in this series.

Google Cloud Platform
Amazon Web Services (this post)
Microsoft Azure

Sample Application

This is the same as the first post in the series, but am including it in case you missed that post. The sample application that we will be deploying is the basic Razor Pages applications created using the .NET CLI. The following commands are what I used to create the application, create a solution, and add the project to the solution. I ran all these commands in a CloudSample directory.

dotnet new razor
dotnet new sln
dotnet sln add CloudSample.csproj

Amazon Web Services

The rest of this post is going to be based on a couple of pieces of documentation from Amazon for the Visual Studio Toolkit and Beanstalk with .NET Core. I will be walking through the whole process but wanted to make sure you had access to the same information this post is based on.

AWS Toolkit for Visual Studio

Amazon provides a Visual Studio extension to aid in interactions with their platform from Visual Studio. To install open the Tools > Extensions and Updates menu. Select Online from the left side of the dialog and then search for AWS and select AWS Toolkit for Visual Studio 2017.

Click download, close Visual Studio to trigger installation, and restart Visual Studio after the extension’s installation has completed. When you open Visual Studio back up you should see the AWS Getting Started page with has this link to the IAM Users page in the AWS Console. This will land you on the following page where you have to either sign into your account or sign up for a new account.

After you get through the sign-up/in process you will be at the IAM console where we are going to add a new user by clicking the Add user button.

On the next page fill out the User name you want to use and check the Programmatic access check box and then click the Next: Permissions button.

The next page is all about what permissions the user will have which is handled via groups. Since my account doesn’t have any groups I need to create one using the Create group button.

On the Create group form enter the Group name you want to use. Next, use the search box for Policy type to find the policies related to Elastic Beanstalk. I have selected the full access policy for this sample, but if it is more than just a test this would require more research to see if that is the proper policy to use or not. When done click the Create group button.

This will return you to the group page from above. Click the Next: Review button to continue. The next page is just a review of the options selected on previous pages. Click the Create user button to continue.

The last page has a button to Download .csv, click it and download the file. Back over in Visual Studio on the AWS Getting Started page click the Import from csv file.

Browse and select the csv download above. Then click the Save and Close button on the AWS Getting Started page. After that is complete you should see the AWS Explorer window.

Sign up for AWS Elastic Beanstalk

If you notice in the AWS Explorer screenshot above the Please sign up for this service under AWS Elastic Beanstalk. Double click the Please sign up for this service it will open a new browser window. On this window click the Get started with AWS Elastic Beanstalk button.

The next page will prompt for payment information. After entering your information click the Secure Submit button. The next step is an automated phone call to verify that you who you say you are. The final step in signing up is to select your plan.

Select the Free plan to finish your sign up.

Publishing from Visual Studio

Now that sign up is complete hope back in Visual Studio and open the solution you want to publish. In the Solution Explorer window right-click on the project and click Publish to AWS Elastic Beanstalk.

This will open the Publish to Amazon Web Services dialog. I just took the defaults since this is a new application and I don’t have any existing enviroments. Click the Next button to continue.

On the next screen, an Application Name and Environment Name are needed. Fill them in and click Next.

The next screen is where you can configure the type and size of the EC2 instance that will back your application. I used the defaults. As always click Next when ready.

The next screen allows you to configure permissions. You will never guess, but I used the defaults and clicked Next.

The next screen allows you to pick your project build configuration and framework to use. The defaults are set based your project so they should be right. Click the Finish button.

The final screen is to review all the options. If you are happy with everything click the Deploy button.

During deployment, I saw the following error in my output window.

Caught AmazonIdentityManagementServiceException whilst setting up role: User:myUser/CloudSampleBlog is not authorized to perform: iam:GetInstanceProfile on resource: instance profile aws-elasticbeanstalk-ec2-role
Caught Exception whilst setting up service role: User: myUser/CloudSampleBlog is not authorized to perform: iam:PutRolePolicy on resource: role aws-elasticbeanstalk-service-role

This turned out to not really be an issue. I went to my Elastic Beanstalk Dashboard and the application was there and in the process of deploying. The whole process took around 5 minutes. Unlike with Google, I did have to find the URL in the dashboard instead of a browser being opened to the URL automatically.

Wrapping Up

Initial setup with AWS is much more complex than with Google. Redeployment was in the two-minute range which is much faster than what I saw with Google. Overall it seems that Elastic Beanstalk is on par with Google’s App Engine.

Don’t forget to shut down and/or delete your test project when you are finished to make sure you don’t get charged.

Deploying an ASP.NET Core Application to Amazon Web Services Read More »

Deploying an ASP.NET Core Application to Google Cloud Platform

This is the first post in what is going to be a series on deploying a very simple ASP.NET Core application to the major cloud providers starting with Google’s cloud. This series was inspired by a question from one of my coworkers about the ease-of-use differences for .NET between the cloud providers.

The other posts in the series:
Google Cloud Platform (this post)
Amazon Web Services
Microsoft Azure

Sample Application

The sample application that we will be deploying is the basic Razor Pages applications created using the .NET CLI. The following commands are what I used to create the application, create a solution, and add the project to the solution. I ran all these commands in a CloudSample directory.

dotnet new razor
dotnet new sln
dotnet sln add CloudSample.csproj

Google Cloud Platform

The rest of this post is going to be based on information from Google which can be found here.

Google Cloud SDK Installation

The first step is to download and install the Google Cloud SDK which can be found here. I’m walking through these steps for Windows, but the previous link has instructions for Linux and Mac as well.

After downloading and run the installer, and be patient it takes a while to run. When it is done you should see the following screen.

Make sure and leave the bottom two boxes checked as gcloud init must be complete to get everything working as it should. Click the Finish button and a console will open and walk you through the initialization process.

The first step in the process will open a web browser and ask you to login to your Google account. After logging in you will be asked for a cloud project to use. It should look something like the following.

You are logged in as: [your account].

Pick cloud project to use:
 [1] api-project-276206909805
 [2] blogbackup-141901
 [3] oval-botany-94217
 [4] Create a new project
Please enter numeric choice or text value (must exactly match list
item):

We want to select Create a new project which is option 4 in my case. When prompted for a project ID I just hit enter and it let me out of the process so I don’t think a project is actually required at this point so that is a little confusing.

Google Cloud Tools for Visual Studio Installation

Google provides a Visual Studio extension that makes interacting with their platform simpler from within Visual Studio. To install open the Tools > Extensions and Updates menu. On the left select Online and then search for Google. Next, select Google Cloud Tools for Visual Studio.

Click download, close Visual Studio to trigger installation, and restart Visual Studio. Now that the extension is installed we need to connect it to an account. Using the Tools > Google Cloud Tools > Manage Accounts menu to launch the Manage Accounts Dialog. Next click Add Account.

This will launch a web browser where you will need to complete the account add. After the account auth completes Visual Studio should show the Google Cloud Explorer window logged in the account you selected.

Create an App Engine Project

Open App Engine and click Create.

On the next page, you will need to give the project a name and then click Create.

On the next page select .NET as your language.

And select the location you want your application hosted in and click next.

If you aren’t already signed up then the next step signs you up for a 12 month trial with $300 worth of credit. Click the Signu up for free trial button to continue.

On the next screen, for the trial, you have to select your county as well as agree to the terms.

The next screen is a summary of your account. Review the information and click the Start my free trial button to continue.

Deploy from Visual Studio

In the Google Cloud Explorer window in Visual Studio select the project you just created in the drop-down.

If you don’t see your new project try clicking the refresh button in the upper left-hand side of the window. Now that you have the correct project selected switch over to the Solution Explorer window and right click on your project and select Publish to Google Cloud.

On the next screen select App Engine Flex.

Then on the next screen click publish.

My first deployment took a really long time so don’t worry if you end up waiting a while (5 to 10 minutes). I tried a second deployment and it took just as long so I guess that is just how long it takes. When deployment finished it should open your site in a web browser.

Wrapping Up

After the initial set of installs and setup, Google Cloud with ASP.NET Core is pretty straightforward. The deployment time seems way too long, but that is really the only complaint I have.

Don’t forget to shut down and/or delete your test project when you are finished to make sure it doesn’t use up your credits.

Deploying an ASP.NET Core Application to Google Cloud Platform Read More »

Migration from ASP.NET Core 2.0 to 2.1

On May 30th .NET Core 2.1 was released including the corresponding version of ASP.NET Core 2.1 and Entity Framework Core 2.1. In this post, I will be taking one of the projects used in my ASP.NET Basics series and converting it from its current 2.0.x version into the newly released 2.1 version. The starting point for the code can be found here. This is all based on the official migration guide.

If you are looking at the sample solution it is the Contacts project that this post is going to be dealing with.

Installation

Thankfully the download page is much improved over the last time there was a release. Head here and to download and install the .NET Core 2.1 SDK. It is available for Windows, Linux, and Mac. The link should drop you on the appropriate page for your OS.

After installation, open a command prompt and run the following command. If all worked well you should see version 2.1.300 listed.

dotnet --list-sdks

If you are on Windows make sure and install at least Visual Studio 2017 15.7.

Project File Changes

Right-click on the project and select Edit {projectName}.csproj from the menu.

First, change the TargetFramework to netcoreappp2.1.

Before:
<TargetFramework>netcoreapp2.0</TargetFramework>

After:
<TargetFramework>netcoreapp2.1</TargetFramework>

The other required changes it to move away from the Microsoft.AspNetCore.All package to the version-less Microsoft.AspNetCore.App package.

Before:
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />

After:
<PackageReference Include="Microsoft.AspNetCore.App" />

I did a bit more digging and it turns out that the project file can be greatly simplified from the version I had for this application. The following is the full project file will all the bits that were not required removed and the two changes above already made.

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <UserSecretsId>Your-Secrests-ID</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.0" PrivateAssets="All" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
  </ItemGroup>

</Project>

Main Changes

There have been changes to how what the Main function looks like to better allow for integration tests. This is the original code in the Program.cs file.

public class Program
{
    public static void Main(string[] args)
    {
        BuildWebHost(args).Run();
    }

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();
}

And the following is the new version.

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 Changes

Startup.cs has one change that is required which is the removal of the following line from the Configure function.

app.UseBrowserLink();

In the ConfigureServices function if you want to use the new features in 2.1 change the services.AddMvc() to set the compatibility version. This allows you to upgrade the version of the SDK without having to change your whole application since you have to opt into the version you want to target.

Before:
services.AddMvc();

After:
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

If you check out the official migration guide they also point out how to enable a couple more features such as HTTPS and some things to help with GDPR. Neither of these is needed in this application so I’m skipping them in this guide.

Identity Changes

I had to make one change in ManageLogins.cshtml to get my project to build because of a rename/removal AuthenticationScheme to DisplayName.

Before:
<button type="submit" class="btn btn-default" name="provider" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName account">@provider.AuthenticationScheme</button>

After:
<button type="submit" class="btn btn-default" name="provider" value="@provider.DisplayName" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>

If you haven’t made many changes to the identity code in your project you might consider using the new identity razor class library. You can find the details here.

Wrapping Up

Migrations between versions of ASP.NET Core have gotten easier over time as you can tell by the smaller length of these posts. One thing to note is while this will get you targeting 2.1 with all the performance benefits and access to a lot of the new features there will still be work needed if you want to do everything the new 2.1 style. I highly recommend creating a new 2.1 application to get a feel for the other changes you might want to make to your existing applications.

The code with all the changes can be found here. Remember that the only project that was upgraded was the Contacts project.

Migration from ASP.NET Core 2.0 to 2.1 Read More »

Electron.NET: Save Dialog & File Writing

This post is another expansion of my Electron.NET sample to show how to prompt the user with a save dialog and write a file to disk. The sample code before any changes can be found here. As with all the posts I have done on Electron.NET the API Demos repo helped out a lot.

For this example, we will be adding an export button to the contact detail page that will export the contact as JSON.

Dialog Controller

Following how the API Demo is setup I added a DialogController with the following code.

public class DialogsController : Controller
{
    private static bool saveAdded;

    public IActionResult Index()
    {
        if (!HybridSupport.IsElectronActive || saveAdded) return Ok();

        Electron.IpcMain.On("save-dialog", async (args) =>
        {
            var mainWindow = Electron.WindowManager.BrowserWindows.First();
            var options = new SaveDialogOptions
            {
                Title = "Save contact as JSON",
                Filters = new FileFilter[]
                {
                    new FileFilter { Name = "JSON", 
                                     Extensions = new string[] {"json" } }
                }
            };

            var result = await 
                  Electron.Dialog.ShowSaveDialogAsync(mainWindow, options);
            Electron.IpcMain.Send(mainWindow, "save-dialog-reply", result);
        });

        saveAdded = true;

        return Ok();
    }
}

The setup above tells Electron when it receives a save-dialog request to show the operating system’s save dialog with the options specified. When the user completes the dialog interaction then it is set up so Electron will send out a save-dialog-reply message so anything listing can act on the user’s selection.

The bits with saveAdded is to work around an issue I was having with the dialog being shown multiple times. There is something off about my setup that I haven’t had time to track down, but I felt like even with this one querk this post is still valuable.

Next, I added the following import to the _Layout.cshtml file.

<link rel="import" href="Dialogs">

As I am writing this I am wondering if this could be the cause of my multiple dialog issues? Maybe this should just be on the contact detail page?

Contact Detail Page Changes

The rest of the changes are in the Views/Contacts/Details.cshtml. The first thing I did was add a new div and button at the bottom of the page. Based on the look of the existing page it isn’t the prettiest looking thing, but the look of the UI isn’t really the point of this post. Here is the code for the new div. Make note that the button has a specific ID.

<div>
    <button id="save-dialog" class="btn">Export</button>
</div>

Finally, the following script section was added.

<script>
    (function(){
        const { ipcRenderer } = require("electron");
        const fs = require('fs');
        var model = '@Html.Raw(Json.Serialize(@Model))';

        document.getElementById("save-dialog")
                .addEventListener("click", () => {
            ipcRenderer.send("save-dialog");
        });

        ipcRenderer.on("save-dialog-reply", (sender, path) => {
            if (!path) return;

            fs.writeFile(path, model, function (err) {
                console.log(err);
                return;
            });
        });
       
    }());
</script>

On the server side, the model is converted to JSON and stored which will be used when writing the file. If anyone has a better way of doing this part I would love to hear about it in the comments. I’m referring to this bit of code.

var model = '@Html.Raw(Json.Serialize(@Model))';

Next, a click event is added to the export button which when fired sends a message to show the save dialog defined in the controller.

document.getElementById("save-dialog")
        .addEventListener("click", () => {
                                     ipcRenderer.send("save-dialog");
                                   });

Finally, a callback is added for the message that the user has finished with the dialog that was shown.

ipcRenderer.on("save-dialog-reply", (sender, path) => {
    if (!path) return;

    fs.writeFile(path, model, function (err) {
        console.log(err);
        return;
    });
});

In the callback, if the user entered a path then the JSON for the model is written to the selected path.

Wrapping Up

While writing a contact to JSON might not be the most useful thing in the world the same idea could be used to with the information to a vCard file.

After working on this example I finally feel like I am getting a better hold on how Electron is working. Hopefully, this series is helping you feel the same. The completed code can be found here.

Electron.NET: Save Dialog & File Writing Read More »

Pass ASP.NET Core Appsettings Values to Angular via an API Call

There have been a few issues opened on the repo I have showing usage of Angular, Identity Server 4, and ASP.NET Core together that related to incompatibilities with the newer versions of Angular. In an effort to fix this issue the plan was to recreate the client application using the new Angular template from Microsoft which from what I read should address the issue.

The code before any changes can be found here, but in this case, the whole client application has been recreated so the starting point may not be super helpful.

The Problem

For the most part, this worked well, but the problem can when I needed to use some configuration values from ASP.NET Core in my new Angular application. The previous versions of the template used server-side rendering which I utilized to pass the configuration values. The new template doesn’t use server-side rendering by default and I wanted to find a way to solve the issue without requiring server-side rendering.

The other issue is that I want to be able to run this application in Azure and set the configuration values as environment variables. While Angular seems to have support for environment files finding a solution that used a systems environment variables turned out too not be simple.

Configuration API Endpoint

Since the configuration values I need to get to the client application are secret I decided to go the route of pulling them via an API call back to the same ASP.NET Core application that is hosting the Angular Application, which is the Client App project in the sample solution.

I added a ConfigurationController.cs class to the Controller directory with the following contents.

[Produces("application/json")]
[Route("api/Configuration")]
public class ConfigurationController : Controller
{
    private readonly IConfiguration _configuration;

    public ConfigurationController(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    [HttpGet("[action]")]
    public IActionResult ConfigurationData()
    {
        return Ok(new Dictionary<string, string>
        {
            { "IdentityServerAddress", _configuration["IdentityServerAddress"] },
            { "ApiAddress", _configuration["ApiAddress"] }
        });
    }
}

This controller gets constructed with a reference to the application’s configuration which is then used to populate a dictionary with the values my Angular application needs. For completeness, the following is the contents of the application’s appsettings.json file.

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "IdentityServerAddress": "http://localhost:5000",
  "ApiAddress": "http://localhost:5001/api/"
}

Angular Changes

This is the part that I really struggled to get right. I needed the configuration values from the API above to be available as soon as possible. Thankfully I came across this blog post by Juri Strumpflohner which covers using Angular’s APP_INITIALIZER.

The first thing I need was to create a class in Angular to get the configuration values from the API and serve to them the rest of the Angular application. To do this I added a configuration.service.ts into a new ClientApp/src/app/configuration directory. The full class follows.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class ConfigurationService {

  private configuration: IServerConfiguration;

  constructor(private http: HttpClient) { }

  loadConfig() {
    return this.http.get<IServerConfiguration>('/api/Configuration/ConfigurationData')
      .toPromise()
      .then(result => {
        this.configuration = <IServerConfiguration>(result);
      }, error => console.error(error));
  }

  get apiAddress() {
    return this.configuration.ApiAddress;
  }

  get identityServerAddress() {
    return this.configuration.IdentityServerAddress;
  }

}

export interface IServerConfiguration {
  ApiAddress: string;
  IdentityServerAddress: string;
}

This class hits the API to get the configuration values in the loadConfig function and maps it to a class level field. It also provides properties to get the individual configuration values.

As I mentioned above, getting the application to get these configuration values in a timely matter was something I really struggled to do. The first step to using Angular’s APP_INITIALIZER to solve this issue is to change the import from @angular/core to include APP_INITIALIZER and to import the ConfigurationService.  All these changes are being made in the app.module.ts file.

import { NgModule, APP_INITIALIZER } from '@angular/core';
import { ConfigurationService } from "./configuration/configuration.service";

Next, we need to define a function that will call the ConfigurationService.loadConfig function.

const appInitializerFn = (appConfig: ConfigurationService) => {
  return () => {
    return appConfig.loadConfig();
  };
};

Finally, in the providers array add an element for the APP_INITIALIZER and the ConfigurationService.

providers: [
  ConfigurationService,
  {
    provide: APP_INITIALIZER,
    useFactory: appInitializerFn,
    multi: true,
    deps: [ConfigurationService]
  }]

 Wrapping Up

This is one of those things that turned out to be way more complicated than I expected. Thankfully with the above changes, I was able to get it working. I hope this saves you all some time. The code with all the changes can be found here.

Pass ASP.NET Core Appsettings Values to Angular via an API Call Read More »

Electron.NET: Tray Icon

This post is a continuation of my exploration of Electron.NET which started with this post. Today I’m going to take the existing sample project and expand it to include a tray icon. As with the post on customizing the application level menus, this post relied heavily on the Electon.NET API Demos repo.

Add an Icon

The first step I took was to find an icon I wanted to show in the tray area. Since this is just a sample application I didn’t spend a lot of time on this. Once you have your icon it needs to be added to your project. Following the example, in the API Demo, I add an Assets directory to the top level of the project and copied in my Stock-Person.png file. This directory and file need to end up in the output of the builds which can be done by adding the following to the csproj file.

<ItemGroup>
  <None Update="Assets\Stock-Person.png">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
</ItemGroup>

In Visual Studio this can be done via the UI, but since I am sticking to VS Code for this project I did the edit manually.

Tray Controller

Add a TrayController to the Controllers directory which will be used to hold all the code needed to add the tray icon. The following is the full class.

public class TrayController : Controller
{
    public IActionResult Index()
    {
        if (!HybridSupport.IsElectronActive ||
            Electron.Tray.MenuItems.Count != 0)
        {
            return Ok();
        }

        var menu = new MenuItem[] {
            new MenuItem 
        { 
          Label = "Create Contact", 
          Click = () => Electron
                            .WindowManager
                        .BrowserWindows
                .First()
                .LoadURL($"http://localhost:{BridgeSettings.WebPort}/Contacts/Create")
        },
            new MenuItem 
        { 
          Label = "Remove", 
          Click = () => Electron.Tray.Destroy()
            }
        };

        Electron.Tray.Show("/Assets/Stock-Person.png", menu);
        Electron.Tray.SetToolTip("Contact Management");

        return Ok();
    }
}

Most of the code above is dealing with building an array of MenuItem which will be options when right-clicking the tray icon. In this case of this sample, there will be two menu items one for creating a contact and the other to remove the tray icon.

Electron.Tray.Show is the bit that actually shows the tray icon and it takes a path for the icon to display and the menu items to show. The last bit is a call to Electron.Tray.SetToolTip which, not surprisingly, sets the tooltip on the tray icon.

Include the tray icon

The final change is to make sure the code to show the tray icon gets run when the application starts. Open the _Layout.cshtml file in the Views/Shared directory. In the head tag add the following which will cause the application to call the Index action on the TrayController.

<link rel="import" href="Tray">

Wrapping Up

As with everything I have tried so far, Electon.NET makes it easy to add a tray icon to your applications. If you are a .NET developer so far I haven’t found any downsides to using Electron.NET. If you have hit any walls with this tool leave a comment. The finished code for this post can be found here.

Electron.NET: Tray Icon Read More »

Basic ASP.NET Core API Test with Postman

I had a reader email me about using Postman with ASP.NET Core API base on this post from a couple of years ago. Rather than working through that their specific issues are with that code, I thought it might be more helpful to write a post on creating a super basic ASP.NET Core API and use Postman to test it.

API Creation

We are going to use the .NET CLI to create and run API project so no Visual Studio or other IDE will be needed. The first step is to open a command prompt and navigate to (or create) the directory where you want the API project to live. Run the following command to create the API project.

dotnet new webapi

The webapi template creates a ValuesController with a Get action that returns an array with two values in it which we will be using as our test endpoint.

After the process finished we can now run the project using the following command.

dotnet run

After the run command, you should see something like the following.

Hosting environment: Production
Content root path: C:\YourProjectPath\ApiTest
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.

The key bit in the above you need to look for is the Now listening on line as that is the URL we will need to use in Postman to test.

Testing with Postman

Postman is a great tool that to use when developing an API. It allows me to exercise all the functions of the API before any clients have been built. You can do some of the same things using a browser, but Postman was built for this type of usage and it shows. Postman is free and you can grab it here.

Run Postman and you will see something similar to the following screenshot.

For our simple test we want to do a Get request, which is the default, so all we need to do is past the URL from above into the address box and add in the route to the controller we are trying to test. For our sample to test the Get action on the ValuesController our URL ends up being http://localhost:5000/api/values.

Click the Send button and the results will show the lower area Postman (the large red box in the screenshot).

Wrapping Up

This is the simplest setup I could think of to get up and going with Postman and ASP.NET Core. Postman has so many more functions than I showed in this post so I hope this will be a good jumping off point for you all to learn more about this great tool.

Basic ASP.NET Core API Test with Postman Read More »

Electron.NET: Custom Application Menus

This post will take the existing sample Electron.NET application used in Create a Desktop Application using ASP.NET Core and Electron.NET and Electron.NET with a Web API and expand it to customize the application menu. I leaned heavily on the Electron.NET API Demos repo to guide how this should be done. The code before any changes can be found here.

Menu Controller

While not a requirement I follow the API Demo example of putting the application level menus in its own controller. Add a MenusController to the Controllers directory. The following is the full class.

public class MenusController : Controller
{
    public IActionResult Index()
    {
        if (HybridSupport.IsElectronActive)
        {
            var menu = new MenuItem[] {
            new MenuItem { Label = "Edit", Submenu = new MenuItem[] {
                new MenuItem { Label = "Undo", Accelerator = "CmdOrCtrl+Z", Role = MenuRole.undo },
                new MenuItem { Label = "Redo", Accelerator = "Shift+CmdOrCtrl+Z", Role = MenuRole.redo },
                new MenuItem { Type = MenuType.separator },
                new MenuItem { Label = "Cut", Accelerator = "CmdOrCtrl+X", Role = MenuRole.cut },
                new MenuItem { Label = "Copy", Accelerator = "CmdOrCtrl+C", Role = MenuRole.copy },
                new MenuItem { Label = "Paste", Accelerator = "CmdOrCtrl+V", Role = MenuRole.paste },
                new MenuItem { Label = "Select All", Accelerator = "CmdOrCtrl+A", Role = MenuRole.selectall }
            }
            },
            new MenuItem { Label = "View", Submenu = new MenuItem[] {
                new MenuItem
                {
                    Label = "Reload",
                    Accelerator = "CmdOrCtrl+R",
                    Click = () =>
                    {
                        // on reload, start fresh and close any old
                        // open secondary windows
                        Electron.WindowManager.BrowserWindows.ToList().ForEach(browserWindow => {
                            if(browserWindow.Id != 1)
                            {
                                browserWindow.Close();
                            }
                            else
                            {
                                browserWindow.Reload();
                            }
                        });
                    }
                },
                new MenuItem
                {
                    Label = "Toggle Full Screen",
                    Accelerator = "CmdOrCtrl+F",
                    Click = async () =>
                    {
                        bool isFullScreen = await Electron.WindowManager.BrowserWindows.First().IsFullScreenAsync();
                        Electron.WindowManager.BrowserWindows.First().SetFullScreen(!isFullScreen);
                    }
                },
                new MenuItem
                {
                    Label = "Open Developer Tools",
                    Accelerator = "CmdOrCtrl+I",
                    Click = () => Electron.WindowManager.BrowserWindows.First().WebContents.OpenDevTools()
                },
                new MenuItem
                {
                    Type = MenuType.separator
                },
                new MenuItem
                {
                    Label = "App Menu Demo",
                    Click = async () => {
                        var options = new MessageBoxOptions("This demo is for the Menu section, showing how to create a clickable menu item in the application menu.");
                        options.Type = MessageBoxType.info;
                        options.Title = "Application Menu Demo";
                        await Electron.Dialog.ShowMessageBoxAsync(options);
                    }
                }
            }
            },
            new MenuItem { Label = "Window", Role = MenuRole.window, Submenu = new MenuItem[] {
                 new MenuItem { Label = "Minimize", Accelerator = "CmdOrCtrl+M", Role = MenuRole.minimize },
                 new MenuItem { Label = "Close", Accelerator = "CmdOrCtrl+W", Role = MenuRole.close }
                 }
            },
            new MenuItem { Label = "Contacts", Role = MenuRole.window, Submenu = new MenuItem[] {
                 new MenuItem { Label = "Create", 
                                Accelerator = "Shift+CmdOrCtrl+C",
                                Click = () => Electron.WindowManager.BrowserWindows.First().LoadURL($"http://localhost:{BridgeSettings.WebPort}/Contacts/Create")
                              }
                 }
            }
        };

            Electron.Menu.SetApplicationMenu(menu);
        }

        return Ok();
    }
}

What the above comes down to is building an array of MenuItem types and then using Electron.Menu.SetApplicationMenu(menu) to pass the array to Electron which handles replacing the default set of menus with the ones defined in the array.

For most of the items that were on the default set of menus all that is needed to add back the default functionality is to set the Role to the function you want. For example in the above for a Copy menu item, we can assign Role to MenuRole.copy and Electron will handle the implementation of a copy without us having to write any additional code.

Navigate to a page from the application menu

One thing I wanted to be able to do was from a menu create a new contact. It was easy enough to add a top-level menu for Contacts and a sub-item for Create. It took me a while, but I finally figured out how to build a URL that would work. The following code is the menu items for the Contacts menu.

new MenuItem { Label = "Contacts", Role = MenuRole.window, Submenu = new MenuItem[] {
     new MenuItem { Label = "Create", 
                    Accelerator = "Shift+CmdOrCtrl+C",
                    Click = () => Electron.WindowManager.BrowserWindows.First().LoadURL($"http://localhost:{BridgeSettings.WebPort}/Contacts/Create")
                  }
     }
}

The ASP.NET Core backend is running on localhost, the key that took me a while to locate was the port. In the end, I found that the port being used can be found using BridgeSettings.WebPort.

Include the menu

The final change that is needed is to make sure the new set of menus get rendered. For the sample application open the _Layout.cshtml file in the Views/Shared directory. Inside the head tag add the following line which will force a call to the MenusController when the application loads.

<link rel="import" href="menus">

Wrapping Up

Customizing the application menu ended up being pretty easy. If I hadn’t wanted to navigate to a specific page I would have been done in no time, but hitting the issue with navigation helped me learn more about how Electron.NET is working. You can check out the finished code here.

Electron.NET: Custom Application Menus Read More »

.NET CLI Errors Due to VSTS Package Source

At work, we use Visual Studio Team Services for source control, internal NuGet package management, and continuous integration. It has been a great tool that has really helped streamline our processes.

.NET CLI Issue

The problem with the setup is if the .NET CLI calls anything that uses NuGet (restore, installing new templates) with the VSTS package source enable it results in the following unauthorized error.

C:\Program Files\dotnet\sdk\2.1.103\NuGet.targets(104,5): error : Unable to load the service index for source https://company.pkgs.visualstudio.com/_packaging/feedname/nuget/v3/index.json. [project.csproj]
C:\Program Files\dotnet\sdk\2.1.103\NuGet.targets(104,5): error : Response status code does not indicate success: 401 (Unauthorized). [project.csproj]

I have been working around this by disabling the VSTS package source when working with the .NET CLI. It is a bit of a pain, but it works since I’m not using any of the packages from our private feed.

A Fix

I had the opportunity to talk to one of the VSTS product managers (PM) for the package management area and they are aware that this issue. While not the ultimate fix the PM pointed out that I could use a personal access token to get around the error.

Create a Personal Access Token

Log in to VSTS and hover over your profile picture and select security.

Next, click the add button on the Personal access tokens screen.

The next page you will need to enter a description for the token and select how long the token should be good for. It is also very important to change the Authorized Scopes off of all and only select the ones you want the token to be valid for. In my case, I selected everything package related, but Packaging (read) would be enough if you aren’t going adding packages to the feed.

At the bottom of the above screen click the Create Token button. This will take you back to the token list page with a new item for your new token and this will be your only opportunity to get a copy of the token.

Add NuGet Source with Token

Now that you have a token open up a command prompt and use the following command to add the NuGet source that will use your new personal access token.

nuget.exe sources add -name {your feed name} -source {your feed URL} -username {anything} -password {your token}

Wrapping Up

It turned out to be pretty easy fix this issue. Don’t be like me and just deal with it by disabling the package sources causing the problem. Just make sure that you don’t check-in your NuGet config file that contains your personal access token.

.NET CLI Errors Due to VSTS Package Source Read More »

Electron.NET with a Web API

This post will be expanding on the introduction to Electron.NET that I did here to add in a Web API hit to pull some data as well as the UI on the Electron side to use this data. The code before any changes can be found here.

API Creation

To create the API I used the following from the command prompt in the folder where I wanted the new project to be created.

dotnet new webapi

API Data

Now that the API project is created we need to add in the ability to interact with a database with Entity Framework Core. Adding in Entity Framework Core ended up turning into a post of its own when you can read here.

The model and DB Context of the API project match what was in the blog post I linked above, but I am going to include them here. The following is the model.

public class Contact
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string Subregion { get; set; }
    public string PostalCode { get; set; }
    public string Phone { get; set; }
    public string Email { get; set; }
}

Next, is the DB Context, which is empty other than the DB Set for the contacts table.

public class ContactsDbContext : DbContext
{
    public DbSet<Contact> Contacts { get; set; }

    public ContactsDbContext(DbContextOptions<ContactsDbContext> options)
        : base(options)
    {

    }
}

With our model and context setup, we can run the following two commands to add the initial migration and apply the migration to the database.

dotnet ef migrations add Contacts
dotnet ef database update

API Endpoints

The API is just going to handle the basic CRUD (create, read, update, delete) operations for contact. Instead of hand coding the controller we are going to use some code generation provided by Microsoft. First, we need to add the Microsoft.VisualStudio.Web.CodeGeneration.Design NuGet package to the API project using the following command in a command prompt set to the root of the API project.

dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design

Now with the above package installed, we can use the following command to generate a controller with the CRUD operations already implemented.

dotnet aspnet-codegenerator controller -name ContactsController --model Contact --dataContext ContactsDbContext -outDir Controllers -api

There is a lot of switches when using aspnet-codegenerator. The following is a rundown of the ones used above.

  • controller tells the code generator we are creating a controller
  • name defines the name of the resulting controller
  • model is the model class that will be used for the generation
  • dataContext is the DB Context that will be used for the generation
  • outDir is the directory the output will be in relative to the current directory of your command prompt
  • api tells the code generator this controller is for a REST style API and that no views should be generated

With the code generation complete the API should be good to go.

Electron Model

Over in the Electron project, we need a model to match the data the API is returning. This could be the point where a third project is added to allow the API and the Electron app to share common items, but just to keep the example simple I’m just going add a copy of the contact model from the API project to the Electron project.  The following is the full contact model class.

public class Contact
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string Subregion { get; set; }
    public string PostalCode { get; set; }
    public string Phone { get; set; }
    public string Email { get; set; }
}

Electron Views

Now that we have a model in our Electron project we need to create the views that go along with it. Start by adding the code generation package like we did above using the following command.

dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design

Unfortunately, controller generation needs a DBContext to work which our project doesn’t have, so we have to take the long way about to generate our views and then manually create a controller to go with them. In order to get view generation to work, I had to add references to the Entity Framework Core Tools package using the following command.

dotnet add package Microsoft.EntityFrameworkCore.Tools

In the csproj file add the following .NET CLI tool reference.

<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.2" />

Now the project is ready to use the command prompt to generate the views we will need for our CRUD operations related to our contacts. Use the following commands to create the full range of views needed (Create, Edit, List, Delete, Details).

dotnet aspnet-codegenerator view Create Create --model Contact --useDefaultLayout -outDir Views/Contacts

dotnet aspnet-codegenerator view Edit Edit --model Contact --useDefaultLayout -outDir Views/Contacts

dotnet aspnet-codegenerator view Index List --model Contact --useDefaultLayout -outDir Views/Contacts

dotnet aspnet-codegenerator view Delete Delete --model Contact --useDefaultLayout -outDir Views/Contacts

dotnet aspnet-codegenerator view Details Details --model Contact --useDefaultLayout -outDir Views/Contacts

Again there is a lot of switches when using aspnet-codegenerator. The following is a rundown of the ones used above.

  • view  tells the code generator we are creating a view
  • the next two items are the name of the view and the name of the view template
  • model is the model class that will be used for the generation
  • useDefaultLayout uses the default layout (surprise!)
  • outDir is the directory the output will be in relative to the current directory of your command prompt

The Index.cshtml generated above comes with links for Edit, Details, and Delete that won’t work as generated. Open the file and make the following changes to pass the key of the contact trying to be opened.

Before:
@Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
@Html.ActionLink("Details", "Details", new {/* id=item.PrimaryKey */ }) |
@Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })

After:
@Html.ActionLink("Edit", "Edit", new {  id=item.Id }) |
@Html.ActionLink("Details", "Details", new { id=item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id=item.Id })

Electron Controller

With the views complete let’s add a ContactsController.cs to the Controllers directory. The code for the controller follows, but I’m not going to go into the details. I took a controller from another contact base project and just replaces all the Entity Framework stuff with calls to the API we created above. Please don’t use this as an example of how something like this should be done it is just quick and dirty to show that it can work.

public class ContactsController : Controller
{
    private string _apiBaseUrl = "http://localhost:5000/api/contacts/";

    // GET: Contacts
    public async Task<IActionResult> Index()
    {
        using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
        {
            return View(JsonConvert.DeserializeObject<List<Contact>>(await (await client.GetAsync("")).Content.ReadAsStringAsync()));
        }
    }

    // GET: Contacts/Details/5
    public async Task<IActionResult> Details(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
        {
            var contact = JsonConvert.DeserializeObject<Contact>(await (await client.GetAsync(id.ToString())).Content.ReadAsStringAsync());

            if (contact == null)
            {
                return NotFound();
            }

            return View(contact);
        }
    }

    // GET: Contacts/Create
    public IActionResult Create()
    {
        return View();
    }

    // POST: Contacts/Create
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create([Bind("Id,Address,City,Email,Name,Phone,PostalCode,State")] Contact contact)
    {
        if (ModelState.IsValid)
        {
            using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
            {
                await client.PostAsync("", new StringContent(JsonConvert.SerializeObject(contact), Encoding.UTF8, "application/json"));
            }

            return RedirectToAction("Index");
        }
        return View(contact);
    }

    // GET: Contacts/Edit/5
    public async Task<IActionResult> Edit(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
        {
            var contact = JsonConvert.DeserializeObject<Contact>(await (await client.GetAsync(id.ToString())).Content.ReadAsStringAsync());

            if (contact == null)
            {
                return NotFound();
            }

            return View(contact);
        }
    }

    // POST: Contacts/Edit/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(int id, [Bind("Id,Address,City,Email,Name,Phone,PostalCode,State")] Contact contact)
    {
        if (id != contact.Id)
        {
            return NotFound();
        }

        if (ModelState.IsValid)
        {
            using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
            {
                await client.PutAsync(id.ToString(), new StringContent(JsonConvert.SerializeObject(contact), Encoding.UTF8, "application/json"));
            }
            return RedirectToAction("Index");
        }
        return View(contact);
    }

    // GET: Contacts/Delete/5
    public async Task<IActionResult> Delete(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
        {
            var contact = JsonConvert.DeserializeObject<Contact>(await (await client.GetAsync(id.ToString())).Content.ReadAsStringAsync());

            if (contact == null)
            {
                return NotFound();
            }

            return View(contact);
        }

    }

    // POST: Contacts/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DeleteConfirmed(int id)
    {
        using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
        {
            await client.DeleteAsync(id.ToString());
            return RedirectToAction("Index");
        }
    }

    private async Task<bool> ContactExists(int id)
    {
        using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
        {
            return JsonConvert.DeserializeObject<Contact>(await (await client.GetAsync("id")).Content.ReadAsStringAsync()) != null;
        }
    }
}

Electron Add Link To Navigation

The final step to add a link to the list of contacts to the navigation bar of the application. Open the _Layout.cshtml and in the unordered list for the nav bar add the following line.

<li><a asp-area="" asp-controller="Contacts" asp-action="Index">Contacts</a></li>

Wrapping Up

That is all the changes to get the application up and running. If you run the API and then use dotnet electronize start from a command prompt in the ElectronTest project root all should be good to go.

The completed code can be found here.

Electron.NET with a Web API Read More »