Adding AJAX to an existing project that includes Routing

I was working on a project and just on a whim decided to try out some of .NET’s AJAX controls. I chose a random page, added a ScriptManager, tossed in a TextBox and a CalendarExtender.

F5. Page shows up. Javascript error:

Line: 121
Char: 1
Error: 'Sys' is undefined
Code: 0
Url: http://localhost:49329/admin.aspx

Hmm.

Try a new project. Add a ScriptManager, tossed in a TextBox and a CalendarExtender. Works fine. Huh?

I go through the Web.Config files of the two. Nothings changed except the version number on everything:

non working: Version=3.5.0.0
working: Version=1.0.61025.0

Ah ha! That must be it. I reset the working project as a 3.5 app and run it. I was so sure it was going to fail. Nope. Worked fine.

Must be something else. *grumble*

I start taking pieces out of my existing app and when I came to the Global.asax I took out the RouteTables and AJAX comes to life! FINALLY!

Here is what I had:

Global.asax

		protected void Application_Start(object sender, EventArgs e)
		{
			// Code that runs on application startup
			RegisterRoutes(RouteTable.Routes);
		}
		private static void RegisterRoutes(ICollection<RouteBase> Routes)
		{
			RouteTable.Routes.Add(
				new Route("{Parameter}/{pageID}", new RouteHandler()));
			RouteTable.Routes.Add(
				new Route("{Parameter}", new RouteHandler()));
		}

With my routing handler file showing this little gem:

RouteHandler.cs

				string virtualPath = string.Format("~/{0}.aspx", pageName);
				return (Page)BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page));

So basically it was swiping the file extensions from my httpHandlers and replacing them with .aspx meaning they were not found.

Web.Config

		<httpHandlers>
			<remove verb="*" path="*.asmx"/>
			<add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
			<add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
			<add verb="GET,HEAD" path="ScriptResource.axd" validate="false" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
		</httpHandlers>

To handle the situation I added the following to Global.asax:

Global.asax

		protected void Application_Start(object sender, EventArgs e)
		{
			// Code that runs on application startup
			RegisterRoutes(RouteTable.Routes);
		}
		private static void RegisterRoutes(ICollection<RouteBase> Routes)
		{
			RouteTable.Routes.Add(new System.Web.Routing.Route("{resource}.axd/{*pathInfo}",
					   new System.Web.Routing.StopRoutingHandler()));
			RouteTable.Routes.Add(new System.Web.Routing.Route("{service}.asmx/{*path}",
					   new System.Web.Routing.StopRoutingHandler()));
			RouteTable.Routes.Add(
				new Route("{Parameter}/{pageID}", new RouteHandler()));
			RouteTable.Routes.Add(
				new Route("{Parameter}", new RouteHandler()));
		}

I hope this helps. 🙂

Working on Bible Verse Tags for ASP.NET (C#)

To do:

  • build Regex to detect bible verses in tags
  • put in named back-references
  • find a way to choose different tables with LINQ
  • make it work for an entire chapter
  • add error messages
  • move it to a class file instead of in the page
  • run it from the filterText method if IndexOf(“[bible]”) !=-1
  • fix toVerse in case it goes out of range
  • fix misspelled or abbreviated bookName with value from database
  • change to Compiled Queries
  • add footnotes
  • pretty up error messages
  • clean up final code
public class bibleParts
{
	public static Func<bibleDataContext, string, IQueryable<bibleParts>>
		theVersion = CompiledQuery.Compile((bibleDataContext context, string bibleVersion) =>
			(from v in context.bibles_infos
			 where v.bibleName == bibleVersion
			 select new bibleParts
				 {
					 version = v.bibleID,
					 language = v.language,
					 copyright = v.copyright
				 }));

	public static Func<bibleDataContext, int, string, IQueryable<bibleParts>>
		theBook = CompiledQuery.Compile((bibleDataContext context, int bookLang, string book) =>
			(from b in context.book_names
			 where (b.book == book || b.abbreviation == book || b.alt == book) && b.language == bookLang
			 select new bibleParts
			 {
				 bookID = b.book_id,
				 book = b.book
			 }));
	public static Func<bibleDataContext, int, int, int, IQueryable<bibleParts>>
		theVerses = CompiledQuery.Compile((bibleDataContext context, int version, int book, int chapter) =>
			(from v in context.verses
			 where v.versionID == version && v.bookID == book && v.chapter == chapter
			 select new bibleParts
			 {
				 verseNum = v.verse1,
				 thisVerse = v.text
			 }));

	public int version {get;set;}
	public int language {get;set;}
	public string copyright {get;set;}
	public int bookID {get;set;}
	public string book {get;set;}
	public int verseNum {get;set;}
	public string thisVerse {get;set;}
}
</pre></pre>
<pre><pre>private string verseMod(string text)
{
	string textVal = text;
	string errorValue = "";
	string verseNumBegin = "<sup>";
	string verseNumEnd = "</sup>";
	string footerBegin = "<em> - ";
	string footerEnd = "</em>";
	string bibleVersion = "kjv";  //default version if none is specified
	string pat = @"\[bible[=]?(?<version>[a-zäëïöüæø]*)](?<entire>(?<book>([0-9][\s]?)?[a-zäëïöüæø]*[\s]{1}([a-zäëïöüæø]*[\s]?[a-zäëïöüæø]*[\s]{1})?)(?<chapter>[0-9]{1,3})(:{1}(?<verse>[0-9]{1,3})(-{1}(?<toVerse>[0-9]{1,3}))?)?)\[/bible]";

	Regex r = new Regex(pat, RegexOptions.IgnoreCase);

	MatchCollection m = r.Matches(textVal);
	foreach (Match match in m)
	{
		string verseVal = "";
		string errorText = "";
		if (!String.IsNullOrEmpty(match.Groups["version"].Value))
			bibleVersion = match.Groups["version"].Value;
		string thisBook = match.Groups["book"].Value;
		int fromVerse = 0;
		int toVerse;
		if (!String.IsNullOrEmpty(match.Groups["verse"].Value))
			fromVerse = Convert.ToInt32(match.Groups["verse"].Value);
		toVerse = fromVerse;
		if (!String.IsNullOrEmpty(match.Groups["toVerse"].Value))
			toVerse = Convert.ToInt32(match.Groups["toVerse"].Value);

		using (var bdc = new bibleDataContext())
		{
			var searchVersion = bibleParts.theVersion(bdc, bibleVersion).SingleOrDefault();
			if (searchVersion != null)
			{
				var searchBook = bibleParts.theBook(bdc, searchVersion.language, thisBook).SingleOrDefault();
				if (searchBook != null)
				{
					var searchVerses = bibleParts.theVerses(bdc, searchVersion.version, searchBook.bookID, Convert.ToInt32(match.Groups["chapter"].Value));
						if (fromVerse != 0)
						{
							searchVerses = from v in searchVerses
										   where v.verseNum >= fromVerse && v.verseNum <= toVerse
										   select v;
						}
							string realToVerse = "";
							verseVal += String.Format("{1}{0}{2}", match.Groups["entire"].Value.Replace(match.Groups["book"].Value.Trim(), searchBook.book), "<span class=\"bibleHead\">", "</span>");
							verseVal += "<span class=\"bibleContent\">";
							foreach (var theVerse in searchVerses)
							{
								string verseFormat = "{2}{1}{3}{0} ";
								if (String.IsNullOrEmpty(match.Groups["toVerse"].Value) && !String.IsNullOrEmpty(match.Groups["verse"].Value))
									verseFormat = "{0} ";

								verseVal += String.Format(verseFormat, theVerse.thisVerse, theVerse.verseNum, verseNumBegin, verseNumEnd);
								realToVerse = theVerse.verseNum.ToString();
							}
							verseVal += String.Format("{1}{0}{2}</span>", searchVersion.copyright, footerBegin, footerEnd);
							verseVal = verseVal.Replace(String.Format("-{0}", toVerse), String.Format("-{0}", realToVerse));
							verseVal = verseVal.Replace("-</span>", "</span>");
				}
				else
				{
					errorText = "The book \"{0}\" does not exist.  Please check the spelling and try again.";
					errorValue = match.Groups["book"].Value;
				}
			}
			else
			{
				errorText = "The version \"{0}\" is not installed on this server";
				errorValue = match.Groups["version"].Value;
			}
			if (String.IsNullOrEmpty(errorText))
			{
				textVal = textVal.Replace(match.Groups[0].Value, verseVal);
			}
			else
			{
				textVal = textVal.Replace(match.Groups[0].Value,String.Format(errorText,errorValue.Trim()));
			}
		}
	}
	return textVal;
}

Currently active at newcastlechurchofChrist.com.

Firing a click event using the enter key

This one had me stumped for a few minutes. I had a text box that would post to a google search. Entering text in and hitting the enter key would just post back to the page and have no effect.

Hmm. Frustrating. I don’t expect everyone to type in their search criteria and grab the mouse to click the tiny little button so here’s what you have to do.

                <asp:Panel ID="searchPanel" DefaultButton="topSearchButton" runat="server">
                <asp:TextBox ID="topSearch" columns="21" CssClass ="formbox" runat="server" EnableViewState="False" />
                <asp:ImageButton ID="topSearchButton" ImageURL="images/bg/buttonGoHome.gif" OnClick="topSearchButton_Click" CSSClass="buttonGoHome" AlternateText="Submit" runat="server" />  
                </asp:Panel>

OnClick event:

    protected void topSearchButton_Click(object sender, ImageClickEventArgs e)
    {       Response.Redirect("http://www.google.com/u/xxx?domains=www.xxx.edu&sitesearch=www.xxx.edu&q=" + topSearch.Text);
    }

The DefaultButton attribute names the ID of the button you want to fire. The only issue is it doesn’t work on a text box (or any other form item) it only works on a Panel. I suppose it makes sense because you usually don’t just have one form field and a button, it’s usually many text boxes, any of which can fire the click event using the enter key.

I figured there was no point in having ViewState on a text box that posts to another site so EnableViewState should be set to False in this case.

FPDF.asp carriage return / line feed in MultiCell

Recently I was given the task of printing address labels from a database using Avery 5160 labels. These labels are 3 columns of 10 rows. 2.625″ x 1″ (66.6mm x 25.4mm).

I was having trouble with carriage returns. First problem is that the ASP version of FPDF doesn’t seem to recognize the \n line feed. It was easy enough to replace it with Chr(10) but I suppose I had a misconception of how FPDF handles line feeds in that it used the entire height of the multicell height for every line of text.

At first I used this incorrect code to test (notice the carriage return has no effect):

<%@language=vbscript%>
<!--#include file="fpdf.asp"-->
<%
Dim i,pdf
Set pdf=CreateJsObject("FPDF")
     pdf.CreatePDF "P","cm","Letter"
     pdf.SetPath("fpdf/")
     pdf.SetTopMargin(1.27)
     pdf.SetLeftMargin(.42)
     pdf.SetRightMargin(.42)
     pdf.Open()
     pdf.AddPage()
     pdf.SetFont "Times","",9
for i=0 to 40
          labelText = "Client #" & i & Chr(13) & "<--carriage return there address" & Chr(10) & "City, State  Zip "
     pdf.MultiCell 6.66,2.54,labelText,1,L
Next
     pdf.Close()
     pdf.Output()
Set pdf = Nothing
%>

with the results shown below:
bad line feeds

Then after googling a bit I found a php version of a script for creating a PDF for Avery 5160 labels and ported it to ASP:

<%@language=vbscript%>
<!--#include file="fpdf.asp"-->
<%
Set pdf = CreateJsObject("FPDF")
	pdf.CreatePDF "P","mm","Letter"
	pdf.Open()
	pdf.AddPage()
	pdf.SetFont "Arial","B",10
	pdf.SetMargins 0,0
	pdf.SetAutoPageBreak(0)
x = 0
y = 0
' instead of for next call names and addresses from database
for a = 1 to 40
	' Make label text
	LabelText = "Contact Name" & a & chr(10) & "Entire Company Name" & a & chr(10) & "1313 Mockingbird Lane" & chr(10) & "Chicago, IL  60617"
	LeftMargin = 4.2
	TopMargin = 12.7
	LabelWidth = 66.6
	LabelHeight = 25.45
	' Create Co-Ords of Upper left of the Label
	AbsX = LeftMargin + ((LabelWidth + 4.22) * x)
	AbsY = TopMargin + (LabelHeight * y)
	pdf.SetXY AbsX+3,AbsY+3
	pdf.MultiCell LabelWidth-8,4.5,LabelText,0,L
	y=y+1
	if (y=10) then
		x=x+1
		y = 0
		if (x = 3) then
			x = 0
			y = 0
			pdf.AddPage()
		end if
	end if
next
	pdf.Output()
Set pdf = Nothing
%>

Just change the for-next loop to a database call and you’re in business.

I find FPDF extremely useful and am sure to use it in the future.

Making FTP work on Windows Vista

I just built a Windows machine from parts and installed Windows Vista Ultimate 64bit. The only problem I was having was the FTP service would not stay active upon shutdown or restart. I had to start it manually every time.

If anyone else is wondering, here is how to make it work (if you already have FTP installed and just need it to start on reboot then start at step 7):

  1. Control Panels
  2. Programs
  3. Turn Windows features on or off
  4. locate and check FTP Publishing Service
  5. Click OK to install the items you selected
  6. You may or may not have to start FTP through IIS 6 but it may work without this step
  7. Start Menu
  8. right-click “Computer”
  9. Select “Manage”
  10. Select “Services” under “Services and Applications”
  11. double-click “FTP Publishing Service”
  12. Change “Startup Type” to “Automatic”
  13. Click OK

Let me know if this helps. I don’t know, it may be obvious to everyone that knows Windows but I’ve been a Macintosh guy since I bought my second computer back in 1995 (I had a Texas Instruments TI-99/4A before that). I’m used to starting FTP with one click in the Sharing control panel. 😉

For comparison here’s how turn on FTP under Mac OS X:

  1. Apple Menu
  2. System Preferences…
  3. Sharing
  4. Click the “FTP Access” checkbox

This also opens the port in the firewall and keeps it active any time the Mac is started.