Exceptions? Errors? In my app_offline.htm? (Gotchas and tips!)

So, app_offline.htm isn’t a fullproof as we’d like. But just to catch up a few people who might be lost already…

What is it? How does it work?

The general idea is that you make a plain-jane html file, name it app_offline.htm, and drop it in the root directory of your ASP.NET website in IIS. IIS automagically knows that if it sees this file, that it should serve it for ALL requests. That way, when you’re doing maintenance, you can have a nice pretty message instead of an application in flux. Great idea right? Yes, it is! Too bad it doesn’t work 100% straightforward that way.

My current understanding (as in, probably wrong) is that IIS only checks for this when serving up ASP.NET content. Which I guess means that IIS isn’t actually doing it at all, but some ASP.NET framework gobbledegook is at some point. This has some pretty serious differences, the two I’ve ran into being that it’s not going to do this for static content requests, and that you have to actually get ASP.NET loaded before this file can be served.

Why I’m running into it

I’ve been working on automated build and deployment scripts for some website applications at work. I have it plugged into our CI server, and we do a lot of QA and UA on the destination environments (note: UA at this stage is done by our internal Product Owner, not an outside client). Since those environments will obviously get messed with whenever we check-in code changes, showing them a nice “under maintenance” to let them know what’s up is a good idea. They know how these servers work, but at least this way they don’t think they uncovered some bug that caused the site to start throwing errors.

We cranked out a quick app_offline.htm, and I worked it into the deploy script. The deployment script works in these steps:

  1. Copy over the app_offline.htm
  2. Delete everything else in the virtual directory (this is done so old pages/usercontrols/files/css/image/etc don’t stay around)
  3. Copy over the new build
  4. Delete app_offline.htm

However, when trying to access the site during this, most of the time you’d just get exceptions. WOW THAT’S WORKING GREAT!

How to avoid issues

First off – since the app_offline.htm done by the ASP.NET framework, it still has to be able to complete initialization. What this means is that if you have a global.asax defined in your web application (and you probably do), it still has to be able to load properly. And if you’re deleting your /bin/ folder before global.asax, then your global.asax can’t finish loading. BAM! Exceptions. This also is important when copying items back over. So, I modified my scripts to delete global.asax FIRST, then everything else. When I’m copying the new build over, I copy /bin/ first, then everything else.

But there’s more!

You’re probably also using a default document. Default.aspx is the one that’s probably actually in your project. If your site’s home page doesn’t have a specific actual page (like 99% of websites), users could just be on http://mysite.com/, and get the “Virtual listing not denied”. Why? Because you’ve deleted Default.aspx, so the ASP.NET processing isn’t happening, so your user gets the “virtual listing not allowed” error. You could add app_offline.htm to the default document rule, but that’s kind of crappy. The real solution here is not to delete Default.aspx. Instead, just copy over it.

My final deployment process

So, now it looks like this:

  • Copy over the app_offline.htm
  • Delete global.asax
  • Delete everything but Default.aspx and web.config (I know those files will ALWAYS exist – so I’m still cleaning out old garbage, but not getting nasty errors)
  • Copy over new /bin/ contents
  • Copy over the rest of the new build
  • Delete app_offline.htm
  • And now I’ve greatly reduced the change for a user to get nasty errors. Why only greatly reduced? Because if they were trying to get to static content, they’ll STILL get 404 (if the old file hasn’t been copied over yet), since the ASP.NET framework isn’t invoked. The solution for that? IIS7 and the integrated pipeline. You’ll want to add this to your web.config:

    <?xml version="1.0"?>
      <configuration>
        <system.webServer>
          <modules runAllManagedModulesForAllRequests="true" />
        </system.webServer>
      </configuration>

    Which is just yet another reason to only copy over your web.config, and not delete it. This setting tells IIS now that ANY requests in this virtual directory should fire the ASP.NET pipeline. However, our servers are IIS6, so we cannot take use of this feature. It’s low risk for us, so it’s not a large deal, but for those of you using IIS7 this is a nice thing to add, especially if you’re using MVC since none of your URLs directly link to an aspx page anyways.

    Hope you find this information useful!

    Accessing custom data interfaces in ASP.NET WebForms

    Yeah, this is a pain.

    So, you want to use “out of the box” controls like a listview, or gridview, etc. You have a custom interface for getting your data, let’s say something like:

    public interface IGetData
    {
         IEnumerable<MyData> GetAll(int pageSize, int page, string sort);
    }
    public partial class MyPage : Page
    {
      public IGetData MyData {get;set;}
    }

    That’s a pretty basic interface. You have a large database, and you need to very specifically control how paging and sorting is done (no returning everything, and sorting in memory, especially). Unfortunately, if you want to display that information with the standard webform controls, you will run into MANY issues. All your sort/paging commands will be jacked up, and attempts at manually setting what page number you’re on will leave you smashing your head into a wall. A wall with spikes (hooray for read-only properties…).

    (To be honest here, I tackled this nonsense a few months back, and don’t remember the specifics of the trouble I ran into, so feel free to try it anyways!)

    The way ASP.NET wants you to do it, is to have your (let’s say ListView) point to an object datasource. After fighting these things, I can say that object datasources are complete garbage. But, here we are anyways. Here’s what you’ll need to do to get it work correctly.

    You’ll create your ListView, and your ObjectDatasource. Have your ListView’s DataSourceID set to the name of your ObjectDatasource. Then you’ll set various properties on the datasource for what functions to call at certain events. The overall way how the ObjectDatasource is going to work? It’s going to create it’s own f’in instance of your control/page, and call those functions. Yes. You heard me correctly. That means any controls/data on your page are USELESS. Since your ObjectDatasource has it’s own instance, those values won’t exist. Awesome, right! So how do we set this up anyways? Like so…

    On your ObjectDatasource, you’ll want to set the following properties:

    • MaximumRowsParameterName, SortParameterName, StartRowIndexParameterName – These are optional. If set, when the ObjectDatasource uses reflection to search for it’s select function, it’ll look for parameters with those names.
    • SelectMethod – A name of a function in your codebehind, that has parameters equal to what you have set (in markup or codebehind) to your SelectParameters as well as the parameters listed above
    • SelectCountMethod – Function with the same parameters as SelectMethod, minus paging and sorting parameters.
    • TypeName – FQDN of your page or control
    • EnablePaging – Set to true if paging is enabled. Obvious at least.
    • OnObjectCreated – This is the bastard one. This is an event that you’ll want to wire into.

    So, we’ve set up our ListView and set up our ObjectDatasource with a bunch of functions that don’t do anything. The first thing that you’ll notice is a different function for Select and SelectCount. We don’t want to actually have to search our database twice. The good news is that Select is called first, so I made a tiny custom object to hold the records we’re going to show, as well as the total count. I modified my interface to return this object instead, as well.

    public class DataControlResults<T>
    {
       public IEnumerable<T> Data { get; set; }
       public int TotalRecords { get; set; }
    }

    Now, we, can make a private field on our page (let’s define it as “private int _totalRecords”), and we call our Select method, we’ll store it.

    public List<MyData> dsItems_Select(int pageSize, int startRow, string sortColumn)
    {
      var results = MyData.GetAll(pageSize, (int)(startRow/pageSize + 1), sortColumn);
    
      var listData = new List<MyData>();
      listData.AddRange(results.Data);
    
      _totalRecords = results.TotalRecords;
    
      return listData;
    }

    Again, unfortunately, we have to do a little wonkiness with the paging. I prefer page and pageSize, but the data source uses starting row and page size. Oh well.

    Now, our SelectCount just returns _totalRecords, so easy enough there that I can skip formatting code!

    However, if you, like most people, are using a DI/IoC framework, your actual concrete implementation of your interface will be null! Most frameworks, when in the ASP.NET world, are instantiating those properties in your global.asax, or perhaps a custom Page base control, etc. Because the ObjectDatasource, though, is creating a copy of your page/control through Activator.CreateInstance, that’s all gone! SWEET! (That’s sarcasm, by the way.) So, that’s where the OnObjectCreated event is used.

    protected void dsItems_ObjectCreated(object sender, ObjectDataSourceEventArgs e)
    {
      var newObj = e.ObjectInstance as MyPage
    
      newObj.MyData = MyData;
    }

    There ya go, now it’ll actually have a facade to use. Sigh.

    At the end of the day, it works pretty simply, but I originally just wanted to throw up a ListView control, capture the paging/sorting events from the ListView (triggered from Commands from objects displayed in the ListView), and manually call my facade with proper paging/sorting/search parameters as needed. Then I’d just bind the results to the ListView.DataSource property, and be set. But that really just isn’t how they intend on you using these controls. They really want you to use some DataSource control, which unfortunately adds nothing but more noise to your markup and code-behind. If you want to have Updates and such as well, it’s the same pattern again.

    Hopefully this approach can help someone else who wants control over their data access in their webform project! Thank god our next project is MVC!

    Designed by Posicionamiento Web | Bloggerized by GosuBlogger