# Friday, March 20, 2009

I was having trouble getting an ASP.NET 2.0 site up and running today on a (new to me) server.  I could browse static content from the site, but any ASPX pages would return a 404 error.   At first I thought I was mistyping the URL, then I thought that ASP.NET just wasn't registered so I ran the aspnet_regiis.exe utility, but still no dice. 

I played around with all kinds of settings until I find that ASP.NET was completely disabled on the server under Web Service Extensions.  For ASP.NET to work the WSE for ASP.NET must be set to allowed!  I have no idea why it was disabled, but it was.  Argh!

Friday, March 20, 2009 9:16:01 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
# Monday, December 22, 2008

I've been playing around with Grails on my Ubuntu box for the past week and really like the ability to do fast web development while still retaining all the power that the JVM provides.  Unfortunately, it also comes with the baggage of Java, which became pretty apparent once I needed to map a date time object.

Since I still have a day job, and I don't see us switching from .NET anytime soon, I thought I would spend a little time with ASP.NET MVC and IronPython to see where things are. I was able to find a sample MVC sample application on CodePlex which made use of IronPython in the views and the global.asax. The one feature I really wanted support for was writing controllers in python, apparently that's in the works but not currently supported.  Not much python in the sample, but pretty neat to know it works.

... Well actually at some point it probably did.  I'm using the first beta release of ASP.NET and some things have changed since the IronPython sample came out.  Needless to say, the sample no longer works, I get an HttpParseException with the message "HtmlHelper' object has no attribute 'ActionLink'"

Pretty strange message huh?  In C# I can find this method no problem. Long story short, IronPython can't use extension methods like C# can.

At some point the MVC team moved the HtmlHelper.ActionLink method from System.Web.Mvc to Microsoft.Web.Mvc.  The latter is the ASP.NET futures assembly, which provides a bunch of extension methods that they may add to the System DLL at a later date.

Monday, December 22, 2008 7:34:35 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Tuesday, July 29, 2008

Specifically ID and CLASS attributes are case sensitive when it comes to CSS.  If you take a look at the HTML 4 spec you'll notice that both ID and CSS have [CS] annotation for case-sensitive. 

Although this has always been the case, very few developers know this as a fact.  Its obvious Microsoft missed this in the first few HTML 4 compatible versions of IE.  In fact it wasn't until IE 6, when run in strict mode, would IE in fact honor the case sensitive nature of the CLASS and ID attribute.

I think its because of the early IE 'quirks' mode implementation that very few developers actually know that ID and CLASS are in fact case sensitive. They may guess correctly, but very few actually know this from experience.  A simple quiz at work today confirmed my suspicion.

Well after today, I know from experience that CSS is case sensitive.  <div class="msg-Error"/>  is not the same as  <div class="msg-error"/>.

So in summary if your CSS is not getting applied as expected you might want to check the casing between your HTML and your CSS.

Tuesday, July 29, 2008 12:37:01 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Monday, April 28, 2008

I really like what Microsoft has done with the web development capabilities of Visual Studio 2008, they seem so much more mature and refined than in previous versions.

For instance, when you open up a new project in Visual Studio 2008 that has been configured to run under IIS, it now asks you if you would like VS to create the virtual directory for you in IIS.  No need to open up inetmgr and manually type in directory paths and virtual directory names.

Not only that, it creates the virtual directory in the correct place which is source control friendly, i.e. in place, not under some c:\inetpub\wwwroot folder like it used to do with Visual Studio 2003.  Microsoft finally figured out that .NET developers actually use source control.

Additionally you can now apply the settings on a per project basis, not on a per developer basis.  This is very handy when you want to share settings between devs, like when you have tests that are dependant upon web URLs.

VS2008WebSettings

Monday, April 28, 2008 4:29:14 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Monday, February 25, 2008

From Jeff Palermo in the comment section of Hammet's blog post:

I'm, at this point, very concerned that the average Microsoft developer will use ASP.NET MVC not to improve their application design, but merely because it's the new thing. I can see it now: before there was 1000 lines of code in Page_Load. Now there is 1000 lines of code in the controller action.

Monday, February 25, 2008 7:24:25 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Monday, January 14, 2008

I posted last week about testing JavaScript through NUnit with JSUnit.  While this worked, it was a little slower to run tests than I really wanted.  It was also slower (for me) to write the tests since I had to leave the comfort of NUnit and .NET for JSUnit and JavaScript.

I'm continuing to look at the problem of JS testing for ASP.NET.  I evaluated YUI Test, Yahoo's JS unit test library.   YUI Test actually looks very good, but I really wanted to keep things integrated with my current build and test process, even if it isn't the best solution; you don't know unless you try.  ;-)

This time I'm only using NUnit and WatiN for my JavaScript tests.  Since WatiN now has the ability to evaluate JavaScript and return the results, I'm using this to run the JavaScript function under test and then using an NUnit assertion for validation.

I create each test using regular NUnit syntax, and then wrap the JavaScript function call with a C# method which passes the arguments off to the JS function to test.

[Test]
public void ShouldValidateDiscover()
{
    Assert.IsTrue(ValidateCreditCard("6011648040903965", "discover"));
}
 
private bool ValidateCreditCard(string cc, string ccType)
{
    string testJs = string.Format("var cc = new CreditCardValidator(); cc.isValidCreditCardNumber('{0}', '{1}');", cc, ccType);
    return EvalToBool(testJs);
}

The EvalToBool method just calls the WatiN eval method which accepts JavaScript as a parameter, and then converts the JS string result to a .NET boolean.

protected bool EvalToBool(string js)
{
    string result = ie.Eval(js);
    return bool.Parse(result);
}

The overall process is:

  1. Create a dummy HTML page which includes the JavaScript source file I want to test.
  2. Kick off WatiN and navigate to the dynamically created HTML file.
  3. Pass the JavaScript function to be evaluated by WatiN.
  4. Assert the eval call results using NUnit.

The test setup is very simple, and it runs fast for this type of test.  On my PC it was taking 1.7 seconds to setup the test fixture, and then .2 seconds to run each test.  The nice thing is that the errors are generally obvious and easy to diagnose since they are native NUnit tests.  Its also nice to have multiple NUnit test entries in the runner instead of one monolithic JavaScript test function which encapsulated all my JS tests that run in JSUnit. 

The tests can get ugly if a JavaScript exception is thrown, in that case you get a generic COM exception returned from WatiN rather than a nice error message.  The is one area where a JSUnit or YUI test are naturally better at.

This makes me wonder if WatiN couldn't be extended to return better error information from JavaScript, and whether the use of a dynamic .NET language along with NUnit couldn't make for a nice testing experience.

Monday, January 14, 2008 6:44:21 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Saturday, January 05, 2008

Environment specific configuration can be a real pain to manage effectively.  I think I've tried every approach imaginable and it seems there is no perfect solution.  I just switched my many environment specific configuration files (I actually got to delete ~25 config files from SVN!) over to a handful of Windsor configuration files that use conditional elements and includes like so:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <properties>
    <?if LIVE?>
      <billingGatewayHost>webserver1</billingGatewayHost>
      <sessionConnectionString>server=dbserver1;database=ASPStateNET2;integrated security=SSPI</sessionConnectionString>
    <?elseif TEST?>
      <billingGatewayHost>webserver3</billingGatewayHost>
      <sessionConnectionString>server=dbserver3;database=ASPStateNET2;integrated security=SSPI</sessionConnectionString>
    <?end?>
  </properties>
</configuration>

 

I had so many configuration files because I had 5 environments, plus a session state, http module, appSettings, and some other stuff broken out into separate files using the standard .NET include section file mechanism.

I think I like the conditionals better than separate files for each environment which requires a postbuild process either at build or deployment time to merge or copy XML.  Also, it should be pretty obvious to any developer what's going on here.  Unfortunately the standard ASP.NET configuration mechanism doesn't have anything like this, and what's worse is that some components - like session state as in my case - can only be configured through the web.config.

That means no overriding the configuration at runtime or using Windsor.  No, the internal SqlSessionStateStore class in the FCL reads directly from the web.config using an internal static helper class.  This means no inheritance or injecting my configuration, even with the help of reflection.

ASP.NET does support custom session state providers, however I just want to use the SqlSessionStateStore class with a different configuration mechanism (I would really like to wire the thing up using Windsor); and creating a brand new provider is a lot of work.

Since all of my properties (i.e. connection strings) are in an external Windsor configuration file, it makes the most sense to also put my conditional session state connection strings in there as well (if you haven't picked up on it yet, I'm using SQL Server to store my session data, hence the SQL connection string for session).

Here's how I ended up getting the SqlSessionStateStore to use the connection string from my Windsor config...  And here is the hack in the global.asax.cs... 

private static volatile bool appDomainIsShuttingDown = false;
 
public override void Application_Start(Object sender, EventArgs e)
{
    base.Application_Start(sender, e);
 
    if (IoC.Resolve<SessionStateConnectionStringSetter>().SyncWebConfigSettingIfDifferent())
    {
        HttpRuntime.UnloadAppDomain();
        appDomainIsShuttingDown = true;
    }
}

 

On application start my base Application_Start method gets my DI container up and running.  After that I ask the container for a SessionStateConnectionStringSetter instance which is injected with the proper session DB connection string from the container.  All this class does is modify the web.config if the connection string it has differs from the one in the web.config.  After that, if the web.config has been modified, the current app domain is shutdown and the appDomainIsShuttingDown flag is set to true. 

In case you're not sure, static variables are scoped to the current app domain by default, so the next request for the web app will have the appDomainIsShuttingDown variable set to false (it's default value).  I think there is a threading bug in there if multiple application start events get fired off simultaneously, so I should probably add a lock around the web.config update.  I'll have to look into that.  Anyway, here's the class that updates the web.config with the new session state connection string.

public class SessionStateConnectionStringSetter
{
    private readonly string sessionStateConnStr;
 
    public SessionStateConnectionStringSetter(string sessionConnectionString)
    {
        this.sessionStateConnStr = sessionConnectionString;
    }
 
    public bool SyncWebConfigSettingIfDifferent()
    {
        ExeConfigurationFileMap map = new ExeConfigurationFileMap();
        map.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "web.config");
 
        Configuration config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
        SessionStateSection session = (SessionStateSection)config.GetSection("system.web/sessionState");
 
        if (session.SqlConnectionString != sessionStateConnStr)
        {
            session.SqlConnectionString = sessionStateConnStr;
            config.Save();
            return true;
        }
 
        return false;
    }
}

 

Further along in the ASP.NET pipeline the Application_BeginRequest method executes and the current user request (the one that initiated the web.config to be updated in the first place) is redirected - back to the same page they asked for originally.  This is done so the user doesn't see a nasty exception that will likely happen if processing continues with the original incorrect session DB connection string.

protected void Application_BeginRequest(object sender, EventArgs e)
{
    // This will cause the user to re-request the page using a new appdomain
    if (appDomainIsShuttingDown)
        Response.Redirect(Request.Url.ToString());
}

 

The thing to note about all this, is that the web.config update should only ever happen on the very first request for the application after a deployment, which not too surprisingly will almost always be the person doing the deployment (assuming they check their work). 

I wish I could find a better way to do this, but as far as I can tell there's no other way to set this programmatically at runtime.  I may just break down and add a special case to my NSIS installer to update the web.config session connection string from my Windsor configuration.  I really just wanted to see if I could get this to work without a build or deploy step...

Saturday, January 05, 2008 9:25:15 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Sunday, November 11, 2007

Here's a good reason not to use untyped datasets.  I was tasked with hiding a radio button on a webform depending on the SkuType of the product.  Unfortunately I have no idea if the web application, let alone the web service it uses for data access, returns the SkuType already.  The web service just returns a bunch of untyped datasets as raw strings.  To actually figure out what each web service call is returning I have to look at each sproc's SELECT statement.

If I had some hint in the code of what the expectations of the form of data, I could at least grep for SkuType.  As it is now I may be adding it to an existing sproc, thus unknowingly duplicating logic.  I guess my only alternative is to export out all sprocs to a script and then grep the script for SkuType.

Sunday, November 11, 2007 10:17:25 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Wednesday, June 27, 2007
Sometimes you want to share session between web apps, not just between web servers.  By default you cannot do this with the SQL Server session state provider because it keeps differently named applications session's separate.  To allow this, we can modify the TempGetAppID sproc in the ASPState database.  Most solutions I've seen to this just override the sproc and allow ALL web apps using this database to share session, or they are hardcoded to look for specific app names.  Neither of these options was appealing, and worse yet the second option wasn't even viable when using the builtin Visual Studio 2005 web server. 

The solution I came up with was to use the connection string "Application Name" as the appName.  If you are familiar with SQL Profiler you will recognize this.  By default all .NET applications report they have an application name of ".NET SQLClient Data Provider" unless you have overridden it in the connection string.  This allows me to pool sessions between web applications conditionally through a configuration change, that even works on the built-in Visual Studio 2005 web server (webdev.webserver.exe).  Here's the new sproc:

    USE ASPState
GO

ALTER PROCEDURE dbo.TempGetAppID
    @appName tAppName,
    @appId int OUTPUT
AS

    -- start change

    -- Use the application name specified in the connection for the appname if specified
    -- This allows us to share session between sites just by making sure they have the
    -- the same application name in the connection string.
    DECLARE @connStrAppName nvarchar(50)
    SET @connStrAppName = APP_NAME()

    -- .NET SQLClient Data Provider is the default application name for .NET apps
    IF (@connStrAppName <> '.NET SQLClient Data Provider')
        SET @appName = @connStrAppName

    -- end change

SET @appName = LOWER(@appName)
SET @appId = NULL

SELECT @appId = AppId
FROM [ASPState].dbo.ASPStateTempApplications
WHERE AppName = @appName

IF @appId IS NULL BEGIN
BEGIN TRAN

SELECT @appId = AppId
FROM [ASPState].dbo.ASPStateTempApplications WITH (TABLOCKX)
WHERE AppName = @appName

IF @appId IS NULL
BEGIN
EXEC GetHashCode @appName, @appId OUTPUT

INSERT [ASPState].dbo.ASPStateTempApplications
VALUES
(@appId, @appName)

IF @@ERROR = 2627
BEGIN
DECLARE @dupApp tAppName

SELECT @dupApp = RTRIM(AppName)
FROM [ASPState].dbo.ASPStateTempApplications
WHERE AppId = @appId

RAISERROR('SQL session state fatal error: hash-code collision between applications ''%s'' and ''%s''. Please rename the 1st application to resolve the problem.',
18, 1, @appName, @dupApp)
END
END

COMMIT
END

RETURN 0
GO


Wednesday, June 27, 2007 7:34:09 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [5]  | 
# Friday, June 01, 2007
Found this in a comment on ScottGu's blog:

We use the web setup project to deploy our web applications. To automate additional installation tasks like setting the .NET version and encrypting the web.config files we use a custom installer along with the web setup project (see relevant code below). However we recently ran into a problem when deploying an application on a server where there are several web sites. The site we wanted to install on was not the default site. Therefore our code to set the .NET version and assign the newly created application pool did not work since "W3SVC/1/ROOT/" was not the correct path to the website. Is there a property similar to [TARGETDIR] which we can pass to our custom installer to account for this. We know hard-coding "W3SVC/1/ROOT/" was not the best option but we also knew it would be correct 90% of the time. Thanks as always for your time.

Best Regards,

Stephen

info = New ProcessStartInfo()

info.FileName = "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis.exe"

info.Arguments = String.Format("-s W3SVC/1/ROOT/" & strVDir)

info.CreateNoWindow = True

info.UseShellExecute = False

Process.Start(info)

info = Nothing

Dim myApp As New DirectoryEntry("IIS://localhost/W3SVC/1/ROOT/" & strVDir)

myApp.Properties("AppPoolId").Item(0) = strMyAppPoolName

myApp.CommitChanges()

myApp = Nothing

# re: Using IIS with VS 2005 and the new Web Project system

Friday, January 12, 2007 2:00 AM by ScottGu

Hi Stephen,

You can pass in the targetsite and targetvdir path as context parameters to your custom action by passing this:

/targetdir="[TARGETDIR]\" /targetvdir="[TARGETVDIR]" /targetsite="[TARGETSITE]"

Friday, June 01, 2007 10:11:13 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
I upgraded a web service application from .NET 1.1 and was getting the following error:

Parser error global.asax could not load type

I found this was caused because the installer for the application was creating an IIS web application set to use .NET 1.1.

Friday, June 01, 2007 9:46:32 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, May 24, 2007
I had forgotten how much I hate dealing with web projects in Visual Studio 2003, until today.  I went to open up one of our companies VS 2003 web projects and didn't have a virtual directory configured before hand, so when it tried to open it complained it couldn't find the path to the csproj file.  OK, no big deal, open up inetmgr and create a virtual directory.  Well that was a good though, except I entered the incorrect path for the virtual directory.  Try again, change the virtual directory to the correct path and try an open the project again.  No good, I get this error now, "Unable to get the project file from the web server."  What the?

The fix: delete *.* from c:\documents and settings\sneal\VSWebCache.

Thursday, May 24, 2007 6:01:56 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
# Wednesday, May 02, 2007
HTML textarea controls do not support the maxlength attribute like the text input control.  Doh!  The nice easy way around this is to add an ASP.NET regular expression validator with the following regex: ^[\s\S]{0,25}$

The only slight problem with this approach is that it is possible to input more than X number of characters, but the validator will display an error and disallow the page to be submitted.

Wednesday, May 02, 2007 5:33:01 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Wednesday, April 11, 2007
There may be a time you need access to the current executing ASP.NET handler's (page) view state without having a reference to the executing page.  I did today while creating a view state adapter that is injected into my page controllers.  I swapped out the Session adapter for this new ViewState adapter in my Spring.NET config.

Here's the code where I grab a view state reference in the adapter:

return ((PageBase)System.Web.HttpContext.Current.Handler).PageViewState;

Basically I grab the current handler and cast it to PageBase type.  In my application all pages inherit from PageBase, and PageBase exposes a public PageViewState property.  This allows me to grab the viewstate without having a direct reference to the page instance.  Some of this extra work is required because the System.Web.UI.Page ViewState property is not public, but protected.

Wednesday, April 11, 2007 8:53:47 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, March 22, 2007
"ctl00___ctl00___ctl00_ctl00_bcr_ctl00___Comments___Comments_ctl06_NameLink" Why do you mock me?!  Why do you make something so blatantly easy, so cumbersome and impossibly difficult?  Its for my own protection you say?  I can't be trusted you say?

Clearly when MS brought out ASP.NET they never  really intended for programmers to do client side programming, because its too difficult for Mort to even understand the difference between client side code versus server side code.  I've explained the difference to Mort several times, however I still don't think he truly understands the difference.  I blame the leaky abstraction that is ASP.NET for making it too difficult for Mort to truly understand what's really happening under the hood of ASP.NET.

Thursday, March 22, 2007 4:36:28 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Saturday, March 17, 2007

To get my WatiN tests working in predictable and repeatable manner I ended up integrating Spring.NET into my application so that I could swap out the data service implementation through configuration.  When running under WatiN the stubbed data service is used instead of the production web service.  Having a predictable set of test data is critically important, especially since some of my test assertions are looking for specific data values in the rendered HTML, which would be impossible if I was hitting the actual web service for data.

I integrated Spring.NET into the ASP.NET 1.1* application I'm working on and so far I'm very happy with it.  What I like about it:

  • XML configuration almost exactly the same as Spring in Java (which I've been using pretty heavily for the last 3 months).
  • Tight ASP.NET integration, so I have no code dependencies on any of the Spring namespaces in my code.  None.  Zero.
  • There are data validation libraries and other features which may be useful in the future.
  • It just works.

So check it, here's my code behind for the Search.ascx, notice how the controller instance is set via the Controller property by Spring.  Then the view instance (the Search user control instance itself) is set into the controller in the Initialize method call on page load.  The one other thing that might seem strange is that I'm creating the SearchCriteria instance directly from the user control's Request collection rather than using declared ASP.Net controls; sometimes straight HTML controls are just what we need.

public class Search : ControlBase, ISearchView
{
   protected System.Web.UI.WebControls.LinkButton m_btnSearch;
   protected System.Web.UI.WebControls.DataGrid m_resultsGrid;
   private SearchController m_controller;

   private void Page_Load(object sender, System.EventArgs e)
   {
      Controller.Initialize(this, !Page.IsPostBack);
   }

   public void SortSearchResults(object sender, DataGridSortCommandEventArgs e)
   {
      Controller.Sort(e.SortExpression);
   }

   public void SearchClick(object sender, EventArgs e)
   {
      Controller.Search(Criteria);
   }

   public SearchCriteria Criteria
   {
      get
      {
         SearchCriteria criteria = new SearchCriteria();
         criteria.BorrowerId     = Request.Form["txtBorrowerId"];
         criteria.BorrowerName   = Request.Form["txtBorrowerName"];
         criteria.BusinessLine   = Request.Form["txtBusinessLine"];
         criteria.LoanNumber     = Request.Form["txtLoanNumber"];
         criteria.UnderwriterUid = Request.Form["txtUnderwriterUid"];

         return criteria;
      }
   }

   /// <summary>
   /// Injected by Spring.NET.
   /// </summary>
   public SearchController Controller
   {
      get { return m_controller; }
      set { m_controller = value; }
   }

   #region ISearchView Members

   public void setResults(IList results)
   {
      m_resultsGrid.DataSource = results;
      m_resultsGrid.DataBind();
   }

   #endregion

}

And here's the Spring.NET specific configuration for the Search page, this does all the wiring via configuration of the different objects.

<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net">

    <object type="Search.ascx">
        <property name="Controller" ref="SearchController" />
    </object>

    <object id="SearchController" type="CRR.Web.Controllers.SearchController, CRR.Web">
        <constructor-arg name="dataService" ref="DataService" />
        <constructor-arg name="stateProvider" ref="SessionStateProviderAdapter" />
    </object>
   
    <object id="SessionStateProviderAdapter"
            type="CRR.Web.Controllers.SessionStateProviderAdapter, CRR.Web"/>


    <!-- Production data service implmentation -->
    <!-- <object id="DataService" type="CRR.Domain.DataService, CRR.Domain"/> -->
   
    <!-- Stubbed data service implmentation for testing -->

    <object id="DataService" type="CRR.Domain.DataServiceStub, CRR.Domain"/>   
   
</objects>

There is also a Spring provided HttpHandler and an HttpModule for Spring in the web.config.  This is the important part.  By registering Spring web as the http handler for aspx pages, I'm now telling Spring to construct my aspx (and thus ascx) page instances rather than ASP.NET.  With Spring now constructing my pages, its now possible to inject dependencies directly into my pages upon construction.

<httpModules>
   <add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web"/>
</httpModules>
<httpHandlers>
   <add verb="*" path="*.aspx" type="Spring.Web.Support.PageHandlerFactory, Spring.Web"/>
   <add verb="*" path="*.asmx" type="Spring.Web.Services.WebServiceHandlerFactory, Spring.Web"/>
   <add verb="*" path="ContextMonitor.ashx" type="Spring.Web.Support.ContextMonitor, Spring.Web"/>
</httpHandlers>

The search controller uses a state service, specifically ASP.NET session state via a custom session state adapter, and a data service which are injected into the controller by Spring - the view (code behind page) has NO knowledge of the services being injected into the controller, as the controller instance itself is injected into the view.

Here's the relevant parts of the controller used for wiring, the constructor and Initialize().  Note that the constructor is called by Spring and that the Initialize method is called by the search view.

/// <summary>
/// Controller logic for search view.
/// </summary>
public class SearchController
{
   public static readonly string ResultsKey = "SearchController.ResultsKey";

   private ISearchView m_view;
   private IDataService m_dataService;
   private IStateProvider m_stateProvider;

   public SearchController(IDataService dataService, IStateProvider stateProvider)
   {
      m_dataService = dataService;
      m_stateProvider = stateProvider;
   }

   public SearchController Initialize(ISearchView view, bool firstTime)
   {
      m_view = view;

      if (firstTime)
         DefaultSearchForCurrentUser();

      return this;

   }
   // snip //
}

The plan is to have two different Spring configurations.  One for production and integration testing and the other one used for WatiN tests.  That way when WatiN drives Internet Explorer to verify the UI is working, it does so without ever touching the data service, only testing specific stubs are being used for the data service.  This is partly for speed but also because our data service implementation is not yet complete.

*I know… very sad.  My last ASP.NET project was using .NET 2.0, and that was a year and a half ago when that was started.

ASP.NET | IoC
Saturday, March 17, 2007 5:29:16 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Saturday, October 21, 2006
Since I took the day off to prepare for our family cruise to Mexico tomorrow, I ran into an extra hour today to do with as I please.  Having wanted to play around with Atlas (now ASP.NET AJAX) and Castle ActiveRecord for a while, why not cram them both into my free hour today.  So I did exactly that, and I still have 10 minutes to spare to write this blog entry - and that includes the downloading of the software, installation, and reading some of the docs.  Although it has become painfully obvious to me that I need a new PC as I think I wasted a good 5 to 10 minutes waiting for my PC to do "stuff."

I was amazed at how dumb foundingly easy both these technologies make it to throw together a featureful web app.  First I created a database table:

CREATE TABLE GalleryUser (
    GalleryUserId            int IDENTITY(1, 1) PRIMARY KEY,
    FullName                varchar(50) NOT NULL,
    Login                    varchar(16) NOT NULL,
    CreatedDate                smalldatetime NOT NULL DEFAULT GETDATE()
)

then I went about creating a GalleryUser class marked up with ActiveRecord attributes.  The attributes mapped my class properties to the table I just created above.

using System;
using Castle.ActiveRecord;

namespace Sneal.Gallery.Core
{
    [ActiveRecord("GalleryUser")]
    public class GalleryUser : ActiveRecordBase
    {
        private int id;
        private string fullName;
        private string login;
        private DateTime createdDate;

        public GalleryUser() { }

        [PrimaryKey(PrimaryKeyType.Native, "GalleryUserId")]
        public int Id
        {
            get { return id; }
            set { id = value; }
        }

        [Property("FullName")]
        public string FullName
        {
            get { return fullName; }
            set { fullName = value; }
        }

        [Property("Login")]
        public string Login
        {
            get { return login; }
            set { login = value; }
        }

        [Property("CreatedDate")]
        public DateTime CreatedDate
        {
            get { return createdDate; }
            set { createdDate = value; }
        }

        public static GalleryUser[] FindAll()
        {
            return (GalleryUser[])FindAll(typeof(GalleryUser));
        }

        public static GalleryUser Find(int id)
        {
            return (GalleryUser)FindByPrimaryKey(typeof(GalleryUser), id);
        }
    }
}

Next I created a Global.asax to handle my ActiveRecord initialization and NHibernate session per request schtuff.  This particular part (including my web.config mods) is where I spent the bulk of my time with ActiveRecord.

using System;
using System.Web;
using System.Web.Security;
using System.Configuration;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework;
using Sneal.Gallery.Core;

namespace Sneal.Gallery.WebUI
{
    public class Global : System.Web.HttpApplication
    {
        public Global()
        {
            this.BeginRequest += new EventHandler(Global_BeginRequest);
            this.EndRequest += new EventHandler(Global_EndRequest);
        }

        protected void Application_Start(Object sender, EventArgs e)
        {
            IConfigurationSource source = ConfigurationSettings.GetConfig("activerecord") as                      IConfigurationSource;
            ActiveRecordStarter.Initialize(source, typeof(GalleryUser));
        }

        public void Global_BeginRequest(object sender, EventArgs e)
        {
            HttpContext.Current.Items.Add("nh.sessionscope", new SessionScope());
        }

        public void Global_EndRequest(object sender, EventArgs e)
        {
            try
            {
                SessionScope scope = HttpContext.Current.Items["nh.sessionscope"] as SessionScope;
                if (scope != null)
                {
                    scope.Dispose();
                }
            }
            catch (Exception ex)
            {
                HttpContext.Current.Trace.Warn("Error", "EndRequest: " + ex.Message, ex);
            }
        }

    }

}

Now for the fun ASP.NET AJAX hookup in my ASPX page.  This is where all of the AJAX magic happens, specially in the Update Panel.  I could have thrown all of the controls into the UpdatePanel and then I wouldn't have needed to add the explicit AsyncPostBackTrigger reference, but hey - I wanted to see how it works.  Having used Telerik's AJAX framework, which is quite good by they way, I found the ASP.NET AJAX framework even easier.  It's so well integrated now into ASP.NET and Visual Studio, I can't imagine anyone not using it going forward.  It won't be cool to use AJAX next year, it will just be the de facto standard.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>ASP.NET AJAX Test Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" />
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <asp:ListBox ID="lstUsers" runat="server" />
            </ContentTemplate>
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="btnGetUsers" />
            </Triggers>
        </asp:UpdatePanel>
        <asp:Button ID="btnGetUsers" runat="server" Text="Get users from DB"
            OnClick="btnGetUsers_Click" />       
        <asp:TextBox ID="txtInfo" runat="server" EnableViewState="false" />           
    </form>
</body>
</html>

And the associated code behind, which looks just like it would without the AJAX magic.  The fun part to note here is the data fetching; just too easy!  So easy in fact I just skipped following any decent application patterns like MVP, but hey its just a tech spike.

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using Sneal.Gallery.Core;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack)
            txtInfo.Text = "IsPostback = true";
        else if (IsCallback) // doesn't work as hoped.
            txtInfo.Text = "IsCallback = true";
        else
            txtInfo.Text = "Initial page load = true";
    }

    protected void btnGetUsers_Click(object sender, EventArgs e)
    {
        GalleryUser[] users = GalleryUser.FindAll();

        foreach (GalleryUser user in users)
        {
            lstUsers.Items.Add(user.FullName);
        }
    }
}

In the end, I'm very happy with the results.  And to my surprise my spike app worked on the first run.

My only issue is that I'm not sure I like ActiveRecord and its static data access methods on the actual objects.  It seems like it's crossing the boundaries of separation of concern, but then again it didn't cost me any coding time...  I'll have to think about it.

Friday, October 20, 2006 11:23:08 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  |