There seems to be a bug in the ReportViewer control. It deals with interface properties that are explicitly implemented in a concrete class. Take, for example, the following interface and concrete implementation:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ReportViewerDataBindProblem
{
public interface IPerson
{
string FirstName
{
get;
}
string LastName
{
get;
}
}
public class Person : IPerson
{
public Person(string firstName, string lastName)
{
_firstName = firstName;
_lastName = lastName;
}
private string _firstName;
public string FirstName
{
get { return _firstName; }
}
private string _lastName;
string IPerson.LastName
{
get { return _lastName; }
}
}
}
Notice that I implemented the FirstName property implicitly and the LastName property explicitly. If I use the IPerson Type in an Object Data Source and then bind a RDLC table against it everything seems to be fine. The properties for IPerson are available for selection as you might expect:
If I inspect the raw RDL, everything appears fine:
<Fields>
<Field Name="FirstName">
<DataField>FirstName</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
<Field Name="LastName">
<DataField>LastName</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
</Fields>
I can then add a ReportDataSource (containing a collection of IPerson) to the ReportViewer instance like:
List<IPerson> people = new List<IPerson>();
people.Add(new Person("Bob", "Barker"));
people.Add(new Person("Bill", "Gates"));
this.reportViewer1.ProcessingMode = ProcessingMode.Local;
this.reportViewer1.LocalReport.ReportEmbeddedResource = "ReportViewerDataBindProblem.Test.rdlc";
this.reportViewer1.LocalReport.DataSources.Add(new ReportDataSource("ReportViewerDataBindProblem_IPerson", people));
this.reportViewer1.RefreshReport();
However, when I render the report, the “Last Name” column does not contain any data:
This is a design flaw IMHO. I have not looked into this yet using Reflector but I have a guess as to what the problem is. The ReportViewer control is probably invoking the GetProperties method (from Type) on the concrete instance. I stress this because if you simply use BindingFlags.Public (the default for the GetProperties method) then you will obviously not see the explicitly implemented property (since it is considered NonPublic). The solution is to simply reflect on the type that is being databound (IPerson in this case) and not the concrete type. An instance of the concrete type can still obviously be passed to the databinding context (since it implements the interface). Isn’t that, after all, the point of interfaces?