Here is how it works:
Filter web part sends row of data to all other webparts on the page. It's control is rendered at load time, rendering the control selects which row is sent back to the other webparts on the page.
This causes the issue on the first page load where the other webparts will request the row from provider before it has finished loading and therefore has no information to provide yet.
The only solution (which is really ugly, slow and horrible) is to run all of the code that would be run in the control class the webpart uses in the webpart's constructor and use it to predict what values the control will have on the first run. This also leads to a whole bunch of issues with deploying that I really would rather avoid.
Here's the webpart code:
public class FilterProjectHeader : WebPart, IWebPartRow
{
// Visual Studio might automatically update this path when you change the Visual Web Part project item.
private const string _ascxPath = @"[link goes here]";
public DataRowView data;
public DataTable table;
private FilterProjectHeaderUserControl control;
public FilterProjectHeader()
{
//Code I want to avoid using:
//var web = SPContext.Current.Web;
//table = web.Lists["foo"].Items.GetDataTable();
//data = foo();
}
protected override void CreateChildControls()
{
control = Page.LoadControl(_ascxPath) as FilterProjectHeaderUserControl;
control.provider = this;
Controls.Add(control);
}
public PropertyDescriptorCollection Schema
{
get
{
return TypeDescriptor.GetProperties(table.DefaultView[0]);
}
}
[ConnectionProvider("Row")]
public IWebPartRow GetConnectionInterface()
{
return this;
}
public void GetRowData(RowCallback callback)
{
callback(data);
}
}
And for the control:
public partial class FilterProjectHeaderUserControl : UserControl
{
public FilterProjectHeader provider { get; set; }
private String _selectedValue;
//Both OnLoad and OnInit have the same result.
protected override void OnInit(EventArgs e)
{
//This is what gets run the first time:
if (!IsPostBack)
{
//Code here finds data then sends it back to webpart like this:
//All of the code in this method definitely does run; I have stepped
//through it and it works but it seems to happen too late to have any
//effect.
provider.data = item;
provider.table = profilesTable;
}
}
protected void filterDropDown_SelectedIndexChanged(object sender, EventArgs e)
{
//Post back method code exempted... it works.
provider.data = item;
provider.table = profilesTable;
}
So after a lot of time working with this, I found the issue is actually with what Microsoft recommends to do as best practice (they say to always use CreateChildControls to load controls onto the page).
CreateChildControls runs AFTER OnLoad when it is the first time a page is loading, but runs BEFORE OnLoad on a repost. This is why it works on reposts, but not on first page load.
Switching CreateChildControls to OnInit solves the problem, because OnInit will always run before OnLoad.