navigation
 Thursday, June 26, 2008
The company has been doing some Webinars, with materials distributed in a PDF format. I got tasked the other day with developing the materials for an upcoming session - but I didn't really have a way to create PDF output, just Microsoft Word. "Just", I say, until I googled some phrase like "Microsoft Word PDF".
posted on June 26, 2008  #    by Rick Miller  Comments [0]
 Thursday, August 16, 2007

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]
 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]
 Friday, July 13, 2007

Like many developers, I have both VS2003 and VS2005 installed on my machine. I had also placed a shortcut to the Visual Studio  command prompt in my programs menu. A couple of days ago, I had the opportunity to write my first Windows service application. I got everything set the way I wanted, and it was time to install. So, I opened up the VS command prompt, and typed in the magic chant "installutil servicename.exe". And fairly promptly got the error "Exception occurred while initializing the installation: System.BadImageFormatException: the format of the file 'servicename.exe' is invalid...".

If you're encountering the same error, make sure that you're using the correct installer. For me, it turned out that I was using the VS2003 installer. As soon as I switched to the installer from VS2005 (using the correct command prompt), everything worked just fine. It took me a while to track this down - perhaps this will save you some time.

posted on July 13, 2007  #    by Rick Miller  Comments [0]
 Friday, March 02, 2007

I was doing some work the other day, and found that I had the need to implement a template programatically (rather than creating it at design time). I love the internet search engines, and the (sometimes overwhelming) amount of information you can usually find on any subject, but there wasn't a whole lot on this subject. To make matters worse, the articles I did find hinted around the edges of what I was looking for, but none seemed to describe what I needed to do very closely - they all addressed the aspect of the problem that they were interested in solving. So, at the risk of introducing one more post that addresses the issue that I was trying to solve, perhaps this post will help you.

The basic idea behind using a template is that you can place controls into it that are bound to data at run-time. In a very general sense, that's what a grid control does - you provide a list of columns you're interested in, hook the grid up to a datasource, issue a databind command, and the data gets displayed where you want it to be. The next step is to take your own asp.net controls of choice and put them into the itemtemplate or edittemplate for a column. If you know what column you're binding to at run-time, the task is fairly straightforward, and we've all done it countless times.

The problem I was trying to solve was that I had a control (the radRotator control by telerik) that I needed to place an unknown number of copies (at design time) onto a web page, each of which would dind to a differently-named column in the data table it was attaching to. Yes, I probably could have created a bunch of different controls, and altered the "visible" property, but that wasn't how I wanted to skin this cat. Doing it the way I wanted to, however, required that I instantiate the controls dynamically at run time, and populate the control's template with the controls I wanted to use in the display, which would themselves bind to data columns that varied from instance to instance.

The first step is to create a helper class that creates your template. The template in this case is a very simple one, containing only a label control which will take as its text the name of a color, and change the background color of the label to that same color. The class looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class RotatorTemplate : ITemplate
{
string columnName;
public RotatorTemplate(string column)
{
columnName = column;
}

public void InstantiateIn(Control c)
{
Label lbl = new Label();
lbl.DataBinding += new EventHandler(OnDisplayDataBinding);
c.Controls.Add(lbl);
}

void OnDisplayDataBinding(object sender, EventArgs e)
{
Label lbl = (Label)sender;
RadRotatorFrame iContainer = (RadRotatorFrame)lbl.NamingContainer;
DataRowView drv = ((DataRowView) iContainer.DataItem);
lbl.Text = drv[columnName].ToString();
lbl.BackColor = System.Drawing.Color.FromName(lbl.Text);
}
}

What's going on here is that when the constructor is called, the name of the column that we're going to bind to is passed in to the constructor, and stored in the local variable "columnName" at line 6. The InstantiateIn method is called by the control wrapping the template each time it needs a new copy of the template contents. In our example, we create a new label, hook up the OnDisplayDataBinding event handler. Note that there's no magic in the name internal to the helper class - I named it that because it's descriptive of what the routine does. The only important piece here is that the "lbl.DataBinding" event be specified properly. You should also realize that you could create and add any number of controls of different types here, you're not limited to a single control. Finally, we add the control to the controls collection of whatever control requested the new template instance.

The most interesting part is the event handler. I don't see how this helper class can avoid having a lot of knowledge about the environment it's working in, because of the code on line 17 that typecasts the naming container to be of type RadRotatorFrame - though I suspect that the RotatorFrame descends from a more generic class that still has  a dataitem property; in any event, this solved my immediate problem, I'll look for the generic class later, and you should just be aware that you'd substitute your own naming container in the typecast.

The real breakthrough for me was the realization that the DataItem was available to be cast into a dataRowView, which I could then extract the desired data from, and set the text of the control. Because I was writing this for a very specific application, I knew I'd always be getting a color name, so I also took the opportunity to set the label's background color. As a general rule, you'd want to do better data checking before making the assignment on line 20, though it turns out that an attempt to convert a non-color into a color fails gracefully, and returns white.. Also, if you're adding more that one control in the InstantiateIn event, you'll want to name them when you create them, so that you can find them in the databinding event.

That's the helper class. After that, the rest of the process is trivial. As an example, say you wanted to hook up a table that contains proverbs. You could do it like this (and here, I'm populating the template of a radRotator control, but the containing control could be anything that would take a template):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected void Page_Load(object sender, EventArgs e)
{
DataTable proverbsTable = new DataTable();
proverbsTable.Columns.Add("proverb");

proverbsTable.Rows.Add("A stitch in time saves nine");
proverbsTable.Rows.Add("A rolling stone gathers no moss");
proverbsTable.Rows.Add("The early bird catches the worm");
proverbsTable.Rows.Add("A penny saved is virtually worthless");

RadRotator rotator1 = new RadRotator();
rotator1.ID = "rotator1";
rotator1.DataSource = proverbsTable;

rotator1.FrameTemplate = new RotatorTemplate("proverb");
rotator1.TransitionType = RadRotator.RotatorTransitionType.Slideshow;
rotator1.TransitionEffect = RadRotator.RotatorTransitionEffect.GradientWipe;
rotator1.Width = 500;
rotator1.Height = 50;
rotator1.AutoPostBack = true;
PlaceHolder1.Controls.Add(rotator1);

rotator1.DataBind();
}

The only really interesting thing here is the creation of the RotatorTemplate on line 12 - here's where you pass the variable information (in this case, the name of the data column you're going to use to populate the label text).

That's it for ITemplate Instantiation. I hope it saves you some time and sheds a little light on your own problem.

posted on March 2, 2007  #    by Rick Miller  Comments [0]
 Tuesday, January 09, 2007

I've written and revised a reasonable number of DotNetNuke modules, as well as Falafel's course material for DNN, and I think I can probably create binary Private Assemblies in my sleep.

I was a little unnerved, then, when I was getting something ready for an important customer this morning, and discovered some very weird and different behavior in my test environment than in the development environment. Specifically, I had written a module which collected some extended profile information from Joe User when he was logged in, which I didn't want to collect for the host user or for members of the Administrator group. So I'd upload the new module (logged in as host), then log out and go register a new user name, going immediately to the new module page, where the first thing I needed to do was enter the profile information. Dutifully, I would enter the information and submit it. And nothing would get updated. Yet this worked perfectly in my development environment.

Did I say it didn't get updated? That's not exactly true. I was feeling pretty confused, so when Lino the Wise walkd past my office, I grabbed him and said "Boss, I need some help". He listened patiently as I explained the problem, then turned to my keayboard to demonstrate.

And it worked just like it should. Lino laughed, and said it was because he was standing there that the problem got scared and went away. Then so did Lino. And the problem came right back. After a while longer, I got another coworker to come in. And again I explained the problem. And again, the program performed correctly. These two times, out of perhaps a hundred attempts.

I tried lots of things. Rebooting, of course, and iisreset. Clearing out the temporary files. Creating a fresh virtual site with the same name as the old one, then a new virtual site with a different name. Nothing worked.

I finally noticed (while doing some further testing, with a second user name) that the information from a previously logged in user was being displayed. I had been in the habit of logging in as host, uploading the new module, then logging out and immediately registering a new user. I resorted to brute force: I displayed the current user id on the form. When I'd enter and submit the new user's information, I checked and found that DNN's this.ModuleId function said I was userId=1 (host). Since I was disabling profile update through this particular page for host and administrators, my updates were getting thrown away. Apparently the couple of times it did update were when I was explaining the issue to co-workers - thus taking longer, letting the cache time out (??) and by the time I loaded the offending page, everything behaved properly.

I loaded DNN 4.4.0, and the problem went away. I had been developing and testing in DNN 4.3.5, because this was a custom job and that's what the customer was using. And the heck of it is, they (or actually, their users) will probably not ever encounter the problem because of the way the module will be used, one user at a time from computers all around the country. This particular bug hatched to frustrate only the developer. Too bad I didn't find that out more quickly...

posted on January 9, 2007  #    by Rick Miller  Comments [0]
 Wednesday, December 27, 2006
One way you might want to customize your Sharepoint home page is to add your company logo to an "Image" web part displayed on the page - in fact, the team site template comes with a "site image" preconfigured on the page. You can change the image presented here by putting the page into edit mode, then edit the "Site Logo" web part, and select the logo. It can be any logo, accessible from anywhere, of any recognized graphic type. Very flexible.
 
And then they turn right around and bite you in the butt, because if you have a logo on your local machine, and you want to use it as the site logo, how the heck do you load it up onto the server? I poked around for a while looking for something that would let me transfer a file up to the server and specify where I wanted it to end up, but had to finally connect to the server by mapping a network drive, and copy the file into the default file location. And oh yeah, you'd better know where the file goes and/or where you placed it, because when you click on the button with the ellipses (...), don't think you're going to find a file open dialog - nope. All you get is a bigger text box into which you can type the logo's path.
 
Our company logo is a JPG file about 450 kb in size, and in its natural state, the image is something like 2109 x 1398 pixels. Too big to fit onto a normal screen, much less squeeze down into a reasonable-sized corner of the screen like you might want for a company logo on a home page. So - when you put in the path to the JPG, there are two possibilities for resizing the logo, both of which sound promising. The options presented for the question "Should the WebPart have a fixed height" are "Yes" (with an option for entering the size in pixels, picas, points, inches, etc), and the second is "No. Adjust height to fit zone". There are corresponding entries for the width. If you pick yes, and enter a reasonable size in pixels (120 x 80), what you get is a logo-sized area with scrollbars which permit you to scroll across the entire 2109 x 1398 pixels. Not very useful. OK, let's choose "No", and let the image be adjusted to fit the zone. Or not - all this choice does is adjust the zone size to fit the image. In other words, the "Site Logo" web part becomes huge, so instead of getting a shrunken logo displayed in a reasonable-sized area, the area baloons up, the logo is displayed full size, and you can now scroll the entire web page using the sdcroll bars on the browser if you want to see the whole thing.
 
Aargh. All I found to do was to edit and re-save the JPG as a smaller size, then use that logo.
posted on December 27, 2006  #    by Rick Miller  Comments [1]