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.



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.



BizTalk RegexReplace PipelineComponent

clock February 11, 2009 22:47 by author Ryan Montgomery

BizTalk solves a lot of problems, but with it comes a whole other bag of fun new ones to solve. Oh joy. All kidding aside I have been facing some real challenges working with cXML in BizTalk due to the nature of the cXML standard. Primarily the fact that cXML does not use XSD’s to describe itself and instead uses a DTD. BizTalk does not like DTD’s, as in, if you send a document to BizTalk with a DTD using the standard XML Pipeline, it will fail.

BizTalk also doesn’t work well if you try sending it messages that don’t have an XML Namespace because BizTalk uses the Namespace and the root node of the XML document to identify the message inside the system. So I have to inject something like xmlns=”http://something” into the root node as well.

Some solutions have been documented but I ran into other issues and ended up the solution a little further. Long story short, I ended up creating a custom Pipeline Component to strip out DTD’s and add Namespaces using regular expressions. At first I created a cXML cleaner component that worked specifically for my scenario. This worked fine but eventually I noticed that I was using a regular expression to find and replace the DTD which led me to think “Why can’t I use any regex I pass in and replace anything I want to?” So I did. I even made it open source so no one ever has to feel the anguish of having to develop custom BizTalk Pipeline Components, because it sucks. Bad.

Feel free to pull it down and give it whirl. If you improve it, by all means, send me a patch and I’ll include it in the trunk.

RegexReplace on Google Code

Screenshots:

1. Use Multiple RegexReplace in a single stage to process a document multiple times.

RegexReplace_01

2. Simply set the Pattern and the Replacement string.

RegexReplace_02



ASP.NET MVC RC + Gallio + TestDriven.NET = Fatal Execution Engine Error

clock February 1, 2009 12:00 by author Ryan Montgomery

UPDATE: Turns out it was just Gallio (v3.0.5 build 546 - x86). I also tried uninstalling TestDriven.NET and the problem persisted.

This weekend I spent some time working with the release candidate of ASP.NET MVC to familiarize myself with the new features, only to find Visual Studio crashing every time I opened a View. It was pretty frustrating, especially since this was the almost ready for prime time version, and I had been working without any problems for a while using earlier versions. Obviously I started searching the interweb for an answer.

Zero point two seconds later Google found me an answer on StackOverflow. Turns out I'm not the only one who has experienced this strange behavior. When I say strange behavior, I mean “.NET Runtime version 2.0.50727.3053 - Fatal Execution Engine Error (70DC5E00) (80131506)” strange. I had started by removing the Spark View Engine add-in I have for working with Spark and MVC (which is awesome). That didn’t fix it though. A Christian Dalagar pointed out that he experienced the same problem and it appeared to be a conflict between Gallio and TestDriven.NET. Hmmm…That’s funny. I happen to have both of those installed as well!

This was the hard decision…well not really. I can’t live without my TestDriven.NET. Sorry Gallio. Maybe we’ll meet again in another life. Don’t get me wrong I like Gallio, and MBUnit is kind of growing on me, but TestDriven.NET is the best way to run tests in Visual Studio.

PROBLEM: Opening ASP.NET MVC RC Views Crashes Visual Studio when both TestDriven.NET and Gallio are installed.

SOLUTION: Un-install Gallio.



Loading CSV data into a Table using jQuery

clock January 20, 2009 15:35 by author shane sievers

Recently I was working on a web page that was overburdened with tabular data. The original goal was to database the information and just read/format the data with some server side code (PHP). The problem was that I had FTP access to the site but no administrative logins to setup and configure MySQL or another data store. This felt like a good time to experiment with another great extension to jQuery: $.csv() [http://plugins.jquery.com/project/csv ].

Drop the source code into an existing .js file or create a new one and make sure you link to it after you've loaded jQuery to the page. Now lets take a look at our HTML.

<h4>Select a year
<select id="year">
 <option>1900</option>
 <option>1910</option>
 <option>1920</option>
 <option>1930</option>
 </select>
</h4>

<table class="tableContent"> 
 <tr style="background-color:#516DB6; color: #fff;"> 
   <th>NAME</th><th>M/F</th><th>BORN</th><th>RESIDENCE AT HH</th> 
 </tr>
</table>

Now for the controls that will cause the update. You can see in the screen shot that we want to keep the update controls very simple. When a visitor changes the drop down list we want to take the new SELECT box value and search for that data.

HHO_year_select

Now that we have our HTML setup, lets start writing some javascript. We need to start by loading the CSV document on page load or once the document is ready. For this we can use the jQuery AJAX method $.get().

  $.get("documents/1900_Census.csv", function(data) { 
      array = $.csv() (data); 
      }); 

Now that we have our data in an Array, lets use jQuery to loop through each row.

  $.each(array, function() { 
          var row = String(this).split(",");
      }

The jQuery .each() function will setup each new row for our table. The line inside of this function splits the row into an array representing each column of data. We can later use those array elements to output our data.

Now that we can load the CSV data into our table. To clean things up a bit, lets put our code into a more complete function that will cause the table to fade out, load the data, then fade back in. Removing table rows with jQuery is simple, all we need to do is find every row but the first and call the .remove() method. Below you can see how we load the data once the document is ready and bind the change event of the drop down list.

 <script type="text/javascript">
  $(document).ready(function() {
     loadCSVFile(1900);
     $('#year').change(function() {
	loadCSVFile($('#year').val());
     });	
 });

function loadCSVFile(year)
{
  $('.tableContent').hide();
 
  $('#loading').fadeIn(); 
 
  $('.tableContent').find("tr:gt(0)").remove();
  $.get('documents/' + year + '_Census.csv', function(data) { 
      array = $.csv() (data); 
      $.each(array, function() { 
        var row = String(this).split(","); 
	if (row[0] != "") 
	{
          $('.tableContent').append("" + row[0] + "," + row[1] + "");
          $('.tableContent').append("" + row[2] + "" + row[3] + "" + row[4] + ""); 
	}
      }); 
  $('.tableContent tr:odd').css('background-color', '#ffc'); 
  $('.tableContent').fadeIn(); 
});
</script>

You can check out the final result here.