Smart Nerd Dinner

I think there is general agreement that the age of the ASP.NET wire-framing post-back web dev is over.  If you are going to writing web applications in 2015 in the .NET stack, you have to be able to use java script and associated javascript frameworks like Angular.  Similarly, the full-stack developer needs to have a much deeper understanding of the data that is passing in and and out of their application.  With the rise of analytics in an application, the developer needs different tools and approaches to their application.  Just as you need to know javascript if you are going to be in the browser, you need to know F# if you are going to be building industrial-grade  domain and  data layers.

I decided to refactor an existing ASP.NET postback website to see how hard it would be to introduce F# to the project and apply some basic statistics to make the site smarter.  It was pretty easy and the payoffs were quite large.

If you are not familiar, nerd Dinner is the cannonal example of a MVC application that was created to show Microsoft web devs how to create a website using the .NET stack.  The original project was put into a book with the Mount Rushmore of MSFT uber-devs

image

The project was so successful that it actually was launched into a real website

image

and you can find the code on Codeplex here

image

When you download the source code from the repository, you will notice a couple of things:

1) It is not a very big project – with only 1100 lines of code

image

2) There are 191 FxCop violations

image

3) It does compile coming out of source, but some of the unit tests fail

image

4) There is pretty low code coverage (21%)

image

Focusing on the code coverage issue, it makes sense that there is not much code coverage because there is not much code that can be covered.  There is maybe 15 lines of “business logic” if the term business logic is expanded to include input validation.  This is an example

image

Also, there is maybe ten lines of code that do some basic filtering

image

So step one in the quest to refactor nerd dinner to be a bit smarter was to rename the projects.  Since MVC is a UI framework, it made sense to call it that.  I then changed the namespaces to reflect the new structure

image

The next  step was to take the domain classes out of the UI and put them into the application.  First, I created another project

image

I then took all of the interfaces that was in the UI and placed them into the application

1 namespace NerdDinner.Models 2 3 open System 4 open System.Linq 5 open System.Linq.Expressions 6 7 type IRepository<'T> = 8 abstract All : IQueryable<'T> 9 abstract AllIncluding 10 : [<ParamArray>] includeProperties:Expression<Func<'T, obj>>[] -> IQueryable<'T> 11 abstract member Find: int -> 'T 12 abstract member InsertOrUpdate: 'T -> unit 13 abstract member Delete: int -> unit 14 abstract member SubmitChanges: unit -> unit 15 16 type IDinnerRepository = 17 inherit IRepository<Dinner> 18 abstract member FindByLocation: float*float -> IQueryable<Dinner> 19 abstract FindUpcomingDinners : unit -> IQueryable<Dinner> 20 abstract FindDinnersByText : string -> IQueryable<Dinner> 21 abstract member DeleteRsvp: 'T -> unit

I then tooks all of the data structures/models and placed them in the application.

1 namespace NerdDinner.Models 2 3 open System 4 open System.Web.Mvc 5 open System.Collections.Generic 6 open System.ComponentModel.DataAnnotations 7 open System.ComponentModel.DataAnnotations.Schema 8 9 type public LocationDetail (latitude,longitude,title,address) = 10 let mutable latitude = latitude 11 let mutable longitude = longitude 12 let mutable title = title 13 let mutable address = address 14 15 member public this.Latitude 16 with get() = latitude 17 and set(value) = latitude <- value 18 19 member public this.Longitude 20 with get() = longitude 21 and set(value) = longitude <- value 22 23 member public this.Title 24 with get() = title 25 and set(value) = title <- value 26 27 member public this.Address 28 with get() = address 29 and set(value) = address <- value 30 31 type public RSVP () = 32 let mutable rsvpID = 0 33 let mutable dinnerID = 0 34 let mutable attendeeName = "" 35 let mutable attendeeNameId = "" 36 let mutable dinner = null 37 38 member public self.RsvpID 39 with get() = rsvpID 40 and set(value) = rsvpID <- value 41 42 member public self.DinnerID 43 with get() = dinnerID 44 and set(value) = dinnerID <- value 45 46 member public self.AttendeeName 47 with get() = attendeeName 48 and set(value) = attendeeName <- value 49 50 member public self.AttendeeNameId 51 with get() = attendeeNameId 52 and set(value) = attendeeNameId <- value 53 54 member public self.Dinner 55 with get() = dinner 56 and set(value) = dinner <- value 57 58 59 and public Dinner () = 60 let mutable dinnerID = 0 61 let mutable title = "" 62 let mutable eventDate = DateTime.MinValue 63 let mutable description = "" 64 let mutable hostedBy = "" 65 let mutable contactPhone = "" 66 let mutable address = "" 67 let mutable country = "" 68 let mutable latitude = 0. 69 let mutable longitude = 0. 70 let mutable hostedById = "" 71 let mutable rsvps = List<RSVP>() :> ICollection<RSVP> 72 73 [<HiddenInput(DisplayValue=false)>] 74 member public self.DinnerID 75 with get() = dinnerID 76 and set(value) = dinnerID <- value 77 78 [<Required(ErrorMessage="Title Is Required")>] 79 [<StringLength(50,ErrorMessage="Title may not be longer than 50 characters")>] 80 member public self.Title 81 with get() = title 82 and set(value) = title <- value 83 84 [<Required(ErrorMessage="EventDate Is Required")>] 85 [<Display(Name="Event Date")>] 86 member public self.EventDate 87 with get() = eventDate 88 and set(value) = eventDate <- value 89 90 [<Required(ErrorMessage="Description Is Required")>] 91 [<StringLength(256,ErrorMessage="Description may not be longer than 256 characters")>] 92 [<DataType(DataType.MultilineText)>] 93 member public self.Description 94 with get() = description 95 and set(value) = description <- value 96 97 [<StringLength(256,ErrorMessage="Hosted By may not be longer than 256 characters")>] 98 [<Display(Name="Hosted By")>] 99 member public self.HostedBy 100 with get() = hostedBy 101 and set(value) = hostedBy <- value 102 103 [<Required(ErrorMessage="Contact Phone Is Required")>] 104 [<StringLength(20,ErrorMessage="Contact Phone may not be longer than 20 characters")>] 105 [<Display(Name="Contact Phone")>] 106 member public self.ContactPhone 107 with get() = contactPhone 108 and set(value) = contactPhone <- value 109 110 [<Required(ErrorMessage="Address Is Required")>] 111 [<StringLength(20,ErrorMessage="Address may not be longer than 50 characters")>] 112 [<Display(Name="Address")>] 113 member public self.Address 114 with get() = address 115 and set(value) = address <- value 116 117 [<UIHint("CountryDropDown")>] 118 member public this.Country 119 with get() = country 120 and set(value) = country <- value 121 122 [<HiddenInput(DisplayValue=false)>] 123 member public self.Latitude 124 with get() = latitude 125 and set(value) = latitude <- value 126 127 [<HiddenInput(DisplayValue=false)>] 128 member public v.Longitude 129 with get() = longitude 130 and set(value) = longitude <- value 131 132 [<HiddenInput(DisplayValue=false)>] 133 member public self.HostedById 134 with get() = hostedById 135 and set(value) = hostedById <- value 136 137 member public self.RSVPs 138 with get() = rsvps 139 and set(value) = rsvps <- value 140 141 member public self.IsHostedBy (userName:string) = 142 System.String.Equals(hostedBy,userName,System.StringComparison.Ordinal) 143 144 member public self.IsUserRegistered(userName:string) = 145 rsvps |> Seq.exists(fun r -> r.AttendeeName = userName) 146 147 148 [<UIHint("Location Detail")>] 149 [<NotMapped()>] 150 member public self.Location 151 with get() = new LocationDetail(self.Latitude,self.Longitude,self.Title,self.Address) 152 and set(value:LocationDetail) = 153 let latitude = value.Latitude 154 let longitude = value.Longitude 155 let title = value.Title 156 let address = value.Address 157 ()

Unlike C# where there is a class per file, all of the related elements are placed into a the same location.  Also, notice that the absence of semi-colons, curly braces, and other distracting characters, and finally you can see that because were are in the .NET framework, all of the data annotations are the same.  Sure enough, pointing the MVC UI to the application and hitting run, the application just works.

image

With the separation complete, it was time time to make our app much smarter.  The first thing that I thought of was when the person creates an account, they enter their first and last name

 

This seems like an excellent opportunity to add some user manipulation personalization to our site.  Going back to this analysis of names gives to newborns in the United States, if I know your first name, I have a pretty good chance of guessing your age/gender/and state of birth.  For example ‘Jose’ is probably a male born in his twenties in either Texas or California.  ‘James’ is probably a male in his 40s or 50s.

I added 6 pictures to the site for young,middleAged, and old males and females.

image

 

I then modified the logonStatus partial view like so

1 @using NerdDinner.UI; 2 3 4 @if(Request.IsAuthenticated) { 5 <text>Welcome <b>@(((NerdIdentity)HttpContext.Current.User.Identity).FriendlyName)</b>! 6 [ @Html.ActionLink("Log Off", "LogOff", "Account") ]</text> 7 } 8 else { 9 @:[ @Html.ActionLink("Log On", "LogOn", new { controller = "Account", returnUrl = HttpContext.Current.Request.RawUrl }) ] 10 } 11 12 @if (Session["adUri"] != null) 13 { 14 <img alt="product placement" title="product placement" src="@Session["adUri"]" height="40" /> 15 }

Then, I created a session variable called adUri that the picture will reference in the Logon controller

1 public ActionResult LogOn(LogOnModel model, string returnUrl) 2 { 3 if (ModelState.IsValid) 4 { 5 if (ValidateLogOn(model.UserName, model.Password)) 6 { 7 // Make sure we have the username with the right capitalization 8 // since we do case sensitive checks for OpenID Claimed Identifiers later. 9 string userName = MembershipService.GetCanonicalUsername(model.UserName); 10 11 FormsAuth.SignIn(userName, model.RememberMe); 12 13 AdProvider adProvider = new AdProvider(); 14 String catagory = adProvider.GetCatagory(userName); 15 Session["adUri"] = "/Content/images/" + catagory + ".png"; 16

And finally, I added an implementation of the adProvider back in the application:

1 type AdProvider () = 2 member this.GetCatagory personName: string = 3 "middleAgedMale"

So running the app, we have a product placement for a Middle Aged Male

image

So the last thing to do is to turn names into those categories.  I thought of a couple of different implementations: loading the entire census data set and searching it on demand,  I then thought about using Azure ML and making a API request each time, I then decided into just creating a lookup table that can be searched.  In any event, since I am using an interface, swapping out implementations is easy and since I am using F#, creating implementations is easy.

I went back to my script file that analyzed the baby names from the US census and created a new script.  I loaded the names into memory like before

1 #r "C:/Git/NerdChickenChicken/04_mvc3_Working/packages/FSharp.Data.2.0.14/lib/net40/FSharp.Data.dll" 2 3 open FSharp.Data 4 5 type censusDataContext = CsvProvider<"https://portalvhdspgzl51prtcpfj.blob.core.windows.net/censuschicken/AK.TXT"> 6 type stateCodeContext = CsvProvider<"https://portalvhdspgzl51prtcpfj.blob.core.windows.net/censuschicken/states.csv"> 7 8 let stateCodes = stateCodeContext.Load("https://portalvhdspgzl51prtcpfj.blob.core.windows.net/censuschicken/states.csv"); 9 10 let fetchStateData (stateCode:string)= 11 let uri = System.String.Format("https://portalvhdspgzl51prtcpfj.blob.core.windows.net/censuschicken/{0}.TXT",stateCode) 12 censusDataContext.Load(uri) 13 14 let usaData = stateCodes.Rows 15 |> Seq.collect(fun r -> fetchStateData(r.Abbreviation).Rows) 16 |> Seq.toArray 17

I then created a function that tells the probability of male

1 let genderSearch name = 2 let nameFilter = usaData 3 |> Seq.filter(fun r -> r.Mary = name) 4 |> Seq.groupBy(fun r -> r.F) 5 |> Seq.map(fun (n,a) -> n,a |> Seq.sumBy(fun (r) -> r.``14``)) 6 7 let nameSum = nameFilter |> Seq.sumBy(fun (n,c) -> c) 8 nameFilter 9 |> Seq.map(fun (n,c) -> n, c, float c/float nameSum) 10 |> Seq.filter(fun (g,c,p) -> g = "M") 11 |> Seq.map(fun (g,c,p) -> p) 12 |> Seq.head 13 14 genderSearch "James" 15

image

I then created a function that calculated the year the last name was popular (using 1 standard deviation away)

1 let ageSearch name = 2 let nameFilter = usaData 3 |> Seq.filter(fun r -> r.Mary = name) 4 |> Seq.groupBy(fun r -> r.``1910``) 5 |> Seq.map(fun (n,a) -> n,a |> Seq.sumBy(fun (r) -> r.``14``)) 6 |> Seq.toArray 7 let nameSum = nameFilter |> Seq.sumBy(fun (n,c) -> c) 8 nameFilter 9 |> Seq.map(fun (n,c) -> n, c, float c/float nameSum) 10 |> Seq.toArray 11 12 let variance (source:float seq) = 13 let mean = Seq.average source 14 let deltas = Seq.map(fun x -> pown(x-mean) 2) source 15 Seq.average deltas 16 17 let standardDeviation(values:float seq) = 18 sqrt(variance(values)) 19 20 let standardDeviation' name = ageSearch name 21 |> Seq.map(fun (y,c,p) -> float c) 22 |> standardDeviation 23 24 let average name = ageSearch name 25 |> Seq.map(fun (y,c,p) -> float c) 26 |> Seq.average 27 28 let attachmentPoint name = (average name) + (standardDeviation' name) 29 30 let popularYears name = 31 let allYears = ageSearch name 32 let attachmentPoint' = attachmentPoint name 33 let filteredYears = allYears 34 |> Seq.filter(fun (y,c,p) -> float c > attachmentPoint') 35 |> Seq.sortBy(fun (y,c,p) -> y) 36 filteredYears 37 38 let lastPopularYear name = popularYears name |> Seq.last 39 let firstPopularYear name = popularYears name |> Seq.head 40 41 lastPopularYear "James" 42

image

 

And then created a function that takes in the gender probability of being male and the last year the name was poular and assigns the name into a category:

1 let nameAssignment (malePercent, lastYearPopular) = 2 match malePercent > 0.75, malePercent < 0.75, lastYearPopular < 1945, lastYearPopular > 1980 with 3 | true, false, true, false -> "oldMale" 4 | true, false, false, false -> "middleAgedMale" 5 | true, false, false, true -> "youngMale" 6 | false, true, true, false -> "oldFemale" 7 | false, true, false, false -> "middleAgedFemale" 8 | false, true, false, true -> "youngFeMale" 9 | _,_,_,_ -> "unknown"

And then it was a matter of tying the functions together for each of the names in the master list:

1 let nameList = usaData 2 |> Seq.map(fun r -> r.Mary) 3 |> Seq.distinct 4 5 nameList 6 |> Seq.map(fun n -> n, genderSearch n) 7 |> Seq.map(fun (n,mp) -> n,mp, lastPopularYear n) 8 |> Seq.map(fun (n,mp,(y,c,p)) -> n, mp, y) 9 10 let nameList' = nameList 11 |> Seq.map(fun n -> n, genderSearch n) 12 |> Seq.map(fun (n,mp) -> n,mp, lastPopularYear n) 13 |> Seq.map(fun (n,mp,(y,c,p)) -> n, mp, y) 14 |> Seq.map(fun (n,mp,y) -> n,nameAssignment(mp,y)) 15

image

And then write the list out to a file

1 open System.IO 2 let outFile = new StreamWriter(@"c:\data\nameList.csv") 3 4 nameList' |> Seq.iter(fun (n,c) -> outFile.WriteLine(sprintf "%s,%s" n c)) 5 outFile.Flush 6 outFile.Close()

Thanks to this stack overflow post for the file write (I wish the csv type provider had this ability).  With the file created, I can then use the file as a lookup for my name function back in the MVC app using a csv type provider

1 type nameMappingContext = CsvProvider<"C:/data/nameList.csv"> 2 3 type AdProvider () = 4 member this.GetCatagory personName: string = 5 let nameList = nameMappingContext.Load("C:/data/nameList.csv") 6 let foundName = nameList.Rows 7 |> Seq.filter(fun r -> r.Annie = personName) 8 |> Seq.map(fun r -> r.oldFemale) 9 |> Seq.toArray 10 if foundName.Length > 0 then 11 foundName.[0] 12 else 13 "middleAgedMale"

And now I have some (basic) personalization to Nerd Dinner. (Emma is a young female name so they get a picturer of a campground)

image

So this a rather crude.  There is no provision for nicknames, case-sensitivity, etc.  But the site is along the way to becoming smarter…

The code can be found on github here.

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.

MVC, LINQ, and WinHost

I love MVC. I recently had to make some changes to the swim team website to get ready for the 2011 season. Because there was a nice SOC enforced by MVC, I simply had to add a couple of new controllers with some basic CRUD:

clip_image001

add their associated views, and then wire up to my factory using my POCOs:

public ActionResult Index(int? page) { FamilyFactory familyFactory = new FamilyFactory(); IQueryable<Family> families = familyFactory.GetAllFamilies() .OrderBy(family => family.FamilyDesc) .AsQueryable <Family>(); const int pageSize = 10; var paginatedFamilies = new PaginatedList<Family>(families, page ?? 0, pageSize); return View(paginatedFamilies); }

(note that I used the paginated list from Nerd Dinner)

Boom goes the dynamite.

Upon reflection, you can really see how useful using POCOs are when you have to extend your application. That upfront cost 2 years ago is paying huge dividends now.

I did run into 1 small hiccup when I deployed to WinHost. I received the following error:

clip_image002

After some digging (this error does not Google well), I added this:

<trust level="Full" />

To System.Web and things corrected.

Globalizing the carpool app

I had a question about how to internationalize the carpool app.  I ran through the exercise to try out the Globalization and Localization features of the .NET framework.  

I 1st I added a couple of resource files:

image

Next, I added a simple string to test:

image

Next, I added a reference in the markup on the site.master:

<div id="title"> <h1> <asp:Label ID="LabelMainTitle" runat="server" Text="<%$ Resources:LocalizedText, SiteTitle %>"></asp:Label> </h1> </div>

Next, I updated the web.config file:

<globalization enableClientBasedCulture="true" uiCulture="auto" />

Finally, I changed the browser settings:

image

And Boom goes the dynamite:

image

So the next step is adding the strings to the resource file…

I am using this site for translations.

A couple of random thoughts about the process:

1) ActionLink – need to refer to the class explicitly:

<ul id="menu"> <li><%: Html.ActionLink(Resources.LocalizedText.HomeTab, "Index", "Home")%></li> <li><%: Html.ActionLink(Resources.LocalizedText.PracticeTab , "Index", "Practice")%></li> <li><%: Html.ActionLink(Resources.LocalizedText.AboutTab, "About", "Home")%></li> </ul>

And I don’t know why no inteliisense from the namespace to class, but there is intliisense from the class to the property name.

2) I appreciate the split screen feature of VS2010:

image

3) I ran into a problem with a new HTML Helper Controls in MVC:

<%: Html.LabelFor(model => model.Date) %>

This is how it renders:

image

Apparently, the LabelFor does not support globalization in the view. That means I would need to add the LocalizedDisplayName attribute to each of the POCOs as recommend here OR remove the LabelFor and hand code  labels. Since I am not interested in creating something outside of the base MVC API, I split out the labels like so:

From This:

<%: Html.LabelFor(model => model.Date) %>

 

To This:

<%: Html.Label(Resources.LocalizedText.DateHeader)%>

After spending a couple of hours associating text to the resource files, I was done

Here is the login screen:

image

And the main page:

image

Web.Config Transforms

I wanted to dig into Web.Config transformations a bit more. I read a couple of articles here and here and decided to try it in a “Hello World” MVC2 project. I created a new MVC2 project and found the two Web.xxx.config files:

image

I then created a new setting variable in the Web.Config file:

1 <appSettings> 2 <add key="myValue" value="default"/> 3 </appSettings> 4

I then added a transform to the Debug and Release Files.

Debug:

1 <appSettings> 2 <add key="myValue" value="Debug" xdt:Transform="SetAttributes" xdt:Locator="Match(myValue)"/> 3 </appSettings> 4

Release:

1 <appSettings> 2 <add key="myValue" value="Release" xdt:Transform="SetAttributes" xdt:Locator="Match(myValue)"/> 3 </appSettings> 4

I then added some code to show the value on the main page:

1 public ActionResult Index() 2 { 3 ViewData["Message"] = String.Format("The Value in Web.Config is {0}", ConfigurationManager.AppSettings["myValue"].ToString()); 4 5 return View(); 6 } 7

I then hit F5, expecting the Debug value replace the default value in the web.config

image

Darn, the transform is not being recognized.

MVC2 && EF4.0 Cascading Drop Down List

I am trying to create a cascading drop down in MVC2. This is a simple application so I am using Entity Framework 4.0 classes in my User Layer (instead of the usual POCOs).

I looked at Steve’s Walther’s solution(s) here: the problem is that it doesn’t work – you get an object null reference b/c the page isn’t done loading (I think). I tried putting it into JQuery, which I know waits to fire until the DOM is loaded, but I started getting too deep into the port.

I then looked at other people’s attempts like here and here – lots of code, little explanation and it doesn’t work without modifications.

I then went back to a project that I created does an Ajax call for the second drop down and tried to create my own solution.

Here is the initial controller:

1 public JsonResult GetDoctrinesForAnIssue(int issueId) 2 { 3 IDoctrineFactory doctrineFactory = new DoctrineFactory(); 4 List<Doctrine> doctrines = doctrineFactory.GetDoctrines(issueId); 5 return Json(doctrines.ToList()); 6 } 7  

The problem is that I am using an EF class directly in the JSON return. I am getting a missing object context because the entire graph is not loading and the context is disposed when it is going back to rehydrate – one of limitations of EF is that the instantiated class has to exist with the object context that created it so when that context goes away, you are stuck.

image

When I put the entire graph in to see, I got a circular reference error. Ugh

1 var doctrineQuery = (from doctrine in patentEntities.Doctrines 2 .Include("Issue") 3 .Include("Comment") 4 .Include("Comment.Claim") 5 .etc…. 6 select doctrine).ToList<Doctrine>(); 7 return doctrineQuery; 8  

I then thought about some of my other projects that I have worked on lately. I used POCOs extensively so this kind of problem didn’t surface – the downside is that I would then need to code up the POCOs, the Factories, and the mapping between the POCOs and EF classes – which is a bit overkill for this project. I then thought of a third way of using a dynamic object just for this drop down lists – outside of the factory, inside the controller:

1 public JsonResult GetDoctrinesForAnIssue(int issueId) 2 { 3 IDoctrineFactory doctrineFactory = new DoctrineFactory(); 4 var query = (from doctrine in doctrineFactory.GetDoctrines(issueId) 5 select new { doctrine.DoctrineId, doctrine.DoctrineName }).ToList(); 6 7 return Json(query); 8 } 9  

Add a little LINQ and boom goes the dynamite…