Friday, April 30, 2010

jQuery Access Key Helper




So I was at the SHDNUG last night and watched Todd Anglin give a demonstration on Silverlight 4. One of the really cool things I saw was when he hit a key, all the access keys popped up on the screen. Granted he wasn’t really showing that part off because obviously Silverlight 4 does some cool things, but I thought it was a nice feature to have on an accessible site.

So, I first googled to see if there was something already out there and I did find something by Dave Methvin, but it appeared to be really outdated. Although I did google him and apparently he joined the jQuery team in January of this year. Also, on a side note, his family ratted out Bonnie & Clyde. So he obviously knows his stuff and his family has some guts.

Anyhow, I ultimately decided to start from pretty much scratch and write my own. So…here is the first draft:

$(document).ready(function() {
$(document).keydown(
function(e) {
if (e.keyCode != 18 && !e.altKey)
return;

$(
"[accesskey]").each(function() {
if ($("div#" + $(this).attr("accesskey")).length > 0)
return;

$(
"<div id='" + $(this).attr("accesskey") + "' class='access'>" + $(this).attr("accesskey") + "</div>")
.offset({ top: $(
this).offset().top, left: $(this).offset().left })
.insertAfter($(
this));

$(
this).addClass("accessactive");
});
});

$(document).keyup(
function(e) {
if (e.keyCode != 18 && !e.altKey)
return;

$(
"[accesskey]").each(function() {
$(
"div#" + $(this).attr("accesskey")).remove();
$(
this).removeClass("accessactive");
});
});
});

Basically, it checks to see if the alt key was pressed if not, it returns. If so, it checks to make sure a div doesn’t already exist and then it creates a new div with an id of the accesskey and assigns it a CSS style called access and sets the inner HTML to be the accesskey and finally it inserts it on top of whatever has the accesskey (longest sentence ever). It also adds a class to the item with an accesskey.

The keyup simply removes what was done.

I uploaded it to my code samples and I took advantage of the new page feature on blogger and took Dave Methvin’s HTML sample and applied my jQuery code to it.

Please let me know what you think or if there’s a better way. Thanks for reading!

Version 2

This time I decided to take advantage of the qtip plugin by Craig Thompson instead of working out the best way to position the access keys. With qtip, you can position the access key pretty much where ever you want and it comes with some really nice default styles. It also works in IE, which I suppose is a plus :) So here’s what the new version looks like:

$(document).ready(function () {
$(document).keydown(
function (e) {
if (e.keyCode != 18 && !e.altKey)
return;

$(
"[accesskey]").each(function () {
if ($(this).data('qtip'))
return;

$(
this).qtip({
content: $(
this).attr("accesskey"),
position: { corner: { target:
'rightTop'} },
show: { ready:
true, when: false },
style: { name:
'light' }
});

$(
this).addClass("accessactive");
});
});

$(document).keyup(
function (e) {
if (e.keyCode != 18 && !e.altKey)
return;

$(
"[accesskey]").each(function () {
$(
this).qtip("destroy");
$(
this).removeData('qtip');
$(
this).removeClass("accessactive");
});
});
});

I uploaded the new code sample and I updated the jQuery Access Key Helper demo. Also note that I’m using jQuery 1.4.2 and the latest release of qtip with this change made.

Thanks for reading!


Shout it

kick it on DotNetKicks.com

Wednesday, April 28, 2010

Coding Dojo XP Part 2: TDD & BDD with Nunit, Moq, WatiN, SpecFlow, and MVC 2.0


This post is a continuation of a 3 part series about my first coding dojo experience and what we created. I’m going to assume you’ve read part 1 of this series. Otherwise, you might be lost.

With that said, let me make a correction here to part 1 :)

The feature scenario should’ve been this:

Scenario: Sign Guestbook
Given I am on the sign guestbook page
When I have filled out all required information
When I press sign
Then I should be redirected to the full list of guestbook entries and see a success message

and not this:

Scenario: Sign Guestbook
Given I have filled out all required information
When I press sign
Then I should be redirected to the full list of guestbook entries and see a success message

This means our SignGuestbookDefinitions changed to this:

    [Binding]
public class SignGuestBookDefinitions
{
[
Given(@"I am on the sign guestbook page")]
public void GivenIAmOnTheSignGuestbookPage()
{
var browser = new IE();
browser.GoTo(
"http://localhost:2345/guestbook/sign");
Assert.That(browser.Title, Is.EqualTo("Sign Guestbook"));
}

[
When(@"I have filled out all required information")]
public void GivenIHaveFilledOutAllRequiredInformation()
{
ScenarioContext.Current.Pending();
}

[
When(@"I press sign")]
public void WhenIPressSign()
{
ScenarioContext.Current.Pending();
}

[
Then(@"I should be redirected to the full list of guestbook entries and see a success message")]
public void ThenIShouldBeRedirectedToTheFullListOfGuestbookEntriesAndSeeASuccessMessage()
{
ScenarioContext.Current.Pending();
}
}

Basically, I missed an important step that makes more sense now. Sorry about that!

Let’s go ahead and dig in. So last time I said I’d pick up with the unit tests necessary to get the next step definition working in our SpecFlow feature. So that’s where we are…

First, add a new class library project called Dojo.UnitTests and add a reference to moq, nunit.framework, and the Dojo.UI.

Now we’ll create a new file called GuestbookControllerTests. Basically we’re going to test our controller because that’s where we’re going to throw our view model to our view.

Second, write our test. Here’s what our first controller test looks like:

[TestFixture]
public class GuestbookControllerTests
{
[
Test]
public void Sign_viewdata_model_should_return_SignGuestbookView()
{
var guestbookController = new GuestbookController();
var result = guestbookController.Sign();

Assert.That(result.ViewData.Model, Is.TypeOf(typeof(SignGuestbookView)));
}
}

Now, to make it pass. We need to create a new class called SignGuestbookView, which we’ll place in our Dojo.UI project under the Models folder. For the time being, it doesn’t have any properties. We want to do the bare minimum to get the test to pass.

Run the test and it will fail stating that the value was actually null. So, we have to pass in this new class to our view. To do that, we edit the GuestbookController in our Dojo.UI project. It should now look like this:

        public ViewResult Sign()
{
return View(new SignGuestbookView());
}

Okay, now we’re getting somewhere. We have all green now. However, we don’t really have anything yet. We’re trying to meet the “I have filled out all required information” step definition, so what is all required information? Well, in this case, it’s Name, Email, Comments and all are required. So…let’s keep going.

Let’s go ahead and add our properties to our SignGuestbookView so it’ll look like this:

    public class SignGuestbookView
{
public string Name { get; set; }
public string Email { get; set; }
public string Comments { get; set; }
}

I’m not going to write a test to verify that the get set works for each one. Okay, so let’s create the view. I’ll use the MVC 2.0 syntax instead of my usual fluentHTML. So, here it is:

<%=Html.ValidationSummary(false, "Important Message") %>
<%
using (Html.BeginForm())
{
%>
<fieldset>
<
legend>Sign Guestbook</legend>
<
p><%=Html.LabelFor(f=>f.Name) %><br /><%=Html.TextBoxFor(f=>f.Name) %></p>
<
p><%=Html.LabelFor(f=>f.Email) %><br /><%=Html.TextBoxFor(f => f.Email)%></p>
<
p><%=Html.LabelFor(f=>f.Comments) %><br /><%=Html.TextAreaFor(f=>f.Comments) %></p>
<
input type="submit" value="Sign" />
</
fieldset>
<%} %>

I don’t think this needs any explanation, but basically it uses the model, SignGuestbookView, passed in from the controller. The f in the lambda expression represents the model. I’ve also seen it model=>model.Name, etc, but I like the f because it’s short and to me represents field. When we hit play and browse to guestbook/sign now, we get this view:

image

So now we can go back to our feature step definitions (SignGuestbookDefinitions.cs in the Dojo.Features project) and play with some more WatiN.

So back in our SignGuestbookDefinitions class, in the GivenIHaveFilledOutAllRequiredInformation step, we’re going to fill in our textboxes. It’s pretty easy, here it is:

[When(@"I have filled out all required information")]
public void GivenIHaveFilledOutAllRequiredInformation()
{
_browser.TextField(
Find.ByName("Name")).TypeText("John Doe");
_browser.TextField(
Find.ByName("Email")).TypeText("john@email.com");
_browser.TextField(
Find.ByName("Comments")).TypeText("John wuz here.");
}

That’s it! I love WatiN, it’s really cool for browser automation. OH! I should mention, I made the browser from the first step a private field. You’ll also notice that you don’t have to GoTo a URL again because the step definitions cascade down as I mentioned in the first post.

Okay, we have our next step complete. Now let’s implement the WhenIPressSign.

        [When(@"I press sign")]
public void WhenIPressSign()
{
_browser.Button(
Find.ByValue("Sign")).Click();
}

Done! Next please! Okay, now we’re going to get into some fun stuff. So we need to redirect to a full list of guestbook entries and show a success message. So let’s handle the redirect first. Let’s write the test. I’m going to be using the MvcContrib.TestHelper for these tests. Here it is:

        [Test]
public void Sign_should_redirect_when_no_model_errors_present()
{
var result = _controller.Sign(new SignGuestbookView());

result.AssertActionRedirect().ToAction(
"Index");
}

The AssertActionRedirect is part of the MvcContrib.TestHelper…VERY HANDY! Make sure to download the new version that is compatiable with MVC 2.0. So when we run this, it fails because we’re not redirecting currently, so let’s fix that problem.

        [HttpPost]
public RedirectToRouteResult Sign(SignGuestbookView model)
{
return RedirectToAction("Index");
}

Okay, run the test again and we get green! Now what? Well, let’s go check our step and see what we need to do next. We should show a success message and a full list of entries. Let’s take care of the success message now. Here’s the new test:

[Test]
public void Sign_should_redirect_when_no_model_errors_present_and_pass_a_success_flag()
{
var result = _controller.Sign(new SignGuestbookView());

result.AssertActionRedirect()
.WithParameter(
"showSuccess", true)
.ToAction(
"Index");
}

So, let’s implement this to work in the Sign(SignGuestbookView model) action:

        [HttpPost]
public RedirectToRouteResult Sign(SignGuestbookView model)
{
return RedirectToAction("Index", new {showSuccess = true});
}

Now we need to make a change to our Index action like this:

        public ActionResult Index(bool showSuccess)
{
if (showSuccess)
ViewData[
"Success"] = "true";

return View();
}

Okay, let’s setup the display message in the Index view. This view just has this:

<html xmlns="http://www.w3.org/1999/xhtml" >
<
head runat="server">
<
title>View Guestbook</title>
</
head>
<
body>
<
div>
<%=Html.DivSuccessMessage("Guestbook entry successfully added.") %>
</div>
</
body>
</
html>

The DivSuccessMessage is a helper I wrote to check the ViewData[“Success”]. You can read more about my favorite extensions in an old post. Okay, I think we’re done with the success message. So, now we need to show a full list of guestbook entries. Well, I’m not sure how to test this one without assuming we have guestbook entries, so I’ll handle that with unit tests and in our step definition, we’re just going to make sure we’re on the right page. So, let’s go ahead and setup our final step definition and run those tests. They should all pass now. Here’s the final implementation:

[Then(@"I should be redirected to the full list of guestbook entries and see a success message")]
public void ThenIShouldBeRedirectedToTheFullListOfGuestbookEntriesAndSeeASuccessMessage()
{
Assert.That(_browser.Title, Is.EqualTo("View Guestbook"));
Assert.That(_browser.Div(Find.ByClass("success-message")).Text, Is.EqualTo("Guestbook entry successfully added."));
}

So this verifies that we’re on the right page and that our success message is displayed. You should see this:

image

I love seeing green! So, obviously we’re not done with everything required for our feature, but we’re close and have the basics all setup and configured. What’s nice is that we have built and tested the functionality and the application is ignorant of our actual implementation…because right now we don’t have any implementation. That’s a good thing. We should be able to build a whole guestbook app without ever actually knowing where we’re going to store the data. I think this is probably the safest way to develop because you know you’re not tying your application to a particular implmentation. Anyhow…let’s keep going.

First things first, we need to save our SignGuestbookView in the Sign action. Here’s our first test:

[Test]
public void Sign_should_save_and_redirect_when_no_model_errors_present()
{
var result = _controller.Sign(new SignGuestbookView());
_repository.Verify(rep=>rep.Save(
new GuestbookEntry()));

result.AssertActionRedirect().ToAction(
"Index");
}

I’m using moq to verify that a save method was called on our repository. Let’s make this pass. First we need a repository…should look like this:

    public interface IGuestbookRepository
{
void Save(GuestbookEntry model);
}

Now we need the GuestbookEntry, which is our domain model representation of a guestbook entry.

    public class GuestbookEntry
{
public string Name { get; set; }
public string Email { get; set; }
public string Comments { get; set; }
}

Now we need to define the repository in our test class like this:

private readonly Mock<IGuestbookRepository> _repository = new Mock<IGuestbookRepository>();

Okay, now we need to implement our controller action like so:

        public ActionResult Sign(SignGuestbookView model)
{
if (ViewData.ModelState.IsValid)
{
_repository.Save(model.CreateGuestbookEntry());
return RedirectToAction("Index", new {showSuccess = true});
}

return View(model);
}

Now we have to pass in our _repository in the controller’s constructor like this:

        private readonly IGuestbookRepository _repository;
public GuestbookController(IGuestbookRepository repository)
{
_repository = repository;
}

In order to finally get it to pass, I had to override the equals on the GuestbookEntry so moq could pick up the Save actually being called. So here are the tests for that implementation:

[TestFixture]
public class GuestBookEntryTests
{
[
Test]
public void GuestbookEntry_should_equal_another_GuestbookEntry_if_email_addresses_match()
{
var entry = new GuestbookEntry();

Assert.That(entry, Is.EqualTo(new GuestbookEntry()));
}

[
Test]
public void GuestbookEntry_should_not_equal_another_GuestbookEntry_if_email_addresses_do_not_match()
{
var entry = new GuestbookEntry();

Assert.That(entry, Is.Not.EqualTo(new GuestbookEntry{Email = "donotmatch@email.com"}));
}
}

Here’s the implementation:

        public override bool Equals(object obj)
{
var other = obj as GuestbookEntry;
return other != null && other.Email == Email;
}

Okay, so now all we need to do is display the guestbook entries. Well, here’s the test:

[Test]
public void Index_should_return_all_entries()
{
_repository.Setup(rep => rep.GetAll()).Returns(
new List<GuestbookEntry> {new GuestbookEntry()});
var result = _controller.Index(false);

Assert.That(result.AssertViewRendered().ViewData.Model, Is.TypeOf(typeof (List<GuestbookEntry>)));
Assert.That(((IList<GuestbookEntry>)result.AssertViewRendered().ViewData.Model).Count, Is.EqualTo(1));
}

I’m using moq again here to GetAll from my repository and I return a list with just one GuestbookEntry in it. I call the Index action and pass in false because I don’t care about showing the success message. Then I assert that the model is of type list<guestbookentry> then I assert that there is one record in the list.

So, let’s get this test to pass. We need to add a new method to the repository called GetAll like this:

    public interface IGuestbookRepository
{
void Save(GuestbookEntry model);
IList<GuestbookEntry> GetAll();
}

Also, just FYI, I added a new project called Dojo.Core and added a domain folder & interfaces folder and put the GuestbookEntry and IGuestbookRepository in that project. Anyhow, let’s keep going…so now all we need to do is implement the index action like this:

       public ActionResult Index(bool showSuccess)
{
if (showSuccess)
ViewData[
"Success"] = "true";

return View(_repository.GetAll());
}

Now we have all green. Alright, that’s it for this one. Feels good to see this:

image

Next post will have some of the implementation items in it. I probably won’t post every little detail like I have in this one and part 1, but I will provide the downloadable source. I did not get to the EmitMapper because I ended up just creating the CreateGuestbookEntry() on the view. I actually think I may have left that out of this post, but it is in the downloadable project.

Please note that when downloading the projects, you need to have SpecFlow installed, VS 2008 SP1 & MVC 2.0. Also, I’m going to add in a dummy repository and an empty constructor to the GuestbookController so the project will run without errors when you download it. I’ll tie in StructureMap in the next post.

I hope you enjoyed the post. Thanks for reading!

Download the Part 2 Solution

Shout it

kick it on DotNetKicks.com

Sunday, April 25, 2010

Coding Dojo XP Part 1: TDD & BDD with Nunit, WatiN, SpecFlow, and MVC 2.0


0

Saturday, I experienced my first coding dojo. I didn’t know what to expect, if I should prepare, what I should bring, etc. Well, I thought I’d let you know.

My experience was a good one. There were only 5 developers there, so it was easy to converse and ask questions. We talked about primarily TDD with nunit, moq, and WatiN. We also briefly talked about BDD with SpecFlow. It seems like you can’t really be prepared for a coding dojo. Just come with what you know and hopefully you’ll learn something new.

I brought my laptop this time, but ended up not pulling it out. Our dojo wasn’t like a typical coding dojo like the one talked about in the “How do you put on a coding dojo event?” video on YouTube. To learn more about coding dojos, check out codingdojo.org.

Now, for what I learned! I’m going to do 3 posts showing what we went over at the dojo. Part 1, this post, will be only about SpecFlow and how to set it up for your projects. Basically our leader/sensei came in and said pretend you were just handed a user story that says “Visitors to our website will be able to sign a guestbook”. So, with that user story in mind, we setup our SpecFlow. Here’s how:

1. Download SpecFlow

2. Run the installer

3. Create a new project called Dojo.Features (Dojo in our case, you can obviously call it whatever you want)

4. Add new SpecFlowFeature item to the project. We called it SignGuestBook.feature

image

5. SpecFlow provides an example, but this is what our’s looked like (NOTE: I couldn’t remember exactly what our’s looked like, but this is very similar :)

Feature: Sign
In order to sign our guestbook
As a visitor
I want to be able to say that I was at the site

Scenario: Sign Guestbook
Given I have filled out all required information
When I press sign
Then I should be redirected to the full list of guestbook entries and see a success message

As you can see, this is very readable and useful for BDD. The language used for the features is Gherkin, which is also used by Cucumber.

6. At this point, you can run your tests on the project. If you have Resharper, you can right-click the project name and click “Run Unit Tests”. If you have TestDriven.net, you can right-click and click “Run Test(s)”.

Obviously it will fail. Should look something like this:

image

7. Let’s make it pass. First we’ll copy the code it provides to us, which is this:

    [Binding]
public class StepDefinitions
{
[Given(@"I have filled out all required information")]
public void GivenIHaveFilledOutAllRequiredInformation()
{
ScenarioContext.Current.Pending();
}
}

8. Create a new SpecFlowStepDefinition item called SignGuestBookDefinitions. It will give you more samples, but we’re going to delete all of them. You could also just create an empty class and paste exactly what’s above in it, but I wanted to show you both ways.

So our final SignGuestBookDefinitions class should look like this:

using TechTalk.SpecFlow;

namespace Dojo.Features
{
[
Binding]
public class SignGuestBookDefinitions
{
[
Given(@"I have filled out all required information")]
public void GivenIHaveFilledOutAllRequiredInformation()
{
ScenarioContext.Current.Pending();
}
}
}

9. Run the tests again. It should fail again at the next step. Should look like this:

image

Notice the second line says –> pending: …

Then it goes on to the next step definition and fails because we haven’t set it up. Let’s do that now, here it is:

using TechTalk.SpecFlow;

namespace Dojo.Features
{
[
Binding]
public class SignGuestBookDefinitions
{
[
Given(@"I have filled out all required information")]
public void GivenIHaveFilledOutAllRequiredInformation()
{
ScenarioContext.Current.Pending();
}

[
When(@"I press sign")]
public void WhenIPressSign()
{
ScenarioContext.Current.Pending();
}

[
Then(@"I should be redirected to the full list of guestbook entries and see a success message")]
public void ThenIShouldBeRedirectedToTheFullListOfGuestbookEntriesAndSeeASuccessMessage()
{
ScenarioContext.Current.Pending();
}
}
}

If you reword your feature, you will need to change the matching attribute. There has to be a better way to set this up, but this was my first experience with SpecFlow and I’m basically recreating what we did on Saturday. So if you know another way or have blogged another way, please feel free to comment and your link!

10. Run the tests again and guess what, we fail again because everything is pending. It’s important to note that the given, when, then cascade, so you won’t always have an assert in each area.

11. Create your first implementation, which is our Given(). We used WatiN to do our testing for this part of the project, so you’ll need to download WatiN and reference it in the features project. Here’s what the first implementation looks like:

        [Given(@"I have filled out all required information")]
public void GivenIHaveFilledOutAllRequiredInformation()
{
var browser = new IE();
browser.GoTo(
"http://localhost:2345/guestbook/sign");
Assert.That(browser.Title, Is.EqualTo("Sign Guestbook"));
}

WatiN is a really cool UI testing tool. The two browsers that you can create an instance of are IE & Firefox. Generally speaking if it works in IE, it’s going to work in Firefox. So the next line is a goto, which tells WatiN to browse to that URL. Finally we do our nunit assert that the browser title matches “Sign Guestbook”.

12. Run your tests again and if you used IE instead of Firefox, you’ll most likely get this error:

“System.Threading.ThreadStateException: The CurrentThread needs to have it's ApartmentState set to ApartmentState.STA to be able to automate Internet Explorer. at WatiN.Core.IE.CheckThreadApartmentStateIsSTA()”

It’s REALLY annoying, but easily fixed. Just add an app.config file to your project and put this in it:

<?xml version="1.0" encoding="utf-8"?>
<
configuration>
<
configSections>
<
sectionGroup name="NUnit">
<
section name="TestRunner" type="System.Configuration.NameValueSectionHandler"/>
</
sectionGroup>
</
configSections>
<
NUnit>
<
TestRunner>
<!--
Valid values are STA,MTA. Others ignored. -->
<
add key="ApartmentState" value="STA" />
</
TestRunner>
</
NUnit>
</
configuration>

I also seem to remember our lead putting this “[assembly: NUnit.Framework.RequiresThread(System.Threading.ApartmentState.STA)]” at the end of the assemblyinfo.cs, but I didn’t find it necessary when re-creating the solution. I could’ve also just been imagining things…who knows.

13. Run your tests again and you should see IE launch and then your test fail because it doesn’t have anything at that URL yet. So you should get this error:

Expected string length 14 but was 44. Strings differ at index 0.

Expected: "Sign Guestbook"

But was:  "Internet Explorer cannot display the webpage"

14. Let’s make it pass.

  1. Add a new project to your solution, we used MVC 2.0, so that’s what I’m going to do now too. I’m going to call it Dojo.UI. Set the new project to be the startup project.
  2. Right-click the MVC project name and go to properties
  3. Click the Web tab, and click “Specific Port” and change the port to match the URL we are using above. In my case, 2345.
  4. Create a new Controller in the Controllers folder called “GuestbookController”
  5. Add a Sign method to the new controller that returns a ViewResult
  6. Create a new folder in the “Views” folder called “Guestbook
  7. Create a new View called Sign.aspx
  8. Change the title to be “Sign Guestbook”
  9. Hit F5 and add to the address bar guestbook/sign

Okay, now we’re all set up. Let’s run our tests again. Now our first step passes, but the others still fail. Well, I hate seeing red, but the other two are going to have to wait because we have to write a lot more unit tests and development to get those to pass. So what do we do? Well, I’m going to have to ignore them so our bar stays yellow and we know there are some more tests we have to touch. Let’s do that real quick.

In the app.config in the Dojo.Features project, make it look like this:

<?xml version="1.0" encoding="utf-8"?>
<
configuration>
<
configSections>
<
sectionGroup name="NUnit">
<
section name="TestRunner" type="System.Configuration.NameValueSectionHandler"/>
</
sectionGroup>
<
section name="specFlow" type="TechTalk.SpecFlow.Configuration.ConfigurationSectionHandler, TechTalk.SpecFlow"/>
</
configSections>
<
specFlow>
<
unitTestProvider name="NUnit" />
<
runtime missingOrPendingStepsOutcome="Ignore" />
</
specFlow>
<
NUnit>
<
TestRunner>
<!--
Valid values are STA,MTA. Others ignored. -->
<
add key="ApartmentState" value="STA" />
</
TestRunner>
</
NUnit>
</
configuration>

15. Let’s run our tests again. You should see this:

image

Okay, that was part 1 of my coding dojo experience. SpecFlow & WatiN are really cool tools and obviously you’ll see more of them in later posts because we still have two definitions to implement.

What’s next:

Part 2: TDD with nunit & moq. We’ll also start building out the sign page in our MVC project. I also might try to throw in EmitMapper for our mapping to see the differences between it and AutoMapper.

Part 3: Repository implementation with Fluent NHibernate

Hopefully you enjoyed this post as much as I enjoyed learning about this stuff at the dojo. Thanks for reading!

Download the Part 1 Solution

Shout it

kick it on DotNetKicks.com

Thursday, April 08, 2010

Google Calendar Management with MVC & StructureMap




I recently read John Petersen’s blog entitled “Adding Google Charts to your ASP MVC Applications” and I thought it was a pretty cool read. I thought I’d do a similar one taking advantage of the Google Data API SDK.

In order to get started, you’ll need to do some setup by following the awesome instructions Google provides called Getting Started with the .NET Client Library to setup the SDK.

Let’s get to the code!

First things first, create a calendar repository interface like this:

    public interface ICalendarRepository
{
void Add(Activity activity);
void Delete(string activityId);
Activity GetById(string activityId);
Activity[] GetAll();
}

Now we just need to impelment it. I’ll go through some of this, but I think it’s pretty self-explanatory if you just read through it. I may be wrong :)

    public class GoogleCalendarRepository : ICalendarRepository
{
private const string GoogleCalendarFeed = "http://www.google.com/calendar/feeds/{0}/private/full";

private readonly string _calendarId;
private readonly CalendarService _calendarservice;
public GoogleCalendarRepository(string calendarId, string googleUsername, string googlePassword)
{
_calendarId = calendarId;
_calendarservice =
new CalendarService("Google Calendar");
_calendarservice.setUserCredentials(googleUsername, googlePassword);
}

private string GoogleCalendarFeedWithId
{
get { return string.Format(GoogleCalendarFeed, _calendarId); }
}

public void Add(Activity activity)
{
var entry = new EventEntry(activity.Title, activity.Description, activity.Location);

var eventTime = new When(activity.StartDate, activity.EndDate);
entry.Times.Add(eventTime);

var postUri = new Uri(GoogleCalendarFeedWithId);
var eventEntry = _calendarservice.Insert(postUri, entry);

activity.Id = eventEntry.EventId;
}

public void Delete(string activityId)
{
_calendarservice.Delete(getEventBy(activityId));
}

public Activity GetById(string activityId)
{
return getActivityFrom((EventEntry) getEventBy(activityId));
}

public Activity[] GetAll()
{
var query = new EventQuery();
query.Uri =
new Uri(GoogleCalendarFeedWithId);
var feed = _calendarservice.Query(query);

var activities = new List<Activity>();
foreach(var e in feed.Entries)
activities.Add(getActivityFrom((
EventEntry)e));

return activities.ToArray();
}

private static Activity getActivityFrom(EventEntry eventEntry)
{
return new Activity
{
Description = eventEntry.Content.Content,
StartDate = eventEntry.Times[
0].StartTime,
EndDate = eventEntry.Times[
0].EndTime,
Id = eventEntry.EventId,
Location = eventEntry.Locations[
0].ValueString,
Title = eventEntry.Title.Text
};
}

private AtomEntry getEventBy(string eventId)
{
try
{
var entryQuery = new EventQuery();
entryQuery.Uri =
new Uri(string.Format(GoogleCalendarFeedWithId + "/{0}", eventId));
var result = _calendarservice.Query(entryQuery);
if (result.Entries.Any())
return result.Entries[0];
}
catch {}

throw new EventNotFound();
}
}

Line 1. I inherit from my ICalendarRepository
Line 2. Open Bracket
Line 3. Constant string for the Google Uri – notice I put the {0} in there so I can plop my calendar id in its place later on.
Line 4. Line break…just kidding…I’ll just explain each group of code from here on out.

In the constructor, I take in the calendar id with a google username and google password that are authorized to modify/read the calendar. I played with a couple other authentication methods, but nothing worked as well as the good ole setUserCredentials, so I stuck with it.

NOTE: Get your Calendar Id by following these steps:

  1. Click the dropdown arrow next to your calendar name at http://www.google.com/calendar
  2. Select Calendar Settings
  3. At the bottom of the page, you’ll see “Calendar Address” look to the right of the three images shown in that section and you’ll see the Calendar ID.

After the constructor, I have a private read-only property that just formats my Google Uri with my calendar id.

Also worth noting is that I’m only referencing these namespaces: Google.GData.Calendar; Google.GData.Client; Google.GData.Extensions; in this repository. This is the ONLY place the Google namespace is mentioned in my project.

The Add method is simple enough. It accepts an activity, creates an EventEntry, and uses the calendar service that’s initialized in our constructor to insert the event and then it sets the activity.Id to the new id created.

The Delete method is even easier. It calls the Delete method on the calendar service and the getEventBy() private method to get the event via an EventQuery. I extracted that code out because I was using the code in my Delete and in the next method, GetById.

In GetById, I call another private method called getActivityFrom(), which does the same thing that AutoMapper would have done, but this was easier for this sample. I also needed to extract this code because I was doing it in the GetById & GetAll methods. I obviously could’ve cleaned this method up and added more validation like checking if the collections are null before just referencing them, but this is an example :)

That’s it for the repository!

The EventNotFound exception thrown in the getEventBy method looks like this if you’re wondering:

    public class EventNotFound : Exception
{
public override string Message
{
get { return "Event not found. Activity.Id is required."; }
}
}

The activity class looks like this:

    public class Activity
{
public string Id { get; set; }
public string Title { get; set; }
public string Location { get; set; }
public string Description { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}

Let’s create the controller real quick as an example of how to use this repository. I’m not going to show the view code because this thing is already pretty long :)  If you think it’s necessary, feel free to comment!

    public class ActivityManagementController : Controller
{
private readonly ICalendarRepository _calendarRepository;

public ActivityManagementController(ICalendarRepository calendarRepository)
{
_calendarRepository = calendarRepository;
}

public ViewResult Add()
{
return View(new Activity());
}

[
AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken]
public ViewResult Add(ActivityView activity)
{
_calendarRepository.Add(
Mapper.Map(activity));

return View(activity);
}

public ViewResult View(string activityId)
{
var activity = _calendarRepository.GetById(activityId);

return View(activity);
}

public ViewResult ViewAll()
{
var activities = _calendarRepository.GetAll();

return View(activities);
}

public ViewResult Delete(string activityId)
{
_calendarRepository.Delete(activityId);

return View();
}
}

The most important thing to note here is the controller constructor. What it allows us to do is inject whatever repository we want into the controller via StructureMap. So, let’s take a look at the StructureMap configuration.

    public class DependencyRegistry : Registry
{
protected override void configure()
{
Scan(x =>
{
x.TheCallingAssembly();
x.WithDefaultConventions();
});

ForRequestedType<
ICalendarRepository>()
.TheDefault.Is.OfConcreteType<
GoogleCalendarRepository>()
.WithCtorArg(
"calendarId").EqualToAppSetting("calendarId")
.WithCtorArg(
"googleUsername").EqualToAppSetting("googleUsername")
.WithCtorArg(
"googlePassword").EqualToAppSetting("googlePassword");
}
}

That’s all you’ll need to get this thing up and running. If you get the “No parameterless constructor…” error, then you need to look at rolling out your own controller factory that leverages StructureMap. You can see an example in my demo MVC app.

Well, I guess that’s it for now. Obviously there are more items we could include in our Activit object that Google has in their’s, but I mainly wanted to show how to get it setup. Anyhow, I hope you enjoyed it and please feel free to comment.

Thanks for reading!


Shout it

kick it on DotNetKicks.com

Related Posts Plugin for WordPress, Blogger...