In the last part (now really the last part) of this series, we added all the required classes and interfaces we need to wire anything together:
- A bootstrapper to initialize our system
- All required interfaces for data-access and services we want to implement
- A base class for our view-models
- A view-model-locater to locate available view-models we can use for data-binding
This part will cover all the “wiring” that is necessary to make the whole thing work.
Just to recap the structure, here is the current dependency-graph of our portable class implementation:
Wiring It All Together
Doing It the TDD (Test Driven Development) Way
We have created two test projects, one for Windows Phone 8 and for Windows 8.
Let’s start to implement the Data-Access-Layer, and write some tests for it .
Navigate to our WP8 unit test project, right click the project and add a new class. Name it “DataAccessLayerTests
” or anything you prefer.
To have everything available, we need to implement the tests we need to add additional using
statements:
using Microsoft.VisualStudio.TestTools.UnitTesting
=> everything we need to create unit tests using MVVMWindowsPhone.Core.Portable.DAL
=> Our portable core DAL implementation we want to test here - And a
using
statement for Moq => “Using Moq
”
Every class that represents a series of unit tests, needs the [TestClass]
attribute to be attached.
Test methods need the [TestMethod]
attribute attached to be recognized as such.
TIP: If you did not follow the other parts, please see the link to the GitHub repository at the end of this post.
We have everything in place now to test our repositories and to mockup the objects we want to run tests against.
TIP: If you work with solutions that contain multiple projects, and you want to avoid scrolling to focus on a specific project, right click that project in Solution Explorer and choose “Scope to this”. You can navigate to the complete solution tree again, by pressing the blue back-button. And you can use the forward button to return to your scoped view again.
Testing Our IUnitOfWork and IRepository
To be able to test our implementations, we need at first to do some additional work.
We need to add a fake context (database driver), to test our IUnitOfWork
implementation.
Add a new folder to our WP8 unit test project and name it fakes. Right click the folder and add a new interface. Name the file “IFakeDBContext
”.
<!--CRLF--><!--CRLF--><!--CRLF--><!--CRLF--><!--CRLF--><!--CRLF-->
<!--CRLF--><!--CRLF--><!--CRLF--><!--CRLF--><!--CRLF--><!--CRLF--><!--CRLF-->
Now simply copy all the method definitions from our IRepository
class and insert them into our IFakeDBContext
, and modify them a bit:
using MVVMWindowsPhone.Core.Portable.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace UnitTesting.Fakes
{
public interface IFakeDBContext<T>
{
IEnumerable<T> FakeTable {get;set;}
IQueryable<T> GetAllEntries();
IQueryable<T> GetFilteredEntries(Expression<Func<T, bool>> filter);
User DeleteEntry(T entry);
User UpdateEntry(T entry, T updateValue);
User AddEntry(T Entry);
}
}
Now, we need to implement the interface IFakeDBContext
. Add a new class to the Fakes folder and name it FakeDBContext
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MVVMWindowsPhone.Core.Portable.Model;
namespace UnitTesting.Fakes
{
public class FakeDBContext : IFakeDBContext<User>
{
IEnumerable<User> fakeTable;
public virtual IEnumerable<User> FakeTable
{
get
{
return fakeTable;
}
set
{
this.fakeTable = value;
}
}
public FakeDBContext(IEnumerable<User> fakeTable)
{
this.fakeTable = fakeTable;
}
public IQueryable<User> GetAllEntries()
{
return this.fakeTable.AsQueryable<User>();
}
public IQueryable<User> GetFilteredEntries
(System.Linq.Expressions.Expression<Func<User, bool>> filter)
{
return fakeTable.AsQueryable<User>().Where(filter);
}
public User DeleteEntry(User entry)
{
if(this.fakeTable.Contains<User>(entry))
{
var list = this.fakeTable.ToList<User>();
list.Remove(entry);
this.fakeTable = list;
return entry;
}
else
{
return null;
}
}
public User UpdateEntry(User entry, User updateValue)
{
if (this.fakeTable.Contains<User>(entry))
{
var list = this.fakeTable.ToList<User>();
list[list.IndexOf(entry)] = updateValue;
this.fakeTable = list;
return updateValue;
}
else
{
return null;
}
}
public User AddEntry(User Entry)
{
if(!this.fakeTable.Contains<User>(Entry))
{
var list = this.fakeTable.ToList<User>();
list.Add(Entry);
this.fakeTable = list;
return Entry;
}
else
{
return null;
}
}
}
}
Implementing the Unit Tests
Now that the FakeDBContext
was implemented, we are ready to create our tests. These tests have only sample character and are not considered to be perfect unit tests. Select the UnitTesting
project and add a new class. Name the class “DataAccessLayerTests
” or whatever you like and add the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVVMWindowsPhone.Core.Portable.DAL;
using UnitTesting.Fakes;
using MVVMWindowsPhone.Core.Portable.Model;
using Microsoft.Phone.Testing;
namespace UnitTesting
{
[TestClass]
public class DataAccessLayerTests
{
List<User> users;
IRepository<User,FakeDBContext> testRepo;
[TestInitialize]
public void InitializeTest()
{
users = new List<User>();
users.Add(new User(){ Image="http://userimages.com/image1.png",
Url="http://user1.com",UserName="TestUser1"});
users.Add(new User() { Image = "http://userimages.com/image2.png",
Url = "http://user2.com", UserName = "TestUser1" });
users.Add(new User() { Image = "http://userimages.com/image3.png",
Url = "http://user3.com", UserName = "TestUser2" });
users.Add(new User() { Image = "http://userimages.com/image4.png",
Url = "http://user4.com", UserName = "TestUser3" });
users.Add(new User() { Image = "http://userimages.com/image5.png",
Url = "http://user5.com", UserName = "TestUser4" });
users.Add(new User() { Image = "http://userimages.com/image6.png",
Url = "http://user6.com", UserName = "TestUser5" });
users.Add(new User() { Image = "http://userimages.com/image7.png",
Url = "http://user7.com", UserName = "TestUser6" });
users.Add(new User() { Image = "http://userimages.com/image8.png",
Url = "http://user8.com", UserName = "TestUser7" });
users.Add(new User() { Image = "http://userimages.com/image9.png",
Url = "http://user9.com", UserName = "TestUser8" });
users.Add(new User() { Image = "http://userimages.com/image10.png",
Url = "http://user10.com", UserName = "TestUser9" });
users.Add(new User() { Image = "http://userimages.com/image11.png",
Url = "http://user11.com", UserName = "TestUser10" });
users.Add(new User() { Image = "http://userimages.com/image12.png",
Url = "http://user12.com", UserName = "TestUser11" });
users.Add(new User() { Image = "http://userimages.com/image13.png",
Url = "http://user13.com", UserName = "TestUser12" });
users.Add(new User() { Image = "http://userimages.com/image14.png",
Url = "http://user14.com", UserName = "TestUser13" });
users.Add(new User() { Image = "http://userimages.com/image15.png",
Url = "http://user15.com", UserName = "TestUser14" });
users.Add(new User() { Image = "http://userimages.com/image16.png",
Url = "http://user16.com", UserName = "TestUser15" });
users.Add(new User() { Image = "http://userimages.com/image17.png",
Url = "http://user17.com", UserName = "TestUser16" });
this.testRepo = new FakeUserRepository();
var fakeUnitOfWork = new FakeUnitOfWork();
fakeUnitOfWork.SetContext(new FakeDBContext(users));
this.testRepo.Driver = fakeUnitOfWork;
}
[TestMethod]
public void GetAllUsersAndCountTest()
{
var count = this.testRepo.GetAllEntries().Count();
Assert.AreEqual<int>(count,17);
}
[TestMethod]
public void AddUserAndCountTest()
{
this.testRepo.AddEntry(new User(){ Image="Image",
Url="some url",UserName="Some UserName"});
var count = testRepo.GetAllEntries().Count();
Assert.AreEqual<int>(count,18);
}
[TestMethod]
public void UserFilterTest()
{
var filteredUsers = testRepo.GetFilteredEntries
(user=>user.UserName.Equals("TestUser1") ||
user.UserName.Equals("TestUser2")).ToList<User>();
Assert.AreEqual<int>(filteredUsers.Count(),3);
}
[TestMethod]
[Tag("DeleteOnly")]
public void UserDeleteTest()
{
var userToRemove = testRepo.GetFilteredEntries
(user => user.UserName.Contains("TestUser1")).First();
var deletedUser = testRepo.DeleteEntry(userToRemove);
Assert.AreEqual<int>(testRepo.Driver.Context.FakeTable.Count(), 16);
}
[TestMethod]
public void UpdateUserTest()
{
var userToUpdate = testRepo.GetFilteredEntries
(user => user.UserName.Contains("TestUser1")).First();
var updatedEntry = new User(){UserName=userToUpdate.UserName,
Image="changed",Url = userToUpdate.Url};
testRepo.Driver.Context.UpdateEntry(userToUpdate,updatedEntry);
var updatedUser = testRepo.GetFilteredEntries
(user => user.UserName.Contains("TestUser1")).First();
Assert.AreEqual<string>(updatedUser.Image, "changed");
}
}
}
We have 5 unit test methods:
GetAllUsersAndCountTest
=> Check if we have the added amount of users available AddUserAndCountTest
=> After we add a user, do we have the right number of users? UserFilterTest
=> Filter users by a certain criteria and check if the users meet the criteria UserDeleteTest
=> Check if users can be deleted UpdateUserTest
=> Check if users can be successfully updated
There is a special method named InitializeTest
. You can name this method whatever you want, as long as you put the “[TestInitialize]
” attribute on it. It initializes the repository for every test.
[TestInitialize]
public void InitializeTest()
{
users = new List<User>();
users.Add(new User(){ Image="http://userimages.com/image1.png",
Url="http://user1.com",UserName="TestUser1"});
users.Add(new User() { Image = "http://userimages.com/image2.png",
Url = "http://user2.com", UserName = "TestUser1" });
users.Add(new User() { Image = "http://userimages.com/image3.png",
Url = "http://user3.com", UserName = "TestUser2" });
users.Add(new User() { Image = "http://userimages.com/image4.png",
Url = "http://user4.com", UserName = "TestUser3" });
users.Add(new User() { Image = "http://userimages.com/image5.png",
Url = "http://user5.com", UserName = "TestUser4" });
users.Add(new User() { Image = "http://userimages.com/image6.png",
Url = "http://user6.com", UserName = "TestUser5" });
users.Add(new User() { Image = "http://userimages.com/image7.png",
Url = "http://user7.com", UserName = "TestUser6" });
users.Add(new User() { Image = "http://userimages.com/image8.png",
Url = "http://user8.com", UserName = "TestUser7" });
users.Add(new User() { Image = "http://userimages.com/image9.png",
Url = "http://user9.com", UserName = "TestUser8" });
users.Add(new User() { Image = "http://userimages.com/image10.png",
Url = "http://user10.com", UserName = "TestUser9" });
users.Add(new User() { Image = "http://userimages.com/image11.png",
Url = "http://user11.com", UserName = "TestUser10" });
users.Add(new User() { Image = "http://userimages.com/image12.png",
Url = "http://user12.com", UserName = "TestUser11" });
users.Add(new User() { Image = "http://userimages.com/image13.png",
Url = "http://user13.com", UserName = "TestUser12" });
users.Add(new User() { Image = "http://userimages.com/image14.png",
Url = "http://user14.com", UserName = "TestUser13" });
users.Add(new User() { Image = "http://userimages.com/image15.png",
Url = "http://user15.com", UserName = "TestUser14" });
users.Add(new User() { Image = "http://userimages.com/image16.png",
Url = "http://user16.com", UserName = "TestUser15" });
users.Add(new User() { Image = "http://userimages.com/image17.png",
Url = "http://user17.com", UserName = "TestUser16" });
this.testRepo = new FakeUserRepository();
var fakeUnitOfWork = new FakeUnitOfWork();
fakeUnitOfWork.SetContext(new FakeDBContext(users));
this.testRepo.Driver = fakeUnitOfWork;
}
This method is called before any of the test methods are called. That way, it is guaranteed, that you have always a freshly initialized repository for every test you run.
Running the Unit Tests
Simply right click the UnitTesting
project, select “debug
” and select “Start new instance
”:
Just ignore the “Use tags” feature for now and click on the play button in the command bar. Your tests should run all successfully:
The topmost area shows you how many tests have successfully passed, failed and the total amount of tests. The “UnitTesting
” area can be unfolded to show you each and every test that has been run by the unit test framework. You can click on each test to view the execution time and more.
You have now additional options like saving the test results to the isolated storage of the Windows Phone Emulator, or sending them via mail to a specific email address. But that is only possible with a valid account. If you want to send mails, just run the test on your phone, instead of the emulator.
Test Filtering Using Tags
You can filter tests by tagging them using the “Tag
” attribute.
[TestMethod]
[Tag("DeleteOnly")]
public void UserDeleteTest()
{
var userToRemove = testRepo.GetFilteredEntries
(user => user.UserName.Contains("TestUser1")).First();
var deletedUser = testRepo.DeleteEntry(userToRemove);
Assert.AreEqual<int>(testRepo.Driver.Context.FakeTable.Count(), 16);
}
In the small snippet (extracted from the current test source), you can see that the “UserDeleteTest
” has a tag
attribute with a constructor parameter “DeleteOnly
”. When you start the test again, you can activate test-filtering by activating the “Use tags” switch:
Then you just hit the play button again, and you can see, that only tests with the tag-attribute
set to “DeleteOnly
” have been executed:
Only the test we “tagged
” has been run now.
If you want to learn more about the testing framework, here is a link to a blog-post where you can find out more: Windows Phone Toolkit overview.
Using Moq to Mock Our Fake Repository
IMPORTANT: To make Moq work on Windows Phone, you NEED to download an additional assembly from the Castle project. It’s Castle.Core 2.5.2. Download the first archive from this page: Castle Project Extract the archive, and browse the Silverlight 4 folder (“\Castle.Core.2.5.2\Silverlight4\”) and add a reference to the “Castle.Core.dll” to the Windows Phone test project to make it work.
First of all, a definition of what Moq is, from the GitHub repo:
Moq (pronounced “Mock-you” or just “Mock”) is the only mocking library for .NET developed from scratch to take full advantage of .NET Linq expression trees and lambda expressions, which makes it the most productive, type-safe and refactoring-friendly mocking library available. And it supports mocking interfaces as well as classes. Its API is extremely simple and straightforward, and doesn’t require any prior knowledge or experience with mocking concepts
Ok, That’s Nice, What Is Mocking Anyway?
When you search the internet about what mocking (in terms of testing, unit testing) really is, you will find various definitions. On StackOverflow, I found a simple and straight forward definition of what mocking is:
What is mocking on StackOverflow
Mocking is primarily used in unit testing. An object under test may have dependencies on other (complex) objects. To isolate the behavior of the object you want to test you replace the other objects by mocks that simulate the behavior of the real objects. This is useful if the real objects are impractical to incorporate into the unit test.
In short, mocking is creating objects that simulate the behavior of real objects.
In our case, we need to mock the fake repository to be able to test the behavior and functionality of the implementation. Usually, you write stories to define the expectations that have to be met by your users. For the sake of brevity, this step will be omitted.
Starting with Moq
Using Moq always starts with creating a new Mock<T>
where T
is the type of the interface (or class, also possible with Moq) that you want to mock. Add a new test to the UnitTesting
project by right-clicking and choosing “Add=>Class …”. Name the class “DataAccessLayerTestsMoq
” or whatever you want, and add the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVVMWindowsPhone.Core.Portable.DAL;
using UnitTesting.Fakes;
using MVVMWindowsPhone.Core.Portable.Model;
using Microsoft.Phone.Testing;
using Moq;
namespace UnitTesting
{
[TestClass]
public class DALTestsMoq
{
Mock<IRepository<User, IFakeDBContext<User>>> testRepo;
List<User> userList;
[TestInitialize]
public void InitializeTest()
{
IQueryable<User> users = null;
userList = new List<User>();
userList.Add(new User() { Image = "http://userimages.com/image1.png",
Url = "http://user1.com", UserName = "TestUser1" });
userList.Add(new User() { Image = "http://userimages.com/image2.png",
Url = "http://user2.com", UserName = "TestUser1" });
userList.Add(new User() { Image = "http://userimages.com/image3.png",
Url = "http://user3.com", UserName = "TestUser2" });
userList.Add(new User() { Image = "http://userimages.com/image4.png",
Url = "http://user4.com", UserName = "TestUser3" });
userList.Add(new User() { Image = "http://userimages.com/image5.png",
Url = "http://user5.com", UserName = "TestUser4" });
userList.Add(new User() { Image = "http://userimages.com/image6.png",
Url = "http://user6.com", UserName = "TestUser5" });
userList.Add(new User() { Image = "http://userimages.com/image7.png",
Url = "http://user7.com", UserName = "TestUser6" });
userList.Add(new User() { Image = "http://userimages.com/image8.png",
Url = "http://user8.com", UserName = "TestUser7" });
userList.Add(new User() { Image = "http://userimages.com/image9.png",
Url = "http://user9.com", UserName = "TestUser8" });
userList.Add(new User() { Image = "http://userimages.com/image10.png",
Url = "http://user10.com", UserName = "TestUser9" });
userList.Add(new User() { Image = "http://userimages.com/image11.png",
Url = "http://user11.com", UserName = "TestUser10" });
userList.Add(new User() { Image = "http://userimages.com/image12.png",
Url = "http://user12.com", UserName = "TestUser11" });
userList.Add(new User() { Image = "http://userimages.com/image13.png",
Url = "http://user13.com", UserName = "TestUser12" });
userList.Add(new User() { Image = "http://userimages.com/image14.png",
Url = "http://user14.com", UserName = "TestUser13" });
userList.Add(new User() { Image = "http://userimages.com/image15.png",
Url = "http://user15.com", UserName = "TestUser14" });
userList.Add(new User() { Image = "http://userimages.com/image16.png",
Url = "http://user16.com", UserName = "TestUser15" });
userList.Add(new User() { Image = "http://userimages.com/image17.png",
Url = "http://user17.com", UserName = "TestUser16" });
users = userList.AsQueryable<User>();
testRepo = new Mock<IRepository<User, IFakeDBContext<User>>>();
testRepo.SetupProperty(repo => repo.Driver.Context.FakeTable,userList);
testRepo.Setup(repo => repo.GetAllEntries()).Returns(users);
testRepo.Setup(repo => repo.AddEntry(It.IsAny<User>())).Returns((User user) => {
userList.Add(user);
return user;
});
testRepo.Setup(repo => repo.DeleteEntry(It.IsAny<User>())).Returns
((User user) => { userList.Remove(user); return user; });
testRepo.Setup(repo => repo.GetFilteredEntries
(It.IsAny<System.Linq.Expressions.Expression<Func<User, bool>>>()))
.Returns(
(System.Linq.Expressions.Expression<Func<User, bool>> filter) =>
{
return userList.AsQueryable<User>().Where(filter);
});
testRepo.Setup(repo => repo.UpdateEntry(It.IsAny<User>(),
It.IsAny<User>())).Returns((User orig, User update) => {
if (userList.Contains<User>(orig))
{
List<User> list = userList.ToList<User>();
list[list.IndexOf(orig)] = update;
userList = list;
return update;
}
else
{
return null;
}
});
}
[TestMethod]
[Tag("Moq")]
public void GetAllUsersAndCountTest()
{
var allEntries = testRepo.Object.GetAllEntries().Count();
Assert.AreEqual<int>(allEntries, 17);
}
[TestMethod]
[Tag("Moq")]
public void AddUserAndCountTest()
{
User userToAdd = new User() { Image = "Image",
Url = "some url", UserName = "TestUser18" };
var user = testRepo.Object.AddEntry(userToAdd);
Assert.AreEqual<int>(testRepo.Object.GetAllEntries().Count(), 18);
}
[TestMethod]
[Tag("Moq")]
public void UserFilterTest()
{
var filteredUser = testRepo.Object.GetFilteredEntries
(user => user.UserName.Contains("TestUser1")).First();
Assert.AreEqual<string>(filteredUser.UserName,"TestUser1");
}
[TestMethod]
[Tag("Moq")]
public void UserDeleteTest()
{
var filteredUser = testRepo.Object.GetFilteredEntries
(user => user.UserName.Contains("TestUser1")).First();
var userDeleted = testRepo.Object.DeleteEntry(filteredUser);
Assert.IsNotNull(userDeleted);
}
[TestMethod]
[Tag("Moq")]
public void UpdateUserTest()
{
var userToUpdate = testRepo.Object.GetFilteredEntries
(user => user.UserName.Contains("TestUser1")).First();
var updatedEntry = new User()
{ UserName = userToUpdate.UserName, Image = "changed", Url = userToUpdate.Url };
testRepo.Object.Driver.Context.UpdateEntry(userToUpdate, updatedEntry);
var updatedUser = testRepo.Object.GetFilteredEntries
(user => user.UserName.Contains("TestUser1")).First();
Assert.Equals(updatedUser.Image, "changed");
}
}
}
As you can see, there are two private
properties defined:
testRepo
– the Moq “Mock
” object userList
– The list of users to work with
In the first few lines, the userList
is initialized and filled with 17 test users. Then, we cast the userList
to IQueryable<User>
because the repository is expecting and IQueryable<T>
.
Then the Mock is created. An IRepository<User, IFakeDBContext<User>>>
is set as the type parameter for the generic Moq “Mock
” class.
This will create a virtual type of T
(IRepository<User, IFakeDBContext<User>>>
) that we can use to bind expectations to, by using the Setup Method of the Moq “Mock
” instance. Expectations in short can be explained like this (usually they are the user stories):
When I do this, I expect the following result….
If you take a look at the first SetupProperty
call, you can see that it is expected for the FakeTable
property to contain all users from the userList
.
The next line, binds the AddEntry
-Method of the IRepository
interface by specifying that the parameter passed to add Entry
can be any type of User
object. We don’t expect the user to have a certain UserName
or picture for example. Then, using fluent configuration, we define (Using a Func<T>
) what we expect to be returned, if adding the new user to the list was successful. We return the new User
itself.
The next few lines do exactly the same thing, to complete our CRUD scenario for the test based on Moq. Please don’t get confused about all the Lambdas and Linq expressions. Just check the code of the IRepository
interface and you will see that it is not much different from what you see there. Very intuitive.
This is the way you add the implementation by setting up expectations using Moq. It is not so hard at all.
The tests are not much different from the tests we created before, using the fake implementations for our repository. If you take a close look, you can see, that I decorated each test method using the Tag
attribute. That way, you can easily filter your tests, and try only the Moq based ones.
As Moq is not supported in Windows 8/8.1, we will not use it for the Windows 8.1 testing. I have copied the fake classes from the Windows Phone 8 testing project to the “UnitTestingWin8
” project, using the same folder “Fakes” and the same name for the test class “DataAccessLayerTests
”.
Running Your Windows 8/8.1 Unit Tests
To run your Windows 8/8.1 tests, go to the Visual Studio 2013 “TEST
” menu and select Windows=>Test Explorer. Then rebuild the project, so that the unit tests can be discovered by Visual Studio 2013. Then click on the “Run All” link to run all of the unit tests for Windows 8/8.1.
Finalizing the Implementation
Now that you have seen, how to mock repositories using Moq and mocking them manually, it’s time to go on with our implementation.
All the important stuff is in the MVVMWindowsPhone.Core.Portable
assembly. Just expand the project to recall the parts that we need to implement and finalize the project:
If you are not sure what all this means, please read part 1, part 2 and part 3 of this article series in order to understand what’s going on.
- Bootstrapping => Everything we need to use DI (utilizing Ninject) in our project and decouple as good as possible.
- DAL => This is the data-access layer we use, you have seen it already in the tests. This time, we implement it using SQLite for Windows Phone 8 and Windows 8/8.1.
- Model => These are our data classes. In this sample, we are working with users.
- Services => The folder for the services we want to inject. Here, we have a VERY simple implementation of a navigation service, that will be used in our view-models to navigate from page to page using commands.
- ViewModel => A base view-model and base view-model locater that we will use with MVVM-Light.
Before we start anything else, let’s implement the most important parts, the repository and the bootstrapper.
Implementing the Repository using SQLite
Based on our IUnitOfWork<T>
interface, we can implement a SQLiteDriver
class. The idea behind it is that we can implement any kind of data-access driver and abstract it through the IUnitOfWork<T>
interface and our IRepository<T,U>
interface. However, here is the implementation of the SQLiteDriver
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SQLite;
using MVVMWindowsPhone.Core.Portable.DAL;
using Ninject;
namespace MVVMWindowsPhone.DAL
{
public class SQLiteDriver : IUnitOfWork<SQLiteAsyncConnection>
{
private SQLiteAsyncConnection context;
[Inject]
public SQLiteAsyncConnection Context
{
get
{
return this.context;
}
set
{
this.context = value;
}
}
public SQLiteDriver()
{
}
public void SetContext(SQLiteAsyncConnection context)
{
if(context == null)
{
throw new ArgumentException("Parameter cannot be null.", "context");
}
this.Context = context;
}
}
}
Now, we have two possibilities of setting the SQLiteAsyncConnection
. Using the SetContext<T>
method or via property injection using Ninject. As you can see, we have an [Inject]
attribute placed on the context property, that will automagically inject an object of type SQLiteAsyncConnection
, if we register that in our bootstrapper.
This means that Ninject will create and resolve the SQLiteAsyncConnection
for us at runtime, and we decouple our code with this method!
Before we do that, let’s implement the SQLiteRepository
.
Implementing the SQLiteRepository
Now that we have our driver ready, we need to add an additional class to our project. Let’s call it SQLiteRepository
(or whatever you want). Add it to the DAL folder, and add the following code:
using MVVMWindowsPhone.Core.Portable.DAL;
using MVVMWindowsPhone.Core.Portable.Model;
using MVVMWindowsPhone.Model;
using Ninject;
using SQLite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMWindowsPhone.DAL
{
public class SQLiteRepository : ITAPRepository<SQLiteUser, SQLiteAsyncConnection>
{
IUnitOfWork<SQLiteAsyncConnection> driver;
[Inject]
public IUnitOfWork<SQLiteAsyncConnection> Driver
{
get
{
return this.driver;
}
set
{
this.driver = value;
}
}
public async Task<IQueryable<SQLiteUser>> GetAllEntries()
{
var list = await Driver.Context.Table<SQLiteUser>().ToListAsync();
if (list != null)
{
return list.AsQueryable<SQLiteUser>();
}
return null;
}
public async Task<IQueryable<SQLiteUser>>
GetFilteredEntries(System.Linq.Expressions.Expression<Func<SQLiteUser, bool>> filter)
{
if (filter == null)
{
throw new ArgumentException("Parameter cannot be null or empty.", "filter");
}
var list = await Driver.Context.Table<SQLiteUser>().ToListAsync();
if (list != null)
{
return list.AsQueryable<SQLiteUser>().Where(filter);
}
return null;
}
public async Task<SQLiteUser> DeleteEntry(SQLiteUser entry)
{
if (entry == null)
{
throw new ArgumentException("Parameter cannot be null or empty.", "entry");
}
var deleted = await Driver.Context.DeleteAsync(entry);
if (deleted != 0)
{
return entry;
}
return null;
}
public async Task<SQLiteUser> UpdateEntry(SQLiteUser entry, SQLiteUser updateValue)
{
if (entry == null)
{
throw new ArgumentException("Parameter cannot be null or empty.", "entry");
}
if (updateValue == null)
{
throw new ArgumentException("Parameter cannot be null or empty", "updateValue");
}
entry.Image = updateValue.Image;
entry.Url = updateValue.Url;
entry.UserName = updateValue.UserName;
int updateSuccess = await Driver.Context.UpdateAsync(entry);
if (updateSuccess != 0)
{
return entry;
}
return null;
}
public async Task<SQLiteUser> AddEntry(SQLiteUser entry)
{
if (entry == null)
{
throw new ArgumentException("Parameter cannot be null or empty.", "entry");
}
var addSuccess = await Driver.Context.InsertAsync(entry);
if(addSuccess != 0)
{
return entry;
}
return null;
}
}
}
As you can see, the SQLiteRepository
is implemented as ITAPRepository<SQLiteUser, SQLiteAsyncConnection>
(to use the TAP async
/await
pattern).
And we implemented a special SQLiteUser
class, that has a primary key attribute on it, inheriting from our User
class (it has also an auto-increment option). This is necessary to allow SQLLite to identify objects that need to be updated and/or otherwise modified.
Now imagine that you can exchange T
and U
with anything you want to implement CRUD operations on specific objects (T
) using a specific type of “Driver
” (U
). Comfortable, isn’t it?
We inject the type of U
driver we need, using Ninjects [Inject]
attribute. This ensures that an instance of the SQLite driver is injected at runtime.
The Bootstrapper using Ninject
In the previous posts, a basic bootstrapper was already added. The bootstrapper for Windows Phone 8 will inherit this one, to make it easier for us.
This is the code of the SimpleBootstrapper
class:
using MVVMWindowsPhone.Core.Portable.Bootstrapper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject;
using Ninject.Modules;
namespace MVVMWindowsPhone.Core.Portable.Bootstrapping
{
public abstract class SimpleBootstrapper:IBootstrapper
{
private IKernel container;
private IList<INinjectModule> modules;
private IViewModelLocator viewModelLocator;
public IKernel Container
{
get
{
return this.container;
}
set
{
this.container = value;
}
}
public IList<INinjectModule> Modules
{
get
{
return this.modules;
}
set
{
this.modules = value;
}
}
public IViewModelLocator ViewModelLocator
{
get
{
return this.viewModelLocator;
}
set
{
this.viewModelLocator = value;
}
}
public SimpleBootstrapper()
{
}
public virtual void ConfigureBootstrapper()
{
}
}
}
Based on this class, we implement the bootstrapper for the Windows Phone project:
using MVVMWindowsPhone.Core.Portable.Bootstrapping;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ninject;
using Ninject.Modules;
using MVVMWindowsPhone.NinjectModules;
namespace MVVMWindowsPhone.Bootstrapper
{
public class Bootstrapper:SimpleBootstrapper
{
public override void ConfigureBootstrapper()
{
this.Modules = new List<INinjectModule>();
this.Modules.Add(new ModuleRuntime());
this.Modules.Add(new ModuleViewModels());
this.Container = new StandardKernel(this.Modules.ToArray<INinjectModule>());
}
}
}
To de-clutter the bootstrap process, we can add Ninject modules. Those are like little sub-bootstrapper, that can help to “name” the different scenarios one is using for his app. Create a new folder called NinjectModules within the Windows Phone 8 project. Add a new class to the folder and call it “ModuleRuntime
” (or call it whatever you want). Add the following code to the module:
using Ninject.Modules;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MVVMWindowsPhone.Core.Portable.DAL;
using MVVMWindowsPhone.Core.Portable.Model;
using MVVMWindowsPhone.DAL;
using SQLite;
using System.IO;
using Windows.Storage;
using MVVMWindowsPhone.Model;
namespace MVVMWindowsPhone.NinjectModules
{
public class ModuleRuntime:NinjectModule
{
public override void Load()
{
string dbPath = Path.Combine(Path.Combine
(ApplicationData.Current.LocalFolder.Path, "users.db"));
SQLiteAsyncConnection con = new SQLiteAsyncConnection(dbPath, true);
var exists = con.ExecuteScalarAsync<int>
("select count(type) from sqlite_master where type='table' and name='SQLiteUser';");
exists.Wait();
if(!(exists.Result == 1))
{
var created = con.CreateTableAsync<SQLiteUser>();
created.Wait();
}
this.Bind<SQLiteAsyncConnection>().ToConstant(con).InSingletonScope();
this.Bind<IUnitOfWork<SQLiteAsyncConnection>>().To<SQLiteDriver>().InSingletonScope();
this.Bind<ITAPRepository<SQLiteUser,
SQLiteAsyncConnection>>().To<SQLiteRepository>().InSingletonScope();
}
}
}
We use this module to create the database itself, and to bind instances of SQLiteAsyncConnection
, IUnitOfWork<SQLiteAsyncConnection>
and IUnitOfWork<SQLiteAsyncConnection>
as singletons. This means that the instances are exactly available once in the Windows Phone project.
Activating the Bootstrapper
Open App.xaml.cs and add a static
property of type MVVMWindowsPhone.Bootstrapper.Bootstrapper
and initialize it.
Then call the configureBootstrapper()
method in Application_Activated
and Application_Launching
.
public static MVVMWindowsPhone.Bootstrapper.Bootstrapper bootstrapper =
new Bootstrapper.Bootstrapper();
bootstrapper.ConfigureBootstrapper();
ModelLocator = bootstrapper.Container.GetService
(typeof(ViewModelLocator)) as ViewModelLocator;
That’s it so far for the bootstrapper and the production repository.
The Real Stuff – Short Timeout
Looks like this post has nothing in common with MVVM. Really? This is the usual procedure (or parts of it, let’s come down) when you do enterprise level development. One big exception (from my personal experience) is, that we are not creating something like our own “In house” framework. Makes me laugh every time when I think about it.
Time to enlighten the architecture a bit and to move to the “Real Stuff” like view-models, locators, bindings, commands and all the other funny things you are confronted every day with.
The View Model Locator
People who have worked with MVVM-Light think that the class locating the view-models is an essential, not exchangeable part of MVVM-Light. It is not, it is simply a class that uses the Locator Pattern – Mark Seemann aka “ploeh” even discusses that Service Locator is an Anti-Pattern (link to his blog-post, he’s well know expert in the field of unit testing, architecture and much much more). I highly recommend to read his blog, his books and look for his posts also on StackOverflow.
You can replace the MVVM-Light view-model locator anytime with your own implementation. This is exactly what happened here.
Since XAML is able to bind to dynamic properties and indexers, I have chosen a different approach. This enables you to register any kind of view-model that implements ViewModelBase
(all classes are inside the PCL project) and bind it to your views. One thing you have to take care about, is to use the exact property names and view-model names. Just to recall, here the IViewModelLocator
, the ViewModelLocator
and the BaseViewModel
class.
using System;
using System.Collections.Generic;
using GalaSoft.MvvmLight;
namespace MVVMWindowsPhone.Core.Portable.Bootstrapping
{
public interface IViewModelLocator
{
dynamic this[string viewModelName] { get; }
Dictionary<string, dynamic> ViewModels { get; set; }
T Cast<T>(string viewModelName);
}
}
using GalaSoft.MvvmLight;
using MVVMWindowsPhone.Core.Portable.Bootstrapper;
using MVVMWindowsPhone.Core.Portable.DAL;
using MVVMWindowsPhone.Core.Portable.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.CompilerServices;
using MVVMWindowsPhone.Core.Portable.Bootstrapping;
using Ninject;
namespace MVVMWindowsPhone.Core.Portable.ViewModel
{
public class ViewModelLocator:IViewModelLocator
{
Dictionary<string, dynamic> viewModels;
[Inject]
public Dictionary<string, dynamic> ViewModels
{
get { return viewModels; }
set { viewModels = value; }
}
public ViewModelLocator()
{
ViewModels = new Dictionary<string,dynamic>();
}
public dynamic this[string viewModelName]
{
get
{
if(ViewModels.ContainsKey(viewModelName))
{
return this.ViewModels[viewModelName];
}
else
{
return null;
}
}
}
public T Cast<T>(string viewModelName)
{
if (ViewModels.ContainsKey(viewModelName))
{
return (T) this.ViewModels[viewModelName];
}
else
{
return default(T);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GalaSoft.MvvmLight;
using MVVMWindowsPhone.Core.Portable.DAL;
using System.Collections.ObjectModel;
using MVVMWindowsPhone.Core.Portable.Services;
using Ninject;
namespace MVVMWindowsPhone.Core.Portable.ViewModel
{
public class BaseViewModel<T,U>:ViewModelBase where U:class where T:class
{
private ObservableCollection<T> data;
private readonly INavigationService navigationService;
public ObservableCollection<T> Data
{
get { return data; }
set { data = value; }
}
protected readonly ITAPRepository<T, U> repository;
public BaseViewModel(ITAPRepository<T, U> repo, INavigationService navService)
{
this.repository = repo;
this.navigationService = navService;
this.Data = new ObservableCollection<T>();
}
public BaseViewModel()
{
}
}
}
One little change will be made on the BaseViewModel
class. I decided to support only the ITAPRepository
interface, as it allows one to use async
and await
. It is still based on the excellent ViewModelBase
class by Laurent Bugnion.
Add a New Ninject Module for the ViewModel-Locator and the View-Models
Add a new class to the NinjectModules folder within the MVVMWindowsPhone
project, name it “ModuleViewModels
” (or anything you want), and make it inherit from NinjectModule
and let Visual Studio implement the abstract
class. Do this by selecting the “NinjectModule
” after the “:
” and press CTRL + “.” . Then select “Implement Abstract Class”. That will create an override for the Load()
method.
What we need to do now, is tell Ninject to bind the ViewModelLocator
. Until now, we don’t have any view-models.
This is done by telling the Ninject-Module to bind IViewModelLocator
to ViewModelLocator
. As we want only one instance of it hanging around, we choose InSingletonScope
. The type of configuration we use here is called “Fluent Configuration”.
To finalize the view-model story, we add a new view-model called UserViewModel
(folder ViewModel, Windows Phone project), let it inherit from our BaseViewModel
class. Here is the final class:
using GalaSoft.MvvmLight.Command;
using MVVMWindowsPhone.Core.Portable.DAL;
using MVVMWindowsPhone.Core.Portable.Services;
using MVVMWindowsPhone.Core.Portable.ViewModel;
using MVVMWindowsPhone.DAL;
using MVVMWindowsPhone.Model;
using Ninject;
using SQLite;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Text;
using System.Windows;
using Windows.Storage;
using Windows.UI.Core;
namespace VVMWindowsPhone.ViewModel
{
public class UserViewModel:BaseViewModel<SQLiteUser, SQLiteAsyncConnection>
{
public RelayCommand<SQLiteUser> AddNewUserCommand { get; set; }
public RelayCommand DeleteUserCommand { get; set; }
public RelayCommand EditUserCommand { get; set; }
public RelayCommand<object> FilterUsersCommand { get; set; }
public RelayCommand<string> DialogResultCommand { get; set; }
public RelayCommand Init { get; set; }
private SQLiteUser crudUser;
public SQLiteUser CrudUser
{
get { return crudUser; }
set
{
if (crudUser != value)
{
crudUser = value;
RaisePropertyChanged("CrudUser");
}
}
}
private string dialogTitle;
public string DialogTitle
{
get { return dialogTitle; }
set
{
if (dialogTitle != value)
{
dialogTitle = value;
RaisePropertyChanged("DialogTitle");
}
}
}
private string filterText;
public string FilterExpression
{
get { return filterText; }
set
{
filterText = value;
RaisePropertyChanged("FilterText");
this.FilterUsersCommand.RaiseCanExecuteChanged();
}
}
private bool userControlVisibility;
public bool UserControlVisibility
{
get { return userControlVisibility; }
set
{
if (userControlVisibility != value)
{
userControlVisibility = value;
RaisePropertyChanged("UserControlVisibility");
}
}
}
private object selectedItem;
public object SelectedItem
{
get { return selectedItem; }
set
{
if (selectedItem != value)
{
selectedItem = value;
RaisePropertyChanged("SelectedItem");
}
this.EditUserCommand.RaiseCanExecuteChanged();
this.DeleteUserCommand.RaiseCanExecuteChanged();
}
}
private bool AddOrEdit;
[Inject]
public UserViewModel(ITAPRepository<SQLiteUser, SQLiteAsyncConnection> repo,
INavigationService navService)
: base(repo, navService)
{
this.AddNewUserCommand = new RelayCommand<SQLiteUser>
(this.AddNewUser, this.CanExecuteAddNewUser);
this.DeleteUserCommand = new RelayCommand
(this.DeleteUser, this.CanExecuteEditCommand);
this.EditUserCommand = new RelayCommand
(this.EditUser, this.CanExecuteEditCommand);
this.FilterUsersCommand = new RelayCommand<object>
(this.FilterUsers, this.CanExecuteFilterCommand);
this.DialogResultCommand = new RelayCommand<string>
(this.DialogResultCheck, this.CanExecuteCommandWithParameter);
this.Init = new RelayCommand(this.InitData, this.CanExecuteCommand);
this.CrudUser = new SQLiteUser();
var users = this.repository.GetAllEntries();
}
private bool CanExecuteFilterCommand(object arg)
{
var evtArgs = (System.Windows.Input.KeyEventArgs)arg;
if (evtArgs.Key == System.Windows.Input.Key.Enter)
{
return true;
}
return false;
}
private async void InitData()
{
var users = await this.repository.GetAllEntries();
if (users != null)
{
RefreshDataSource(users);
}
}
private async void DialogResultCheck(string obj)
{
if(obj == "ok")
{
if(this.AddOrEdit)
{
var user = await this.repository.AddEntry(this.CrudUser);
this.Data.Clear();
var users = await this.repository.GetAllEntries();
RefreshDataSource(users);
this.CrudUser = new SQLiteUser();
}
else
{
var updatedUser = await this.repository.UpdateEntry(
(SQLiteUser)this.SelectedItem, this.CrudUser
);
this.Data.Clear();
var users = await this.repository.GetAllEntries();
RefreshDataSource(users);
this.CrudUser = new SQLiteUser();
}
this.UserControlVisibility = false;
this.RaiseExecuteChangedDialog();
}
if(obj == "cancel")
{
this.CrudUser = new SQLiteUser();
this.UserControlVisibility = false;
this.RaiseExecuteChangedDialog();
}
}
private void RaiseExecuteChangedDialog()
{
this.AddNewUserCommand.RaiseCanExecuteChanged();
this.EditUserCommand.RaiseCanExecuteChanged();
this.DeleteUserCommand.RaiseCanExecuteChanged();
this.DialogResultCommand.RaiseCanExecuteChanged();
}
private bool CanExecuteAddNewUser(SQLiteUser arg)
{
if (this.CanExecuteCommand())
{
return true;
}
return false;
}
private void AddNewUser(SQLiteUser obj)
{
this.DialogTitle = "Add new user";
this.AddOrEdit = true;
this.UserControlVisibility = true;
this.RaiseExecuteChangedDialog();
}
public void RefreshDataSource(IQueryable<SQLiteUser> list)
{
list.ToList().ForEach(this.Data.Add);
}
private bool CanExecuteCommandWithParameter(string arg)
{
if (!arg.Equals("cancel") && !arg.Equals("ok"))
{
if (this.CanExecuteCommand())
{
if (string.IsNullOrEmpty(arg) || string.IsNullOrWhiteSpace(arg))
{
return false;
}
else
{
return true;
}
}
}
else
{
if (this.CanExecuteCommandDialogResult())
{
if (string.IsNullOrEmpty(arg) || string.IsNullOrWhiteSpace(arg))
{
return false;
}
else
{
return true;
}
}
}
return false;
}
private async void FilterUsers(object obj)
{
if (this.FilterExpression.Length == 1 ||
string.IsNullOrEmpty(this.FilterExpression))
{
this.Data.Clear();
var users = await this.repository.GetAllEntries();
RefreshDataSource(users);
}
else
{
this.Data.Clear();
var users = await this.repository.GetFilteredEntries
(u => u.UserName.ToLower().Contains(this.FilterExpression.ToLower())
|| u.Image.ToLower().Contains(this.filterText.ToLower()) ||
u.Url.ToLower().Contains(this.FilterExpression.ToLower())
);
RefreshDataSource(users);
}
}
private void EditUser()
{
var user = new SQLiteUser();
user.Image = ((SQLiteUser)this.SelectedItem).Image;
user.Url = ((SQLiteUser)this.SelectedItem).Url;
user.UserName = ((SQLiteUser)this.SelectedItem).UserName;
this.CrudUser = user;
this.DialogTitle = "Edit user";
this.AddOrEdit = false;
this.UserControlVisibility = true;
this.RaiseExecuteChangedDialog();
}
private async void DeleteUser()
{
var result = MessageBox.Show
("Delete this item?","CAUTION",MessageBoxButton.OKCancel);
if(result == MessageBoxResult.OK)
{
var deletedUser =
this.repository.DeleteEntry((SQLiteUser)this.SelectedItem);
this.Data.Clear();
var users = await this.repository.GetAllEntries();
RefreshDataSource(users);
}
}
private bool CanExecuteCommand()
{
string dbPath = Path.Combine(Path.Combine
(ApplicationData.Current.LocalFolder.Path, "users.db"));
return IsolatedStorageFile.GetUserStoreForApplication().FileExists(dbPath) &&
!this.UserControlVisibility;
}
private bool CanExecuteEditCommand()
{
return this.CanExecuteCommand() && !this.userControlVisibility &&
(this.SelectedItem != null);
}
private bool CanExecuteCommandDialogResult()
{
return this.UserControlVisibility;
}
}
}
There is only one small improvement to add to solve. Since we have different view-models of different types, all inheriting from BaseViewModel
, and the view-model locator does have a specific type to return (BaseViewModel
). Some of you may like to see a method to cast from everything, to everything, to get the concrete type of the view-model (strongly typed) instead of dynamically. I changed the ViewModel
locator to use dynamic for the Dictionary
(as value), where the view-models reside.
To cast the dynamic to the desired type, just use the Cast<T>
method, passing the name of the view-model (the exact name). If it is unable to cast it, it will return the default of T
.
Now we need to find a comfortable way to add our view-models to the view-model locator. This is again done, by using Ninject. Open the ModuleViewModels
, and change it like this:
using MVVMWindowsPhone.Core.Portable.Bootstrapping;
using MVVMWindowsPhone.Core.Portable.DAL;
using MVVMWindowsPhone.Core.Portable.ViewModel;
using MVVMWindowsPhone.Model;
using MVVMWindowsPhone.Services;
using Ninject.Modules;
using SQLite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VVMWindowsPhone.ViewModel;
namespace MVVMWindowsPhone.NinjectModules
{
public class ModuleViewModels : NinjectModule
{
public override void Load()
{
var viewModels = new Dictionary<string, dynamic>();
var repo = Kernel.GetService
(typeof(ITAPRepository<SQLiteUser, SQLiteAsyncConnection>))
as ITAPRepository<SQLiteUser, SQLiteAsyncConnection>;
var navigationService = new NavigationService();
var userViewModel = new UserViewModel(repo,navigationService);
viewModels.Add("UserViewModel", userViewModel);
this.Bind<Dictionary<string, dynamic>>().ToConstant(viewModels).InSingletonScope();
this.Bind<IViewModelLocator>().To<ViewModelLocator>().InSingletonScope();
}
}
}
A new Dictionary<string,dynamic>
was added to the ModuleViewModels
Ninject-Module and it is bound and injected into the view-model locator.
Now we need to tell Ninject to add the ModuleViewModels
module to the kernel. Open Bootstrapper.cs (from within the Windows Phone 8 project) and add a new entry to the Modules
list.
A Simple Navigation Service
To be able to navigate from within the view-models, a simple navigation service is needed. Add a new class to the Services folder of the Windows Phone 8 project and call it “NavigationService
” (or whatever your want). Implement the interface INavigation
service, and add the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MVVMWindowsPhone.Core.Portable.Services
{
public interface INavigationService
{
void NavigateTo(Uri page);
void NavigateTo(Type pageToNavigateTo);
void GoBack();
}
}
The base idea is very simple to use one interface for Windows 8 and Windows Phone 8. Therefore, the NavigateTo(Type pageToNavigateTo)
is not implemented and should not be used. Otherwise, a NotImplemented
exception will be thrown.
The implementation is very simple. The navigation-service tries to get the current root-visual of the current app, which is the current PhoneApplicationFrame
. If that was successful, it utilizes the Navigate
and GoBack
methods. That’s it.
All dependencies that are needed have been added to Ninject and all the required repositories and services have been implemented. The next step is now, to make the view-model locator globally available, so that we can avoid passing the view-models in code-behind.
Locating Our View-Models
Since it is not possible on Windows Phone to bind to a static
value, we need another solution to locate our view-models, using a workaround.
First of all, we need a single ViewModelLocator static
instance in App.xaml.cs. Open App.xaml.cs and add the following property code:
ModelLocator = bootstrapper.Container.GetService(typeof(ViewModelLocator)) as ViewModelLocator;
Because we bound already an instance (singleton) of ViewModelLocator
in ModuleViewModel.cs, we can use our bootstrapper in App.xaml.cs to request that specific instance and assign it to our static ModelLocator
property.
This is done by calling the GetService<T>
method of the bootstrapper.Container
property, which is an implementation of Ninject.IKernel
.
We do that in Application_Activated
and in Application_Launched
.
Now we need a comfortable way to access the static ModelLocator
property using static
resources in our views. To be able to do that, we create a value converter and pass it a static
resource of type system.string
.
Inside the value-converter, we access the current app, and use a bit of reflection to access the static
property ModelLocator
via a string
constant “ModelLocator
”. We cast the object that is returned by GetType().GetProperty().GetValue()
to a ViewModelLocator
instance.
After that is done, and the ViewModelLocator
instance is not null
, we take the “value
” parameter of the Convert
method, and check if it is a string
. If so, and the string
is not empty, we pass the value to the indexer of the ModelLocator
instance and return the ViewModel
if we can find it. If not, we return null
. Add a new folder to our Windows Phone 8 project, and name it “Converters”. Then add a new class named “ViewModelConverter
”. Add the following code:
using MVVMWindowsPhone.Core.Portable.ViewModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace MVVMWindowsPhone.Converter
{
public class ViewModelConverter:IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
App current = (App)Application.Current;
var locator = current.GetType().GetProperty("ModelLocator").GetValue
(current, null) as ViewModelLocator;
if(value == null)
{
return null;
}
if(value is string)
{
var viewModelName = (string) value;
var viewModel = locator[viewModelName];
if(viewModel == null)
{
return null;
}
return viewModel;
}
return null;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
In App.xaml, we first add a namespace sys
that points to the System
namespace (assembly: mscorlib
). We need that to create a static
resource of type string
, that will contain the name of the ViewModel
to bind to. The next step is to add another namespace conv
, that will point to clr-namespace:MVVMWindowsPhone.Converter
. Now we add a new static
resource of type ViewModelConverter
.
With these two resources in place, we can now assign the data-context to the PhoneApplicationPage
:
<sys:String x:Key="UserViewModel">UserViewModel</sys:String>
<conv:ViewModelConverter x:Key="ViewModelConverter"/>
DataContext="{Binding Converter={StaticResource ViewModelConverter},
Source={StaticResource UserViewModel}}"
Time to Extend UserViewModel
Let’s extend our UserViewModel
class a bit, to allow the following functions:
- Add a new user to our SQLite-Database using our
SQLiteRepository
- Delete a selected user
- Filter users
- And edit a user
This is where the relationship to the MVVM-Light ViewModelBase
class, will save us a lot of work. We can use the complete feature set here, including commanding, property change notifications and more.
Adding the Required Commands
Let’s start with the commands to implement the CRUD operations on our data. MVVM-Light offers an implementation of ICommand
, called RelayCommand
, that is very easy to use.
Open our UserViewModel
to see the implementations.
As you can see, we added four RelayCommands
:
AddNewUserCommand
=> Add a new user (this is another type of RelayCommand
, that can accept a parameter. We need to pass the filter string
here.) DeleteUserCommand
=> Delete the selected User
(it will take the user from a SelectedItem
property.) EditUserCommand
=> Edit the selected User (this is another type of RelayCommand
, that can accept a parameter. We need to pass the filter string
here.) FilterUsersCommand
=> To filter the list of users (this is another type of RelayCommand
, that can accept a parameter. We need to pass the filter string
here)
To tell the RelayCommand
what to do, we need to assign an action. In fact, we can apply two actions. One action to execute pre-conditions (can execute) to see, if the command is actually executable and another action that will perform the work, if the command is executable.
In our case, the commands are pretty useless, if we are unable to access the database. Therefore, one action to check, if the data-source parameter of our SQLAsyncConnection
was set or not. If not, the command cannot be executed and vice versa.
We can use also Lambda expressions to assign the command functionality. But for better readability, we will avoid that and add the actions manually.
The Bindable Application Bar
To execute the various commands, we will use the bindable application bar. You can download it here:
To save some lines here, I have pasted the original sample code from the codeplex website to demonstrate how to use the BindableAppllicationBar
:
<bar:Bindable.ApplicationBar>
<bar:BindableApplicationBar
IsVisible="{Binding BarIsVisible}"
IsMenuVisible="{Binding IsMenuVisible, Mode=TwoWay}"
IsMenuEnabled="{Binding IsMenuEnabled}"
ForegroundColor="{Binding ForegroundColor,
Converter={StaticResource DoubleToColorConverter}}"
BackgroundColor="{Binding BackgroundColor,
Converter={StaticResource DoubleToColorConverter}}"
BindableOpacity="{Binding Opacity}"
Mode="{Binding Mode}"
MenuItemsSource="{Binding MenuItems}"
ButtonsSource="{Binding Buttons}">
<bar:BindableApplicationBarButton
Text="{Binding IconButtonText}"
IconUri="{Binding IconUri, FallbackValue=/Icons/Dark/appbar.add.rest.png}"
IsEnabled="{Binding ButtonIsEnabled}" />
<bar:BindableApplicationBarButton
Text="XAML Btn 2"
IconUri="/Icons/Dark/appbar.next.rest.png"
Command="{Binding TestCommand}"
CommandParameter="{Binding TestCommandParameter}" />
<bar:BindableApplicationBar.MenuItems>
<bar:BindableApplicationBarMenuItem
Text="{Binding MenuItemText}"
IsEnabled="{Binding MenuItemIsEnabled}" />
<bar:BindableApplicationBarMenuItem
Text="XAML MnuIt 2"
Command="{Binding TestCommand2}"
CommandParameter="{Binding TestCommand2Parameter}" />
</bar:BindableApplicationBar.MenuItems>
</bar:BindableApplicationBar>
</bar:Bindable.ApplicationBar>
It should give you a good overview of how to use it. It allows us to bind commands and parameters to the application bar. That’s the most important fact.
After you have downloaded it, extract the archive, open the solution (ignore the source code dialog) and add a reference to the output assembly to our Windows Phone 8 project and rebuild the solution.
Now add the required namespace to MainPage.xaml. I used “appb
” as the namespace. You can choose whatever you want.
Now add the required XAML code for 3 of the commands, except for the FilteredUserCommand
. We will use a textbox
for that later on.
Now create a new “icons” folder and add some standard application-bar icons to that folder. You can find the sample icons in:
C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v8.0\Icons
Access the sub-directory “Dark” and add the following 3 icons:
- delete.png
- add.png
- edit.png
Here is the snippet to build app the BindableApplicationBar
.
<appb:Bindable.ApplicationBar>
<appb:BindableApplicationBar IsVisible="True" Background="Azure">
<appb:BindableApplicationBarButton IconUri="/Icons/add.png"
Text="add" Command="{Binding AddNewUserCommand}"/>
<appb:BindableApplicationBarButton IconUri="/Icons/edit.png"
Text="edit" Command="{Binding EditUserCommand}"/>
<appb:BindableApplicationBarButton IconUri="/Icons/delete.png"
Text="delete" Command="{Binding DeleteUserCommand}"/>
</appb:BindableApplicationBar>
</appb:Bindable.ApplicationBar>
If you set now a break-point in AddNewUser()
(in UserViewModel.cs) and CanExecute()
, you will see that can execute will check always if the command can be executed as soon as the binding is complete. And if you hit F5 to continue, you can see that CanExecute()
is called for all three buttons. You see as well, that our view-model-binding is working, and that the SQLite db has been successfully created during our Ninject initialization. The view-model-locator works as well. You can ignore the XAML errors. The code is ok. XAML is complaining about the cast in our ValueConverter
that is resolving the view-models.
Payday
Can you feel it? All of the previous work starts to payoff now. We will be rewarded with a clean and simple design, and methods that require only a few lines of code. And a great side-effect is, that we have ZERO code in our code-behind files! Yeah!
Back to Work – Implement the Commands
Ok. Let’s implement the add/edit user functionality. First, we need something like a simple popup that can be used as input mask, to add a new user. We will not add any sanity checks for the input, so be careful. Maybe good for some testing and exploring.
The user control is fairly simple. Create a new folder, name it “UserControls” (or whatever you want) and add a new item of type “Windows Phone User Control”, name it “AddEditUserControl
” (or, you know what I mean…).
Now add a canvas
and resize it to fill the whole area of the user control. To do that, right-click the canvas
and choose Layout=>Reset all.
Place a textblock-control
on the canvas
for the dialog title. Then we need 3 additional textboxes and 3 additional labels. We will edit or add a new SQLiteUser
item here, and we don’t need a field for the id. SQLite will manage that for us. Here my version of the dialog:
Now we need to extend the UserViewModel
, and add a new property of type SQLiteUser
. Call it “CrudUser
”. We will use this property to add a new user or edit a current user. And we need another property of type string
, for the dialog title (Edit, Add). Both of the properties need to notify when their values have been changed to get updated values.
MVVM-Light has already implemented all the required interfaces for us (ViewModelBase
) and we only need to call the method RaisePropertyChanged(“PROPERTYNAME”)
to indicate a value change. We need to do this with standard and not auto-properties, using a backing-field. Here is how to do it:
public SQLiteUser CrudUser
{
get { return crudUser; }
set
{
if (crudUser != value)
{
crudUser = value;
RaisePropertyChanged("CrudUser");
}
}
}
Now we need to bind the dialog-title
and the textbox
es to the CrudUser
property of the UserViewModel
.
So far, so good. But how to show/hide the user-control? We need another property on our view-model named UserControlVisibility
of type Boolean
.
TIP: To save you some time, you can just type “propfull
” where you want to add the new property and Visual Studio will expand a template to create a property with a backing-field for you (a full property).
Add the property change notification, and bind the visibility property of the AddEditUserControl
property to it. That will look good in XAML, but will not work. Why? Because the Visibility
property is not of type Boolean
, it is an enumeration. Therefore, we need what’s called a BooleanToVisibilityConverter
. If you fire up a search in your favorite search engine, you will see thousands of entries and sample code. Anyway, here is the implementation used in this project:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace MVVMWindowsPhone.Converter
{
public class BooleanToVisibilityConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if(value != null)
{
var collapse = (bool)value;
if(collapse)
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
To use the converter, add a new static
resource to App.xaml using the “conv
” namespace. Afterwards, add the converter to the Visibility
binding in AddEditUserControl.xaml.
Now we need one additional command:
We need to know what’s going on. Add this command, and give it a string
parameter. We will use this parameter to distinguish between an ok and cancel, using a string
. The value is “ok
” for the ok button and “cancel
” for the cancel button. Now bind that command to the OK and Cancel buttons of our user control, and set the parameter values.
That’s fine, you might say, but how do we know, if it is an edit or add thing? Simple! We just add a boolean private
property to our UserViewModel
and set it accordingly to false
for edit, and to true
for add. Add a new private boolean property named “addOrEdit
”. And set it to true
, when the AddNewuser
command is called, and to false
, when the edit command is called.
Just one thing is missing. We need to bind the currently selected value of the ListBox
that is showing our users to a property of our UserViewModel
. We add a new property called “SelectedItem
” of type object
and add property changed notification. That way, we have always the selected value to manipulate in our view-model. And set the binding-mode to Two-Way.
Adding and Editing a New User
Let’s pimp-up now our MainPage
. First, add a ListBox
to show our available and future user-entries. Then bind that baby (had a beer, sorry) to the Data
property of our UserViewModel
.
To make the list look better, we add a new items-template. Just a simple one with a stack-panel and the three properties of interest, a background-color and some spacing between the items. The username will be in bold. Not very sexy, but useful. And we add an ItemContainer
style (for TargetType ListBoxItem
) to stretch the content to full width.
This is what it could look like (one test item added):
Add the AddEditUserControl
Now add the AddEditUserControl
to MainPage.xaml and add move it between the title-panel
and the content-panel
. Then move the content-panel
above it, using the Document Outline Window in Visual Studio. It will make it the top-most
element. Stretch it to fill the whole page and set the Visibility
to Collapsed
. This is how it should look before you set the visibility
to collapsed
.
Set the DataContext
of the AddEditUserControl
to {Binding}
. That way, we can remote-control the AddEditUserControl
from within our UserViewModel
.
The code to implement adding a new user is very straight forward:
{
RefreshDataSource(users);
}
}
private async void DialogResultCheck(string obj)
{
if(obj == "ok")
{
if(this.AddOrEdit)
{
var user = await this.repository.AddEntry(this.CrudUser);
this.Data.Clear();
var users = await this.repository.GetAllEntries();
RefreshDataSource(users);
this.CrudUser = new SQLiteUser();
}
else
{
var updatedUser = await this.repository.UpdateEntry(
(SQLiteUser)this.SelectedItem, this.CrudUser
);
this.Data.Clear();
var users = await this.repository.GetAllEntries();
RefreshDataSource(users);
this.CrudUser = new SQLiteUser();
}
this.UserControlVisibility = false;
this.RaiseExecuteChangedDialog();
}
if(obj == "cancel")
{
this.CrudUser = new SQLiteUser();
this.UserControlVisibility = false;
this.RaiseExecuteChangedDialog();
}
}
First it checks, if we have an “ok
” or “Cancel
” result from our AddEditUserControl
. Then, we use the previously mentioned AddOrEdit (Boolean)
property to see if we add a user or edit one. If we add a new user, we simply call the AddEntry
method (awaitable) and add what we found in CrudUser
.
SQLite gives the inserted object automatically and ID and increases it. You can check that, if you set a breakpoint after the call to AddEntry
and inspect the user variable. Then we clear the ObservableCollection
Data, and call GetAllEntries
to get all the current entries in our SQLite table.
And to refresh the ListBox
with the data from the table, we call RefreshDataSource
– a very cool method, found a sample here: Add Elements from IList to ObservableCollection – SO and adapted it.
Afterwards, the visibility of the AddEditUserControl
is set to false
, so that it disappears again. Then, I call RaiseExecuteChangedDialog
to refresh the can execute values, so that all commands are available again on the AppBar
.
Imagine all the wired-up events, not using MVVM!
Editing an entry is nearly similar. There are only two differences. The first is, that it is necessary to create a copy of the selected entry. Assigning the selected entry from the ListBox
to the CrudUser
property would give us just the same version of the object caused by reference.
When the app is starting, the ListBox
is empty. This could indicate to the user that there is something wrong, after a new record was added to the database.
Therefore the existing entries in the database need to be loaded when the ViewModel
is ready. Unfortunately, we can not do this in the constructor, because it is not async and Ninject does not like it when it is creating an instance of the UserViewModel
(or any other class, at least on Windows Phone).
What can we do now? One possibility cloud be to do it in code-behind – meh. There is something better – EventToCommand
, offered by MVVM-Light. This will give us the possibility to bind an event to an command.
The best event to do that is the loaded event of the PhoneApplicationPage
. When this event is fired, it is already present in and was added to the object tree. Usually, EventToComman
’s are added using Expression Blend. I will show you how to do it manually. That way, you will understand it better how to use it.
First, we need to add two new namespaces to Main.xaml:
xmlns:i=”clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity”
xmlns:cmd=”clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform.WP8″
The first namespace gives us access to the triggers that are currently loaded for the specific element. In our case, the PhoneApplicationPage
, because we are in that “scope
” when defining the EventToCommand
. Those triggers are EventTriggers
that are bound to specific RoutedEvents
like the Loaded-Event
(this is the event type used in XAML, but that is another story). What we do now here, is to add an EventTrigger
to the collection of triggers through XAML, that is fired when the Loaded-Event
is fired.
Read more details about Event-Triggers
here: EventTrigger Class –MSDN
The second namespace gives us access to the command implementations of MVVM-Light, where EventToCommand
can be found. It allows us to bind the event to a command. We don’t need a parameter here or something else. Let’s just add a new command called “Init
” and read the existing data from the SQLite table into our Data
property of the UserView
model.
private async void InitData()
{
var users = await this.repository.GetAllEntries();
if (users != null)
{
RefreshDataSource(users);
}
}
That was not too heavy.
Now let’s add the delete functionality. Thanks to MVVM-Light and our architecture, another piece of cake!
Firing Up Message Boxes
Delete
functionality should always be backed by a “security” question. Easy to use extensions like the a message-box are the perfect candidates.
Here is the implementation of the delete-command functionality:
private async void DeleteUser()
{
var result = MessageBox.Show("Delete this item?","CAUTION",MessageBoxButton.OKCancel);
if(result == MessageBoxResult.OK)
{
var deletedUser = this.repository.DeleteEntry((SQLiteUser)this.SelectedItem);
this.Data.Clear();
var users = await this.repository.GetAllEntries();
RefreshDataSource(users);
}
}
Implementing Filtering
To implement filtering (or searching), we add a TextBlock
and a TextBox
to MainPage.xaml:
Then we add another EventToCommand
. This time, we pass the EventArgs
to our ViewModel
. The filter will be executed, when the user hits Enter on the virtual keyboard. We bind the EventToCommand
to the KeyUp
event:
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<cmd:EventToCommand Command="{Binding FilterUsersCommand}"
PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
This time, we have set the PassEventArgsToCommand
property to true
. This will ensure that we have the value of the current key that was pressed available. We add an additional property for the filter text. Name the property “FilterExpression
” and implement the property-change notification.
Filtering should happen, when the enter-key is pressed by the user. To remove the filter and show all records again, the user clears the TextBox
and hits the enter-key. That’s all fine. The standard behavior of a Textbox
on Windows Phone is, that the property changed notification is fired after the TextBox
has lost its focus. This will not work for our solution.
Behaviors to the Rescue!
Behaviors have been introduced with Expression Blend. They are like extensions or overrides for controls, to change an existing behavior or attach a new one.
In our case, we need to modify the binding behavior of the TextBox Text-Property
. We want the TextBox
to update the binding source when the OnTextChanged
event is fired, and not only when losing focus.
The Behaviour<T>
generic class allows us to set the type parameter to TextBox
. When we add the behavior in XAML, it will add itself to the Behaviors attached property of the scope where it is added to (within the filter-textbox).
I found a great implementation of such a behavior on StackOverflow. It was a behavior defined in Prism, and modified for Silverlight.
using System;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interactivity;
namespace MVVMWindowsPhone.Behaviours
{
public class UpdateTextBindingOnPropertyChanged : Behavior<TextBox>
{
private BindingExpression _expression;
protected override void OnAttached()
{
base.OnAttached();
_expression = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
AssociatedObject.TextChanged += OnTextChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.TextChanged -= OnTextChanged;
_expression = null;
}
private void OnTextChanged(object sender, EventArgs args)
{
_expression.UpdateSource();
}
}
}
Essentially, it grabs the binding expression of a TextBox Text-Property
, registers itself to the TextChanged
event of the TextBox
and updates the source of the BindingExpression OnTextChanged
. Simple, effective and works.
The FilterUsersCommand
will only fire, if the key-value passed by the EventToCommand
is the enter key, this is how the CanExecuteFilterCommand-Method
was implemented:
private bool CanExecuteFilterCommand(object arg)
{
var evtArgs = (System.Windows.Input.KeyEventArgs)arg;
if (evtArgs.Key == System.Windows.Input.Key.Enter)
{
return true;
}
return false;
}
The FilterUsers-Method
implementation:
private async void FilterUsers(object obj)
{
if (this.FilterExpression.Length == 1 || string.IsNullOrEmpty(this.FilterExpression))
{
this.Data.Clear();
var users = await this.repository.GetAllEntries();
RefreshDataSource(users);
}
else
{
this.Data.Clear();
var users = await this.repository.GetFilteredEntries
(u => u.UserName.ToLower().Contains(this.FilterExpression.ToLower())
|| u.Image.ToLower().Contains(this.filterText.ToLower()) ||
u.Url.ToLower().Contains(this.FilterExpression.ToLower())
);
RefreshDataSource(users);
}
}
The solution in action
Final Words
You should be able now without any problems to understand the rest of the source code without any problems.
I leave the rest of the implementation up to you. You can re-use most of the source (about 80-90%) to implement the Windows 8 solution.
This post is my (late) Christmas gift in 2013 for all the readers waiting so long for this last part of the series! Thank you again for being so patient!