Setting up mobile projects

Now we move back to the mobile side; in our mobile projects we are going to be setting up SignalR clients on both Android and iOS natively. We will also be creating a presenter layer to share the UI logic between both native platforms. Open up Xamarin Studio and create a new shared project called Chat.Common; inside this project add two empty folders called Model and Presenter.

We then want to create a single view iOS application, a general Android application and a shared project called Chat.ServiceAccess. Our project structure will look like this:

Setting up mobile projects

Creating the SignalRClient

We are going to start implementing a new class called SignalRClient. This will sit in the service access layer, the shared project called Chat.ServiceAccess. Create a new file called SignalRClient.cs, and implement the following:

public class SignalRClient
     {
         private readonly HubConnection _connection;
         private readonly IHubProxy _proxy;
         public event EventHandler<Tuple<string, string>> OnDataReceived;
         public SignalRClient()
         {
             _connection = new HubConnection("http://{IP Address}:{Port}/");
             _proxy = _connection.CreateHubProxy("ChatHub");
         }
 }  

Now let's look more closely. We have two readonly properties in which we only initialize once when the object is created, the hub connection which is set to the server URL, and the HubProxy which is created off the connection to the server.

Now let's add two functions for connecting and disconnecting to the ChatHub:

public async Task<bool> Connect(string accessToken)
         {
             try
             {
                 _connection.Headers.Add("Authorization",
                 string.Format("Bearer {0}", accessToken));
                 await _connection.Start();
                 _proxy.On<string, string>("displayMessage", (id, data) =>
                 {
                     if (OnDataReceived != null)
                     {
                         OnDataReceived(this, new Tuple<string,
                         string>(id, data));
                     }
                 });
                 return true;
             }
             catch (Exception e)
             {
                 Console.WriteLine(e);
             }
             return false;
         }
         public void Disconnect()
         {
             _connection.Stop();
             _connection.Dispose();
         }

The Connect function requires an access token which we add to the Headers dictionary of the HubConnection object.

Note

The access token is used as a Bearer token to authorize access to the ChatHub.

The function On called from the proxy takes in two parameters, the name of the function on the server we are listening for, and the action that will be performed every time this function is called on the Hub's connected clients. In this example, our proxy will fire this action whenever two strings are received from the server. The first string is an ID for the data passed in the second string (this could be a JSON list of connected clients or it could be a simple chat message). This data will then be passed a Tuple<string, string> object to the EventHandler.

Note

We can call On for multiple functions, and fire different actions for as many different functions being called on the Hub.

The Disconnect function simply closes the connection and disposes the HubConnection object. Finally, we add another function for invoking the Send function via the ChatHub  object on the server:

public async Task SendMessageToClient(string user, string message)
{
    await _proxy.Invoke("Send", new object[]
    {
        message,
        user
    });
}

When we invoke server functions, we use an array of objects, in order to match the parameters required on the server function.

Since the SignalRClient  will sit in a shared project, the same code will be used for each different platform, but the libraries referenced from the using statements will come from each platform project. Now let's have both the iOS and Android projects reference this shared project. We also want to add the Microsoft.AspNet.SignalR.Client  NuGet package for all the platform projects (iOS and Android).

Creating the SignalRClient

If you are trying to add the NuGet package for SignalR version 2.2.0 with Xamarin.iOS 1.0, the package will fail to add. If so, visit the following link and add the correct .dll files from the lib folder to each platform project's references: https://components.xamarin.com/auth?redirect_to=%2fdownload%2fsignalr.

Creating the SignalRClient

Tip

To add the references correctly, right-click the folder References for each project, click the .Net assembly tab, and click the Browse button to add the .dll files (Microsoft.AspNet.SignalR.Client, System.Net.Http.Extensions, and System.Net.Http.Primitives).

For each platform project, we also need to add the Json.Net package from NuGet, then right-click on the References, click the All tab, and select System.Net and System.Net.Http.

Creating the SignalRClient

Now that we have SignalR configured, let's move on to building the WebApiAccess layer.

Building the WebApiAccess layer

Our WebApiAccess object will be mapped to the AccountController on the server. Let's add in a new file called WebApiAccess.cs, and implement the LoginAsync function:

 
public class WebApiAccess
     {
         private string _baseAddress = "http://{IP Address}:{Port}/";
         public async Task<bool> LoginAsync(string name, string password,
         CancellationToken? cancellationToken = null)
         {
             var httpMessage = new HttpRequestMessage(HttpMethod.Post,
             new Uri(_baseAddress + "api/Account/Login"))
             {
                 Content = new StringContent(string.Format
                 ("Username={0}&Password={1}", name, password), Encoding.UTF8,
                  "application/x-www-form-urlencoded"),
             };
             var client = new HttpClient();
             var response = await client.SendAsync(httpMessage,
             cancellationToken ?? new CancellationToken(false));
             switch (response.StatusCode)
             {
                 case HttpStatusCode.NotFound:
                     throw new Exception(string.Empty);
             }
             var responseContent = await response.Content.ReadAsStringAsync();
             var loginSuccess = false;
             bool.TryParse(responseContent, out loginSuccess);
             return loginSuccess;
         }
     }  

The _baseAddress property will be the same as the SignalRHubConnection address; this is our server link. In our LoginAsync function, we start with creating a new HttpRequestMessage set as a HttpMethod.Post. We also set the content to a new StringContent object, which takes the username and password. This message is used in a new HttpClient to send to the server, and the response received is read as a string and parsed in to a new bool object to determine the success of the login.

Let's go ahead and implement the rest of the access layer:

public async Task<bool> RegisterAsync(string name, string password, CancellationToken? cancellationToken = null)
         {
             var httpMessage = new HttpRequestMessage(HttpMethod.Post,
             new Uri(_baseAddress + "api/Account/Register"))
             {
                 Content = new StringContent(string.Format
                 ("Username={0}&Password={1}", name, password), Encoding.UTF8,
                 "application/x-www-form-urlencoded"),
             };
             var client = new HttpClient();
             var response = await client.SendAsync(httpMessage,
             cancellationToken ?? new CancellationToken(false));
             return response.StatusCode == HttpStatusCode.OK;
         } 

The Register function is very much the same, but we only check that the response status code is a 200(OK) response; if so, then we have registered successfully.

public async Task<TokenContract> GetTokenAsync(string name, string password, CancellationToken? cancellationToken = null)
         {
             var httpMessage = new HttpRequestMessage(HttpMethod.Post,
             new Uri(_baseAddress + "token"))
             {
                 Content = new StringContent(string.Format
                 ("Username={0}&Password={1}&grant_type=password", name,
                 password), Encoding.UTF8, "application/x-www-form-urlencoded"),
             };
             var client = new HttpClient();
             var response = await client.SendAsync(httpMessage,
             cancellationToken ?? new CancellationToken(false));
             switch (response.StatusCode)
             {
                 case HttpStatusCode.NotFound:
                     throw new Exception(string.Empty);
             }
             var tokenJson = await response.Content.ReadAsStringAsync();
             return JsonConvert.DeserializeObject<TokenContract>(tokenJson);
            }

The GetTokenAsync function is responsible for retrieving the access token from the OAuth endpoint (/token). The JSON response will be of the type TokenContract; let's go ahead and add this object into the Chat.ServiceAccess project. Create a new folder called Contracts inside the Web folder, add in a new file called TokenContract.cs, and implement the following:

public class TokenContract
     {
         [JsonProperty("access_token")]
         public string AccessToken { get; set; }
         [JsonProperty("token_type")]
         public string TokenType { get; set; }
         [JsonProperty("expires_in")]
         public int ExpiresIn { get; set; }
         [JsonProperty("userName")]
         public string Username { get; set; }
         [JsonProperty(".issued")]
         public string IssuedAt { get; set; }
         [JsonProperty(".expires")]
         public string ExpiresAt { get; set; }
     }  

Notice the JsonProperty attribute?

We can map properties from the JSON objects into other named variables for the class.

Now for the final Web API function, GetAllConnectedUsersAsync. This function will be called when a user logs in for the first time. We need to have both an API call and a real-time update with the SignalRClient to keep track of the current connected clients because when a new user logs in, the server will call displayMessage on all other clients. Even if we were to call displayMessage on Clients.All (this is a reference to all the connected clients on any SignalR Hub), the newly connected client won't appear in the Clients list as there is a minor delay with the connection.

Tip

This minor delay is something we cannot control; only sometimes would the newly connected client receives the updated list through the HubProxy event. So, to make things more reliable, we add this update through the API access layer.

Let's add the final Web API function for GetAllConnectedUsersAsync. This function will deserialized an IEnumerable of strings which represents the list of connected clients from the ChatHub:

public async Task<IEnumerable<string>>
 GetAllConnectedUsersAsync(CancellationToken? cancellationToken = null)
         {
             var httpMessage = new HttpRequestMessage(HttpMethod.Get,
             new Uri(_baseAddress + "api/Account/GetAllConnectedUsers"));
             var client = new HttpClient();
             var response = await client.SendAsync(httpMessage,
             cancellationToken ?? new CancellationToken(false));
             switch (response.StatusCode)
             {
                 case HttpStatusCode.NotFound:
                     throw new Exception(string.Empty);
             }
             var responseContent = await response.Content.ReadAsStringAsync();
             return JsonConvert.DeserializeObject<IEnumerable<string>>
(responseContent);
         }  

Great! We now have our Web API access layer. Our next step is to start building the application state and navigation service required for each presenter.

Application state

In MVP, every presenter must include the current application state. When we cross between different screens, the persistent state of application data is kept alive throughout the entire life of the application (this includes search results, downloaded JSON objects, and so on.

Tip

In some most MVP applications, the application state will include a service for saving and loading this persistent data between different sessions. For an extra learning activity, try implementing a new service called IApplicationStateService. This will be responsible for saving and loading the ApplicationState object locally to your device.

Excellent! Now let's add another file called ApplicationState.cs, and implement the following:

public class ApplicationState
     {
         #region Public Properties
         public string AccessToken { get; set; }
         public string Username { get; set; }
         #endregion
     } 

Nothing much to it, right?

We only ever want one instance of this object throughout the entire life of the application, so we will build upon the persistent data to be kept alive between each screen.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.205.235