Wednesday, November 12, 2008

Open Ajax Accordion control panes with code

Here's a clever chap telling people how to use java script to open panes manually in an Ajax Accordion control.

And here's some additions to his function to make it a Toggle and not just an open.

function Togglepane(paneIndex, eventElement) {
var behavior = $get('<%=accReply.ClientID%>').AccordionBehavior;
var sel = behavior.get_SelectedIndex();
if (sel == paneIndex){paneIndex = -1};
behavior.set_SelectedIndex(paneIndex);
eventElement = eventElement == null ? window.event : eventElement;
eventElement.cancelBubble =true;
}

I also recommend you have a browse through the methods for the behaviour object. It's codearific!

Thursday, September 25, 2008

A quick ASP.NET AJAX Thread Watcher

Thanks to this awesome article by Peter A. Bromberg i've unlocked the secrets of ASP.NET threading. Making it classy also makes it perpetually useful for dropping a chunk of business logic in any of your apps, not just the ASP ones.

So here is a VB.NET version of the above. Note that I have added in a parameter object to the class so you can pass that into the thread if need be. Also, the thread function makes a call to another function to do the actual work. So from this class that does the thread management, you can inherit another class that does the work, as well as add any other members you might want to update as the thread progresses.

Imports System.Threading

Public Class ThreadTask
Protected _firstRunComplete As Boolean = False


Public ReadOnly Property firstRunComplete() As Boolean

Get

Return _firstRunComplete

End Get

End Property


Protected _running As Boolean = False


Public ReadOnly Property Running() As Boolean

Get

Return _running

End Get

End Property


Protected _lastTaskSuccess As Boolean = True


Public ReadOnly Property LastTaskSuccess() As Boolean

Get

If (_lastFinishTime = Date.MinValue) Then

Throw New InvalidOperationException("The task has never completed.")

End If

Return _lastTaskSuccess

End Get

End Property


Protected _exceptionOccured As Exception = Nothing


Public ReadOnly Property ExceptionOccured() As Exception

Get

Return _exceptionOccured

End Get

End Property


Protected _lastStartTime As Date = Date.MinValue


Public ReadOnly Property LastStartTime() As Date

Get

If (_lastStartTime = Date.MinValue) Then

Throw New InvalidOperationException("The task has never started.")

End If

Return _lastStartTime

End Get

End Property


Protected _lastFinishTime As Date = Date.MinValue


Public ReadOnly Property LastFinishTime() As Date

Get

If (_lastFinishTime = Date.MinValue) Then

Throw New InvalidOperationException("The task has never completed.")

End If

Return _lastFinishTime

End Get

End Property


Protected _parameter As Object

Public ReadOnly Property Parameter() As Object

Get

Return _parameter

End Get

End Property


Public Sub RunTask(ByVal Parameter As Object)

SyncLock Me

If Not _running Then

Me._running = True

Me._lastStartTime = Date.Now

Dim t As Thread = New Thread(AddressOf Kickoff)

t.Start(Parameter)

Else

Throw New InvalidOperationException("The task is already running!")

End If

End SyncLock

End Sub


Protected Sub Kickoff(ByVal Parameter As Object)

Try

Me._parameter = Parameter

Dim ex As Exception = DoWork(Parameter)

If ex IsNot Nothing Then

Throw ex

End If

Me._lastTaskSuccess = True

Catch e As Exception

Me._lastTaskSuccess = False

Me._exceptionOccured = e

Finally

Me._running = False

Me._lastFinishTime = Date.Now

If Not Me._firstRunComplete Then Me._firstRunComplete = True

End Try

End Sub


Public Overridable Function DoWork(ByVal Parameter As Object) As Exception

Try

Thread.Sleep(8000)

Catch e As Exception

Return e

Finally

End Try

Return Nothing

End Function

End Class

Now derive something that actually does a useful job.

Public Class JobThread
Inherits ThreadTask

Public Overrides Function DoWork(ByVal Parameter As Object) As Exception
Try
Dim Job As Integer = Parameter
Dim dc As New JobDataContext
dc.CommandTimeout = 10000
dc.ProcessJob(Job)
Catch e As Exception
Return e
End Try
Return Nothing
End Function
End Class
Last but not least, if you are running a thread people will want to know what is happening with it, so based on the original page I've made a Web User Control you can drop on the page. Just call it's Start method when you need to and boom shanka, all done.

The control itself is designed to ensure only one thread is running. It also works between sessions as it runs the thread in the server cache under a unique global name. Useful if you close the wrong tab in your browser.
Markup:

<%@ Control Language="vb" AutoEventWireup="false" CodeBehind="JobWatcher.ascx.vb"
Inherits="JobWatcher" %>

<script language="JavaScript">
var sURL = unescape(window.location.pathname);

function refresh() {
window.location.href = sURL;
}
</script>

<asp:UpdatePanel ID="upnlWatch" runat="server">
<ContentTemplate>
<asp:Timer ID="tmrRefresh" runat="server" Interval="3000" Enabled="false">
</asp:Timer>
<asp:Panel ID="pnlWorking" runat="server" BackColor="AliceBlue" BorderStyle="Solid"
BorderWidth="1" Visible="false">
<asp:Label ID="msgLabel" runat="server"></asp:Label>
<asp:Image ID="imgTrobber" runat="server" ImageUrl="~/Images/Throbber-mac.gif" Visible="false" />
</asp:Panel>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="tmrRefresh" EventName="Tick" />
</Triggers>
</asp:UpdatePanel>

Code Behind:

Public Class ComparisonThread
Inherits ThreadTask Imports System.ComponentModel
Partial Public Class JobWatcher
Inherits System.Web.UI.UserControl

Protected task As JobThread = Nothing
Protected _name As String = "JobWatcher"

Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property

Public Sub Run(ByVal Parameter As Object)
If Cache(Me.Name) Is Nothing Then
task = New JobThread()
task.RunTask(Parameter)
Cache(Me.Name) = task
tmrRefresh.Enabled = True
pnlWorking.Visible = True
msgLabel.DataBind()
Else
msgLabel.Text = "There is already a Job running!"
End If
End Sub

Private Sub msgLabel_DataBinding(ByVal sender As Object, ByVal e As System.EventArgs) Handles msgLabel.DataBinding
If task IsNot Nothing Then
If task.Running Then
msgLabel.Text = _
"Job " & task.Parameter & " is running now.<br>" & _
"Time Started: " & task.LastStartTime.ToString() & "<br>" & _
"Time Elapsed: " & Math.Ceiling((Date.Now - task.LastStartTime).TotalSeconds) & " seconds."
Else
imgTrobber.Visible = False
tmrRefresh.Enabled = False
msgLabel.Text = "Job " & task.Parameter & " is <b>finished</b> now. <a href='javascript:refresh()'> (refresh page) </a><br>"
If task.LastTaskSuccess Then
msgLabel.Text &= "<font color='Green'>Job succeeded.</font>"
Else
msgLabel.Text &= "<font color='Red'>Job failed.</font>"
If task.ExceptionOccured IsNot Nothing Then
msgLabel.Text &= "<br>The exception was: " + task.ExceptionOccured.Message()
End If
End If
Cache.Remove(Me.Name)
End If
End If
End Sub

Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
task = CType(Cache(Me.Name), JobThread)
If task IsNot Nothing Then
If Not tmrRefresh.Enabled Then tmrRefresh.Enabled = True
If Not pnlWorking.Visible Then pnlWorking.Visible = True
msgLabel.DataBind()
Else
pnlWorking.Visible = False
End If
End Sub
End Class

Monday, September 22, 2008

A comprehensive and generic ListView Pager.

Here is a comprehensive List View Pager, for your convenience. Just copy the code into your markup and the functions into your code behind It is absolutely generic, you can have any number of listviews in your markup and they can all use the one set of pager functions. That's double plus generic!

Please forgive Bloggers horrendous formatting.

First, A checkbox to switch Paging on and off, if you like. Put it in your layout template, I recommend in the first column header of your view which is usually the one above your edit and delete buttons:

<asp:checkbox id="AllowPaging" runat="server" autopostback="True" oncheckedchanged="AllowPaging_CheckedChanged" text=" Paging" Backcolor="Transparent" bordercolor="Transparent" checked="true">


Next, the pager Template itself. I put this as the last row in my Listview Layout template:
<tr runat="server" id="PagerRow">
<td id="Td2" runat="server" class="lvPagerow ">

<asp:DataPager ID="lvPager" runat="server">

<Fields>

<asp:TemplatePagerField OnPagerCommand="lvPager_OnPagerCommand">

<PagerTemplate>

<asp:Panel ID="pnlPager" runat="server">

<asp:ImageButton ID="ImageButton1" runat="server" ImageUrl="~/images/first.gif" CommandArgument="First" CommandName="Page" />

<asp:ImageButton ID="ImageButton2" runat="server" ImageUrl="~/images/prev.gif" CommandArgument="Prev" CommandName="Page" />

Page

<asp:DropDownList ID="ddlPages" runat="server" AutoPostBack="True" OnSelectedIndexChanged="Pages_SelectedIndexChanged" OnDataBinding="ddlPages_OnDataBinding">

</asp:DropDownList>

of

<asp:Label ID="lblPageCount" runat="server" Text='<%# System.Math.Ceiling(Container.TotalRowCount / Container.PageSize) %>'></asp:Label>
<asp:ImageButton ID="ImageButton3" runat="server" ImageUrl="~/images/next.gif" CommandArgument="Next" CommandName="Page" />

<asp:ImageButton ID="ImageButton4" runat="server" ImageUrl="~/images/last.gif" CommandArgument="Last" CommandName="Page" />

</asp:Panel>

</PagerTemplate>

</asp:TemplatePagerField>

</Fields>

</asp:DataPager>

</td>

</tr>

Add the following Event binding in your ListView declaration:
OnDataBound="ListView_DataBound"
And Now, the functions that do the work in code behind:
Protected Sub AllowPaging_CheckedChanged(ByVal sender As Object, ByVal e As System.EventArgs)
Dim lvPager As DataPager = CType(sender.bindingcontainer, ListView).FindControl("lvPager")

Dim chkPaging As CheckBox = CType(sender, CheckBox)

lvPager.Visible = Not lvPager.Visible

CType(sender.BindingContainer, ListView).DataBind()

End Sub

Protected Sub Pages_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)
Dim ddlPages As DropDownList = CType(sender, DropDownList)

Dim lvPager As DataPager = CType(sender.BindingContainer, DataPager)

lvPager.SetPageProperties((ddlPages.SelectedValue - 1) * lvPager.PageSize, lvPager.PageSize, True)


End Sub

Protected Sub ddlPages_OnDataBinding(ByVal sender As Object, ByVal e As System.EventArgs)
Dim lvPager As DataPager = CType(sender.BindingContainer, DataPager)


Dim i As Integer


If lvPager IsNot Nothing Then

Dim Pages As Integer = System.Math.Ceiling(lvPager.TotalRowCount / lvPager.PageSize)

Dim CurrentPage As Integer = System.Math.Ceiling((lvPager.StartRowIndex + 1) / lvPager.PageSize)


Dim ddlPages As DropDownList = lvPager.Controls(0).FindControl("ddlPages")


If ddlPages IsNot Nothing Then

For i = 1 To Pages

Dim lstItem As New ListItem(i)

If i = CurrentPage Then

lstItem.Selected = True

End If

ddlPages.Items.Add(lstItem)

Next

End If


End If

End Sub

Protected Sub lvPager_OnPagerCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DataPagerCommandEventArgs)
If e.CommandName = "Page" Then

Select Case e.CommandArgument

Case "Next"

Dim newIndex As Integer = e.Item.Pager.StartRowIndex + e.Item.Pager.PageSize

If newIndex <= e.TotalRowCount Then

e.NewStartRowIndex = newIndex

e.NewMaximumRows = e.Item.Pager.MaximumRows

End If

Case "Prev"

e.NewStartRowIndex = e.Item.Pager.StartRowIndex - e.Item.Pager.PageSize

e.NewMaximumRows = e.Item.Pager.MaximumRows

Case "First"

e.NewStartRowIndex = 0

e.NewMaximumRows = e.Item.Pager.MaximumRows

Case "Last"

e.NewStartRowIndex = System.Math.Floor(e.Item.Pager.TotalRowCount / e.Item.Pager.PageSize) * e.Item.Pager.PageSize

e.NewMaximumRows = e.Item.Pager.MaximumRows

End Select


End If

End Sub

Protected Sub ListView_DataBound(ByVal sender As Object, ByVal e As System.EventArgs)
Dim lvPager As DataPager = CType(sender, ListView).FindControl("lvPager")

Dim chkPaging As CheckBox = CType(sender, ListView).FindControl("AllowPaging")

If chkPaging.Checked Then

lvPager.PageSize = 10

lvPager.SetPageProperties(lvPager.StartRowIndex, 10, False)

Else

lvPager.PageSize = lvPager.TotalRowCount

lvPager.SetPageProperties(0, lvPager.TotalRowCount, False)

End If

End Sub
And don't forget the following snippet of CSS:
.lvPagerow { background-color: #284775; color: white; text-align: center; }
Now add your own salt and pepper to taste. DONE!

You're welcome.

Tuesday, September 16, 2008

LINQ Left Outer Joins and filtering the Outer fields.

I just had a psycho time trying to get a fairly simple LEFT OUTER JOIN query working in LINQ. In the end, I had to fudge my query with some nesting in the Select. but that's a fairly inelegant way to do things. Especially given the Transact-SQL query itself is easy, it should easily translate to LINQ. Yes, i can already hear you laughing at that statement just as I did when I wrote it. As is usually the case with LINQ, the complexity is related more to how you are trying to get your chosen data view to sing with the data you get back from a query. So while it would have been fairly easy to solve my problem with a conventional list or details view control, conventional solutions do not a guru make. So anyway, let me lay out some data definition for you to demonstrate my problem. I have a list of Companies that do Mail Ordering. Each Company may have a presence in a number of Regions. Each Region for a company may allow a number of predetermined Postage options. So, my Db tables and fields for this may be something like:
Company (Company_Id, Company_Name)
Regions(Region_Id, Region_Name)
Postage(Postage_Id, Postage_Name)
Company_Regions(Company_Region_Id, Company_Id, Region_Id)
Region_Postage(Company_Region_Id, Postage_Id)
Now, we've got a Company Interface. There is a section in the interface for managing the Regions, which is a Gridview that lists rows for the associated Regions and allows insertion and deletion to the list. What I've been asked to do is allow row selection in the Region list and have all Postage items displayed as Checkboxes under the Region list, with those that are associated being checked. Obviously, checking or unchecking a box will insert or delete a row into Region_Postage. The quickest way to get my Checkboxes to show is in a repeater that has a LEFT OUTER JOIN query behind it. In Transact-SQL, my OUTER JOIN query would look something like this.
Select post.Postage_Name, Post.Postage_Id, reg.Company_Region_Id
From Postage post
left outer join Region_Postage reg
on post.Postage_Id = reg.Postage_Id
and reg.Company_Id = --selected value of the region gridview--
The results of which will look like
Postage_NamePostage_IdCompany_Region_Id
Local 1 22
International 2 Null
Local Courier 3 22
International Courier 4 22
Pick Up Only 5 Null
Nice. Now I bind my Checkbox value to the presence of Contract_Output_Id and handle insertion and deletion in my code behind. Done. But no, we are using LINQ now, so that Transact-SQL needs to be translated. For the most part I can translate the LEFT OUTER JOIN. There are plenty of examples online on how to do this. Here's one that I almost got working.
FROM p IN Postage
GROUP JOIN r in Region_Postage on p.Postage_Id EQUALS r.Postage_Id into reg = GROUP
FROM r in reg.DefaultIfEmpty
SELECT NEW WITH {p.Postage_Name, p.Postage_Id, .Company_Region_Id =
r.Company_Region_Id.ToString}
So that makes a group join on Region_Postage and returns a null object if there is no match. The trick has been to translate the condition on Company_Region_Id. Where does it go? I haven't found a sample and I haven't been able to figure it out with trial and error. So here's how I kludged it.
FROM p IN Postage
SELECT p.Postage_Name, p.Postage_Id, Company_Region_Id =
{ From creg in RegionPostage
Where creg.Company_Regions.Company_Id = --some selected value--
And creg.Postage_Id = p.Postage_Id
Select Company_Region_Id = creg.Company_Region_Id.ToString}.SingleOrDefault
Any port in a storm. Funny thing is, this query was more efficient.

Tuesday, September 9, 2008

I have the first book

So now I have the official Microsoft Study guide for .NET 3.5 Windows Presentation Foundation. It cost me $65 all up which is a good patch on the $100 you'll pay in a bookshop. IF you manage to find it on the shelf that is. One online service was asking up to $1700. That's not for all six VS2008 training and exams, that's for just one section! Nuts I tells ya! I've opted for C# coding seeing as how I do VB at work. I also like the pain. Really though, it's simply a matter of getting accustomed to different flavours of the same thing. The syntax is extremely similar. Now if only I could get over this flu so I could read more than a page without getting dizzy....

Friday, August 29, 2008

The two most useful hints for LINQ n00bs.

If you are reading this, welcome to LINQ. You won't regret it. I hope you scoured Scott Guthrie's blog for all the LINQy goodness it has. In terms of keeping your business rules for data together in your app, LINQ is a god send. Defining reams of stored procedures then hooking them all up in ADO.NET was getting tiresome. Especially if your Update parameter changes would not be detected by a Table Adapter reconfigure no matter how many rebuilds and cleans you ran so you had to recreate from scratch and you forgot to note the extra queys first and crap VSS just cacked itself and the other dev just rebuilt his workstation so he doesn't have a copy and OH MY GOD THE URGE TO KILL IS RISING!!! Now, a couple of tips for first timers. To help avoid random homicide. First, what is the data type returned by a LINQ query? Well, given we have a SalesDataAccess namespace with a SalesRecord class sitting in a LINQ data context, we would do this:
Dim query As System.Linq.IQueryable(Of SalesDataAccess.SalesRecord) = From sr In ldsSales.SalesRecords Select Sales
That's it. IQueryable is the one. You've no idea how hard it was to figure that out with just intellisense and google. Or maybe my googling is lame. If your result set is shaped, you'll need to create a Generic.List with the data types of your result set as items. Put your query in parentheses and use .ToList to make it happen.
Dim query AsGeneric.List(Of String, Integer, String) = (From sr In ldsSales.SalesRecords Select Name = Sales.Name, ID = Sales.ID, Store = Sales.StoreName).ToList
Another good tip is how to use the .Where function. Handy if you have to filter your query according to a list of controls that may or may not have filter options selected. After your query, write something like this for each filter option that has a value.
query = query.Where(Function(salerec As SalesDataAccess.SalesRecord) salerec.Company_Id = ddlCompany.SelectedValue)
So what's going on here? In the Where parameter, you actually define a boolean function that rows from the query will be passed into. If the row would return true from the function, it stays. Otherwise it goes. Obviously the parameter in the function is the same type as the rows in your query. Easy when you know how, but I sacrificed a lot of hair to these two nuggets. My cert books cannot come fast enough.

Send any saved Image over HTTP

How to send an image from your DB in a web page with ASP.NET is well documented. You make an aspx page that sends the bytes on page_load via the response object. If you don't know what type of image you are sending, use the below to figure it out and get your response headers right.
Dim stream As System.IO.MemoryStream Dim img As System.Drawing.Bitmap stream = New System.IO.MemoryStream(ImageBytes) img = System.Drawing.Bitmap.FromStream(stream) If ImageBytes.Length > 0 Then Response.ContentType = "image/" & ImageFormat(img.RawFormat) Response.BinaryWrite(ImageBytes) End If Private Function ImageFormat(ByVal format As System.Drawing.Imaging.ImageFormat) As String If format.Equals(System.Drawing.Imaging.ImageFormat.Bmp) Then Return "BMP" End If If format.Equals(System.Drawing.Imaging.ImageFormat.Emf) Then Return "EMF" End If If format.Equals(System.Drawing.Imaging.ImageFormat.Exif) Then Return "EXIF" End If If format.Equals(System.Drawing.Imaging.ImageFormat.Gif) Then Return "GIF" End If If format.Equals(System.Drawing.Imaging.ImageFormat.Icon) Then Return "Icon" End If If format.Equals(System.Drawing.Imaging.ImageFormat.Jpeg) Then Return "JPEG" End If If format.Equals(System.Drawing.Imaging.ImageFormat.MemoryBmp) Then Return "MemoryBMP" End If If format.Equals(System.Drawing.Imaging.ImageFormat.Png) Then Return "PNG" End If If format.Equals(System.Drawing.Imaging.ImageFormat.Tiff) Then Return "TIFF" End If If format.Equals(System.Drawing.Imaging.ImageFormat.Wmf) Then Return "WMF" End If Return "Unknown" End Function
If you want to actually send the Bitmap you derived from the Bytes, that's a whole other kettle of fish. When you stream the Bitmap you'll have to make sure you set the filter settings on it to high. Setting filter settings on all the various image types is quite a juggle. For gifs you'll even need to define octree quantizing at the very least.

Tuesday, August 26, 2008

How to output a query as an Aspose workbook download.

Being an 'on the job'er rather than a 'cetified'er, I'm often tasked with figuring things out by doing them. Now now, stop your wailing and gnashing of teeth. Unlike the usual fly by night programmer I spend a lot of time researching before I do. That means learning things about what I'm using that are extraneous to the job at hand. I do like to get thanked for this. The only way to do that is advertise, so every time I figure these things out I pass them around. It annoys my manager somewhat, but I'll be damned if I'm going to be invisible anymore. So anyway, here's the first thing I figured out that was actually hard. It was my intro to the Response object and .NET streams. How to output a query as an Aspose workbook verbatim as a download. For those of you that don't use Aspose, it is a suite of document editing dll's for ASP.NET. We use it for handling excel files and pdf's etc etc etc. Here's the code.
' Set up a query Dim ta As New SomeTableAdapter Dim results As SomeDataTable = ta.GetData() ' Set up the Response headers to describe the content being delivered Response.Clear() Response.AddHeader("content-disposition", "attachment;filename=" & filename) Response.Charset = "" 'If you want the option to open the Excel file without saving then comment out the line below 'Response.Cache.SetCacheability(HttpCacheability.NoCache); Response.ContentType = "application/vnd.xls" 'Register Aspose Liscence Dim cellslicense As Aspose.Cells.License = New Aspose.Cells.License() cellslicense.SetLicense("Aspose.Total.lic") Dim wb As New Aspose.Cells.Workbook ' Import Query results into the Workbook wb.Worksheets(0).Cells.ImportDataTable(results, True, "A1") ' Save workbook to a memory stream then output the memory stream to the HTTP Response object Dim stream As System.IO.MemoryStream stream = wb.SaveToStream() stream.WriteTo(Response.OutputStream) Response.End()

Tuesday, August 19, 2008

So now I'm a Pro.

Hi, I'm Chris. After a year of .NET learning 'on the job' I think it's time for some real certification. Namely, the MCTS in Visual Studio 2008. So I have begun this blog, to track my progress and get my tech buddies on my back when the dotOmeter isn't rising. I also want somewhere to post the snippets I've discovered over the last year. Those things that someone with training would know but a lagger like me has to figure out. Intellisense is great but it can't read my mind, only my keystrokes. So now, to push on. Wish me luck!