Dynamic Javascript using ASP.NET MVC

  1. Create a new MVC applictaion
  2. Open /Views/Shared/Site.Master
    1. <script src=”/Scripts/script.js” type=”text/javascript”></script>
    2. This doesn’t point to anything yet
  3. Open Global.asax
    1. Add a MapRoute
      1. routes.MapRoute(“Javascript”, “Scripts/{scriptFile}.js”, new { controller = “Scripts”, action = “Index”, scriptFile = “script” });
      2. Now /Scripts/script.js points to Script/Index
  4. Add a view for CssController/Index
    1. <%= Url.Content(“~/Content/test.jpg”) %>
  5. Change contenttype in page directive of Views/Scripts/Index.aspx to “text/javascript”
  6. That should do it.

Adding strongly typed objects to javascript is now simple to achieve and instead of adding javascript to the <head> in your master page you can just have a dynamically generated and cached .js file.

You may download a sample MVC2 project here.

Dynamic CSS using ASP.NET MVC

I’ve been considering the usefulness of adding a CSS management system to my CMS that I’ve been working on. Here are the steps I’ve taken to implement it.

  1. Create a new MVC applictaion
  2. Open /Views/Shared/Site.Master
    1. <link href=”/Css/Site.css” rel=”stylesheet” type=”text/css” />
    2. This doesn’t point to anything yet
  3. Open Global.asax
    1. Add a MapRoute
      1. routes.MapRoute(“Css”, “Css/{cssFile}.css”, new { controller = “Css”, action = “Index”, cssFile = “Site” });
      2. Now /Css/Site.css points to Css/Index
  4. Add a class Css.cs to Models
    1. Add public string Body { get; set; }
    2. Add public string Width { get; set; }
    3. Add public string Background { get; set; }
  5. Add definitions as desired
  6. Add a view for CssController/Index
    1. Make it strongly typed with CSScontrol.Models.Css as the model
  7. Change contenttype in page directive of Views/Css/Index.aspx to “text/css”
  8. That should do it.

I’m not sure that I’m convinced of the usefulness of this idea but it’s possible and easy to implement in your ASP.NET MVC project. The possibility of using variables in a CSS file is intriguing.

You may download a sample of the MVC2 project here.MVC3 project here. Let me know if this is a good idea, a horrible idea or just a bit interesting.

Edit – I’ve added a Response.Filter to this to remove extra spaces, tabs, carriage returns/new lines, final semicolons and comments. It brought the 4.96kb file down to 2.74kb. It can be enabled/disbled in Web.config using appSettings key CompressCSS (bool)

Finding the current season using C#

Recently someone challenged me to write a server-side method for finding the current season and changing a style sheet accordingly.

The first step is deciding how to differentiate the seasons. I figured to use the day of the year so that astronomically Spring begins on March 21st, the 80th day of the year. Summer begins on the 172nd day, Autumn, the 266th and Winter the 355th. Of course, on a leap year add one day to each, 81, 173, 267 and 356.

Now to find the day of the year use the DayOfYear property. Also, use the IsLeapYear property to take a day away on leap year if the current day is after February 28th (the 59th day of the year). This is easily done by converting the bool IsLeapYear to an Int32 giving a 0 during non-leap years and 1 during leap year.
int doy = DateTime.Now.DayOfYear - Convert.ToInt32((DateTime.IsLeapYear(DateTime.Now.Year)) && DateTime.Now.DayOfYear > 59);

Then find the current season using a few nested ternary operators (I like ternary operators)
string currentSeason = String.Format("{0}.css",((doy < 80 || doy >= 355) ? "winter" : ((doy >= 80 && doy < 172) ? "spring" : ((doy >= 172 && doy < 266) ? "summer" : "fall"))));

I use this in the head of my MasterPage:
<link rel="Stylesheet" runat="server" id="seasonSheet" type="text/css" />

So all I have to do to add the correct style sheet for the current season is this:
seasonSheet.Attributes.Add("href",currentSeason);

Now a stylesheet named spring.css, summer.css, fall.css or winter.css will be added to your rendered MasterPage.

I hope you’ve found this helpful.

SCOPE_IDENTITY() with LINQ

While reading about a recent question concerning @@Identity and the reasons to use SCOPE_IDENTITY() instead I experimented with LINQ to get the SCOPE_IDENTITY() results.

 using (var dbc = new siteDataContext())
    {
        dbc.Log = Console.Out;
        repertoire newSkill = new repertoire
            {
                skill = "a new item"
            };
        dbc.repertoires.InsertOnSubmit(newSkill);
        dbc.SubmitChanges();
        Console.Write(newSkill.id.ToString());
    }

It seems that calling the id column of the inserted object gives you the SCOPE_IDENTITY(). Here is the SQL Log:

INSERT INTO [dbo].[repertoire]([skill])
VALUES (@p0)
SELECT CONVERT(Int,SCOPE_IDENTITY()) AS [value]
-- @p0: Input NVarChar (Size = 10; Prec = 0; Scale = 0) [a new item]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.1

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.