Vault of Thoughts

2006-05-31

Please visit my new blog at vaultofthoughts.net

Problems with DataSource controls

I have blogged about Problems with ObjectDataSource control some time ago. That was a problem I was aware of for some time now. It was until recently when I have come across yet another problem. This time it is with how the DataSource controls handle the SelectParameters. I'm mainly working with business objects so I have little experience using the SqlDataSource but from what I have seen it uses the same mechanism for specifying parameters. The problem is that you can only select a fixed number of parameter sources:




It often happens that you need to get a value from some other location such as a property on a page. What to do when such a need arises? Worry not! There is a solution. You will have to create your own class derived from the Parameter class from System.Web.UI.WebControls namespace. You will need to overwrite some of the methods. As an example I present the TemplateContainerParameter which makes it possible to use public properties defined on a page or a user control as a parameter providers.



public class TemplateControlParameter : Parameter
{
protected override object Evaluate(
HttpContext context, Control control)
{
if ((control == null) || (string.Empty.Equals(PropertyName)))
{ return null; }
TemplateControl templateControl = control.TemplateControl;
Type type = templateControl.GetType();
PropertyInfo property = type.GetProperty(PropertyName);
if (property == null)
{ return null; }
return property.GetValue(templateControl, null);
}
protected override Parameter Clone()
{
TemplateControlParameter parameter =
new TemplateControlParameter();
parameter.PropertyName = this.PropertyName;
return parameter;
}
public string PropertyName
{
get { return (string)ViewState["PropertyName"] ?? string.Empty; }
set
{
if (PropertyName != value)
{
ViewState["PropertyName"] = value;
base.OnParameterChanged();
}
}
}
}


The interesting part here is using the TemplateControl property which will return the reference to a nearest template control containing our parameter which in most cases will be either page or a user control. You can also create a parameter which always uses page properties:



protected override object Evaluate(
HttpContext context, Control control)
{
if ((context == null) || (string.Empty.Equals(PropertyName)))
{ return null; }
IHttpHandler handler = context.Handler;
Type type = handler.GetType();
PropertyInfo property = type.GetProperty(PropertyName);
if (property == null)
{ return null; }
return property.GetValue(handler, null);
}


Here the interesting part is using the Handler property of the context object which in most cases will return the reference to a page object but hidden behind the IHttpHandler interface. It does not mather though since we are getting the property value using Reflection.



The bad news is that you will not be able to use those custom parameters in the designer :-(. The 6 parameter sources shown on the picture above are hard-coded in the .NET Framework library (to my best knowledge). You have to resort to the html editor - as in many more cases, more than it should be necessary :-(.


kick it on dotnetkicks.com

Please visit my new blog at vaultofthoughts.net

Problems with ObjectDataSource

ASP.NET 2.0 comes bundled with a control called ObjectDataSource. At first it looks like it is a perfect solution for all OOP lovers like me. The problem is that in practice it is completly unusable.



There are several problems with the control, the greatest of which are the fact that all objects that are to be updated by the control are required to have a default, parameterless constructor since the Update method takes as a sole parameter the object of a specified type created by none other than the ObjectDataSource control.
(another method is to provide an update method that takes simple parameters representing updated object's properties such as name, age etc. but this solution is even worse since it requires separate methods for every combination of parameters, and defeats OOP feature - the inheritance - altogether).
Now where exactly is the problem? You may think hell I can go with having a default constructor on every object since I will probably need it anyway to support the [Serializable] attribute. But wait! There is one more problem with the control. It creates our business object and sets its the properties!!! Now what is wrong with that you ask? Once the business object is created ALL its fields are set to initial values such as null/1/1.0/false. Next, each property that was bound using the two way databinding mechanism (Bind) is set to a new value taken from the html form. This is of course ok. But what happens to the properties that were not bound using Bind? AHA! Now's the catch. They are not updated with any value and so we end up having a business object with only some of its properties set.



A more realistic example: given a business object User with properties such as Id, FirstName, LastName, Age, Login, Password and a form on which we use an ObjectDataSource control and a FormView control to edit just the Login and Password. Of course the FormView allows us to specify the DataKeyNames so the Id property will be preserved - we will need it to update the right record in the database. The form is first displayed and both textboxes are populated with user's Login and Password. User edits the data and decides to save them. On PostBack the magic happens and the ObjectDataSource creates a brand new User object, sets it Login and Password property with the new values from the form. Such an object is then passed to an Update method expecting a User object. But what to do with such an object? We cannot save it to the database since there is no way to determine if a given property's value is the default value or a value just set by someone and we cannot just save all the new values because that would mean loosing the data that was not edited.
In most cases we cannot even compare the property value with the default value for that property or the value from the database since given an Age=0 we cannot tell if it results from a user's setting or the fact that it was not set at all.



Now. There are multiple workarounds for this problem. One mentioned earlier is to use a separate Update method for each combination of parameters.
The other is a hack. It is possible to fool the ObjectDataSource so that it thinks it uses the parametrized Update method. It is necessary in order to get the Keys and Values from the form. If the Update method is used, there is no way of accessing those values. Next step is to intercept the Updating event. There you will have access to a collection of Keys and Values read from the form (accessible through the event args). You can than get the destination object from the database or from some other location, and copy the new values to its properties using Reflection. Once you have copied the new values to the business object you have a stiutation where the object is properly set: properties present on a form are updated, the other ones are set to the original values and not the default ones. There is just one problem. What to do with such an object and what about the parametrized method? We still need them yes? No! After copying the new values to a business object we have to clear the ObjectDataSourceMethodEventArgs's InputParameters collection and add to it just one parameter - our business object. The good news is that it is only after the Updating event when the propert Update method is selected to be called. The method is selected based on the InputParameters and since there is only one, the Update method expecting our business object will be used :-).


Example code:



void DataSourceControl_Updating(object sender, ObjectDataSourceMethodEventArgs e)
{
IOrderedDictionary inputParameters = e.InputParameters;
// Get the business object
object element = GetElement();
// Copy the values from the form to an object - the hard part
CopyValues(element, inputParameters);
// Clear the parameters list and add a single object to it
e.InputParameters.Clear();
e.InputParameters.Add("element", element);
}


To fool the ObjectDataSource control we have to delete DataObjectTypeName attribute but do it from the html view and forget about using the designer for edditing the control from there after.



Of course the above mentioned methods are not something I would recommend, but for a starter this is the only way I'm aware of you can use the ObjectDataSource. The other way is to write your own ObjectDataSource. This is exactly what I have done and will provide a complete solution in a few days.

Please visit my new blog at vaultofthoughts.net

Benefits of PathsBuildProvider

I have posted an article in which I describe the PathsBuildProvider that reflects the local file system of a web application as a strongly typed class hierarchy. The most obvious benefit that the provider brings is that once you start to use it, you can say good bye to a 404 error resulting from Response.Redirect to a nonexisting page. It is ensured that the error will occure at compile time sine once there is no file, there is also no property on a class to use as a redirect target :-).

Please visit my new blog at vaultofthoughts.net

PathsBuildProvider

Some time ago I have found some articles about a feature of ASP.NET called the Build Providers. I especially liked the one about generating classes representing tables from the database. More on that one on Fritz Onion's blog.
Since then I have found numerous ways of using build providers to make my life easier. Today I have built a library that will greatly simplify keeping all Response.Redirect's up to date.



So without further ado I present the PathsBuildProvider.
Using it is as simple as placing a single file with ".pbp" extenstion in the "App_Code" directory and adding a build provider under the compilation section of of the "web.config" file and:


type="PathsBuildProvider.PathsBuildProvider" extension=".pbp"

The .pbp file should contain one word which will be used as a namespace for the generated types. If the file is empty this namespace will default to "Paths".
From now on you can type your namespace name and when you hit "." the name of the application should appear as the only choice. The instance of this class provides a way to access the names of all files located in the application folder in a strongly typed way i.e.: each file is represented by a property such as "Default_aspx". Such a property returns an absolute virtual path to a file so that it can be used in places sucha as Response.Redirect, or Server.MapPath.
Directories are also accessible by means of the properties prefixed with "_". Each directory provides access to its files and sub directories.


You can see an example of how it looks here:



I have put the code for the soulution outside the blog or at the alternative location.


Keep in mind that the code is for demonstrational purposes only and as so it is not a production quality :-). Feel free to use it as you wish though. Any feedback is always welcome.


kick it on dotnetkicks.com


Liked this article? digg it

2006-05-30

Please visit my new blog at vaultofthoughts.net

The Recommended Books

The books that I would really recommend reading are:


ASP.NET and .NET Framework 1.1
An absoulte must read: either "Programming Microsoft ASP.NET" by Dino Esposito or "Essential ASP.NET With Examples in C#" by Fritz Onion. As a complementary read I would suggest reading "Developing Microsoft ASP.NET Server Controls and Components" by Nikhil Kothari and Vandana Datye. The last one gave me a very good insight on the life cycle of ASP.NET controls. It is especially valuable since there is currently no v2.0 edition :-(.
As for the basics of the .NET Framework, there are two books which you cannot live without and those are: "Programming Microsoft .NET" by Jeff Prosise and "Applied Microsoft .NET Framework Programming" by Jeffrey Richter in that order since the first one talks about all major features of the .NET Framework such as Windows Forms, ASP.NET, Remoting, Web Services and in doing so it does not go in to details. The other one details the inner plumbings of the .NET Framework describing the Intermediate Language, the Base Class Library. Especially good is the chapter on exception handling where the author talks a lot about best practices.


ASP.NET and .NET Framework 2.0
There are just few books I can recommend and those are: both Dino Esposito's books on ASP.NET: "Programming Microsoft ASP.NET 2.0 Core Reference" and "Programming Microsoft ASP.NET 2.0 Applications: Advanced Topics". The second of those books I have yet to read but knowing the previous works by Dino I'm pretty sure it will be a good one. The same is true for the "CLR via C#, Second Edition" by Jeffrey Richter which is a 2.0 version of his previously mentioned book.


While speaking about books there are of course other books than those dealing with computers. In this field I would strongly recommend reading the Dragonlance series - all books by Margaret Weiss and Tracy Hickman and the Wheel of Time series by Robert Jordan.

2006-05-29

Please visit my new blog at vaultofthoughts.net

I need to write about few things...

I need to write about few things:


1) ObjectDataSource control and my own implementation. (with source and binary) - DONE
2) My implementation of DataMapper.(with source and binary)
3) My validation component. (with source and binary)
4) BuildProvider for generating classes to enable developers to redirect to pages in a strongly typed way. - DONE
5) Bug in the DataList control that causes properties set in ItemDataBound event to be not persisted on postback.
6) Working with object graphs, ObjectDataSource and base detail page.
7) Creating a class for handling Session variable access. - DONE
8) Something about VirtualPathProvider.
9) Why bother with exceptions if ultimately what you get is an error. - DONE
10) Recommended books. - DONE
11) Antipatterns on all levels.
12) Custom ObjectDataSource select parameters. - DONE
13) Working on a project using rich domain model with developers of various levels of experience.
14) Pair programming in practice.
15) Woring with Db4o in ASP.NET projects.
16) ReturnUrl pattern. - DONE
17) Conditionaly hiding a column in a GridView control - declarative way.
18) Benefits of switching from PHP to ASP.NET
19) Control.HasControls() instead of Controls.Count.
20) Position Absolute - Positioning Context. - DONE
21) When is the Select method of the DataSource control called? - DONE