Setting Up for Mono Development in Linux Mint/Ubuntu

Posted on October 19 2014 06:08 PM by jatten in Linux, Learning Linux, C#   ||   Comments (2)

follow-the-yerllow-mint-roadI recently needed to create a cross-platform C# library which needs to work across Windows, OSX, and Linux. Obviously, getting a .NET library running on Windows presented no great difficulties. Getting the same library working under Mono on a Linux platform was a little more involved.

The folks at Xamarin have brought Mono, and the development tools available for Mono, a long ways. Xamarin Studio is a beautiful IDE which, while it differs from Visual Studio in some important ways, is a joy to work with. Xamarin's focus is building mobile application development tools. However, Xamarin Studio and Mono both lend themselves fairly well to working with C# code on either Windows or OSX.

Image by Neil Fowler  | Some Rights Reserved

There are some notable things missing (you can't work directly with Windows Forms, for example, nor is there significant support for ASP.NET), but overall, working with strictly library code presented only minor hurdles (not the least of which is a different workflow and navigation scheme from VS).

However, Xamarin Studio is only available for Windows and OSX - there is not currently any support for direct Linux platforms (yes, I know a shiny veneer on top of what amounts to Linux under the hood).

Getting things working on my Linux machine took a little more work.

Mono Development on Linux Using MonoDevelop

Xamarin Studio represents a "Super-Set" of the original, cross-platform Mono IDE, MonoDevelop. The Xamarin team have generously pushed a good number of improvements back to the MonoDevelop source. However, the most recent changes are not directly available as packages to all Linux distros.

Specifically, while the most recent version of MonoDevelop is 5.0.1.3, the most recent package available to Ubuntu, and Ubuntu-based systems from the MonoDevelop site is version 2.6.0.1.

In this article, we will look at how to get a proper Mono development environment set up on Ubuntu-based systems, including the most recent (as of this writing) release of MonoDevelop. We will also look at getting Nuget, and Nuget package restore, working properly in this environment.

Got Linux?

If you don't have a Linux box, set up a VM to work through this post. I prefer using Linux Mint for my Linux-based activity. I found that all of the following steps worked fine in the most recent version of Ubuntu. However, I DON'T like Ubuntu itself. I prefer Linux mint. The last version of Mint I was using (Mint 13 with the KDE desktop) did not work very well with the most recent version of MonoDevelop. I found the the most recent stable release of Mint 17, using the Cinnamon desktop, worked great, with none of the bloat I found with Ubuntu.

See the following for detailed instructions if you need to set up a Linux VM:

For the purpose of this post, we will assume you are starting with a reasonably fresh VM, and do not already have Mono or MonoDevelop Installed.

As mentioned previously, the links on the MonoDevelop download site don't get you the most recent Mono release for Ubuntu-based systems. Fortunately, there are some great Personal Package Archives out there which DO.

Install Mono

Your machine may or may not already have Mono installed. Either way, we are going to pull down the most recent, complete version.

Open a terminal in your home folder, and do the following:

Update Everything:
$ sudo apt-get update

 

Install Mono:
$ sudo apt-get install mono-complete

Once the Mono Installation completes, update again before moving forward:

Update Everything Again:
$ sudo apt-get update

 

The most recent Mono release is now installed on your machine. Now, let's get all the goodness of the most recent MonoDevelop release.

Add the Personal Package Archive (PPA) for Latest Stable MonoDevelop Release

To get the most recent stable release of MonoDevelop, we can thank Eberhard Beilharz for making the Stable Version of MonoDevelop PPA at Launchpad.Net.

To get the most recent version of MonoDevelop on our machine, we simply need to add the PPA to our Synaptic Package Manager's list of sources, and then use synaptic to pull it down.

Add the MonoDevelop PPA to Synaptic:
$ sudo add-apt-repository ppa:ermshiperete/monodevelop

 

Update to refresh Synaptic:

Update Everything Yet Again:
$ sudo apt-get update

 

Install MonoDevelop

Now that we have added the PPA, we can use Synaptic just like any other package:

Install MonoDevelop from PPA:
$ sudo apt-get install monodevelop-current

 

Again, update everything:

Update Everything One More Time:
$ sudo apt-get update

 

Now, as indicated on the MonoDevelop PPA site, the install we just completed placed a shell script at /opt/monodevelop/bin/monodevelop-launcher.sh

Let's add an alias for this, so that we don't have to deal with that big long path every time we want to open MonoDevelop.

If this is a fresh install of Mint 17 on your machine, you will need to add a .bashrc file. If you already have a .bashrc file (or a .bash_profile file - in Mint they are functionally the same), add one:

Add a .bashrc File in your Home Directory:
$ touch .bashrc

 

Next, add an alias to .bashrc pointing to and executing the shell script:

Add Alias to Execute MonoDevelop-Launcher Script:
$ echo >> .bashrc "alias monodev=Exec=\"sh /opt/monodevelop/bin/monodevelop-launcher.sh\""

 

Close the terminal, and open a new terminal instance. You should now be able to open MonoDevelop from the terminal by typing, simply "monodev"

With that, we have successfully installed the most recent release of MonoDevelop on our Mint 17 or Ubuntu machine. However, we are likely to find that Nuget, and Nuget package restore, give us difficulties in this out-of-the box scenario.

Making Nuget Package Restore Work in MonoDevelop

Out-of-the-box, Nuget works a little differently in MonoDevelop than we are accustomed to in Visual Studio (this is true of much about MonoDevelop, actually…).

To see what I mean, use your new terminal alias and open MonoDevelop. Then, create a new Console project. Once that's done, take a moment to get oriented.

We can add Nuget packages by right-clicking on individual projects within the solution in the tree to the left:

Add Nuget Packages in MonoDevelop:

add-nuget-packages

However, if we try to add Nuget packages right now, we get a big FAIL:

Add Nuget Packages Fails in MonoDevelop:

add-packages-fail

WAT?

What's equally confounding is that, if we were to open and build an existing project which required Nuget Package Restore, the Package Manager would is not always able to connect to Nuget to pull down the packages, and an error would result.

This doesn't affect all packages, but enough to be annoying. In particular, several NUnit-related packages and a few others, appear to be affected, and your project can't be properly built.

As it turns out, we are missing some important SSL certificates. The fix is simple, and we can do it from our terminal.

Add Required SSL Certificates for Nuget to Work in MonoDevelop

Since we are using our current terminal instance to run MonoDevelop, we will either need to quit MonoDevelop, or open a new terminal instance, and then execute the following to import a number of important SSL certificates:

Import SSL Certificates from mozroots:
$ mozroots --import --sync

 

Once we have run this command, we should be able to add Nuget packages at will. Also, Update Packages, and Restore Packages commands from the Solution context menu both should work with no problems.

Additional Resources and Items of Interest

 

Posted on October 19 2014 06:08 PM by jatten     

Comments (2)

Setting Up Linux in a Virtual Machine for Windows Users

Posted on October 19 2014 10:04 AM by jatten in Linux, Learning Linux   ||   Comments (0)

photozel-linux-mint-18142If you have spent most of your computing life using Windows, odds are good you may find yourself in unfamiliar territory when if comes to setting up a Linux box. Even for some experienced technical folks, the differences between the platforms can have you feeling like a noob from the very start.

Setting Linux up in a virtual machine can save some time and pain, but comes with the added overhead of understanding how the particular virtualization software works.

In this post, we will look at setting up a basic Linux environment in a virtual machine, and look at some of the trouble-shooting you may need to do along the way.

Image by Photozel  |  Some Rights Reserved

Note, I am not a Linux expert. I have been learning my way through Linux for most of the past year or so, but there is a lot to learn. I find I really, really enjoy using Linux, and once I got past the initial learning curve, things began to make sense rather quickly.

Learn Linux the Hard Way

Most of the recommended introductory Linux distributions ("distro's") feature a GUI to varying degrees. It is possible to do much of what you set out to do without cracking open a terminal. However, when I started down the golden Linux path, a determined to use the terminal wherever possible, and patiently become proficient with this OS in the terms under which, ultimately, it was designed to be used.

I strongly recommend you do the same. Not only will you gain a deeper, better understanding of Unix-based systems, but of how your machine works in general. Unlike the Windows world in which I grew up, Linux/Unix is first and foremost a command-line driven platform, and is optimized as such. In fact, it's fair to say that, unlike Windows, in *nix OS's, anything you can do from the GUI, you can also do from the terminal, usually more efficiently (once you know how, and depending on your typing proficiency).

If you are stuck on how to do something from the terminal, Google is you friend. If your are still stuck, you can probably do it from the GUI until you find an answer. As I said, though, I strongly recommend using the terminal for everything you can. It will feel incredibly clumsy at first, but like all things, the more you do, the better you get.

The article linked below was focused on getting to know Git for folks with a Windows background, but there are a number of handy bash references you may find helpful:

Virtualization Software

Before we can set up a virtual machine, we need some virtualization software. I tend towards the free and open source VirtualBox solution. Despite lacking some of the polish associated with VMWare, I find that VirtualBox meets my needs nicely.

In this article we will be using VirtualBox as our virtualization environment.

Download and Install the appropriate VirtualBox package for your PC from the VirtualBox Downloads page.

Linux Mint

While Ubuntu is the most popular "beginner" Linux distro, I prefer Linux Mint. Newer releases of Ubuntu seem slow and bloated to me, and oriented more directly at desktop users, with many of the more advanced tools well hidden. Also note that Linux Mint is derived from Ubuntu.

Mint seems to more effectively consider both desktop/GUI users, as well as more technical folk. While the full desktop GUI is available (and well-done, compared to some), the terminal is a short context menu away.

At the time of this writing, Linux Mint 17 is the most recent stable release, and is targeted for long-term support.

Downloading the Linux Mint ISO

Linux Mint is available with a number of different desktop options. Until recently, I have been using Mint with the KDE desktop, but have just switched to the newer Cinnamon desktop to check it out. Also, I had issues getting MonoDevelop working in the KDE implementation.

You can download the stable Linux Mint release with the Cinnamon desktop from one of the mirrors on this page:

Once you have downloaded the Linux Mint ISO, save the file somewhere safe. You will need this shortly.

Configure a new Virtual Machine in VirtualBox

Once you have installed VirtualBox on your PC, select the "New" button from the top menu. You should be greeted with the Create Virtual Machine window like so:

VirtualBox Configuration - The Create Virtual Machine Window

create-virtual-machine

Choose a unique and descriptive name for the VM you intend to create. I tend to follow a convention of OS-RAM-HDD specs, but how you do it may depend on what you plan to do with the VM.

Next choose the type (Linux), and the version. In this case, Linux Mint is not one of the options. However, Mint is a descendant of Ubuntu, so Ubuntu will work here.

Allocating Resources to the Virtual Machine

Once you click next, you will be asked to select how much RAM your virtual machine will have. Note that your VM will be sharing RAM with your physical host machine. VirtualBox will offer up a "recommended" amount of RAM to allocate. However, I tend to push this up, as I have physical RAM in abundance on my PC. You can see here I selected 4Gb of RAM, which is likely more than I will ever need.

Allocate RAM to the Virtual Machine:

allocate-ram

Clicking next again, you will be asked if you want to create a virtual hard drive. The default (VDI) is the best bet here. You will also be asked if you want this to be fixed size, or dynamically allocated. I generally go with dynamically allocated.

Once you select these parameters, you will have the opportunity to specify the "size"  and location of the disk to be created. The recommended size if 8Gb. I generally bump this up to 20Gb, which is probably overkill. However, you will want to consider your intended use for the VM, and allocate disk space accordingly. If you plan to be working with lots of images or video, for example, you will want more disk space than if you plan to simply play with the terminal and familiarize yourself with Linux.

Allocate Disk Space to the Virtual Machine:

allocate-hdd

Once we hit the Create button, we are returned to the VirtualBox Management window. However, we are not done preparing our VM.

Other VM Settings

Now that we are back in the VirtualBox Management window, we can adjust the various settings for individual VM's by clicking on the appropriate heading to the right.

We want to change a few of the default settings here before we proceed.

Enable 3D Video Acceleration

If we click on the "Display" heading, we are greeted with the Display settings window. Here, we may want to increase the amount of Video RAM available to our VM. I generally push this up to 32 Mb.

Also, in order to get acceptable graphics performance, we will enable 3D acceleration.

Adjust Display Settings:

display-settings

Share Folder(s) Between the VM and Physical Host

We may want to share files between our host machine (our physical PC) and our VM. We can do this by specifying a folder on the host to be shared. I generally have a dedicated folder named "VM Share" that I use for this.

To set up a folder to be shared between the host and your VM(s), select the "Shared Folders" header in the settings window. Then, click the folder icon (top right) to select/create a folder to share.

add-shared-folder

For our purposes, the other settings for network, USB, and such are generally good with the default settings.

Start the VM and Install Linux Mint

The first time you start your VM, you will be prompted to select a startup disk. At this point, navigate to the Linux Mint ISO you downloaded previously. Click on the folder icon to the right of the drop-down list to navigate your file system.

Select Startup Disk:

select-startup-disk

The system will boot into the Linux Mint OS on the ISO. When Mint is done loading, note the CD icon on the Mint desktop:

Mint Desktop when Booted from ISO:

mint-boot-from-iso

Double click the CD icon, and follow the installation instructions to install Linux Mint on your VM.

NOTE: Linux will only accept lower-case user names, but the password you enter during set-up is case-sensitive.

If you enter a mixed-case username, it will be down-cased when it is saved.

When the installation completes, you will be asked to re-start the VM. Do this. If your VM hangs during the shutdown process (mine did), Simply close the VM window. When you are prompted to indicate how you want the machine to shut down, select the simple "Power Down the Machine" option.

Now, start the machine again using the "Start" icon in the VirtualBox Manager window. As soon as the window comes up, go to the "Devices" menu, eject the Mint Installation ISO:

Startup and Eject the Mint Installation ISO:

remove-disk-from-virtual-drive

If everything has worked properly to this point, you should be greeted with a login screen to log in to Linux Mint.

Enter the user name and password you specified during installation (remember, your username will be all lower-case), and you should be off and running.

Additional Resources and Items of Interest

 

Posted on October 19 2014 10:04 AM by jatten     

Comments (0)

ASP.NET Web Api: Unwrapping HTTP Error Results and Model State Dictionaries Client-Side

Posted on September 28 2014 08:42 PM by jatten in ASP.Net, ASP.NET MVC, C#, CodeProject   ||   Comments (0)

404 on wall-320When working with ASP.NET Web Api from a .NET client, one of the more confounding things can be handling the case where errors are returned from the Api. Specifically, unwrapping the various types of errors which may be returned from a specific API action method, and translating the error content into meaningful information for use be the client.

How we handle the various types of errors that may be returned to our Api client applications can be very dependent upon specific application needs, and indeed, the type of client we are building.

Image by Damien Roué  |  Some Rights Reserved

In this post we'll look at some general types of issues we might run into when handing error results client-side, and hopefully find some insight we can apply to specific cases as they arise.

Understanding HTTP Response Creation in the ApiController

Most Web Api Action methods will return one of the following:

  • Void: If the action method returns void, the HTTP Response created by ASP.NET Web Api will have a 204 status code, meaning "no content."
  • HttpResponseMessage: If the Action method returns an HttpResponseMessage, then the value will be converted directly into an HTTP response message. We can use the Request.CreateResponse() method to create instances of HttpResponseMessage, and we can optionally pass domain models as a method argument, which will then be serialized as part of the resulting HTTP response message.
  • IHttpActionResult: Introduced with ASP.NET Web API 2.0, the IHttpActionResult interface provides a handy abstraction over the mechanics of creating an HttpResponseMessage. Also, there are a host of pre-defined implementations for IHttpActionResult defined in System.Web.Http.Results, and the ApiController class provides helper methods which return various forms of IHttpActionResult, usable directly within the controller.
  • Other Type: Any other return type will need to be serialized using an appropriate media formatter.

For more details on the above, see Action Results in Web API 2 by Mike Wasson.

From Web Api 2.0 onward, the recommended return type for most Web Api Action methods is IHttpActionResult unless this type simply doesn't make sense.

Create a New ASP.NET Web Api Project in Visual Studio

To keep things general and basic, let's start by spinning up a standard ASP.NET Web Api project using the default Visual Studio Template. If you are new to Web Api, take a moment to review the basics, and get familiar with the project structure and where things live.

Make sure to update the Nuget packages after you create the project.

Create a Basic Console Client Application

Next, let's put together a very rudimentary client application. Open another instance of Visual Studio, and create a new Console application. Then, use the Nuget package manager to install the ASP.NET Web Api Client Libraries into the solution.

We're going to use the simple Register() method as our starting point to see how we might need to unwrap some errors in order to create a more useful error handling model on the client side.

The Register Method from the Account Controller

If we return to our Web Api project and examine the Register() method, we see the following:

The Register() method from AccountController:
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
 
    var user = new ApplicationUser() 
    { 
        UserName = model.Email, 
        Email = model.Email 
    };
 
    IdentityResult result = await UserManager.CreateAsync(user, model.Password);
 
    if (!result.Succeeded)
    {
        return GetErrorResult(result);
    }
    return Ok();
}

 

In the above, we can see that there are a number of options for what might be returned as our IHttpActionResult.

First, if the Model state is invalid, the BadRequest() helper method defined as part of the ApiController class will be called, and will be passed the current ModelStateDictionary. This represents simple validation, and no additional processes or database requests have been called.

If the Mode State is valid, the CreateAsync() method of the UserManager is called, returning an IdentityResult. If the Succeeded property is not true, then GetErrorResult() is called, and passed the result of the call to CreateAsync().

GetErrorResult() is a handy helper method which returns the appropriate IHttpActionResult for a given error condition.

The GetErrorResult Method from AccountController
private IHttpActionResult GetErrorResult(IdentityResult result)
{
    if (result == null)
    {
        return InternalServerError();
    }
    if (!result.Succeeded)
    {
        if (result.Errors != null)
        {
            foreach (string error in result.Errors)
            {
                ModelState.AddModelError("", error);
            }
        }
        if (ModelState.IsValid)
        {
            // No ModelState errors are available to send, 
            // so just return an empty BadRequest.
            return BadRequest();
        }
        return BadRequest(ModelState);
    }
    return null;
}

 

From the above, we can see we might get back a number of different responses, each with a slightly different content, which should assist the client in determining what went wrong.

Making a Flawed Request - Validation Errors

So, let's see some of the ways things can go wrong when making a simple POST request to the Register() method from our Console client application.

Add the following code to the console application. Note that we are intentionally making a flawed request. We will pass a valid password and a matching confirmation password, but we will pass an invalid email address. We know that Web Api will not like this, and should kick back a Model State Error as a result.

Flawed Request Code for the Console Client Application:
static void Main(string[] args)
{
    // This is not a valid email address, so the POST should fail:
    string email = "john";
    string password = "Password@123";
    string confirmPassword = "Password@123";
 
    HttpResponseMessage result = 
        Register(email, password, confirmPassword);
 
    if(result.IsSuccessStatusCode)
    {
        Console.WriteLine(
            "The new user {0} has been successfully added.", email);
    }
    else
    {
        Console.WriteLine(result.ReasonPhrase);
    }
    Console.Read();
}
 
 
public static HttpResponseMessage Register(
    string email, string password, string confirmPassword)
{
    //Attempt to register:
    using (var client = new HttpClient())
    {
        var response =
            client.PostAsJsonAsync("http://localhost:51137/api/Account/Register",
 
            // Pass in an anonymous object that maps to the expected 
            // RegisterUserBindingModel defined as the method parameter 
            // for the Register method on the API:
            new
            {
                Email = email,
                Password = password,
                ConfirmPassword = confirmPassword
            }).Result;
        return response;
    }
}

If we run our Web Api application, wait for it to spin up, and then run our console app, we see the following output:

Console output from the flawed request:
Bad Request

 

Well, that's not very helpful.

If we de-serialize the response content to a string, we see there is more information to be had. Update the Main() method as follows:

De-serialize the Response Content:
static void Main(string[] args)
{
    // This is not a valid email address, so the POST should fail:
    string email = "john";
    string password = "Password@123";
    string confirmPassword = "Password@123";
 
    HttpResponseMessage result = 
        Register(email, password, confirmPassword);
 
    if(result.IsSuccessStatusCode)
    {
        Console.WriteLine(
            "The new user {0} has been successfully added.", email);
    }
    else
    {
        string content = result.Content.ReadAsStringAsync().Result;
        Console.WriteLine(content);
    }
    Console.Read();
}

 

Now, if we run the Console application again, we see the following output:

Output from the Console Application with De-Serialized Response Content:
{"Message":"The request is invalid.","ModelState":{"":["Email 'john' is invalid."]}}

 

Now, what we see above is JSON. Clearly the JSON object contains a Message property and a ModelState property. But the ModelState property, itself another JSON object, contains an unnamed property, an array containing the error which occurred when validating the model.

Since a JSON object is essentially nothing but a set of key/value pairs, we would normally expect to be able to unroll a JSON object into a Dictionary<string, object>. However, the nameless property(ies) enumerated in the ModelState dictionary on the server side makes this challenging.

Unwrapping such an object using the Newtonsoft.Json library is doable, but slightly painful. Equally important, an error returned from our API may, or may not have a ModelState dictionary associated with it.

Another Flawed Request - More Validation Errors

Say we figured out that we need to provide a valid email address when we submit our request to the Register() method. Suppose instead, we are not paying attention and instead enter two slightly different passwords, and also forget that passwords have a minimum length.

Modify the code in the Main() method again as follows:

Flawed Request with Password Mismatch:
{
    "Message":"The request is invalid.",
    "ModelState": {
        "model.Password": [
            "The Password must be at least 6 characters long."],
        "model.ConfirmPassword": [
            "The password and confirmation password do not match."]
    }
}

 

In this case, it appears the items in the ModelState Dictionary are represented by valid key/value pairs, and the value for each key is an array.

Server Errors and Exceptions

We've seen a few examples of what can happen when the model we are passing with our POST request is invalid. But what happens if our Api is unavailable?

Let's pretend we finally managed to get our email and our passwords correct, but now the server is off-line.

Stop the Web Api application, and then re-run the Console application. Of course, after a reasonable server time-out, our client application throws an AggregateException.

What's an AggregateException? Well, it is what we get when an exception occurs during execution of an async method. If we pretend we don't know WHY our request failed, we would need to dig down into the InnerExceptions property of the AggregateException to find some useful information.

In the context of our rudimentary Console application, we will implement some top-level exception handling so that our Console can report the results of any exceptions like this to us.

Update the Main() method once again, as follows:

Add Exception Handling to the Main() Method of the Console Application:
static void Main(string[] args)
{
    // This is not a valid email address, so the POST should fail:
    string email = "john@example.com";
    string password = "Password@123";
    string confirmPassword = "Password@123";
 
    // Add a Try/Catch in case something goes wrong and the server throws:
    try
    {
        HttpResponseMessage result =
            Register(email, password, confirmPassword);
 
        if (result.IsSuccessStatusCode)
        {
            Console.WriteLine(
                "The new user {0} has been successfully added.", email);
        }
        else
        {
            string content = result.Content.ReadAsStringAsync().Result;
            Console.WriteLine(content);
        }
    }
    catch (AggregateException ex)
    {
        Console.WriteLine("One or more exceptions has occurred:");
        foreach (var exception in ex.InnerExceptions)
        {
            Console.WriteLine("  " + exception.Message);
        }
    }
    Console.Read();
}

 

If we run our console app now, while our Web Api application is offline, we get the following result:

Console Output with Exception Handling and Server Time-Out:
One or more exceptions has occurred:
  An error occurred while sending the request.

 

Here, we are informed that "An error occurred while sending the request" which at least tells us something, and averts the application crashing due to an unhandled AggregateException.

Unwrapping and Handling Errors and Exceptions in Web Api

We've seen a few different varieties of errors and exceptions which may arise when registering a user from our client application.

While outputting JSON from the response content is somewhat helpful, I doubt it's what we are looking for as Console output. What we need is a way to unwrap the various types of response content, and display useful console messages in a clean, concise format that is useful to the user.

While I was putting together a more in-depth, interactive console project for a future article, I implemented a custom exception, and a special method to handle these cases.

ApiException - a Custom Exception for Api Errors

Yeah, yeah, I know. Some of the cases above don't technically represent "Exceptions" by the hallowed definition of the term. In the case of a simple console application, however, a simple, exception-based system makes sense. Further, unwrapping all of our Api errors up behind a single abstraction makes it easy to demonstrate how to unwrap them.

Mileage may vary according to the specific needs of YOUR application. Obviously, GUI-based applications may extend or expand upon this approach, relying less on Try/Catch and throwing exceptions, and more upon the specifics of the GUI elements available.

Add a class named ApiException to the Console project, and add the following code:

ApiException - a Custom Exception
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
 
namespace ApiWithErrorsTest
{
    public class ApiException : Exception
    {
        public HttpResponseMessage Response { get; set; }
        public ApiException(HttpResponseMessage response)
        {
            this.Response = response;
        }
 
 
        public HttpStatusCode StatusCode
        {
            get
            {
                return this.Response.StatusCode;
            }
        }
 
 
        public IEnumerable<string> Errors
        {
            get
            {
                return this.Data.Values.Cast<string>().ToList();
            }
        }
    }
}

 

Unwrapping Error Responses and Model State Dictionaries

Next, let's add a method to our Program which accepts an HttpResponseMessage as a method argument, and returns an instance of ApiException. Add the following code the the Program class of the Console application:

Add the CreateApiException Method the to Program Class:
public static ApiException CreateApiException(HttpResponseMessage response)
{
    var httpErrorObject = response.Content.ReadAsStringAsync().Result;
 
    // Create an anonymous object to use as the template for deserialization:
    var anonymousErrorObject = 
        new { message = "", ModelState = new Dictionary<string, string[]>() };
 
    // Deserialize:
    var deserializedErrorObject = 
        JsonConvert.DeserializeAnonymousType(httpErrorObject, anonymousErrorObject);
 
    // Now wrap into an exception which best fullfills the needs of your application:
    var ex = new ApiException(response);
 
    // Sometimes, there may be Model Errors:
    if (deserializedErrorObject.ModelState != null)
    {
        var errors = 
            deserializedErrorObject.ModelState
                                    .Select(kvp => string.Join(". ", kvp.Value));
        for (int i = 0; i < errors.Count(); i++)
        {
            // Wrap the errors up into the base Exception.Data Dictionary:
            ex.Data.Add(i, errors.ElementAt(i));
        }
    }
    // Othertimes, there may not be Model Errors:
    else
    {
        var error = 
            JsonConvert.DeserializeObject<Dictionary<string, string>>(httpErrorObject);
        foreach (var kvp in error)
        {
            // Wrap the errors up into the base Exception.Data Dictionary:
            ex.Data.Add(kvp.Key, kvp.Value);
        }
    }
    return ex;
}

 

In the above, we get a sense for what goes into unwrapping an HttpResponseMessage which contains a mode state dictionary.

When the response content includes a property named ModeState, we unwind the ModelState dictionary using the magic of LINQ. We knit the string key together with the contents of the value array for each item present, and then add each item to the exception Data dictionary using an integer index for the key.

If no ModelState property is present in the response content, we simply unwrap the other errors present, and add them to the Data dictionary of the exception.

Error and Exception Handling in the Example Application

We've already added some minimal exception handling at the top level of our application. Namely, we have caught and handled AggregateExceptions which may be thrown by async calls to our api, which are not handled deeper in the call stack.

Now that we have added a custom exception, and a method for unwinding certain types error responses, let's add some additional exception handling, and see if we can do a little better, farther down.

Update the Register() method as follows:

Add Handle Errors in the Register() Method:
public static HttpResponseMessage Register(
    string email, string password, string confirmPassword)
{
    //Attempt to register:
    using (var client = new HttpClient())
    {
        var response =
            client.PostAsJsonAsync("http://localhost:51137/api/Account/Register",
  
            // Pass in an anonymous object that maps to the expected 
            // RegisterUserBindingModel defined as the method parameter 
            // for the Register method on the API:
            new
            {
                Email = email,
                Password = password,
                ConfirmPassword = confirmPassword
            }).Result;
 
        if(!response.IsSuccessStatusCode)
        {
            // Unwrap the response and throw as an Api Exception:
            var ex = CreateApiException(response);
            throw ex;
        }
        return response;
    }
}

 

You can see here, we are examining the HttpStatusCode associated with the response, and if it is anything other than successful, we call our CreateApiException() method, grab the new ApiException, and then throw.

In reality, for this simple console example we likely could have gotten by with creating a plain old System.Exception instead of a custom Exception implementation. However, for anything other than the simplest of cases, the ApiException will contain useful additional information.

Also, the fact that it is a custom exception allows us to catch ApiException and handle it specifically, as we will probably want our application to behave differently in response to an error condition in an Api response than we would other exceptions.

Now, all we need to do (for our super-simple example client, anyway) is handle ApiException specifically in our Main() method.

Catch ApiException in Main() Method

Now we want to be able to catch any flying ApiExceptions in Main(). Our Console application, shining example of architecture and complex design requirements that it is, pretty much only needs a single point of error handling to properly unwrap exceptions and write them out as console text!

Add the following code to Main() :

Handle ApiException in the Main() Method:
static void Main(string[] args)
{
    // This is not a valid email address, so the POST should fail:
    string email = "john@example.com";
    string password = "Password@123";
    string confirmPassword = "Password@123";
 
    // Add a Try/Cathc in case something goes wrong and the server throws:
    try
    {
        HttpResponseMessage result =
            Register(email, password, confirmPassword);
        if (result.IsSuccessStatusCode)
        {
            Console.WriteLine(
                "The new user {0} has been successfully added.", email);
        }
        else
        {
            string content = result.Content.ReadAsStringAsync().Result;
            Console.WriteLine(content);
        }
    }
    catch (AggregateException ex)
    {
        Console.WriteLine("One or more exceptions has occurred:");
        foreach (var exception in ex.InnerExceptions)
        {
            Console.WriteLine("  " + exception.Message);
        }
    }
    catch(ApiException apiEx)
    {
        var sb = new StringBuilder();
        sb.AppendLine("  An Error Occurred:");
        sb.AppendLine(string.Format("    Status Code: {0}", apiEx.StatusCode.ToString()));
        sb.AppendLine("    Errors:");
        foreach (var error in apiEx.Errors)
        {
            sb.AppendLine("      " + error);
        }
        // Write the error info to the console:
        Console.WriteLine(sb.ToString());
    }
    Console.Read();
}

 

All we are doing in the above is unwinding the ApiException and transforming the contents for the Data dictionary into console output (with some pretty hackey indentation).

Now let's see how it all works.

Running Through More Error Scenarios with Error and Exception Handling

Stepping all the way back to the beginning, lets see what happens now if we try to register a user with an invalid email address.

Change our registration values in Main() back to the following:

// This is not a valid email address, so the POST should fail:
string email = "john";
string password = "Password@123";
string confirmPassword = "Password@123";

 

Run the Web Api application once more. Once it has properly started, run the Console application with the modified registration values. The output to the console should look like this:

Register a User with Invalid Email Address:
An Error Occurred:
Status Code: BadRequest
Errors:
  Email 'john' is invalid.

 

Similarly, if we use a valid email address, but password values which are both too short, and also do not match, we get the following output:

Register a User with Invalid Password:
An Error Occurred:
Status Code: BadRequest
Errors:
  The Password must be at least 6 characters long.
  The password and confirmation password do not match.

 

Finally, let's see what happens if we attempt to register the same user more than once.

Change the registration values to the following:

Using Valid Registration Values:
string email = "john@example.com";
string password = "Password@123";
string confirmPassword = "Password@123";

 

Now, run the console application twice in a row. The first time, the console output should be:

Console Output from Successful User Registration:
The new user john@example.com has been successfully added.

 

The next time, however, an error result is returned from our Web Api:

Console Output from Duplicate User Registration:
An Error Occurred:
Status Code: BadRequest
Errors:
  Name john@example.com is already taken.. Email 'simon@example.com' is already taken.

 

Oh No You did NOT Use Exceptions to Deal with Api Errors!!

Oh, yes I did . . . at least, in this case. This is a simple, console-based application in which nearly every result needs to end up as text output. Also, I'm just a rebel like that, I guess. Sometimes.

The important thing to realize is how to get the information we need out of the JSON which makes up the response content, and that is not as straightforward as it may seem in this case. How different errors are dealt with will, as always, need to be addressed within terms best suited for your application.

In a good many cases, treating Api errors as exceptions, to me, has merit. Doing so most likely will rub some architecture purists the wrong way (many of the errors incoming in response content don't really meet the textbook definition of "exception"). That said, for less complex .NET-based Api Client applications, unwrapping the errors from the response content, and throwing as exceptions to be caught by an appropriate handler can save on a lot of duplicate code, and provides a known mechanism for handling problems.

In other cases, or for your own purposes, you may choose to re-work the code above to pull out what you need from the incoming error response, but otherwise deal with the errors without using exceptions. Register() (and whatever other methods you use to call into your Api) might, in the case of a simple console application, return strings, ready for output. In this case, you could side-step the exception issue.

Needless to say, a good bit of the time, you will likely by calling into your Web Api application not from a desktop .NET application, but instead from a web client, probably using Ajax or something.

That's a Long and Crazy Post about Dealing with Errors - Wtf?

Well, I am building out a more complex, interactive console-based application in order to demo some concepts in upcoming posts. One of the more irritating aspects of that process was figuring out a reasonable way to deal with the various issues that may arise, when all one has to work with is a command line interface to report output to the user.

This was part of that solution (ok, in the application I'm building, things are a little more complex, a little more organized, and there's more to it. But here we saw some of the basics).

But . . . Can't We Just do it Differently on the Server?

Well . . . YES!

In all likelihood, you just might tune up how and what you are pushing out to the client, depending upon the nature of your Web Api and the expected client use case. In this post, I went with the basic, default set-up (and really, we only looked at one method). But, depending upon how your Api will be used, you might very will handle errors and exceptions differently on the server side, which may impact how you handle things on the client side.

Additional Resources and Items of Interest

 

Posted on September 28 2014 08:42 PM by jatten     

Comments (0)

About the author

My name is John Atten, and my "handle" on many of my online accounts is xivSolutions. I am Fascinated by all things technology and software development. I work mostly with C#, JavaScript/Node, and databases of many flavors. Actively learning always. I dig web development. I am always looking for new information, and value your feedback (especially where I got something wrong!). You can email me at:

jatten at typecastexception dot com

Web Hosting by