navigation
 Thursday, August 30, 2007
I was debugging some ASP.Net 2.0 code that renders a PDF to the response buffer of a page request, using code like this (byte[] bytes is the PDF file, which was rendered using Microsoft Reporting Services Web Service interface) :
posted on August 30, 2007  #    by John Waters  Comments [0]
 Wednesday, August 29, 2007

Falafel software is proud to announce the successful launch of version 2.0 of its custom built ERP system Velocity. Velocity handles billions of dollars of business for America’s leading organic produce company. Version 2.0 provided a rewrite of key parts of the user interface, using Web 2.0 technology like AJAX and Web Services to provide the data entry performance of a desktop application combined with the ease of deployment and central manageability of a web application. The system handles hundreds of users, hundreds of thousands of sales orders and invoices, and provides 24/7 availability and high performance using a scale-out Microsoft architecture.

posted on August 29, 2007  #    by Lino Tadros  Comments [0]
 Monday, August 27, 2007
If you need to share resources across multiple pages you can use global resources as opposed to the local resources shown in the previous blog. Global resources are implemented using explicit binding, i.e. server tags like this one:
posted on August 27, 2007  #    by Noel Rice  Comments [0]
Discover some strange but sadly true limitations of the Virtual PC support for ISB devices...
posted on August 27, 2007  #    by John Waters  Comments [1]
 Saturday, August 25, 2007

I had the privilege of attending a two-day training for Expression Design and Expression Blend this week in San Jose, CA.  The trainer, Joshua, was knowledgeable and fun to learn from.  We enjoyed his training and personality.

Unfortunately, the product is just NOT ready for prime time.  I was shocked! Really shocked! Poor guy had to apologize almost every three minutes for two days on how the product works and why he has to do things in a very awkward way to get it to behave.
First the IDE is not intuitive at all.  I thought, hey I am a developer, maybe this is just not for me, but all the designers in the room were shaking their heads as well.

There was total confusion between canvas and layers in Blend, disturbing implementation for differentiating between selection and scope that could easily waste hours of work. There lacked in the IDE a clear path to redirect the effort to the correct path.
Look at the Trigger clickable button in the IDE in the picture below.

image

I thought you click that to access the Trigger page, nope, if you click on that Trigger button, you will delete the only trigger you have on your WPF form.  Who designed this IDE?
Yes I can see now the + sign and the minus sign but that is just not the way it is done guys.
At some point the IDE will be so cluttered with docked windows that it rendered the experience of working in the IDE totally useless.
You can save your designs as .Design files in Expression Design and export the XAML to Blend. That is a one way street; blend can not send the xaml back to design.  Expression Design does not know anything about .XAML files either and can not open them.  Just make them!
The scrollbar in the properties window disappears often and the user can no longer get to the properties off the screen space currently visible. Eventually, resizing the docked windows brings back the scrollbar.
Modifying XAML code in the editor sometimes does not reflect the change in the designer unless you close down the project and reopen.
Ok, that is enough, you get the picture.  Great idea, powerful product, long way to go to get it to be productive and useful.

posted on August 25, 2007  #    by Lino Tadros  Comments [0]
 Monday, August 20, 2007

ASP.NET 2.0 has a rich set of localization features built-in. Early in the ASP.NET 1x lifecycle we "rolled our own" using an HTTP handler that reflected for a [localize] tag, looked up the control name in a resource file and assigned the localized value.  I prefer out-of-the-box solutions though when available and the MS resource provider model approach provides enough flexibility to be worthwhile.

ASP.NET 2.0 Localization adds two new resource flavors: local and global resources.  Local resources are used for controls on a specific page. The resources are located in the ASP.NET App_LocalResources folder with the same name as the page you're localizing.  So, if you're translating default.aspx then your App_LocalResources might also contain default.fr-FR.resx with a French translation. Global resources are contained in App_GlobalResource and can be used anywhere in the application.

You bind resources to your controls using explicit or implicit expressions. Explicit expressions use an inline server syntax similar to the data binding syntax you're already familiar with.  Implicit expressions syntax use a "<meta>" tag in the control you're localizing to identify the resource.

Local Resources, Implicit Expressions

It all makes more sense in practice so here's a basic walk-though using local resources with implicit expressions:

  • Create an ASP.NET web application.
  • Add a TextBox control to the default form.
  • While you're on the page to localize (default.aspx in this case), select Tools |Generate local resources. This step has a number of effects. 
    • An App_LocalResources folder appears populated with "Default.aspx.resx". 
    • In the Source view, the ASP.NET markup page tag will have a Culture="auto" attribute added. The value of "auto" lets the application react to the browser language settings.
    • In the ASP.NET HTML markup for the control will have a "meta:resourcekey" attribute added:

      <asp:TextBoxID="TextBox1" runat="server" meta:resourcekey="TextBox1Resource1"></asp:TextBox>

      This identifies a single element in the ASP.NET HTML markup.  If there are other nested elements, each needs to be marked with a "meta:resourcekey" attribute before you can implicitly bind to them (as when you have BoundField elements within a GridView for example).  The general rule is, if the element is qualified with a namespace, it needs a "meta:resourcekey" attribute.

    • The properties are marked with an icon that show they are implicitly bound.

image

  • Double-click default.aspx.resx to edit the resource. This will contain resources for the page as a whole and for any controls on the page. The naming convention for resources is <resourcekey>:property name (you typically don't have to know that, but if localization support for a component is incomplete you can still add the "meta:resourcekey" tags by hand). Add text for the page title and TextBox Text property.

Resource for Default.aspx

  • Copy default.aspx.resx to default.aspx.fr-FR.resx.
  • Edit the values in the resource file to French translations.  You can use a tool like Google Translate to get a fair approximation of what the translation should look like. I wouldn't use it as a production tool though unless you want chuckles from the target audience (try translating to and from the target language a few times and you'll see what I mean). 

image

  • Run the application.  Assuming Internet Explorer as your browser, go to Tools | Options | Languages.  Select the "fr-FR" culture code, then refresh the page and Voila! (or "Blick dort!" or "sguardo là!".  Hmm, sure hope I'm not insulting anyone).

image

Slightly More Complex Scenarios

The same pattern works for more complex objects, such as GridView.  Notice there is a "meta:resourcekey" for the grid and also for each of the BoundField objects.

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataSourceID="SqlDataSource1" meta:resourcekey="GridView1Resource1"> <Columns> <asp:BoundField DataField="ProductName" HeaderText="ProductName" meta:resourcekey="BoundFieldResource1" SortExpression="ProductName" /> <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" meta:resourcekey="BoundFieldResource2" SortExpression="UnitsInStock" /> </Columns> </asp:GridView>

Again, you can bind to any resourcekey/property name combination:

image

And because the page Culture is set to "Auto", you can change the browser language settings and the translated text is displayed automatically:

image

 

The localization facilities built-in to ASP.NET 2.0 should save you some time if they fit your requirements.  If you need a heavier weight solution that uses a database (or other data store) instead of XML to store resources, check out this article by Jeff Modzel "ASP.NET 2.0 Custom SQL ResourceProvider".

posted on August 20, 2007  #    by Noel Rice  Comments [0]
 Thursday, August 16, 2007

I have spent the last few days trying to install Visual Studio 2008 a.k.a. Orcas Beta 2. The download page is here.

The basic idea is that you download a virtual machine that has both the OS (Windows Server 2003) and Orcas preloaded. First, you download a base file called VSCTPBase.exe, which you unpack to get a virtual hard drive. Then, you download 7 RAR files. The first one is self extracting and extracts itself and the rest of them, and then builds a differencing disk which is added to the initial virtual hard drive to create the final virtual drive. At least, that was the theory!

As the whole setup uses a lot of space, and I am almost out of space (as usual) on my laptop, I borrowed a 300 Gb external disk from work to put the VPC on. I downloaded the files mentioned above onto that disk. I then unpacked the base image, and started unpacking the 7 RARS. Well - somewhere in the middle of the third one, I get an error message saying

"Write error in the file OrcasBeta2_VSTS.vhd. Probably the disk is full."

Not! I had about 250 Gb free on the disk. OrcasBeta2_VSTS.vhd is the differencing disk that was being merged from the RARS. So, I scratched my head, googled, couldn't find anyone else with the same problem, tried downloading the file again, rebooted, tried again, sacrificed a goat. No luck.

So I tried searching for the message above as "Write error in the file" and "Probably the disk is full." Now I got lots of hits, this seems to happen a lot to people. Finally, I stumbled across a post saying that there is a file size limit on FAT32 volumes, somewhere around 4Gb.

FAT32? I had forgotten about that evil stuff, along with win.ini, config.sys, thunking, himem, edlin, floppy disks and gorilla.bas! And sure enough, the 300Gb external drive was formatted as FAT32. Duh!

So, a quick command brought my external I: drive out of the bronze age:

convert I:/FS:NTFS 

And now the extraction worked.

Can you believe that? Poor Microsoft trying to get it's developers to beta test their latest and greatest development tools, and you run into FAT32! Talk about a blast from the past. Tonight I will dream nightmares about 640K memory limits and 16 bit operating systems... and the turbo button.

posted on August 16, 2007  #    by John Waters  Comments [0]

One of the problems we've encountered while using Selenium to execute automated tests feels like it's related to timing, and that's when we use the "IsElementPresent" method followed immediately by a command to get the contents in that element. The results are very intermittent - sometimes, even when we KNOW that there should be something there, we're unable to find it, almost like the element started getting rendered, but didn't get populated yet.

The problem, then, is that when you expect that there's going to be data there, it would be nice to make sure that there actually is before you read it, try to convert it or match it, and throw an error. That's where WaitForCondition comes in. Basically, this is a javascript call that returns a true/false value. THe references to it in the documentation are tantalizing, but not very specific. After some research, I found how to make it work for me. Here's the relevant code:

1
2
3
4
5
6
7
    public class Utilities
{
static public void WaitForCondition(string script, string timeout)
{
selenium.WaitForCondition(script, timeout);
}
}
And then in the test code:

1
2
const string ELEMENT_HAS_LENGTH = "var value = selenium.getText('{0}'); value.length > 0;";
Utilities.WaitForCondition(String.Format(ELEMENT_HAS_LENGTH, "ctl00_cphContents_ode_ddlPrices"), "10000");
 
The way this works is that the id of the control we want to be checked is passed in to the constant string containing the script, and formatted with the control's id. Next, the little snippet of javascript gets executed. The control is located on the page (if it's not there, it gets assigned a null), and then gets evaluated to see if the length of the text associated with the control is longer than zero. If it's not, the result is false, and (I assume) the selenium engine waits for some predetermined length of time before the script snippet executes again. This continues until either the condition evaluates as true, or the timeout (expressed in milliseconds) expires.

Pretty easy, once you understand the necessary format of the javascript snippet.

posted on August 16, 2007  #    by Rick Miller  Comments [1]
 Sunday, August 12, 2007

I was configuring Custom Errors in the web.config of my ASP.Net 2.0 application, and set it up as follows:

<customErrors mode="remoteOnly" defaultRedirect="~/Error.aspx">
  <error statusCode="403" redirect="~/AccessDenied.aspx" />
  <error statusCode="404" redirect="~/Error404.aspx" />
</customErrors>

I had found the syntax on MSDN. To my surprise, the applicationm wouldn't start, it told me there was something wrong with the value of the mode attribute.

So I went back to MSDN and noticed I had looked at the .NET Framework 1.1 documentation. Clicking on the corresponding 2.0 documentation, I found it had changed to initial Caps:

<customErrors mode="RemoteOnly" defaultRedirect="~/Error.aspx">

Duh! I guess my chances of getting this right the first time were remote only...

posted on August 12, 2007  #    by John Waters  Comments [0]
 Thursday, August 09, 2007

I've been looking at the "Scheduler" entry on the DNN Host menu for quite a while now, thinking about looking into it further and wondering how I might be able to take advantage of the promise that's there.

First, the bad news: the scheduler only checks if there's a scheduled activity that needs to be run when the DNN server gets hit. So, for instance, if you have a task that you want to run every ten minutes, and nobody hits your website for three hours in the middle of the night, the task will not run until the next time there's a web request to your site. There are some DNN ways to work around this - go to http://www.dotnetnuke.com/, and search for KeepAlive, which is a module that will automatically access your website just prior to Application_End.

Now the good news: writing a scheduled service is as easy as can be, once you know the tricks. Here's the code for a complete Hello World implementation, which you should add as a new class to your App_Code folder for the project you're working on.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using DotNetNuke.Services.Scheduling;

namespace Falafel.WebPlanner
{
public class TestClass1 : SchedulerClient
{
public TestClass1(ScheduleHistoryItem objScheduleHistoryItem) : base()
{
this.ScheduleHistoryItem = objScheduleHistoryItem;
}

public override void DoWork()
{
try
{
this.ScheduleHistoryItem.Succeeded = true;
this.ScheduleHistoryItem.AddLogNote("Notification processing completed at " +
                  DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString());
}
catch (Exception exc)
{
this.ScheduleHistoryItem.Succeeded = false;
this.ScheduleHistoryItem.AddLogNote("Scheduled service failed");
this.Errored(ref exc);
}
}
}
}

Let's look at a couple of the things that are going on here. First, notice that the constructor includes a reference to a "ScheduleHistoryItem. This is used by the DNN system to pass success/failure information, as well as any messages that need to be logged. The other feature is the DoWork() method. This is the method that takes care of business for you, the method that is called every time the scheduler fires the event. Using the DoWork method as a springboard, you can accomplish anything you like programmatically.

After compiling this code into your application, the last thing you need to do is to schedule the event. Start your site from within the IDE, and log in as host. Go to the Host -> Schedule menu entry, and select "Add Item To Schedule".  The entry in the first text box is a little odd, asking you for the full class name and assembly. In the case of the example above, I enter Falafel.WebPlanner.TestClass1, App_SubCode_Falafel_WebPlanner. Set the period for the schedule to call for the time period you'd like (for the purposes of this demonstration, I chose every 15 seconds), and also (for demo purposes) select to retain schedule history for 10 iterations. Click the "Schedule Enabled" checkbox, and update.

Just to prove the point, if there's no activity on your system (and there probably should not be - you're doing this on a development system, aren't you?), leave the site alone for a minute or two. After waiting for this respectful length of time, go back to the main Host -> Schedule page, locate your entry, and click on history. You shouldn't see any more than one entry. If you click around on your website for a while, then inspect the history again, yo'll see that the scheduled events have fired on a (fairly) regular basis. Leave the site alone again for a while, and you'll notice that the events also have not fired.

In summary, the DNN scheduler is a pretty simple yet very useful tool to run scheduled tasks if either a) your site is an active one, or b) you use some method of keeping your site alive.

posted on August 9, 2007  #    by Rick Miller  Comments [0]