Monday, August 22, 2005

A while back, I showed you my file-based viewstate persistence solution.  Thanks to Google search hits and traffic from Scott Hanselman's analysis, it's been one of my more popular entries.  With ASP.net's improvements in this area, I felt that it was due for an update.  So, here's a quick whack at it.  The usual disclaimers apply.

2.0 adds the notion of "control state" to state persistence, which is very cool.  It's an opt-in mechanism for things that need to survive postbacks even if ViewState is turned off.  In addition, there's some new flexibility with page adapters and such, but we'll ignore that complexity for now and go for a direct port of my old sample, but include the new ControlState mechanism.

public class FileBasedPageStatePersister : HiddenFieldPageStatePersister {

      public FileBasedPageStatePersister(Page page) : base(page) {}

 

      public override void Load() {

            //let the base class do its thing

            base.Load();

            //get the control state

            object baseControlState = base.ControlState;

            if (baseControlState != null) {

                  //the control state should be our Guid

                  Guid guid = (Guid)baseControlState;

                  //read the contents of the file and set the two states

                  using (TextReader reader = new StreamReader(CreateOfflineViewStateFilePath(guid))) {

                        Pair pair = this.StateFormatter.Deserialize(reader.ReadToEnd()) as Pair;

                        base.ViewState = pair.First;

                        base.ControlState = pair.Second;

                  }

            }

      }

 

      public override void Save() {

            //create a guid for this viewstate

            Guid guid = Guid.NewGuid();

 

            //serialize the states into a temp file

            using (TextWriter writer = new StreamWriter(CreateOfflineViewStateFilePath(guid))) {

                  Pair pair = new Pair(base.ViewState, base.ControlState);

                  writer.Write(this.StateFormatter.Serialize(pair));

            }

            //trick the normal system into thinking all it needs to save is the guid

            base.ControlState = guid;

            base.ViewState = null;

            base.Save();

      }

 

      string CreateOfflineViewStateFilePath(Guid guid) {

            //TODO: put these files whereever you like

            return Path.Combine(Path.GetTempPath(), string.Format("{0}.viewstate", guid));

      }

 

}

So, we immediately see that it's much shorter.  This is because ASP.net uses a mechanism very similar to my 1.1 solution, so alot of the plumbing is built-in. The only thing you need to do to use it is override the PageStatePersister property on Page and return one of these. Again, we're piggybacking on the "hidden field" persistence mechanism and using that to store our Guid for the request.  Not much different, and I'm pretty happy to say that converting to this model from my old is very simple.

Another interesting idea would be to leave the ControlState in the hidden field, and only store the ViewState in the file. it would be a simple change that I'll leave as an exercise to you.  Then, you could be very aggresive about purging old or large files without worrying about breaking anything (provided of course that you've made your controls tolerant to such a method).

posted on Monday, August 22, 2005 1:21:14 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]
 Saturday, August 20, 2005

My friend Peter and I often use Remote Assistance to collaborate on projects.  He and I were doing some things today in Visual Studio, and while I was watching, a beautiful window appeared that showed the open files and windows and such, complete with appropriate icons. "Whoa!", I said, "What was that?!"  "What", he replied, "this?"  The window re-appeared.  Turns out, it was the Ctrl-Tab window.  For some reason, I never even thought of using it to browse the open windows. I always assumed I had to navigate the frightful tabbed interface, which just might actually be usable with that little trick.

I felt better when I typed in a type name and Intellisense didn't recongnize it.  A quick right-click->Resolve instantly added the appropriate using statement.  Peter hadn't seen that feature.  Although I guess Ctrl-Tab's been a standard for much longer than Resolve.

posted on Saturday, August 20, 2005 11:26:33 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]
 Thursday, August 18, 2005

This past week, I've found myself writing alot of methods with the following pattern:

IEnumerable<T> DoSomething(IEnumerable<T> stream);

Then I use C#'s wonderful new iterator syntax to manipulate the stream, or simply intercept and process the data.  I've found this wonderfully useful for alot of the statistical type algorithms that I implement for our project.  It's been especially cool to combine this with a bit of reflection for creating "dynamic filters" based on user input.  All of a sudden, all your algorithms are incredibly flexible with very little effort.

The beauty of this pattern is that it can be chained.  Obviously, there is a practical limit here, but I've found it extremely useful and I thought I'd share it for those who haven't stumbled across this concept before.

Now, if we could only create iterators using anonymous delegates, then I would proceed to drown in my own drool.

[UPDATE] fixed title grammar. Sorry if that pops it back up in your aggregators, RSS readers.

posted on Thursday, August 18, 2005 12:29:34 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]
 Wednesday, August 17, 2005

I blogged a few days ago about a crazy problem I had with garbage collection in ASP.net under 2.0.  I finally tracked the problem down to Array.Sort.  I created a framework for working with large sets of data in a dynamic way.  This allows us to group data and do other complex statistical things based on fairly open-ended user input, rather than having to write special case code for each scenario.  It uses some reflection to be able to drill into objects.  When sorting, we pass in a dynamic IComparer that can drill into each object and do dynamic comparisons.  Unfortunately, many of our algorithms were dependent on sorting the data first.  This was causing a huge number of allocations due to drilling in and boxing lots of value types.  And, the nature of the sort causes that to happen multiple times per item.

Under 1.1, we took the hit on memory.  It didn't take that long, and it was soon collected.  Under 2.0, the GC seems to recognize this allocation pattern early and proactively begins collecting aggresively.  The problem seems to be that this slows down the sort tremendously, to the point where it essentially comes to a halt.  After a long time, you get a bizarre looking exception about a failed sort from deep in the framework.

What I can't figure out is, during all this time, there's still plenty of memory, and activity on other requests is not impacted horribly.

Well, I took a long hard look at some of the operations, and re-implemented them with a hashing strategy rather than relying on a sort.  As it turns out, for most cases, this is much more efficient.  Long story short, overnight stress testing turned up with clean results this morning.

posted on Wednesday, August 17, 2005 6:37:49 AM (Pacific Standard Time, UTC-08:00)  #    Comments [1]
 Sunday, August 14, 2005

I've talked about church bulletin humor before. Today, I experienced it first hand. Our church publishes the Wednesday night meal menu in the bulletin. I suppose this is so families can plan around it. I don't think anyone is going to have the church dinner this Wednesday. It read:

Menu: Poopyseed Chicken over rice, vegetable, rolls & dessert.

It was all I could do to keep from falling out of the pew laughing.

posted on Sunday, August 14, 2005 3:50:05 PM (Pacific Standard Time, UTC-08:00)  #    Comments [2]
 Friday, August 12, 2005

This is awesome.  Joe Duffy reveals what I had suspected.  A change to the CLR that will fix the issues I've previously discussed about nullable types in version 2.0!  From his blog:

The core of this change is that the IL box instruction has been modified to recognize Nullable<T>s. For non-Nullables, behavior remains the same; but upon seeing one, it inspects its HasValue property. If HasValue is true, box peeks inside the structure, extracts the T value, and boxes that instead; otherwise, box simply leaves behind a null reference. Obviously, unbox has also been changed to allow nulls to be unboxed back into Nullable<T> structures. This had a rippling effect in the CLR codebase and also required changes to late-bound semantics to mimic the static case.

This is fantastic, and reveals just how strong Microsoft's commitment is to the development community.  I gave my feedback on this before.  I felt it was a problem that aware developers could understand and live with, but I felt that novice developers would struggle with it, and ultimately it would make the feature, and the platform less understandable and approachable.

As you can tell from my earlier post, I had pretty much decided that MS would be unable to fix it at this late stage in the game, and that would be a shame.  But, thanks to some good decision making, they've done it.  Also, the solution was quite similar to my suggestion, I'm happy to say.

Be sure to follow through and read Somasegar's post on the subject.

posted on Friday, August 12, 2005 10:04:23 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]
 Thursday, August 11, 2005

We're migrating an app to 2.0, and I've come across a rather bizarre behavior.  Basically, after running for a while, the app will begin taking up 100% of one CPU.  Perf counters would indicate that the process is in GC (% Time in GC is very high).

Now, some background.  This is an ASP.net app. We've got very aggresive caching such that the static memory footprint is about 600MB. When things first get going, everyting behaves wonderfully.  Then, at some point, the GC gets hungry or something and starts chewing up cycles.  The heaps never go down.  No allocations are being made.  Nothing tangible seems to be going on.

When requests comes in, they are handled normally, and the GC seems to "get out of the way".  (DB wait time is accompanied by 0% CPU) But after the request is completed, it's back up to 100%.

This app worked fine under 1.1.  And I guess there's an argument that says it still does.  It just doesn't play nice with anything else on the system.  I'm just kind of writing this to get this problem out there in case anyone else is seeing it and is searching for a solution.  I'll probably also ping a few folks at MS to see what they have to say.

[UPDATE] Check out this update.

posted on Thursday, August 11, 2005 10:46:20 AM (Pacific Standard Time, UTC-08:00)  #    Comments [1]
 Friday, August 05, 2005

I finally got around to upgrading the ol' blog. I was just getting too much trackback spam.

Sometime I'll get around to customizing this new theme. I was getting tired of the old one.

posted on Friday, August 05, 2005 4:00:20 PM (Pacific Standard Time, UTC-08:00)  #    Comments [1]