Windows Azure Developer Challenge – Day Four (May 2nd)

Currently working on the input screens for conferences and speakers. I really love the MVC framework, both how easy it is to use for common scenarios such as validation, and also how easy it is to extend through ModelBinders, DisplayTemplates etc. Some cool things I’ve discovered:

Display Templates/Editor Templates

Each conference has a TimeZoneId, such as (UTC-04:00) Atlantic Time (Canada).This is stored as a string property on the Conference e.g.

public class Conference
{
public string TimeZoneId { get; set; }
...
}

The advantage of just storing this as a string rather than a TimeZoneInfo is that I don’t need to write a custom modelbinder or custom validator as it’s just a plain old string, so the framework can take care of binding and validating it when it’s a mandatory field etc.

When adding/editing a conference I want to be able to display a dropdown list of all timezones, and have this automatically bound to the conference. To achieve this, I used code from the http://romikoderbynew.com/2012/03/12/working-with-time-zones-in-asp-net-mvc/ and omitted the custom ModelBinder as I didn’t need it. I created a new Editor Template in /Views/Shared/EditorTemplates named TimeZone, and also in /Views/Shared/DisplayTemplates as follows:

@* Thanks to http://romikoderbynew.com/2012/03/12/working-with-time-zones-in-asp-net-mvc/*@
@model string
@{
    var timeZoneList = TimeZoneInfo
        .GetSystemTimeZones()
        .Select(t => new SelectListItem
        {
            Text = t.DisplayName,
            Value = t.Id,
            Selected = Model != null && t.Id == Model
        });
}
@Html.DropDownListFor(model => model, timeZoneList)
@Html.ValidationMessageFor(model => model)

This will handle displaying a dropdown with all timezones, however, I needed to tell the framework that when rendering the TimeZoneId property on a Conference it should use this template… and it turned out to be really easy! I just had to add a UiHint to the TimeZoneId property and it automagically wired it up. E.g

[Required]
[<strong>UIHint("TimeZone"), Display(Name = "Time Zone")]</strong>
public string TimeZoneId { get; set; }

And that’s it! Now when I call .DisplayFor or .EditorFor in my views for the TimeZoneId property it automatically renders this template. In the view it looks like this:

</pre>
<div class="editor-label">@Html.LabelFor(model => model.TimeZoneId)</div>
<div class="editor-field">@Html.EditorFor(model => model.TimeZoneId)
 @Html.ValidationMessageFor(model => model.TimeZoneId)</div>
<pre>

and on-screen:

5TimeZoneDropdownList

BOOM!!!

Validation

Well that turned out to be as easy as adding the right attributes to the properties I wanted to validate. You’ll see above I added the [Required] attribute to the TimeZoneId property, which ensures a user has to enter it. I also added the [Display] attribute with a more user-friendly property name.

Azure Table storage issues when updating a conference

When storing conferences, I used “Conferences” as the PartitionKey, and the conference HashTag as the RowKey, as each conference should have a unique HashTag. My UpsertConference code is as follows:

public void UpsertConference(Conference conference)
{
    //Wrap the conference in our custom AzureTableEntity
    var table = GetTable("Conferences");
    var entity = new AzureTableEntity()
    {
        PartitionKey = "Conferences",
        RowKey = conference.HashTag,
        Entity = JsonConvert.SerializeObject(conference)
    };
    TableOperation upsertOperation = TableOperation.InsertOrReplace(entity);
    // Insert or update the conference
    table.Execute(upsertOperation);
}

Unfortunately this means that if I were to update a conference’s HashTag, a new record would be inserted as the .InsertOrReplace code thinks it’s a completely new entry. To work around this, I had to find the old conference record first using the old HashTag, delete it, then Insert the conference again with the new HashTag. It feels a bit clunky, especially since it’t not wrapped in a transaction or batch, but as I mention in my comments, this is something I’ll be refactoring to use SQL Server in Part 3 of the competition, so I’m not stressing too much over it at the moment. The updated code is as follows:

public void DeleteConference(string hashTag)
{
    var table = GetTable("Conferences");
    TableQuery<AzureTableEntity> query = new TableQuery<AzureTableEntity>();
    TableOperation retrieveOperation =
      TableOperation.Retrieve<AzureTableEntity>("Conferences", hashTag);
    TableResult retrievedResult = table.Execute(retrieveOperation);
    if (retrievedResult.Result != null)
    {
        TableOperation deleteOperation = TableOperation.Delete((AzureTableEntity)retrievedResult.Result);
        // Execute the operation.
        table.Execute(deleteOperation);
    }
}
/// <span class="code-SummaryComment"><summary>
</span>/// Inserts or updates a conference
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="hashTag">The hashTag of the existing conference
</span>// (for updates) or the hashTag of the new conference (for inserts)</param>
/// <span class="code-SummaryComment"><param name="conference">The conference itself</param>
</span>public void UpsertConference(string hashTag, Conference conference)
{
    //Wrap the conference in our custom AzureTableEntity
    var table = GetTable("Conferences");
    //We're using the HashTag as the RowKey, so if it gets changed
    // we have to remove the existing record and insert a new one
    //Yes I know that if the code fails after the deletion we could be left
    // with no conference.... Maybe look at doing this in a batch operation instead?
    //Once I move this over to SQL for part 3 we can wrap it in a transaction
    if (hashTag != conference.HashTag)
    {
        DeleteConference(hashTag);
    }
    var entity = new AzureTableEntity()
    {
        PartitionKey = "Conferences",
        RowKey = conference.HashTag,
        Entity = JsonConvert.SerializeObject(conference)
    };
    TableOperation upsertOperation = TableOperation.InsertOrReplace(entity);
    // Insert or update the conference
    table.Execute(upsertOperation);
}

public void DeleteConference(string hashTag)
{
    var table = GetTable("Conferences");
    TableQuery<AzureTableEntity> query = new TableQuery<AzureTableEntity>();
    TableOperation retrieveOperation =
      TableOperation.Retrieve<AzureTableEntity>("Conferences", hashTag);
    TableResult retrievedResult = table.Execute(retrieveOperation);
    if (retrievedResult.Result != null)
    {
        TableOperation deleteOperation =
          TableOperation.Delete((AzureTableEntity)retrievedResult.Result);
        // Execute the operation.
        table.Execute(deleteOperation);
    }
}
/// <span class="code-SummaryComment"><summary>
</span>/// Inserts or updates a conference
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="hashTag">The hashTag of the existing conference
</span>// (for updates) or the hashTag of the new conference (for inserts)</param>
/// <span class="code-SummaryComment"><param name="conference">The conference itself</param>
</span>public void UpsertConference(string hashTag, Conference conference)
{
    //Wrap the conference in our custom AzureTableEntity
    var table = GetTable("Conferences");
    //We're using the HashTag as the RowKey, so if it gets changed
    // we have to remove the existing record and insert a new one
    //Yes I know that if the code fails after the deletion we could be left
    // with no conference.... Maybe look at doing this in a batch operation instead?
    //Once I move this over to SQL for part 3 we can wrap it in a transaction
    if (hashTag != conference.HashTag)
    {
        DeleteConference(hashTag);
    }
    var entity = new AzureTableEntity()
    {
        PartitionKey = "Conferences",
        RowKey = conference.HashTag,
        Entity = JsonConvert.SerializeObject(conference)
    };
    TableOperation upsertOperation = TableOperation.InsertOrReplace(entity);
    // Insert or update the conference
    table.Execute(upsertOperation);
}  

CRUD

I’ve found it fairly easy to perform simple CRUD operations using Table storage thus far, with the minor issue relating to updating an entity’s RowKey. While developing locally I used Development storage by setting my web.config storage connection string as follows <add key=”StorageConnectionString” value=”UseDevelopmentStorage=true” />. In order to get this working in the cloud I just had to setup a storage account and update my Azure Cloud settings as per http://www.windowsazure.com/en-us/develop/net/how-to-guides/table-services/

I created a storage account name youconf, then copied the primary access key. I then went to the websites section, selected my youconf site, clicked Configure, then added my StorageConnectionString to the app setttings section with the following value:

DefaultEndpointsProtocol=https;AccountName=youconf;AccountKey=[Mylongaccountkey] 

Date/time and TimeZone fun

I’ve had to do a bit more work than expected with the date/times, given that when a conference is created, the creator can select a start/end date/time, and also a timezone. The same goes for a Presentation, which has a start date/time, duration, and timezone.

Initally I was going to store them in local format, along with the timezone Id (as they appear to be stored in dotNetConf from reading Scott’s blog post). However, after doing some reading on the subject of storing date/time information, I gathered that it’s best to store datetimes in UTC, then convert them into either the user’s timezone, or your chosen timezone (such as the event timezone) as close to the UI as possible. This allows for easier comparisons in server-side code, and also makes it easy to order Conferences and presentations by date/time E.g.

@foreach (var presentation in Model.Presentations.OrderBy(x =&gt; x.StartTime))

http://stackoverflow.com/questions/2532729/daylight-saving-time-and-timezone-best-practices seems to be an article that I keep coming back to whenever I do anything involving datetimes and different timezones, and I read it once again to re-familiarise myself with how to go about things.

So, a user enters the datetime in their chosen timezone, selects the timezone from a dropdown list, and hits Submit. In order to store the date in UTC I have to have code such as this in the Controller, or possibly in a ModelBinder (I haven’t tried using a Custom ModelBinder yet though)

var conferenceTimeZone = TimeZoneInfo.FindSystemTimeZoneById(conference.TimeZoneId);
conference.StartDate = TimeZoneInfo.ConvertTimeToUtc(conference.StartDate, conferenceTimeZone);
conference.EndDate = TimeZoneInfo.ConvertTimeToUtc(conference.EndDate, conferenceTimeZone);

… then to render it back out again in the local timezone I created a custom EditorTemplate called LocalDateTime.cshtml. Note that I also add a date class onto the input field, so that I can identify and date fields using jQuery when wiring up a date time picker (more on that later).

@model DateTime
@{
    var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById((string)ViewBag.TimeZoneId);
    var localDateTime = Model.UtcToLocal(localTimeZone);
}
@Html.TextBox("", localDateTime.ToString(),
  new { @class = "date",
  @Value = localDateTime.ToString("yyyy-MM-dd HH:mm") })

.. and to use this template, I can either decorate the relevant properties on my Conference/Presentation classes with a UIHint, or specify the editor template directly from another view. For example, here’s some of the code from /Views/Conference/Edit.cshtml:

@Html.LabelFor(model => model.StartDate)
@Html.EditorFor(model => model.StartDate, "LocalDateTime",
  new { TimeZoneId = Model.TimeZoneId }) @Html.ValidationMessageFor(model => model.StartDate)

Note that 2nd parameter which specifies the editor template that I want to use. I also pass in the TimeZoneId of the conference as a parameter to the LocalDateTime editor template.

The UI – How to display date/times?

I was investigating how best to render date/times, and was initially looking at using dual input boxes, with one holding the date, and one holding the time, as per yet another of Scott’s articles. However, after getting partway through implementing that, I discovered an amazing jQuery datetimepicker plugin at http://trentrichardson.com/examples/timepicker/ which extends the existing jQuery datepicker.

By using that I was able to get away with having a single input box containing both the date AND time, along with a nice picker to help users. It really is cool, and only takes a single line of code to add:

$(function () {
            $(".date").datetimepicker({ dateFormat: 'yy-mm-dd' });
        });

… and the resulting UI looks pretty good to me!

6DateTimePicker

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s