Introduction
For Support Desk, I used Visual Studio 2015 ASP.NET Web API to host a help desk server. It will allow authenticated users to create, edit, and search support tickets, customers, and solutions. The client is in WPF, but any client using HTTP can access the server through Web API allowing the customer to easily customize their client.
Day One and Two
For testing purposes, we created two different solutions on days one and two for the client and server. Open two instances of Visual Studio, with the server solution from day one in one and the client solution from day two in the other. If you didn't follow along through days one and two, you can do it now. Or you can find the source code on CodePlex with the links below.
You can find the day one article here and download the completed project from here on CodePlex.
Download the completed day two source code here on CodePlex. The day two article can be found here.
Day Three
Our support ticket server is going to be available publicly, so we are going to have to add some authentication. So we will register a user, and use those credentials to retrieve an Access Token.
NuGet Packages
The methods we need for HttpClient
are in the Microsoft.AspNet.WebApi.Client
NuGet package. In Solution Explorer, right click on the project and select "Manage NuGet Packages" then install the Microsoft.AspNet.WebApi.Client
and EntityFramework
NuGet packages.
Register Binding Model
Let's hop back over to the server solution and compile and run. Debug>>Start Debugging. Let the project home page load, then follow the API link across the top. On day one, we created a Web API project and made some small changes to the ValuesController
.
This page is the auto generated documentation for the API. A customer that wants to get our "Hello World!
" message could follow the link GET api/Values and see a simple string
is returned.
We want to register a user, so follow the POST api/Account/Register
link. As you can see, we were given parameters for the RegisterBindingModel
that will need to Post to api/Account/Register
. It consists of three strings
, Email
, Password
, and ConfirmPassword
. There is no UserName
, instead use Email
for the account.
Now that we know what kind of object the server's expecting us to post, we'll represent it in the client project with a custom class. You can close your browser if it's still open, stop the server project, and switch back to the client. In Solution Explorer, right click on the project>>Add>>New Folder and name it Models. Now right click on the Models folder>>Add>>Class and name the file PublicModels. Visual Studio will add a file called PublicModels.cs into our Models folder with an empty class PublicModels
. Rename PublicModels
class to RegisterBindingModel
and add properties to represent the parameters as follows.
PublicModels.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SupportDeskClient.Models
{
public class RegisterBindingModel
{
public string ConfirmPassword { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public RegisterBindingModel()
{ }
}
}
Post RegisterBindingModel
We'll create a login window to register users later, but to get this first user registered, let's just add another method to the constructor of our MainWindow
. Modify MainWindow.cs as follows and don't forget the using
statement to the Models folder.
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Windows;
using SDCFollowThrough.Models;
namespace SDCFollowThrough
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
PostRegisterAsync().Wait();
}
public async Task PostRegisterAsync()
{
using (var client = new HttpClient())
{
var RegisterData = new RegisterBindingModel();
RegisterData.Email = "rbass@displaysoft.com";
RegisterData.Password = "Rb_123456";
RegisterData.ConfirmPassword = "Rb_123456";
client.BaseAddress = new Uri("http://localhost:57975/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add
(new MediaTypeWithQualityHeaderValue("application/json"));
var response = client.PostAsJsonAsync<RegisterBindingModel>
("api/Account/Register", RegisterData).Result;
if (response.IsSuccessStatusCode)
{
var GetString = await response.Content.ReadAsStringAsync();
var StringResponce = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(GetString);
if (StringResponce != null)
{
MessageBox.Show(StringResponce);
}
}
}
}
async Task GetRunAsync()
{
using (var client = new HttpClient())
{
string StringResponce;
client.BaseAddress = new Uri("http://localhost:57975/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add
(new MediaTypeWithQualityHeaderValue("application/json"));
var response = client.GetAsync("api/Values/").Result;
if (response.IsSuccessStatusCode)
{
var GetString = await response.Content.ReadAsStringAsync();
StringResponce = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(GetString);
if (StringResponce != null)
{
MessageBox.Show(StringResponce);
}
}
}
}
}
}
The using
statement lets the compiler look for our RegisterBindingModel
class anywhere in the Models folder. Next, we commented out our call to GetRunAsync().Wait()
and added a call to our new PostRegisterAsync()
and again Wait()
because communication with the sever is asynchronous. Followed by our new method PostRegisterAsync
and again like our GetRunAsync
, everything is wrapped in a using
statement to ensure that a connection is closed when the method finishes, regardless of the result of our communication with the server.
After creating a new RegisterBindingModel
object RegisterData
, I have hard coded an email and a password. The next few lines should be familiar to you from the GetRunAsync
explanation from day one. Then responce
is set to the Result of client.PostAsJsonAsync<T>
, in this case T
is our RegisterBindingModel
class RegisterData
that we send to "api/Account/Register
". Then if successful, we will display our string
response in a MessageBox
.
Support Desk Server
SaveAll
on the client and open our server solution back up. We have two changes to make, change our Register response to type string
, and setup your connection string which is beyond the scope of the article. You can read more on connection string here. I have SQL's LocalDB installed. If I open the projects' Web.config file, this is what my connection string looks like:
<connectionStrings>
<add name="DefaultConnection"
connectionString="Data Source=(LocalDb)\V11.0;
AttachDbFilename=|DataDirectory|\aspnet-HDSFallowAlong-2015121903515.mdf;
Initial Catalog=aspnet-HDSFallowAlong-2015121903518;Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
In the Controllers Folder, open the AccountController
and find the Register
method and change it as follows:
[AllowAnonymous]
[Route("Register")]
public async Task<string> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return "Fail";
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return "Fail";
}
return "Register succeeded.";
}
Here, I've changed the return type to Task<string>
and now return either "Fail
" or "Register
succeeded
." SaveAll and F5>>Start Debugging. Now go back to the client project. We hard coded the username and password that we are going to register, so "Register succeeded
." will only be returned once. Start the client F5>>Start Debugging.
Logging In
Stop the client program and add the following class in the Models folder.
public class MyToken
{
public string access_token;
}
Now add using System.Collections.Generic
to the MainWindow
and the following method.
public async Task PostLogin()
{
using (var client = new HttpClient())
{
var RegisterData = new RegisterBindingModel();
RegisterData.Email = "rbass@displaysoft.com";
RegisterData.Password = "Rb_6521189";
RegisterData.ConfirmPassword = "Rb_6521189";
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", RegisterData.Email),
new KeyValuePair<string, string>("password", RegisterData.Password),
});
client.BaseAddress = new Uri("http://localhost:57975/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add
(new MediaTypeWithQualityHeaderValue("application/json"));
var response = client.PostAsync("/token", formContent).Result;
if (response.IsSuccessStatusCode)
{
var GetString = await response.Content.ReadAsStringAsync();
var StringResponce = Newtonsoft.Json.JsonConvert.DeserializeObject<MyToken>(GetString);
if (StringResponce != null)
{
MessageBox().Show(StringResponce.access_token);
}
}
}
}
The added reference to System.Collections.Generic has the definition for KeyValuePair
and like before, we wrap all asynchronous calls in using
statements, then setup RegisterData
the same as in PostRegister
. Setting up FormUrlEncodedContent
in this way is the only successful way I've been able to retrieve an access token. If you have been following along up to this point, the rest of this method should be self explanatory.
Change the constructor of MainWindow
as follows:
public MainWindow()
{
InitializeComponent();
PostLogin().Wait();
}
Start the server, then run the client and we should get something like below: