Monday, March 29, 2010

Generating .Net 1.1 dll from .net 3.5

Recently, I was working on a project that had half its code developed using .Net 1.1 framework while the other half developed using .Net 3.5. Though the pieces worked independently, a few windows services written in .Net 1.1 required an assembly generated at runtime. Now this assembly was being generated from the web site (previously written in .Net 1.1 framework), which now was re-written using 3.5.

This was a problem, because the .Net 3.5 code could compile code at run time targeting either 2.0 or the 3.5 framework. Since the services written in 1.1 required an assembly to be compiled in 1.1, it could not load the assembly that was being generated.

A bit of research and the use of .Net Reflector tool, provided the solution.

Lets start with the code that will compile an assembly at runtime using code written in a text file targeting .Net 3.5 framework.


Dim Compiler As VBCodeProvider = Nothing
Dim pvdr As CodeDomProvider
Dim CompilerParams As CompilerParameters
Dim CompileResults As CompilerResults

Public Function CompileCode() As String
Dim strReturn As String = ""
Try
CompilerParams = New CompilerParameters
With CompilerParams
.TreatWarningsAsErrors = False
.WarningLevel = 4
.GenerateInMemory = False
.IncludeDebugInformation = True

Dim References() As String = {"System.dll",
"System.Data.dll", "Microsoft.VisualBasic.dll"}
.ReferencedAssemblies.AddRange(References)
.OutputAssembly = "C:\Projects\ThisIsATestDLL.dll"

End With

Dim provOpt = New Dictionary(Of String, String)
provOpt.Add("CompilerVersion", "v3.5")
Compiler = New VBCodeProvider(provOpt)


CompileResults = Compiler.CompileAssemblyFromFile(CompilerParams,
"C:\RandD\VS08\TestClassLib\Test.vb")

If CompileResults.Errors.HasErrors Then
For Each Err As CodeDom.Compiler.CompilerError In
CompileResults.Errors
strReturn &= Err.ErrorText & "
@Line: " & Err.Line & vbCrLf
Next
End If

Catch ex As Exception
Throw New Exception(ex.Message, ex)
End Try
Return strReturn
End Function



That is the key. If we switch this to

provOpt.Add("CompilerVersion", "v2.0")


Our code will compile in version 2.0 framework.

What we want is to comile in 1.1. It is not as simple as changing the version to say "v1.1". That wont work. To understand how to accomplish this, we have to look at the code of VBCodeProvider's constructor. I used the .Net reflector to take a look at this.

The CodeDOMCompiler code does the following

If version is 3.5 then
look at the environment variables. If environment variables COMPLUS_InstallRoot and COMPLUS_Version is not present then compile code to default 3.5 version. Else read those environment variable versions to do the compile.
If the version is not 3.5 then it is defaulted to 2.0.

As you can see, the only two values that can be passed to the VBCodeProvider's constructor is "v3.5" or "v2.0".

So in order to compile the assembly in 1.1, we will introduce the env variables in code.

Imports System.CodeDom
Imports System.CodeDom.Compiler

Public Class CodeCompiler
Dim Compiler As VBCodeProvider = Nothing
Dim pvdr As CodeDomProvider
Dim CompilerParams As CompilerParameters
Dim CompileResults As CompilerResults

Public Function CompileCode() As String
Dim strReturn As String = ""

Try
CompilerParams = New CompilerParameters
With CompilerParams
.TreatWarningsAsErrors = False
.WarningLevel = 4
.GenerateInMemory = False
.IncludeDebugInformation = True

Dim References() As String = {"System.dll",
"System.Data.dll", "Microsoft.VisualBasic.dll"}
.ReferencedAssemblies.AddRange(References)
.OutputAssembly = "C:\Projects\ThisIsATestDLL11.dll"


End With

'You can put these values in the config file if you dont want to 'hard code it.
Environment.SetEnvironmentVariable("COMPLUS_InstallRoot",
"C:\WINDOWS\Microsoft.NET\Framework")
Environment.SetEnvironmentVariable("COMPLUS_Version",
"v1.1.4322")


Dim provOpt = New Dictionary(Of String, String)
provOpt.Add("CompilerVersion", "v3.5")


Compiler = New VBCodeProvider(provOpt)
CompileResults = Compiler.CompileAssemblyFromFile(CompilerParams,
"C:\RandD\VS08\TestClassLib\Test.vb")

If CompileResults.Errors.HasErrors Then
For Each Err As CodeDom.Compiler.CompilerError In
CompileResults.Errors
strReturn &= Err.ErrorText & "
@Line: " & Err.Line & vbCrLf
Next
End If

Catch ex As Exception
Throw New Exception(ex.Message, ex)
End Try
Return strReturn
End Function
End Class



The lines of code

Environment.SetEnvironmentVariable("COMPLUS_InstallRoot", "C:\WINDOWS
\Microsoft.NET\Framework")
Environment.SetEnvironmentVariable("COMPLUS_Version",
"v1.1.4322")


does the trick. It foxes the .Net code into believing that we have 3.5 version to be compiled, but since we introduce the env variables before calling the VBCodeCompiler's constructor, it will read the env variables and compile the code in 1.1. It's that simple.

These env variable setting is only for that current code and once the code runs out of scope, the env variables are not persisted.

I had posted this solution on www.experts-exchange.com too.. Hope it helps someone!

Enjoy!

Tuesday, March 9, 2010

Using JQuery for Mouseover, Mouseout and Click on Gridview in ASP.Net

So I have searched, re-searched (pun intended) to find an acceptable solution where I am able to do the following,

1. Enable mouseover and mouseout on a Data Grid View control in ASP.Net
2. Enable Multiple Clickable rows and Single click rows on a grid view control.
3. And access a key data from the row that has been clicked.

Now instead of writing server side code and attaching a ton of javascript at each row creation along with a lot of data, I finally did something with JQuery and Javascript. It works like a charm. Ofcourse, you can all tinker with the code and make the code better... so let me know how it all went!

For starters, here is what you will need..

The latest version of JQuery.

So let's get our hands dirty. (I am obviously assuming you have a test page, with a gridview control on it and a web project created... if not please do that, else reading this would be pointless... ;-D)


STEP1


1. Create a stylesheet and name it.. I dont know, name it something.. maybe styles.css
2. Add the following items to it. This is what we will use to style our grid view. Ofcourse you can use your own styles. The key thing to remember is to have a Pager class if you have paging setup for your grid and a style for tag so that the header is styled too. The pager is important.

.GridClass

{

background-color: #fff;

margin: 5px 0 10px 0;

border: solid 1px #525252;

border-collapse: collapse;

}

.GridClass a

{

color: #284E98;

}

.GridClass a:visited

{

color: #284E98;

}

.GridClass .GridWrap

{

padding: 2px;

border: solid 1px #c1c1c1;

font-size: xx-small;

color: #284E98;

white-space: normal;

}

.GridClass .GridNoWrap

{

padding: 2px;

border: solid 1px #c1c1c1;

font-size: xx-small;

color: #284E98;

white-space: nowrap;

}

.GridClass td

{

padding: 2px;

border: solid 1px #c1c1c1;

font-size: xx-small;

text-align:justify;

color: #284E98;

}

.GridClass th

{

text-align: Center;

padding: 2px 2px 2px 2px;

border-color: #a8bbd5;

border-width: 1px;

border-style: solid;

color: #284E98;

font-size: 8pt;

font-weight: bold;

filter: progid:DXImageTransform.Microsoft.Gradient(endColorstr='#7795BD', startColorstr='#c9d5e4', gradientType='0');

}

.GridClass .alt

{

background: white;

}

.GridClass .pager

{

background: #7795BD;

}

.GridClass .pager table

{

margin: 5px 0;

}

.GridClass .pager td

{

text-align: left;

border-width: 0;

padding: 0 6px;

border-left: solid 1px #666;

font-weight: bold;

color: #fff;

line-height: 12px;

}

.GridClass .pager a

{

color: Black;

text-decoration: none;

}

.GridClass .pager a:hover

{

color: White;

text-decoration: none;

}

.GridClass .pager a:visited

{

color: Black;

text-decoration: none;

}

.GridRowClicked

{

cursor: hand;

}

.GridRowClicked td

{

background-color:#FFFF99;

}

STEP2

Save your stylesheet and create a new file and name it HoverScript.js. We will add our JQuery and Javascripts here and add the following code into the HoverScript.js file and save it.


var PrevRow; //Stores the last clicked row

var NewRow; //Gets the new row clicked

var RowClickedValue = ''; //Stores a comma separated value of the items clicked

var CurrentRowClicked = ''; //Gets the value of the row that has been clicked

var GridPlainHover = '#add8e6'; //Some styling

var GridPlainClicked = '#ffff99'; //Some styling

var GridPlainNeutral = 'white'; //Some styling

var sSkipHeaderFooter = ''; //Tracks whether the Header and Footer will be skipped

var CancelClick = false;

var hdnClickedValues = '';

//This is the function that will set all the hover and click functions.

//The function takes the ClientID of the grid for which the rules will be set

//boolean flags to say if Mulitple clicks should be allowed, If a Header is

//included in the Grid and if a footer is included in the grid. We dont want

//to hover over the Header and footer.

function fn_SetHoverRules(sGrid, bMultiClick, bIncludeHeader, bIncludeFooter) {

if (bIncludeHeader) {

sSkipHeaderFooter = ":first,";

}

if (bIncludeFooter) {

sSkipHeaderFooter = sSkipHeaderFooter + ":last";

}

if (bMultiClick) {

if (sSkipHeaderFooter.length > 0) {

$('table#' + sGrid + ' tr').not(sSkipHeaderFooter).click(function() { fn_RowClickMultiple($(this)) });

}

else

$('table#' + sGrid + ' tr').click(function() { fn_RowClickMultiple($(this)) });

}

else {

if (sSkipHeaderFooter.length > 0) {

$('table#' + sGrid + ' tr').not(sSkipHeaderFooter).click(function() { fn_RowClickSingle($(this)) });

}

else

$('table#' + sGrid + ' tr').click(function() { fn_RowClickSingle($(this)) });

}

if (sSkipHeaderFooter.length > 0) {

$('table#' + sGrid + ' tr').not(sSkipHeaderFooter).mouseover(function() { fn_mouseoverAll($(this)); });

$('table#' + sGrid + ' tr').not(sSkipHeaderFooter).mouseout(function() { fn_mouseoutAll($(this)); });

}

else {

$('table#' + sGrid + ' tr').mouseover(function() { fn_mouseoverAll($(this)); });

$('table#' + sGrid + ' tr').mouseout(function() { fn_mouseoutAll($(this)); });

}

}

function fn_mouseoutAll(item) {

if (item[0].className != 'pager') {

item[0].style.cursor = 'hand';

if (item[0].className != 'GridRowClicked')

item[0].style.backgroundColor = GridPlainNeutral;

}

}

function fn_mouseoverAll(item) {

if (item[0].className != 'pager') {

item[0].style.cursor = 'hand';

if (item[0].className != 'GridRowClicked')

item[0].style.backgroundColor = GridPlainHover;

}

}

function fn_RowClickMultiple(item) {

if (!CancelClick) {

if (item[0].className != 'pager') {

CurrentRowClicked = item[0].cells[0].getAttribute('id');

if (CurrentRowClicked != '') {

if (item[0].className == 'GridRowClicked') {

fn_RemoveKeys(item[0].cells[0].getAttribute('id'));

item[0].className = '';

}

else {

item[0].className = 'GridRowClicked';

fn_AddKeys(item[0].cells[0].getAttribute('id'));

}

}

}

}

CancelClick = false;

} //End fn_RowClickMultiple

function fn_RowClickSingle(item) {

if (!CancelClick) {

if (item[0].className != 'pager') {

RowClickedValue = item[0].cells[0].getAttribute('id');

if (RowClickedValue != '') {

PrevRow = NewRow;

NewRow = item[0];

if (PrevRow != undefined) {

PrevRow.className = '';

PrevRow.style.backgroundColor = GridPlainNeutral;

}

NewRow.style.backgroundColor = GridPlainClicked;

CurrentRowClicked = RowClickedValue;

}

}

}

CancelClick = false;

} //End fn_RowClickSingle

function fn_AddKeys(KeyValue) {

RowClickedValue = RowClickedValue + KeyValue + ';'

}

function fn_RemoveKeys(KeyValue) {

var indexOf = 0;

indexOf = RowClickedValue.indexOf(KeyValue);

if (indexOf >= 0)

RowClickedValue = RowClickedValue.replace(KeyValue + ';', '');

}


STEP 3


In my example, I am using a Gridview control with paging setup. For the purpose of explaining how to do the setup, I have my gridview retrieving information from an Employees table. The key item I will be associating with the Row Click is the “Employee ID” which I will use to perform the required operations. For example, select Multiple Employee rows and click Delete or Select Multiple Employee rows and click a Process button .etc.

Obviously, you will have your own Gridview control with your own Bound or unbound columns. So feel free to modify it the way you want. A few things to remember (and I take the liberty in the assumption)..

  1. The Gridview will have a CSS class as “GridClass”.
  2. If there is a pager, it’s class will be set to “pager” CSS class.
  3. The unique id for each row will be associated with the FIRST VISIBLE COLUMN. Now this is important. For the code above to work correctly, it is assuming that the first visible column of the grid has the id that will be retrieved and stored when the user clicks on the row. If you ommit this step, the javascript will fail to retrieve the id. You can modify that piece get it from whichever column you set the unique key to.

Based on these conditions and assumptions, add a Gridview to your aspx page



<asp:GridView ID="gdvEmployees" runat="server" AutoGenerateColumns="False" AllowPaging="True"

EnableViewState="False" PageSize="500" PagerStyle-CssClass="pager" Width="100%"

AlternatingRowStyle-CssClass="alt" CssClass="GridClass" AllowSorting="True" DataKeyNames="Emp_ID">

<Columns>

<asp:BoundField Visible="false" DataField="Emp_ID" HeaderText="Employee ID"/>

<asp:BoundField DataField="Date_Inserted" HeaderText="Date Inserted" />

<asp:BoundField DataField="Emp_Name" HeaderText="Employee Name"/>

<asp:BoundField DataField="Emp_DOB" HeaderText="Date Of Birth" />

<asp:BoundField DataField="Manager_ID" HeaderText="Manager"/>

Columns>

<PagerStyle CssClass="pager">PagerStyle>

asp:GridView>

Add a hidden control to the page and call it “hdnClickedValues” and enable it to run at Server

<input type="hidden" runat="server" id="hdnClickedValues" value="" />


Add a button to the page that will process the values clicked on the grid.



<asp:Button runat="server" ID="btnShowClicked" Text="Show Clicked" CssClass="ButtonMain"

Width="150px" OnClientClick="return fn_GetValues();" />



I am not going to add the code to retrieve the data from the database and bind it to the grid. You do that. Now once you have done that in your code behind, go to the RowDataBound event of the Gridview and add the following lines of code.



Private Sub gdvEmployees_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles gdvUnknownErrors.RowDataBound

'Get the current instance of the Row being bound

Dim GridRow As GridViewRow = e.Row

'Make sure this is a row and not a Header or a Footer

If GridRow.RowType = DataControlRowType.DataRow Then

'Create an instance of the DatarowView from the DataItem being bound

Dim DRV As DataRowView = CType(e.Row.DataItem, DataRowView)

'Make sure we have a Emp_ID

If Not IsDBNull(DRV("Emp_ID")) Then

'Assign the Id attribute to the first visible column, in our case it will be Date Inserted

GridRow.Cells(1).Attributes.Add("id", DRV("Emp_ID"))

End If

End If

End Sub



Now in your aspx source code include the Javascript file and the Style sheet




<script language="javascript" src="/HoverScript.js" type="text/javascript">script>



Just below the include line, add the following script



<script language="javascript" type="text/javascript">

$(document).ready(function() {

var bEnableMultiRowClick = 1; //Set the value to state whether multiple rows can be clicked 1=true, 0=single row click only

var bHasHeader = 1; //Set the value to indicate if the Grid contains a Header 1=Yes, 0=No

var bHasFooter = 1; //Set the value to indicate if the Grid contains a Footer 1=Yes, 0=No

var stblUnknownErrors = '';

stblEmployees = "<%= gdvEmployees.ClientID %>";

hdnClickedValues = "<%= hdnClickedValues.ClientID %>";

fn_SetHoverRules(stblEmployees , bEnableMultiRowClick, bHasHeader, bHasFooter);

});

function fn_GetValues() {

$get(hdnClickedValues).value = RowClickedValue;

return true;

}

script>



In your code behind, add code for the click event of your button control



Protected Sub btnShowClicked_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnShowClicked.Click

Dim strClickedRows() As String

Try

strClickedRows = hdnClickedValues.Value.Split(";")

For Each strClickedRow As String In strClickedRows

If Not Trim(strClickedRow) = "" Then

Response.Write(strClickedRow)

End If

Next

LoadGridData(gdvUnknownErrors.PageIndex, "")

Catch ex As Exception

Throw New Exception(ex.Message, ex)

End Try

End Sub



That’s it. Run your web page and see it all come together. I have shown you how you can do the following

  1. Use JQuery to perform Mouse over, Mouse Out and Click on a Grid view control.
  2. Skip Header and Footer from mouse over and mouse out and click while using JQuery to perform that operation.
  3. Retrieve the clicked values in your code behind.

Hope this is helpful to anyone looking for it. I am not including any code for this, it is upto you to put it all together and make it work. Comments are most welcome and I will answer any questions or issues you might have doing this.