Create an abstraction layer between our client UWP app and our hosted app service to be consumed in our ViewModels.
- [Instructor] Now that our most simple view is functional, it's time to think about the user-data interactions. Ultimately, we want our app consumers to be able to create and view projects, create tasks for projects, assign users to projects, and more. In order for all of these scenarios to work, we need to interact with the app service, and specifically, the iMobile Service Table. Let's work on defining the client API interface that will provide all of the functionality we will need for the UWP app in terms of working with our app service. We'll implement the first method together, and then I encourage you to implement the rest as a challenge.
The source will be available as an exercise file in case you get stuck. But a lot of the code will be similar. Before we can define our client API interface, we're going to need knowledge of the types that we will be operating on. In other words, we need to create our models. In the Models folder, create a new model for each of the classes that we store in our tables. So, right-click on Models. Add Class. The first model I will create is going to be called Project.cs. I'll add another class and call it TaskItem.cs.
I'll add two more, User.cs and Assignment.cs. These are going to look very similar to the objects we had on the server. And that's because they are. The only difference is that these will not inherit from the entity database. These are going to be POCO objects, or Plain Old CLR Objects, meaning the types we use will not depend on them being run in a Windows application. Let's start with project as the most complicated type. First, I'll make it public. And then I'll think about the properties that I'll need.
The first type I'll add, again by typing prop, is a string that represents the ID of the project. We don't have this property on our server code, because it's handled automatically with the entity database class. But each model on the UWP app will need to have an ID in order to operate nicely with the iMobile service tables. The next property will be a string as well, which will just be the property name. Next, we'll add the owner ID on line 13. And finally, on line 14, we'll add public User Owner.
These properties match very closely to the objects in the server. After adding the Owner object, you may see that there's inconsistent accessibility. And that's only because the User class isn't public. On line nine in the User class, you can make it public. And in fact, do that for the rest of the objects as well, for TaskItem and for Assignment. If I return to my project, everything works. Now, the only two remaining properties are actually going to be the collections, the collection of tasks and the collection of collaborators that can be assigned to a project.
Again, we'll add two more: iCollection TaskItem Tasks and iCollection User Collaborators. These are all the properties we need. The only remaining code we'll need for our object is the constructor. On line 11, you can add the constructor, public Project. And all we'll do is, we'll create an empty list of tasks and an empty list of collaborators that we'll populate later.
On line 13, Tasks equals new List TaskItem. And on line 14, Collaborators equals new List of type User. That's it for our project object. The remaining objects are pretty simple. For a TaskItem, on line 11 we'll add the first property. Again, it's going to be a string that represents the ID. On line 12, we'll add the string that is the ProjectId in which the task is assigned. On line 13, we'll add the name of the task.
And on line 14, we'll add the Boolean property that determines whether or not the task is complete. Next we'll do the user object. Again, this is just going to be a list of properties. On line 11, we'll have the Id. The Id is very important. On line 12, we'll have the ProviderId. On line 13, we'll add the UserName. And on line 14, we'll add the Email.
All that's left is the assignment, which will just be three properties. On line 11, again, the Id. There's a pattern here. On line 12, we'll have the UserId for the assignment. And on line 13, we'll have the ProjectId. Now that all of our objects are defined, we can start writing some of the business logic. I think it's easy to first define the interface that will represent our business logic and then implement the business logic around that interface. In the Comments folder, let's add a new class that will be common to the rest of our view models.
I'm going to call this iClientAPI to represent the interface for the client API class. Now, when defining our interface, we need to think about all of the operations that can happen in our application. The first thing I'm going to do is actually change this from a class to an interface. And I'm going to make it public on line nine. The next thing I need to do is think about all the tables that I'll be accessing data from, whether it's reading or writing data. And there's four. These map directly to our SQL database tables. The first one will be IMobileServiceTable.
And we'll have to add a reference to the missing Microsoft Windows Azure Mobile Services reference in this interface. Assignment. And we'll have to add the reference to our Models folder. And I'll call this AssignmentsTable. To save time, I can just copy and paste this three more times and replace the types and the names. The next one will be the users table, then the projects table, and then the task items table.
Let me rename these as well. UsersTable, ProjectsTable, and TaskItemsTable. So these are the tables that we're going to read data from, write data to, and update data. The next thing I need to think about is all of the different methods that I'm going to need in my UI. The first view that I want to display is the list of projects that the user owns when they sign in. So that will be the first method. On line 18, I'll define this method's signature.
As a task, that returns a collection of projects. Task, ICollection, Project, GetProjectsByOwnerAsync, string, ownerId will be the parameter. I can add a comment to say, "Returns all projects that the user owns." The next method signature will be almost identical, but, instead, it will return all of the projects that the user collaborates on rather than the projects they own. I'll copy and paste this signature and rename it to GetProjectsByContributorAsync.
Again, I'll just change the string to contributorId, which will still represent a user. I'll add the comment on line 20, "Returns all projects that the user works on "but doesn't own." Next, I need to think about all of the read and write operations that the user's going to want to do. First, they're going to want to be able to create a new project. So, again, Task AddNewProjectAsync. And the parameter will just be the new project. Next, they'll want to be able to add a task item to that project.
The next one will be Task AddNewTaskItemAsync on line 24 with a new TaskItem. The next thing that the user will probably want to be able to do is mark a task as complete or not. We can define this method signature on line 25 with Task CompleteTaskItemByIdAsync. And we'll pass in the task item's ID as well as whether it's complete or not complete. So, a string of the ID and the Boolean of whether it's complete or not.
Next, on line 26, they'll want to be able to delete a project. Task, DeleteProjectByIdAsync, string id. And finally, they'll want to be able to add a user to a project. I'll define this method signature on line 27 as Task User AddCollaboratorToProjectAsync. And this will have the string ID of the project and string email for the user. The reason I have this return a user is because we're passing in the user email.
So if the user exists in our database, we can get the user information to return it to the app. Otherwise we'll create a new user and just return the email, 'cause we won't have the username. I think this all we're going to need to implement our view models for our UI. So let's go ahead and create the client API class that will implement this interface. Again, and common, add a new class and call it ClientAPI.cs. On line nine, make the class public and have it implement your new interface, IClientAPI.
Now, Visual Studio makes it really easy to automatically implement the methods of the interface. You can use the light bulb to implement the interface. And each of those different objects will be created for you. Now we need to fill in all of the blanks. Let's start by saving. The first thing we'll want to do is get rid of these throw new NotImplementedExceptions for all of our tables. We will also not need a setter for these tables. On line 13, delete the set equals throw new NotImplementedException and get equals throw new NotImplementedException by returning that method.
Do this for all of your tables. What we can do instead is we can have a private table for each of these objects that we can return and modify in the code. So, on line 18, add private IMobileServiceTable Assignment, lowercase assignmentsTable, and do this for each of the four tables on line 19, 20, and 21. These will be the private versions of the table that we'll actually refer to in this class.
And then, when you call get on any one of the public tables, we'll return these tables. So let me call the projects tables lowercase projectsTable, assignments table lowercase taskItemsTable, and the users table lowercase usersTable. Now, in each of my public iMobile service tables, in the getters, we'll return the private iMobile Service Tables. And in the rest of the code, we'll do our operations on the private iMobile service tables. So, on line 13, in the getter, we're going to return lowercase assignmentsTable.
We're going to repeat this for each of our other three public mobile service tables. On line 14, return lowercase projectsTable. On line 15, return lowercase taskItemsTable. And the same on line 16. Return lowercase usersTable. Now, the only other object we're going to need to help us is our mobile service client. So, on line 23, I'll add my private MobileServiceClient client.
And this will be our way to access the app service. Now what we need is the constructor of the client API. On line 25, I'll create the constructor, public ClientAPI with a single parameter, string url. And that will represent our mobile service client. We'll then create a new mobile service client, client equals new MobileServiceClient url. Finally, we'll get all of our tables. Our assignments table, we can get by setting it equal to client.GetTable of type Assignment.
And we can do the same for the other three. On line 29, projectsTable equals client.GetTable Project. On line 30, TaskItemsTable equals client.GetTable TaskItem. Should be lowercase taskItemsTable. On line 31, usersTable equals client.GetTableUser.
Now we can use each of these private iMobile service tables to do read and write and delete operations to our SQL database for each of those tables. And this is what we'll need in our methods. Let's do one of our methods together. And then I'll let you try to implement the rest. This code will be available to you in the exercise file. Let's start with GetProjectsByOwnerAsync, as this is the method that's going to return the list of projects that's displayed the first time the user signs into the app. In GetProjectsByOwnerAsync, the first thing I want to do is find all of the projects in the projects table where the ownerId parameter matches the owner ID of the user for that project.
I can do that on line 66 by using a LINQ query. I'll type var projects equals await projectsTable.Where x to x.OwnerId equals equals ownerId, which is the string parameter, essentially my search parameter. And then I'm going to do ordering directly in the LINQ query so that the projects are sorted by project name in the UI. I'll order by x to x.Name, and then I'll set it to a collection.
The only other thing that I have to do now is make this method asynchronous, because I'm calling an await operator on an asynchronous method, Where. Now what I can do is populate my project. Now, remember, in the projects table in the SQL database, I couldn't store my collection of tasks and collection of collaborators. So, when I get this list of projects returned, I'm only going to have the properties that are just a single value. What I can do is I can create a helper method so that I can populate my project with the list of tasks and the list of collaborators.
On line 69, I'm going to write one more method that will also be used in GetProjectByContributorAsync. I'm going to call this, private async Task PopulateProjectsAsync, because it's going to populate the projects with the list of tasks and collaborators that couldn't come directly from the table. First I'll set my owner value for my project. Remember, the owner is of type User, which has multiple properties. So it cannot be stored directly in the SQL table. I'll set p.Owner equals await usersTable.LookupAsync p.OwnerId.
So, I'm using the unique ID of that owner to find that user and set that user to my project owner. Now what I need to do is find each of the collaborators and each of the tasks to add them to my project. Let's start with collaborators. Foreach, on line 72, var, a, which is going to be an assignment, in await, assignmentsTable, and again I'm going to use a LINQ query, .Where, x to x.ProjectId equals equals p.Id.
And what this is doing is, it's finding all of the assignments in which the project that I'm searching for is matching an assignment. So then I can find all of the users in those assignments that are collaborators on my project. The only thing I need to do now is make this a collection. Now, for each of these assignments, I should get a user returned. So what I can do is add that user to my collaborators. On line 74, p.Collaborators.Add await usersTable again, dot LookupAsync, a, which is the assignment, .userId, which represents the ID of the user of that assignment.
Now, I can do something similar for tasks, but it's a little bit easier, because I just have a tasks table directly. I don't have to go through my assignments table. On line 76, foreach var t in await taskItemsTable.Where x to x.ProjectId equals equals p.Id. And then what I'm going to do again is order these by the name so that they're returned in the right order. .OrderBy x to x.Name, and then to a collection.
And now I can add each one of these tasks to my p.Tasks. Now all I need to do is call this method for each project in my list of projects so that the projects are populated with all of the tasks and all of the collaborators. Foreach, on line 67, var p in projects, await PopulateProjectsAsync for that specific project. Finally, on line 71, I'll return the projects. Now, when I call GetProjectsByOwnerAsync, the first thing it's going to do is get the list of projects directly from the SQL database projects table, which will not have any of the user information, list of users, or list of collaborators, because those contain multiple properties.
I will then populate each of those projects with that information. So I'll find the owner, all of the collaborators, and all of the tasks, and add it to that project before I return the list of projects. Now, this is probably the hardest method to implement in the client API. But you're going to use a similar collection of LINQ queries in each of these other methods. Go ahead and give it a shot, and if you get stuck, refer to the file in Exercise Files. Before you continue with the implementation of the client API class, I just want to point out one small mistake I made in the client API interface.
Each of these mobile service tables should not have a setter, only a getter. In lines 13, 14, 15, and 16 of iClientAPI.cs, delete the set part of those properties. After that, everything should be ready to go.
- Working with .NET for UWP app development
- Establishing application architecture
- Configuring Azure services
- Configuring the mobile app service backend
- Testing and publishing the service backend
- Using Facebook authentication
- Using XAML
- Client server abstraction API
- Using the UWP community toolkit
- Styling app views
- Creating adaptive views
- Testing an app for multiple user accounts
- Publishing to the Windows Store
- Sideloading app packages