Use HTTP Client Factory with NSwag Generated Classes in ASP.NET Core 3

In last week’s post, Using NSwag to Generate C# Client Classes for ASP.NET Core 3, we left off with a usable client, but we were missing out on using some of the features provided by ASP.NET Core such as the HTTP Client Factory and utilizing dependency injection.

Changes to NSwag Client Generation

This post is only going to point out the difference needed to help enable utilization of the ASP.NET Core features mentioned above and won’t be a full walkthrough of using NSwag. If you need a reference for what this post is covering make sure and read last week’s post.

The one change needed from last week’s post is to check Generate interfaces for Client classes.

With the above checked the client class can be regenerated and the files in the consuming application updated.

Using HTTP Client Factory and Dependency Injection

In the consuming application, we need to add the following to line in the ConfigureServices function of the Startup class to add an HTTP Client specifically for our Contacts API and make it available via the dependency injection system.

services.AddHttpClient<IContactsClient, ContactsClient>(client => 
           client.BaseAddress = new Uri("https://localhost:5001"));

For a production application, I would recommend using the configuration system to store the URL for the API instead of hardcoded like it is above.

For example usage, I’m using the IndexModel. First,  add a class-level field to hold our API client and inject the client via the constructor.

private readonly IContactsClient _contactsClient;

public IndexModel(ILogger<IndexModel> logger, IContactsClient contactsClient)
{
    _logger = logger;
    _contactsClient = contactsClient;
}

Now that we have a contacts client at the class-level we can use it get data from our API. The following example uses the client to get all the contacts from the API and stores them in a variable.

public async Task OnGet()
{
    var contacts = await _contactsClient.GetContactsAsync();
}

Wrapping Up

I highly recommend using this style of client vs. using HTTP client directly. If you do some searching you will find that managing the lifetime of HTTP client in .NET before the HTTP client factory was something that is easy to screw up.

The following posted were used as references:

Generating a Typed Client for use with HttpClientFactory using NSwag
How to add generated HttpClient to ASP.NET Core dependency injection

Using NSwag to Generate C# Client Classes for ASP.NET Core 3

This post is going to use one of the tools provided by NSwag to generate C# client classes to provide access to an API. While the NSwag tooling provides multiple ways to discover the definition of an API we will be using the tooling to generate C# classes from an OpenAPI/Swagger specification.

For details on how to use NSwag to provide OpenAPI/Swagger for your APIs check out my Swagger/OpenAPI with NSwag and ASP.NET Core 3 post. You can grab the API I’m using in the post from this GitHub repo if you need an API to play around with. If you do grab the sample API from GitHub not that it does use Entity Framework Core and SQLite which means you will need to create the associated database. Details of how to do that can be found in the Create and Apply Initial Migration section of my ASP.NET Core 3: Add Entity Framework Core to Existing Project post.

Sample Client Application

For this example, we will spin up a Razor Pages application using the .NET CLI with the following command from your favorite terminal application in the directory you want the application created.

dotnet new webapp

NSwag Client Generation

NSwag provides multiple options for client generation including a CLI option, code, and a Windows application. This post is going to use the Windows application which is called NSwagStudio. Download and install NSwagStudio from here.

Next, make sure your API is running and get the URL of its OpenAPI/Swagger specification URL. For example, I am using a local instance of my API and the URL I need is https://localhost:5001/swagger/v1/swagger.json. If you are using the Swagger UI you can find a link to your swagger.json under the API title.

Now that we have the OpenAPI/Swager specification URL for the API we are dealing with open NSwagStudio. The application will open with a new document ready to go. There are a few options we will need to set. First, we want to use the NetCore30 Runtime. Next, select the OpenAPI/Swagger Specification tab and enter your API’s specification URL in the Specification URL box.

In the Outputs section check the CSharp Client checkbox and then select the CSharp Client tab. As you can see from the screenshot below there are a ton of options to tweak. For this example, we are taking the defaults for all of them except for Namespace, which I set to ContactsApi, and Output file path, which is only needed if you use the Generate Files option. Click the Generate Files button and NSwagStudio will create a file that contains all the code needed to access the API described in the OpenAPI/Swager specification selected in the Input section.

Note, the Generate Outputs button can be used if you want to see what the generated code will look in the Output tab on the same level as Settings.

Use Generated Client from the Sample Project

In the sample project, I created an APIs directory and dropped the ContactsApi.cs created with NSwagStudio there. The files generated with NSwagStudio are expecting JSON.NET to be present so the sample project will need a reference to the Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package.

Now that the project has a reference to JSON.NET in the ConfigureServices function of the Startup class we need to tell the app to make JSON.NET available via dependency injection with the following change.

services.AddRazorPages()
        .AddNewtonsoftJson();

Now to test out the client I used the following OnGet function in the Index.cshtml.cs file.

public async Task OnGet()
{
    using (var httpClient = new HttpClient())
    {
        var contactsClient = new ContactsClient(httpClient);
        var contacts = await contactsClient.GetContactsAsync();
    }
}

Note the above is only meant to show that the generated client work and isn’t meant to be a production-grade example. For more production-grade scenarios make sure and following Microsoft’s guidance on HTTP client usage.

Wrapping Up

NSwag’s client generation seems to be an easy way to get started consuming API’s. I’m not sure if the CLI would provide more options for how the client code is generated or not with support of HTTPClientFactory and strongly typed HTTP Clients. This will be something I may explorer more in a future post.

Swagger/OpenAPI with NSwag and ASP.NET Core 3

Now that .NET Core 3 is out I thought it would be a good time to revisit exposing API documentation using Swagger/OpenAPI. In the past, I have written posts on using Swashbuckle to expose Swagger documentation, but for this post, I’m going to try out NSwag.

What is OpenAPI vs Swagger?

To quote the Swagger docs:

OpenAPI Specification (formerly Swagger Specification) is an API description format for REST APIs. An OpenAPI file allows you to describe your entire API. API specifications can be written in YAML or JSON. The format is easy to learn and readable to both humans and machines.

Swagger is a set of open-source tools built around the OpenAPI Specification that can help you design, build, document and consume REST APIs.

What is NSwag?

Quoting the NSwag GitHub readme:

NSwag is a Swagger/OpenAPI 2.0 and 3.0 toolchain for .NET, .NET Core, Web API, ASP.NET Core, TypeScript (jQuery, AngularJS, Angular 2+, Aurelia, KnockoutJS and more) and other platforms, written in C#. The OpenAPI/Swagger specification uses JSON and JSON Schema to describe a RESTful web API. The NSwag project provides tools to generate OpenAPI specifications from existing ASP.NET Web API controllers and client code from these OpenAPI specifications.

One neat thing about NSwag is it also has the tooling to help generate the API consumer side in addition to the OpenAPI specs.

Sample Project

For this post, I created a new API project via the .NET CLI using the following command. Not that all this can be done via the Visual Studio UI if that is your preference.

dotnet new webapi

For me, this project is going to be the start of a new series of posts so I also added a solution file and added the project created above to it. These commands are optional.

dotnet add sln
dotnet sln add src\ContactsApi\ContactsApi.csproj

Add NSwag

Using the CLI in the same directory as the project file use the following command to add a reference to NSwag.AspNetCore to the project.

dotnet add package NSwag.AspNetCore

Next, in your favorite editor open the project/directory we created and open the Startup.cs file. In the ConfigureServices function add services.AddOpenApiDoccument.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddOpenApiDocument();
}

Then at the end of the Configure function add calls to app.UseOpenApi and app.UseSwaggerUi3.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment()) app.UseDeveloperExceptionPage();

    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });

    app.UseOpenApi();
    app.UseSwaggerUi3();
}

Note that NSwag also supports ReDoc if you prefer that over Swagger UI.

Sample Model and Controller

Now that we have NSwag installed let’s create a new endpoint for it to display. As per my norm, I will be doing this using contacts as an example. First I created a Models directory and then added the following Contact class to it.

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 State { get; set; }
    public string PostalCode { get; set; }
    public string Phone { get; set; }
    public string Email { get; set; }
}

Next, in the Controllers directory add a ContactsController, which in the following code returns a list of 5 generic contacts.

[ApiController]
[Route("[controller]")]
public class ContactsController : ControllerBase
{
    private readonly ILogger<ContactsController> _logger;

    public ContactsController(ILogger<ContactsController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IEnumerable<Contact> Get()
    {
        return Enumerable.Range(1, 5).Select(index => new Contact
        {
            Id = index,
            Name = $"Test{index}",
            Address = $"{index} Main St.",
            City = "Nashville",
            State = "TN",
            PostalCode = "37219",
            Phone = "615-555-5555",
            Email = $"test{index}@test.com"
        });
    }
}

Results

Run your project and then in a browser navigate to your base URL /swagger. For example my for my project that is https://localhost:5001/swagger. You should see something like the following that will let you explore your API and even execute requests against your API using the Try it out button you see in the UI.

Wrapping Up

Just like with Swashbuckle, NSwag makes it very easy to get started providing API documentation. This post just covers the very basics and I’m looking forward to digging into some of the more advanced features that NSwag has such as client generation.

Microsoft has a great article on Getting Started with NSwag on their docs site that I recommend reading. This is a preview of something I plan to cover in the future, but there are attributes that can be added to controllers that help NSwag provide better details about what your API can return and Microsoft has a doc on Use web API conventions that makes it easy to apply some of the common conventions.

Trying Out BenchmarkDotNet

In software, there are tons of different ways to accomplish the same thing and one of the metrics we tend to use to determine a course of action is how we feel that one set of code will perform over another.  The thing is determining which code actually performs better is a bit tricky and I feel like in general people make the choice based on a gut feeling more than actual evidence. Even when evidence a developer has involved if it wasn’t properly collected then it isn’t actually helpful. For example, trying to determine the performance of a set of code when running in debug mode isn’t actually a good indicator of how it is going to perform.

What is a developer to do? Well, this is where BenchmarkDotNet comes in. Here is how the project describes itself.

Benchmarking is really hard (especially microbenchmarking), you can easily make a mistake during performance measurements. BenchmarkDotNet will protect you from the common pitfalls (even for experienced developers) because it does all the dirty work for you: it generates an isolated project per each benchmark method, does several launches of this project, run multiple iterations of the method (include warm-up), and so on. Usually, you even shouldn’t care about a number of iterations because BenchmarkDotNet chooses it automatically to achieve the requested level of precision.

This rest of this post is going to cover creating a sample project using BenchmarkDotNet.

Sample Project

We will be using a new .NET Core console application which can be created using the following .NET CLI command.

dotnet new console

Next, run the following command to add the BenchmarkDotNet NuGet package.

dotnet add package BenchmarkDotNet

Now in the Main function of the Program class, we need to tell the application to run the benchmark we are interested in. In this example, we are telling it to run the benchmarks in the Strings class.

public static void Main(string[] args)
{
    BenchmarkRunner.Run<Strings>();
}

Now in the Strings class, we have two functions marked with the Benchmark attribute which is how the package identifies which functions to measure. For this example, we will be measuring the performance of two different ways to do case insensitive string comparisons.

public class Strings
{
    private readonly Dictionary<string, string> _stringsToTest = 
         new Dictionary<string, string>
         {
             { "Test", "test" },
             { "7", "7" },
             { "A long string", "Does not match" },
             { "Testing", "Testing" },
             { "8", "2" }
         };


    [Benchmark]
    public bool EqualsOperator()
    {
        var result = false;

        foreach (var (key, value) in _stringsToTest)
        {
           result = key.ToLower() == value.ToLower();
        }

        return result;
    }

    [Benchmark]
    public bool EqualsFunction()
    {
        var result = false;

        foreach (var (key, value) in _stringsToTest)
        {
            result = string.Equals(key, value,
                                   StringComparison.OrdinalIgnoreCase);
        }

        return result;
    }
}

I’m sure there is a better way to set up data for test runs, but the above works for my first go at it.

Results

Run the application in release mode and you will see output similar to the following.

Wrapping Up

Having a tool that takes all the guesswork out of how operations perform is going to be very valuable. This is one of those tools I really wish I had found years ago. The project is open source and can be found on GitHub.

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: 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: 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 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.

Run Multiple Projects in Visual Studio Code

While expanding the sample used in the Electron.NET post to include an API I hit a snag with Visual Studio Code. I have used multiple projects with Visual Studio Code before, but never two distinct applications that I need to run at the same time.  Sample code that contains the two projects, but before any modifications covered in this post can be found here.

Building Multiple Projects

The first issue to tackle was getting both projects to build since they are technically independent. As I’m sure you are aware VS Code doesn’t need a solution file like full Visual Studio does. What I learned during this process was that while a solution file isn’t required once can be used to ensure multiple projects all get built. Using VS Code’s Terminal I ran the following commands to create a new solution and add my two projects to that solution.

dotnet new sln -n ElectronTest
dotnet sln add src/ElectronTest/ElectronTest.csproj
dotnet sln add src/ContactsApi/ContactsApi.csproj

With the solution file in place, I then opened the tasks.json file found in the .vscode directory. In the build task, I removed the path to a specific csproj file and just used the workspace folder.

Before:
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
    "build",
    "${workspaceFolder}/src/ElectronTest/ElectronTest.csproj"
]

After:
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
    "build",
    "${workspaceFolder}"
]

This is just one option on how to handle the builds. I believe another way would be to have two build tasks (one for each project) and use the individual build task in your launch configurations (which we are going to talk about next).

Debugging Multiple Projects

Open up your launch.json file in the .vscode directory. By default you will see a couple of configurations more than likely you will see one named .NET Core Attach and another named .NET Core Launch (web). It is the .NET Core Launch (web) configuration that we are interested in. This configuration controls how our application is launched. If you notice the program property points to the dll created during the build process, in the sample code this is  ${workspaceFolder}/src/ElectronTest/bin/Debug/netcoreapp2.0/ElectronTest.dll. This is all fine but doesn’t give us a way to run both of our projects. Let’s tweak the env property to set the ASPNETCORE_URLS which will control the URL the application is launched with.

Before:
"env": {
    "ASPNETCORE_ENVIRONMENT": "Development"
}

After:
"env": {
    "ASPNETCORE_ENVIRONMENT": "Development",
    "ASPNETCORE_URLS": "http://localhost:5001"
}

Now that the original application’s launch configuration is set we need to add a second launch configuration for our second application. The following is the full configuration section for my second application (the API).

{
    "name": ".NET Core Launch (API)",
    "type": "coreclr",
    "request": "launch",
    "preLaunchTask": "build",
    "program": "${workspaceRoot}/src/ContactsApi/bin/Debug/netcoreapp2.0/ContactsApi.dll",
    "args": [],
    "cwd": "${workspaceRoot}/src/ContactsApi",
    "stopAtEntry": false,
    "launchBrowser": {
        "enabled": false,
        "args": "${auto-detect-url}",
        "windows": {
            "command": "cmd.exe",
            "args": "/C start ${auto-detect-url}"
        },
        "osx": {
            "command": "open"
        },
        "linux": {
            "command": "xdg-open"
        }
    },
    "env": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "ASPNETCORE_URLS": "http://localhost:5000"
    },
    "sourceFileMap": {
        "/Views": "${workspaceRoot}/Views"
    }
}

I started with a copy of the original application and just modified a couple of things. First, the name property is now .NET Core Launch (API) which will help us know which application we are launching later. In the launchBrowser section, I set enabled to false since this is an API and I don’t need to launch the browser when the application starts. The final difference is in ASPNETCORE_URLS to make sure the URLs of the two applications are different. I used http://localhost:5000 in this example.

Now that all our configurations are good to go hop over to the debug section in VS Code.

On the debug you will notice that both of our launch configurations are listed. If you select the API one and hit the green play button it will start the API. With the API running you can then select the web configuration and hit the green play button and you will have both of your applications running in debug mode.

Wrapping Up

While it is not obvious at all how to get multiple applications to run in a single instance of VS Code the process isn’t hard after you do it the first time. I think my Visual Studio experience made figuring this out harder than it should have been. My key to learning how to get this going was on this GitHub issue.

Hopefully, this saved you some of the struggles I went through. Please leave a comment if there are different ways to accomplish this. The code with the final configuration can be found here.

Create a Desktop Application using ASP.NET Core and Electron.NET

Electron is a cross-platform (Windows, Linux, and Mac) library for building desktop applications using JavaScript, HTML, and CSS. Even if you have never head of Electron I’m sure you have used some of the desktop applications it has been used to build such as Visual Studio Code, Atom, Slack.

Electron.NET provides a wrapper around Electron with an ASP.NET Core MVC application in the mix. Here is the reason why this was done from the creates of the project.

Well… there are lots of different approaches how to get a X-plat desktop app running. We thought it would be nice for .NET devs to use the ASP.NET Core environment and just embed it inside a pretty robust X-plat enviroment called Electron. Porting Electron to .NET is not a goal of this project, at least we don’t have any clue how to do it. We just combine ASP.NET Core & Electron.

Project Creation

Seems only fitting to use VS Code for this post since it is built using the same base library. From the command line create a new ASP.NET Core MVC application using the following command.

dotnet new mvc

Make note that it is important at this time that you use the MVC template when attempting this and not a Razor Page application. This is more of a warning if you are using Visual Studio to do your project creation and not the CLI command above.

Next, we need to reference the ElectronNET.API NuGet package which can be added using the following command.

dotnet add package ElectronNet.API

Then, open the csproj and add a reference for the Electron.NET CLI tool which should match the following.

<ItemGroup>
     <DotNetCliToolReference Include="ElectronNET.CLI" Version="0.0.9" />
</ItemGroup>

After that make sure and restore packages using the following command.

dotnet restore

Wire-up Election.NET

Open Program.cs and add UseElectron(args) to the WebHost builder chain.

WebHost.CreateDefaultBuilder(args)
    .UseElectron(args)
    .UseStartup<Startup>()
    .Build();

Net, open Startup.cs and add the following line to the bottom of the Configure function. This is what will open the Electron window on startup.

Task.Run(async () => await Electron.WindowManager.CreateWindowAsync());

Finally, run the following from the command prompt to get the project ready to run under Electron. This should only need to be done once.

dotnet electronize init

Run the application

At this point, the application is ready to go. The neat part is you can just hit F5 and it will run like any normal ASP.NET Core application (this would change when you get into Electron specific calls I’m sure) or if you run the following command it will run inside of the Electron shell.

dotnet electronize start

Wrapping up

It was surprisingly simple to get a new application up and running, of course so far the app isn’t anything other than a web site hosted in Electron. My plan is to take this base application and create my normal basic contacts application, which will be the next post. From there I may look into layering in some Electron features.

The completed code can be found here.