GetName() fails in Windows Phone 7

I am finishing up my 1st real windows phone application and I am creating an “About” page. Instead of hard-coding in the version number, I thought I would take a trick form my old WinForm days and use System.Reflection

I whipped up some code like this:

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { this.VersionTextBlock.Text = String.Format("Version {0}", Assembly.GetExecutingAssembly(). GetName().Version.ToString()); base.OnNavigatedTo(e); }

I then ran it and got the following error:

image

Ugh. I was going to figure out what was wrong but then I deferred to the example in Chapter 6 in Adam Nathen’s 101 Windows Phone Apps . I changed the code to this:

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { string fullAssemblyName = typeof(AboutPage).Assembly.ToString(); int firstComma = fullAssemblyName.IndexOf("Version="); int secondComma = fullAssemblyName.IndexOf(",", firstComma); string versionNumber = fullAssemblyName.Substring(firstComma, secondComma - firstComma); this.VersionTextBlock.Text = versionNumber; base.OnNavigatedTo(e); }

That worked fine.

MSTest Name Conflicts

As part of my day job, I sometimes have to present a solution with a “before” and an “after” project.  Typically, I would create a before solution folder and an after solution folder and put the two different projects in into their respective folder.  I would then flip over to the file system and move the folders around so that the physical paths match the logical paths in VS2010.  I recently created a before and after C# project and I included a test proejct to accompany each working project.  When I tried to include both test projects in the same solution (even in different solution folders), I got this:

image

Apparently, MSTest is run at the solution level, not the project level.  The easiest way out of the problem was to rename the test projects to match Before/After and leave them at the solution level.  Not the best, but it solved my problem in a pinch

UX that suck

I am not the most anal-retentive person, but I do appreciate web pages that are well-designed.  I also resent professional organizations that make web sites that are less well-designed than a typical 8th grade class project.  I recently experienced one that falls more into the later category.  I had to pay a medical bill so I went to the hospital’s on-line payment site and filled in the information:

image

notice that I selected June 31 as her birthday.  I then hit submit and I got this:

image

 

Basic input validation should be part of any site –especially one that deals with medical/financial transactions.  I didn’t try to do a sql injection attack, but if I was a betting man, I would bet that this site is vulnerable to that too…

Select MIN In LINQ

I recently had to take a set of swim time data for an entire season and determine the lowest time for each swimmer.  The TSQL in me thought “this is a snap – just do this:”

Select SwimmerID, MIN(Time) from dbo.tblTimesIndividual where MeetID > 74 and RaceStrokeID = 4 Group By SwimmerID

However, when I tried to implement in LINQ, things went bad pretty quickly.  I stumbled around with the language extensions for a bit before raising the white flag with MSDN.  The easiest implementation that I found was this:

var minQuery = from times in entities.tblTimesIndividuals where times.MeetID > 74 && times.RaceStrokeID == 4 group times by times.SwimmerID into grouping select new { grouping.Key, LowestFreeStyleTime = from times2 in grouping where times2.Time == grouping.Min(times3 => times3.Time) select times2 };

Yikes!  Since it worked, I kept it – however, I do wonder if there is an easier implementation.   I wonder if there is a tool to reverse engineer TSQL to LINQ – like you can look at the TSQL generated from EF when you send using SQL Profiler (which I can’t use on my remote server) or the ToTraceString() method like so:

string sql = ((ObjectQuery)freeMinQuery).ToTraceString();

The key thing is to remember that ObjectQuery is in System.Data.Objects so I had to add a using statement for that.  In any event, check out the TSQL:

SELECT [Project1].[SwimmerID] AS [SwimmerID], [Project1].[C1] AS [C1], [Project1].[TimesIndividualID] AS [TimesIndividualID], [Project1].[SwimmerID1] AS [SwimmerID1], [Project1].[MeetID] AS [MeetID], [Project1].[AgeGroupID] AS [AgeGroupID], [Project1].[RaceStrokeID] AS [RaceStrokeID], [Project1].[RaceTypeID] AS [RaceTypeID], [Project1].[RaceLengthID] AS [RaceLengthID], [Project1].[Lane] AS [Lane], [Project1].[Place] AS [Place], [Project1].[Time] AS [Time], [Project1].[TimerID] AS [TimerID] FROM ( SELECT [GroupBy1].[K1] AS [SwimmerID], [Extent2].[TimesIndividualID] AS [TimesIndividualID], [Extent2].[SwimmerID] AS [SwimmerID1], [Extent2].[MeetID] AS [MeetID], [Extent2].[AgeGroupID] AS [AgeGroupID], [Extent2].[RaceStrokeID] AS [RaceStrokeID], [Extent2].[RaceTypeID] AS [RaceTypeID], [Extent2].[RaceLengthID] AS [RaceLengthID], [Extent2].[Lane] AS [Lane], [Extent2].[Place] AS [Place], [Extent2].[Time] AS [Time], [Extent2].[TimerID] AS [TimerID], CASE WHEN ([Extent2].[TimesIndividualID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]FROM (SELECT [Extent1].[SwimmerID] AS [K1], MIN([Extent1].[Time]) AS [A1]FROM [dbo].[tblTimesIndividual] AS [Extent1]WHERE ([Extent1].[MeetID] > 74) AND (4 = [Extent1].[RaceStrokeID])GROUP BY [Extent1].[SwimmerID] ) AS [GroupBy1]LEFT OUTER JOIN [dbo].[tblTimesIndividual] AS [Extent2] ON ([Extent2].[MeetID] > 74) AND (4 = [Extent2].[RaceStrokeID]) AND ([GroupBy1].[K1] = [Extent2].[SwimmerID]) AND ([Extent2].[Time] = [GroupBy1].[A1])) AS [Project1]ORDER BY [Project1].[SwimmerID] ASC, [Project1].[C1] ASC

 

Yikes!  I’ll continue to search for a tool that can give hints to make my LINQ more clear and have it generate better TSQL.

Windows Phone 7 Design Time and Asynch Initialization

I am working on a Windows Phone 7 app and I ran into an interesting problem.  The architecture of the app is MVVM and the ViewModel is a ‘Threat’.  When consuming the ThreatViewModel in my Vi,ew I havew the following code:

image

This fails because the TheatViewModel created a proxy to consume a WCF service. I made the WCF-creating method, made it public, and called it during the initialization of the page:

public CurrentStatusView() { InitializeComponent(); ThreatViewModel threatViewModel = (ThreatViewModel)this.DataContext; threatViewModel.InitializeLocations(); }

The problem is that consuming the control – the initialize component gets called and this is the error message:

image

Since initialize component is getting called in deign time, the asynch is getting kicked off – and the designer is throwing an exception.  Rob Seder seems to think it is a .config issue – because the .config is not present in design time, the proxy doesn’t have the information to make the proxy call.

Stepping back, the proxy and asynch call needs to happen outside of the designer – at some point in the code behind to respond to an event on the screen. Perhaps application load or page load.  In any event, merging asycnh calls is not a good idea…

The power of separation

I am getting ready to deploy my 1st Windows Phone 7 app. The application consumes some data that is served up by a WCF service that I wrote. Until today, I had been testing the application using some dummy data that I hard-coded into the UI.

To add the WCF service, I added a new WCF project to my solution (and a supporting data project that uses Entity Frameworks). I ran into the same problem as I encountered last year that I forgot about – when you add a reference to a project that uses Entity Frameworks, you need to add a reference to System.Data.Entities in the consuming project. If not, you can see the business classes but the context will not work. Unfortunately, the only way to see this error it to hard-code the context into your consuming project, hit F5, and get this message:

'System.Data.Objects.DataClasses.EntityObject' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.

Once I figured that out, I whipped up a basic method using some LINQ like so:

public Location GetLocation(int locationId) { var q = from l in context.RoadAlert_Location where l.LocationId == locationId select l; return MapLocation(q.FirstOrDefault()); }

Easy enough, I then added my interface:

[ServiceContract(Namespace="http://schemas.tff.com/2011/06/13/RoadAlert.Services")] [ServiceKnownType(typeof(Location))] [ServiceKnownType(typeof(List<Location>))] public interface IRoadAlert { [OperationContract] Location GetLocation(int locationId); [OperationContract] List<Location> GetLocations(int alertTypeId); }

And then published the service to my WinHost server. It published fine and I got the WSDL no problem. However, after I added a reference, I could not create a proxy class. I checked the clientConfig and I was surprised because it was empty:

image

I did a quick search and found posts like this one. I made sure I was using BasicHTTPBinding like so:

image

However, it still was not working. I would see the service reference and the wizard exited with no errors. However, when I tried to create the proxy, I got an exception.

I then decided to take a step back and separate the service and data projects from my solution – instead of deplying to WinHost. I started the service project and then tried to add a reference from the client. Once I did that, I got this in my client config:

<configuration> <system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IRoadAlert" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647"> <security mode="None" /> </binding> </basicHttpBinding> </bindings> <client> <endpoint address="http://localhost:52296/RoadAlert.svc" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IRoadAlert" contract="Tff.RoadAlert.Services.IRoadAlert" name="BasicHttpBinding_IRoadAlert" /> </client> </system.serviceModel> </configuration>

Magic! I have no idea why/how it now works, but separating the projects did the trick. I then could create my proxy locally. Then to deploy, I just published the service and changed the endpoint address on the client. I found this Microsoft page that details many of the problems and steps I went through.

Another interesting note is that you can’t do a synchronous call in Win Phones – I Guess they all have to be asynch…

Old English NLP

I started working through the SharpNLP library as referenced in my last blog post. To recap, the SharpNLP library depends on some source binaries from the SharpEntropy library which depend on sonce data files that have to be placed in a specific location on your file system.

I cracked open the SharpNLP with reflector and took a spin through the more commonly used methods. It looks like you deal with strings and a couple of classes called “chunk” and “word” that have a couple of supporting enumerations.

With the library, you can split paragraphs into sentences, sentences into words, sentences into phrases (what they call chunks), and then identify each word and/or phrase with its part of speech. These functions seem perfect for my Old English translator.

Here is an example of a basic console application with the parser output.

public class Program { static void Main(string[] args) { Console.WriteLine("--Start--"); string testSentence = "A quick brown dog jumped over a lazy cat."; Parse parse = ParseSentence(testSentence); PrintParse(parse); Console.WriteLine("---End---"); Console.ReadLine(); } private static Parse ParseSentence(string sentence) { string path = @"C:\ProgramData\OpenNLP\Models\\"; EnglishTreebankParser parser = new EnglishTreebankParser(path, true, false); return parser.DoParse(sentence); } private static void PrintParse(Parse parse) { Parse rootNode = parse.GetChildren()[0]; Parse[] parses = rootNode.GetChildren(); foreach (Parse currentParse in parses) { Console.WriteLine(String.Format("{0} : {1}", currentParse.ToString(), currentParse.Type)); } } }

image

 

I then wanted to change the order from S-V-O to S-O-V or O-S-V. In this example, the output should be “A quick brown dog over a lazy cat jumped.” or even “Over a lazy cat a quick brown dog jumped.”

Looking at the output, the VP needs to be broken down even further. I changed the PrintParse to handle the children (using recursion):

private static void PrintParse(Parse parse) { Parse[] parses = parse.GetChildren(); foreach (Parse currentParse in parses) { Console.WriteLine(String.Format("{0} : {1}", currentParse.ToString(), currentParse.Type)); PrintParse(currentParse); } }

The output is now:

image

Things looks better – except I can’t figure out what the “TK” is – it is duplicating each word and it is not a part of speech (at least according to the WordType enum)…

Removing the TK, I get this (I dropped the Console window and went to the Output window) :

A quick brown dog jumped over a lazy cat . : S

A quick brown dog : NP

A : DT

quick : JJ

brown : JJ

dog : NN

jumped over a lazy cat : VP

jumped : VBD

over a lazy cat : PP

over : IN

a lazy cat : NP

a : DT

lazy : JJ

cat : NN

. : .

So to take a sentence and take it from S-V-O to O-S-V, I need to map the parsing word types to either a S,V,or O. In this example:

  • NP = S
  • VBD = V
  • PP=O

I can then rearrange the words into Old English. The challenge is that the word types are not context-free – the 1st NP that is in the sentence (“A quick brown dog”)is the Subject, but the second NP (“a lazy cat”) is the object – the PP is for the prepositional phrase. So the 1st NP that is encountered is the subject and the second NP is ignored if it is in a PP, but if it is alone (“A quick brown dog jumped a lazy cat”), then it becomes the object. I can build a table with the different patterns – at least the common ones, and leave it at that. Here is an example of the function (yes, I know this is procedural code at its worst, I’ll refactor once I have all of my thoughts down)

private static void AssignOldEnglishOrder(List<Phrase> phrases) { int nounCount = 0; int verbCount = 0; int objectCount = 0; foreach (Phrase phrase in phrases) { switch (phrase.ParseType) { case "NP": if (nounCount == 0) { phrase.SentenceSection = "S"; } else if (nounCount == 1) { phrase.SentenceSection = "O"; } else { phrase.SentenceSection = "Z"; } nounCount++; break; case "VBD": if (verbCount == 0) { phrase.SentenceSection = "V"; } else { phrase.SentenceSection = "Z"; } verbCount++; break; case "PP": if (objectCount == 0) { phrase.SentenceSection = "O"; } else { phrase.SentenceSection = "Z"; } objectCount++; break; default: break; } } }

I then whipped up a function that spits out the phrase in Old-English:

private static void PrintPhrases(List<Phrase> phrases) { StringBuilder stringBuilder = new StringBuilder(); foreach (Phrase phrase in phrases) { switch (phrase.SentenceSection) { case "O": stringBuilder.Insert(0, phrase.Text); break; case "S": if (stringBuilder.Length > 1) stringBuilder.Insert(1, phrase.Text); else stringBuilder.Append(phrase.Text); break; case "V": stringBuilder.Append(phrase.Text); break; case "Z": break; default: stringBuilder.Append(phrase.Text); break; } } Debug.WriteLine(stringBuilder.ToString()); }

And the output looks like this:

over a lazy catA quick brown dogjumped

My next steps are:

  • Spacing between words
  • Sentences formatted correctly (all lower case until final output)
  • Find..Replace key words for Old English
  • More patterns to match O-S-V from a variety of input sentences
  • Handle the apostrophe (Don’t comes down as [Don] ‘[t] from the parser)
  • Refactor using Unit tests to better patterns (strategy seems appropriate here)
  • Stick into a WCF service
  • Probably a bunch more stuff that I haven’t thought of

That’s it. I’ll pick it up more later.

Natural Language Parsing

I wanted to go back to a project that I started working on <gulp> three years ago. There is a great website called the Efanchiser that translates English into Jive/VallyGirl/Sweedish Chef. There are some knock-off sites like this one, but I prefer the original. When you go through the header file (written in C) of the translation methods (not the page’s html), you can see this comment:

/* chef.x - convert English on stdin to Mock Swedish on stdout * * The WC definition matches any word character, and the NW definition matches * any non-word character. Two start conditions are maintained: INW (in word) * and NIW (not in word). The first rule passes TeX commands without change. * * HISTORY * * Apr 26, 1993; John Hagerman: Added ! and ? to the Bork Bork Bork rule. * * Apr 15, 1992; John Hagerman: Created. */

Some guy at AT&T back in the 80s came up with the code – check out this comment for the language parsing. Looks like Google and Microsoft weren’t the 1st to come up with the idea about parsing English speech 😉

/* Copyright (c) 1989 AT&T */ /* All Rights Reserved */ /* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */ /* The copyright notice above does not evidence any */ /* actual or intended publication of such source code. */

The actual .c file has the logic like this:

# line 28 "chef.l" { BEGIN NIW; i_seen = 0; printf("%c\nBork Bork Bork!",yytext[0]); } break; case 4: # line 31 "chef.l" ECHO; break; case 5: # line 32 "chef.l" ECHO; break; case 6: # line 34 "chef.l" { BEGIN INW; printf("un"); } break; case 7: # line 35 "chef.l" { BEGIN INW; printf("Un"); } break; case 8:

Basically, it is one big Select…Case statement with certain words/phrases replaced and throwing the words “Bork Bork Bork” at the end. The implementation uses word positioning like this:

0}; # define YYTYPE unsigned char struct yywork { YYTYPE verify, advance; } yycrank[] = { 0,0, 0,0, 1,7, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 1,8, 9,34, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 15,39, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 15,0, 0,0, 39,0, 0,0, 1,7, 1,9, 0,0, 0,0, 0,0, 0,0, 0,0, 1,10, 0,0, 0,0, 0,0, 0,0, 0,0, 3,7, 0,0

Which is pretty cool but really hard to duplicate or extend.

I wanted to add an old-english translator. Instead of adding to the C++ project, I thought of creating a WCF Service that creates an Old-English translation using C#.

My first step was to find a natural language parser that can break the input sentence into its words and tag each word as a part of speech. A quick bing search found a couple of options:

· This site has what I am looking for – but he does not expose the tokenizing as a web service or provide the API source code. I really am not interested in parsing the html so I moved on.

· The Sharp Natural Language Parser (SNLP) found on the codeplex seems to fit the bill – but it is complex to implement and it is a very brittle solution – it relies on a third party library called Maximum Entropy Models (MEM) that has to have a certain folder structure created. The SNLP has a database alternative (called Wordnet Lexical Database) but that link is not working.

· This site has what I am looking for – but he wants to charge a fee to use it. No thanks.

· There is another codeplex project that uses Pyton called ConceptNetUtils found here. I really don’t want to learn Python for this.

There are more hits, but the quality of the sites gets worse and worse.

Biting the bullet, I decided to use the SNLP project from codeplex and therefore use the MEM on my local file system. A great example is found here. Perhaps as a follow up project I will abstract the MEM from the file system. I did have to peek at the MEM to see if it would be an easy port – here is one of the files in notepad:

image

Bummer – looks like that project will be harder said than done….

So I added the directories to my source folder and hit F5 – sure enough it worked. I then started to dive into the examples and see how the API was structured. That is the subject of my next post.

To get an idea of how to make an English phrase an old English phrase, I turned to Bing. There are a couple of sites that translate individual words (like this one). I also got a laugh because there are TONS of sites that willtranslate Shakespeare to modern English – doubtlessly helping High School English students everywhere.

There are a couple of components:

· Rearrange the sentence structure as recommended here

· Find..replace certain words/phrases as found here

There are obviously other things I could do to make the Old English better, but I thought I would start with the basics.

Export .sdif file to the client’s desktop

One of the user requirements of the swim team website is to send the .sdif file that I blogged about here from the webpage to the user’s browser – or download it to the users file system. Since the file is dynamic and I can’t write to the file system of the web server, I need a way to generate the file and then have a dialog box “Save To” open.

The first step was to add a UI project to the solution that matches the MVC project that the swim team currently users. I love the organization of the initial project – it was very easy and logical to drop in a new Web project:

image

I then opened up the home controller and added a base method and ported the code from the Console UI:

[HttpPost] public ActionResult GetSdifFile() { string fileName = @"C:\Users\Public\PracticeMeetSetup.SD3"; MeetSetupFactory factory = new MeetSetupFactory(); Collection<string> collection = factory.CreateMeetSetUp(72); File.WriteAllLines(fileName, collection); return View(); }

Basically, I need to rip out the disk write code and replace it with something that can go to the browser. Being a traditional ASP.Net guy, I immediately thought of something like this:

public void Guess(string filePath) { try { using (StreamReader sr = new StreamReader(filePath)) { String line; while ((line = sr.ReadLine()) != null) { Response.Write(line + "<br />"); } } } catch (Exception ex) { Response.Write("<p>The file could not be read:"); Response.Write(ex.Message + "</p>"); } }

I then thought “Wait, this is MVC…” so I thought of something like this:

[HttpPost] public ActionResult GetSdifFile() { MeetSetupFactory factory = new MeetSetupFactory(); Collection<string> collection = factory.CreateMeetSetUp(72); return View(collection); }

And the view displaying the contents of the collection:

<h2>GetSdifFile</h2> <% foreach (string _currentString in Model){ %> <%= Html.Encode(_currentString) %> <br /> <% } %>

And here are the results:

image

So it is a start – I guess they could copy/paste the contents of the page. However, the User Case is for them to click a button and get a .sdif file that saves to their file system (via, I assume, a Save Dialog box).

I wrote a new function that returns JSON:

public JsonResult CurrentMeetSdifFile() { MeetSetupFactory factory = new MeetSetupFactory(); Collection<string> collection = factory.CreateMeetSetUp(72); JsonResult result = new JsonResult(); result.Data = collection; result.JsonRequestBehavior = JsonRequestBehavior.AllowGet; return result; }

Since the browser doesn’t know what to do with JSON (I only tested in IE), then you get a save dialog box:

image

Saving that to the desktop, I open in note pad and get the following results:

image

The results are losing their line formatting.

This is the right track – but I have bit more work to do.

My 1st step was to add a parameter of the actual meet that the user wants:

public JsonResult CurrentMeetSdifFile(int meetId) { MeetSetupFactory factory = new MeetSetupFactory(); Collection<string> collection = factory.CreateMeetSetUp(meetId); Etc… }

I then tried it from the browser:

image

Ugh, it looks like with the default routing engine, I need to make the parameter have the name id. I have a choice. I can either add a new route with an explicit meetId or I can use the id variable name. I chose option B as a path of least resistance:

public JsonResult CurrentMeetSdifFile(int id) { MeetSetupFactory factory = new MeetSetupFactory(); Collection<string> collection = factory.CreateMeetSetUp(id); JsonResult result = new JsonResult(); result.Data = collection; result.JsonRequestBehavior = JsonRequestBehavior.AllowGet; return result; }

With the routing set up, I now need to fix the JSON output to stick a new line after each string in the collection.

My 1st attempt was just to throw an Environment.NewLine into the Json result:

image

Yikes! It looks like I am mixing formatting. “\r\n” is coming down as a literal value. I need a way to tell notepad that they are line breaks.

I binged around a bit and looked at attack overflow. This post seems to have the answer. I tried to add

result.ContentType = "text/html";

But then the browser displayed the data:

image

Note the “\r\n” is still there.

I then tried some other ways of breaking (using the \\n for example), nothing.

I then stepped back and thought that I was approaching the problem incorrectly. Instead of sending back JSON from the function, what if I sent something else? A quick tour through MSDN showed me a file result class. That is what I need and here is an example of my question.

A quick run through the overloads of the method, I realized that all I have to do is to convert the string collection into a FileStream and then send it out via the File class. I used the FileStreamResult class and the rest was pretty easy to wire up:

public FileStreamResult CurrentMeetSdifFile(int id) { MeetSetupFactory factory = new MeetSetupFactory(); Collection<string> collection = factory.CreateMeetSetUp(id); StringBuilder stringBuilder = new StringBuilder(); foreach(string _currentString in collection) { stringBuilder.Append(_currentString); stringBuilder.Append(Environment.NewLine); } byte[] byteArray = Encoding.ASCII.GetBytes(stringBuilder.ToString()); MemoryStream stream = new MemoryStream( byteArray ); FileStreamResult fileStreamResult = new FileStreamResult(stream,"text/plain"); return fileStreamResult; }

And if I want to get the popup, I change the output format to “.sdif” and I get the dialog box with the data formatted correctly.

image

And boom goes the dynamite…

MVC Route Constraints

I started to dig into MVC3 routing a bit more over the weekend.  I came across some routing constraints and realized that there is a clear progression.  I created an out of the box MVC3 web application (I am using Razor) and then added a Product Controller with the default methods and 1 View for the Details method.  I made 1 change to the out of the box convention – I changed the name of the int parameter to the Details method to productId.

 

image

The Details controller method pushes the parameter back out to the View:

public ActionResult Details(int productId) { return View(productId); }

and the view parrots the productId back to the user:

@{ ViewBag.Title = "Details"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>Details</h2> <br /> You Entered = @Model

 

I then dropped into Global.asax to work with the constraints.  The first thing I did was to add a route to the routing table to account for the productid parameter:

routes.MapRoute( "Product", "Product/{productId}", new { controller = "Product", action = "Details" } );

This worked fine to a point.  Product/1 resolved:

image

 

but Product/foo was allowed by the routing engine and the method threw an error:

 

image

 

To handle this malformed parameter, I added a regular expression to the constraint definition (using Stephen Walter’s blog post as an example:

 

routes.MapRoute( "ProductWithConstraint", "Product/{productId}", new { controller = "Product", action = "Details" }, new { productId = @"\d+" } );

Now, when Product/foo comes in, I got a 404.

image

The next scenario I wanted to handle was that only some integers are allowed as a parameter.  For example, perhaps only products that are in stock are allowed – which means that you need to do a database call before defining the route constraint definition.  To that end, I created a new class that inherited from IRouteConstraint (based on Yuri Nayyeri blog post):

public class ProductRouteConstraint: IRouteConstraint { public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { if ((routeDirection == RouteDirection.IncomingRequest) && (parameterName.ToLower(CultureInfo.InvariantCulture) == "productid")) { int productId = 0; try { productId = Convert.ToInt32(values["productId"]); if (productId > 0 && productId < 10) { return true; } } catch (FormatException formatException) { return false; } } return false; } }

I then added this constraint to the routing table:

 

routes.MapRoute( "ProductWithConstraintClass", "Product/{productId}", new { controller = "Product", action = "Details" }, new { productId = new ProductRouteConstraint() } );

And I got the desired result – products less than 0 or greater than 10 returned a 404.

So the progression in my mind is:

  1. No constraints
  2. Reg Ex constraints in the Global.asax
  3. Create a class that implements IRouteConstraint

I my mind, I would rather skip #2 and go to #3 altogether.  Having all of the logic encapsulated in 1 class seems cleaner and not having the Global.asax mucked up with custom defs makes for a more maintainable solution.