Category Archives: Dev Stuff

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”.

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! 🙂

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.

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…

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.

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.

Setting the Test Run Config in Team Build

At my current client, we’re in a situation where a couple of us have Visual Studio 2008 Team System and the rest have Professional Edition. This means that we’ve been having a hard time with getting Code Coverage in our team build because everyone has been changing the active test configuration to suite their environment.

After trawling through a few blog posts and support forums, I finally discovered a gold nugget. In a couple of steps, I defined the test configuration and get it to run as part of the team build.

Firtsly, I added the following test arguments to my project build file (TFSBuild.proj):

<MetaDataFile Include="$(SolutionRoot)/HelloWorld.vsmdi">
  <TestList>HelloWorldUnitTests</TestList>
  <RunConfigFile>$(SolutionRoot)/TeamBuildTestRun.testrunconfig</RunConfigFile>
</MetaDataFile>

This defines the test metadata file (HelloWorld.vsmdi), the list of tests to execute (HelloWorldUnitTests) and the configuration file to use (TeamBuildTestRun.testrunconfig). However, only the metadata file and test list will be included to the MSTest command. To get it all working, we have to edit the targets file on the server C:\Program Files\MSBuild\Microsoft\VisualStudio\Team\Microsoft.TeamFoundation.Build.targets. There is a target called CoreTestConfiguration that calls the TestToolsTask MSBuild task with the parameters. The first three calls are for non-desktop (i.e. server) builds, e.g.

<TestToolsTask
      Condition=" '$(IsDesktopBuild)'!='true'
                  and '$(V8TestToolsTask)'!='true'
                  and '%(LocalMetaDataFile.Identity)' != '' "
      BuildFlavor="$(Configuration)"
      Platform="$(Platform)"
      PublishServer="$(TeamFoundationServerUrl)"
      PublishBuild="$(BuildNumber)"
      SearchPathRoot="$(OutDir)"
      PathToResultsFilesRoot="$(TestResultsRoot)"
      MetaDataFile="%(LocalMetaDataFile.Identity)"
      RunConfigFile="%(RunConfigFile)"
      TestLists="%(LocalMetaDataFile.TestList)"
      TeamProject="$(TeamProject)"
      TestNames="$(TestNames)"
      ContinueOnError="true" />

When the build is run on the server, the values of the MetaDataFile property are copied to the LocalMetaDataFile variable. This means the RunConfigFile property needs to be changed to %(LocalMetaDataFile.RunConfigFile), e.g.

<TestToolsTask
      Condition=" '$(IsDesktopBuild)'!='true'
                  and '$(V8TestToolsTask)'!='true'
                  and '%(LocalMetaDataFile.Identity)' != '' "
      BuildFlavor="$(Configuration)"
      Platform="$(Platform)"
      PublishServer="$(TeamFoundationServerUrl)"
      PublishBuild="$(BuildNumber)"
      SearchPathRoot="$(OutDir)"
      PathToResultsFilesRoot="$(TestResultsRoot)"
      MetaDataFile="%(LocalMetaDataFile.Identity)"
      RunConfigFile="%(LocalMetaDataFile.RunConfigFile)"
      TestLists="%(LocalMetaDataFile.TestList)"
      TeamProject="$(TeamProject)"
      TestNames="$(TestNames)"
      ContinueOnError="true" />

There are three more calls to TestToolsTask that should be modified. These calls are for desktop builds, so the LocalMetaDataFile has not been created. This means we use %(MetaDataFile.RunConfigFile) instead, e.g.

  <TestToolsTask
        Condition=" '$(IsDesktopBuild)'=='true'
                    and '$(V8TestToolsTask)'!='true'
                    and '%(MetaDataFile.Identity)' != '' "
        SearchPathRoot="$(OutDir)"
        PathToResultsFilesRoot="$(TestResultsRoot)"
        MetaDataFile="%(MetaDataFile.Identity)"
        RunConfigFile="%(MetaDataFile.RunConfigFile)"
        TestLists="%(MetaDataFile.TestList)"
        TestNames="$(TestNames)"
        ContinueOnError="true" />

And that’s it!