ASP.NET MVC Outlook styled appointment form with JQuery date and time picker

30 Jun
June 30, 2013

Developers new to ASP.NET MVC often struggle with the transition to JQuery from the built in webforms controls many of them are used to.   The discipline of binding controls such as drop down lists in a model or controller can also trip up developers used to modifying an aspx file to populate a control inline or with an ObjectDataSource.

This article will show you how to create a form that allows users to enter events and appointments, with events able to span multiple days and be all day or at set times.  We’re going to store the date in a single field in the Model, but seperate the date and time components in the View and use Jquery to improve the user experience.

1. Create the Model

The first thing we’re going to do is to create a simple Event model to hold our event data:

namespace Project.Models
{
    public class Event
    {
       public Event()
        {
            StartDate = System.DateTime.Now;
            EndDate = System.DateTime.Now;
        }

        [Key]
        public int EventID { get; set; }

        [Required(ErrorMessage = "Start Date is required.")]
        [DataType(DataType.DateTime)]
        [DisplayName("Start Date")]
        public DateTime StartDate { get; set; }

        [Required(ErrorMessage = "End Date is required.")]
        [DataType(DataType.DateTime)]
        [DisplayName("End Date")]
        public DateTime EndDate { get; set; }

        [Required(ErrorMessage = "Event Name is required.")]
        [MaxLength(100, ErrorMessage = "Event Name cannot be longer than 100 characters.")]
        public string EventName { get; set; }

        public bool AllDayEvent { get; set; }
    }
}

This should be fairly straightforward, we’re using Data Annotations to validate the Model properties.  In the Event contructor we set the start and end date of the event to today, as the default value for creating a new event.

2. Create a ViewModel

Our event model by itself isn’t sufficient for our form.  Our form to create a new event contains 2 drop down lists, one for the start time and one for the end time: Create event form In order to populate the drop down lists with all the times, we’re going to use a ViewModel.  The ViewModel consists of the Event object, a holder for the start and end event times, and the list of all the times that are the options in the drop down lists. Create a ViewModels folder, and add a new class called EventCreateViewModel.cs with the following:

using Project.Models;

namespace Project.ViewModels
{
    public class EventCreateViewModel
    {
        public EventCreateViewModel()
        {
            StartTime = "9:00 AM";
            EndTime = "10:00 AM";
        }
        [DisplayName("Start Time")]
        public string StartTime { get; set; }

        [DisplayName("End Time")]
        public string EndTime { get; set; }

        public Event Event { get; set; }
        public IEnumerable<SelectListItem> Times { get; set; }
    }
}

3. Create the Event Controller GET method

The event controller for our form consists of two methods, a Create GET and a a Create POST. Here is the Create GET method, which populates the ViewModel we previously created:

public ActionResult Create()
{
    // Create a new instance of our View model
    var Model = new EventCreateViewModel();

    // Initialise a new Event - this will set the start and end dates to today
    Model.Event = new Event();
    Model.Times = new[]
    {
        new SelectListItem{ Text="12:00 AM", Value = "12:00 AM" },
        new SelectListItem{ Text="12:30 AM", Value = "12:30 AM" },
        new SelectListItem{ Text="1:00 AM", Value = "1:00 AM" },
        // etc etc ...
    };
    return View(Model);

}

We’ll come back to the POST method, let’s first get the view up and running.

4. Create a View for the event  form

For the view, we’re going to use Twitter Boostrap which is a great front end development Framework to get a site up and running quickly.  The first iteration of the create.cshtml file simply displays the Model elements on the page:

@model Project.ViewModels.EventCreateViewModel

<!DOCTYPE html>
<html lang="en">
<head>
    <link href="/Content/css/bootstrap.css" rel="stylesheet" />
</head>

@using (Html.BeginForm())
{
    <div class="container-fluid">
        <fieldset>
            <legend>Add a new Event</legend>

            <div class="editor-label">
                Event Name
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(model => model.Event.EventName, new { @class = "wide" })
            </div>
            <div class="editor-field">
                <table>
                    <tr>
                        <td style="width: 80px">Start time</td>
                        <td style="width: 100px">@Html.EditorFor(m => m.Event.StartDate)</td>
                        <td>
                            @Html.DropDownListFor(x => x.StartTime, Model.Times, new { @id = "ddlStartTime" })
                        </td>
                        <td style="text-align: left; width: 140px">
                            @Html.CheckBoxFor(m => m.Event.AllDayEvent, new { @id = "chkAllDayEvent" })
                            All day event
                        </td>
                    </tr>
                    <tr>
                        <td style="width: 80px; vertical-align: middle">End time</td>
                        <td>@Html.EditorFor(m => m.Event.EndDate)</td>
                        <td>
                            @Html.DropDownListFor(x => x.EndTime, Model.Times, new { @id = "ddlEndTime"})
                        </td>
                        <td></td>
                    </tr>
                </table>
            </div> 
            <div class="text-center">
                <input type="submit" value="Add Event" class="btn btn-large" />
            </div>
        </fieldset>
    </div>
}
<script type="text/javascript" src="/scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="/scripts/bootstrap.js"></script>

Note how we’ve bound the Times from our ViewModel into a drop down list that store it’s value in the StartTime property and consists of the Times array:

@Html.DropDownListFor(x => x.StartTime, Model.Times, new { @id = "ddlStartTime" })

However before this code can work,  you need to create a new Helper for your dates.

5. Creating a custom date helper

If you have a property in your model of type ‘Date’ then when you display it in your view, the standard syntax is to use the EditorFor method:

@Html.EditorFor(x => x.Created)

Unfortunately this doesn’t let you add HTML Attributes such as class or ID, which we need for our Datepicker.  An alternative is to use the TextBoxFor method:

@Html.TextBoxFor(x => x.Created, new { @class = "datepicker" }) 

But the problem here is that you can’t use DataAnnotations to format your date to remove the time.

So we get around this by creating our own custom helper for Dates.  Create a file called date.cshtml in the /Views/Shared/EditorTemplates/ folder with the following code:

@model DateTime

@Html.TextBox("", Model.ToShortDateString(), new { @class = "datepicker" })

Now you can use the EditorFor function and your dates will be displayed in the ShortDateformat, and support the datepicker which we’re about to integrate.

6. Integrating Bootstap Datepicker

There are many Jquery date pickers you can use, and my personal favourite is a fork of Stefan Petre’s Bootstrap Datepicker by @eternicode.  Eternicode has added a few nice features, most importantly the auto close on click function and an event that fires when the date is changed.  Full details and download links here. To integrate it, simply add the datapicker.css and datepicker.js files to your project and create.cshtml file, then ad the following Javascript:

<script type="text/javascript">
$(".datepicker").datepicker(
    {
        weekStart: 1,
        format: 'dd/mm/yyyy',
        gotoCurrent: true,
        autoclose: true,
        todayBtn: "linked",
        keyboardNavigation: false,
        autoclose: true,
        todayHighlight: true
    });
</script>

Of course, feel free to modify the options to suit your project.

7.  Create the Event Controller POST method

The POST method combines the date and time from our ViewModel and saves it in the database:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(EventCreateViewModel model)
{
    // Get the time portion of our date/time from our drop down lists
    DateTime startTime = Convert.ToDateTime(model.StartTime);
    DateTime endTime = Convert.ToDateTime(model.EndTime);

    // Create a new date based on the date from our date picker, and time from our drop down lists:
    model.Event.StartDate = new DateTime(model.Event.StartDate.Year, model.Event.StartDate.Month, model.Event.StartDate.Day, startTime.Hour, startTime.Minute, startTime.Second);
    model.Event.EndDate = new DateTime(model.Event.EndDate.Year, model.Event.EndDate.Month, model.Event.EndDate.Day, endTime.Hour, endTime.Minute, endTime.Second);

    // Show an error if the end date is before the start date
    if (model.Event.StartDate > model.Event.EndDate)
    {
        ModelState.AddModelError(string.Empty, "The end time has to be after the start time.");
    }
    if (ModelState.IsValid)
    {
        // Save the database record
        db.Events.Add(model.Event);
        db.SaveChanges();
        TempData["success"] = "Your event was successfully added.";
        return RedirectToAction("Index");
    }
    // Validation failed, redisplay the form
    return View(model);
}

8.  Finishing Touches

To finish up, we’re going to improve the user experience with some JQuery. Firstly, if the user selects the All Day option, then diable the time controls:

$("#chkAllDayEvent").click(function () {
    $('#ddlEndTime').attr("disabled", $(this).is(':checked'));
    $('#ddlStartTime').attr("disabled", $(this).is(':checked'));
});

Secondly, when the user sets a Start Date, set the End Date to be the same:

$('#Event_StartDate').on('changeDate', function(e){
    $('#Event_EndDate').val($(this).val())
});

Finally. if the user sets a Start Time, set the End Time to be an hour later”

$("#ddlStartTime").change(function () {
    var selectedValue = $(this).val();

    if (selectedValue == "11:00 PM") {
        $('#ddlEndTime').val("12:00 AM");
    }
    else if (selectedValue == "11:30 PM") {
        $('#ddlEndTime').val("12:30 AM");
    }
    else {
        var startTime = selectedValue.split(':');
        var endHours = parseInt(startTime[0]) + 1;
        endHours = Math.min(Math.max(endHours, 1), 24);
        $('#ddlEndTime').val(endHours + ':' + startTime[1]);
    }

});

And that’s it!

Code First development with the Entity Framework

13 Mar
March 13, 2013

EntityFramework (EF) is data access library that lives in the System.Data.Entity namespace and was first introduced in .NET 3.5 SP1.  Now at version 4.3, Microsoft recently added a code-centric development option known as code first which allows developers to define database models by creating standard classes which Visual Studio then uses to build your SQL database.

The tutorials cover the basics, but for real world projects one of the items that trips up developers is extending the built in membership table to store additional information about users, and then seeding that data when the database is recreated.

This walkthrough will take the Internet Application MVC 4 Visual Studio template and add a number of custom classes, extend the userprofile table, and then seed the database with default values every time the model changes.

1. Create a new MVC 4 project

Open up Visual Studio and create a new Internet Application, as this one uses the SimpleMembership provider that we will extend.

Code First Development with Entity Framework

2. Add references to the Membership and Role providers

Even though the application has membership support built into the template, we will add the provider references into the web.config file in order for EF to create these tables automatically.

Add the following lines in the <system.web> section in your web.config:

    <membership defaultProvider="SimpleMembershipProvider">
      <providers>
        <clear/>
        <add name="SimpleMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
      </providers>
    </membership>
    <roleManager enabled="true" defaultProvider="SimpleRoleProvider">
      <providers>
        <clear/>
        <add name="SimpleRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData"/>
      </providers>
    </roleManager>
  </system.web>

3. Create your data classes.

In this simple example, we’ll have an Article model which contains details of published articles, and link this to a Categories table as shown in the following diagram:

Entity Framework with Code First Development

Create a new class by right clicking on the Models folder, selecting Add and then choosing Class.

Add the following 2 classes:

article.cs

    public class Article
    {
        [Key]
        public int ArticleID { get; set; }

        [Required(ErrorMessage = "Article Title is required.")]
        [MaxLength(200, ErrorMessage = "Article Title cannot be longer than 200 characters.")]
        public string Title { get; set; }

        [DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
        [Display(Name = "Date Published")]
        public DateTime? DatePublished { get; set; }

        [MaxLength(100, ErrorMessage = "Article Author cannot be longer than 100 characters.")]
        public string Author { get; set; }

        public int? CategoryID { get; set; } 
        public virtual Category Category { get; set; }

        public string Body {get; set;}

    }

Note the nullable type for CategoryID, this is the foreign key for the Categories table and defines an optional one to many relationship.

category.cs

    public class Category
    {
        [Key]
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }
        public virtual ICollection<Article> Articles { get; set; }
    }

At this point, we’ll add additional fields to the User Profile table by modifying the AccountModel.cs class.  Replace the definition for UserProfile with the following:

    [Table("UserProfile")]
    public class UserProfile
    {
        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int UserId { get; set; }
        public string UserName { get; set; }

        [Required]
        [Display(Name = "First name")]
        [MaxLength(50, ErrorMessage = "First Name cannot be longer than 50 characters.")]
        public string FirstName { get; set; }

        [Required]
        [Display(Name = "Last Name")]
        [MaxLength(50, ErrorMessage = "Last Name cannot be longer than 50 characters.")]
        public string LastName { get; set; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get
            {
                return FirstName + " " + LastName;
            }
        }

        [Display(Name = "Email Address")]
        [MaxLength(50, ErrorMessage = "Email Address cannot be longer than 50 characters.")]
        public string EmailAddress { get; set; }

        [Display(Name = "Work Phone")]
        [MaxLength(20, ErrorMessage = "Work Phone cannot be longer than 20 characters.")]
        public string WorkPhone { get; set; }

        [Display(Name = "Mobile Phone")]
        [MaxLength(20, ErrorMessage = "Mobile Phone cannot be longer than 50 characters.")]
        public string MobilePhone { get; set; }
    }

4. Configure your database context

We’re going to remove the built-in UsersContext database definition, and move it to it’s own folder.

Delete the following section from the AccountModel.cs file:

    public class UsersContext : DbContext
    {
        public UsersContext()
            : base("DefaultConnection")
        {
        }

        public DbSet<UserProfile> UserProfiles { get; set; }
    }

Create a new folder in your solution (I called this DAL) and add a new class with the following

    public class EvonetContext : DbContext
    {

        public EvonetContext()
            : base("DefaultConnection")
        {
        }
        public DbSet<Article> Articles {get; set;}
        public DbSet<Category> Categories { get; set; }

        public DbSet<UserProfile> UserProfiles { get; set; } 
    }

Now search and replace throughout your solution for any instances of UsersContext and replcce them with what you called your DbContext (in the example above, mine is called EvonetContext).  You now have a single database context for your account and custom classes that EntityFramework will use.

Build your solution to ensure you haven’t made any errors, and have included all the required namespaces.

5. Initialise the Membership Provider

The InitializeSimpleMembershipAttribute is generated by the MVC 4 Internet template and lives in the Filters folder. It provides a lazy initialization of the underlying providers, and creates the database for storing membership, roles, and logins.

Because it can initialised later than when you need to call it, it is recommended you move the InitialiseDatabaseConnection() call to your global.asax file and remove the [InitializeSimpleMembership] call from the top of your AccountController.

Firstly add the InitialiseDatabaseConnection call to the AuthConfig.cs file in the App_Start folder:

        public static void RegisterAuth()
        {

            WebSecurity.InitializeDatabaseConnection(
                "DefaultConnection",
                "UserProfile",
                "UserId",
                "UserName",
                autoCreateTables: true);

        }

Next, delete the InitialiseSimpleMembership.cs file from the Filters folder.

Finally, comment out or delete the [InitialiseSimpleMembership] line from the top of your account controller.

6. Enable Migrations

Now we can enable entity framework code first migrations.  You do this by going to the Package Manager Console and typing in enable-migrations:

PM> enable-migrations
Checking if the context targets an existing database…
Code First Migrations enabled for project Evonet.

This command adds a Migrations folder to your project, which contains a configuration.cs class.  This class allows you to configure how Migrations behaves for your context, and this is where you’ll seed your database with initial values.

7. Seed the database

Modify the code in the configuration.cs class that was created in the Migrations folder when you enabled migrations to initialise Simple Membership, add a test user, and seed the database with some initial data.

We also need to include another call to InitialiseDatabaseConnection or the member functions won’t work – it’s not called from our AuthConfig.cs file when we run the update-database command, hence the duplication.

internal sealed class Configuration : DbMigrationsConfiguration<Evonet.Models.EvonetContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
        }

        protected override void Seed(Evonet.Models.EvonetContext context)
        {
            WebSecurity.InitializeDatabaseConnection(
                "DefaultConnection",
                "UserProfile",
                "UserId",
                "UserName",
                autoCreateTables: true);

            if (!Roles.RoleExists("Administrator"))
                Roles.CreateRole("Administrator");

            if (!WebSecurity.UserExists("test"))
                WebSecurity.CreateUserAndAccount(
                    "test",
                    "password",
                    new
                    {
                        FirstName = "Test",
                        LastName = "Test",
                        EmailAddress = "user@server.com"
                    });

            if (!Roles.GetRolesForUser("test").Contains("Administrator"))
                Roles.AddUsersToRoles(new[] { "test" }, new[] { "Administrator" });

            var categories = new List<Category>
            {
                new Category {CategoryName="Law", Articles = new List<Article>()},
                new Category {CategoryName="Construction", Articles = new List<Article>()},
                new Category {CategoryName="Media", Articles = new List<Article>()},
            };
            categories.ForEach(s => context.Categories.Add(s));

            var articles = new List<Article>
            {
                new Article {Title="An article about Law"},
                new Article {Title="An article about Construction"},
                new Article {Title="An article about Media"},
            };

            articles.ForEach(s => context.Articles.Add(s));
            categories[1].Articles.Add(articles[1]);
            context.SaveChanges();
        }
    }

8. Update the database to reflect changes in the model

Now go to the Package Manager Console and type in update-database – verbose to create (or update) your database to reflect the model changes you have made:

PM> update-database -verbose
Using StartUp project ‘Evonet’.
Using NuGet project ‘Evonet’.
Specify the ‘-Verbose’ flag to view the SQL statements being applied to the target database.
Target database is: ‘Evonet’ (DataSource: (LocalDb)\v11.0, Provider: System.Data.SqlClient, Origin: Configuration).
No pending code-based migrations.
Applying automatic migration: 201304130142483_AutomaticMigration.
CREATE TABLE [dbo].[Articles] (
[ArticleID] [int] NOT NULL IDENTITY,
[Title] [nvarchar](200) NOT NULL,
[DatePublished] [datetime],
[Author] [nvarchar](100),
[CategoryID] [int],
[Body] [nvarchar](max),
[Category_CategoryID] [int],
CONSTRAINT [PK_dbo.Articles] PRIMARY KEY ([ArticleID])
)
CREATE TABLE [dbo].[Categories] (
[CategoryID] [int] NOT NULL IDENTITY,
[CategoryName] [nvarchar](max),
CONSTRAINT [PK_dbo.Categories] PRIMARY KEY ([CategoryID])
)
CREATE TABLE [dbo].[UserProfile] (
[UserId] [int] NOT NULL IDENTITY,
[UserName] [nvarchar](max),
[FirstName] [nvarchar](50) NOT NULL,
[LastName] [nvarchar](50) NOT NULL,
[EmailAddress] [nvarchar](50) NOT NULL,
[WorkPhone] [nvarchar](20),
[MobilePhone] [nvarchar](20),
CONSTRAINT [PK_dbo.UserProfile] PRIMARY KEY ([UserId])
)
CREATE INDEX [IX_CategoryID] ON [dbo].[Articles]([CategoryID])
CREATE INDEX [IX_Category_CategoryID] ON [dbo].[Articles]([Category_CategoryID])
ALTER TABLE [dbo].[Articles] ADD CONSTRAINT [FK_dbo.Articles_dbo.Categories_CategoryID] FOREIGN KEY ([CategoryID]) REFERENCES [dbo].[Categories] ([CategoryID])
ALTER TABLE [dbo].[Articles] ADD CONSTRAINT [FK_dbo.Articles_dbo.Categories_Category_CategoryID] FOREIGN KEY ([Category_CategoryID]) REFERENCES [dbo].[Categories] ([CategoryID])
CREATE TABLE [dbo].[__MigrationHistory] (
[MigrationId] [nvarchar](255) NOT NULL,
[Model] [varbinary](max) NOT NULL,
[ProductVersion] [nvarchar](32) NOT NULL,
CONSTRAINT [PK_dbo.__MigrationHistory] PRIMARY KEY ([MigrationId])
)
BEGIN TRY
EXEC sp_MS_marksystemobject ‘dbo.__MigrationHistory’
END TRY
BEGIN CATCH
END CATCH
[Inserting migration history record]
Running Seed method.

That’s it!  You can now build and run your project, and login with the username of ‘test’ and password of ‘password’

You won’t be able to register a user just yet though, because You’ll bneed to modify the register functions to include the additional fields we defined in our account model.

9. Modifying the registration pages

Firstly, modify the register model in /Models/AccountModel.cs to add the first name and last name:

public class RegisterModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }

        [Required]
        [Display(Name = "First name")]
        [MaxLength(50, ErrorMessage = "First Name cannot be longer than 50 characters.")]
        public string FirstName { get; set; }

        [Required]
        [Display(Name = "Last Name")]
        [MaxLength(50, ErrorMessage = "Last Name cannot be longer than 50 characters.")]
        public string LastName { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }

Now add the fields to the register view in /Views/Account/Register.cshtml

        <legend>Registration Form</legend>
        <ol>
            <li>
                @Html.LabelFor(m => m.UserName)
                @Html.TextBoxFor(m => m.UserName)
            </li>
            <li>
                @Html.LabelFor(m => m.FirstName)
                @Html.TextBoxFor(m => m.FirstName)
            </li>
            <li>
                @Html.LabelFor(m => m.LastName)
                @Html.TextBoxFor(m => m.LastName)
            </li>
            <li>
                @Html.LabelFor(m => m.Password)
                @Html.PasswordFor(m => m.Password)
            </li>
            <li>
                @Html.LabelFor(m => m.ConfirmPassword)
                @Html.PasswordFor(m => m.ConfirmPassword)
            </li>
        </ol>

Finally, modify the Register action in /Controllers/AccountController.cs:

// Attempt to register the user
        try
        {
            WebSecurity.CreateUserAndAccount(model.UserName, 
                                            model.Password,
                                            new { 
                                                FirstName = model.FirstName,
                                                LastName = model.LastName
                                            }, 
                                            false);
            WebSecurity.Login(model.UserName, model.Password);
            return RedirectToAction("Index", "Home");
        }
        catch (MembershipCreateUserException e)
        {
            ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
        }

Now you should be able to register new accounts.

One final note, if you are using SQL LocalDb, don’t ever delete the physical .mdf and .log files for your database without going through the SQL Server Object Explorer in Visual Studio or in SQL Server Management Studio. If you delete the files only, you end up with an error like the following in a web application:

Cannot attach the file ‘…\App_Data\DepartmentDb.mdf’ as database ‘DepartmentDb’.

Or the following error in a desktop app:

SqlException: Cannot open database “DepartmentDb” requested by the login. The login failed.

In this case the database is still registered in LocalDb, so you’ll need to go in the Object Explorer and also delete the database here before SQL Server will recreate the files.

Hope this helps!

Windows 8, three months later

31 Jan
January 31, 2013

Almost 5 months after Windows 8 was released to manufacturing, and 3 months after general availability, Microsoft is being battered by the media for supposedly poor Windows 8 sales figures which have sold slowly compared to Windows 7 and are said to have disappointed Microsoft internally.

Plenty of blame is directed at the gamble that Microsoft took with Windows 8 – a significant re-imagining of the user interface to focus on touch in order to be able to better support tablets, and to provide a ‘no comprise’ experience regardless of computing device.

Having used Windows 8 since the early Beta’s across a multitude of machines, I can now safely say that Windows 8 is significant improvement over prior versions in nearly all aspects, with a confusing user interface that only starts to makes sense with touch enabled hardware.

While touch support is where Windows 8 really shines, it is certainly a worthy upgrade for older hardware.  My three year old Lenovo T420 laptop had it’s boot times improved from 1~2 minutes with Windows 7 down to an average boot time of 10-20 seconds under Windows 8.  In addition, the OS certainly feels snappier, and once you get past the user interface and start relying on the built-in (and vastly improved) settings, application and file search then you’ll find it difficult to go back.

But understanding the new user interface is still the largest hurdle.  The most publicized change is of course the new start menu, replaced with a full page start screen. Microsoft touts the Live tiles as workflow improvements, allowing you to see at a glance your latest email or next calendar appointment.  However, while this dashboard makes sense on a tablet or phone, not so much on a desktop PC or laptop as you will spend very little time in this view, merely using it as a springboard to your next application.

Further confusing the situation is that the Live tiles are all from applications that have been downloaded from the Windows Store, and entirely separate from the traditional desktop applications that you can still install.  While you can add a shortcut to legacy applications on the start screen, they open in the traditional desktop mode, and use different user interface principles.  For example, Windows Store applications are influenced by the formally-known-as-Metro design language and are full screen, don’t have a menu bar, and aim to hide as many options as possible in favour of a clean interface.  This makes task switching and application navigation both confusing and inconsistent.

And unfortunately the built-in metro apps are shockingly bad.  There are a number of Bing apps (News, Weather, Travel etc) and four core ‘productivity’ Apps that Microsoft includes, Mail, Messaging, Calendar, and a glorified contacts manager known as the “People” app. Hopefully they will improve, but at this stage they offer very little value.

In the Mail App for example, you cannot drag mails to a folder and there is no unified inbox view for multiple accounts.  You cannot flag messages or mark them as Junk mail.  Most frustratingly though, there is no integration between the Mail App and the Desktop – if you try to send a file using the right click context menu in Windows Explorer, you’ll be told that there are no applications installed that can do that if you have not installed a desktop mail  application.

The Calendar App is just as poor – for starters it only shows notifications for meetings briefly in the new notification area, so if you’re not at your desktop during those seconds, then good luck remembering your next appointment.  You can create appointments, but not invite people to them, Oh, and you can’t snooze reminders.  All these Apps further suffer from the full screen Metro design paradigm, which may make sense on a tablet, but not on a desktop with a large widescreen display.  Simple tasks, like copying and pasting between Metro applications becomes harder, as you need to switch back and forth between full screen Apps.  (You can also pin a Metro app to the side of your screen, but you are limited in the size of the new window).

If you stick to legacy desktop applications though, there is much to like – this is the second consecutive Windows version that Microsoft has made that requires less resources than it’s predecessor, and the OS just flies.  Start-up and Shutdown – assuming you work out how to –  are crazy fast, file copying is vastly improved (even using the same drivers, file copying was significantly faster than Windows 7, especially across wireless networks), multi-monitor support is great, and I’m a big fan of the new flattened design for windows that replaces Aero for desktop applications.

Windows 8 is really caught in a transition between the PC and post PC era.  In trying to make a single operating system, Microsoft has created a confusing mixture of tablet and desktop metaphors that doesn’t quite gel well together.  Full screen metro apps don’t really make sense on desktops, and on tablets the space taken to support legacy applications means that you can lose almost 40GB of your storage space.  However if the App store gathers enough developer support, and the desktop mode truly becomes only for ‘legacy’ applications, then by the time Windows 9 comes around, this painful journey will be just a distant memory.

Facebook Graph Search

28 Jan
January 28, 2013

Facebook last week announced Social Graph Search, a replacement of the rudimentary built in Facebook search.  Graph Search not only makes anything posted publicly in the Facebook world searchable, but also provides context to those searches by understanding the connections between all that data.  This allows Graph Search to consist of natural language queries that will allow, for example, searches to go beyond “restaurants in New York”  to “restaurants in New York that are liked by my friends (and friends of my friends)”.

It’s certainly much better than the old buil-int search. Graph Search will actually bring up results you’re looking for, and present them beautifully.  However for Graph Search to work well, it’s users needs to have a lot of Facebook friends and they need to be very prolific in the businesses that they interact with, beyond simply clicking Like from a major brand to get a discount or coupon, to reflecting a genuine appreciation towards a local deli or plumber. Unfortunately there is so much distrust in Facebook that the immediate responses from most people I spoke to about Graph Search was to check their privacy settings, clean up their ‘Likes’ and cull back the amount of data they are providing the company.

And if you think that’s just paranoia, check out “Actual Facebook Graph Searches“ , a blog that illustrates what Graph Search is capable of – “Married people who like Prostitutes,  “Islamic men interested in men who live in Tehran, Iran” (where homosexuality is a crime punishable by death),  and “Single women who live nearby and who are interested in men and like Getting Drunk”

Facebook Social Search

If you love Facebook you’ll love this.  Otherwise start deleting your LIkes and tightening up your Privacy settings.

 

 

January 2013 release of the Ajax Control Toolkit adds Charts

25 Jan
January 25, 2013

The Ajax Control Toolkit has been updated, with most of the work going to adding new chart functionality.

The following charts are now available in your projects:

AreaChart

The AreaChart control enables you to render a area chart from one or more series of values. This control can display two types of Areacharts – Basic, Stacked.

AreaChart

BarChart

The BarChart control enables you to render a bar chart from one or more series of values. This control can display four types of BarCharts: Column, StackedColumn, Bar and StackedBar.

BarChart

BubbleChart

The BubbleChart control enables you to render a bubble chart from one or more series of values.

BubbleChart

LineChart

The LineChart control enables you to render a line chart from one or more series of values. This control can display two types of LineCharts: Basic and Stacked.

LineChart

PieChart

The PieChart control enables you to render a pie chart from one or more PieChartValues.

PieChart

The simplest was to install the toolkit is through the Nuget Package Manager:

Install-Package AjaxControlToolkit

Click Frenzy – Online Shopping at Myer

20 Nov
November 20, 2012

At 7pm tonight, Australian retailers will for the first take participate in a local version of US Cyber Monday, an online only event dubbed Click Frenzy.  Click Frenzy many major retailers, including Myer, Target and Dick Smith.

Myer launched their online store early last year, and I thought I’d finally give it a spin in preparation for the sale tonight.

Two things interested me recently that would make a great opportunity to kick the tyres on Myer’s online strategy, a Microsoft XBOX 360, and the Lego Town Hall (part of the Lego town collection http://shop.lego.com/en-US/Town-Hall-10224)

So let’s start by visiting Myer at http://www.myer.com.au

Online shopping at Myer

Ok, there’s a large search box at the top, let’s look for an XBOX:

Online shopping at Myer

There’s a few problems here.  First of all, the banner reading ‘Search Page’ takes up most of my screen, not a really helpful design.  Secondly ‘Your search for xbox on myer.com.au generated a total of 1 results’.  Really? You didn’t take the ‘s’ off ‘results’ when there was only one search item found?

Thirdly, and most importantly, I was expecting a list of search results.  However all may not be lost, there are two helpful links that may find what I want. The first one:

Online shopping at Myer

That’s a bit strange, I thought I was in the shop already, not to worry, what happens when I click on the link?

Online shopping at Myer

Cute, but not helpful.

Still, on the previous page there was a link to Myer’s electical and gaming section, and it looks like Myer at least sell XBOX’s online:

Online shopping at Myer

So let’s click that link…

And it take me to: http://www.myer.com.au/404.aspx?aspxerrorpath=/electrical_gaming.aspx

Online shopping at Myer

Ok, let’s try the Lego set.  This time I at least get a large number of search results, although scanning them shows they don’t really point to any Lego products.  But this is my fault, I now know I’m not looking at the online shop, so I’ll click on the link to ‘Search for “lego” products on our online store.

Online shopping at Myer

Server error again:

Online shopping at Myer

Looking at the URL that this takes me to, http://shop.myer.com.au/webapp/wcs/stores/servlet/MyerGenericJSPErrorView? it’s obvious there are 2 separate sites, www.myer.com.au which is written in ASP.NET, and the shopping cart itself at shop.myer.com.au written in JavaServer pages and the search functionality might be broken between the two.  So I’ll go straight so shop.myer.com.au

shop.myer.com.au

Promising!  And a toy sale too!

Free shipping and lots of Lego items, looks like I’m in luck.

Online shopping at Myer

… except that the range is really limited – I took a stroll over to the Myer shop in Sydney to compare, and there are certainly more than 22 different Lego products on the shelves. And despite the earlier message that I could shop online for XBOX:

Online shopping at Myer

Myer has a long way to go.

Finally, an excellent article by Michael Pascoe in the Sydney Morning Herald sums up the issues really well:

Myer, DJs’ online plans: tell ‘em they’re dreaming

It takes a 10-minute conversation with someone who really knows the business to destroy the announced Myer and David Jones internet ambitions of achieving 10 per cent of sales online. After last week’s effort to talk up department store sales and the hype over tomorrow’s ‘Click Frenzy’ promotion, tell ‘em they’re dreaming.

The pair’s ‘omni channel’ internet strategies – pretty much cut-and-paste jobs from Nordstrom’s annual report – are based on a lack of understanding of the businesses they are trying to copy, fundamental differences between US and Australian retail structures and the simple mistake of comparing apples with oranges. Or perhaps sheep with goats.

 

 

Thoughts on the Microsoft Surface tablet

19 Jun
June 19, 2012

Microsoft today announced two tablets known as Surface PC’s.  The two tablets will both run Windows and be branded as Surface for Windows RT and Surface for Windows 8 Pro.

The Surface for Windows RT tablet uses an ARM processor and will run only Windows 8 Metro apps (including Office 15 RT which will be bundled with the tablet).  The Surface for Windows 8 Pro will run on the Intel chipset and provide a traditional Windows 8 computing experience.

Microsoft managed to keep these projects under wraps until their launch today, and they show some interesting directions the company is going in.  Despite Microsoft historically not being known for their hardware design, it really seems like they finally understand the importance of good design now, with the majority of the presentation given over to superlatives around the design philosophy and process.  The built-in stand and ultra-thin covers that double as a multi touch keyboard are certainly very impressive, although a few eyebrows were raised at the Steve Jobsesque line from Ballmer that it’s a “combination of hardware and software works together to deliver an amazing experience.”.

Ie – We love and need our partners, but the things we do without them help us better compete with Apple.

Pricing was not announced at todays event, but would be “competitive with a comparable ARM tablet or Intel Ultrabook-class” computers, Microsoft said.

 

Translation of Microsoft’s FAQ on Windows 8 Media Center

05 May
May 5, 2012

Some interesting items have come to light in an FAQ that Microsoft has posted to further explain their position on Windows 8 Media Center.  I recommend reading the whole FAQ For an lesson in PR spin, otherwise the translation below will suffice:

FAQ – DVD playback and Windows Media Center in Windows 8

How has Windows handled DVD related decoder licensing prior to Windows 8?

Based on sales and usage, we supplied codecs to a very large number of PCs that were not capable of playing DVDs or simply did not ever play DVDs.

Therefore we were losing money.

Who pays decoder royalties associated with DVD playback on PCs?

According to the MPEG-LA program, the company that ships the end product is responsible for paying. In the case of new PCs with Windows pre-installed, that would be the PC OEMs. The Dolby program for Windows 7 was defined based on an agreement between Dolby and Microsoft where Microsoft has paid Dolby directly for the rights to Dolby Technologies built in Windows 7

Either us or our OEM’s – but either way this makes Windows expensive.

How much does it cost the PC ecosystem to play DVDs?

MPEG-2 decoder costs $2.00 per unit under current MPEG-LA terms. Dolby license is an additional cost that varies by the technology licensed, the type of device, and unit volume. While not related to Windows, Blu Ray would be an additional cost on top of these.

We’re losing LOTS of money

Why can’t I just pay for DVD when I need it?

When we have DVD playback capabilities in software broadly like in Windows 7, there is no way to distinguish whether the PC will ever play a DVD disc but still this cost is carried on every PC.

Because it’s too hard and that’s a silly question anyway.

Will devices with Windows 8 pre-installed be able to play DVDs out of the box?

This is ultimately an OEM choice for what peripherals and software to include in a given system. If a new device has an optical drive, it will most likely include necessary software and licenses making it a seamless experience to the vast majority of customers.

Yes, but only if your OEM has installed the relevant crapware.

What if I upgrade to Windows 8 on my current Windows 7 PC with a DVD drive?

If there is existing third-party playback software the Windows Upgrade Assistant will help determine if this software is compatible with Windows 8 and you will have the option to keep it during the upgrade to Windows 8. Otherwise, you will need to acquire third-party playback software after the upgrade to play DVDs.

You’ll need third party software.

Why can’t I buy a Windows 8 device that includes Windows Media Center pre-installed?

With the evolution of device form factors (tablets, thin and light, etc., none of which have optical drives) and change in media consumption patterns from optical disks and broadcast TV to online (Netflix, Youtube, Hulu, etc.), we concluded that we would no longer make DVD and broadcast TV capabilities available in all Windows editions, simply because the feature applies to a decreasing number of PCs sold.

Because we don’t think you’ll use it.

Are you adding another Windows 8 edition called “Windows 8 Pro with Media Center”?
The Windows 8 Pro edition that includes Media Center will be named and branded Windows 8 Pro. The only difference is that it will include Media Center and you will also find a different string in the system properties where it will say “Windows 8 Pro with Media Center”. This is not a new edition of Windows 8.

We’re not complicated things by adding a new version, as Windows 8 is all about following the simplicity of Apple’s model… but actually we are.

Why do I have to upgrade to Windows 8 Pro to get Media Center?

When we look at actual usage, most customers using Media Center and playing DVDs used Windows Ultimate and XP Pro/Media Center. We believe those customers will also be interested in the additional features provided in the Windows 8 Pro edition, such as Boot from VHD, Client Hyper-V, etc., especially if they are using Media Center on a PC used for general tasks.  Considering the audience and current usage, we conclude the vast majority of Media Center customers upgrading to Windows 8 will be to the Windows 8 Pro edition.

This actually doesn’t make sense to me – if you want a dedicated media center then you want to be able to run virtual machines … ?

What is the Windows 8 Pro Pack and why does it include Media Center?
Windows 8 Pro Pack is an upgrade from Windows 8 to Windows 8 Pro. Like we described above, Media Center is only available on Windows 8 Pro. When you acquire the Pro Pack, we make it a single step that takes you to Windows 8 Pro with Media Center. The cost of the Media Center Pack is essentially built into Pro Pack. Again, this is an attempt to add simplicity to the process of acquiring Media Center.

This is how we make back our royalty fees.

What version of Windows Media Center will be included in Windows 8?
The version of Media Center included in Windows 8 is what we shipped in the Windows 8 Consumer Preview. It is much consistent with what shipped in Windows 7.

Wait, what?  Windows 8 includes the Windows 7 version of Media Center?

Will CableCard and other devices continue to work with Media Center in Windows 8?
Yes, there is no change in hardware supported between Windows 7 and Windows 8.

Seeing as it’s the same version as Windows 7 – yes.

Why doesn’t Windows Media Player support DVD playback even after installing Media Center?
Based on the above discussion, it should be clear that we cannot enable DVD playback all the time in Windows Media Player.  Given the ongoing feedback to avoid feature overlap and to avoid the complexity of behavior changing for a previously installed component, we only enable DVD playback in Media Center once it is installed.

We figure Apple aren’t including DVD devices, so why should we support them?

© Copyright - Evonet