How to use MvcContrib Html.Grid() with a cell that has a colspan of 2

clock February 17, 2010 10:34 by author Shane Sievers

In a recent project, I was working on an MVC View that displayed a list of records with Edit and Delete buttons on them.  This project was using the Blueprint CSS framework as well as Jquery-UI.  Having both of my Edit and Delete buttons in the same cell was forcing one of them onto the next line, like so:

Here is the Html.Grid code I was using:

Html.Grid(Model.Items).Columns(column => {
  column.For(x => Html.ActionLink(x.RecordNumber, "Edit", new {id = x.Id})).DoNotEncode().Named("Record Number");
  column.For(x => x.Author);
  column.For(x => "<div class='option-button ui-corner-all ui-state-default'><a href='Records/Edit/" + x.Id + "' class='ui-icon ui-icon-wrench'></a></div><div class='option-button ui-corner-all ui-state-default'><a class='ui-icon ui-icon-trash'></a></div>")
    .DoNotEncode().Named("Options").HeaderAttributes(new Hash(@class => "{sorter: false} span-1"));
  }).Render();

The problem here is evident, who wants buttons stacked like that?  Now a simple fix may have been to just apply some new CSS properties to better control default width, margin, or padding of these elements.  But that solution doesn't look as pretty in code and adds unneeded weight to the page.  The Blueprint span classes do not go smaller than "span-1", so controlling the content through Blueprint was also out of the question.  I wanted to give my 'Options' table header cell the attribute colspan a value of 2.  At first I wasn't sure of the best way to do this and after a few searches through Google and Stackoverflow I still didn't know the best route.  A coworker pointed out that I should probably start with the Attributes method, and a solution was born:

Html.Grid(Model.Items).Columns(column => {
 column.For(x => Html.ActionLink(x.RecordNumber, "Edit", new {id = x.Id})).DoNotEncode().Named("Record Number");
 column.For(x => x.Author);
 column.For(x => "<div class='option-button ui-corner-all ui-state-default'><a href='Records/Edit/" + x.Id + "' class='ui-icon ui-icon-wrench'></a></div>")
   .Attributes(@width => "20px")
   .DoNotEncode().Named("Options").HeaderAttributes(new Hash(@class => "{sorter: false} span-1", @colspan => "2"));
 column.For(x => "<div class='option-button ui-corner-all ui-state-default'><a class='ui-icon ui-icon-trash'></a></div>")
   .Attributes(@width => "20px").DoNotEncode().Header("");
}).Render();

The solution was to set the HeaderAttribute with a colspan of 2 and then on the next column, override the Header() method with an empty string.  Usually you would place a <th> tag in the Header string, leaving it empty keeps it from rendering that column header.  Voila!  You'll notice I did still have to add width attributes to the content cells, that was unfortunate however I still feel good about the solution.



A .Net class library for Pivotal Tracker API

clock February 1, 2010 09:24 by author Shane Sievers
A few weeks ago our team started using Pivotal Tracker to manager our user stories, bugs, and features.  As taken from their website, 
Tracker is a free, award winning, agile project management tool that enables real time collaboration around a shared, prioritized backlog.
- http://www.pivotaltracker.com/ 

On top of that, its a refreshing break from our previous project management tool,  Target Process.  While Target Process is no doubt a powerful, robust, agile project management solution; it was a bit much sometimes.  Pivotal Tracker is more asthetically pleasing as well as easy to use. 

Unfortunately, there wasn't a lot of help out there for integrating our .Net websites with their API.  The Pivotal Tracker API integration page does feature an exmple by Michael McKerlie here.  This was a decent start, so I've added to it and in turn answered some of the questions posted in his comments.  Behold, my first contribution to Google's Open Source project hosting - http://code.google.com/p/pivotal-tracker-api/.  Here you will find some great wiki pages on how to integrate Pivotal Tracker API for .Net into your solution as well as planned features.  

We currently use the API to integrate our user Feedback form with our Pivotal project.  This way, when a user submits feedback, it enters our project queue as a bug so that we have to take action on it.  It seems to work good in our enviornment, but thats probably because we have closed channel of users - I'm not sure I would recommend giving a public site this functionality.  Opening your Project Management queue to spam-bots would probably be a bad thing.

Two things I look forward to adding in the near future include examples for using with Asp.Net MVC and the ability to Export project stories during a build (NUnit) for backup.  Check back here or the project site often for updates.



NHibernate SQL Queries and SQL Server Profiler

clock May 21, 2009 12:24 by author Anthony Shaw

Have you ever had a time when you were working with an ICriteria to return a specific group of data and found that it just wasn't returning what you wanted and you weren't sure why. In the past when I've had to troubleshoot troublesome SQL queries I would open up SQL Server Profiler to see what might be happening behind the scenes that could be messing up what is being returned. This can be a little bit troublesome when dealing with NHibernate. NHibernate generates the SQL in such a fashion that it becomes rather difficult and time consuming to make these queries readable after looking at their output withing SQL Profiler. Here is an example of a query from profiler, generated by NHibernate:

SELECT this_.id as id49_2_, this_.part_number as part2_49_2_, this_.quantity as quantity49_2_, this_.manufacturer as manufact4_49_2_, this_.part_name as part5_49_2_, this_.description as descript6_49_2_, this_.requested_date as requested7_49_2_, this_.notes as notes49_2_, this_.status as status49_2_, this_.cancelled_date as cancelled10_49_2_, this_.location_user_id as location11_49_2_, this_.customer_id as customer12_49_2_, locationus3_.person_id as person1_40_0_, locationus3_.name_first as name2_40_0_, locationus3_.name_middle as name3_40_0_, locationus3_.name_last as name4_40_0_, locationus3_.name_suffix as name5_40_0_, locationus3_.phone_number as phone6_40_0_, locationus3_.fax_number as fax7_40_0_, locationus3_.email_address as email8_40_0_, locationus3_.job_title as job9_40_0_, locationus3_1_.password as password41_0_, locationus3_1_.is_active as is3_41_0_, locationus3_1_.username as username41_0_, locationus3_1_.location_id as location5_41_0_, locationus3_1_.shopping_cart_id as shopping6_41_0_, customer1_.customer_id as customer1_72_1_, customer1_.name as name72_1_, customer1_.created_by as created3_72_1_, customer1_.dt_created as dt4_72_1_, customer1_.po_prefix as po5_72_1_, customer1_.is_active as is6_72_1_, customer1_.po_required as po7_72_1_, customer1_.enforce_order_multiples as enforce8_72_1_, customer1_.order_alert_recipient_id as order9_72_1_, customer1_.order_alert_dollar_amount as order10_72_1_, customer1_.show_on_hand as show11_72_1_, customer1_.is_central_billing as is12_72_1_, customer1_.display_promatch_number as display13_72_1_, customer1_.site_id as site14_72_1_ FROM quote_requests this_ left outer join people locationus3_ on this_.location_user_id=locationus3_.person_id left outer join dbs_users locationus3_1_ on locationus3_.person_id=locationus3_1_.person_id inner join customers customer1_ on this_.customer_id=customer1_.customer_id WHERE customer1_.site_id = @p0 and (this_.status = @p1 or (this_.status = @p2 and this_.cancelled_date < @p3)) and this_.status = @p4

My question was, what in the world is this query actually doing? In comes Instant SQL Formatter. Once you past the above generated SQL into Instant SQL Formatter and press 'Format SQL' it gives you this:

 

SELECT this_.id                             AS id49_2_,
       this_.part_number                    AS part2_49_2_,
       this_.quantity                       AS quantity49_2_,
       this_.manufacturer                   AS manufact4_49_2_,
       this_.part_name                      AS part5_49_2_,
       this_.DESCRIPTION                    AS descript6_49_2_,
       this_.requested_date                 AS requested7_49_2_,
       this_.notes                          AS notes49_2_,
       this_.status                         AS status49_2_,
       this_.cancelled_date                 AS cancelled10_49_2_,
       this_.location_user_id               AS location11_49_2_,
       this_.customer_id                    AS customer12_49_2_,
       locationus3_.person_id               AS person1_40_0_,
       locationus3_.name_first              AS name2_40_0_,
       locationus3_.name_middle             AS name3_40_0_,
       locationus3_.name_last               AS name4_40_0_,
       locationus3_.name_suffix             AS name5_40_0_,
       locationus3_.phone_number            AS phone6_40_0_,
       locationus3_.fax_number              AS fax7_40_0_,
       locationus3_.email_address           AS email8_40_0_,
       locationus3_.job_title               AS job9_40_0_,
       locationus3_1_.password              AS password41_0_,
       locationus3_1_.is_active             AS is3_41_0_,
       locationus3_1_.username              AS username41_0_,
       locationus3_1_.location_id           AS location5_41_0_,
       locationus3_1_.shopping_cart_id      AS shopping6_41_0_,
       customer1_.customer_id               AS customer1_72_1_,
       customer1_.name                      AS name72_1_,
       customer1_.created_by                AS created3_72_1_,
       customer1_.dt_created                AS dt4_72_1_,
       customer1_.po_prefix                 AS po5_72_1_,
       customer1_.is_active                 AS is6_72_1_,
       customer1_.po_required               AS po7_72_1_,
       customer1_.enforce_order_multiples   AS enforce8_72_1_,
       customer1_.order_alert_recipient_id  AS order9_72_1_,
       customer1_.order_alert_dollar_amount AS order10_72_1_,
       customer1_.show_on_hand              AS show11_72_1_,
       customer1_.is_central_billing        AS is12_72_1_,
       customer1_.display_promatch_number   AS display13_72_1_,
       customer1_.site_id                   AS site14_72_1_
FROM   quote_requests this_
       LEFT OUTER JOIN people locationus3_
         ON this_.location_user_id = locationus3_.person_id
       LEFT OUTER JOIN dbs_users locationus3_1_
         ON locationus3_.person_id = locationus3_1_.person_id
       INNER JOIN customers customer1_
         ON this_.customer_id = customer1_.customer_id
WHERE  customer1_.site_id = @p0
       AND (this_.status = @p1
             OR (this_.status = @p2
                 AND this_.cancelled_date < @p3))
       AND this_.status = @p4

Then, all you have to do is DECLARE your parameters, set their values and run the query and view the output.

 This is going to save me a ton of time during development and troubleshooting troublesome queries.



West Michigan .NET University

clock February 18, 2009 11:40 by author Ryan Montgomery

I will be presenting at the West Michigan .NET University on April 4th about ASP.NET with my friend Carl Furrow. The goal of the .NET University is to educate beginners about .NET. Specifically our discussion will bring you up to speed on ASP.NET, Microsoft’s Web platform. But don’t miss out on some of the other great sessions by some really great people from West Michigan.

We even get a cool badge!

WM .Net University April 4, 2009 - I'll be there!


How to use NHibernate and StructureMap in a WCF application

clock February 17, 2009 12:48 by author Ryan Montgomery

Do you use StructureMap? NHibernate? Good. Have you tried using them with WCF? Yeah, me too. It didn’t go so well for me either. StructureMap, NHibernate, and WCF all worked fine. They did nothing wrong. I guess it was my journey to find out how to make them work together that felt painful. So how did I do it? I Googled it, of course. This led me to read a few varying accounts of how others made it work. Each scenario was different than mine in some significant way (of course) so they each gave me a piece of the whole picture.

First, I tackled with getting StructureMap working with WCF. This wasn’t nearly as problematic as it could have been. Thanks to Jimmy Bogard for writing up a walkthrough on how to make it work.

Jimmy’s post: http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/07/29/integrating-structuremap-with-wcf.aspx

Second, I had to get NHibernate opening sessions per request, which is different than a typical request in an ASP.NET application. This is where the trouble started. I followed a second post by Jimmy discussing this very issue.

Jimmy’s Other Post: http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/09/16/integrating-structuremap-and-nhibernate-with-wcf.aspx

So this got me further but eventually I began having weird issues. As in things working and then not working out of nowhere. Eventually after some frustration I began to suspect a threading issue. Sure enough, In a discussion on the StructureMap User Group, Jimmy mentioned that the solution may have threading problems. Awesome.

So, Googling I found another post by Frank-Leonardo Quednau who described a different approach.

Frank-Leanardo’s Post: http://realfiction.net/?q=node/167

Using the OperationContext.Current.InstanceContext.Extensions collection to store the NHibernateSession because the instance context only lives once for the service request. The idea being that you can open the Session when the InstanceProvider starts and close the Session when it release the service instance.

I followed the post and nearly got it working pretty well, but my project structure didn’t work very well with how it was setup. I don’t know if it’s because of my UnitOfWork or FluentNHibernate or what, but things just didn’t line up for me.

So here is what I ended up doing. (Sorry for the images. It looked better than our style for inline code. Don’t worry though all the code will be posted at the end. And Here.)

1. In my WCF application I created my StructureMap Registry. The registry wires up all my object instances at runtime. You’ll notice that the ServiceinstanceContextBuildPolicy; this is a custom build policy (caching policy is a better term maybe) which instructs StructureMap to cache it’s objects in a  special way for WCF (per service instance). More on that later. Everything else here is pretty run of the mill. I create an NHibernate Session from my SessionSource (fluentNHibernate) which sets up my UnitOfwork and creates an instance of my Repository to do the Querying, Loading, Saving of my domain objects.

SuperAwesomeForkliftParts.WCF.Registry

2. To setup this registry, I needed a BootStrapper. The BooStrapper is initializing StructureMap with my registries, in this case I only have one, but I could add more later.

SuperAwesomeForkliftParts.WCF.Bootsrapper

3. To get call the BootStrapper I needed a StructureMapServiceHostFactory.

SuperAwesomeForkliftParts.WCF.StructureMapServiceHostFactory 

4. The StructureMapServiceHostFactory creates instances of…StructureMapServiceHosts. The StructureMap Service Host isn’t necesarily the best name for it. The Service Host is responsible to add the InstanceCreationService Bahavior, when a Service Host is opened.

SuperAwesomeForkliftParts.WCF.StructureMapServiceHost

5. The InstanceCreationServiceBehavior wires up two things. First it wires up the InstanceProvider which does the “heavy lifting” for our UnitOfWork. Things like Initialize, Commit, RollBack, and Dispose.

SuperAwesomeForkliftParts.WCF.ServiceInstanceProvider

Notice that after the finally block, I check for the InstanceContext.Extensions to Remove the InstanceCreationExtension. Doing so fires the Detach Event which  calls the DisposesAndClear() method on our StructureMap Cache living inside of the InstanceCreationExtension. So where is that InstanceCreationExtension added? Well, in our InstanceCreationInitializer of course! :)

SuperAwesomeForkliftParts.WCF.ServiceInstanceProvider_2

5.a. The InstanceCreationBehavior also wires up an InstanceCreationInitializer which enables the Custom Cache Policy from above, to work. The Initialize method adds a new InstanceCreationExtension which is being created with a null StructureMap InstanceCache as you see below.

SuperAwesomeForkliftParts.WCF.InstanceCreationInitializer

6. So the InstanceCreationExtension is quite important to this whole thing working properly. Its job is to create and destroy the StructureMap InstanceCache when it is added and removed from the service instance.

SuperAwesomeForkliftParts.WCF.InstanceCreationExtension

7. Whew! so now that we have all that in place we still need StructureMap to use that Extensions collection to store the InstanceCache. The InstanceCache in structureMap is where it stores all of it’s Instantiated Objects. So in this case (refer to the Registry) the UnitOfWork and NHibernateUnitOfWork are being stored there but I want a new one every time a new Service Instance is started.

SuperAwesomeForkliftParts.WCF.ServiceInstanceContextBuildPolicy

RED: This is where I check for an existing InstanceCreationExtension which stores our StructureMap InstanceCache. I learned the hard way that many of these things can be null so you’ll see I do a lot of checking for null instances of many things here.

YELLOW: This is where the InstanceCache which will be returned to StructureMap is being set.

GREEN: This is where I’m adding that InstanceCache back to our InstanceCreationExtension so that when StructureMap calls this Cache Policy again it will find it.

Eventually I have a Cache to return to StructureMap so it can grab the object that was being requested. For us, the requested instanceCache will have our NHibernateUnitOfwork on it.

So…Given a ServiceContract that looks like this:

SuperAwesomeForkliftParts.WCF.Services.IPartService

I can do things like this:

SuperAwesomeForkliftParts.WCF.Services.Impl.PartService

My Repository will be injected by StructureMap and will have the UnitOfWork on it to execute the query against the database. The next time this Service is called a new UnitOfWork will be created and we’ll start the whole thing all over again.


If you’ve read this much I appreciate the time you’ve taken sitting through this lengthy post. So as a reward, please feel free to download the code and play around with it. I know it’s not perfect and I’m very open to the possibility that there is a better way to do this. If you have any suggestions please let me know. I appreciate any feedback. As long as it’s good feedback. :)

DOWNLOAD: GET THE CODE HERE!