PDA

View Full Version : Something strange with DataBinding


equiv
12-24-2005, 01:02 AM
I have a class 'Person' which contains a property 'ContactInfo' which contains a property 'Address' which contains properties such as 'City', 'State', etc. There are business classes for 'Name', 'ContactInfo' and 'Address', too.

I have a form with textboxes that are data binded to 'Person.Name.FirstName', 'Person.Identification', Person.ContactInfo.FaxNumber', 'Person.ContactInfo.Address.StreetAddress', and so on. For example:

[Binding("Text", "Person.Identification")]
protected System.Web.UI.WebControls.TextBox _textIdentification;

[Binding("Text", "Person.Name.FirstName")]
protected System.Web.UI.WebControls.TextBox _textFirstName;

[Binding("Text", "Person.ContactInfo.HomeNumber")]
protected System.Web.UI.WebControls.TextBox _textHomeNumber;

[Binding("Text", "Person.ContactInfo.Address.City")]
protected System.Web.UI.WebControls.TextBox _textCity;

Everything seems to work fine except for the 'ContactInfo' property which is always null after clicking the submit button.

The constructor of 'Person' initializes its 'Name' and 'ContactInfo' properties. The constructor of 'ContactInfo' initializes its 'Address'.

Why then is 'ContactInfo' null after a postback?

Aleks Seovic
12-26-2005, 09:56 AM
Beats me... Your definitions look just fine, and if ContactInfo is initialized properly within Person constructor there is no reason why it should be null.

Can you post full source for your page? I'd like to see how everything is initialized within the page.

Later,

Aleks

equiv
12-26-2005, 05:36 PM
public class ContactInfo
{
private Address _address;
private string _email;
private string _homeNumber;
private string _celNumber;
private string _workNumber;
private string _faxNumber;

public Address Address
{
get { return _address; }
set { _address = value; }
}
public string Email
{
get { return _email; }
set { _email = value; }
}
public string HomeNumber
{
get { return _homeNumber; }
set { _homeNumber = value; }
}
public string CelNumber
{
get { return _celNumber; }
set { _celNumber = value; }
}
public string WorkNumber
{
get { return _workNumber; }
set { _workNumber = value; }
}
public string FaxNumber
{
get { return _faxNumber; }
set { _faxNumber = value; }
}

public ContactInfo()
{
_address = new Address();
}
public override bool Equals(object obj)
{
...
}
public override int GetHashCode()
{
...
}
}

public class Address
{
private string _address;
private string _city;
private string _stateProvince;
private string _postalZipCode;
private string _countryCode;

public string StreetAddress
{
get { return _address; }
set { _address = value; }
}
public string City
{
get { return _city; }
set { _city = value; }
}
public string StateProvince
{
get { return _stateProvince; }
set { _stateProvince = value; }
}
public string PostalZipCode
{
get { return _postalZipCode; }
set { _postalZipCode = value; }
}
public string CountryCode
{
get { return _countryCode; }
set
{
if (value == null)
{
_countryCode = null;
}
else
{
if (value.Length != 2)
throw new ArgumentException("Country Code must be 2 letters", "value");
_countryCode = value;
}
}
}
public override bool Equals(object obj)
{
...
}
public override int GetHashCode()
{
...
}
}

public class Person
{
private int _personId;
private string _identification;
private Name _name;
...
private ContactInfo _contactInfo;
...

public int PersonId
{
get { return _personId; }
set { _personId = value; }
}
public string Identification
{
get { return _identification; }
set { _identification = value; }
}
public Name Name
{
get { return _name; }
set { _name = value; }
}
...
public ContactInfo ContactInfo
{
get { return _contactInfo; }
set { _contactInfo = value; }
}
...

public Person()
{
_name = new Name();
_contactInfo = new ContactInfo();
_birthDate = DateTime.Today;
}
}



I also have a class Patient as follows:

public class Patient : Person
{
...
}

which is the type used for the property being data binded:

In my aspx page:

public Patient Person
{
get { return _person; }
set { _person = value; }
}

I'm working with the lastest version from CVS.

Aleks Seovic
12-26-2005, 06:55 PM
Your domain classes look ok, but that's not what I need -- I need to see how you are initializing _person field within your page in order to figure out if you are running into some kind of page lifecycle related problem.

Another thing I would try is replacing hardcoded length check in the CountryCode setter with an appropriate validator -- as it stands, this setter will usually throw an exception because control value will be an empty string instead of null.

Later,

Aleks

equiv
12-27-2005, 02:30 PM
This is the code:

public class IdCardPage : Spring.Web.UI.Page
{
...
[DataModel]
public Patient Person
{
get { return _person; }
set { _person = value; }
}
...
private void Page_Load(object sender, System.EventArgs e)
{
if (!this.IsPostBack)
{
if (this.Request["id"] != null && this.Request["id"].Length >= 0)
_person = Patient.Read(Convert.ToInt32(this.Request["id"]));
else
_person = new Patient();
}
}
...
private void ButtonSaveChanges(object sender, System.EventArgs e)
{
if (this.Person.PersonId == 0)
{
MedicalFile newFile = new MedicalFile();
newFile.Patient = this.Person;
MedicalFile.Create(newFile);
}
else
Patient.Update(this.Person);
}
}

The methods 'MedicalFile.Create', 'Patient.Read' and 'Patient.Update' contain typical NHibernate code, which I plan to change later when we have Spring support for NHibernate.

I don't have any other event handler for this page.

Thanks!

Aleks Seovic
01-04-2006, 06:10 PM
Sorry it took me so long to respond.

If I were you I'd try removing that length check from the CountryCode property as I think that might be what is causing exception during unbinding (which is unfortunately completely ignored at the moment).

Second thing I would do is replace Page_Load method with this:


protected override void OnInitializeDataModel(EventArgs e)
{
if (this.Request["id"] != null && this.Request["id"].Length >= 0)
_person = Patient.Read(Convert.ToInt32(this.Request["id"]));
else
_person = new Patient();
}


There is no need to wrap it with if (!IsPostBack), because OnInitializeDataModel is only called in that case by the framework, basically the first time page is requested.

I don't recommend using Load event handler directly, as there is so much that is happening in the Spring.Web.UI.Page.OnLoad and it's dificult to determine whether your event handler fires at the appropriate time. That's exactly the reason why I extended default page lifecycle with OnInitializeDataModel and OnInitializeControls methods (and corresponding events) -- they ensure that these two things happen at the appropriate time and are also more specific than way too general Load event. (BTW, unlike InitializeDataModel, InitializeControls event is fired every time request is processed in order to allow you to rebind controls if necessary. This means that you *should* use IsPostBack check within it as appropriate.)

I should probably also mention that the whole data binding framework will likely have a major overhaul before the final release in order to support binding of things other than server-side controls to a data model (for example, you will be able to bind Request["id"] to an ID property instead of doing all the checks and type conversion yourself).

There will be some migration work involved when that's finished, because attributes are likely to be replaced with method calls such as:


RegisterBinding("Request['id']", "ID");
RegisterBinding("txtFirstName.Text", "Patient.FirstName");
...


Overall, it shouldn't be too much work and it will be much more flexible approach.

Regards,

Aleks

smhinsey
01-06-2006, 07:20 PM
Aleks, you should IM me sometime and go over your ideas for the new data binding stuff, because I am going to be working on extending it over the next several months and I don't want to waste effort.

Aleks Seovic
01-06-2006, 08:11 PM
Sure, I'm extremely busy through next week, but we can have a chat the following week and try to come up with the final (well, at least for R1.1) data binding feature set. In the meantime, if you can describe what you wanted to extend, it would be a good start.

If anyone else has feedback/comments/suggestions related to data binding, now would be the best time to post them so we can take them into consideration.

Regards,

Aleks

equiv
01-06-2006, 09:42 PM
Please make sure that these data binding decisions will be easy to implement for ASP .NET 2 in a near future.

Esteban

Aleks Seovic
01-06-2006, 10:13 PM
Yup, that was one of the factors that triggered changes.

- Aleks

equiv
01-07-2006, 11:06 PM
Finally I found the cause of my problem. It's all my fault. I didn't realize that NHibernate set 'ContactInfo' to null if all the values mapped to ContactInfo were null in the table.

I made some changes to fix that.

Now I have DAOs that are injected to the pages like in Spring.Air. I use them this way:

protected override void OnInitializeDataModel(EventArgs e)
{
base.OnInitializeDataModel(e);
if (this.Request["id"] != null && this.Request["id"].Length >= 0)
_patient = this.PatientDao.Read(Convert.ToInt32(this.Request["id"]));
else
_patient = new Patient();
}

Now the NHibernate implementation for 'IPatientDao.Read()' makes sure to create a new empty 'ContactInfo' if null. And it works now.

But then I discovered something.

If I uncomment the catch section for the 'Bind' method in file 'Spring.Web\Web\DataBinding\BindingAttribute.cs' then I got exceptions for some of my textboxes. I realized that if a varchar is null in the database then NHibernate assigns null to my string properties. I could fix that by doing the following change:

if (format != null)
{
sourceExp.SetValue(source, null, String.Format(format, targetExp.GetValue(target, null)));
}
else
{
object val = targetExp.GetValue(target, null);
if (val != null)
sourceExp.SetValue(source, null, val);
}

Now everything works perfectly and I'll keep the catch section uncommented to make sure to catch any future problem with data binding.

Regards,

- Esteban

smhinsey
01-09-2006, 05:33 PM
Aleks,

In no particular order...

Collection binding. For example, I'd like to be able to bind a collection to a repeater and specify DataSource along with the bindings of each repeater item instance.

I am toying with the idea of some sort of ModelMapper class, similar in intent to what http://www.castleproject.org/index.php/MonoRail_Basic_Reference#PropertyBagMonoRail does. I'm not really sold on this, but I think it might make things easier.

A smaller thing thing I'd like to see is a little bit more sophisticated formatting options.

I'm also toying with the idea of making controls to front-end the databinding, similar to how the build-in validators work, so that you can change your databindings without needing to recompile the page.

i was also just reminded of the annoyance of databinding to radio buttons and checkboxes, so that's on the menu as well.