Tuesday, July 11, 2006

Google Site Maps and Http Handlers Part 2

In my last post I discussed using the method for the section of the web.config file. The remove element comes in handy if you’ve added an HttpHandler to the root of your Web site, but don’t want that handler to be loaded in sites below the root Web site.

Today I found a better way to control inheritance of the Http Handlers. The <add> element for Http Handlers has an attribute, validate, which tells ASP.NET whether it should load the class immediately or wait until a request comes. By default the value is set to true. Setting validate to false prevents the class from being loaded and thus eliminates the error.

In the case of the Google Site Maps handler, we had the following where validate was defaulting to true

<httpHandlers> <add verb="*" path="googlesitemap.axd" type="Newtonsoft.GoogleSitemap.GoogleSitemapHandler, Newtonsoft.GoogleSitemap"/> </httpHandlers>

We can change the add element to this to prevent ASP.NET from loading the googlesitemap.dll until we browse to googlesitemap.axd.

<httpHandlers> <add verb="*" path="googlesitemap.axd" type="Newtonsoft.GoogleSitemap.GoogleSitemapHandler, Newtonsoft.GoogleSitemap" validate="false”/> </httpHandlers>

Of course within your subsites, ASP.NET will generate an error if you try to browse to http://yourdomain.com/subsite/googlesitemap.axd. Ideally I wish there were an attribute like inheritance=false but until then this gets the job done.

Friday, July 07, 2006

Google Site Maps and Http Handlers

As you may have read about by now, Google allows you to publish an XML site map that helps their Web crawlers find all the pages in your site. Newtonsoft was nice enough to publish an assembly that reads your ASP.NET sitemap and produces a Google sitemap for you. You only need to add one line of code to your web.config, which sets up an HttpHandler. Then you can point Google to the path specified in the handler, and you’re done. This allows you to maintain just the ASP.NET sitemap yet still take advantage of Google’s new service.

Setup of the Newtonsoft GoogleSitemap is easy, and is well documented on their site.

One gotcha to watch out for, though, occurs if you have Web applications in folders below the application you add this HttpHandler to. For instance, if you add the HttpHandler to your root web.config, as follows, it will be inherited by all the applications beyond the root.

<httpHandlers> <add verb="*" path="googlesitemap.axd" type="Newtonsoft.GoogleSitemap.GoogleSitemapHandler, Newtonsoft.GoogleSitemap"/> </httpHandlers>

So here you’re saying you can go to http://www.yourdomain.com/googlesitemap.axd to view your Google-ready Sitemap. But what if you have another application at http://www.yourdomain.com/subfolder/myapp.aspx? In the subsite the HttpHandler will be inherited, which means that http://www.yourdomain.com/subfolder/googlesitemap.axd should work too.

The problem is that the DLL you download from Newtonsoft is located in the root’s bin folder. When you visit http://www.yourdomain.com/googlesitemap.axd the DLL is found in the root’s bin folder. However, any applications below the root go looking for that assembly within their bin folder. When it can’t be found you’re stuck with the Could not load file or assembly 'Newtonsoft.GoogleSitemap' or one of its dependencies error.

The simplest way to fix this issue is to remove the httpHandler in the subsite. That can be accomplished by adding the following remove element in your subsite’s web.config, as shown below:

<httpHandlers> <remove verb="*" path="googlesitemap.axd" /> </httpHandlers>

Thursday, June 15, 2006

Changing output created by the Crystal Report Viewer

I often use Crystal Reports to report on Lotus Notes data. (I connect using NotesSQL, although I've tried a number of other methods that weren't as effective or reliable.) Crystal Reports is a very good reporting tool and is integrated nicely into Visual Studio.

Recently I setup a Web form with a CrystalReportSource and CrystalReportViewer control on it, and set the appropriate properties to point to my report. Opening the page in the browser launches the report and it is displayed using DHTML. This all works well with the exception of a bug that appears when it renders hyperlinks in the browser.

I have a field on the report that has the hyperlink formula set to a column in my datasource. My datasource is a Notes database view, and one of the columns in that view is called NotesURL. The values in the NotesURL column have the form notes://server/database/view/document. When a user clicks on a Notes URL from within a browser, their Notes client is launched and the specified document is opened. This works much like a doclink does within Notes.

When the Crystal Web viewer control renders the field, it normally injects the correct HTML code to display the hyperlink. This works fine when the URL begins with "http://". However, the control ignores hyperlinks that begin with "notes://" or even "javascript:". As a result, only the field value appears on the report, without a hyperlink.

Thanks to the power of OOP it really wasn't that hard to fix.

First, I hardcoded the hyperlink formula to start with "http://", so the NotesURLs would appear as http://notes://server/database/view/document. Then I added the code below to modify the field's hyperlink property just before the field is rendered on the page.

This worked great and the hyperlinks appear as they should. Unfortunately there is a trade off because exporting to excel or word doesn't cause the BeforeRenderObject event to fire. As a result, the URLs appear in the http://notes:// format within Excel.

Private Sub CrystalReportViewer1_BeforeRenderObject(ByVal source As Object, ByVal e As _ CrystalDecisions.Web.HtmlReportRender.BeforeRenderObjectEvent) Handles CrystalReportViewer1.BeforeRenderObject

'Crystal Reports XI release 2 (and all other releases I've tried) does not render hyperlinks correctly

'if they begin with notes:// or javascript:

'

'To work around this issue, I add the http:// in the hyperlink formula on the field object from

'within Crystal Reports designer. Then just before the report viewer renders the object I remove

'the http:// from the beginning of the URL.

'Find FieldObjects

If TypeOf e.Object Is CrystalDecisions.CrystalReports.ViewerObjectModel.FieldObjectInstance Then

Dim myField As CrystalDecisions.CrystalReports.ViewerObjectModel.FieldObjectInstance = CType(e.Object, CrystalDecisions.CrystalReports.ViewerObjectModel.FieldObjectInstance)

If myField.HasHyperLink Then

If myField.HyperLink.Contains("notes://") Or myField.HyperLink.Contains("javascript:") Then

myField.HyperLink = myField.HyperLink.Substring(7) 'grab all after http://

End If

End If

End If

End Sub

Error executing agent

A strange error started to appear in my Domino server logs, stating an agent couldn’t run:

AMgr: Error executing agent 'SendReminders' in ‘hr\mydb.nsf': You are not authorized to perform that operation

Obviously this is an access rights issue, but it wasn’t obvious at first where the access issue existed. My agent Runs on Behalf of another user, mainly so that emails generated from the agent will appear to have come from that user.

I confirmed that the signer of the agent (me) and the other id (let’s call him user B) used to run that agent both had appropriate access in the security settings for the server. It was bugging me that the issue occurred only on one database and I started dreaming up conspiracy-theory-like explanations like the database is corrupted or the server document is corrupted.

Fortunately I know from past experiences it’s best to ignore those wild explanations, at least at first, and look for a simpler explanation (Occam's Razor). It then occurred to me to check the databases ACL and make sure that user B had access. User B wasn’t listed on the ACL. No surprise, as soon as I gave User B access the agent ran fine.

Wednesday, June 14, 2006

Domino Server Upgrades

I ran into an issue today during an incremental upgrade where I needed to rename the nxmlcommon.dll file to get the upgrade to work. After the upgrade, I needed to rename it back to .dll so Domino would start. When it started, I got an error message:

nSERVER.EXE - Entry Point Not Found

The procedure entry point ?g_statusmgs@DxlStr@@2QBGB could not be located in the dynamic link library nxmlcommon.dll

I later realized that by renaming the file I must’ve prevented that file from being upgraded. I checked another Domino server at the same release and its file was newer. I copied the newer file over and restarted to make sure the message didn’t appear again, and then all was good.

Stuff like this happens a lot during upgrades.

A little background on my Domino Server Upgrade experiences:

Whenever I upgrade a Domino server, I prepare myself for the worst. Even a simple point release that should take 5 minutes to run can go wrong. Not so wrong that I’ve had to rebuild a server, but wrong enough to require some strange workarounds and take 10 times longer than expected.

Everytime I run an incremental installer on the Domino server, I run into another set of issues with the files currently on the server. The incremental installer loads and then fails to confirm the current release. I then need to find the UPGRADE.LOG file, read it to find what files failed a checksum test, rename or move those files, then continue with the upgrade. Usually these files live in the Domino\Data\Domino\HTML\downloads\filesets folder.

Other times I run into issues with a few key dlls. I swear the server has completely shutdown, but somehow the dlls are still in use. They include js32.dll, nxmlcommon.dll, and a few others. I usually am able to rename them to .OLD and continue the install.

Finally I’m able to get through an incremental install. I then start the next incremental install and start the process all over again.

I wonder does this problem happen to everyone?

Saturday, June 10, 2006

A simple walkthrough of ELMAH

A few well known developers / authors in the .NET community, Atif Aziz and Scott Mitchell, have released a useful error logging utility demonstrating the power of HTTP Modules and HTTP Handlers in ASP.NET. Here's my attempt to oversimplify the process of adding their utility to an existing ASP.NET Web site.
  1. Download the latest installation from the ELMAH gotdotnet workspace
  2. After installing the MSI, add a reference to the GotDotNet.Elmah.dll usually located in the GotDotNet\ELMAH\1.0\bin folder under Program Files. Or, if you had the assembly installed into the GAC then skip this step.
  3. Download the Error Mail Module Extension also from the ELMAH gotdotnet workspace. Add a reference to this the ErrorMailModuleExtension.dll as well.
  4. Edit the web.config and add the following 4 sections: First section goes after the configuration tag at the top <configSections> <!-- Allows for a new section group to the Web.config --> <sectionGroup name="gotdotnet.elmah"> <!-- Indicates that inside the section group there will be anerrorLog section --> <section name="errorLog" type="System.Configuration.SingleTagSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <section name="errorMail" type="System.Configuration.SingleTagSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> </sectionGroup> </configSections> Second section goes just before system.web section

    <gotdotnet.elmah> <errorMail from=yoursender@yourdomain.com to=youruser@yourdomain.com smtpServer="yoursmtpserver.yourdomain.com"/> <errorLog type="GotDotNet.Elmah.MemoryErrorLog, GotDotNet.Elmah, Version=1.0.5527.0, Culture=neutral, PublicKeyToken=978d5e1bd64b33e5" /> </gotdotnet.elmah> Third and fourth sections go within the system.web section <httpHandlers> <add verb="POST,GET,HEAD" path="elmah/default.aspx" type="GotDotNet.Elmah.ErrorLogPageFactory, GotDotNet.Elmah, Version=1.0.5527.0,Culture=neutral, PublicKeyToken=978d5e1bd64b33e5"/> </httpHandlers>

    <!-- Adds the ErrorLogModule HTTP Module to the HTTP pipeline. --> <httpModules> <add name="ErrorLog" type="GotDotNet.Elmah.ErrorLogModule, GotDotNet.Elmah, Version=1.0.5527.0, Culture=neutral, PublicKeyToken=978d5e1bd64b33e5"/> <add name="ErrorMail" type="GotDotNet.Elmah.ErrorMailModuleExtension, ErrorMailModuleExtension"/> </httpModules>

  5. Make sure to edit the gotdotnet.elmah section and enter in your own details within the errorMail tag.

  6. Test the system by going to /elmah/default.aspx off the root of your application (http://yourserver/yourapproot/elmah/default.aspx) If that comes up, you can create a test error by adding a /test to the end of the URL, as in: http://yourserver/yourapproot/elmah/default.aspx/test

  7. If you run into any trouble, check the gotdotnet workspace. There’s a great technical doc that goes into this in much greater detail.