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!

0 comments:

Designed by Posicionamiento Web | Bloggerized by GosuBlogger