Tuesday, December 29, 2009

Quick Hack to Restrict Countries in DotNetNuke's Address Control

Today, I needed to restrict the countries that were listed in DotNetNuke's Address control for a custom module I was writing. I only wanted to display the North American countries of Canada, Mexico and United States. After surfing the net for 5 minutes, nothing was rising to the top as a solution so I hacked my own solution out.

Assuming you have a web control with the following declaration,
...
<%@ Register TagPrefix="dnn"
TagName="Address"
Src="~/Controls/Address.ascx" %>
...
<dnn:Address id="addressControl" runat="server" />
...

you can restrict the countries in the code-behind like so:

using System.Linq;
...
private static readonly string[] Countries
= new[] { "Canada", "Mexico", "United States" };
...
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);

var list = this.addressControl.FindControl("cboCountry")
as CountryListBox;
foreach (var item in list.Items.Cast<ListItem>().ToArray())
{
if (!Countries.Contains(item.Text))
list.Items.Remove(item);
}
}


Enjoy!

Tuesday, December 8, 2009

Global.asax Session_End NullReferenceException

I had an issue pop up on an ASP.NET web app this morning consisting primarily of NullReferenceExceptions being thrown when Global.asax runs some cleanup code from its Session_End handler. Basically, the cleanup code is disposing some IDisposable items stored in session state before the session blinks out of existence. The manner in which the session was being accessed was the following:

System.Web.HttpContext.Current.Session[...

This throws a NullReferenceException since HttpContext.Current is null. Null? Why is the context null? It didn't seem logical at first, but after a minute of thought, it came to me. Of course there's no context, the session expires on a timeout. Although, the lifecycle of a session begins with a request, it doesn't end with one. Thus, when a session times out there is no request or response available, and thus no context.

Well, how do I access the session then?

Easy. Global.asax has a Session property. It is not null at this point, and this allows you to work with the session before the session is garbage collected. I had to change my cleanup code interface, but I the resolution consisted of Global.asax passing the session to the cleanup code rather than having the cleanup code acquire it itself through the current HttpContext.

protected void Session_End(object sender, EventArgs e)
{
// HttpContext.Current.Session throws a
// NullReferenceException since no HttpContext
// exists when a session expires, thus the client
// code can't access the HttpSessionState object this
// way and needs it to be passed directly from
// Global.asax (i.e. this.Session)

this.Controller.Cleanup(this.Session);
}

Sunday, November 15, 2009

Manually Installing DotNetNuke on GoDaddy

These steps detail the process of manually installing DotNetNuke to the root of an empty GoDaddy hosted domain.

  1. Acquire DotNetNuke

    • Go to http://www.dotnetnuke.com/ and login (note: if you're not registered, then you'll have to register before you can login)

    • Click the "DOWNLOAD" icon at the top of the page

    • Download the edition of your choice (I chose Community)

    • Unzip the file to the directory of your choice



  2. Virtual directory setup

    • Login to GoDaddy

    • Hover over "Hosting" on the menu bar and click the "My Hosting Account" link

    • Click "Manage Account" next to the site to which you want to install DNN. This will take you to GoDaddy's Hosting Control Center.

    • Hover over the "Content" tab and click the "File Manager" link

    • Select the "[Root]" row by clicking on it or checking the checkbox

    • Click the "Permissions" icon on the toolbar

    • Check the "Read", "Write", and "Reset all children to inherit" and click OK (this needs to occur because DotNetNuke writes to its directory and sub-directories for many reasons, the most common is module installation)



  3. Database setup

    • In GoDaddy's Hosting Control Center, hover over the "Databases" tab and click the "SQL Server" link

    • Click the "Create Database" button

    • In the description type something descriptive like "www.mydomain.com DotNetNuke Database"

    • Check the DSN and ASP Schema checkboxes (this tells GoDaddy's database creation script to create a Data Source Name for the database and to Create the tables for the ASP Schema - e.g. aspnet_Applications, aspnet_Roles, etc.)

    • (Optional) If you want remote admin access to the database, check the "Yes" radio button under "Allow Direct Database Access" (this allows external access to the database - e.g. you can connect to it using SQL Server Management Studio on your own PC)

    • Enter a name for the database in the "SQL Server Database/User Name" field - e.g. mydomaindnn (this will be the name of the database and the user name for used for DNN's access)

    • Pick a good password, but be mindful of GoDaddy's password requirements. Write down this password as you'll need it to edit the web.config in a future step.

    • (Optional) If you'd like to create an user with read-only access this database, fill out the remaining fields correlating to "Read-Only User"

    • Click OK (You'll be taken back to teh SQL Server databases page and the status of your database will be "Pending Setup" - this should only be the case for a few minutes)

    • Click the "Edit/View Details" icon for your database in the "Actions" column. This page contains the values you'll need for changing the web.config in the next step.



  4. Web.Config

    • On your PC, go to the directory where you unzipped the DNN installation files.

    • Rename web.config to original-web.config

    • Rename release.config to web.config

    • Open web.config in your favorite editor

    • In the conectionStrings section, fully comment out the SQLExpress connection string (it contains "Data Source=.\SQLExpress"), and uncomment the SQL Server connection string (it contains "Server=(local)"). Change the SQL Server connection string include the database values seen on GoDaddy (Server is Host Name; Database is Database Name; uid is User Name and pwd is the password you entered for this user when you set up the datbase).

    • Repeat the above step for the connections strings found in the appSettings section just a few lines below.



  5. Upload via FTP

    • Configure your FTP clint (FileZilla, or CuteFtp, or the like) to point to ftp.mydomain.com, with your GoDaddy username/passoword (or another FTP user for whom you've register FTP credentials through the GoDaddy Control Center).

    • Connect

    • Once connected, you can delete everything in the root except the _db_backups and _hcc_thumbs directories.

    • This upload the contents of the directory to which you unzipped the DNN installation files.



  6. You are ready to install

    • Navigate to the www.mydomain.com

    • Follow the steps to walk through creating your site (Note: choosing the Auto option is recommended.)

    • For SMTP settings, use

      server: relay-hosting.secureserver.net

      basic

      username: emailuser@mydomain.com

      password: [emailuser's email password]

      * Make sure to Test the settings before you move on!


Monday, August 31, 2009

Simple MVP in ASP.NET

Model View Presenter (MVP)

The model:

public interface IEntity
{
SomeType SomeProperty1 { get; set;}
SomeOtherType SomeProperty2 { get; set;}
YetAnotherType SomeProperty3 { get; set;}
}



The interface (the view contract - insulates the presenter from the implementation):

public interface IEntityView
{
event EventHandler Init;
event EventHandler Load;
event EventHandler Unload;
IEntity Entity { get; set; }
bool Visible { get; }
}


The code behind (the view implementation):

public partial class EntityDisplayPage : Page, IEntityView
{
protected EntityDisplayPage()
: base()
{
new EntityPresenter(this); // wire the MVP pattern
}

#region IEntityView Members

public IEntity Entity { get; set; }

#endregion
}


The presenter (doesn't know the view implementation, manipulates it through the view contract defined as an interface):

public class EntityPresenter
{
public EntityPresenter(IEntityView view)
: base()
{
if (null == view)
throw new ArgumentNullException("view");
this.view = view;

this.View.Init += (sender, e) => { /* some business logic on init */ };
this.View.Load += (sender, e) =>
{
/* some business logic on load*/
this.View.Entity = GetEntityToDisplay();
};
this.View.Unload += (sender, e) => { /* some business logic on unload*/ };
}

private readonly IEntityView view;
public IEntityView View
{
get { return view; }
}
}


And that's how you get good separation through a simple MVP implementation in ASP.NET. I think it speaks for itself.

Tuesday, August 4, 2009

Embrace Your Technology

Recently, I was working with a fairly progressive client that was spending a lot of time working its internal architecture into an SOA solution. I was brought on in January as a consultant to build a greenfield application of modest complexity, but probably more complex than the apps they'd built internally to date. They placed several strict requirements on certain technological aspects of development, which I could easily handle. However, they did declare one that gave me some fits.

A few months before I arrived, their internal staff had started to develop a web service to centralize contact information for the entire agency. During the initial phases of my project, I was told that my app was to integrate with this new contact web service when in comes online in March. (Those of you with inter-team experience already know where I'm going.) March came and went; no contact web service. I kept hearing rumors of re-work from members of the team, which made me very nervous. My project manager (also a consultant) and I began to think of contingencies as this was probably going to bite us and delay our release. We were then told May; the web service would be up and running by May. May came and went. Our thoughts eventually turned entirely to "It's never going to be delivered. We're going to have to implement something ourselves to make our release date."

Our app was due to make it's initial production release the first of July. We had to have that contact piece to go live. But we had nothing. At the end of May, we decided to write our own and merge it with theirs later. I wrote our solution for contacts the following week and we moved into production the first week of July. Whew!

Sometime that month, they finally pushed their contact web service to production and I was able to begin integration with my app on July 30th. They were using an ASMX web service, so I figured that it'd be easy enough to create a proxy and adapt it to our solution. I silently thought to myself, "I bet I can have this integrated in just a couple days."

I was in for shock though. When I dug into the WSDL, I was both disgusted and astonished. The schema was flat. They were using SOAP to usher strings (and only strings) back and forth to the web service. The schema had no depth at all anywhere - everything was a string, a string that had to be parsed manually. If an operation returned person information, it returned a string which contained the information in XML form, but still a string that'd have to be parsed or accessed via XPath. There other issues that come about by having such a poorly defined schema. For example, if everything is a string, I can send anything I want to the service including the current version of Webster's dictionary or the New York City phone book. And oh yeah! I was delayed for this? It took them months and months to build this? Less than twenty-five operations, no schema... What were they doing all that time?

It's as if they intended to build a house, took as long as building a sky scraper and wound up with a tool shed. They decided to leverage a very powerful tool, an air nailer, but used it as a hammer. When they went to drive a nail, they turned the air nailer sideways and drove it with the side of the air nailer. In this way, not only did they negate the benefits of the air nailer, but they also endured a loss of productivity.

I had no doubt; this was going to be painful. To add some extra salt to the wound, some features my application needed were simply not implemented. So, on top of having to break it to my users that some of their favorite contact functions were going away, now I have to write custom parsing logic to extract information from the operations, rather than let .NET's out-of-the-box SOAP digestion handle if for me. Which is going to delay the whole process by days. I cringe when I think of parsing XML myself for any reason; especially when so many good people worked hard to write frameworks for SOAP and various other specifications so I wouldn't have to.

Thus, I plead to the masses, "Embrace Your Technology". It seems more and more commonplace that my clients not only fail to understand the technologies they choose, but choose technologies they don't understand. From ASP.NET to Oracle to Visual Studio to web services, they don't understand what these choices provide and end up misusing them negating the benefit. They stand up and claim "We're using SOAP web services!", but have no idea what that the specification does for them. They miss the benefits a specification provides by allowing them to leverage pre-established frameworks to forgo the normal rigmarole that other web service specifications impose on them, allowing them the ability to move on with more important development without writing custom XML parsing and operation security for invalid data.

I will contend that too many folks are content to acquire a technology without acquiring a book on the subject. Heaven forbid someone even try reading the book before acquiring the technology. This is so unfortunate as there are typically some really great minds on these technologies that share their expertise through writing books and whitepapers.

I'll take it a step further and sideways; I will contend that too many folks that barely need a firecracker acquire a nuclear bomb. Some acquire Oracle when a text file will suffice for the moment; even when an RDBMS is necessary, MySQL will do the trick before you bring in the heavy hitter. Remember, your real need is persistence of data, not the acquisition of Oracle. A good friend watched his startup fail because his colleagues spent their capital on Oracle rather than a prototype of their idea - a simple case of confusing needs.

So to all you hard-working folk out there, embrace the technology you choose and choose the technology you need. Perhaps, you'll deliver a functioning house on time.