Archive for the 'Dev Stuff' Category

13
Jul
11

Back button broken for URL hashes in IE on WP7 Mango

I’ve been on a little twitter rant recently about this, and thought I’d use my blog as another distribution medium.

Feel free to leave comments here, here (connect forum) or here (jQuery Mobile forum).

Here’s the content of my connect forum post on the matter:

Mango seems to have brought along with it a broken browser…

When you link to an anchor on your page using a hash, the back button doesn’t seem to work too well.

For example, go to http://jquerymobile.com/demos/1.0b1/. Those in the know will realise why… To put it simply, jQuery Mobile uses hash changes to navigate between “pages” in a single or multiple page web application. It uses the hash changed events to determine when to use javascript to request the next page, load it into the DOM and perform an animation to display the next page.

Navigating forward on a jQuery Mobile site works perfectly as expected. It’s when you hit the back button that things go wrong. On a desktop browser, going from the landing page to “Into to jQuery Mobile” swipes in the intro page and hitting back swipes it out again to show the landing page. However, on Mango, hitting the back button when on the intro page does nothing. If you were on another site before you went the the demo landing page, then hitting the back button again will take you back to the previous site.

I have been working on an embedded web application recently and this has been driving me crazy! I’m seeing a Navigated event being raised by the WebBrowser control when it goes to the second page in the application, but back buttons (and manually invoking history.back() and history.go(-1) in javascript) just don’t do anything.

This seems to be a breaking change to me and would potentially mean I push to have Windows Phone 7 dropped as a targetted platform for the (relatively high traffic) website I am working on. An embarrassing idea seeing as I pushed for it to be targetted… :-(

UPDATE 14 July 2011 – It’s been noted that the jQuery Mobile docs site mentioned does work in previous builds of WP7, running IE 7 Mobile. This verifies that this issue has been introduced with IE9 Mobile on WP7 Mango.

UPDATE II 14 July 2011 - The issue is a little deeper than I initially realized. If you define a simple page with an anchor at the top of the page referencing a div at the bottom of the page then we see some more exotic behaviour. Clicking the link and hitting back will work the first time. However, you’ll notice that in the address bar the hash is not removed from the URL. This means that clicking the link and hitting back again will get the browser into a tangle. Here is the HTML snippet I used:

<a href="#bottom">bottom</a>
<div style="height: 1000px"></div>
<a id="bottom" href="#" onclick="javascript:history.back();return false;">back</a>

UPDATE III October 2011 - The issue appears to have been fixed in Mango RTM.

07
Jun
11

Making MVC 3 a little more… dynamic

The other day I ran into a little problem. I wanted to be able to post some JSON to a web service and have it accept a whole bunch of data (not exactly the technical term) that was not predefined.

Given this very vague problem, I decided I wanted to use ASP .NET MVC 3. So starting with File\New Project, I added a method to my HomeController as follows:

[HttpPost]
public ActionResult DoSomething(string a, string b, dynamic c)
{
    return new EmptyResult();
}

The thought behind this was that I wanted to supply two required properties (a and b) and basically have a dynamic bag for everything else. Also, I didn’t want to be restricted to a single layer of properties. Instead, I wanted to be able to pass a deep tree and have it totally accessible.

Then I pulled up fiddler and set up a request with the following details:

Method POST
URL http://localhost:2643/Home/DoSomething
Request Headers User-Agent: Fiddler

Content-Type: application/json

Request Body { a: “Hello”, b: “World”, c: { d: “this”, e: “is”, f: “dynamic” } }

image

When I hit execute and set a breakpoint on the action, I received the following values in my locals:

clip_image002

As expected, the JsonValueProviderFactory has kicked in and populated the values of a and b using the JSON provided. However, it cannot match c properly so simply throws an object at it that is not dynamic and does not have values on it, hence useless.

After some searching, I found a few valid solutions. The most useful one was this blog post by Anoop Madhusundanan. In this post, he describe the process of using a model binder and a custom model binder attribute to identify parameters of an action method that are to use the model binder. This seemed like a great solution for me, but had a couple of little problems… Most importantly, Anoop’s solution does not allow for multiple parameters on an action method or mapping the parameter to the property in the JSON object.

I came up with a very similar way of solving this problem. I created a class named DynamicJsonBinder which implements IModelBinder, just like Anoop. The main difference with my solution is that I provided a way to ensure that the parameter being populated with the dynamic object is mapped based on the name of the parameter. This is done by using a switch on the attribute called MatchName, which will cause the binder to look at the parameter name and find the matching property in the JSON object to return.

Here’s the attribute:

using System.Web.Mvc;

public class DynamicJsonAttribute : CustomModelBinderAttribute
{
    public override IModelBinder GetBinder()
    {
        return new DynamicJsonBinder(MatchName);
    }

    public bool MatchName { get; set; }
}

And here’s the model binder:
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Web.Helpers;
using System.Web.Mvc;

public class DynamicJsonBinder : IModelBinder
{
    private readonly bool matchName;

    public DynamicJsonBinder(bool matchName)
    {
        this.matchName = matchName;
    }

    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var contentType = controllerContext.HttpContext.Request.ContentType;
        if (!contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
            return null;

        string bodyText;
        using (var stream = controllerContext.HttpContext.Request.InputStream)
        {
            stream.Seek(0, SeekOrigin.Begin);
            using (var reader = new StreamReader(stream))
                bodyText = reader.ReadToEnd();
        }

        if (string.IsNullOrEmpty(bodyText)) return null;

        var desiralized = Json.Decode(bodyText);

        if (!matchName) return desiralized;

        var members = desiralized.GetDynamicMemberNames() as IEnumerable&lt;string&gt;;
        return members == null
            || members.Contains(bindingContext.ModelName)
            ? desiralized[bindingContext.ModelName] : null;
    }
}

So if by adding a DynamicJson attribute to the c parameter on the action method, we’ll get the following:
public ActionResult DoSomething(string a, string b, [DynamicJson] dynamic c)
{
    return new EmptyResult();
}

Debugging into this with the same post request as earlier:

clip_image004

We see that c has been populated with a DynamicJsonObject (the result of the call to Json.Decode) and that it has recursively mapped the properties under the JSON object to dynamic object. However, the value is the entire JSON object, that is it’s not mapped to the c property.

To enable name matching we get the following code:

[HttpPost]
public ActionResult DoSomething(string a, string b, [DynamicJson(MatchName = true)] dynamic c)
{
    return new EmptyResult();
}

Debugging we get:

clip_image006

And we see that the property has been matched correctly and the correct value is passed through to the parameter.

Using these two simple classes and a bit of dynamic know-how, I’ve managed to post anything to my web service and have it dynamically accessible. This basically means we have the equivalent of a property bag on steroids that can be queried and manipulated in the same way as any dynamic object. We can even re-serialize the dynamic object to JSON and store it as is if required.

25
Aug
10

A Day in the Life of a Metro-veloper

This is a follow-up post to my Windows Phone 7 presentation last week at the SDDN.

I have uploaded my powerpoint deck to SlideShare.

Some of the sample apps I used are available on MSDN.

And there was a recent video on youtube of an awesome golfing app that really shows the power of the WP7 UX.

13
Aug
10

push notifications in windows phone 7

Basics

There’s lots of information hiding within about 10 links on MSDN about how push notifications for WP7. The short of it is the phone opens a HttpNotificationChannel which gives it a unique URI for your applications to send notifications to it.

There are three types of notifications – tile, toast and raw. Tile notifications are used for changing the background, count and title of the application when it is pinned to the Start screen. Toast notifications provide unobtrusive notifications to users when they are outside the application, allowing them to step in easily to perform an action. Raw notifications are messages of any format that can be sent to the phone application and received while it is active.

The problem with the information out there is that it is slightly conflicting because of the recent changes to the APIs with different versions of the toolkit. As a result, I spent many hours trying to get this relatively simple piece of functionality working. At one point I decided that whenever I got something working I’d try shrink wrap it and publish it, so here we go…

DISCLAIMER: WOMM…

WARNING: There’s a lot of code here… Please read the Client Side and Server Side sections before believing you are qualified to download the sample. The sample has a thin WPF client implementation over the server side code to make it a little easier to get started. Remember to check the Debug output window for the phone application when you need a Uri to send a notification.

Client Side

My aim was to get the code required for subscribing to notifications to be as simple as possible. I got it down to the following few lines of code:

// in App.ctor
NotificationService notificationService = new NotificationService("some funky channel name");
notificationService.RawNotificationReceived += RawNotificationReceived;
notificationService.ToastNotificationReceived += ToastNotificationReceived;
notificationService.ChannelUriUpdated += ChannelUriUpdated;

// in Application_Launching & Application_Activated
notificationService.Subscribe();

// event handlers
void ToastNotificationReceived(object sender, ToastNotificationReceivedEventArgs e)
{    // example of handling a toast notification within the application
    MessageBox.Show(e.Message, e.Title, MessageBoxButton.OK);
}

void RawNotificationReceived(object sender, RawNotificationRecievedEventArgs e)
{
    MessageBox.Show(e.Message);
}

void ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
{
    // call a webservice to report e.ChannelUri
}

There are a couple of things to note. Firstly, the Subscribe() method is called in the Application_Launching and Application_Activated events. This is due to the tombstoning nature of WP7.

Next, there are separate event handlers for Toast and Raw notifications. They are handled differently because toast notifications have a definite payload format that specifies the ability to send to pieces of text (a title and a message). A raw notification can be anything, which is why I have decided to simply surface the string representation of the payload. A toast must also be handled if the application is executing – i.e. it will not be displayed by the OS.

Also, there is no tile notification handler. This is because the OS handles this directly.

Lastly, the ChannelUriUpdated event handler. This is necessary for your phone application to let the server application know that there is a phone waiting for notifications. When this occurs, the phone application should call a web service to register the URI. The NotificationService class will write this information to the Debug console whenever the application is started, so it is not required for debugging purposes.

Server Side

While I was busy working on the client side, I realised that obviously I’d eventually need to have some server side code to send the notifications. Again, I wanted to simplify it as much as possible:

NotificationService service = new NotificationService();

// send a raw notification
service.SendRaw(clientUri, messageText);

// send a toast notification
service.SendToast(clientUri, titleText, messageText);

// send a tile notification
service.SendTile(clientUri, backgroundImageUri, countValue, titleText);

See… simple. Smile

The Real Code

As I mentioned earlier, you can download all this here. Otherwise, feel free to read through this (or just copy/paste it) to get a better understanding of how the HttpNotificationChannel works.

Client side NotificationService implementation:

public class NotificationService
{
    string channelName;
    HttpNotificationChannel channel;

    public NotificationService(string channelName)
    {
        this.channelName = channelName;
    }

    /// <summary>
    /// Subscribes to the notification events on the channel.
    /// If the channel doesn't already exist, it will be created
    /// and bound to shell tile and toasts.
    /// </summary>
    public void Subscribe()
    {
        if (channel == null) BindChannel();
    }

    /// <summary>
    /// Unubscribes from the notification events on the channel.
    /// </summary>
    public void Unsubscribe()
    {
        if (channel != null) UnsubscribeFromChannelEvents();
    }

    /// <summary>
    /// Finds or creates the notification channel and binds the shell tile
    /// and toast notifications as well as events.
    /// </summary>
    private void BindChannel()
    {
        channel = HttpNotificationChannel.Find(channelName);

        if (channel == null || channel.ChannelUri == null)
        {
            if (channel != null) DisposeChannel();

            channel = new HttpNotificationChannel(channelName);
            channel.ChannelUriUpdated += channel_ChannelUriUpdated;
            channel.Open();
        }
        else System.Diagnostics.Debug.WriteLine(channel.ChannelUri.AbsoluteUri);

        SubscribeToChannelEvents();

        if (!channel.IsShellTileBound) channel.BindToShellTile();
        if (!channel.IsShellToastBound) channel.BindToShellToast();
    }

    /// <summary>
    /// Subscribes to the channel's events.
    /// </summary>
    private void SubscribeToChannelEvents()
    {
        channel.ShellToastNotificationReceived += channel_ShellToastNotificationReceived;
        channel.HttpNotificationReceived += channel_HttpNotificationReceived;
        channel.ErrorOccurred += channel_ErrorOccurred;
    }

    /// <summary>
    /// Unsubscribes from the channel's events
    /// </summary>
    private void UnsubscribeFromChannelEvents()
    {
        channel.ShellToastNotificationReceived -= channel_ShellToastNotificationReceived;
        channel.HttpNotificationReceived -= channel_HttpNotificationReceived;
        channel.ErrorOccurred -= channel_ErrorOccurred;
    }

    /// <summary>
    /// Closes the channel and disposes it.
    /// </summary>
    private void DisposeChannel()
    {
        channel.Close();
        channel.Dispose();
        channel = null;
    }

    /// <summary>
    /// Event handler for the ChannelUriUpdate event.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void channel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
    {
        channel.ChannelUriUpdated -= channel_ChannelUriUpdated;
        System.Diagnostics.Debug.WriteLine(e.ChannelUri.AbsoluteUri);
        OnChannelUriUpdated(e);
    }

    /// <summary>
    /// Raised when the notification channel is given a URI.
    /// </summary>
    /// <remarks>
    /// This is when you would call a web service to tell it that a client is
    /// registered and what the notification URI is.
    /// </remarks>
    public event EventHandler<NotificationChannelUriEventArgs> ChannelUriUpdated;

    /// <summary>
    /// Raises the ChannelUriUpdated event.
    /// </summary>
    /// <param name="e"></param>
    protected virtual void OnChannelUriUpdated(NotificationChannelUriEventArgs e)
    {
        if (ChannelUriUpdated != null) ChannelUriUpdated(this, e);
    }

    /// <summary>
    /// Event handler for the HtppNotificationReceived event.
    /// This is called when a raw notification is received.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void channel_HttpNotificationReceived(object sender, HttpNotificationEventArgs e)
    {
        byte[] bytes;
        using (var stream = e.Notification.Body)
        {
            bytes = new byte[stream.Length];
            stream.Read(bytes, 0, (int)stream.Length);
        }
        var message = Encoding.UTF8.GetString(bytes, 0, bytes.Length);

        OnRawNotificationReceived(message);
    }

    /// <summary>
    /// Occurs when a raw notification is received.
    /// </summary>
    public event EventHandler<RawNotificationRecievedEventArgs> RawNotificationReceived;

    /// <summary>
    /// Raises the RawNotificationReceived event on the UI thread.
    /// </summary>
    /// <param name="message"></param>
    protected virtual void OnRawNotificationReceived(string message)
    {
        Deployment.Current.Dispatcher.BeginInvoke(() =>
        {
            if (RawNotificationReceived != null) 
                RawNotificationReceived(
                    this, 
                    new RawNotificationRecievedEventArgs(message)
                    );
        });
    }

    /// <summary>
    /// Event handler for the ShellToastNotificationReceived event.
    /// This occurs when a toast notification is received on the channel.
    /// </summary>
    /// <remarks>
    /// This must be handled by the application if it is running when a toast is received.
    /// </remarks>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void channel_ShellToastNotificationReceived(object sender, NotificationEventArgs e)
    {
        var title = e.Collection.Values.First();
        var message = e.Collection.Values.Skip(1).FirstOrDefault() ?? string.Empty;
        OnToastNotificationReceived(title, message);
    }

    /// <summary>
    /// Occurs when a toast notification is received.
    /// </summary>
    /// <remarks>
    /// This must be handled by the application if it is running when a toast is received.
    /// </remarks>
    public event EventHandler<ToastNotificationReceivedEventArgs> ToastNotificationReceived;

    /// <summary>
    /// Raises the ToastNotificationReceived event on the UI thread.
    /// </summary>
    /// <param name="title"></param>
    /// <param name="message"></param>
    protected virtual void OnToastNotificationReceived(string title, string message)
    {
        Deployment.Current.Dispatcher.BeginInvoke(() =>
        {
            if (ToastNotificationReceived != null) 
                ToastNotificationReceived(
                    this, 
                    new ToastNotificationReceivedEventArgs(title, message)
                    );
        });
    }

    /// <summary>
    /// Event handler for the ErrorOccurred event.
    /// Handles different events according to ErrorType.
    /// </summary>
    /// <remarks>
    /// Needs more work... ;-(
    /// </remarks>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void channel_ErrorOccurred(object sender, NotificationChannelErrorEventArgs e)
    {
        switch (e.ErrorType)
        {
            // something went severely wrong. lets wait a while before trying again.
            case ChannelErrorType.ChannelOpenFailed:
                DisposeChannel();
                System.Threading.Thread.Sleep(60000);
                BindChannel();
                break;
            // an image uri has been referenced in a notification that was
            // not bound to the shell tile.
            case ChannelErrorType.MessageBadContent:
                break;
            // too many notifications have been received in too short a time span.
            case ChannelErrorType.NotificationRateTooHigh:
                break;
            // a bad payload was received. re-establish the connection to overcome this.
            case ChannelErrorType.PayloadFormatError:
                DisposeChannel();
                BindChannel();
                break;
            // the type notifications we're receiving is going to change.
            case ChannelErrorType.PowerLevelChanged:
                break;
            default:
                break;
        }
    }
}

public class RawNotificationRecievedEventArgs : EventArgs
{
    public string Message { get; private set; }
    public RawNotificationRecievedEventArgs(string message)
    {
        Message = message;
    }
}

public class ToastNotificationReceivedEventArgs : EventArgs
{
    public string Title { get; private set; }
    public string Message { get; private set; }
    public ToastNotificationReceivedEventArgs(string title, string message)
    {
        Title = title;
        Message = message;
    }
}

Server side code:

public interface INotificationService
{
    NotificationResponse SendTile(string uri, string backgroundImageUri, int count, 
                                  string title, [Optional] Guid messageId);
    NotificationResponse SendToast(string uri, string text1, [Optional] string text2, 
                                   [Optional] Guid messageId);
    NotificationResponse SendRaw(string uri, string message, [Optional] Guid messageId);
}

public class NotificationService : INotificationService
{
    const int maxPayloadLength = 1024;

    const string targetHeader = "X-WindowsPhone-Target";
    const string notificationClassHeader = "X-NotificationClass";
    const string messageIdHeader = "X-MessageID";
    const string notificationStatusHeader = "X-NotificationStatus";
    const string subscriptionStatusHeader = "X-SubscriptionStatus";
    const string deviceConnectionStatusHeader = "X-DeviceConnectionStatus";

    const string tileMessageFormat =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
        "<wp:Notification xmlns:wp=\"WPNotification\">" +
            "<wp:Tile>" +
                "<wp:BackgroundImage>{0}</wp:BackgroundImage>" +
                "<wp:Count>{1}</wp:Count>" +
                "<wp:Title>{2}</wp:Title>" +
            "</wp:Tile>" +
        "</wp:Notification>";
    /// <summary>
    /// X-WindowsPhone-Target: token
    /// </summary>
    const string tileTarget = "token";
    /// <summary>
    /// X-NotificationClass: 1
    /// </summary>
    const string tileNotificationClass = "1";

    const string toastMessageFormat =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
        "<wp:Notification xmlns:wp=\"WPNotification\">" +
            "<wp:Toast>" +
                "<wp:Text1>{0}</wp:Text1>" +
                "<wp:Text2>{1}</wp:Text2>" +
            "</wp:Toast>" +
        "</wp:Notification>";
    /// <summary>
    /// X-WindowsPhone-Target: toast
    /// </summary>
    const string toastTarget = "toast";
    /// <summary>
    /// X-NotificationClass: 2
    /// </summary>
    const string toastNotificationClass = "2";

    /// <summary>
    /// X-NotificationClass: 3
    /// </summary>
    const string rawNotificationClass = "3";

    public NotificationResponse SendTile(string uri, string backgroundImageUri, int count, 
                                         string title, [Optional] Guid messageId)
    {
        var str = string.Format(tileMessageFormat, backgroundImageUri, count, title);
        return SendNotification(uri, str, tileNotificationClass, tileTarget, messageId);
    }

    public NotificationResponse SendToast(string uri, string text1, [Optional] string text2, 
                                          [Optional] Guid messageId)
    {
        var str = string.Format(toastMessageFormat, text1, text2);
        return SendNotification(uri, str, toastNotificationClass, toastTarget, messageId);
    }

    public NotificationResponse SendRaw(string uri, string message, 
                                        [Optional] Guid messageId)
    {
        return SendNotification(uri, message, rawNotificationClass, messageId: messageId);
    }

    private NotificationResponse SendNotification(string uri, string message, 
                                                  string notificationClass, 
                                                  [Optional] string target, 
                                                  [Optional] Guid messageId)
    {
        var payload = Encoding.UTF8.GetBytes(message);
        if (payload.Length > maxPayloadLength) 
            throw new ArgumentException(
                "The message provided is longer than the maximum payload length (1024B).",
                message
                );

        var sendNotificationRequest = WebRequest.Create(uri) as HttpWebRequest;
            
        sendNotificationRequest.Method = WebRequestMethods.Http.Post;
        sendNotificationRequest.ContentLength = payload.Length;
        sendNotificationRequest.ContentType = "text/xml";

        // X-NotificationClass: 1, 2, 3
        sendNotificationRequest.Headers.Add(notificationClassHeader, notificationClass);
        // X-WindowsPhone-Target: token, toast
        if (!string.IsNullOrWhiteSpace(target)) 
            sendNotificationRequest.Headers.Add(targetHeader, target);
        // X-MessageId: 00000000-0000-0000-0000-000000000000
        if (messageId != null && messageId != Guid.Empty) 
            sendNotificationRequest.Headers.Add(messageIdHeader, messageId.ToString());

        using (var requestStream = sendNotificationRequest.GetRequestStream())
            requestStream.Write(payload, 0, payload.Length);

        HttpWebResponse response;
        string errorMessage = null;
        try
        {
            response = sendNotificationRequest.GetResponse() as HttpWebResponse;
        }
        catch (WebException ex)
        {
            response = ex.Response as HttpWebResponse;
            errorMessage = ex.Message;
        }
        return new NotificationResponse
        {
            MessageId = response.Headers[messageIdHeader],
            ErrorMessage = errorMessage,
            NotificationStatus = response.Headers[notificationStatusHeader],
            SubscriptionStatus = response.Headers[subscriptionStatusHeader],
            DeviceConnectionStatus = response.Headers[deviceConnectionStatusHeader],
            StatusCode = response.StatusCode
        };
    }
}

public class NotificationResponse
{
    public string ErrorMessage { get; set; }
    public DateTimeOffset Timestamp { get; set; }
    public string MessageId { get; set; }
    public HttpStatusCode StatusCode { get; set; }
    public string NotificationStatus { get; set; }
    public string DeviceConnectionStatus { get; set; }
    public string SubscriptionStatus { get; set; }
}

DOWNLOAD IT NOW!

Remember, to get the Uri just watch the Debug output window when your WP7 application starts up.

Good luck Smile

[Update 2010-08-27] Added extra check to Client.NotificationService.BindChannel() so that a channel that already exists but does not have a URI is disposed and a new one is created.

30
Dec
09

Commerce Server 2009 R2 and Visual Studio 2010

So you’re a Commerce Server developer that’s sitting on the bleeding edge…? Well now you’ve got the same chance of starting your site as easily as you did with CS2007 and VS2008 – i.e. not much. Why, you ask…? Because the team have not updated the template that they use for the Project Creation Wizard addin, so it’s just as useful as it always has been… =p

Let’s see how it works…

Using the Project Creation Wizard

Just like in the old days of CS2007 and pre-R2, hit File > New Website and you’ll get the “New Web Site” wizard. Select your language of choice (C# is the better one) and “Commerce C# ASP.NET Web Application”.

image

Although for some odd reason, they’ve decided you can’t use the file system or any non-localhost url (like above), so at least when you first create the site you need to do so under localhost.

If you’re a real developer, you’ll say yes to this too… :-)

image

You’ll then have the Commerce Server Site Packager application pop up to unpack a default web site. This will ask for the site url, but not ask any of the good old questions like what you want to name the application directories, meaning you’re stuck with a crappy prefix on every directory it unpacks.

Anyway, once that’s done you should have an unpacked beginning of a web site. If you go to this point without a couple of COM errors, then congratulations. But now you’ll also notice that the project created was a “Web Site” project instead of a “Web Application” project.

So what’s next… oh yeah! Find another way to do it that actually makes sense.

Manual Site Creation

If you’ve been through the process of using the Commerce Server Site Package application to extract a site in previous versions of commerce server, then you’re not going to learn much here… nothing has changed! If you haven’t, please read on.

Well if you’ve been through the Project Creation Wizard then you have two things up your sleeve, you have a good web.config to start from and a csapp.ini file that points to the original PUP file used to unpack the empty website. If you haven’t, then don’t worry – I’ve uploaded the Commerce Server 2009 Starter Files to my SkyDrive and I can tell you that the original PUP file lives at “C:\Program Files (x86)\Microsoft Commerce Server 9.0\Extensibility Kits\Samples\Pup Packages\empty.pup”.

Now you can open the Commerce Server Site Package application manually at “C:\Program Files (x86)\Microsoft Commerce Server 9.0\PuP.exe”. It will ask you if you want to package or unpackage, but if you don’t have a site on your machine package will be disabled and unpackage will be selected. After hitting next, select the PUP file mentioned above and select Custom Unpack then hit next again. Then select create a new site and hit next.

Now you need to type a name into Site Name text box that does not conflict with any existing sites and click next – e.g. CommerceSample. This name is used by your web application to identify which site resources are used by the application because Commerce Server allows you to have multiple “Sites” on a machine. Then you’ll want to unpack all the resources available and click next. Click next again to create the authentication and profiling resources.

Now you will be setup all the database connections for each resource in the site.

image

Selecting a resource and clicking Modify allows you to set all the basic connection details. If you are using a remote database server, this is where you need to change the settings.

image

After modifying the connection strings as necessary and clicking next, you will be able to select the applications you want to unpack.

image

Each Site is “usually” made up of 5 applications – a Marketing, Orders, Profile and Catalog web service and a Web app. After selecting them all hit next.

You can now rename any or all of the applications and change the web site in IIS that they will be hosted on and virtual directory they will be under.

image

After a few seconds, you will be asked to provide some scripts. Click next twice to skip this.

After about a minute, you will be notified of whether the database connections were successfully set up. Click next to continue. You will then be notified of whether all the resources were extracted successfully. Click done.

Now, to get the web config into the right place, find the location of the web application that was extracted and drop in the web.config. This folder will have 3 files in it before you drop in the config file– csapp.ini, OrderObjectMappings.xml and OrderPipelineMappings.xml.

And that’s it! Well, not really… you now have extracted a Commerce Server web site, but it will not run. Now you’re in another world of pain called “Configuring a Commerce Server Site”.

07
Oct
09

Commerce Server Developer Wiki is Live!

As the title suggests, my latest foray into building more awareness around Microsoft Commerce Server in the developer community has become a reality. The Commerce Server Developer Wiki has been on the back of my mind for months now, and I’ve finally had a chance to make it available.

It’s currently suffering from a serious lack of content, so all you Commerce Server developers out there who are looking to help build the community and help out your peers please feel free to start contributing.

http://csdevwiki.com

Enjoy! :)

06
Aug
09

Want Open Search Integration in Your Website…?

Over the past few weeks, Tatham Oddie, Damian Edwards and myself have been working on publishing a framework/toolkit for integration OpenSearch into any ASP.NET search enabled website. I’m pleased to announce we have finally hit a release!

The project is available at opensearchtoolkit.codeplex.com. Tatham has a great post on how to integrate it into your site on his blog

OpenSearch is a technology that already has widespread support across the web and is now getting even more relevant with Internet Explorer 8’s Visual Search feature and the Federated Search feature in the upcoming Windows 7 release.

Now it’s time to make it even easier. Ducas Francis, one of the other members of my team, took on the job of building out our JSON feed for Firefox as well as our RSS feed for Windows 7 Federated Search. More formats, more fiddly serialization code. Following this, he started the OpenSearch Toolkit; an open source, drop-in toolkit for ASP.NET developers to use when they want to offer OpenSearch.

Today marks our first release.

So get on over to codeplex, hit up Tatham’s blog for instructions and drop the toolkit into your web site so you can take advantage of all the coolness that is OpenSearch.

28
Apr
09

Quick Commerce Server Post – NotSupportedException on ResetPassword

As the resident CS go-to guy, I was hit up with a bug about a whacky exception message that was exposed whenever a user tried to reset their password. The stack trace resembled the following:

System.NotSupportedException – Microsoft.CommerceServer.Runtime, Specified method is not supported.

at Microsoft.CommerceServer.Runtime.Profiles.UpmMembershipUser.ValidateUserAnswer(String answer)

at Microsoft.CommerceServer.Runtime.Profiles.UpmMembershipUser.ResetPassword(String passwordAnswer)

After staring at it for a couple of minutes and looking precisely at where we called it for a while I opened Reflector and gave it a crack. The UpmMembershipProvider and associated classes are in Microsoft.CommerceServer.Runtime.dll, found in C:\Program Files\Microsoft Commerce Server 2007\Assemblies.

Looking at UpmMembershipUser.ValidateUserAnswer(string answer), I found it used a variable called RequiresQuestionAndAnswer to determine whether validating the answer is required and throws a NotSupportedException if it is not. This variable was set in the UpmMembershipSettings class’ GetProfileConfiguration() method as follows:

this.requiresQuestionAndAnswer =

   inspector.DoesProfilePropertyExist("GeneralInfo.password_question", "STRING")

  && inspector.DoesProfilePropertyExist("GeneralInfo.password_answer", "STRING");

This tells me that the way we determine whether we require a Question/Answer combination to reset the password is actually by seeing whether the question and answer properties are exposed on the UserObject profile. Much to my dismay, these properties had been removed by someone…

Looking at the code again, I realised that an empty string was being passed through to ResetPassword because a custom answer validation was being performed in code before that. In the end, simply removing the parameter or passing null fixed this issue because ResetPassword() calls ResetPassword(null) which causes another branch to be executed that does not call the method ValidateAnswer.

Doing a quick search through the codebase revealed that there was another part of the system that called the method without a parameter. In fact, the same 3 lines were repeated almost exactly…

string generatedPassword = membershipUser.ResetPassword();
membershipUser.ChangePassword(generatedPassword, e.NewPassword);
membershipProvider.UpdateUser(membershipUser);

Lessons learnt:

  1. Reflector is AWESOME!
  2. Put common code in an accessible place…
15
Oct
08

Discovering Search Terms

More trawling through old code I had written brought this one to the surface. One of the requirements of the system I’m working on was to intercept a 404 (Page Not Found) response and determine if the referrer was a search engine (e.g. google) to redirect to a search page with the search term. Intercepting the 404 was quite easily done with a Http Module…

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;

namespace DemoApplication
{
    public class SearchEngineRedirectModule : IHttpModule
    {
        HttpApplication _context;

        public void Dispose()
        {
            if (_context != null)
                _context.EndRequest -= new EventHandler(_context_EndRequest);
        }

        public void Init(HttpApplication context)
        {
            _context = context;
            _context.EndRequest += new EventHandler(_context_EndRequest);
        }

        void _context_EndRequest(object sender, EventArgs e)
        {
            string searchTerm = null;
            if (HttpContext.Current.Response.StatusCode == 404
                && (searchTerm = DiscoverSearchTerm(HttpContext.Current.Request.UrlReferrer)) == null)
            {
                HttpContext.Current.Response.Redirect("~/Search.aspx?q=" + searchTerm);
            }
        }

        public string DiscoverSearchTerm(Uri url)
        {
            …
        }
    }
}

Implementing DiscoverSearchTerm isn’t that difficult either. We just have to analyse search engine statistics to see which ones are most popular and analyse the URL produced when performing a search. Luckily for us, most are quite similar in that they use a very simple format that has the search term as a parameter in the query string. The search engines I analysed included live, msn, yahoo, aol, google and ask. The search term parameter of these engines was either named “p”, “q” or “query”.

Now, all we have to do is filter for all the requests that came from a search engine, find the search term parameter and return its value…

public string DiscoverSearchTerm(Uri url)
{
    string searchTerm = null;
    var engine = new Regex(@"(search.(live|msn|yahoo|aol).com)|(google.(com|ca|de|(co.(nz|uk))))|(ask.com)");
    if (url != null && engine.IsMatch(url.Host))
    {
        var queryString = url.Query;
        // Remove the question mark from the front and add an ampersand to the end for pattern matching.
        if (queryString.StartsWith("?")) queryString = queryString.Substring(1);
        if (!queryString.EndsWith("&")) queryString += "&";
        var queryValues = new Dictionary<string, string>();
        var r = new Regex(
        @"(?<name>[^=&]+)=(?<value>[^&]+)&",
        RegexOptions.IgnoreCase | RegexOptions.Compiled
        );
        string[] queryParams = { "q", "p", "query" };
        foreach (var match in r.Matches(queryString))
        {
            var param = ((Match)match).Result("${name}");
            if (queryParams.Contains(param))
                queryValues.Add(
                ((Match)match).Result("${name}"),
                ((Match)match).Result("${value}")
                );
        }
        if (queryValues.Count > 0)
            searchTerm = queryValues.Values.First();
    }
    return searchTerm;
}

The above code uses two regular expressions, one to filter for a search engine and the other to separate the query string. Once it’s decided that the URL is a search engine’s, it creates a collection of query string parameters that could be search parameters and returns the first one.

Unfortunately, there wasn’t enough time in the iteration for me to properly match the search engine with the correct query parameter, but as is most commonly the parameter comes into the query string quite early so it’s fairly safe to assume that the first match is correct.

08
Oct
08

Randomly Sorting a List using Extension Methods

I was trawling through some old code I had written while doing some “refactoring” and came across this little nugget. I wanted to sort a list of objects that I was retrieving from a database using LINQ to SQL into a random order. Seeing as extension methods are all the rage, I decided to use them…

public static class ListExtensions {
  public static IEnumerable<T> Randomise<T>(this IEnumerable<T> list) {
    Random rand = new Random();
    var result = list.OrderBy(l => rand.Next());
    return result;
  }
}

How does it work…? It adds the Randomise() extension method to the end of any IEnumerable<T> (e.g. List<T>) and uses the OrderBy function to change the sort order based on a randomly generated number.

var randomCategories = context.Categories.Randomise();

The above code will execute the Randomise function to reorder the list of Category objects retrieved from the context randomly and assign the result to randomCategories.




Twitter – @ducas

  • Being with Vodafone is a great way to keep my mobile costs down... Because whenever I try to use it I have no reception. 15 hours ago
  • The MS Mobile P&P team have made the source code for the Project #Liike reference application available - http://t.co/QgmoToij #mobileweb 2 days ago
  • Ctrl + Shift + Eject = Lock Screen on Mac... provided you have "require password after sleep or screensaver" on - http://t.co/wDHiQGNS 4 days ago
  • What makes someone use"warm regards" in their signature? Does it say anything about them? Does commenting say anything about me...? :-S 4 days ago
  • is it sad that i have a virtual machine to view a pdf using adobe acrobat...? 4 days ago

Flickr Photos

Photo A1267

More Photos

Follow

Get every new post delivered to your Inbox.

Join 507 other followers