web comments edit

Jeff Atwood posts about the exact thing I thought when I saw the MVC tidal wave coming to overtake web forms. It feels like I’m back in the 90’s, doing old-school ASP, because of the tag soup.

Don’t get me wrong - I’m all about separation of concerns and such, but every time I look at something like MVC, I see all roads leading to web forms. Maybe different web forms, but web forms nonetheless.

  • Too much tag soup? Package stuff up in helper methods.
  • Oh, wait, the helper methods that generate control structures aren’t really flexible enough to allow overridden behaviors or wire things up (like validation). Let’s make them instance methods on objects.
  • Hmmm, there’s sort of an object lifetime to manage here, and wouldn’t it be nice if the packaged widget could be a little smarter about handling view data? Like dynamically populating itself based on data and letting me know if the user changed stuff, because writing that every time is such a pain.
  • Hang on, that’s web server controls.

Some folks argue they want that “tighter control over the HTML” that the tag soup provides. I, personally, am much less interested in hacking HTML. Let the framework do it for me. I’m more concerned with the business logic anyway.

I’ve done my share of classic ASP style development in lots of different server side languages, and it always ends up that to get rid of the tag soup, you head towards web forms.

Since my birthday falls in the middle of the week this year, we’re sort of splitting up the festivities across weekends - some this past weekend, some this weekend.

Friday night I started out by playing some GRAW 2 co-op with my dad and uncle. We normally spend a lot of time struggling through just one of the missions (none of us are super-good and some of the missions really need four or five people), but this time we beat two in one night. My uncle came out with the medal of honor on this one, finishing out the mission after my dad and I had both gotten killed.

Saturday morning I went to the DMV and got my license renewed and actually got out far sooner than I thought I would. In the afternoon we saw The Dark Knight, and it was awesome. Heath Ledger was amazing as the insane Joker and really made him a believeable bad guy, not the cartoonish villain you might envision with the Joker. I hope they make more Batman in this style, and maybe adopt it for future super hero movies. Comic movies don’t have to be all cartoonish.

Sunday we went to the driving range with our friends K and Angela to have lunch and hit a bucket of balls. It was a beautiful day out and we had a great time. I’d never hit a golf ball before in my life, so this was a fun intro, even though I didn’t do too well. Most of my shots went about 40 or 50 yards, though I had a few that went… a bit shorter than that… and some that darn close to injured people. My furthest went about 140 yards. K put all of us to shame going way past everything we did.

K said we might be sore today, but I’m not doing too bad. My back is a little tight and the inside of my right arm is kind of sore (so I can’t really grip things or pick up heavy stuff on the right), but other than that, I’m doing well. I think Jenn is mostly feeling it in the back. Regardless, it was a heck of a fun time and we’ll definitely be trying that again.

We stopped by on the way home at Baskin-Robbins, had some ice cream, and finished off the day with a barbecue at our place. Someone was outside cooking something delicious smelling and we couldn’t resist - we had to cook our own.

This coming weekend I think we’re going to hit the Washington County Fair so we can see the Pirate’s Parrot Show and maybe have a funnel cake. Since it’s free admission, we may even go Friday night and Saturday. It’s a lot of fun and only a few minutes away from home.

gists, net, csharp comments edit

You really have to be careful when you use generic types. Say you have a generic type Foo<T> like this:

public class Foo<T>
{
  public static string StaticValue = "Unset.";
}

Fine, right? Well, what happens if you do this?

Console.WriteLine("Foo<string>: {0}", Foo<string>.StaticValue);
Console.WriteLine("Foo<int>: {0}", Foo<int>.StaticValue);
Foo<string>.StaticValue = "String value.";
Foo<int>.StaticValue = "Integer value.";
Console.WriteLine("Foo<string>: {0}", Foo<string>.StaticValue);
Console.WriteLine("Foo<int>: {0}", Foo<int>.StaticValue);

I bet you can guess what the output is.

Foo<string>: Unset.
Foo<int>: Unset.
Foo<string>: String value.
Foo<int>: Integer value.

That’s right - each closed generic type (a generic that has a type parameter provided) has its own set of static values.

Think about how that can bite you - if you have multiple parameters on your generic type, like MyType<T, U, V>, and you have three different types that are used in each of T, U, and V, you end up with 27 sets of static values (it’s combinatoric). Possibly not what you’re thinking at the time you write the code.

I really can’t find any documentation on this. There’s a nice CodeProject article about it that shows why this can really work against you if you’re trying to make a strongly-typed generic singleton factory, but beyond that, MSDN really only yields up an FxCop rule that’s fairly unrelated.

Be careful with your generics and static values!

gists, aspnet, csharp, net comments edit

While working on solving a CR_Documentor known issue, I realized I needed to have some sort of embedded web server running so I could serve up dynamically generated content. I didn’t need a full ASP.NET stack, just something I could pipe a string to and have it serve that up so a hosted IE control would be getting content from a “live server” rather than from a file on disk or from in-memory DOM manipulation… because both of those latter methods cause security warnings to pop up.

I looked at creating a dependency on the ASP.NET development server, WebDev.WebServer.exe, or the assembly that contains the guts of that, WebDev.WebHost.dll, but both of those only get installed in certain configurations of Visual Studio (I think it’s when you install the Visual Web Developer portion) and I couldn’t really assume everyone had that. Paulo Morgado then pointed me to HttpListener, and let me tell you, that’s a pretty sweet solution.

Here’s a very simple web server implementation that uses HttpListener. You handle an event, provide some content for the incoming request, and that’s what the response content is. It doesn’t read files from the filesystem, it doesn’t do auth, it doesn’t do ASP.NET… it’s just the simplest of simple servers, which is exactly what I need for CR_Documentor.

UPDATE 2/2/2009: I found that the super-simple way I had things caused some interesting and unfortunate race conditions which meant things occasionally locked up for no particular reason. As such, I’ve updated the code sample to use events to handle incoming requests and show the listener running on a separate thread. It’s still pretty simple, all things considered, and I have it up and running in CR_Documentor.

using System;
using System.Globalization;
using System.Net;
using System.Threading;

namespace HttpListenerExample
{
  public class WebServer : IDisposable
  {
    public event EventHandler<HttpRequestEventArgs> IncomingRequest = null;

    public enum State
    {
      Stopped,
      Stopping,
      Starting,
      Started
    }

    private Thread _connectionManagerThread = null;
    private bool _disposed = false;
    private HttpListener _listener = null;
    private long _runState = (long)State.Stopped;

    public State RunState
    {
      get
      {
        return (State)Interlocked.Read(ref _runState);
      }
    }

    public virtual Guid UniqueId { get; private set; }

    public virtual Uri Url { get; private set; }

    public WebServer(Uri listenerPrefix)
    {
      if (!HttpListener.IsSupported)
      {
        throw new NotSupportedException("The HttpListener class is not supported on this operating system.");
      }
      if(listenerPrefix == null)
      {
        throw new ArgumentNullException("listenerPrefix");
      }
      this.UniqueId = Guid.NewGuid();
      this._listener = new HttpListener();
      this._listener.Prefixes.Add(listenerPrefix.AbsoluteUri);
    }

    ~WebServer()
    {
      this.Dispose(false);
    }

    private void ConnectionManagerThreadStart()
    {
      Interlocked.Exchange(ref this._runState, (long)State.Starting);
      try
      {
        if (!this._listener.IsListening)
        {
          this._listener.Start();
        }
        if (this._listener.IsListening)
        {
          Interlocked.Exchange(ref this._runState, (long)State.Started);
        }

        try
        {
          while (RunState == State.Started)
          {
            HttpListenerContext context = this._listener.GetContext();
            this.RaiseIncomingRequest(context);
          }
        }
        catch (HttpListenerException)
        {
          // This will occur when the listener gets shut down.
          // Just swallow it and move on.
        }
      }
      finally
      {
        Interlocked.Exchange(ref this._runState, (long)State.Stopped);
      }
    }

    public virtual void Dispose()
    {
      this.Dispose(true);
      GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
      if (this._disposed)
      {
        return;
      }
      if (disposing)
      {
        if (this.RunState != State.Stopped)
        {
          this.Stop();
        }
        if (this._connectionManagerThread != null)
        {
          this._connectionManagerThread.Abort();
          this._connectionManagerThread = null;
        }
      }
      this._disposed = true;
    }

    private void RaiseIncomingRequest(HttpListenerContext context)
    {
      HttpRequestEventArgs e = new HttpRequestEventArgs(context);
      try
      {
        if (this.IncomingRequest != null)
        {
          this.IncomingRequest.BeginInvoke(this, e, null, null);
        }
      }
      catch
      {
        // Swallow the exception and/or log it, but you probably don't want to exit
        // just because an incoming request handler failed.
      }
    }

    public virtual void Start()
    {
      if (this._connectionManagerThread == null || this._connectionManagerThread.ThreadState == ThreadState.Stopped)
      {
        this._connectionManagerThread = new Thread(new ThreadStart(this.ConnectionManagerThreadStart));
        this._connectionManagerThread.Name = String.Format(CultureInfo.InvariantCulture, "ConnectionManager_{0}", this.UniqueId);
      }
      else if (this._connectionManagerThread.ThreadState == ThreadState.Running)
      {
        throw new ThreadStateException("The request handling process is already running.");
      }

      if (this._connectionManagerThread.ThreadState != ThreadState.Unstarted)
      {
        throw new ThreadStateException("The request handling process was not properly initialized so it could not be started.");
      }
      this._connectionManagerThread.Start();

      long waitTime = DateTime.Now.Ticks + TimeSpan.TicksPerSecond * 10;
      while (this.RunState != State.Started)
      {
        Thread.Sleep(100);
        if (DateTime.Now.Ticks > waitTime)
        {
          throw new TimeoutException("Unable to start the request handling process.");
        }
      }
    }

    public virtual void Stop()
    {
      // Setting the runstate to something other than "started" and
      // stopping the listener should abort the AddIncomingRequestToQueue
      // method and allow the ConnectionManagerThreadStart sequence to
      // end, which sets the RunState to Stopped.
      Interlocked.Exchange(ref this._runState, (long)State.Stopping);
      if (this._listener.IsListening)
      {
        this._listener.Stop();
      }
      long waitTime = DateTime.Now.Ticks + TimeSpan.TicksPerSecond * 10;
      while (this.RunState != State.Stopped)
      {
        Thread.Sleep(100);
        if (DateTime.Now.Ticks > waitTime)
        {
          throw new TimeoutException("Unable to stop the web server process.");
        }
      }

      this._connectionManagerThread = null;
    }
  }

  public class HttpRequestEventArgs : EventArgs
  {
    public HttpListenerContext RequestContext { get; private set; }

    public HttpRequestEventArgs(HttpListenerContext requestContext)
    {
      this.RequestContext = requestContext;
    }
  }
}

With this simple wrapper, you can new-up a web server instance, start it listening for requests, and handle the IncomingRequest event to serve up the content you want. Dispose the instance and you’re done. Here’s what it looks like in a simple console program host:

using System;
using System.IO;
using System.Net;
using System.Text;

namespace HttpListenerExample
{
  class Program
  {
    static void Main(string[] args)
    {
      const string ServerTestUrl = "http://localhost:11235/";
      using (WebServer listener = new WebServer(new Uri(ServerTestUrl)))
      {
        listener.IncomingRequest += WebServer_IncomingRequest;
        listener.Start();
        Console.WriteLine("Listener accepting requests: {0}", listener.RunState == WebServer.State.Started);
        Console.WriteLine("Making requests...");
        for(int i = 0; i < 10; i ++)
        {
          HttpWebRequest request = (HttpWebRequest)WebRequest.Create(ServerTestUrl);
          HttpWebResponse response = (HttpWebResponse)request.GetResponse();
          using(Stream responseStream = response.GetResponseStream())
          using(StreamReader responseStreamReader = new StreamReader(responseStream, Encoding.UTF8))
          {
            Console.WriteLine(responseStreamReader.ReadToEnd());
          }
          System.Threading.Thread.Sleep(1000);
        }

      }
      Console.WriteLine("Done. Press any key to exit.");
      Console.ReadLine();
    }

    public static void WebServer_IncomingRequest(object sender, HttpRequestEventArgs e)
    {
      HttpListenerResponse response = e.RequestContext.Response;
      string content = DateTime.Now.ToLongTimeString();
      byte[] buffer = Encoding.UTF8.GetBytes(content);
      response.StatusCode = (int)HttpStatusCode.OK;
      response.StatusDescription = "OK";
      response.ContentLength64 = buffer.Length;
      response.ContentEncoding = Encoding.UTF8;
      response.OutputStream.Write(buffer, 0, buffer.Length);
      response.OutputStream.Close();
      response.Close();
    }
  }
}

I’m using this approach in CR_Documentor to serve up the content preview and maybe augment it a bit later so I can also serve images from embedded resources and such, making the preview that much richer and more accurate.

Saturday was my 12th treatment on my face, and the last in my second set of six (they sell treatments in blocks of six).

A lot has changed since the first treatment, so since I re-upped for a third set of six, I thought it would be time for a retrospective.

  • It still hurts, but it’s nothing like the first time I went. I get MeDioStar on my entire face now, and it’s not nearly as bad because the hair is so much thinner. Reading back on that first entry, I totally remember how much apprehension I felt before going back for the second treatment. I don’t feel that anymore. (There really is no way you can prepare someone for it, so I think a lot of my reaction was that my pain-related expectations were vastly different than reality. Whattaya gonna do?)
  • I still really like all of the people at the clinic. Everyone from the folks at the front desk to the technicians and the sales people are super nice. They all remember my name, they all make me feel totally welcome, and they’re super easy to get along with. I’m glad I chose them.
  • I thought I’d be a closer to done than I am, but then, I was supposed to have done 12 MeDioStar treatments by now and I haven’t. I spent a lot of time doing just Dermo Flash to thin things out. It was the right thing to do, but it’s taking a while.
  • I really like the results so far. My hair grows differently on my face - I pretty much have to shave against the grain to get a good shave - but I’ve also not destroyed any sheets, pillowcases, or shirts for quite some time.
  • The spots that are being stubborn are my chin, upper lip, a spot on my right cheek, and a spot on my neck. The rest has done really well. Even the stubborn spots are starting to give way, it’s just taking a while.

So I did my thing, got my second set of six, and I’m crusin’ along. I should probably take some before and after pix in a couple of weeks to see how well this next set of treatments goes.