Building Layered Web Applications with Microsoft ASP.NET 2.0 - Part 3


NOTE: the concepts presented in this article are now considered obsolete possibly because better alternatives are available.

Update!! - I have written a new series on N-Layer design targeting ASP.NET 4.5 and Entity Framework 5. You can check out the new series here.

Update!! 12-24-2008 - I have written a new series on N-Layer design as a major follow up to this one. It builds on the foundation created in this first series, but digs much deeper into concepts like Validation, Sorting, Paging, Concurrency and Security. You can check out the new series here.

Update!! 04-25-2007 - There is now also a VB.NET version of the code available for download. You find the download at the end of this article. For more information about the translation, check out this blog post.

This is part three of the article series "Building Layered Web Applications" that shows you how to build N-Layer applications with Microsoft ASP.NET 2.0. These articles teach you how to design, build and use custom business objects in your web application. The target audience for this series are developers that are ready to make the switch from using SqlDataSource controls to ObjectDataSource controls with custom business objects. Experience with ASP.NET 2 and C# is necessary while some knowledge about object oriented design certainly helps.

Part one dealt with the design of the application: what business objects do you need to fulfill the requirements of the application. What should these objects be capable of and how do they look. How do these business objects interact with other parts of the system? Part two showed you how to code the classes that were designed in part one. You saw how to implement the data access methods and database code and how the various classes were able to work together. You also saw how to use the API to programmatically create contact persons and their contact data and save those in a database. However, writing explicit code to work with your business objects isn't always fun, and can be a cumbersome task.

Therefore, this article (part three) deals with using the business objects in a web application. You'll see how to use the ASP.NET controls like the GridView in conjunction with the business objects. You'll see how you can build pages that allow you to list, create, edit and delete your contact persons and their contact data, like e-mail addresses and phone numbers.

If you haven't read part one or two yet, you should really read them first, as this article uses many concepts that have been explained in part one and two. The entire series (including this current article) can be found here:

The article uses a SQL Server 2005 Express database which is easy to use in development scenarios. However, the downloads for this series also come with the T-SQL scripts to recreate the database in SQL Server 2000 or SQL Server 2005. You'll find the download link at the end of this article. Besides the forementioned SQL scripts and database, the download also contains the full source for the demo application in C#.

Introduction

Before we dig into the code for the web site, let's briefly recap the application's design I showed you in the two previous articles. The following figure shows the four main components of the application:

The Different Layers and their Relations
Figure 1 - The Four Main Components of the Contact Person Manager Application

Business Objects (BO)

First, there are a number of business objects that live in the Spaanjaars.ContactManager.BO namespace, indicated by the rectangle on the right. The classes used for these objects are the ContactPerson, the Address, the EmailAddress and the PhoneNumber. They don't have any behavior, and can therefore be considered as "dumb" objects. All they can do, is hold and expose data through their public properties. Additionally, each BO object has a List counterpart, like ContactPersonList, EmailAddressList and so on. These lists inherit from a generics list, like List<ContactPerson> and are used to hold collections of the business objects.

Each of the other three components of the application has a reference to the objects in the Business Objects layer. This means that the web site can consume objects like ContactPerson that are returned from the business layer that in turn got them from the data access layer.

Business Logic Layer (Bll)

In the middle of the diagram, you see the Business Logic Layer; the bridge between the web site and the data access layer. The Bll gets instructions from the presentation layer (the web site in this example), to carry out tasks, like retrieving items from the data layer, or sending changed objects back into this layer. Additionally, it can perform tasks like enforcing security and carrying out validation, as you saw in part two of this article series.

Data Access Layer (Dal)

The data access layer contains the code that directly interacts with the data source, a SQL Server database in the case of the Contact Person Manager application but this could be any other kind of data source, like text or XML files, Access, Oracle, DB2 databases and any other data source you can come up with.

Presentation Layer

At the top of the diagram, you see the Web Site, the presentation layer of this application. It's the web site and the pages and code it contains that is the main subject of this article, as you have already seen the other three parts in the previous two articles.

Building the Web Site

In part two of the article series I showed you how the site was set up: the important layers each have a separate folder under the special App_Code folder. Besides that, the site contains two .aspx pages: Default.aspx and AddEditContactPerson.aspx.

The first one lists all the contact persons in the system and allows you to manage their contact records through GridView and FormView controls on the page. The AddEditContactPerson.aspx page allows you to create a new or change an existing contact person in the system.

I'll start by a quick introduction of the setup of the site (web.config, themes, styles and so on) followed by an explanation of the markup and code in Default.aspx and AddEditContactPerson.aspx.

Setup of the Site

In part two you saw the Solution Explorer of the application mainly showing the files in the App_Code folder. Obviously, the entire site contains more files, depicted in Figure 2:

The Solution Explorer for the  Contact Person Manager Application
Figure 2 - The Solution Explorer for the Contact Person Manager Application

Besides the now familiar folders under App_Code, the site has a few other folders and files worth looking at.

App_Data and NLayer.mdf

This is the SQL Server 2005 Express database used for the application. In the Download for this application you also find the SQL Scripts to recreate the database on SQL Server 2000 or SQL Server 2005.

App_Themes and Css\Styles.css

The App_Themes folder contains a single theme called Default which in turn holds a single skin file that is used to change the appearance of the GridViews used in the application. Instead of styling each individual GridView used in the Default.aspx page (four in total), I created a .skin file that changes the styling of elements site-wide. Take a look at the contents of GridView.skin to see how this works:

<asp:GridView runat="server" CellPadding="4" GridLines="None">
  <FooterStyle CssClass="FooterStyle" />
  <RowStyle CssClass="RowStyle" />
  <SelectedRowStyle CssClass="SelectedRowStyle" />
  <PagerStyle CssClass="PagerStyle" />
  <HeaderStyle CssClass="HeaderStyle" />
  <AlternatingRowStyle CssClass="AlternatingRowStyle" />
</asp:GridView> 

Each important *Style element has its CssClass set to a CSS class defined in the file Styles.css. When the page renders, these CssClass attributes are translated to HTML class attributes for the relevant HTML elements. For example, each GridView header gets a HeaderStyle class set like this:

<tr class="HeaderStyle">		  

The HeaderStyle class in the CSS file then changes the appearance of the header to bold white letters on a blue background:

.HeaderStyle 
{
  background-color: #507CD1;
  color: #fff;
  font-weight: bold;
}

This way, all the GridViews in the site have the exact same appearance. More importantly, when you want to change the appearance, you now need to do this in only one location: the Styles.css file. The web.config (discussed next) contains a setting that applies the Default theme to all pages in the site.

Web.config

The web.config file is mostly a standard config file, with only two important settings. The <connectionStrings> element and the <pages /> element. Under the <connectionStrings> node you find two connection strings, with one being commented out, The first contains a connection string that points to a local SQL Server Express database with the database in App_Data, while the other points to a database on a commercial version of SQL Server 2000 or 2005. Refer to this article about configuring SQL Server 2000 or 2005 if you need more information about using this second connection string.

The <pages /> element contains a single attribute that tells ASP.NET to apply the Default theme to all pages in the site:

<pages theme="Default" />		  

If you remove the theme attribute, you'll see that the GridView controls in the site will return to their default layout.

With the additional files in the site looked at, let's take a look at how you can display contact persons on a page.

Displaying all Contact Persons in the System

Because much of the work has already been done by writing the code in the BO, Bll and Dal layers, displaying a list of contact persons is now super easy. To display a list of users in a GridView, follow these 7 steps:

  1. Create a new page and switch to Design View. Add a GridView to the page by dragging it from the Toolbox on the design surface.
  2. Open the GridView's Smart Task panel and under Choose Data Source select <New data source>.
  3. In the Data Source Configuration Wizard, Select Object and click OK.
  4. In the Choose a Business Object, make sure Show only data components is checked and then choose the appropriate business object from the list. In my case, I chose: Spaanjaars.ContactManager.Bll.ContactPersonManager:

The Configure Data Source Wizard Figure 3 - The Configure Data Source Wizard

If you want to know how I was able to limit the items in the drop down to my business objects in the App_Code folder only, and what the meaning of the [DataObjectAttribute()] on the classes in the business layer is, then be sure to check out this article about using attributes in your code. In short, [DataObjectAttribute()] signals to the designer that your class can be used in the Object Data Source wizard. In addition, you can mark methods in these classes with the [DataObjectMethod()] attribute. This attribute is used to mark any method as the default Select, Insert, Update or Delete method, like this:

[DataObjectMethod(DataObjectMethodType.Select, true)]	
public static AddressList GetList(int contactPersonId)
{
  return AddressDB.GetList(contactPersonId);
}			

This marks the GetItem method as the default Select method of the AddressList class (due to the true argument in the attribute's constructor). Since the Save method is used for both Insert and Update statements, ideally I'd like to apply the attribute with the default value twice: for both DataObjectMethodType.Insert and DataObjectMethodType.Update. This, unfortunately, doesn't work. If you know a work around, please let me know. So, instead, I decided to mark the Save method as the default for Update. This means that whenever you need to select an Insert method, you'll need to select one manually. If you don't like this, then you can create two public methods, called Insert and Update for example, and then have both of them call Save.

  1. Next, on the Define Data Methods window, make sure that on the Select tab the GetList() method is selected (it was already preselected due to the DataObjectMethodType attribute). Next, clear the selection on the Update tab as you don't need an Update method at this stage. You can leave the Insert and Delete tabs to their defaults which means there's no Insert method selected while Delete points to Delete(ContactPerson myContactPerson) .
  2. Finally, click Finish to close the wizard.

You should end up with the following markup for the control:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
  DataObjectTypeName="Spaanjaars.ContactManager.BO.ContactPerson"
  DeleteMethod="Delete" 
  SelectMethod="GetList" 
  TypeName="Spaanjaars.ContactManager.Bll.ContactPersonManager">
</asp:ObjectDataSource>

(I removed the OldValuesParameterFormatString attribute, as it's not needed, but added by default).

As soon as you finish the Object Data Source wizard, the GridView refreshes to show the relevant columns from the business object that the ObjectDataSource returns:


Figure 4 - The GridView and its Associated ObjectDataSource Control

Notice how the GridView displays the data for a ContactPerson object from the BO namespace. The ObjectDataSource wizard was smart enough to figure out that although I am using the ContactPersonManager as my methods class, this Manager object returns instances of ContactPerson that I am working with.

That's all for now. Just hit Ctrl+F5 in Visual Studio 2005 and you'll see a list with contact persons appear:

A List of Contact Persons in the GridView
Figure 5 - A List With Contact Persons

Not bad, don't you think? 11 lines of code (the GridView and the ObjectDataSource together) are all that's required to display a list in the presentation layer. Notice how the GridView is skinned automatically by the .skin file you saw earlier. For this to work, you also need to include the CSS file in the page, and enable the Theme in the web.config file. If you don't have the theme set up correctly, your GridView will be quite plain, with black text on a white background.

Although the ASPX page only needs 11 lines of code, a lot of other code is executed under the hood. To get the data in the page, the following sequence of events takes place:

  1. The GridView sees it needs to display its data and requests the data from the ObjectDataSource (ODS) control.
  2. The ODS in turn sees it needs to call GetList on the ContactPersonManager class. Since this method is static, it is able to call it directly. If the method hadn't been static, a new instance of the class would have been created first.
  3. GetList in ContactPersonManager forwards the call to GetList in the ContactPersonDB class in the DAL.
  4. This DAL method eventually fires a stored procedure in the database called sprocContactPersonSelectList that returns the records to the calling code.
  5. GetList in the DAL creates a new ContactPersonList (that derives from List<ContactPerson>) based on the records from the database and returns it to GetList in the business layer.
  6. That GetList forwards the list to the ODS.
  7. The ODS then hands over the list to the GridView which eventually displays all the contact persons on the page.

Obviously, just displaying the contact persons isn't good enough. We also need to be able to Edit and Delete them as well as insert new ones.

Managing Contact Persons in the System

ASP.NET 2.0 offers many features that make it easy to work with data in a web page. You have the various DataSource controls to manage data, the GridView to display lists of records and the DetailsView and FormView controls to display a single record at the time, with insert and update capabilities.

However, I often find the FormView and DetailsView a bit problematic to work with, especially when you need to manage Business Objects with many properties. To fully support all data scenarios (Insert, Update and Display), these controls require you to create three separate templates, each with a number of controls bound to the data source. This can quickly lead to a lot of code that is hard to manage. It also leads to duplicate code which is even worse. In many circumstances, editing an object is identical to inserting a new one, so theoretically you should be able to reuse the templates for insert and update. So, for the Contact Person Manager application I'll use a different approach to manage the contact persons. To show how things work, and because the contact data objects are easier to manage, I do use FormView controls to manage those objects.

However, before we look at that, let's first look at how we can decide what contact person to edit or delete.

To give the user a way to edit or delete a contact person, I added two columns to the GridView: a standard ButtonField with a CommandName of Edit and a TemplateField with an embedded LinkButton. After I configured the fields in the Fields editor for the GridView, I ended up with the following code:

<asp:ButtonField CommandName="Edit" Text="Edit" />
<asp:TemplateField ShowHeader="False">
  <ItemTemplate>
    <asp:LinkButton ID="LinkButton1" runat="server" 
        CausesValidation="False" CommandName="Delete" Text="Delete" 
        OnClientClick="return confirm('Are you sure you want to 
               delete this contact person?');">
    </asp:LinkButton>
  </ItemTemplate>
</asp:TemplateField>

These two fields end up as an Edit and a Delete link in the GridView. As you can see, both of the fields have their CommandName set. This way, when they are clicked, the GridView triggers its RowCommand event. I set up a RowCommand handler for the GridView that points to a method in the Code Behind:

<asp:GridView ID="gvContactPersons" runat="server" AutoGenerateColumns="False" 
  DataSourceID="odsContactPersons" DataKeyNames="Id" 
  OnRowCommand="gvContactPersons_RowCommand" 
  AllowPaging="True" CellPadding="4" GridLines="None">
<Columns>
  ....
</Columns>
</asp:GridView>

The RowCommand event handler looks at the CommandName that is passed to it and then determines what to do:

protected void gvContactPersons_RowCommand(
               object sender, GridViewCommandEventArgs e)
{
  switch (e.CommandName.ToLower())
  {
    case "addresses":
       ..... Shown later

    case "edit":
      int rowIndex = Convert.ToInt32(e.CommandArgument);
      int contactPersonId = Convert.ToInt32(
              gvContactPersons.DataKeys[rowIndex].Value);
      Response.Redirect(String.Format("AddEditContactPerson.aspx?Id={0}", 
              contactPersonId.ToString()));
      break;
  }
}	

When the Edit link is clicked, the case block for edit fires. The index of the clicked row is available from e.CommandArgument so it's easy to use that to retrieve the DataKey for the contact person. The DataKey is the unique ID of the contact person in the database and is used as the DataKeyNames for the GridView. This way, you can always retrieve the original ID of an item in a GridView. Once the ID is known, the code simply redirects to the AddEditContactPerson.aspx page that I'll discuss in a minute.

Notice that you don't see a Delete block in the RowCommand handler. That's because deleting is done fully automatically by the LinkButton in the TemplateField that you saw earlier. The GridView knows it's bound to a DataSource that supports deleting (because it has a DeleteMethod set) so whenever the Delete command is raised, the GridView automatically signals the ODS that it should delete the requested record. The ODS in turn then fires the shared Delete method on the ContactPersonManager class. The only tweak I made was convert a standard ButtonField to a TemplateField that contains a LinkButton with its CommandName set to Delete. By converting the ButtonField to a TemplateField, it's much easier to add a confirmation message to the LinkButton, asking the user for confirmation before actually deleting the contact person:

<asp:TemplateField ShowHeader="False">
  <ItemTemplate>
    <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="False" 
        CommandName="Delete" Text="Delete" 
        OnClientClick="return confirm('Are you sure you want to 
                delete this contact person?');">    
    </asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>    

Finally, allowing a user to insert a new contact person is even simpler. At the top of the page, you find a button called btnNew that simply sends the user to AddEditContactPerson.aspx without passing any query string information. I'll discuss this page next.

The AddEditContactPerson.aspx Page

The markup of this page is really simple. It contains a standard HTML <table> containing standard rows and cells that in turn contain a number of ASP.NET controls, like TextBox, Calendar, Button and a few validator controls. In Design View, the page looks like this:

The Add Edit Contact Person Page In Design View
Figure 6 - The Add and Edit Contact Person Page In Design View

It's the code in the code behind that's a lot more interesting to look at.

protected void Page_Load(object sender, EventArgs e)
{
  if (Request.QueryString.Get("Id") != null)
  {
    contactPersonId = Convert.ToInt32(Request.QueryString.Get("Id"));
  }
  if (!Page.IsPostBack)
  {
    BindTypeDropDown();
    if (contactPersonId > 0) // Edit an existing item
    {
      // Get person
      ContactPerson myContactPerson = ContactPersonManager.GetItem(contactPersonId);
      if (myContactPerson != null)
      {
        txtFirstName.Text = myContactPerson.FirstName;
        txtMiddleName.Text = myContactPerson.MiddleName;
        txtLastName.Text = myContactPerson.LastName;
        calDateOfBirth.SelectedDate = myContactPerson.DateOfBirth;
        if (lstType.Items.FindByText(myContactPerson.Type.ToString()) != null)
        {
          lstType.Items.FindByText(myContactPerson.Type.ToString()).Selected = true;
        }
        this.Title = "Edit " + myContactPerson.FullName;
      }
    }
    else
    {
      this.Title = "Create new Contact Person";
    }
  }
}

When the page loads, the code checks the query string and sees if the ID of a contact person was passed in. If that's the case, the contact person is fetched using ContactPersonManager.GetItem(contactPersonId). When the item is returned, the code accesses its public properties to fill the controls in the page, like txtFirstName and so on.
If there's no query string, the page assumes a new contact person must be created so it leaves all fields empty.

In both cases, the Title property of the page is updated to reflect the action that is being carried out.

Take a look at the code that adds the items to the Type drop down. It uses some nifty code borrowed from the article Universal enumeration editor control on CodeProject.com:

private void BindTypeDropDown()
{
  FieldInfo[] myEnumFields = typeof(PersonType).GetFields();
  foreach (FieldInfo myField in myEnumFields)
  {
    if (!myField.IsSpecialName && myField.Name.ToLower() != "notset")
    {
      int myValue = (int)myField.GetValue(0);
      lstType.Items.Add(new ListItem(myField.Name, myValue.ToString()));
    }
  }
}  

This code loops over the items in the PersonType enum, skipping system items and the NotSet item and then inserts the remaining items in the DropDownList called lstType. This way, the drop down gets filled with the values from the enum type automatically. The good thing about this is you never have to worry about this list anymore. Whenever you remove or add items from the enum, this list is updated automatically.

The final piece of interesting code in this page is inside the Save button's click handler. This code is fired when all the client side validators have done their work. The code for the handler looks like this:

protected void btnSave_Click(object sender, EventArgs e)
{
  Page.Validate();
  if (calDateOfBirth.SelectedDate == DateTime.MinValue)
  {
    valRequiredDateOfBirth.IsValid = false;
  }
  if (Page.IsValid)
  {
    ContactPerson myContactPerson;
    if (contactPersonId > 0)
    {
      // Update existing item
      myContactPerson = ContactPersonManager.GetItem(contactPersonId);
    }
    else
    {
      // Create a new ContactPerson
      myContactPerson = new ContactPerson();
    }
    myContactPerson.FirstName = txtFirstName.Text;
    myContactPerson.MiddleName = txtMiddleName.Text;
    myContactPerson.LastName = txtLastName.Text;
    myContactPerson.DateOfBirth = calDateOfBirth.SelectedDate;
    myContactPerson.Type = (PersonType)Convert.ToInt32(lstType.SelectedValue);
    ContactPersonManager.Save(myContactPerson);
    EndEditing();
  }
}

The page calls Page.Validate() first to ensure all controls check whether they are valid or not. It then checks the SelectedDate of the calendar and sets the IsValid property of the custom validator to false when no date has been selected. Note that the current implementation for the calendar isn't very user-friendly for a birth date. Imagine your contact person is 35. This means you'll need to click around 420 times (35 * 12) to get at the right month back in 1971. Instead, you could add an additional drop down with the years that could allow a user to quickly select the relevant year. As other alternatives, you could drop the entire Calendar and use three drop down instead for the year, month and day or use the new Calendar control from the Ajax Toolkit that features some cool ways to browse through the calendar. However, for the purpose of demonstrating n-layer design, the Calendar control is fine.

Once the date has been checked, the code determines whether we're editing an existing item, or creating a new one. When contactPersonId is larger than zero, it means we're editing an existing item. The code then retrieves this existing ContactPerson using ContactPersonManager.GetItem(contactPersonId) just as with the code in Page_Load. This is done to ensure you always get all the data for the ContactPerson. So, let's say you have a property like CreateDate that you don't want to update when you're changing an existing item. If you'd create a brand new item using a default constructor and then set all the properties, this property might either default to DateTime.MinValue or get today's date. But by retrieving the existing item from the database, and only overriding what has changed, you can leave the existing data in tact.

If we're creating a new contact person, the code instantiates one using the object's default constructor :

myContactPerson = new ContactPerson();   

Regardless of whether we're inserting or updating, in both cases all the public fields of the object are filled with the values from the web controls. At the end, the Save method on the ContactPersonManager is called which receives the ContactPerson instance. The ContactPersonManager forwards this object to the Save method in ContactPersonDB which eventually saves the object in the database and returns its new ID. You saw how this worked in the previous article in this series.

At the end, when the object was saved successfully, the code calls the custom method EndEditing() which simply sends the user back to the Default.aspx page.

You can see that with the Business Objects, Business Logic and Data Access Layers built, it's quite easy to create web pages that allow users to manage objects in your system. You only need around 25 lines of code to save a contact person where most of the code contains straight forward web control to property copying.

However, in some cases, you don't even need all of this code, as you can rely on ASP.NET 2.0's data binding capabilities. In the next section, I'll show you how you can use controls like the GridView and the FormView to allow a user to edit, add and delete contact data, like e-mail addresses, addresses and phone numbers. Just as in part two, I'll only show you the code for the EmailAddress class, as Address and PhoneNumber are pretty similar.

Managing EmailAddress Objects for a ContactPerson

Earlier in this article, I showed you the code for the GridView that displays the contact persons. You saw how I used a number of BoundFields to display ContactPerson properties and how I used a ButtonField and a TemplateField to enable a user to edit or delete a contact person. Besides these two columns I added three more ButtonField columns, to allow a user to select the addresses, e-mail addresses and phone numbers respectively.

<asp:ButtonField CommandName="Addresses" Text="Addresses" />
<asp:ButtonField CommandName="EmailAddresses" Text="Email" />
<asp:ButtonField CommandName="PhoneNumbers" Text="Phonenumbers" />  

When the user clicks one of the columns, the GridView fires its RowCommand that you saw before:

protected void gvContactPersons_RowCommand(
            object sender, GridViewCommandEventArgs e)
{
  switch (e.CommandName.ToLower())
  {
    case "addresses":
      gvContactPersons.SelectedIndex = Convert.ToInt32(e.CommandArgument);
      MultiView1.ActiveViewIndex = 0;
      break;
    case "emailaddresses":
      gvContactPersons.SelectedIndex = Convert.ToInt32(e.CommandArgument);
      MultiView1.ActiveViewIndex = 1;
      break;
    case "phonenumbers":
      gvContactPersons.SelectedIndex = Convert.ToInt32(e.CommandArgument);
      MultiView1.ActiveViewIndex = 2;
      break;
    .....
  }
}		

In each of the three case blocks, the code sets the SelectedIndex of the record that was just clicked. It then displays one of the Views in the MultiView control. To make it a bit easier to hide or show anything related to an e-mail address, an address or a phone number, I wrapped all the functionality in a number of different views. Showing, say, the phone number list, or the FormView to insert a new one is now as easy as showing a specific View.

Inside each view, you find five important controls:

Control Description
GridView The GridView is used to display the contact data, like addresses or phone numbers.
FormView The FormView control is used to insert new items to the data store.
ObjectDataSource The ObjectDataSource is used to display, insert, update and delete the contact records. Inserting is done with the FormView, while displaying, updating and deleting is done by the GridView control
Two Button controls For each contact data type, there are two buttons: an "Add New" and a "Show List" button. To make it easier for  a user to focus on a task, these buttons either show or hide the GridView and the FormView, so only one is visible at any given time.

When the View for the e-mail addresses becomes visible, the GridView knows it must display its data and tells the ObjectDataSource to get a list of EmailAddress objects from the EmailAddressManager class by calling GetList. Recall from part two that this method requires the ID of the contact person. The ObjectDataSource gets this ID by looking at the SelectedValue property of the GridView, that was set in the RowCommand you just saw.

<asp:ObjectDataSource ID="odsEmailAddresses" runat="server" 
    DataObjectTypeName="Spaanjaars.ContactManager.BO.EmailAddress" 
    DeleteMethod="Delete" InsertMethod="Save" SelectMethod="GetList" 
    TypeName="Spaanjaars.ContactManager.Bll.EmailAddressManager" 
    UpdateMethod="Save">
  <SelectParameters>
    <asp:ControlParameter ControlID="gvContactPersons" 
        Name="contactPersonId" PropertyName="SelectedValue" Type="Int32" />
  </SelectParameters>
</asp:ObjectDataSource>  

Finally, if the selected contact person had any e-mail addresses, they are displayed in the GridView using standard BoundFields.

Much of the editing of the EmailAddress objects is done automatically by the standard controls. For example, the Delete column will automatically trigger the Delete method of the ObjectDataSource. Similarly, when you update an item in the GridView, the Save method of EmailAddressManager is called automatically. This method then gets a reference to the EmailAddress that is being edited, so first the BLL and then finally the DAL knows what record to update.

However, there are a few events that are triggered in the code behind that contain some interesting code. If you look in the code behind, you see the following regions and the fv_ItemInserting method:

The Collapsed Code for the Default.aspx Page
Figure 7 - The Collapsed Code for the Default.aspx Page

fv_ItemInserting is the event handler for all three FormView controls in the page and handles their ItemInserting event. I'll get to that a little later.

Each of the regions contains similar code that responds to button clicks and other events to show or hide the GridView or the FormView. It also features an ItemCommand handler that shows the GridView again whenever the user clicks the Cancel link on the FormView to cancel an insert operation:

protected void fvEmailAddress_ItemCommand(
                object sender, FormViewCommandEventArgs e)
{
  switch (e.CommandName.ToLower())
  {
    case "cancel":
      ShowEmailAddressList();
      break;
  }
}  

Other than that, the code in the different regions isn't very exciting.

A lot more interesting to look at is the ItemInserting even handler. This handler is set up for all three FormView controls like this:

<asp:FormView ID="fvEmailAddress" runat="server" DataKeyNames="Id" 
    DataSourceID="odsEmailAddresses" DefaultMode="Insert" Visible="false" 
    OnItemInserting="fv_ItemInserting" 
    OnItemCommand="fvEmailAddress_ItemCommand" 
    OnItemInserted="fvEmailAddress_ItemInserted" EnableViewState="False">
  <InsertItemTemplate>
          ...
  </InsertItemTemplate>
</asp:FormView>  

Since all three FormView controls fire the same event and all of them need to perform the same action, all three are bound to the same event handler. The FormView control fires this event when it is about to send its data to the ObjectDataSource. It's a perfect location to look at the data that the user entered, modify it, or add any data to it. In our case, we need to set the ContactPersonId as this ID is not entered by the user, but is retrieved from the contact person GridView instead:

protected void fv_ItemInserting(object sender, FormViewInsertEventArgs e)
{
  e.Values["ContactPersonId"] = 
              Convert.ToInt32(gvContactPersons.SelectedDataKey.Value);
}  

When the code in this event runs, you get all the available properties of the business object in a dictionary exposed by e.Values. Adding new or changing existing properties is as easy as indexing the Values collection. So, for example, if you wanted to enforce that the Type of the EmailAddress was ContactType.Business regardless of what the user has entered, you could use the following code:

  e.Values["Type"] = ContactType.Business;  

In the sample application though, I am just setting the ContactPersonId that I get from the contact persons GridView by accessing its SelectedDataKey and casting it to an int.

To clarify the process that takes place when a user enters a new e-mail address, here's a detailed description of all the steps:

  1. The user loads the list with contact persons and then clicks the Email link to display existing e-mail addresses (if any).
  2. The page reloads and the existing e-mail addresses are shown.
  3. The user clicks the Create new Email Address button, the GridView disappears and the FormView is shown so the user can enter a new e-mail address:

    Part of the Page that Alllows a User to Enter a New E-mail Address
    Figure 8 - Part of the Page that Allows a User to Enter a New E-mail Address

  4. The user enters a new address, chooses a type and then clicks the Insert link.
  5. The ItemInserting event of the FormView control is triggered. The code in the event handler retrieves the ID of the selected contact person and assigns it to the ContactPersonId key of the Values dictionary.
  6. A new instance of the EmailAddress class is created and each of the objects's properties are filled with the corresponding values in the Dictionary object properties (Email, Type and ContactPersonId).
  7. Next, the Save method of the EmailAddressManager class is called. This method receives the EmailAddress instance created in the previous step.
  8. As you saw before, this Save method forwards the EmailAddress object to the DAL.
  9. The DAL saves the EmailAddress in the database and returns the new ID of the e-mail address to the calling code.
  10. The Save method in the business layer assigns the new ID returned from the data access layer to the Id property of the EmailAddress object and then returns that ID.
  11. Finally, the FormView control fires its ItemInserted event. This method simply calls ShowEmailAddressList() that hides the FormView and displays the GridView again that now shows the newly inserted e-mail address.

That seems like a lot of steps for something as simple as inserting an e-mail address in the database. Remember, though, that most of these steps take place in the framework that was developed earlier. In fact, from an ASPX page perspective, all you need are the controls and the code in the ItemInserting event. All the other event handlers are just there to improve the user's experience, by showing or hiding the relevant controls.

Similar processes are executed when you either edit or delete an item in the GridView. When you edit an item, eventually the Save methods in the BLL and DAL are called, while obviously Delete is called when you delete an item from the list.

That's about all there is to it. With the entire business and data access layer built, managing your objects in a web form is often as simple as dragging and dropping a few controls and writing a little bit of code for some relevant event handlers.

Wrapping it Up

Obviously, the code and the pages you have seen in this article series represent a simplified version of a real world application. But, simplified as it may be, it still represents real-world concepts. The layer architecture, object design, object and method implementation, page design (controls and code behind) are all concepts that can be mapped directly to your own applications.

To limit the size of this article series, I left out a few detail implementations of techniques you would otherwise include in your code:

Validation

In part two I showed you how to implement validation in your business objects, This way, the business layer becomes responsible for checking the data that is sent to the database, and guarding the data integrity. This is crucial in real-world applications.

In addition to checking data at the business layer and optionally throwing exceptions when the data doesn't meet the standards, your presentation layer should also check the data to help the user input correct data in the first place. The various validator controls (RequiredFieldValidator, RangeValidator, CustomValidator and so on) are indispensable tools in validating user input both at the client and the server. You saw how the AddEditContactPerson.aspx page used a number of these controls to validate the input. You can use the same controls in the EditItemTemplate of the GridView columns to validate user input in the GridView as well.

Error Handling

Other than the TransactionScope object to roll back any open transaction whenever an error occurs when saving a contact person, the code has no error handling. In a real-world app, you could wrap the code in a try / catch block and log the error in a database or text file or send it by e-mail. Alternatively, you'd allow all exceptions to bubble up to code in Global.asax and handle it here, or deploy ELMAH to handle and log all errors in your system in a consistent way.

Summary

This article showed you how to use the business objects that were designed and created in parts one and two of this article series.

The article started off with a quick description of the structure of the site. You saw how the pages were organized, how the site uses a Theme and a Skin to ensure a consistent look of some of the controls in the site, and you saw how the connection string for the application is stored and accessed at run-time.

You then saw how to configure an ObjectDataSource control to allow a user to display and delete contact persons and you saw how to modify the GridView to include an Edit link.

The article then discussed a way to create new and update existing ContactPerson objects using a separate page.

Next, I showed you how to use the FormView control to insert new contact data records. Although the FormView and GridView controls become hard to use for more complex business objects, they can be great for relatively simple objects like EmailAddress or PhoneNumber.

The article ended with a brief discussion of the topics that were *not* discussed in this article, including extended validation and error handling.

If there are any topics that you feel should be included in this article series, send me a message and I'll take a look at your request.

Download Files


Where to Next?

Wonder where to go next? You can read existing comments below or you can post a comment yourself on this article .


Consider making a donation
Please consider making a donation using PayPal. Your donation helps me to pay the bills so I can keep running Imar.Spaanjaars.Com, providing fresh content as often as possible.



Feedback by Other Visitors of Imar.Spaanjaars.Com

On Monday, February 26, 2007 11:30:06 PM sur said:
Another excellent article. I found myself agreeing on many issues throughout all 3 articles . I too avoid TableAdapters for the same reasons you stated and i also find the FormView and DetailsView constantly giving me a headache so it was nice to know i'm not alone:) I picked up a number of good tips and will be using them from now on.

I've read a few of your other articles and again they are well written. Keep up the good work.

[I noticed one error in the AddEditContactPerson.aspx.cs file within the Page_Load:
You use - lstType.Items.FindByValue instead of
lstType.Items.FindByText]
On Tuesday, February 27, 2007 8:51:54 PM Imar Spaanjaars said:
Hi sur,

Thank you, good catch. I fixed it, both in the article and in the code download. Initially, I had the name of the Enum as the value as well, so it always worked. Then I changed the value for the DDL to the value of the Enum item and never noticed in didn't work anymore.

If you have other feedback: feel free to post it here, or off-list to me directly through the Contact page.

Cheers,

Imar
On Thursday, March 01, 2007 5:04:15 PM Penny Gray said:
Great article. Very helpful. Its similar to approach that I have taken, but have run into some problems with.

I was wondering how you'd go about displaying "foreignkey" objects in your Business Objects in the GridView? For example if showed all the details from the ContactPerson object, but also wanted to display the Email and Phone number from the EmailAddress & PhoneNumber objects?

Also, given that Business Objects, or rather the Databases behind them can have many related tables, how would you invisage referencing further object, say a table of CountryCodes for the PhoneNumber?

Thank you

Penny
On Thursday, March 01, 2007 5:24:21 PM Imar Spaanjaars said:
Hi Penny,

If you only want to display the child records, you can do something like this:

[asp:TemplateField]
[ItemTemplate]
  [asp:GridView ID="GridView2" runat="server" DataSource='[%# Eval("Addresses") %]']
  [/asp:GridView]
[/ItemTemplate]
[/asp:TemplateField]

This sets up a nested GridView inside the outer GridView that displays the ContactPersons. It then sets the DataSource of the inner grid to the Addresses property of the outer item. This assumes that each CP has an associated Addresses collection. Currently, these are not set up in the GetList method, but the overloaded GetItem method shows you how you can do it. You should probably call the database multiple times to get all the data, or you should have a single sproc that returns all data and then insert it in a DataSet with relations between the tables so you can get the relevant data manually.

However, you'll find that it'll be difficult to make these grids editable / updateable as well. Therefore, I usually tend to decouple the related data from the outer element and present it in separate grids with editing capabilities.

There are a number of ways to do something like the country code. One is to use a custom NameValue object that holds the ID / Code and the Name of the country. The Name is used for display purposes while the ID is used for binding. My latest book ASP.NET 2.0 Instant Results uses this in the BugBase chapter.
Additionally, you could create a separate Country property that is filled in the GetItem of the Address by calling GetItem on the country.
This takes an additional database call, but it may be worth it in your situation.

Either way, you'll find that .NET has problems working with object hierarchies, so you'll end up with a lot of code in Inserting and other events..... :-(

Imar
On Thursday, March 01, 2007 6:19:20 PM Penny Gray said:
Hi Imar

Thank you for getting back to me so quickly. Its definately solved my problem and I will look at using the nested view.

Penny
On Wednesday, March 07, 2007 6:11:20 PM Alper said:
Great article filled with great tips. Thanks for taking the time to write it.
I was thinking about applying the architectural principles presented in your article to a code generator template. What is your opinion on that and do you see any areas where generating code based on your principles would be difficult?

Thanks...
On Wednesday, March 07, 2007 7:57:13 PM Jerry said:
fabulous work. I agree a lot. My questions is there are some other OR mapper other than LINQ/LINQ for SQL, for example, NHibernate. How do you think them comparing to the architecture you mentioned in this article.
On Wednesday, March 07, 2007 10:39:16 PM Imar Spaanjaars said:
Hi Alper,

You can certainly use code generators to generate most of this code. In fact, most of the code from this article is written with a custom-built code generator.

Where I work we have a number of in-house code generators that generate the code we like to work with. Stuff like stored procedures and the code for BO, Bll and Dal layers can be generated automatically to a great extend. Combined with the "partial class" feature of .NET 2, it's relatively easy to generate an application that's easy to maintain and extend.

Of course "connecting classes", e.g. the generics collections of address records in the ContactPerson class isn't easy to create with a custom generator, but there are commercial generators available that can do this as well.

Also, the upcoming Linq for SQL can greatly minimize the code you need to write.

Cheers,

Imar
On Wednesday, March 07, 2007 10:45:16 PM Imar Spaanjaars said:
Hi Jerry,

You can certainly use other data tools, like NHibernate for this. I haven't done a whole lot with NHibernate, but it could replace most of the code in the DAL.
You still want to have the BO and Bll layers I think, because they allow you to store data away from the business logic, and to apply data validation and authorization rules.

Linq for SQL, on the other hand, could take away some of the code in the Bll as well as it generates most of the outside of the object, like properties and methods as well.

Cheers,

Imar

On Saturday, March 10, 2007 12:49:06 PM Oscar said:
I want to do a relational linking with something like these:

I have these classes -  Client - ClientWork - ClientPortfolio...

I want to link in the ClientPortfolio the Client and ClientWork with something like these:

public class ClientPortfolio
[
        private Client _client = new Client[];
        private ClientWork _clientWork = new ClientWork[];
        
        public Client Company
        [
            get [ return _client.Company; ]
        ]

        public ClientWork Briefing
        [
            get [ return _clientWork.Briefing: ]
        ]
]

And then pass it to the ClientPortfolioList...

public class ClientPortfolioList : List[ClientPortfolio]
[
        public ClientPortfolioList() []
]

and then do the things with a relation database etc.
My question is... Is this a good approach or there are betters way to do these kind of releational database/classes...
On Saturday, March 10, 2007 12:57:14 PM Imar Spaanjaars said:
Hi Oscar,

I am not sure I understand what you're asking. Can you elaborate? Adding a few line breaks to your code sample would certainly help....

Imar
On Saturday, March 10, 2007 1:45:29 PM Oscar said:
Sorry, i'm not very good at english, by the way, that was fast answering...
I want to show in a grid a relation that is in the database, I have these tables:
Client, ClientWork...
and want to show the relation between them in a grid... I try to use what you post


If you only want to display the child records, you can do something like this:

[asp:TemplateField]
[ItemTemplate]
  [asp:GridView ID="GridView2" runat="server" DataSource='[%# Eval("Addresses") %]']
  [/asp:GridView]
[/ItemTemplate]
[/asp:TemplateField]

but cant find to use it very well... do you have any other approach? I mean i want to show the name of the client and the clientwork (may have many works for a single client)...

thanks for your time...
On Saturday, March 10, 2007 2:06:29 PM Imar Spaanjaars said:
Hi Oscar,

I suggest you post this on a forum like http://p2p.wrox.com. If you do post there, be sure to provide more detail and the relevant code.

As you can see, posting detailed code here isn't very easy, which makes it hard for me to see what you mean.

Imar
On Saturday, March 10, 2007 8:05:46 PM Dee said:
You have a flwed model here..

You have pushed css outsid eof the theme folder - bad idea.

You have mushed the DAL into the code folder and it should be kept with the data folder so it is complete.

You have not allowed for any module development which would create separate folders for independent modules such as newsletters, uploads or any type of other typical web feature.

Don't just lump business code into  the app_code folder it fails to distinguish its purpose.

Try to think more along the lines of MVC structure for websites.

On Saturday, March 10, 2007 9:08:25 PM Imar Spaanjaars said:
Hi Dee,

I think you're completely missing the point here. This article is not about skinning sites or how to create reusable front ends, but about application design. The location of the CSS in this site, the skins or themes that are used, or even the location of the code are largely irrelevant to this article, and merely serve to make things look a little nicer or easier to understand and work with.

Earlier in this article series I explained my reasons for putting all the code inside App_Code and explained why and how you'd move the code to separate class libraries for anything bigger than this kind of project.

Also, you say this:

"You have mushed the DAL into the code folder and it should be kept with the data folder so it is complete".

How's that? How can you make that work if your code or assemblies are not located in either the App_Code or the Bin folder?

All in all, I don't find your criticism very constructive. I am open for feedback and criticism, but I don't see your comment adding much value. Want to try again, and this time add some more foundation to it?

Imar
On Sunday, March 11, 2007 4:11:51 AM Maryan said:
Dee is absolutely right, I didn't read your previous articles, but you prepared current article and it'd be completed. Many guys will read current article and will use current scheme.

On the image at the top of this article you displayed that Website is Presentation Layer, but you put business, data layers inside of the Website project. Just for an example - what if you want to reuse business, data layers in a some desktop application?

Second reason - any developers from a team can use DAL directly without BLL (in the your solution), in the VS 2003 different class libraries and correct references allow to setup the good constraint.

The same about CSS (styles), if you used the new feature of asp.net 2.0 - themes - your styles in a other folder are redundant.

Looks like that this article is for an initial asp.net developer about old MVC pattern and few new features of asp.net 2.0 (object datasource), so you'd be accurate because they'll follow your solution's structure etc.
On Sunday, March 11, 2007 7:35:15 AM Imar Spaanjaars said:
Hi Maryan,

May I strongly suggest that you do read part 1 and 2 as well? This article is part of a series for a reason: all three belong to each other. You can't judge on one, without reading the others....

Again, and for the last time: in a previous article I am explaining why and how you can move your code to separate assemblies for easier coding, maintainability and reuse. However, not everyone has a full commercial version of Visual Studio. That's why I put all code in the App_Code folder, so anyone with the Express edition and up can follow along. I put them in separate folders to indicate what you should put where when you're using class libraries projects. It's simply a matter of copying the files to a separate project and you're done.

Imar
On Sunday, March 11, 2007 10:09:41 AM DotNetGuru said:
Hi Imaar,

These are nice articles and explain a 3-tier architecture well using Business Entities. I have a few questions to ask...

1) Please provide some sample within your application for session and security access within the Business Logic.

2) What is a Business Object, can you give some example please ?

3) It is argued that all layers should be seperate and we can use the same BLL/DAL for Desktop/Web Applications. Now if there are some session variable accessed in the BLL, it will break for any Desktop Applications. The same is true for the built in asp.net security. So how should one use the session variable specially with the BLL, as parameters or what ? Same question for security ?

Keep up the good work...
Regards.
On Sunday, March 11, 2007 10:19:41 AM Imar Spaanjaars said:
Hi there,

1. See part 2: it explains how to check the Role of the user for security purposes. I don't think it's a good idea to use Session state in your objects, so I won't give an example of that.

2. ??? Business Objects is what the articles are all about. ContactPerson, Address and so on are all BOs.

3. Yes, I agree. So, you shouldn't access Session state in your objects. Instead, if your objects need values from something you stored in Session state, pass it in as arguments to the methods in your Bll. You can use an ObjectDataSource with an asp:SessionParameter for that. Then in your desktop application, you can get the data from a different location, but still pass it into the Bll.

The same applies to security. Don't assume there's a User through HttpContext.Current.User for example, as it won't be available in a desktop application.

Imar
On Monday, March 12, 2007 3:55:00 AM DotNetGuru said:
Hi Imar,

Thanks for the quick reply, your doing a good job by sharing your thoughts with the community, a few more questions (hope you wont mind):

1) In your 3rd reply you said ....Then in your desktop application, you can get the data from a different location, but still pass it into the Bll -- What do you meant over here. Do you mean for any session parameter we may pass a null value if we dont have one from a desktop application.

2) Do you think there is a difference between business objects and business entities. Since im new to this idea so what you are reffering to as objects i call them entities. Are they both the same ? Can you elaborate a bit more on this.

3) What do you think is a good security model to be used by both Desktop and Web Applications. What if we want to use the asp.net built in security but use the same BLL/DAL with a Desktop Application.

Thanks alot for your time,

Regards...
On Monday, March 12, 2007 7:50:14 PM Imar Spaanjaars said:
Hi again,

1. It depends on what you use Session state for. I can imagine situations where you need Session state in a web application while you can use other means in a desk top application. For example, you could store a search expression in session state and pass that to a method in a web application. In a desktop application you may get the expression from a control.
Note I am not advocating to store stuff like sort expressions in Session state; this is just an example.

2. AFAIK, no. They refer to (more or less) the same, although I am sure there are purist that say otherwise. For me, it all comes down to the same thing.

3. Get yourself a copy of "Expert VB / C# 2005 Business Objects" by Rockford Lhotka. He shows a way to serialize and pass identities between layers.

Imar
On Monday, March 12, 2007 9:36:12 PM Arno said:
Hi Imar,

Thanks for the great article series. Do you think it is possible to reduce the number of code lines in the DAL, it's quite a lot to write or generate & modify.

Regards
On Tuesday, March 13, 2007 1:47:40 PM Bliek said:
I see in your GridView you tried to implement sorting of the datagrid, because you defined SortExpressions, however when sorting of the grid is activated (AllowSorting="true") an error occurs when clicking one of the headers of the grid.
My question is: how can sorting of the GridView be achieved?

Thanks,
Bliek
On Tuesday, March 13, 2007 2:10:44 PM Mark Henke said:
Thanks for the Great Article on this topic as there are not too many since the release of ADO.NET 2.0. Most are related to Table-Adapters lately. I have been using Table-Adapters in all of my enterpise application with great success however I wanted to learn the true 3-Tier approach and you illustrate th basic concepts here very well!

In response to the Question by Penny Grey on the topic of displaying relational data I hope you can back me up in saying not to think of business objects as relational objects as they are in a database where it is much easier to join the tables and return the data in a query or view.

I would not recommend using nested Controls at all in the presentation layer as this is very messy!
I think the proper approach would be to return the related data to the Business Object (Entity) itself. For Example when it comes to the Child Addresses of the Contact Person Class I would created a list that returns the Addresses along with the Contact Name from the Database on populate the Collection accordingly. Then this can be databound directly to any ASP.NET list control. Try not to Match the Database Objects directly to business Objects but this of the Business Object as What do I want the end result to be or display to the user. Any comments or other suggestions are welcomed! Thank you in advance!
On Tuesday, March 13, 2007 5:44:22 PM Imar Spaanjaars said:
Hi Arno,

Yes, I think you can. There are many ways to make the DAL and other layers easier to write.

First of all, you can use code generators to create the code for you. That doesn't decrease the total amount of code, but it at least decreases the amount of code you need to write yourself.

Secondly, look into DAL layers like the BinaryIntellect's version  I talked about earlier.

Finally, you can look at technologies like Linq and NHibernate that will greatly minimize the amount of code you need to write.

Cheers,

Imar
On Tuesday, March 13, 2007 5:56:59 PM Imar Spaanjaars said:
Hi Bliek,

Yeah, it's possible. Follow these steps:

1. Create an overload of GetList that accepts the SortExpression as a String.

2. Change the ODS declaration so it contains a parameter for this new sortExpression:

[SelectParameters]
  [asp:Parameter Name="sortExpression" Type="String" /]
[/SelectParameters]

3. Set the SortParameterName attribute of the ODS:

SortParameterName="sortExpression"

4. Enable sorting on the GridView.

Run the page and click one of the column headers. You'll see that GetList is now called with the proper sortExpression from the GridView.

How you sort is up to you; you could forward the sort expression to the database to do a dynamic sort there. Alternatively, create a class that inherits from IComparer[ContactPerson] (replace [] with generics brackets) that performs a custom sort. Then use the Sort methods of the Generics list to sort it with your custom IComparer object.

Check out the chapter The Wrox BugBase in my latest book ASP.NET 2.0 - Instant Results (http://imar.spaanjaars.com/AboutMyBooks.aspx?aboutitem=instantresults) for an implementation of that idea.

Cheers,

Imar
On Tuesday, March 13, 2007 6:07:18 PM Imar Spaanjaars said:
Hi Mark,

I agree. Business Objects != relational data.

With a relational database, it's a sin to duplicate data, so it's important to normalize the database. However, with object design, it's not really a problem to duplicate data (e.g. multiple types of objects can contain similar data), as long as the underlying data store is normalized.

With object design, it's much more important to normalize behavior. So, if you have a PostalAddress and a VisitAddress (for whatever reason you may have), a ValidateZipCode method should not be repeated in both methods, but instead be "normalized" to some intermediate object.

Imar
On Wednesday, March 14, 2007 8:45:04 PM John said:
I have been reading with great interest, part three is not the best, but overall it's a great job, thanks!

I have a question though. You generate code like this right? Does that actually work when the code needs to do just a bit more than CRUD? Of course, I have less experience building apps, but right now I'm creating an app that has many to many relationships, inserts across databases (add contactinfo in one, add membership user in other), delete folders on disk after deleting records, tables that store more than one type of data, handling blob values, webservices, uploading and more requirements. I just don't see it working with _any_ general approach I have read so far.

Here's the central question: is my application so strange? Do all you people only create MS Access-like CRUD applications? I don't think so... So what am I missing here? Sometimes I feel the promise of ASP.NET is an empty one. It never is this simple. I probably need more experience....
On Wednesday, March 14, 2007 9:30:43 PM Imar Spaanjaars said:
Hi John,

If you could generate an application that "has many to many relationships, inserts across databases (add contactinfo in one, add membership user in other), delete folders on disk after deleting records, tables that store more than one type of data, handling blob values, webservices, uploading and more requirements. " we'd all be out of business.

Of course you still need to write code. That's why we're programmers. However, the concepts in the article still apply. When you delete an item by calling a method in the Bll, you forward the call to the Dal to delete the record from the DB, but you can also delete a file from disk or update another system through a web service at the same time.

I am not sure what you're expecting, but a code generator that does all this for you based on your specs alone still needs to be written.

Generally, I create the structure for the application (more than I have shown here) with a generator, and then tweak and add things, either by changing / enhancing the generated code, or through the means of partial classes.

Imar
On Thursday, March 15, 2007 7:05:39 PM John said:
Hi Imar,

Every time I see an article like this one, I read it with great interest. But every time I'm left with a feeling of disappointment, simply because I cannot see how to apply the ideas presented in my own work. I guess it comes down to experience.

So thanks for the articles, they are great. Perhaps you can do a follow up about more advanced topics like caching, transactions, lazy loading and many to many relationships.

Anyway, thanks again for your work, much appreciated!
On Thursday, March 15, 2007 8:29:54 PM Imar Spaanjaars said:
Hi John,

You say: "But every time I'm left with a feeling of disappointment, simply because I cannot see how to apply the ideas presented in my own work."

Does that apply to these article series too? Or did they help you put things into a more practical perspective?

Imar

On Friday, March 16, 2007 8:51:53 AM Saibal said:
Worderful Article. I have been using it more or less similar to these.

I need address more information which I faced in real life with this type of N-layering. With this model I do not benefit much in distributed environment.

1) If all the BLL & DAL layers are in the Same Web Server machine. What if I want to BLL & DAL class library on a different Machine say App server different from the web server.
2) I want to expose some functionality as web service rather than directly call BLL methods from Presentation Layer. But I want to reuse the complex functionality of BLL Layer and also the Business Object.
3) It should have been better to have serialized Business Object  embedded in the different class Library so that it can be shared across all the other class libraries in the project.
4) Another thing we always forget in Object Datasource model is the database concurrency or conflict errors in optimistic locking of the database. There must be some property in Business Object and Logic to tackle situation like updating data which was already been deleted or already been update by another user. It is serious business functionality issue.
5) What if I want update many records at a time?  Batch update options?  This is rare in Web scenario but BLL and DAL layers must be independent of the UI layer. In Windows Forms it is common show data in grid view  , user changes multiple rows and update it at one go.

waiting for your reply
Thanks
On Friday, March 16, 2007 9:42:25 AM Imar Spaanjaars said:
Hi Saibal,

I think all of your questions are answered in the book Expert VB / C# 2005 Business Objects by Rockford Lhotka. Check it out at http://www.lhotka.net/

Cheers,

Imar
On Friday, March 16, 2007 6:58:36 PM John said:
"Does that apply to these article series too? Or did they help you put things into a more practical perspective?"

They gave me great ideas to get started, just not sure how to take it from here, but I'm working on that now.
On Friday, March 16, 2007 10:21:28 PM Imar Spaanjaars said:
Hi John,

Sounds good. Good luck, and have fun with it!!

Imar
On Monday, March 26, 2007 1:34:30 PM Chris said:
Imar,

Thank you so much for taking the time to write these articles. I've manage to incorporate your design and methodology into my latest project. In fact, I've merged the File Upload and n-tier guidance into an app for managing images. One question I have is, how would you handle uploading images to an album, if, in the context of the n-tier articles, an album was equivalent to a contact person, and an image was equivalent to an address, email address, or phone number? My API structure is exactly as you describe in the n-tier design, except for my object names and types, so within that context, how would you do this?

Thanks in advance!

Chris
On Monday, March 26, 2007 7:31:20 PM Imar Spaanjaars said:
Hi Chris,

I am not sure I understand what you're asking. If you know how to upload and save files from the other article, and know how to set up the architecture from this series, what is it that you want to know? Can you be more specific?

Imar
On Thursday, March 29, 2007 6:31:56 AM Johirul Islam said:
very good site, no single % dought site portal achitect point of view
On Friday, March 30, 2007 6:33:44 AM Shailendra Sute said:
Hi Imar

Excellent Article!
Few questions:
1. For BO, Can we use struct instead of class? or Can we use public fields instead of properties?
2. In DAL and BLL all public classes are static. If my project have 30 BO, means I will have 30+30 DAL and BLL and multiply by 4 static methods. Is it okay to have so many static methods in the project?

Looking at performance perspective what improvements you suggest?

Thanks again for a series of very good articles.

Regards
Shailendra
On Friday, March 30, 2007 8:22:48 PM Imar Spaanjaars said:
Hi Johirul,

1) I wouldn't do it. Public properties give you many benefits over fields, allowing you to work with the data before it's stored in private fields (proper casing, for example, or other types of checks / data changing).

2) It doesn't matter much. Static methods only created once. So, even if you have like 30 instances, you still have room / memory taken for only a single method.

Imar
On Saturday, April 07, 2007 8:13:55 PM Benny Halperin said:
Hi Imar,

I greatly enjoyed this tutorial and decided to try to implement it. I have an app I'm working on and currently implement Scott Mitchell's TableAdaptor paradigm. I chose one of the simple accessory tables (id, name), created the appropriate classes (business object, business logic, and data access) and associated these with the ObjectDataSource of this particular table's GridView (I even modified as you instructed in one of your replies above to allow sorting). The GridView displayed the table content nicely.

The thing is, I allow editing on my GridView especially when managing such simple tables to avoid FormView/DetailsView whenever possible. I suspect that your implementation does not support it because I got an error message when the ObjectDataSource's Updating event fired.

The error message is "The OrderedDictionary is readonly and cannot be modified" and is triggered by the following code:

e.InputParameters["name"] = sName;

where e is the ObjectDataSourceMethodEventArgs argument of ObjectDataSource's Updating event handler.

Am I wrong here? and if I'm right how do I enable GridView "inline" editing?

Thanks, Benny
On Saturday, April 07, 2007 9:11:56 PM Imar Spaanjaars said:
Hi Benny,

You bet it supports updating with the GridView. It would be a pretty useless solution if it didn't... ;-) In fact, the contact data records are all managed with updateable GridViews.

However, what may not be so obvious (as I didn't show it in the article) is that when you are calling methods that expect a Business Object (like an Address or an EmailAddress) as opposed to methods that accept individual parameters for the fields (like Street, HouseNumber and so on), the InputParameters collection contains only one property: the business object itself. So, you won't find a Street or a HouseNumber parameter but only a single parameter that holds the entire object. Simply cast it to the expected type, set its fields and let .NET handle the rest for you.

Here's a quick example that enforces the Street to have some default value, set in Ods_Updating, regardless of the value the user entered:

Address myAddress = (Address) e.InputParameters[0];
myAddress.Street = "Whatever it is you want";

Hope this helps,

Imar
On Wednesday, April 11, 2007 12:11:21 PM David Anderson said:
This is one of the best articles on layered applications I have read and I've read lots of them. Despite your cautionary note about using FormViews I am attempting to use one and have one minor problem. In the FormView I have a drop-down listing states. The states populate but I cannot get the selected value to work reliably. The first time I make a selection in the parent GridView, the selected value works and the correct state is indicated. Every time after that, it selects the state for the PREVIOUS data row. Here's a code snippet:

asp:DropDownList
                    ID="ddlState" runat="server"
                     SelectedValue='<%# Bind("state") %>'
                     AppendDataBoundItems="True"
                     DataSourceID="odsStates" DataTextField="abbr"
                     DataValueField="abbr" TabIndex="8">
                     asp:ListItem Select... asp:ListItem
asp:DropDownList
asp:ObjectDataSource
                    ID="odsStates"
                    runat="server" SelectMethod="SelectStates"      
                    TypeName="GeneralDB"
asp:ObjectDataSource

Many thanks for your contribution to ASP.NET best practices.
On Wednesday, April 11, 2007 2:19:40 PM Noreen Akhtar said:
I have recently started asp.net 2.0 and have been searching the net for examples of n-tier objected oriented examples/tutorials that would help me get started. Most of the articles i've read dont' really make much sense and dont' explain the concepts properly. I love this article and really enjoyed reading it. It is very easy to understand and really makes sense. Only one area i feel you haven't covered and I think someone else has mentioned this too is optimistic concurrency.  You know stuff like checking that the record you're updating hasn't already been updated by someone else or deleted by someone else. Please can you expand on this. Thanx
On Wednesday, April 11, 2007 4:49:11 PM Imar Spaanjaars said:
Hi David,

I am glad you like the articles!

I am sorry but I can't help much with the problem, without seeing your code. I think you're better off posting this on a forum like http://p2p.wrox.com/

Cheers,

Imar
On Wednesday, April 11, 2007 4:59:40 PM Imar Spaanjaars said:
Hi Noreen,

Yeah, that would certainly be a useful addition to the discussion. Unfortunately, I don't have the time to dig into this deep enough at the moment.

Here's short version of what you can. Either keep old state in the object for all previous versions and then do an update with a WHERE clause that uses all previous values. This is how controls like the SqlDataSource do it.

Alternatively, give each row a timestamp or another unique row ID, like a Guid. Pass that value into your sproc and use it in the UPDATE statement in the WHERE clause.

In both cases, when 0 records have been affected you can see the update failed and act appropriately; for example, by passing false (or an Enum type) from the DAL all the way up to the UI where you can present a suitable error message.

A lot has been written about this topic, so a Google search should give you a lot of useful results.

Cheers,

Imar
On Wednesday, April 11, 2007 5:22:56 PM David Anderson said:
I posted a code snippet above (with some tags removed so your system would allow it). This is a problem that is specific to the kinds of objects your are using in your article. I never have this problem using ObjectDataSource with DataSets. I will definitely post on WROX as you suggest but was hoping for some guidance from the "master."
On Wednesday, April 11, 2007 5:38:45 PM Imar Spaanjaars said:
Hi David,

Well, the "master" is a little busy at the moment, so he doesn't have the time to look at this so hopefully others on a forum can give it a try as well.

Also, as you found out, it's difficult to post code here.

I doubt it's a problem with the type of objects I create; all they do is get data from a database and fill BOs. This is not related to load / post back / view state issues. However, I could be wrong of course.... ;-)

Imar
On Wednesday, April 11, 2007 5:59:42 PM Hasan Can said:
I think this is one of the best articals has been written about the subject(for beginners in n-tier). Most of the articals are lost in advanced topics.

I don't understand some people post here, why try to proof author is wrong. I beleive it is very nice, and clear start up project for somelike me who is switching to n-tier.

Thanks...Imar
On Wednesday, April 11, 2007 6:34:00 PM Alex said:
If I want to do a CP search page I must implement parametric GetList method "for example: GetList(string lastname, ...)" or I must implement SEARCH method? Can you help me, please?
On Wednesday, April 11, 2007 6:45:00 PM Imar Spaanjaars said:
Hi Alex,

>> Can you help me, please?

Probably not much. You seem to know what to do (create an overload of GetList) and return a generics list of CPs. The concepts of GetItem (a database parameter) and GetList can easily be combined to build you what you need.

Imar
On Wednesday, April 11, 2007 8:46:28 PM Hasan Can said:
Hi,

Does anybody know when you use businessdatasource, how to catch error messages coming from business layer? I would like to show them in formatted label!

Thanks
On Thursday, April 12, 2007 5:04:27 AM Imar Spaanjaars said:
Hi Hasan,

You can handle the *ed events (like Updated, Deleted) and then look at the Exception property of the e argument:

e.Exception

Cheers,

Imar
On Friday, April 13, 2007 11:52:01 AM Noreen Akhtar said:
Hi
I've started doing my project in the object format you describe in the articles but am using vb..i've connected an object datasource to the grid and am selecting and deleting via grid and am using a formview on a separatepage to display update and edit the objects. On updating i'm fine but when i try to delete or create a new contact it's not working..on create i get error list out of bounds..this happens when i click the insert hyperlink it doesnt' even get to my objects insert method..and on delete it does get to the objects delete method but the object passed in is a new object rather than the object that i selected for deletion.
On Saturday, April 14, 2007 7:53:36 AM Imar Spaanjaars said:
Hi Noreen,

This will be difficult for me to troubleshoot through comments on this article, so may I suggest you post a detailed version of your question - including your code and markup - on a forum like http://p2p.wrox.com so others can take a look at it as well.

Cheers,

Imar
On Sunday, April 15, 2007 9:17:04 PM Benny Halperin said:
Hi Imar,

I have a problem with inserting.
Using GridView I use the footer row for entering new values. The table is simple (id - identity, and name) and I need only enter name.

My objectManager layer has Add method that accepts object (custom) as its sole argument like you explained.

The ods of course has its TypeName attribute properly set to "objectManager" and the insertMethod is "Add".

When I handle the Insert button click I call ods.insert() but get the error message:
"System.InvalidOperationException:ObjectDataSource 'categoriesObjectDataSource' has no values to insert. Check that the 'values' dictionary contains values."

Hope this is clear. Any ideas?

P.S. the update works fine but update behavior is built into GridView and insert is not, so maybe GridView collects the values from the columns and constructs an anonymous object, assigns its fields and calls ods updateMethod - objectManager.Update(object).
On Monday, April 16, 2007 3:50:27 PM jhunter said:
I really enjoyed your article, but I have a question about how to implement something.  What if you have a property of one object be another business object.  For example, I have an Article and Member objects. Article has a property called WrittenBy that is of type Member.  When I bind the dataobject to the GridView how do I get the FullName property of the Member class to appear?  I tried making a bound column with WrittenBy.FullName as the value in the DataField parameter, but it says it doesn't exist.  I've tried passing the object in WrittenBy to a method,  (ProcessAuthor(DataBinder.Eval(Container.DataItem, "WrittenBy", ""))) but I get invalid argument error.

I am stuck!

Thanks.
On Monday, April 16, 2007 6:12:39 PM Imar Spaanjaars said:
Hi Benny,

Is the error message correct? What do you see in the Values collection?

Also, does it work with another control like the FormView?

Finally, did you set the DataObjectTypeName on the ODS to the name of your object?

Imar
On Monday, April 16, 2007 6:25:03 PM Imar Spaanjaars said:
Hi jhunter,

For read-only scenarios you can do something like this:

[asp:Label runat="server" Text='<%# ((Member) Eval("Member")).Name  %>' /]

This gets the Member object as an Object, casts it to a true Member and then retrieves its name.
This also works with nested collections; for example when the ItemTemplate of a column contains another data bound control like a Repeater. In that case, you need to use the syntax above to fill the DataSource property.

However, .NET has problems using this type of constructs in two way binding. That is, on a post back it can't construct a proper Article object with a Member property and then set the name correctly.

So, for read-only scenarios, this works fine. For two way display and updating of data, you'll need to consider other ways of doing this.

Cheers,

Imar
On Monday, April 16, 2007 6:32:02 PM jhunter said:
Thank you for the reply Imar, that helps a lot.  Can you point me in the right direction to find ways of doing the two way display and update of the data?
On Monday, April 16, 2007 6:36:06 PM Imar Spaanjaars said:
Hi jhunter,

There are a number of ways to do this. For example, you could create a separate AddEditMember page. When you display the article in the GridView, you could have an additional Edit Member button that redirects to the appropriate page.

Alternatively, you could do the same in-line with a FormView.

Finally, you could handle the Updating event of the ObjectDataSource, manually construct the necessary objects and use FindControl to find the relevant controls inside your GridView.

HtH,

Imar
On Monday, April 16, 2007 6:45:17 PM jhunter said:
Thank you, I was planing on the seperate add/edit page, but thought you might have a better suggestion.  Great article, I've sent the link to several people.
On Monday, April 16, 2007 6:50:11 PM Imar Spaanjaars said:
Hi jhunter,

Thank you. Spread the word, spread the word!!! ;-)

Imar
On Friday, April 20, 2007 1:45:39 PM James said:
Imar,

These 3 articles are excellent and explain the concepts very clearly.

Here I still have a questions:

In these articles, you used the information from the different tables, and make them as objects. If I want to use the information coming from the diffrent tables, can I still make all the information as an object? If possible, there is a lot of same fields between the different objects, expecially, when we do reporting from database. Do you think we can still use this way or any thing else?

Thanks,

James
On Friday, April 20, 2007 3:00:45 PM Imar Spaanjaars said:
Hi James,

You certainly can, and in many cases, it's a logical thing to do.

Often, there is a 1 to 1 mapping between an object and a table, but it doesn't have to be like that. You could store an object in multiple tables, or multiple, similar objects in the same table.

Another thing you can when you have a base class and child classes is store the base data in a base table, and the child classes in separate class tables.

Hope this helps,

Imar
On Friday, April 20, 2007 5:41:57 PM James said:
Thanks, Imar.
On Tuesday, April 24, 2007 5:28:14 AM Przemek said:
Hi,
Finally I got through your tutorial. Nice tutorial by the way.

One quick question. I'm building a spider that goes out on the net and collects information about pictures found on websites. Basically it goes out and finds the path to each picture and stores this in a database so that it can be processed at a later time.
I have a chunk of code that goes through the html and filters out the information so that I can get a path to a picture: ie: www.example.com/pic1.
What is the best place to place such code? My first guess was the code-behind file but on second though I think it should go in the BusinessLogic?
What are your thoughts.
On Tuesday, April 24, 2007 5:40:29 AM Imar Spaanjaars said:
Hi Przemek,

Yeah, I would create a separate class with some methods that extract the content.

Imar
On Wednesday, April 25, 2007 8:02:22 AM Hyzac said:
Superb articles. I just thought to supply a little tip that I found the other day wich works great with the architecture in these articles. Microsoft released in december 2006 the Databindercontrol wich can be found at http://msdn.microsoft.com/msdnmag/issues/06/12/ExtendASPNET/. With just a few changes in the code you can get this to work, and you save some lines of code too. Another tip that I've used is to create code generating templates using myGeneration, fairly easy. I have some "beta" templates for VB.NET if anyone is interested.
On Wednesday, April 25, 2007 8:09:09 AM Imar Spaanjaars said:
Hi Hyzac,

Thanks for the tip. I'm sure many will find this useful. Just a short note: this is not officially released by Microsoft, it's developed by Rick Strahl who wrote an article about it in the MSDN magazine. As such, I think, it's not supported by Microsoft.

Still, it could be very useful in these kind of architectures.

I am interested in your templates for myGeneration. I haven't done a whole lot with it, but it would be interesting to see what you came up with. Can you post them somewhere? Or send them to my e-mail address?

Cheers,

Imar
On Wednesday, April 25, 2007 9:55:12 AM Hyzac said:
Sure no problem. I have plans to put them on my website but until then I will mail them to you.
On Thursday, April 26, 2007 1:30:07 AM Jim said:
Hi Imar,
Thanks for a great article.  This is the best I've read regarding n-tier design.  I've already incorporated this in the app that I'm creating (hope you don't mind).
I have a question on what's the best way to handle a master/detail structure.  Let's say I have 2 tables -- xxMaster and xxDetail.  To be able to do things like Save record, retrieve, delete etc. in your design, should I treat these 2 tables as 1 entity (object) which means I only need to create 1 xx.cs, 1 xxDB.cs, 1 xxManager.cs etc.  If so, in the xx.cs where you enumerate all properties (fields), my xx.cs will have all fields from both tables but what about the fields that both tables have like xx_createdby, xx_createddate etc.?  Also, applying the *DB.cs design in this example, I will have a call to a stored proc that inserts record on both tables.  Is this the right approach?  Apologies if this sounds basic, I'm new to n-tier design.
Thanks again,
Jim
On Thursday, April 26, 2007 9:15:35 AM erick said:
hi,Imar
I've read all your 3 posts about ntier web app. like others,very nice!
my question: If I cant or do not want to use OBS to feed a gridview.  On .net1.1, How can I do when I want to fill a datagrid with custom business objects(like ContactPersonlist class)? Thanks a lot!
On Thursday, April 26, 2007 4:56:38 PM Imar Spaanjaars said:
Hi erick,

You can do it as you're used to: set the DataSource and call DataBind:

myGrid.DataSource = myContactPerson.Addresses;
myGrid.DtaaBind();

Hope this helps,

Imar
On Tuesday, May 01, 2007 4:53:17 PM Grant said:
Hey Imar,

I'm modifying this slightly for use on my website and I want to include a helper class that has a few methods I use a lot. What layer would this fall under and what type of class would you advise I use for this since it needs to be available to all three layers?
On Tuesday, May 01, 2007 5:14:30 PM Imar Spaanjaars said:
Hi Grant,

It depends on what the Helper is supposed to do.

Probably, it's easiest to put things in their own class library, like a Toolkit and then give each layer a reference to this Toolkit.

However, you may need to rethink your strategy. Although it's perfectly OK for some code to be used by all layers, you have to take care if it's really needed in all three layers. Usually, I design Helpers that are used in the UI and the Business Layer, but not in the data layer. Obviously, your mileage may vary, so maybe you do have the need for this kind of code in which case a separate class library is probably the best solution.

Cheers,

Imar
On Wednesday, May 02, 2007 7:03:18 AM Benny Halperin said:
Hi Imar.

Question:

I have a bound DropDownList control (inside GridView for lookup table in edit/insert mode). The ddl is bound to an ods based on your model explained here.

I want to sort the ddl by "name" (a field in the table behind this ods).

The Manager class has a select method:
public static CategoryList SelectList(int iStartRowIndex, int iMaxRows, string sSortExpression)
The method calls the DB class that in turn executes a stored procedure that handles the query with order by etc.

The problem is I can't effect the select parameters (specifically the sSortExpression). I tried by handling the ods_Selecting event and assigning directly the e.InputParameters["sSortExpression"] to no avail. I also assigned the sSortExpression default value (in the ods configuration wizard) to be "name" but nothing seems to work.

Consequently the ddl fills up with the table contents in the order rows has been inserted (natural order).

Any ideas?

TIA, Benny
On Friday, May 04, 2007 9:19:40 PM Imar Spaanjaars said:
Hi Benny,

If you always want to sort by Name, why don't you add an ORDER BY in the stored procedure for that column?

That said, are you sure the SortExpression never makes it into your DAL? It sounds strange that if you set the DefaultValue on the parameter, it's not passed on. Did you also set the SortParameterName on the ODS?

Imar
On Saturday, May 05, 2007 6:49:47 PM Benny Halperin said:
Hi Imar,

I do have a stored procedure with dynamic SQL where I configure the sort expression and the filter expression. It works great with other controls such as GridView. So this is not the problem.

Plus anyway the failure is in the ods_selecting event handler which is called before command chain is seeping down the manager and DB classes. To answer your questions the SortExpression is surely set in the ods.

Having tried back and forth I found another way to access the sort expression in the ods_selecting. That is through ObjectDataSourceSelectingEventArgs.Arguments.SortExpression. Why it fails using e.InputParameters is beyond me.

Benny
On Sunday, May 06, 2007 1:36:01 PM Imar Spaanjaars said:
Hi Benny,

Don't know the answer either, but fortunately, you found a work-around.

Imar
On Tuesday, May 08, 2007 8:17:21 PM Grant said:
Hey Imar,

Couple of quick questions related to the ODS.

I was wondering, you're using Object Data Source in conjunction with a SQL Express DB (in the example files). Because of this in your using Statements you have System.Data.SqlClient, and System.Data.

Could you still use ODS if you were backended by a MySQL Database instead?
If so, would you need to change the using statements System.Data.ODBC then? And how would you advise getting around mySql's not handling transactions or should I be using a different table type than ISAM if I am using a MySQL db for an application like this?
On Tuesday, May 08, 2007 8:21:00 PM Imar Spaanjaars said:
Hi Grant,

Yes, you'd replace all Sql* objects and references to their Odbc counterparts.

Don't know much about MySql so I can't comment on that.

Imar
On Tuesday, May 08, 2007 8:28:39 PM Grant said:
Cool, so I can use object data source with an ODBC database. Thanks!

I was doing some googling and came across this with regard tot he transactions:

Note: By default, MySQL uses MyISAM tables for storing and retrieving data which are not transaction capable and you cannot use transactions with them. InnoDB tables are very fast transaction aware table types for MySQL. You may have to configure a few properties to make MySQL start InnoDB engine to make use of transactions in your code.
On Tuesday, May 08, 2007 8:34:44 PM Imar Spaanjaars said:
Hi Grant,

Yes, you certainly can. One of the ideas behind the ODS is that it hides the implementation. So, you could talk to MS SQL Server, MySQL, Oracle, XML, or all at once, combine the data and return it to the DAL.

As long as your data source can be reached by .NET code, you can consume data from it in your ODS controls.

The idea behind the design of my sample application is that all you need to change to make this happen is the DAL. There is no data source specific code in the BLL or BO layers, so simply recode the DAL and everything should still work.

Cheers,

Imar
On Friday, May 11, 2007 4:15:46 PM Jeff said:
Hey Imar,

I was looking at the source code in the download and I have a question about the GetList() function for EmailAddressList. What you have is:

    public static EmailAddressList GetList(int contactPersonId)
    {
      EmailAddressList tempList = null;
      using (SqlConnection myConnection = new SqlConnection(AppConfiguration.ConnectionString))
      {
        SqlCommand myCommand = new SqlCommand("sprocEmailAddressSelectList", myConnection);
        myCommand.CommandType = CommandType.StoredProcedure;
        myCommand.Parameters.AddWithValue("@contactPersonId", contactPersonId);
        myConnection.Open();
        using (SqlDataReader myReader = myCommand.ExecuteReader())
        {
          if (myReader.HasRows)
          {
            tempList = new EmailAddressList();
            while (myReader.Read())
            {
              tempList.Add(FillDataRecord(myReader));
            }
          }
          myReader.Close();
        }
      }
      return tempList;
    }

and I don't see where you close the connection for myConnection.

Should it be like this:
...
...
          myReader.Close();
        }
         myConnection.Close();
      }
      return tempList;
    }
On Friday, May 11, 2007 7:27:27 PM Imar Spaanjaars said:
Hi Jeff,

Notice how both the connection and the reader are wrapped in a using block. Using ensures in this case that Dispose is always called, which in turn closes the connection.

Cheers,

Imar
On Friday, May 11, 2007 7:38:09 PM Przemek said:
Performance:
From discussions above I know that I can store contact type information in the database as an integer 1,2,3 or storing the literal value for the type: business, home etc...
I am building a search engine into my web application that will search, among other values, this type value. Is it better, for performance reasons, to store the type in its literal representation in the database or as the integer based one?
I am assuming that the literal representation is the better option because then I can use SQL commands to search through tables instead of pulling the values out of the database, figuring out what 1, 2 or 3 means and then finally comparing them.
Let me know what you think.

Thank you.
On Friday, May 11, 2007 9:06:49 PM Imar Spaanjaars said:
I doubt you'll notice the difference, so I'd choose what works best for you and makes the most sense. IMO, storing a reference is a better idea, as it allows you to change the description without modifying records.

But if performance is so important, why don't you try it out? SQL Server comes with tools like the Query Analyzer that shows you execution plans for your queries so you can see what works best for you. Measure, measure measure, very important when dealing with performance issues...

Imar
On Thursday, May 17, 2007 3:39:29 AM Erick said:
old question: In my own projects, where to find a tool that can generate dal layer, bll layer and business objects?There are too many code that must be input, If u say codesmith, do U have a template?
On Thursday, May 17, 2007 7:13:44 AM Imar Spaanjaars said:
Hi Erick,

There are no ready-made tools for this in Visual Studio. You can use the Class Designer to create the BO's and that's about it.

Most of the code in this article is written with a custom, in-house code generator. However, there are other tools like CodeSmith, myGeneration, llblgen or even DevExpress's CodeRush that can help you quickly write this ode.

I don't have any templates for these generation tools, but maybe other readers of these articles do. I saw beta templates for myGeneration from Hyzac some time ago, and there may be others as well. So, if anyone has tool templates for the concepts presented in this article: please post a link here, or send them to me and I'll be happy to host them.

Imar
On Friday, May 18, 2007 5:29:57 PM ajwaka said:
Excellent Article - much appreciated!  But I have a question..
I'm using your ideas to build an ImageDirectory.  So I add images to Categories - Can be Multiple Categories.  So In my ImagesBO I have a CategoryList which is (as you have done) a List[Category] (not array - generics class).

I'm displaying the Categories as a CheckBoxList (this is probably why the headaches) and cannot figure out how to update the Image Categories.  I don't want to delete all and add all selected check boxes.  I want to remove the "Category" from my CategoryList of the image if it's not selected anymore.  And vice versa...  Any insight on how to accomplish this?  

I'm just not catching on to the Delegate and such of the Lists[] (not array) class (from googling)

Thanks!
On Saturday, May 19, 2007 8:03:44 AM Imar Spaanjaars said:
Hi ajwaka,

One way to do this is to created a DeletedList on the collection class that keep track of the items that have been deleted.

When you update the item in a web page and before calling Save, you get a fresh copy from the database. For each unchecked item, you move the item from the normal list to the DeletedList. Checked items are set on the normal list.

Once you need to hit the database, you delete the items in DeletedList from the database and add / check the items in the normal list.

Hope this gives you some ideas.

Imar
On Monday, May 21, 2007 4:00:41 AM MyTeamGo said:
Hi Imar,

Article ID: http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=420

I have read all 3 of your articles on building layered web applications - they are very useful and well written. I have also bought and read your book ASP 2.0 Instant Results - very good book and examples.

I do have a 2 questions and would like your comments on how best to implement it (this applies to both this example in the article and the BugBase example in your book):

a) How do I deal with business rules and validation rules and showing them on the webpage.
For example - lets assume the ContactType - Business and Personal was in a database table and I have a web maintenance page to Show/Add/Edit/Delete this information. How would you implement the Business rule that a contacttype cannot be deleted as it has Contacts associated with it and dynamically hide the delete link/button on the main datagrid ?

b) What is the best way to deal with errors and exceptions like database is down versus application errors like business rules and differentiate them at the UI layer. For example, database errors get logged/emailed versus application errors like "record could not be deleted or updated" show up to the end-user on the web page

Thanks
-mTg
On Monday, May 21, 2007 6:40:26 PM Imar Spaanjaars said:
Hi MyTeamGo,

a) Check out the question by Kurt Gooding  on 5/1/2007 6:53:09 and the replies that followed in Part 2: http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=419

Hiding the relevant controls may be more problematic. You can take a look at the CSLA Framework that supports these kind of scenarios: http://www.lhotka.net/

b) Same story; check out my replies to Kurt and check out the CSLA network.

Sorry I can't be more specific about this. The type of questions you ask are so broad it would take another few articles to answer them all in detail.

On Tuesday, May 22, 2007 4:23:21 PM Mowie said:
I want to do a very similar thing to what you do here, but with an added problem.

Lets say your contact has 'comments' section where someone updating your contact can add/update/delete a comment for the contact.  The way I want to represent this is to have a gridview of comments (1 to many).   But if I bind this gridview to a objectdatasource,  the savemethod from my business layer will fire independently of the save method for the rest of the data (i.e information that resides outside of the gridview).  Basically I want the save for the page information to save the information/changes in the grid within the same transaction.  

The example provided here has a save button click for the page, and doesn't use a save method for the objectdatasource.  
On Thursday, May 24, 2007 12:11:25 PM Imar Spaanjaars said:
Hi Mowie,

I am not sure I understand what you're asking. Can you elaborate a bit?

Imar
On Friday, May 25, 2007 4:35:24 PM Rob said:
Could you refer me to an article that deals with hierarchical dropdowns.  Specifically, I am trying to figure out how to represent these as objects.

Thanks.

-Rob
On Friday, May 25, 2007 11:27:31 PM Imar Spaanjaars said:
Hi Rob,

Just search Google for this; there are many articles that deal with this subject. Whether you deal with objects or not as your data source doesn't really matter; you'd simply call GetList to get the data just as I showed in the article, although you my need a specialized version of it that accepts the ID of the parent.

Imar
On Friday, June 01, 2007 12:08:49 AM Rob said:
Help!

I have the following objects:

Project
_ID       of type int
_Name  of type string
_Airport of type Airport    

Airport
_ID       of type int
_Name  of type string
_Desc   of type string

I created a ProjectDB.GetItem() function which will return all Project fields including the field named _Airport which is an object itself that is fully populated with its own respective fields.

In an aspx page, I use an ObjectDataSource and link it to the GetItem() function for Select.  Then I use a FormView and set the datasource to the objectdatasource.  This works out just fine.  

My question is how or what can I use to display the Airport object information which is "one level deeper" in the Project object?  Do I modify the ItemTemplate of the FormView?

What is this concept called?

As always, thanks again!

Note, only one Airport per Project.  It is not a List^Airport^.  If this were the case I could use a gridview in the itemtemplate of the formview and set the DataSource to the property returning a List^Airport^ because the object returns a "List."
On Friday, June 01, 2007 4:50:54 PM Rob said:
In response to post: "On Friday 6/1/2007 2:08:49 AM Rob said: ......."

I found an "old-school" way of digging into the object from within a formview....

^%# DataBinder.Eval(Container.DataItem, "Airport.Name") %^

Remember, the overall FormView is boud to the Project ObjectDataSource.

Spaanjaars is there a better way?

Thanks again.
On Sunday, June 03, 2007 8:36:55 PM Imar Spaanjaars said:
Hi Rob,

You can call me Imar; Spaanjaars is my last name.

Anyway, you can also cast the object and then use its properties:

%# ((Airport) Eval("Airport")).Name %

This casts the Airport object back to an Airport so you can access its properties.

Imar
On Friday, June 08, 2007 5:07:40 AM shahid said:
I just want to select row from child record is there possible through this artical plz reply me
On Friday, June 08, 2007 6:34:32 AM Imar Spaanjaars said:
Hi shahid,

What "row" are you talking about and what "child record" are you referring to?

Is this in any way related to the orginal N-Layer design topic of this article?

Imar
On Tuesday, June 12, 2007 8:54:07 AM Samaan said:
Hello
thanks for your article ,it was so helpful for me .
Actually i want to reuse your article in my Website and I create the tables and Stored Procedures to the database but I got following errors:


Error 2 Cannot convert type 'ContactManager.BO.Address' to 'Address'


Error 13 The best overloaded method match for 'string.IsNullOrEmpty(string)' has some invalid arguments


and ...!


Can you please help me to solve the problem.
Thanks in advance
On Tuesday, June 12, 2007 9:01:47 AM Imar Spaanjaars said:
Hi Samaan,

Like I said in my person e-mail message to you, you're passing the wrong stuff to the Add method. You seem to be passing a Type, rather than an instance of that type which is what the Add method of the Generics lists expects.

You may want to review your design, this article and your code again; if you have specific questions, maybe you're better off posting your questions at the Wrox forum at http://p2p.wrox.com It's much easier to post code there, and explain your problem in more detail.

Cheers,

Imar
On Tuesday, June 12, 2007 12:14:19 PM Samaan said:
Thanks for you reply,I changed the codes and all but still after I press save in the Contacts i get in the error pageas follow:
Cannot insert the value NULL into column 'id', table ...\APP_DATA\DATABASENALAYER.MDF.dbo.ContactPerson'; column does not allow nulls. INSERT fails.
The statement has been terminated.
The 'sprocContactPersonInsertUpdateSingleItem' procedure attempted to return a status of NULL, which is not allowed. A status of 0 will be returned instead.
Sincerly,
Samaan
On Tuesday, June 12, 2007 12:27:10 PM Imar Spaanjaars said:
Hi Samaan,

Try Google for this error; it's a very common problem and it's a bit outside the scope of this article.

Short version: Id is required, and in my database it is an identity column. Apparently, in your application it isn't so you eiher need to supply it a value, or make it an identity.

Cheers,

Imar
On Wednesday, June 13, 2007 8:49:31 AM SR said:
Hi,
I tried your example, but I get an error when the AllowSorting option of the gridview is set to True and I click on the header of a column.
That said the sort works only with dataview, datatable and dataset.
How can I convert or "store" the Bll for working with option ?
(sorry for the poor english).
On Wednesday, June 13, 2007 8:54:38 AM Imar Spaanjaars said:
Hi SR ,

Check out my comments of 3/13/2007 to Bliek in this article.

Cheers,

Imar
On Wednesday, June 13, 2007 2:58:08 PM SR said:
You're the best.
I applied what you ve said to Bliek using the sortExpression on my SQL Query and it seems it works well..

However, what do u mean by "Alternatively, create a class that inherits from IComparer[ContactPerson] (replace [] with generics brackets) that performs a custom sort. Then use the Sort methods of the Generics list to sort it with your custom IComparer object." ?

I have your book (Asp.net 2.0) and I suppose all the concept is on the page 432, right ??
The only problem :-) with your book is all the examples are in VB.net.. :(
And I don't know how you create the BugComparer class (I know it's Off Topic)..

Anyway, what would be the best practice between the sql query using the sort expression and the one you explained into your book ?



On Wednesday, June 13, 2007 7:37:02 PM Imar Spaanjaars said:
Hi SR,

I just finished a short article about custom sorting with N-Layer design classes and the GridView. Check it out here:

http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=428

Cheers,

Imar
On Thursday, June 21, 2007 8:20:24 AM SR said:
Hi Imaar,
I have a BO  that "encapsulates" another BO.

myClass.name  <--- String
myClass.Country <--- BO

The Country Class comes like this
myCountry.Id;
myCountry.Name;

So if I need to display the country, I write myClass.Country.Name
Everything works.

But my problem appears when I try to use a gridview.
When I add a boundfield like this
BoundField myBoundField= new BoundField();
myBoundField.DataField="Country.Name";

Everything works if I remove this boundfield.

I get an error.
Do I miss something ?

On Thursday, June 21, 2007 8:44:09 AM Imar Spaanjaars said:
Hi SR,

ASP.NET doesn't support this scenario, but you can work around it. Create a template column and then use Eval to bind the data:

Text='[%# ((MyClass)Eval("MyClassColumn")).Name %]'

This casts the column MyClassColumn to a MyClass and then accesses its Name property.

On postback you'll run into more problems do, as the object is not reconstructed automatically for you.

Imar
On Thursday, June 21, 2007 9:04:52 AM SR said:
Imar,
I thank you.
I add a new property to my main class that returns the CountryName

public string CountryName{
get {return this.Country.Name}
}
So, I use CountryName for the gridview.

But I don't know if it's a good practice.
What's your advice ?
On Thursday, June 21, 2007 8:21:14 PM Imar Spaanjaars said:
Hi SR,

Considering the limitations that ASP.NET 2 has with regards to complex classes as properties, I think it's a nice work around. As long as you make sure that interally you keep an instance of your MyClass and delegate its properties to simple properties on your container object, you should be good to go.

Cheers,

Imar
On Monday, June 25, 2007 7:30:33 AM SoftMind said:
Hello Imar,

This Three Part tutorial was Great. I am more interested to know about your future plans of converting the same 3 part tutorial with LINQ support  ( I mean the same Tutorials, but LINQ way )

LINQ is the perfect way for ORM or LINQ to SQL. Finally there is a perefect answer to all database related problems. I know  LINQ will take 3 - 4 months to arrive officially, but you can always keep it ready as  solution for Future.

I am also looking forward to all Scott Mitchel tutorials in LINQ way by MS.

Converting your " Instant Results Code LINQ way " is also appreciated for future blogs.

Thanks

SoftMind
On Monday, June 25, 2007 7:37:40 AM Imar Spaanjaars said:
Hi SoftMind,

Thank you for your suggestions. Don't expect anything soon (or at all) though....

Cheers,

Imar
On Thursday, June 28, 2007 6:06:42 PM David said:
Great Articles on ASP.net and Ntier! Makes creating NTier much-much easier with this clear, simply, and to the point this example. Thank you so much for creating these articles!!!  Very well written in an easy to follow format.

Nice Job!

David
On Friday, June 29, 2007 5:29:20 PM Imar Spaanjaars said:
Hi David,

You're more than welcome; glad you like it.

Cheers,

Imar
On Friday, July 06, 2007 12:15:31 AM Timoshka said:
Hello Imar. Just one word about articles - SUPER! Now I'm looking forward the  time to refactor my project code in accordance with this :) .

Can I ask you some questions if you don't mind?

1. In the post to SR about grid view binding of complex objects, you say: "On postback you'll run into more problems do, as the object is not reconstructed automatically for you". I think, I have this problems now. On postback (while using paging) I'm loosing property that represents List[BO]. I tried to bind this property to my own server control inside a grid view but debug shows that datasource of my control in its PageLoad is always null on postback. Could you give me idea how to resolve this? May be this issue is related to ViewState or Serialization... Anyway I couldn't manage to find solution.

2. Back to the tutorial. Say, I have BOs Person and generic PersonList. Now I want to build a page that shows Persons plus some additional properties. Of course I can create a new (or inherit) BO PersonEx and the corresponding PersonExList and PersonExDB and encapsulate new properties. But it seems to me a bit ugly. Are there any other ways to organize my BOs, how to fill BOs from IDataReader in this case and how to bind. May be, using a hashtable? ht[Person] = PersonData, ht[NewProp1] = NewProp1Value, etc. Have you any thoughts on this?

3. Currently I'm working at project that has one feature: when some event fires (on the web site or web service), say it ALERT, the system makes a snapshot of data in DB (in order future changes don't affect data at the event moment). Data is very complex (hierarchical with many relations), so I left thoughts to implement something like data archivation in relational structure or make snapshots of each record of a table while its editing. Generally, these ALERT snapshots are readonly, so I decided to store them in XML and use XSLT to show them at my pages. Imar, I'd like you to advise me how to organize behavior related to XML serialization and XML transformations. Where should I put the corresponding methods? May be you can suggest another way different from XML? I'm new in mixtures of XML and relational data and I cannot understand how my BOs, BLL and DAL should look like. May be I should use deserialization in spite of XSLT...

4. This question is similar to the second but has real applications. Suppose  your ContactPerson has properties like PersonalWebPageHTML, Avatar, etc - something that uses Blob and Text in DB, that usually is not shown at list of ContactPerson. I mean the web page. I need some of these properties be populated in specific cases. To use boolean flag to populate for every such property solves problem in DB. But how to implement method FillBOFromReader? What if a data field you expect is absent in reader? Is it good idea to write the following code for each such property:

MyBO bo = new MyBO();
try
{
   bo.PersonalWebPageHTML = reader["PersonalWebPageHTML"] .ToString();  
} catch {}
try
{
   bo.Avatar= SomeCodeToExtractImage(reader["Avatar"]);  
} catch {}
return bo;

Many thanks for help and articles.

Timoshka
On Friday, July 06, 2007 6:38:55 AM Imar Spaanjaars said:
Hi Timoshka,

1. If you don't rebind on postback, the data source will be null. Not the entire data source is stored in ViewState.

2. What's wrong or ugly on inheritance?

3. I lost track of what you were trying to see somewhere halfway down that large lump of text.

4. If I understand you correctly: no don't use try/catch. IMO, it's a bit ugly to do that. Instead you could check if the column exists first or create specialized overloads that fill only the necessary data.

Sorry I can't answer you in more detail. You're asking way too many and broad questions in an unreadable format for me to answer. My I suggest you repeat some of them on http://p2p.wrox.com ? Easier to post and share code there.

Cheers,

Imar
On Friday, July 06, 2007 11:27:51 AM Timoshka said:
Sorry, Imar, for this poor formatting. Cannot understand how this happened. I caught your points. Don't mind about my messy question about XML, I will try different ways myself and choose the best.

Thank you for your work again.

Timoshka.
On Saturday, July 07, 2007 5:39:09 AM Robert Searing said:
VERY nice articles.  In fact, I am using them to model a new site I am working on.  I was having difficulty deleting from a gridview I set up and realized I guess I didn't understand how a Gridview deletes.  But this raises an underlying question about your logic:

a)  How does the Gridview ObjectSource know how to pass a ContactPerson to the BLL function?  What I mean by this, is when you are saving a new ContactPerson, do....ok, I am going to stop here, as I just returned from checking the code.  My original question was how does the page, when deleting, pass a ContactPerson to the BLL without you manually having to populate it.  I was originally going to digress and say that you had to manually populate the BO when you save, but you don't.  So, how does a Gridview populate the BO that it passes to the BLL?  (I'm talking high level here).  I mean, I guess I would have to assume that the Datafields get passed into the properties of the BO.

b) When I was setting up the Gridview and adding Delete capability, it automatically puts in a Delete parameter (since my method in my BL did I assume).  My method, however, was Public shared Delete(byVal awardID as integer) as Boolean  I didn't have Delete(byVal BusinessObject as BusinessClass)..and then I was getting an error about didn't have a Delete Method that had id, and awardID...until I deleted the parameter.  BUT, by deleting the parameter, I have to assume that it autopopulated the Datakey into the awardID parameter of my BL method.

I guess that's a mess of about 2-3 different confusion areas, but broken down and restated:

-how does your method autopopulate the Delete(byVal contactPerson) - I assume this is much like the answer to A above?
-Is there any significant difference between your Delete(byVal contact as ContactPerson) and my Delete (byVal awardID as Integer)?
-why does the GV autoprovide a DeleteParameter that I wind up just deleting anyways?  How does my delete work without it--by DataKeyNames?

I guess I'm getting confused as to how controls actually work when dealing with objects.

Thanks,
Rob
On Saturday, July 07, 2007 6:50:24 AM Imar Spaanjaars said:
Hi Robert,

It would really help your reader if you also updated (or cut) your comments if you changed your thoughts half way down the road. Then you can ask your question in a single paragraph instead of rambling on for an hour. It's early in the morning while I am reading this, and I am having hard time trying to follow you... ;-)

Anyway, ASP.NET does all this. When it sees you're using a method that expects a BO, then the ASP.NET run-time creates a new BO for you (that's why you need, by default, a public parameterless constructor in the BO although you can override this behavior)

It then copies all properties it finds from ViewState or the Form (set up with Bind) into this new object and passes it to the method.

Same goes for other methods: it tries to find properties by the name that the BO method expects. The values for these properties are then passed to the relevant method.

In the end, delete with a BO or with ID doesn't really matter. A BO may be slightly slower, especially with large BOs since all the data needs to be copied, only to be deleted again. I usually have two overloads: one for a BO and one that accepts an ID only. The BO version then calls the ID version and passes myBO.Id to it.

Can't answer the final question since I don't know how your code looks like.

Cheers,

Imar
On Saturday, July 07, 2007 1:34:07 PM Robert Searing said:
I apologize for rambling.  I wasn't really talking about my code specifically, but (I realize now I mispoke, as it wasn't the Gridview, but...) when you drop the ObjectDataSource on the page and point it to the delete method of the BLL, it creates a DeleteParameter (that matched, in my case, the Parameter of my BLL Delete Method).

Unlike your Delete Method that was:

Delete (something As ContactPerson) as Boolean (I believe--sorry, on dialup connection at in-laws)

Mine was:

Delete (id as Integer) as Boolean

So, when I pointed the objectdatasource to my method, it created a Delete Parameter.  I guess that's the default behavior?  That's what was getting me--as I then assume if you delete the parameter, the ObjectDataSource must pass the id in via the DataKeyNames?  I'm just not sure how it the objectdatasource deletes in both cases.  If I try to think logically, I would say if the Delete method in the object is an object (as is the case with your code) it must populate the Datafields into an object and pass to the delete method.  If, as in my case, I just have an integer---it passes the datakey into the delete method?
On Saturday, July 07, 2007 4:38:52 PM przemek said:
I am binding my enums to a drop down list just like you did in your example: SelectedValue='<%#Bind("Type") %>'.

The problem I'm having is that I am using nullable types and for some of the results the database returns 'null'. Consequently I get the following runtime error: "'AgeDropDownList' has a SelectedValue which is invalid because it does not exist in the list of items.
Parameter name: value" when my applicaton tries to find null in the drop down list.
I've tried manually adding null the the list as a possible value and it still gives me issues.
Any ideas?

On Monday, July 09, 2007 1:11:49 PM Imar Spaanjaars said:
Hi Robert,

Yes, that's what is happening. Either the entire object is reconstructed from form and view state, or just a key (or multiple keys) can be passed.

Imar
On Monday, July 09, 2007 1:14:33 PM Imar Spaanjaars said:
Hi przemek,

If the value can be null then you need find other ways to overcome databinding problems. Adding the *text* null, doesn't make it a null value; just a string with the text null.

You can use the FindByText or FindByValue methods of the Items collection of the DropDownList to preselect an item. If these methods return null, then preselect nothing or a default option like "Please make a selection"

Cheers,

Imar
On Tuesday, July 10, 2007 12:25:03 PM Richard Lelle said:
Hello Imar.....I see you've been very busy.  That's why you're paid the big bucks!

I am virtually certain that I followed your examples as close as possible, but I am having some difficulty when trying to access a form that contains my Gridview.  Upon page_load, I hard code key data for the parameters that my sproc will use for the GetList.  I keep getting the following error:

System.IndexOutOfRangeException: actvDt  <--- 'activity Date'

I get this error in my 'FillDataRecord' method in the DataBase class of my app:

private static ParticipationActivity FillDataRecord(IDataRecord myDataRecord)
        {
            ParticipationActivity myParticipationActivity = new ParticipationActivity();
            //myParticipationActivity.actv_dt = myDataRecord.GetDateTime(myDataRecord.GetOrdinal("actv_dt"));
            myParticipationActivity.actv_dt = myDataRecord.GetDateTime(myDataRecord.GetOrdinal("actvDt"));
            myParticipationActivity.actv_id = myDataRecord.GetInt32(myDataRecord.GetOrdinal("actv_id"));
            return myParticipationActivity;
        }

Where/how would I initialize an index for this?  I don't recall reading anything about it in the articles.

Richard
On Tuesday, July 10, 2007 6:32:18 PM Imar Spaanjaars said:
Hi Richard,

Big bucks? Not sure where you base that on.

Anyway, the error seems to suggest you're referencing a column that doesn't exist. So there's a fair chance that actvDt or another column is missing from  your SQL statement that drives the Reader passed to FillDataRecord.
Check your SQL statement (in-line or stored procedure) and see if it has all the columns you need.

Hope this helps,

Imar
On Thursday, July 12, 2007 5:19:56 PM Rob said:
Imar,

Based on the Role a user is in, how can I allow/disallow edit|new|delete buttons in the FormView, DetailsView or GridView.

Thanks.
On Thursday, July 12, 2007 5:45:18 PM Imar Spaanjaars said:
Hi Rob,

How is this related to the original topic of the article?

Anyway, you can use User.IsInRole to check the role and hide the button:

btnDelete.Visible = User.IsInRole("Administrators");

You need to "find" buttons within a DetailsView or other control first using FindControl....

Imar
On Saturday, July 14, 2007 8:51:54 PM John C said:
I am learning so much from this article and from your code.  This is a Must Read article for anyone doing anything with a database in .net.  I have been going crazy trying to figure out the best way to design my current application and have been having trouble getting started.  Now I have a great start and direction!  Thank you so much for the great article.

John
On Saturday, July 14, 2007 10:42:59 PM Imar Spaanjaars said:
Hi John,

You're more than welcome.... Spread the word, spread the word!!!! ;-)

And good luck with your application.

Cheers,

Imar
On Tuesday, July 17, 2007 2:19:20 PM Richard Lelle said:
Hello Imar,

I have finally been able to get my app to function.  The gridview pops up and is populated with all the correct data except for one column.  It is a 'date' field that is repeated for all rows.  The gridview should show data within a daterange, with the date showing on each row.  Instead, only the latest date of the daterange is shown in every row.  I inserted a breakpoint to watch what was being loaded in the 'filldata' method.  All the dates within the daterange appear to be getting loaded to the 'tempList' table.  Is there some property that I must set somewhere?

fyi - my previous problem with the 'actv_dt' (on 7/10) had to do with my stored procedure.  It was not getting the data from the db tables like I thought it was.
On Tuesday, July 17, 2007 2:50:30 PM Imar Spaanjaars said:
Hi Richard,

Hard to tell without seeing your code. Can you post it on a forum like p2p.wrox.com?

Imar
On Tuesday, July 17, 2007 3:36:27 PM Richard Lelle said:
Posted, under 'asp.net: beginner'.  The subject is 'gridview/repeating data'
On Wednesday, July 18, 2007 8:06:25 AM Zhang Feng said:
Thanks for your effort.
These three articles do help me much. But I found that even small application like your sample need to write lots of code. Especially the BO Layer, it seems like a simulation of the underlying DataBase structure.  What if there will be lots of object(Tables in DataBase that present a Object) ? In the real world, the enterprise software usually contains 30 or more tables, and the main table usually contains 10 or more fields, I doubt it will be spend much time to implement them.
Sorry for my poor expression, I'm a Chinese ...
On Wednesday, July 18, 2007 9:26:39 AM Imar Spaanjaars said:
Hi Zhang,

Yes, correct. Large implementations require lots of code.

You can use code generators to have the code generated for you, or use tools like NHibernate to take care of persistence. In addition, you can look at the upcoming Linq technology to minimize the code you need to write.

Cheers,

Imar
On Wednesday, July 18, 2007 9:32:18 AM SR said:
Imar,
I talked to all the persons who posted a message.
And here's the result of my poll..
You have to write a tutorial about how to use MVP (Model View Presenter) with asp.net ;-)

Stan
On Wednesday, July 18, 2007 9:42:27 AM Imar Spaanjaars said:
Hi SR,

I don't *have* to do anything ;-)

Imar
On Wednesday, July 18, 2007 10:03:08 AM SR said:
hum.... may be I use an incorrect term..So let me rewrite
Ce serait super si tu pouvais nous expliquer comment utiliser le Modele Vue Controlleur en asp.net
It's a smooth version of my previous post.
Anyway, I hope you will talk about MVP on your next book (but do it as simple as you do it with the N-layer tutorial).
Frankly I like the way you wrote it.
Stan
On Wednesday, July 18, 2007 10:09:54 AM Imar Spaanjaars said:
Hi SR,

My French sucks, but I think the answer is "don't count on it". I am working on a new book so it'll take a long time before I have the time to write articles again.

Imar
On Saturday, July 21, 2007 7:28:32 AM Liaqut said:
Dear Sir,
I appreicate this article. I would like to know whether the ObjectDataSource will work on Winform. If so, please give me
a sample.

Thanks and Regards
Liaqut
On Saturday, July 21, 2007 7:38:55 AM Imar Spaanjaars said:
Hi Liaqut,

You may want to Google for BindingSource and BindingNavigator. They are used in data binding scenarios in WinForms.

Imar

On Tuesday, July 24, 2007 3:10:06 AM Robert Searing said:
Imar,

I have loved this so much, that I am using this design for another site I am working on and am about 80% done.  It has worked beautifully.  I've had to tweak it a bit and do research on how to get to particular controls nested in others--but it's really worked and actually helped me understand quite a bit about ASP.NET 2.0

My question that I have struggled with for some time, but have been able to avoid up until now, revolves around just how the control creates the "object" that it will bind it's data controls (bound Text Boxes, etc) to the properties of itself.  I am trying to, programatically, figure out how I can access that object so that I can change a value in my object.  For example, I have a "paintings" object that has a property "painting type" (ie oil, watercolor, etc) that is it's own table.  So, in the edititemtemplate of my detailsview, I drop a dropdownlist on and have a different objectdatasource to populate it.  How can I grab the selectedvalue of the dropdown list and basically insert that ID into the "paintingType" member of my Painting Object that is binding in the DetailsView?

Kind Regards,
Rob Searing
On Tuesday, July 24, 2007 8:26:47 PM Imar Spaanjaars said:
Hi Rob,

Search this particle for fv_ItemInserting; you'll see how it's done with the FormView...

Instead of a simple dictionary of key / value pairs, you may also find that sometimes the e.Values / NewValues holds a reference to the entire bsuiness object. If that's the case, you can cast the object to your own type, and set properties on it.

The control uses the object's default, parameterless constructor to create an instance of it. If you don't like that behavior, you can handle the ObjectCreating event of the ODS and create your own.

Imar
On Tuesday, July 24, 2007 9:32:50 PM robert searing said:
Imar,

Thanks for the reply.  Would this also work for detailsview?  I'm not using a FV.

Thanks,
rob
On Wednesday, July 25, 2007 12:36:27 AM Robert Searing said:
Ok---I figured it out and used:

Dim myDropDownList As DropDownList
        myDropDownList = CType(DetailsView1.FindControl("DropDownList1"), DropDownList)
        e.NewValues.Insert(e.CommandArgument, "ptype", myDropDownList.SelectedValue)
On Wednesday, July 25, 2007 1:51:14 PM Nick Nelson said:
Imar,

Great articles. I often see them mentioned in other forums with people using them as the basis for their own application frameworks, which is what I'm doing.

I've decided to use the 'standard' selectable gridview master, form/detailsview setup (basically in order to keep the designers job as separate as possible from the coders).

I have a strange problem in that I set up a detailsview with an ODS populating it based on the selection made in the gridview but no results are returned i.e. the details view is always displayed but always empty. Just to test the underlying method worked I created another gridview with the same ODS as the detailsview and this gets populated correctly.

The ODS uses my equivalent (exact copy apart from the names) of your contactperson GetItem method. I don't want to ask you to debug my application but do you know of anything that might cause this behaviour as I've googled and come up with nothing?

Thanks.

Nick




On Wednesday, July 25, 2007 2:11:28 PM Imar Spaanjaars said:
Hi Nick,

The only thing I can think of is that the control is a) invisible, or b) set up in an incorrect mode or data source??

Have you tried recreating the control?

Imar
On Sunday, July 29, 2007 4:11:59 AM John Cross said:
Imar,
I have just finished reading your book, and the Bug Base has a search feature built in that I would like to modify to work with the code from this article.  I am having trouble because the book uses vb.net, and it is complicated (at least for me.)  I was wonder if there is a c# version of the code, or if you could suggest another way for me to go.

Thanks,
John
On Sunday, July 29, 2007 8:30:09 AM Imar Spaanjaars said:
Hi John,

Sorry, no, there is no C# version of the BugBase available. Readers have started translating some of the chapters (http://p2p.wrox.com/forum.asp?FORUM_ID=249) but unfortunately the BugBase is not one of them.

You could use an on-line converter like http://converter.telerik.com to convert the code from VB.NET to C# to get a good start.

Then if you still have questions, feel free to post them in the Wrox forum. There are many people, including myself, willing to help you with specific questions.

Hope this helps a little,

Imar
On Sunday, July 29, 2007 9:17:42 AM Amjad said:
Cool
Instead of writing code from the scratch, what if I use CodeGenerator like LLBL Gen Pro and it is producing BLL,DAL and BO. Look great…

Would you please advise me to go ahead with this… or is there any disadvantages of using this
On Sunday, July 29, 2007 9:23:46 AM Imar Spaanjaars said:
Hi Amjad,

Yes, you can certainly use code generators with this model. In fact most of the code from this article was generated by an in-house built generator I use at work.

I don't have any ready-made templates for those generators although I'll be more than happy to host them if someone offers them to me.

Cheers,

Imar
On Monday, July 30, 2007 7:53:58 PM Richard Urwin said:
I was wondering if you had any thought on how O/R mappers can fit into a nicely separate n-tier architecture. I'm having problems separating my architecture because I'm using DevExpress's XPO.

They're very useful for rapid development. but  I can't get away from using them in the CodeBehind, i.e. calling MyObject.Session.Save() in the Save_Click(...) handler on the page, which isn't the place to do database access.

Should I wrap them and expose only the properties that I need? The problem with this is that it rapidly becomes very complicated since when accessing, say a collection of XPOs through a property on the original XPO, you have to wrap the getter, read all of the objects in the collection, wrap them one by one, then place them in, say, an array and return this. It seems messy and complicated. Effectively you're wrapping something that already has a complicated set of relationships.

I could create a collection of lightweight business objects and create manager classes to convert between the two (much as is done in these articles to convert between datatables and business objects) but that suffers the same complexity issues as above.

Alternatively, should i get over my unease at using them in the CodeBehind? I just don't like the fact that they have their Session connection to the database as a property on them, allowing you to save, etc., simply by calling MyObject.Session.Save() from whichever tier you like, within your application.

Is there another way?

cheers,
rich
On Monday, July 30, 2007 9:06:56 PM Imar Spaanjaars said:
Hi Richard,

Interesting situation.

I see at least two options:

1. Accept the fact you're calling Save() in the web layer. One of the reasons for technologies like XPO is to abstract all data access away to separate layers / objects / technologies. So, you're not directly accessing the database in the web front, you delegate responsibility to an object that knows what to do.

2. Create a layer around your objects using Generics. E.g.:

public class MyWrapper<T> where T : XPObject
{}

This allows you to create a generic layer around your objects, making it easier to pass them around. You don't need to write a wrapper for all your objects, just a generic one. Then you can instantiate your wrapper class with your proper BOs.

Hope this helps a little.

Imar
On Monday, July 30, 2007 9:17:52 PM Richard Urwin said:
Hi Imar,

It does - thanks. I guess if one is happy abstracting the data access to objects, albeit ones instantiated in the web layer instead of other layers, it's not so bad.

thanks!
rich
On Tuesday, July 31, 2007 4:28:00 AM Amjad said:
Hi Imar,
Thanks for your prompt reply.
I found it is very hard to modify the code generated by the third party CodeGenerators.

Can you share with me your inhouse code generator?
Or can you suggest me a good codegenerator which comply with your N-Layer Architecture

Thanks,
Amjad
On Tuesday, July 31, 2007 6:01:43 AM Imar Spaanjaars said:
@Richard: yeah. maybe it's not so bad after all. Then again, you can still create wrappers or look into patterns like MVC to abstract the logic away from your presentation layer.

@Amjad: Sorry, no I can't. First of all, it's very old, messy, classic ASP code that drives the generator. But more importantly, it creates code that works for us, not for you. So, you'll have to put time in it to have it generate code to your likings after all. So IMO, you're better off investing time in a real generator and understand how they work.

Earlier in this thread, Hyzac said he used myGeneration to generate templates for this model "fairly easy", so you may want to take a look at that.

Cheers,

Imar
On Tuesday, July 31, 2007 8:40:53 AM Richard Urwin said:
Hi Imar,

How exactly would one do both of these? I can see that creating a wrapper with a generic type makes it easier to pass around, but are you suggesting I still create proper BOs to use it within? If so, then I'm guessing you have all the complexity issues I mentioned earlier...

With regard to MVC - I guess the CodeBehind acts as the controller in this case, but I can't exactly see how you could do a proper MVC without creating custom BOs - i.e. removing the XPO from the presentation layer altogether - which then causes the same problems as above.

cheers,
rich
On Tuesday, July 31, 2007 4:45:51 PM Imar Spaanjaars said:
Hi Richard,

Well, by creating a generic wrapper, you pass around generic objects. Yet, under the hood, these objects still stick to the XPO contract. They are, in fact, still truely strong typed objects, like Person or People.

So, in your business layer, you know you can call Save on the object and expect it to save correctly.

But of course, it's difficult for me to suggest something.  I don't know much about your setup, your object model and so on, so it's hard for me to suggest something that works best for you.

Cheers,

Imar
On Tuesday, July 31, 2007 5:30:59 PM Richard Urwin said:
Hi Imar,

You've definitely given me some food for thought - thanks!

cheers,
rich
On Wednesday, August 08, 2007 4:59:26 PM Vitek said:
Hi Imar.
Love this concept! Plenty more for me to learn.
I have a question (maybe not directly related)
I am trying to build a relativly similar program but using Oracle instead of SQL.
In the AddressDB class, in the " public static Address GetItem(int id)" sub, how exactly would I use the same sub, but using OracleDataReader? Apperantly FillDataRecord(myReader) part doesn't work with OracleDataReader.

This is what I got:

public Categories GetItem(int id)
            {
                Address myAddress = null;
                using (OracleConnection myConnection = new OracleConnection(AppConfiguration.ConnectionString))
                {
                    OracleCommand myCommand = new OracleCommand("sprocAddressSelectSingleItem", myConnection);
                    myCommand.CommandType = CommandType.StoredProcedure;
                    myCommand.Parameters.AddWithValue("@id", id);

                    myConnection.Open();
                    using (OracleDataReader myReader = myCommand.ExecuteReader())
                    {
                        if (myReader.Read())
                        {                            
                            myCategories = FillDataRecord(myReader);
                        }
                        myReader.Close();
                    }
                    myConnection.Close();
                }
                return myCategories;
            }

Any ideas?
On Wednesday, August 08, 2007 6:58:25 PM Imar Spaanjaars said:
Hi Vitek,

Can you define "does not work"? AFAIK, this should work. An OracleDataReader inherits from DbDataReader which in turn implements the IDataRecord interface. So, passing an OracleReader to FillDataRecord should work, and should give you access to the Get* methods of the IDataRecord interface.

http://msdn2.microsoft.com/en-us/library/system.data.oracleclient.oracledatareader.aspx
http://msdn2.microsoft.com/en-us/library/system.data.common.dbdatareader.aspx
http://msdn2.microsoft.com/en-us/library/system.data.idatarecord.aspx

Cheers,

Imar
On Wednesday, August 08, 2007 7:32:00 PM Vitek said:
My mistake :))) I overlooked something
Thank you very much, and once again, what a great article!
On Wednesday, August 08, 2007 7:54:03 PM Imar Spaanjaars said:
Hi Vitek,

Great; glad it's working. Let me guess: you declared myAddress and tried to use myCategories??

And you're more than welcome; glad you're enjoying the articles.

Cheers,

Imar
On Wednesday, August 08, 2007 8:14:34 PM Vitek said:
That too :)))
But my main mistake was that I thought that FillDataRecord is a standard function of some kind, I only noticed later that I am supposed to write it according to my fields.
I'll make it work for sure.

Thank you Imar for your help, I'll probably be bothering you again soon :)
On Wednesday, August 08, 2007 8:30:29 PM Imar Spaanjaars said:
Hi Vitek,

Yeah, it would be cool if there were methods like FillDataRecord in the .NET Framework that could construct objects from readers like this automatically. Unfortunately, at this stage you need to write this stuff yourself....

Keep a close eye on Linq; it allows you to do stuff like this without writing a whole lot of code.....

Cheers,

Imar
On Monday, August 20, 2007 7:18:56 PM Vitek said:
Hi Imar.
I am still playing with your code samples and trying to build my own based on yours.
I have another question:
At this line:
            while (myReader.Read())
            {
              tempList.Add(FillDataRecord(myReader));
            }

If I have tempList underlined with blue line and if I hover over it, the message pops up: "The best overloaded method match for '.........List<>..Add' has some invalid arguments"

And my FillDataRecord is also underlined and the error message is "Cannot Convert from 'Spaanjaars.ContactManager.BO.Address' to 'Spaanjaars.ContactManager.BO.AddressList'"

I've checked everything like 10 times, and cannot find the error. Can you tell me where to look for it, and what is the possible cause for such error?

Thank you,

Vitek.
On Monday, August 20, 2007 7:23:40 PM Imar Spaanjaars said:
Hi Vitek,

Sounds like you're mixing types.

Look at my implementation of FillDtaaRecord in part 2: http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=419

Notice how its type is of EmailAddress (private static EmailAddress), and that the method creates and returns an instance of EmailAddress as well.

I think somehow, you are declaring as, or returning a List (AddressList)instead of a single item (Address).

Cheers,

Imar
On Monday, August 20, 2007 8:26:54 PM Vitek said:
Thank You Imar, I found what the problem was, thanks to you.

I had it is :
public class ItemsList : List<ItemsList>

Instead of
public class ItemsList : List<Items>

Thanks a lot!
On Tuesday, August 21, 2007 4:55:10 PM Tony C said:
Imar,

Thank you for an exellent tutorial.  It was the first I came across which addressed this subject in such a clear understandable format - I have your book also.

My target application, if successful, could recieve thousands of consecutive hits.  It is completely SQL Server driven, and for the public domain I intend to use DataReaders exclusively.  

My question is regarding architecture and performance, as ideally I would like to dump the data from the DL directly into a datatable instead of a custom object, and pass that back to the BL, such that we disconnect quicker from the DB.  I could then, if so desired, cache the datatable.

I am also concerned about the performance overhead in using this OO architechture to fill gridView or repeater control using custom objects or instead, have my BL simply pass back a datatable.  I will essentially be dealing with large lists, which vary by individual user preference

I would appreciate your thoughts on this.

Big thanks.

Tony
On Tuesday, August 21, 2007 5:10:39 PM Imar Spaanjaars said:
Hi Tony,

Getting data out of a database using a SqlDataReader as used in my examples is pretty much the fastest way to get data.
A DataTable will be slower, and will have more overhead (and thus consume more memory) because of the meta data and change tracking mechanisms.

Additionally, you can cache objects using the ObjectDataSource (that works quite intelligently in ASP.NET) or access the cache API from your BO layer.

So, in many circumstances, I think you'll find that a custom BO with a SqlDataReader will be faster than using a DataTable.

Of course, YMMV. Once you start storing 15MB byte[] objects in large lists with your BOs, things will slow down pretty fast. But I guess that applies to other solutions too.

My advice: try it out. Do some performance analysis and see what works best for you.

Cheers,

Imar
On Thursday, September 20, 2007 10:30:07 AM Praful said:
Hi Imar,

Very gud article I should say U have helped us (new developers) a lot in getting an perfect insight about n-tier applications...

However, I encountered this small problem here when trying to save an contact person. (I received an error for client-server interaction as this application works best if I have database on the same M/C)
errror mesage:"The partner transaction manager has disabled its support for remote/network transactions. (Exception from HRESULT: 0x8004D025)"

I tried to google it out and then fished out a probable answer for this on:

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=230390&SiteID=1

If the steps are followed it gives a message saying that your MSTDC service will be restarted but other services (e.g. RPC remote procedure call) may be stopped and this is important service as many other services depend on these like telnet, IIS admin.. etc
I don't wish to stop my database sever for a second..

Would like you to guide me through this small problem of mine.

Thanks in anticipation.....

Praful
On Thursday, September 20, 2007 8:57:08 PM Imar Spaanjaars said:
Hi Praful,

If restarting the services or server is the only option, it's the only option.... ;-)

Sorry, no other ideas....

Imar
On Saturday, September 22, 2007 10:33:40 AM SAMir Nigam said:
Hi! this very good article. now i want to use Interfaces in this example. how can i do this?
On Saturday, September 22, 2007 10:43:57 AM Imar Spaanjaars said:
Hi SAMir,

It wouldn't be much different. Just have your business objects implement a common interface and work from there.

Imar
On Monday, September 24, 2007 4:25:47 AM SAMir Nigam said:
Actually i want to use Abstract Factory pattern. currrently i'm using 3-layered architecture in my project based on your article.
On Saturday, October 06, 2007 1:47:16 AM Steve said:
Imar,

I'm trying to implement a custom version (VS 2003) of the ASP.Net Calendar control.  I want to load the object with an ArrayList, that loads a Class with 2 properties- Date and TypeOfDate.  (y'know, birthdays, anniversaries).  I'm having trouble pulling the parts out of the class, but mostly assigning the DateItem to the Web.CalendarSelectedDates collection.  It compiles ok, but errors when run.  Most of the examples I've seen allow you to customize format with a given date range- assumes you know what it is.  The Type would load selected dates in the collection, but then allow a switch to format based on type.  Sounds simple, but I'd appreciate any help.  BTW, Cleveland just beat the Yankees, 2 to 1, and lead the playoffs 2-0.  SWEET!
On Saturday, October 06, 2007 8:30:45 AM Imar Spaanjaars said:
Hi Steve,

Custom version of the ASP.Net Calendar control, ArrayList, VS 2003.... I fail to see how this is related to the original topic of building n-layered web applications. Try a forum like http://p2p.wrox.com instead.

Cheers,

Imar
On Wednesday, October 31, 2007 1:50:37 AM jok said:
Hi Imar, kudos to your very simple and clean tutorial :)
Are these DALs and BLLs meant to be application specific or are they to be applied system wide?

If they are to be applied system wide, then wouldn't you have to add another layer on top of the BLL that is specific to your application if you wanted a single object with data from multiple Business Objects?

If they are to be application specific, then wouldn't that require all applications to be updated whenever there is a change that is system wide?

Finally, is it a common and reasonable practice to create a system wide BLL/BO and an application specific BLL/BO?
On Wednesday, October 31, 2007 7:28:43 AM Imar Spaanjaars said:
Hi jok,

They are application specific; isn't that obvious from the article? What good is a ContactManager in an order entry system?

And why would you "require all applications to be updated" when the system is changed?

You can of course binary reuse the resulting assemblies and share common functionality....

Imar
On Thursday, November 15, 2007 5:13:36 PM Vipin said:
Dear Imar,
Great article and  fantastic work also.
I got some interesting tips from it . Thank you very much..

One doubt.
I didnt like to use ObjectDataSource .
So I rewrite it like this(I dont know is it a correct  technique..but it is working for me)

private GetListinASPX
{
      DataTable ContactTable = new DataTable("ContactTable ");
        ContactTable .Columns.Add("Id", typeof(System.Int32));
        ContactTable .Columns.Add("FullName", typeof(System.String));
        ContactTable .Columns.Add("DateOfBirth", typeof(System.String));
        ContactTable .Columns.Add("Type", typeof(System.String));
        int i=0;
        DataRow ContactRow;
        ContactPersonList objlst=ContactPersonManager.GetList();
        for (i = 0; i < objlst.Count; i++)
        {
            ContactRow= ContactTable .NewRow();

            ContactRow["ID"] = objlst[i].Id;  
            ContactRow["FullName"] = objlst[i].FullName;
            ContactRow["DateOfBirth"] = objlst[i].DateOfBirth;
            ContactRow["Type"] = objlst[i].Type;

            ContactTable .Rows.Add(ContactRow);
        }

        GridView1.DataSource = ContactTable ;

        GridView1.DataBind();


}


But the pblm I am facing is ,if  the select query didnt select any record from database, an exception is happening at the "for loop" ..invalid object reference

how can I detect this situation like ..
if(the list contain anything)
{
   for loop
   {
   }
   bind datasource
}

My basic need is before running that loop i need to confirm that items are exist in objlst if not didnt perform the for loop.

Or any other mehod is there??
anything wrong in my logic???

Be greatful to any advice!!
On Thursday, November 15, 2007 5:53:36 PM Imar Spaanjaars said:
Hi Vipin,

Two things:

1. You can check for null and if your collection is not null, then check whether it's Count property is > 0

2. Why don't you bind the list to the GridView directly? E.g.:

GridView1.DataSource = ContactPersonManager.GetList();

What's the need for copying the data to a DataTable?

Imar
On Saturday, November 17, 2007 6:00:15 AM Vipin said:
Thank you very much..

Why i am using datatable is, in my application I am changing the customize the value from the database.
for example ,In database the values of STATUS filed is 1,2,3...
But when shwoing this to user some time i will show text like "ACCEPT","REJECT","PROCESSING" etc..

So using this for loop  I cant customize it.I am not sure any other method is ther to replace it??

May I know, using datatable between  gridview  and list  should affect the performance?

On Saturday, November 17, 2007 9:34:54 AM Imar Spaanjaars said:
Hi Vipin,

It all depends on personal preference, I guess. To me it seems like a lot of overhead to get a complete list, and then copy each and every element and all of its properties to another list or collection.

Maybe you can do what I did with the PersonType and use an eum instead?

Imar
On Saturday, November 17, 2007 11:04:08 AM Vipin said:
Thank you very much.

Let me try with the enum..if i struck at any point i will seek your help.
On Sunday, November 18, 2007 7:21:43 AM vipin said:
Dear Imar,
I have one dooubt also.

I have a Category Table with fields Catid,Catname and Customer Table with fileds Custid,Custname,Catid.for this table I creatd BO,BLL,DAL layer for basic Database operation.

The category_BO contain property variables for catid and Catname
The Customer_BO contain proprty variables for  Custid,Custname,Catid.

Upto this OK.

I want show the customer with "catogory name."

In FillDataRecord in Customer_DAL ,I can assign Custid,Custname,Catid toits correspoding proepry variable in Customer_BO.To assign the "catgory name", i should creat a property varibale in Customer_BO and assign to it..I think this is tedious. how can I use the  property variable "catname"  from category_BO??

Is it possible??
On Sunday, November 18, 2007 9:09:15 AM Imar Spaanjaars said:
Hi vipin,

I don't think I understand what you are asking.

Also, this is getting a bit too much for me to support over an article post. Can you please post this on a forum like http://p2p.wrox.com so others can take a look as well?

Cheers,

Imar
On Thursday, November 22, 2007 5:26:00 AM SAMir Nigam said:
Hi Imar! I have successfully implemented your model for layered web application in my project. Now i want to use some more advanced layered model. e.g. I want to use interfaces, abstract classes & some pattern for enterprize or professional level. Can you write part 4 of this series or can you suggest me some other orticle or resources?
On Thursday, November 22, 2007 6:30:11 AM Imar Spaanjaars said:
Hi SAMir,

You may want to get yourself a copy of "Expert VB / C# 2005 Business Objects" by Rockford Lhotka.

What you're describing warrants a book on its own; not just a part 4 of this article series.

Cheers,

Imar
On Thursday, November 22, 2007 8:56:53 AM VIPIN said:
Hi imar,
one doubt also..I implemented this layered architecture for login
.In that one database filed ie UserStatus ,data type is tinyint

FillDataRecord in  DataAcess layer  shows one error like "invalid cast"

oUser.UserStatus = oDataRecord.GetInt16(oDataRecord.GetOrdinal("userStatus"));

here "userstatus"  is proprty variable with datatype is "int"  and  the datatype of datatbase filed userstatus is tinyint..
be thankful for any advice!!
On Thursday, November 22, 2007 9:22:29 AM Imar Spaanjaars said:
Hi VIPIN

Call GetValue and then convert it to an int. E.g. something like this (untested) should work:

oUser.UserStatus = Convert.ToInt32(oDataRecord.GetValue(oDataRecord.GetOrdinal("userStatus")));

Cheers,

Imar
On Saturday, November 24, 2007 9:49:28 PM SR said:
Hi Imar,
I m just curious to know in which layer will you place the linq  technology (Bll or Dal) ?

Stan

On Sunday, November 25, 2007 10:38:55 AM Imar Spaanjaars said:
Hi SR,

I think it depends on the type of application. For simple, straight forward and maybe small applications you can use LINQ in the BLL or even use LINQ *as* the BLL. If the built-in validation mechanism is good for you, then this may work.

For large projects, you want to move LINQ to the DAL / as the DAL and get data from it through the BLL. You'll loose some of the flexibility with dynamic querying though.

I think the biggest risk with LINQ is that it's going to be used in the presentation layer directly. E.g. build up a query in the Code Behind of a page and bind it to a GridView. Very flexible, but impossible to maintain....

Imar
On Thursday, November 29, 2007 8:59:45 PM mafioso710 said:
hi imar, how can implement and order - order detail with this arquitecture? is something similar like contact person - addresses ???

i'm confused :(
On Thursday, November 29, 2007 10:36:06 PM Imar Spaanjaars said:
Hi mafioso710,

Yes, the same principles would pretty much apply to an Order/OrderDetail scenario.

Cheers,

Imar
On Monday, December 03, 2007 6:37:49 PM longmatch said:
I cannot run your sql script to generate database either in 2000 or 2005. What is the trick there?

Thanks

Haijun
On Monday, December 03, 2007 7:23:03 PM Imar Spaanjaars said:
Hi longmatch,

How do you run it? And what error do you get? The script works fine, AFAIK.

Imar
On Monday, December 10, 2007 12:43:36 AM Mr Noodles said:
Hi Imar,

I must first say big thanks for putting somethings like this together; you would not believe how much this has helped me.

I read all 3 parts of this article series and as a beginner, I have one of the mollio dollar questions:

When given a project/application, how do you determine which objects you want to define?

In other words, in the early stages of the application, while doing your planning, you know what the web app has to do (i.e. a site with users, profile pages, products, services and  forum/message board), but how do you determine which of those are objects?

I am having a hard time designing my application from this perspective. For example, in my case, emails wouldn't be objects but simple attributes of the user object, you see?

Once again, thanks for all your help and keep up the great work!

On Thursday, December 13, 2007 6:24:32 AM sadaf said:
Hi Imar,

Great Articles!!!
I have 1 question though . I am working a web site that has sub modules like chat, recritment, etc. I am confused as to how to structure my BO, BLL and the DAL. Should I use a single BO project or create sperate BO project for each sub module???? Same dielimma for BLL and DAL....
On Saturday, December 15, 2007 2:13:47 PM Imar Spaanjaars said:
Hi Mr Noodles,

I tried to explain part of the process in part 1: talk to clients, users, stakeholders and so on. Many things become clear simply by talking and listening.

However, there are of course caees where you don't know everything up front. In those cases, be prepared to refactor later. Start out with simple properties, but keep in mind you may want to upgrade things to full objects later. Or do it the other way around: start with objects, but bring them down to simple string properties if they don't serve a specific purpose.

I can't be more exact, as I don't know annything about your business domain. I can't judge for you if "simple attributes of the user object" is the right thing to do or not.

Cheers,

Imar
On Saturday, December 15, 2007 2:35:03 PM Imar Spaanjaars said:
Hi sadaf,

It depends on how you want to reuse them. If you think you'll reuse most of your objects in another application, put them in a single class library / assembly.
Otherwise, group related objects together in their own assemblies.

In the end it doesn't really matter; you'll be able to reuse your objects no matter where you put them.

Cheers,

Imar
On Tuesday, December 18, 2007 10:36:53 PM Joshua said:
Imar-

I hope you can assist me with adding some optimistic concurrency checking to my application, which has thus far been built on the great ideas presented in your articles.

I've got a DetailsView that populates from an ObjectDataSource, using business objects with optimistic concurrency checking built in in my Save() method (against a timestamp column). This can be proven to work perfectly in the following scenario:

- Start app, go to the DetailsView page (defaults to readonly), open a new window

- In one window, go to Update mode

- In the other window, go to Update mode, make a change, click Update

- In the first window, make a change and click update

This will cause my code to generate a DBConcurrencyException. All well and good. However, take the following scenario:

- Start app, go to DetailsView page, open a new window

- In one window, go to update mode, make a change, click update

- In the other window, go to Update mode

You will ender Update mode with the changed data. Now, technically, this works, however when this happens (data changed between the time it was opened for reading and the time it was opened for writing) I want to alert the user.

My first thought is to add something to the ModeChanging event for the DetailsView, but what to add to do the checking I am unsure about. Does anyone have any advice here?

Thanks!



Here's the relevant code: my GetByID and Save functions for the BO in question, the CalendarEvent (this is from my DAL, the BLL is just a stub ATM). I'd also post my aspx pages, but your comment routines throw out the HTML-lookalikes, and anyway there's nothing special about them.

        public static CalendarEvent GetEvent(int id)
        {
            CalendarEvent ev = new CalendarEvent();
            SqlConnection conn = new SqlConnection(_connectionString);
            SqlCommand comm = new SqlCommand(
                "SELECT Description, Title, StartDateTime, EndDateTime, Timestamp " +
                "FROM CalendarEvents WHERE ID = @ID", conn
                );
            comm.Parameters.Add("@ID", SqlDbType.Int).Value = id;

            SqlDataReader reader = null;
            try
            {
                conn.Open();
                reader = comm.ExecuteReader();

                if (!reader.HasRows)
                    throw new Exception("Invalid CalendarEvent ID; no data found");

                reader.Read();

                ev.ID = id;
                ev.Title = reader.GetString(reader.GetOrdinal("Title"));
                ev.StartDateTime = reader.GetDateTime(reader.GetOrdinal("StartDateTime"));
                ev.EndDateTime = reader.GetDateTime(reader.GetOrdinal("EndDateTime"));
                ev.Description = reader.GetString(reader.GetOrdinal("Description"));
                ev.Timestamp = (byte[])reader[reader.GetOrdinal("Timestamp")];
            }
            finally
            {
                reader.Close();
                conn.Close();
            }

            return ev;
        }

        public static CalendarEvent Save(CalendarEvent ev)
        {
            SqlConnection conn = new SqlConnection(_connectionString);
            SqlCommand comm = new SqlCommand("UpdateCalendarEvent", conn);
            comm.CommandType = CommandType.StoredProcedure;
            comm.Parameters.Add("@ID", SqlDbType.Int).Direction = ParameterDirection.InputOutput;
            if (ev.ID == -1)
                comm.Parameters["@ID"].Value = DBNull.Value;
            else
                comm.Parameters["@ID"].Value = ev.ID;

            comm.Parameters.Add("@Title", SqlDbType.Text).Value = ev.Title;
            comm.Parameters.Add("@Description", SqlDbType.Text).Value = ev.Description;
            comm.Parameters.Add("@StartDateTime", SqlDbType.DateTime).Value = ev.StartDateTime;
            comm.Parameters.Add("@EndDateTime", SqlDbType.DateTime).Value = ev.EndDateTime;
            comm.Parameters.Add("@Timestamp", SqlDbType.Timestamp).Value = ev.Timestamp; comm.Parameters["@Timestamp"].Direction = ParameterDirection.InputOutput;

            try
            {
                conn.Open();

                int returnVal = comm.ExecuteNonQuery();
                if (returnVal == -1)
                    throw new DBConcurrencyException("UpdateCalendarEvent returned -1--optimistic concurrency failure");
            }
            finally
            {
                conn.Close();
            }

            ev.ID = Convert.ToInt32(comm.Parameters["@ID"].Value);
            ev.Timestamp = (byte[])comm.Parameters["@Timestamp"].Value; // untyped :(

            return ev;
        }
On Tuesday, December 18, 2007 10:40:38 PM Joshua said:
Oh god, that got eaten horribly. Sorry about that. Let me try again.

Imar-


I hope you can assist me with adding some optimistic concurrency checking to my application, which has thus far been built on the great ideas presented in your articles.


I've got a DetailsView that populates from an ObjectDataSource, using business objects with optimistic concurrency checking built in in my Save() method (against a timestamp column). This can be proven to work perfectly in the following scenario:


- Start app, go to the DetailsView page (defaults to readonly), open a new window


- In one window, go to Update mode


- In the other window, go to Update mode, make a change, click Update


- In the first window, make a change and click update


This will cause my code to generate a DBConcurrencyException. All well and good. However, take the following scenario:


- Start app, go to DetailsView page, open a new window


- In one window, go to update mode, make a change, click update


- In the other window, go to Update mode


You will ender Update mode with the changed data. Now, technically, this works, however when this happens (data changed between the time it was opened for reading and the time it was opened for writing) I want to alert the user.


My first thought is to add something to the ModeChanging event for the DetailsView, but what to add to do the checking I am unsure about. Do you have any advice here?


Thanks!


PS. I'd also post my current code, but, well, see above.
On Tuesday, December 18, 2007 10:47:04 PM Imar Spaanjaars said:
Hi Joshua,

Pfffff, this is way way way too much code and info for me to process here as a topic comment. Please try a technical forum like http://p2p.wrox.com. Those forums, and their particiapnts are much better in dealing with queries like this.

Cheers,

Imar
On Monday, December 31, 2007 4:46:23 PM mafioso710 said:
Dear Imar,

May I please giving me some ideas and suggestions on how I can start from the beginning to programming a little Shopping Cart in ASP.NET using your N-Layered Design.

I have problems in using class collections, for example I have no idea how to register several items or products in the Database.

I ask you please, if you can give me some source code or link for me to read.

Thank you.
On Tuesday, January 01, 2008 3:25:43 PM Imar Spaanjaars said:
Hi mafioso710,

I am not sure I understand what to suggest. Isn't this article series exactly about that? I mean, it shows you how to design the object model, build classes and collections, save things in the database and so on.

So maybe you are asking for help with the actual object design? That's a bit too much to answer as replies to this post, so may I suggest you post this question on a forum like http://p2p.wrox.com?

Cheers,

Imar
On Friday, January 11, 2008 7:54:48 PM Kramer said:
If you bind a custom business object collection to a datagrid, how do you keep track of the changes to the data in order to update your database? What if one row is added, one deleted, and one modified? I know that programmatically you could keep track of the rows, but is there an automated way. Like with typed datasets. I want to go with this approach in my projects, but my manager doesn't want to have so much complex code to keep track of row changes in grids. FYI...I am working on a windows project, so that may change your answer a little.

Thanks
On Friday, January 11, 2008 8:00:45 PM Imar Spaanjaars said:
Hi Kramer,

You could build state tracking into the objects directly. E.g. each object knows whether it's new, changed or deleted. Then the *Manager classes or the List of objects could expose lists of certain types (new, deleted etc) so it's easy to see how to propagate certain changes to the database.

HtH,

Imar
On Friday, January 11, 2008 8:22:32 PM Kramer said:
Forgive me if this doesn't make sense.  If you bind a collection object to a datagrid and some of the rows in the datagrid change, does the value of in the collection objects also reflect that change. If so, then it is very simple to track changes with a property that tracks state. I am guess I am not sure how the relation of a grid to object works past setting the datasource equal to the object. I didn't know if they kept each up to date or not.

Thanks

Kramer
On Friday, January 11, 2008 8:38:53 PM Imar Spaanjaars said:
Hi Kramer,

You may want to look into the BindingList<T> class (System.ComponentModel.BindingList) at http://msdn2.microsoft.com/en-us/library/ms132679.aspx

Cheers,

Imar
On Monday, January 14, 2008 8:47:55 PM Anthony said:
Hello Imar,
Hope all is well in your neck of the woods.  I noticed that some other people had had issues with a system.indexoutofrangeexception error.  I seem to be getting the same problem.

I noted that your response to one person was as follows:  Anyway, the error seems to suggest you're referencing a column that doesn't exist. So there's a fair chance that actvDt or another column is missing from  your SQL statement that drives the Reader passed to FillDataRecord.
Check your SQL statement (in-line or stored procedure) and see if it has all the columns you need.

I am getting the following error:
Line 166:            tblsCompany.ProofOfDelivReq = tblsDataRecord.GetBoolean(tblsDataRecord.GetOrdinal("ProofOfDelivReq"))
Line 167:            tblsCompany.Startup = tblsDataRecord.GetBoolean(tblsDataRecord.GetOrdinal("Startup"))
Line 168:            If Not tblsDataRecord.IsDBNull(tblsDataRecord.GetOrdinal("DateAdded")) Then
Line 169:                tblsCompany.DateAdded = tblsDataRecord.GetDateTime(tblsDataRecord.GetOrdinal("DateAdded"))
Line 170:            End If

The funny thing is that I've x-referenced my storedproc with my tables and with my DB class and still no luck.  Everything syncs up from my DAL to my table on the database.  I was wondering if you might have any other suggestions of where I might be able to find the problem.  Thanks again Imar.
On Monday, January 14, 2008 8:59:55 PM Imar Spaanjaars said:
Hi Anthony,

It has to be something like that if it happens in this code. You must be overlooking something obvious.

Try setting a breakpoint in FillDataRecord and figure out which call to GetOrdinal throws the error, by typing something like:

tblsDataRecord.GetOrdinal("ProofOfDelivReq")

in the Watch window.

HtH,

Imar
On Thursday, January 24, 2008 3:56:56 PM Michael E. said:
Hi Imar,

Great series of articles. I have been  trying unsuccessfully until now
to find a good n-tier methodology.  These 3 articles are by far the best I have seen (in fact after reading these articles I plan on purchasing your instant results book).

One reason I read your series was to get a better handle on how to handle/represent Foreign Key database relationships in classes. I am however left with a few questions that others might have addressed, but I am still unclear on.  

1. Am I correct you feel that Dumb classes are better/easier to use then Smart Classes?

2. Am I correct that in using Dumb classes you should not handle foreign key relationships using lazy loading?

3. Using dumb classes should these relationships be handled by passing a bool to GetItem like you did for the ContactPerson class or is there a beter way?

4. Your EmailAddress, PhoneNumber and Address classes all have a ContactPersonID property.  Would each of the Manager classes perform Validation on that property (ie. Make sure that it is positive)? If so wouldn’t the validation code be duplicated in all 3 manager classes?

Thank you so much.

ps. I had trouble posting. Hopefully the post was not duplicated.  
On Thursday, January 24, 2008 7:06:39 PM Imar Spaanjaars said:
Hi Michael,

Thank you for your kind words. Hope you like the book as much as these articles.

1. Not necessarily. Both have their place. However, because of issues with circular references, it's difficult to create a separate DAL. If you choose for Smart class, you might as well have the DAL in the class as well. E.g. have GetItem call the database. If you use .NET's ability to call different database providers (using the database factory pattern) you can still have a BLL / DAL that easily adapts.


2. It's difficult. The dumb class doesn't know anything about its environment, so doesn't know what to call in a lazy loaded property. With smart classes this is much easier.

3. You could do that, although you may be better off using smart classes and embed the DAL code in the class. Not super clean but it certainly works.

4. You could factor out behavior like this to a separate class. E.g. one that is able to validate a valid ContentPersonId.

Hope this helps,

Imar
On Tuesday, January 29, 2008 4:18:25 PM Kramer said:
Imar,

Do you think it would be wise to keep the data access classes Shared(static) if I was going to try to implement some concurrency for updates? I am going to Begin an Oracle/SQl transaction, Select a Record for Update and pass back my data objects to the UI. Then allow the user to change the data, pass the objects back (either New versions or update old, haven't quite figured out how to use binding list of T yet). Then try to update the database , commit the transaction, and close the connection. Keep in my this is a winforms project were the data access is in Classes deployed on the clients machine. I just don't know how it will react to me leaving a transaction and connections open in a Shared class and then either returning to it to either finish the updates or cleaning up the transactions in the case they decide not to update(cancel pb). Let me know if this makes sense.

Thanks

Kramer
On Tuesday, January 29, 2008 4:37:47 PM Imar Spaanjaars said:
Hi Kramer,

Why would you want to leave the connection open? This model is based in the bank robber's 3G rule: Go in, Get what you want and Get out.

E.g. the idea behind the design is that a connection to the database is a scarce resource and should be treated as such. This means you should only use it when you really need it: for one of the CRUD operations. Don't leave it open for nothing.

That said, it would be easy to create instance methods. Just drop the static keyword, and instantiate a Manager instance before you call any methods.

Hope this helps,

Imar
On Tuesday, January 29, 2008 4:43:50 PM Kramer said:
But my manager wants pessimistic locking using select for update. Wouldn't I need the connection open?

On Tuesday, January 29, 2008 4:53:24 PM Imar Spaanjaars said:
Ask him (her) if he wants pessimistic locking because he just wants pessimistic locking or because of the results and benefits it brings.

There are other ways to detect conflicts, inlcuding timestamps and validating all previous fields. That way, you can avoid data conflicts, maintain integrity and still present a user-friendly solution.

Imar
On Sunday, February 03, 2008 1:17:20 AM kurt schroeder said:
THis was great! simple and to the point! It is the sort of development i was hoping to find an example of for quite some time now.
KES
On Sunday, February 03, 2008 11:22:52 AM Imar Spaanjaars said:
Hi kurt,

Glad you like it....

Imar
On Thursday, February 07, 2008 4:36:13 PM Arpit said:
can u please tell me how to get contactPerson id as return value from stored procedure please
On Thursday, February 07, 2008 6:23:37 PM JD said:
Imar, this is a great article.  Thanks for your effort to put all this together.

I have a question about batch update where user can add multiple email addresses but invoke the save method only once all changes have been done.  Appreciate your help on this.
On Thursday, February 07, 2008 8:54:14 PM Imar Spaanjaars said:
Hi Arpit,

Did you read part 2? It's all in there....

Imar
On Thursday, February 07, 2008 8:55:56 PM Imar Spaanjaars said:
Hi JD,

This won't be easy to implement with the current model. However, you can easily apply "standard" batch update techniques. Google for batch update .NET" to find some interesting and useful stuff.

Cheers,

Imar
On Thursday, February 07, 2008 10:28:03 PM JD said:
Hi Imar,

Thanks for your prompt reply.  I wasn't successfull finding a good example about batch update using business objects.  I would appreciate it if you can put me in the right direction if you know of an article, url, or site that has this information.

Cheers,
JD
On Friday, February 08, 2008 6:44:35 AM Imar Spaanjaars said:
Hi JD,

Sorry, I have no ready-made code or article URLs available.

Imar
On Tuesday, February 12, 2008 4:49:08 PM Kurt Schroeder said:
Do you consider execution pages using this type of development to be faster then executing pages using a similar system with tableadapters? It seems faster to me on my development environment, but this could be wishfull thinking.
On Tuesday, February 12, 2008 7:55:50 PM Imar Spaanjaars said:
Hi Kurt

It could certainly be faster than using TablAdapters. My code uses a SqlDataReader which is by far the fastest way to get data out of a SQL Server database. From there, it only takes a cast to get it into a BO.

With TableAdapters, there's more overhead in maintaining rows, version information and a lot of meta data.

However, only tests in your environment can really tell the difference.

Imar
On Friday, February 15, 2008 12:22:39 PM Jeremy64 said:
Hi Imar,

Your articles are helping me a lot in the work. I was searching for long time for such kind of real-world examples.

Thank you :)

Gian Luca
On Sunday, February 17, 2008 9:23:59 PM mann said:
Hi Imar,
Thanks for the article about N-tier architecture. I'v made hard copy all 3 parts.. I am a recent graduate,i just had theoritical knowledge but not how it works practically. ,It helped me alot.By the way i think i know how it works and make sense..Thanks.. Tips were useful..to name few, Using Idatareader,#region,[objectDataAttbts and methods], etc were new to me and m happy u hv provided those with detail explanation..
thanks a million...


But i do have some problems question :)    (thou i understood the methodology,ino ideawhat went wrong in coding) like,

1)After i draw class diagram and add new class,class file with same name is generated automatically.Now when i declare n define private variables n public properties in the class file(not in class diagram), the class diagram  disappears..thats for everyclasses(BO,BLL,DAL) .don know how..

2)So i leave as it is... with declaration in the class file not in diagram..when i create new instance of BO classes and try to access through intellisense,private properties are shown but with  RED " ! " infront of respective properties.

3)Also,when DAL layer tries to access BO's private properties via private [class]> filldatarecord (Idatarecord mydatarecord), i get "not accessible ..." kinda msg. (but i have namespace  reference of BO as well as class instance)

4)"System.Transactions;" namespace doesnot seem to be recognised by the compiler where as when i run ur project,it doesnot show any error..

5)Is there any defficiency using "string name=null" rather than "string name=string.empty".Also,when declared "datetime mydate= Datetime.MinValue",does it holds some kind of values like 00/00/0000 00;00;00 or nothing.

6)As for SQL delete stored procedure,Do i have to rollback and delete other related data rows?..As i understood from my teachers, when declared relationship between two tables,other related rows are automatically deleted.

oh...Its long "problem n question " comment..never mind me asking so much ..you could answer 1,2 n 3

mann
peace..
On Sunday, February 17, 2008 10:30:27 PM Imar Spaanjaars said:
Hi mann,

Glad you like the articles. It's good to hear they make a lot of sense ;-)

Regarding your questions:

1) Don't know. Never seen this before. The only time I see classes disappear from the diagram  is after renaming them or their container namespaces. Simply re-adding seems to fix the problem.

2) This seems to indicate you have two classes with the same name, or you are reusing an existing or reserved word.

3) No idea what you are talking about

4) A quick search for System.Transactions assembly should tell you that the System.Transactions namespace is defined in the System.Transactions assembly that you need to reference.

5) When a string is String.Empty, you can call methods on it like Replace. When it's null, things will crash. For the value of DateTime.MinValue, put a watch on a DateTime variable in the debugger or look in the documentation.

6) Your teachers only told halve the story (or you stopped listening to it somewhere). In a database, you can define "cascading relations". This means that when you delete one record, other associated records are deleted as well. For example, when you delete an Order, you can have connected OrderLines deleted automatically. However, this is not the default behavior.
Personally, I am not a fan of cascading deletes, so I haven't implemented them in my ContactManager application.

Hope this helps,

Imar
On Sunday, February 17, 2008 11:44:15 PM JD said:
Hi Imar,

I'm trying to develop a model for creating multiple addresses and saving them all at once.  I came across an issue and I'm wondering if you can help me.  I load data from the database into an object.  I didn't bind data to formview control.  I'm basically doing the manual simple binding by assigning the text property of each textbox value from object.  In the page load I'm instantiating a new copy of the Contact and then loading object properties into textboxes.  Please note I'm not using formview.  when it comes to update I need to use the same object instance so I can copy values from textboxes into same object properties and then save data into database.  My problem is that I'm not able to find a way to use the same instance of the object.  I had to load the object again from database using an ID hidden on the form and instantiate an object for contact, update its properties and then save the object into database.  Is there a way that I can reference the same object instance that I used in Page_Load to copy data into it when I click btnUpdate command button?  

Thanks,JD
On Monday, February 18, 2008 12:37:28 PM Jeremy64 said:
Hi Imar,

in the DataAccess layer of your example I saw that you choose to create a new SQLConnection each time the method is invoked.
In other articles I saw that the Connection was implemented with the Singleton pattern. I'm thinking that in this way the connection becomes the bottlekneck of the application.

I'm a little confused about the pros and cons. Could you give me your point of view about the two choices?

Thanks :),

Gian Luca
On Monday, February 18, 2008 1:32:07 PM Imar Spaanjaars said:
Hi Jeremy64,

IMO, singletons are very bad in this case. It means all users share the same connection. Since only one DataReader can execute on a single connection (without using MARS) you'll quickly run into problems as users will have to wait for each other.

Imar
On Monday, February 18, 2008 7:35:44 PM Imar Spaanjaars said:
Hi JD,

As soon as the page has renderd to the browser, you loose the BO at the server. This is by design and is generally referred to as the "stateless nature of the web". You could store the object in session state, but I don't think that's a good idea.

Simply retrieving it based on its ID is, IMO, a good way to handle it.

Imar
On Monday, February 18, 2008 9:57:08 PM JD said:
Thanks Imar for your response.  I read few articles where they recommened using the solution you proposed even though it's another hit for the database since this will be worth while for optimistic concurrency.  I was wondering how the form view is able to keep reference of the object and when I update the fields individually it will update the BO.  Trying to implement it using a simple form without formview control doesn;t seem to work.

Cheers,
JD
On Tuesday, February 19, 2008 7:35:01 AM Imar Spaanjaars said:
Hi JD,

The FormView (and other data controls) store data in ViewState. In addition, they often contact the database again on PostBack.

Imar
On Tuesday, February 19, 2008 1:56:48 PM Michael E. said:
I ran into a question in my current design so I went back to this series of articles to see if I misunderstood something.  

I have a CustomerDB class that has several overloads of the GetList method (Get all Customers, Get all Open Customers...etc).  Therefore my CustomerManager class also contains these same methods.

Therefore everytime I add a new GetList method to my DB class I have to make the same change to my Manager class.  Since my Manager class is just a facade around my DB class and I don't plan on adding security or caching I was wondering if it is a bad idea for my presentation layer to directly call the DB layer for database reads (and only database reads)?

It seems like a lot of duplication of effort that whenever I add a new GetList method to my DB class that I have to do the same in the Business Manager class.  I was wondering what you thought.

Thanks

On Tuesday, February 19, 2008 2:34:10 PM Imar Spaanjaars said:
Hi Michael,

Sounds like all your GetList methods are pretty much the same, except for the criteria. A common solution to the problem is something like this:

public List<Customer> GetList (Criteria critera) {}

Then you create a single Criteria class that allows you to define filter criteria. You forward that object to the DAL which then takes care of the rest.

Imar
On Tuesday, February 19, 2008 3:15:17 PM Michael E. said:
That sounds like an interesting solution.  I am going to have to try to figure out how to build a Criteria class.  If for some reason I can't get that to work; in general am I still better off creating duplicate GetList methods in both my Manager and DB classes then calling the DB directly?

Thanks
On Tuesday, February 19, 2008 5:53:15 PM Imar Spaanjaars said:
Hi Michael,

A Criteria class should be relatively easy to implement. It can be just a simple data object with some boolean parameters or other criteria properties.

To make it easier for users of your API, you can have different overloads in the business layer that all forward to a single DAL method and pass in a Criteria instance. That way, you have an easy API, and little code to maintain in the BLL.

If you're willing to access the database in the BLL directly, you can no longer replace the DAL for another one. If that's OK, you may be better off creating "smart classes" in your BLL instead and skip the whole DAL layer to begin with.

Cheers,
Imar
On Tuesday, February 19, 2008 6:13:47 PM Michael E. said:
Thank you. You have been most helpful.
On Wednesday, February 20, 2008 6:11:08 PM JD said:
Imar,

What is the purpose of having foreach loops in Save method of ContantPerson object when each of the list items are saved into the database separately?  For example there is a foreach statement for addresses but each address is saved using AddressManager. I even traced the save for ContactPerson and found that count of Addresses, Phones, and EmailAddresses is always.  I gues there might be no need to have these foreach loops in the Save method.  Please confirm.

Rgds
On Wednesday, February 20, 2008 6:41:35 PM Imar Spaanjaars said:
Hi JD,

I am not sure what you are asking. There *is* no foreach loop in the ContactPerson class. Being a "dump" class, all it does is hold data. You need an instance of the ContactPersonManager class to save a ContactPerson. Inside ContactPersonManager I use a foreach to save individual contact records like Address. That makes ContactPersonManager responsible for saving its "child" records.

Hope this clarifies things.

Imar
On Wednesday, February 20, 2008 9:23:37 PM JD said:
Imar,

I meant ContactPersonManager.  The Save method of ContactPErsonManager has a foreach loop but these loops are not needed.  Take Addresses for example.  When we edit or insert a new address for a contact person the system saves the data into the database immediately after clicking Update or Insert.  the question is if the system is updating address in database after it's being updated or inserted then why add a foreach loop to do the same.  Is this redundant?

Rgds
On Thursday, February 21, 2008 10:49:27 AM MG said:
Really appreciated your articles, and waiting more like those. Very precious articles. I am confused on this N-Tier application so I have some questions in my mind. Please don't laugh to my questions :)

1) You made every table as an object. And you take very simple datas and bind them to appropiate object than datagrids. I mean you take ContactPerson information and assign them to ContacPerson object and make very basic operation on it and show it in a datagrid. Then you take PhoneNumber and goes like this.... What if you take and complex data, say ContactPerson.Firstname , ContactPerson.MiddleName, EmailAddress.Email, PhoneNumber.PhoneNumber,PhoneNumber.Type . And a ContactPerson has more than one Phone number. I want to show them in the datalist may be. What kind of operation we will do? Assign every appropriate data to corresponding object? Or do we need to create a new object name AnotherContact may be or something different, that contains all data.
Ex:
Mike - John - mail@mail.com - 5552222 - Personal
Mike - John - mail@mail.com - 6663311 - Bussiness

2) I made an e-commersial web site which has 55 tables and 160 stored proc. in it. I used SqlDataSource and Datalist so much. when i try to take a data from database, i just simply add a SqlDataSource and call to appropiate stored proc. Or in the code behind , if i want to access database, i create a class simply takes the arguments and calls the stored procedure, and return the value if exists. Everything works well, but after reading your articles i decide to convert my project n-tier app. But suddenly i fear , because if i made every table as an object, and seperate their props or etc. in to BO there will be more than hundred class. What do you say is this normal??? Or did i understand your structure truely? what i understand every table possibly will be an object. And for every object , create an *Manager class. And for every *Manager class create a *DB class.

More questions on the way. Look forward to your advices. Take care your self and and thank you again.
On Thursday, February 21, 2008 3:32:05 PM MG said:
I read some of the comments and my previous questions like them with a little difference. Especially first one. What so ever , here i have a new question,

I have a project that i talked about  on my second question(previous comment). Simply, on PL i have SqlDataSource, and this controls calls sproc from database , gets the data and displays in a datalist mostly. If i make an BO for every table(or most of them) i'm not able to show them directly in the datalist because data is comming from different tables.(See my question 1). What if i made my BLL like this; gets DataTable not the objects? And gives the DataTAbles to directly to DataList in PL using ObjectDs.Then i don't need BO any more. But if i do this, this time i am only using BLL to execute my sproc(of course i am using DAL). Is this kind of approches take me far away the N-Tier , or OOA approach? What is your advice?

Thanks again.
On Thursday, February 21, 2008 5:05:16 PM Imar Spaanjaars said:
>>  take me far away the N-Tier

IMO, yes it does. As soon as you start using DataTables you lose type safety, a central security and validation layer, IntelliSense, a clean API and all the other benefits that proper OO projects can give you.

I can't decide for you if you need hundreds of tables or not. Maybe you can redesign the application so you need fewer classes? One table doesn't necessarily mean one class...


Imar
On Friday, February 22, 2008 8:31:30 AM MG said:
Thanks for answer, but you didn't answered my first question:

What am i going to do if i want to take

[ContactPerson].Firstname ,
[ContactPerson].MiddleName,
[EmailAddress.Email],
[PhoneNumber].PhoneNumber,
[PhoneNumber].Type

And show them in a single datagridview row? It is easy to show [PhoneNumber] and [EmailAddress] data in nested datagrid or datalist but what if i want to show in one row?

Take care...
On Friday, February 22, 2008 9:51:49 PM Imar Spaanjaars said:
You can still do that, using the nested control solution. It's just a matter of defining logical templates on your controls...

Imar
On Saturday, February 23, 2008 9:21:44 AM MG said:
Thanks for the answers. I have an another question. When we are deciding which objects will be created, is this approach true: One object for every one to one connected tables and another object for one to many tables. I mean with a simple example:

<snip>

I hope i m clear on my question. And sorry for the questions :D Im a little bit newbie to n-tier and you have great knowledge i think. I'm just trying to figure out the way you think how you determine BO.

Thanks you so much again. Take Care...
On Saturday, February 23, 2008 9:34:47 AM Imar Spaanjaars said:
Hi MG,

I removed most of your post as it was just a bit too much for me to respond to. The Talk Back feature is about talking back about this article, not your on-line tutor for object design.

You may want to try posting these kind of questions on a forum like p2p.wrox.com for these kind of extended questions, Also, some books about OO design may help you in this.

Sorry, but this is just taking up a bit too much of my time.

Imar
On Tuesday, February 26, 2008 3:02:24 PM Michael E. said:
I have a couple of small questions.

1.  I was wondering if you saw any downside to passing the ID of the Contact Person in a QueryString to the AddEditContactPerson.aspx page? If so, is there a better alternative?

2.  In the Default page you hardocde the response.redirect to the AddEditContactPerson.aspx page.  In the AddEditContactPerson.aspx page you hardcode the Response.Redirect to the Default page.  I have been struggling with this issue for a while. I have always been taught that hardcoding is a bad idea in general, but I can't come up with a better solution then hardcoding the Response.Redirect.  Do you have any thoughts or solutions on the subject?

Thanks
On Tuesday, February 26, 2008 3:17:41 PM Imar Spaanjaars said:
Hi Michael,

1) Probably not. You need some way to pass data from page 1 to page 2. The query string is usually fine for that.

2) Look at the upcoming MVC framework (http://weblogs.asp.net/scottgu/archive/2007/10/14/asp-net-mvc-framework.aspx) It solves many of these issues.

The main purpose of these articles is to show n-layer design in terms of business objects. The site is not necessarily a best practice example for web development (to make it easier to focus on the class design) and as such has "bad examples" like hard coded URLs. MVC is designed to fix these kind of problems.

Cheers,

Imar
On Wednesday, February 27, 2008 3:21:57 PM kurt schroeder said:
This is something that I’d prefer to ask privately and you may by all means answer me directly.
Do you normally use this system for your own development? I’ve created a code generator that starts with field names, types, schema, table name, Identity R/W, …  and it generates the table, stored procedures (I prefer to create a separate Insert and update), and all the separate classes for basic operations outlined here. It seems to work ok, though it still has bugs. My point is that I’m wondering if this is intended for learning only or is it being used. I’m using it, but don’t want to go down a path that will lead to more work later.
I love the system as I have said previously
KES
(you may remove this comment if you feel it is inappropriate)
On Wednesday, February 27, 2008 4:22:53 PM Imar Spaanjaars said:
Hi Kurt,

Of course it's something I am using in real world applications. The techniques discussed in these articles are great for learning purposes, but what's the point in learning something if you can't use it in the real world?

I don't use it for all sites I build though. In some sites I merge the BO and BLL layers into "smart classes". In other sites I replace the DAL with different code. However, the general ideas are often the same.

These articles have been read over 140,000 times in the past year or so. They have received over 600 votes, and the largest majority gave 5 star ratings. That many people can't be wrong... ;-)

Finally, check out the comment by Hendrik at the end of part one; he just released a brand new web site based on these techniques.

Cheers,

Imar
On Wednesday, February 27, 2008 4:34:37 PM kurt schroeder said:
Appreciated,
an i did read the comment, however, it never hurts to get it from the author. I like wise gave this artical a 5 star.

Your help is appreciated. It has given me some insight intoother systems such as csla.net which is quite a bit more complicated, but could work better on very large systems. In this case i can see how it will let me create expandability.

I do have one request. How have you separated this system to run on seprate servers and what communication method did you use for say the dll on server one to dal on server 2?

Thank you again!!!

KES
On Wednesday, February 27, 2008 4:56:53 PM Imar Spaanjaars said:
Hi Kurt,

It's not easy to separate this design over multiple machines without lots of remoting code or "moveable objects"...

However, this design scales pretty well. It's currently being used on sites with over 30,000 visitors a day on a *single server*. If you need to scale, you can always move the database to a different server.

If you need to scale beyond that level, add load balancers or look at different architectures.

Of course your mileage may vary, and your application may have different needs. However, you need to stress test this design in your circumstances before you make that decision.

Imar
On Thursday, February 28, 2008 7:31:26 PM Chuck said:
Hi,
thanks for a great article, I've been using this since you've posted your article. Today I've stumbled upon, for the first time since using this approach, a public shared problem. Perhaps you could give me a few pointers as to what/where I've done/gone wrong.

Ent = New Entreprise
Ent.Flora = FloListe.Item(i)
EntrepriseManager.SaveFlora(Ent)

If Ent.Erreur Is Nothing Then
  'FloraManager.Delete(FloListe.Item(i))
  FloraManager.Delete(FloraManager.GetItem(FloListe.Item(i).Nim.ToString))
End IF

My problem is when accessing the list for the Delete Flora, it has taken the values of the Ent. I'm passing my objects by ref because on the save I add the IDs from the DB in my object so I can use it further down the code.
Both my DA and Manager classes are using Public Shared.

The Floramanager.Delete(Floliste.item(i)) is the one that's taking on the values from Ent (and the value switch is done at the Save function). The following line is what I'm currently doing so I can keep working on the projet until I find a way to solve this.

Thanks for a great article and your help (in advance).
Charles
On Thursday, February 28, 2008 7:47:37 PM Michael E. said:
I am using 'Dumb' classes. As such I have a question as to where something belongs. I use enums to represent certain values. For instance

if (customer.Status = customerStatus.Open)
{
    // Do Something really cool
}

My question is this.  Would the enum for customerStatus be better off in the customerStatusBO class or the CustomerStatusBusinessManager class?

Thanks
On Thursday, February 28, 2008 7:56:54 PM Imar Spaanjaars said:
@@ Michael: neither. I wouldn't nest the enum in a class, but place it in the BO namespace directly.

@@Chuck: No idea what you are talking about, or what the actual problem is. Maybe you're better off posting this on a forum like p2p.wrox.com?

Imar
On Tuesday, March 04, 2008 5:28:51 PM MG said:
What about exception handling? Which layer , exceptions will be handle?

Thanks...
On Tuesday, March 04, 2008 6:22:48 PM Imar Spaanjaars said:
Of course it depends on your implementation, preferences and the type of exceptions you are dealing with, but I generally throw them in the Bll and catch / handle them in the presentation layer.

Imar
On Thursday, March 06, 2008 12:29:01 AM Chuck said:
Hi,
here's a code sample that'll probably explain better what I was trying to explain. This code is part of a new page using the VB.Net version of your tutorial (tried the C# version too).
I created a new contact person using the default page (with ID27, ID26 is  Imar's contactperson). I then went to a newly created page and added the following code in the PageLoad event:

        Dim a As New ContactPerson 'tried Dim a/b As ContactPerson just in case as well
        Dim b As New ContactPerson
        a = ContactPersonManager.GetItem(26)
        b = ContactPersonManager.GetItem(27)
        b = a
        a.FirstName = "NewFirstName"
        Response.Write(a.FirstName + " <-a b-> " + b.FirstName))

The results I expected where :
NewFirstName <-a b-> Imar

but here's what I got instead:
NewFirstName <-a b-> NewFirstName

I believe this is a result of using Shared (or static) functions. If this is normal behavior would you be so kind and explain why it is. If not, anything that can be done ?

Thanks
Chuck
On Thursday, March 06, 2008 9:04:26 AM Imar Spaanjaars said:
Hi Chuck,

This has nothing to do with my design, or with the fact that the application uses static methods to get objects.

This is basic, 101, .NET behavior. What you are witnessing here is the reference type infrastructure. Take a look at this:

b = a

What do you think this does? *Copy* the underlying values of a into the b object? Nope, all this does is let the b variable point to the same object in memory that the a variable is pointing to. At this stage, both the a and b variable point to the same object: me - the contact person with an ID of 26. The object with ID 27 is now no longer referenced and marked for garbage collection.

When you change a.FirstName you change exactly one instance. However, since both the a and b variables are pointing to this instance, both a.FirstName and b.FirstName give you the same value.

So the code behaves as expected; it's your expectations and beliefs that are incorrect.... ;-)

Imar
On Tuesday, March 18, 2008 5:09:44 AM Ankit Mehta said:
This is very good application for beginners and want to work in Modular Programming. Send me others such tutorial on my E-Mail ID.

ThanX
On Tuesday, March 18, 2008 6:30:30 AM Imar Spaanjaars said:
Hi Ankit,

I am really glad you like these articles. However, don't tell me what to do. Don't tell me to "send [you] tutorial on [your] E-Mail ID". Do some research yourself using search engines. You were able to find this one; I am sure you can find others.

Imar
On Tuesday, March 18, 2008 3:36:08 PM Michael E. said:
I need to modify the code in my project and I am looking for something to model it after.  Much of the example code that I found on the net is not really what I would call production level code.  Would you consider the code that you presented here to be production quality?  I am not asking to be tacky. I am hoping that you think it is so that I can 'borrow' what you have presented here (especially the data access layer). I would hate to re write much of my code only to find out that you find the code in this article to be lacking.  I apologize if this question is in bad taste. I just found a major flaw in my dal and I am looking to replace it.

Thanks so much.  
On Wednesday, March 19, 2008 6:27:00 PM Imar Spaanjaars said:
Hi Michael,

Of course it is. Otherwise there would hardly be a point in writing about it. I use this methodology in a large number of sites I have built and/or designed.

Obviously, I can't tell if it's good for your situation, so don't take my word on it but try it out yourself and see if you like it, and whether it works for you.

Cheers,

Imar
On Tuesday, March 25, 2008 12:50:07 AM Eddy said:
I have read countless books and articles on designing n-tier applications, and yours is hands down the best.  You presented the most articulate and thorough explanation of building a 3-tier application that I have come across, not to mention the illuminating tips and techniques that were included.

In an article titled "DataSets vs. Collections" by Dino Esposito (http://msdn2.microsoft.com/en-us/magazine/cc163751.aspx), Dino presents three commonly used enterprise design patterns: 1) Active Record, 2) Data Mapper, and 3) Table Data Gateway.  The Active Record pattern uses "smart" classes, whereas the Data Mapper pattern separates the data and the behavior, which is the technique you use in your design.  Are you familiar with the Table Data Gateway pattern?  If so, have you ever used this pattern?
On Tuesday, March 25, 2008 7:30:05 AM Imar Spaanjaars said:
Hi Eddy,

The pattern sounds a bit like what has been implemented in the CSLA framework that allows for "moveable objects". A set of services can pass around instances of your BO's from server to server (or within the same server) and then the BO saves itself on the target server.

If you need remoting capabilities like this, checking out CSLA.NET (www.lhotka.net) is certainly worth checking out.

Imar
On Tuesday, March 25, 2008 4:11:56 PM Eddy said:
Thank you!  I will check out CSLA.NET.

Eddy
On Tuesday, March 25, 2008 7:45:49 PM kurt schroeder said:
Eddy and Imar, forgive my two cents worth of advice and comment, but ..... The system Imar has developed is hands down the best and most understandable for smaller and medium development. I am currently working on my 4th site since I asked if anyone was actually using it?. Using a home grown web based code generator for classes, tables and sp’s has helped speed up development considerably.  
Imar suggested CSLA.NET a few times here so I decided to work through Rocky’s book. I can’t tell you how much I’ve learned from it that can be applied to smaller sites, but it really is a great tool for large sites with multiple developers. There is a through explanation and code walk through concerning movable objects. You will not regret taking the time to explore CSLA.NET. However, in fairness to Imar I need to say it is not intended for smaller to medium sites and,  I need to say again, the system here described is the best I’ve seen thus far.
On Wednesday, March 26, 2008 2:14:25 PM Vladimir Kelman said:
Hi Imar! Could you explain me two things:
1) How does gwEmailAddresses grid know that it needs to refresh its data after user inserted a new email address or deleted one?
2) ContactPerson BO class contains EmailAddresses list. After a new Email address is saved to database, corresponding item should be inserted into EmailAddresses list.  I don't see where / if it happens. Could you explain, please? I thought there should be something like OnEmailAddressAdded event which re-populates person's EmailAddresses list.

Thank you.
On Wednesday, March 26, 2008 2:49:02 PM Vladimir Kelman said:
I'm sorry for my stupidity. I know answer to both questions: the whole page is refreshed, so objects are re-populated. I would appreciate if you remove both my comments here.
On Wednesday, March 26, 2008 5:32:05 PM Chuck DiRaimondi said:
Imar,
I want to place a label on my web form that will hold the return value (id) that is returned from the stored procedure once a record is inserted into the database. I can not figure out how to do this. Can you assist?
On Wednesday, March 26, 2008 8:26:01 PM Imar Spaanjaars said:
Hi Chuck,

You can simply get the ID from the BO:

Label1.Text = myAddress.Id.ToString();

after saving it.

If you need more help with getting back the actual value from the procedure, may I suggest you try a forum like p2p.wrox.com?

Cheers,

Imar
On Monday, March 31, 2008 5:07:58 PM Michael E. said:
I have a question about your use of the TransactionScope class.   Unless I am mis understanding your code it seems to me that you are opening and closing a connection in each of the Save method calls  (ContactPersonDB, AddressDB, EmailAddressDB and PhoneNumberDB).  

According to someone in a SQL Server forum, TransactionScope may not work unless all of the connections remain open until the myTransactionScope.Complete();.

I was wondering:

1.  Am I correct that you should open and close the connection in each Save method.

2. That the guy in the SQL Server forum is incorrect and that TransactionScope will work perfectly if the connection is opened and closed in each method call.

Thanks so much.
On Tuesday, April 01, 2008 2:45:07 PM Imar Spaanjaars said:
Hi Michael,

I don't know "the guy" you are talking about, so it's impossible for me to say whether he is wrong or not. I also don't know the specifics of the situation, so I can't really judge on that. There's a fair chance thing won't work, because of what you described under certain situations. However, in these cases, trying it out is the only way to find out. I know this works:

using (TransactionScope myTransactionScope = new TransactionScope())
{
  using (SqlConnection myConnection = new SqlConnection(AppConfiguration.ConnectionString))
  {
    SqlCommand myCommand = new SqlCommand("DELETE FROM Address", myConnection);
    myCommand.CommandType = CommandType.Text;
    myConnection.Open();
    int recs = myCommand.ExecuteNonQuery();
    myConnection.Close();
  }

  using (SqlConnection myConnection = new SqlConnection(AppConfiguration.ConnectionString))
  {

    SqlCommand myCommand = new SqlCommand("DELETE FROM ContactPerson", myConnection);
    myCommand.CommandType = CommandType.Text;
    myConnection.Open();
    int recs = myCommand.ExecuteNonQuery();
    myConnection.Close();
  }

  myTransactionScope.Complete();
}

Although the first connection is closed, the addresses are not deleted in the end. When the ContactPersons are deleted, an error occurs (because they have associated EmailAddresses) and the entire transaction is rolled back.

Cheers,


Imar
On Tuesday, April 01, 2008 3:39:58 PM Vladimir Kelman said:
Michael,

Let me tell you that I'm working on a database helper class which will allow to use Imar's technique with either TransactionScope or SQL Server transaction. It will be configurable inside that helper class (or in appropriate config file). Individual DAL Save() methods will obtain connections from this helper object and close them by using its method (something like DBHelper.getConnection() and DBHelper.CloseConnection(thatConnection)). That will allow to keep a connection open in case you need it during a transaction, for example, if you have to use SQL Server transaction instead of TransactionScope. DAL Save() objects will not depend on a having or not having to run inside either TransactionScope or SQL Server transaction.
Please contact me directly if you want at vkelman at gmail dot com.

Imar, I'm sorry for putting it here, I won't do it again.
On Tuesday, April 01, 2008 7:08:03 PM Michael E. said:
Imar, thanks for getting back to me on the TransactionScope question.  I ran a similar test to the one you described and it worked as you said it would. I thought that I might have missed something after reading the other guys post in a different forum. I guess I should learn to trust myself more.

Thanks again.

ps. The ASP.NET 2.0 Instant Result books is great.  
On Thursday, April 03, 2008 4:22:35 PM JB said:
We're trying to decide whether to use "smart" objects or "dumb" objects in our n-layer approach on our team... Could you please give some pros/cons of each approach & which is more suitable for enterprise applications? The article is great & is definitely a foundation for us.. Keep up the great work! Cheers!

Jason
On Saturday, April 05, 2008 10:20:02 AM Imar Spaanjaars said:
Hi Michael,

Glad you got it working. It's also good to hear you like my book... ;-)

Imar
On Saturday, April 05, 2008 10:24:51 AM Imar Spaanjaars said:
Hi JB,

Check out the first part of this series as I explain the differences between the two. Also, read the comments on that part, as the differences are discussed there as well. Keep in mind that an initial draft used smart classes, and that I made a switch later, so some of the comments are off.

Cheers,

Imar
On Thursday, April 10, 2008 10:35:16 AM Nik said:
Wow imar, what an article.  So many times whilist being though my learning process i think that there is some kind of consipracy outhere and developers hide their know-how and then i come across something like your article and it restores my hope to the selfish humanity ;)

your commends are just spot on ,answering almost all the questions that i had in mind before i read this.

I have something for discussion though.

I've been told to avoid using static unless absolutely necessary and always program to an interface.  although your approach makes perfect sence, what do you have to say to that?

Would you use the same approach if the presentation was a windows form?

Nik

ps:are there any similar tutorials out there or books written in the same way?
On Thursday, April 10, 2008 10:43:30 AM nik said:
i swear i had paragraphs and spaces! ;)
nik
On Saturday, April 12, 2008 1:59:42 PM ACostaF said:
Imar,
I've just read your articles and thought they are really very good.
Thanks for helping several programmers.
On Monday, April 14, 2008 5:03:31 PM Imar Spaanjaars said:
Hi Nik,

Who told you not to use static methods? I think it's quite common to use them in these kind of scenarios. In my case, the underlying pattern is referred to as the "factory pattern" where a static method in class is responsible for creating objects of the type of that class. This is not exclusive to static methods but IMO it makes them easier to work with. That is, I find:

  Address myAddress = Address.GetItem(10);

much easier to read then:

  Address myAddress = new Address().GetItem(10);

or

  Address myAddress = new Address();
  myAddress.Fill(10);

or

  Address myAddress = new Address(10);

One of the advantages of these factory method is that they can return null. So, if you delete item number 10 from the database, myAddress from the first example would be null. In the last example, where the constructor accepts the ID of the Address, you have to throw an exception when the item could not be found. Possible, but slightly awkward and difficult and not intuitive to work with.

The .NET Framework itself comes with many other static methods that return instances to calling code.

With regards to interfaces: yes, you can certainly do that. However, I tried to keep the design of this application simple, so it can be understood by as many people as possible.

With regards to books and tutorials: don't know that many that get into this in detail. You may want to try out Blogo.net, located here: http://ferdychristant.com/blog/articles/DOMM-7DDFWT

You may also want to take a look at the CSLA framework (and the accompanying book) from Rockford Lhotka (http://www.lhotka.net/).

Cheers,

Imar
On Friday, April 18, 2008 2:09:16 PM Munish said:
First of all i wish to tell that after searching lot from net and forumns and reading various article i found yours article is of great help and ofcourse code which you provide for download saves lots of time and helps things to understand more clearly.
On Friday, April 18, 2008 2:16:11 PM Munish said:
i am thinking should i post my basic questions to you. but i need to be more clear on followings:
Under Business Object:
1. what is usage of using System.ComponentModel;
2. what is usage of using System.Diagnostics;
3. usage of following:after comments
[
    DebuggerDisplay("Address: {Street, nq} {HouseNumber, nq} {City, nq} - {Country, nq} ({Type, nq})")
  ]
  
4. usage of "[DataObjectFieldAttribute(true, true, false)]"
5. when we are creating classes for list like AddressList.cs, we are writing syntax as follows:
public class PhoneNumberList : "List[PhoneNumber>]"
is PhoneNumber inside "List[ PhoneNumber]" is any name or it should belong to name of table like.


Under Business Logic:

7. Why :  "[DataObjectAttribute()]" before class declaration as in class AddressManager and "[DataObjectMethod(DataObjectMethodType.Select, true)]" before method declaration as public static AddressList GetList(int contactPersonId)
8. Why you have not written any code in try-catch block.
9. EXTRA: what is benefit of using object data source control over traditional query and coding

On Friday, April 18, 2008 3:33:34 PM Imar Spaanjaars said:
Hi Munish,

Maybe you should read part 1, 2 and 3 again and follow the appropriate links? Most of your questions are already dealt with....

1, 2, 3, 4, 7 are dealt with in part 2 with links to other articles.

5. Extensively dealt with in part 1 and 2

9. Extra to what? A bonus question? What's the price? Anyway, the answer is in part 1.

8. What's there to catch and handle? I usually let real errors bubble up to Application_Error in the Global class and deal with it there. Alternatively, look at ELMAH.

6. No 6?

Cheers,

Imar
On Saturday, April 19, 2008 11:02:00 AM Munish said:
Hi Imar,

well thanks to reply. okay as you suggested i will read parts again and will follow links again. if i found still in doubts i will post again

Regards,
Munish
On Monday, April 21, 2008 12:44:52 PM Michael E. said:
I really like your approach and therefore I am going to base my design on your approach.  Since I was not familiar with TransactionScope I started to play around with TransactionScope. I found that using TransactionScope on closed connections resulted in a distrubuted transaction.  In trying to avoid the extra overhead (as well as some MS DTC headaches that I had been experiencing) I came up with an alternate appoach that I would like to get your opinion on.  

I created a class called TransactionManager. This class contain a generic List of type SqlCommand and a method called Execute.  

In order to get my Insert, Update and Delete methods to decide whether or not to participate in a Transaction, I pass an instance of my TransactionManager to these CRUD classes.  After each of these methods processes their respective SqlCommand object, I do the following within the Insert, Update or Delete methods:

if (transactionManager == null)
{
     ExecuteNonQuery(sqlCommand);
}
else
{
    transactionManager.add(sqlCommand);
}


In cases where I don't want a transaction, I execute the CRUD methods in the same manner that you detailed in your article. If I want a transaction I do the following:

       TransactionManager transactionManager = new TransactionManager();
       Customer.Insert(customer, transactionManager);
       Order.Insert(order, transactionManager)
       transactionManager.Execute();

The Execute Method would then so something similar to the code below:

        SqlConnection connection = new SqlConnection(connectionString);
        connection.Open();

        trans = connection.BeginTransaction();

        for (int i = 0; i < transactionManager.Count; i++)
        {
            cmd                         = transactionManager[i];
            cmd.Connection        = connection;
            cmd.CommandType  = CommandType.StoredProcedure;
            cmd.Transaction       = trans;
            cmd.ExecuteNonQuery();

            trans.Commit();
       }

I was wondering what you thought of this approach?

Thanks

ps. Sorry for the lengthy post.
On Monday, April 21, 2008 1:12:57 PM Imar Spaanjaars said:
Hi Michael,

Looks that would work, although it's hard to tell without seeing it run.

Imar
On Monday, April 21, 2008 7:39:17 PM Michael E. said:
Thanks for the fast response. Since had hadn't seen the technique I was using to handle transactions before I was concerned it was really a bad idea.

Thanks again.
On Monday, April 21, 2008 7:41:15 PM Imar Spaanjaars said:
Hi Michael,

Don't take my word for it; I just quickly glanced over the code on this post. Make sure you run it in a real world project and test it well....

Imar
On Monday, April 21, 2008 8:03:16 PM Michael E. said:
Will do.

Thanks
On Wednesday, April 23, 2008 7:50:34 AM javed said:
Hi Imar,

I tried using this sample in my application, but i get below exception while populating BO

System.Data.SqlClient.SqlException: Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.

This exception comes when i am populating List with large number of records. Timeout exception comes at myREader.Read() statement.

What could be best approach while populating BOs with large number of records?
On Wednesday, April 23, 2008 5:52:12 PM Imar Spaanjaars said:
Hi javed,

Difficult to say without seeing more of your implementation. If you try to load a million records, this is expected behavior, and you'll need to look into paging.
However, you may also have a slow or badly tuned database system.

Try posting this on a forum like http://p2p.wrox.com. If you do post there, be sure to provide lots of other information including server specs, number of records and so on.
Cheers,

Imar
On Thursday, April 24, 2008 2:06:46 AM Chaumette said:
I am curious on everyone approach on error handling:

My approach==>

DAL: Catch DB exception and throw (with custom messages for user to understand)

BLL: Business rules/Validation and throw exception

UI: Catch exception bubbled from DAL/BLL and display

I was wondering if anyone follow a certain approach (like Imar's site/examples) or use any tools? I want to implement error logging, not sure how to approach it? Catch it in the global?
On Thursday, April 24, 2008 5:53:31 AM Imar Spaanjaars said:
Hi Chaumette,

It depends on what you want to log. I typically don't log business rule violations like a forgotten first name.

For more serious exceptions, you can use logging mechanisms like Log4Net and ELMAH.

Cheers,

Imar
On Thursday, April 24, 2008 3:20:07 PM Chaumette said:
Imar,

I was curious if there is a sample application/starter kit online that uses ELMAH? Or detail sample code to look at online?

I definitely want to only log the serious exception and some exception from the DAL. How do you configure it (is it easy?) to log only certain exceptions (for example exclude business rule exceptions)?

Is it common practice to log the errors in the error logs plus DB or xml?
If there is a possibility of db connection exceptions and should I even log errors in db? Is error reporting available only when you store errors in the DB?

Thanks,

Chaumette
On Thursday, April 24, 2008 3:28:19 PM Imar Spaanjaars said:
Hi Chaumette,

Search Google for ELMAH and you'll find a lot of information, including the ful source, the project's home page and an article by Scott Mitchell explaining how to configure ELMAH.

By catching exceptions in a layer, you prevent them from bubbling up to upper layers or other calling code . This way, you just let unknown / unhandled exceptions bubble up to ELMAH which will log them for you.

HtH,

Imar
On Friday, May 02, 2008 7:50:56 AM Munish said:
hi imar
thanks to reply to my previous queries.
now as i am feeling in the architecture i have one more question to ask.
i have already created class libraries 1) for Business Logic and  Business Object and 2) for Data Access.

now i need to know where i should put Enums as it is used both libraries and even .aspx pages.
On Friday, May 02, 2008 8:01:32 AM Imar Spaanjaars said:
Hi Munish,

Put them wherever they make the most sense; in this case, it sounds like the Business layer is a good location.

Cheers,

Imar
On Tuesday, May 06, 2008 8:24:28 AM Siegfried said:
Nice articles Imar!

I wondered if its possible to display the edit/new form of the 'contact person' when  editing or creating a new contact person when you click on the 'edit' linkbutton instead of being redirected to the page 'AddEditContactPerson.aspx'. I would love to also be able to create/update the contact person info on the same page. Could you please send or write down the code to accomplish this with the used examples in this article.

Thanks in advance!
On Tuesday, May 06, 2008 6:45:12 PM Imar Spaanjaars said:
Hi Siegfried,

It's a bit too off topic for me (the topic is layer design, not web page design) to get into with a lot of detail.

However, conceptually it's not so difficult. Instead of redirecting, hide a panel that contains the list and display a panel that contains the add/edit controls. Instead of Panel controls you can also use a MultiView.

If you want to do some fancy stuff, take a look at http://www.telerik.com/DEMOS/ASPNET/Prometheus/Grid/Examples/DataEditing/PopUpEditForm/DefaultCS.aspx

Cheers,

Imar
On Tuesday, May 06, 2008 7:05:04 PM Vladimir Kelman said:
Siegfried and Imar,
From my experience, having a read-only list pages and add/edit pages separate almost always work better. It implies simpler pages and better control. DHTML / AJAX adds some possibility of editing in place, but to me, it should be used in limited cases only.
On Wednesday, May 07, 2008 6:53:01 AM Siegfried said:
Hi Imar,

Thanks your for your reply. Also Vladimir for your feedback. I'll take a look at  the Multiview option which seems a good solution.
Vladimir : The goal was to leave the list of records with paging capability in sight for the user while editing (form  view) one record at the time at the same page which would seem user friendly to me but you have a point there :-))
On Monday, May 19, 2008 3:03:29 PM Vladimir Kelman said:
Imar,
Is it true that ObjectDataSource requires a BO object to have parameterless constructor? I prefer not to have such constructors, because it opens the door for errors  (like unpopulated objects). To me, parameterless constructor defeats the whole idea of a guaranteed object initialization by constructor. Sure, we still use GetItem()/GetList() to populate objects, but I think that parameterless constructor is a bad style, if object requires initialization.
On Monday, May 19, 2008 3:11:19 PM Imar Spaanjaars said:
Hi Vladimir,

First of all, I don't agree that a "parameterless constructor defeats the whole idea of a guaranteed object initialization by constructor". Why can't a parameterless constructor that you write yourself properly construct an object?

That said, take a look here: http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=368

It seems you don't have much to do, considering the fact you seem to hang around my web site half the day.... ;-)
On Monday, May 19, 2008 3:29:24 PM Vladimir Kelman said:
I already searched Google for "objectdatasource parameterless constructor" and found your link as a very first one :) Thank you, I'm reading it now.
Yes, parameterless constructor can properly construct an object, but not in this 3-tier design. We use DL's FillDataRecord() methods to populate BO object. So, constructor by itself shouldn't know about database and IDataRecord. What I wanted to use is a constructor with parameters. FillDataRecord() method calls this constructor and returns populated object. And there is no parameterless constructor - no danger.

Sorry for too many comments. That's because you wrote too good article :)
Actually we won a contract and creating a first version of ASP.NET application using your approach. The first release is due on June 1...
On Monday, May 19, 2008 3:38:07 PM Imar Spaanjaars said:
You can simply hide the constructor from the public world by marking it as private or internal so only the class itself or *Manager classes can access it.

Imar
On Monday, May 19, 2008 4:02:54 PM Vladimir Kelman said:
ObjectdataSource has to access BO constructor, so a parametrless constructor cannot be private, right? It could be internal if BO sits in App_Code, not in a different assembly...
I guess, we'd use parameterless constructors for now, because we don't have time. Then we'll try something like

protected  void ObjectDataSource1_ObjectCreating(object sender, ObjectDataSourceEventArgs e) {
  myBO_Object = myBO_ObjectManager.GetItem(ItemId);
  e.ObjectInstance = bClass;
}

although I'm not sure how to obtain current ItemId inside ObjectDataSource1_ObjectCreating() event handler.

Thanks again.
On Monday, May 19, 2008 5:14:30 PM Imar Spaanjaars said:
>> because we don't have time

That's the worst excuse for bad design, and the best way to loose the contract you just won.

Anyway, this is getting way too off-topic for this article, so I'll consider this matter closed. Maybe you should post follow up questions on a forum like http://p2p.wrox.com. They're quite specific and sometimes too off-topic for the original topic of this article: general N-Layer design. That makes it easier for others to wade through all these comments.

Cheers,

Imar
On Thursday, May 22, 2008 1:07:57 PM kurt schroeder said:
I'm asking for advise i guess. What is the best way or how or if could i add an
GetEnumerator to a business Object so that i can loop?
Item ITM = New Item();

                      foreach(object obj in ITM)
                       {
                           Response.Write(obj.ToString() );
                           // or do something useful
                       }
On Thursday, May 22, 2008 1:21:29 PM Imar Spaanjaars said:
Hi kurt,

Can you please ask these kind of questions on a forum like http://p2p.wrox.com?

That said, google for the C# yield keyword and GetEnumerator.

Cheers,

Imar
On Thursday, May 22, 2008 1:42:50 PM kurt schroeder said:
Thank You, You are correct it was a question for a different forumn, but in the interest of follow through the answer  was reflection or System.ComponentModel.
(i had check boxes posted from a previous page and created a list of all then i looped for the id
using System.ComponentModel;
....
....
....
               foreach (Control CON in LCON)
                {
                    if (((CheckBox)CON).Checked)
                    {
                        PropertyDescriptorCollection props = TypeDescriptor.GetProperties(ITM);
                        Response.Write((string)props[((CheckBox)CON).ID.ToString()].GetValue(ITM).ToString() + "\t");
                    }
                }
Your articals have continued to help me and others..
Thanks!!
KES
On Thursday, May 22, 2008 2:17:04 PM kurt schroeder said:
There may be other questions or comments concerning Linq. I do most of my new development based on custom business objects as described here. I'm wondering what are the advantages of this over linq?

Appreciated!!
KES
On Thursday, May 22, 2008 4:30:19 PM Vladimir Kelman said:
Imar, I'm afraid you let the genie out of the bottle.
On Thursday, May 22, 2008 5:19:46 PM Imar Spaanjaars said:
Hi Kurt,

Second attempt: please try a forum like http://p2p.wrox.com for these kind of questions. I know they are only semi off-topic as they are also about comparing LINQ to my patterns, but it's just too much for me to handle here as comments to this article. LINQ is a huge topic and warrants books and articles of its own; it's not something you can compare with my model in a single post here.

However, you can always try to hire me, and I'll be happy to talk about it for hours.... ;-)

Cheers,

Imar
On Wednesday, May 28, 2008 7:28:23 PM Piyush said:
You have only described scenarois in which data is fteched from a single table. In such a case, a single business object containing the getter and setter methods corresponding to the databasse fields are used. But, what about join queries where data is fetched from multiple tables. HOW TO HANDLE JOIN QUERIES IN 3-TIER ARCHITECTURE.
On Thursday, May 29, 2008 12:31:31 PM Emad Suria said:
Like you said that for complex business object formview and datagridview will not be suitable. I think I'm also experiencing this issue and your statement confirmed my doubts. Please could you elaborate further about the suitable approach, if possible plz provide any link to tutorial that handles such issues.
On Thursday, May 29, 2008 1:22:54 PM Imar Spaanjaars said:
Hi Emad,

You may want to take a look at my book ASP.NET 2.0. Chapters like Wrox CMS and Wrox BugBase show you how to implement manually code data entry forms.

@Piyush: it pretty much depends on where your data comes from. Joins in the database may not always mean relations in the object model. If you do need relations, you have a couple of options. You can call separate methods on BO's to fill properties on other BO's that in turn call their own sprocs.
Alternatively, you can have a single sproc that returns more than one resultset and then for example use NextReader to read out the data.

From a design principle, you don't always need to change a lot; just as the Addresses collection, your BO's can have properties whose type is another BO or a collection of BO's.

Imar
On Friday, May 30, 2008 2:41:20 PM Michael said:
Hi Imar, thanks for nice approach and detailed explanation.
I'm interested very much in your comments on the n-tier design in http://www.csharpcourses.com/2008/05/best-practice-for-multi-tier.html.
What potential changes may you envision in your design based on this approach?
On Friday, May 30, 2008 5:31:10 PM Imar Spaanjaars said:
Hi Michael,

I quickly glanced over the article you posted and I see some similarities between mine and that solution. It's an interesting approach, but slightly more complex than mine which may make it more difficult to implement and get right for smaller sites. However, for larger types of sites it certainly looks like a good way to do it.

I don't think that adding that kind of behavior to my approach would give you a lot of benefit. Keep adding enough of it and you'll end up with the same solution.... ;-)

My advice: try out both designs in real world applications and see what works best for you.

Cheers,

Imar
On Saturday, May 31, 2008 5:28:36 AM Emad said:
HI Imar
i've downloaded the file:
The MyGeneration templates for the Bll, BO and Dal classes in C#

It contains files *.vbgen. I've no idea how to use them as i've never used templates ever. Could you plz help me out. DO I need to add VB component to VS2005 which I deselected during installation.
Thanks
On Saturday, May 31, 2008 11:43:59 AM Imar Spaanjaars said:
Hi Emad,

You need a separate tool called MyGeneration: http://www.mygenerationsoftware.com/

Cheers

Imar
On Tuesday, June 10, 2008 12:38:20 PM Malkeet Kumar said:
If I want to add facade layer in BL then what changes I need to do?
On Thursday, June 12, 2008 2:08:22 AM steve said:
Great article.  I rewrote one of my apps using this method and it worked great.  Now, how about a "part four" showing how to handle sorting.
On Thursday, June 12, 2008 6:13:41 AM Imar Spaanjaars said:
Hi steve,

Already taken care of here: http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=428

Imar
On Friday, June 20, 2008 9:06:26 PM allan said:
Hello Emad! Great set of articles.

Minor problem, maybe you can help.  I used the core structure to build a code generator (this is in SQL using master stored procedures, etc).  I have all the code generated, I create a website, I dropped a gridview on the page, etc. etc., pulled up the website and voila!  I had a gridview full of data.  Here's the problem:  It's the last record in the dataset repeated over and over, equal to the number of records in the table the stored procedure pulled from.

I've compared my code with your code, using the contact code as a template, and it's duplicated line for line.  I'm jsut not sure why only the last record is being pulled in.

Any thoughts?
On Friday, June 20, 2008 9:15:12 PM allan said:
DOH!  Never mind.  I found it.  I had static variables set up on my business objects.  :)
On Friday, June 20, 2008 10:44:28 PM Imar Spaanjaars said:
Hi allan,

Emad?? Who's Emad? If you code the way you spell, I am not surprised you get these kind of errors.... ;-)

P.S. You won't get a reply by mail for this message as you require manual verification which I really hate. Add me to your list yourself up front, or don't receive replies. Sorry.

Imar
On Wednesday, June 25, 2008 10:51:20 PM Thomas said:
Imar,

These articles were the perfect blend of simplicity and detail to help me understand the concept thoroughly. My question is how would you handle the data from a non-"top down" perspective, such as a datagrid listing all email addresses in the DB with the customer name. I had two ideas:

1) Rather than the EmailAddress.ContactPersonId property, you would create a EmailAddress.ContactPerson property of type ContactPerson, but I thought this might create a circular reference, as that Contact Person would then contain the calling email address object

2) Creating a ContactPerson object for every instance of an EmailAddress as it is bound to the datagrid, but this seems like a lot of overhead (unless paging is implemented I guess)

I would appreciate any thoughts. Thanks.
Thomas
On Sunday, June 29, 2008 6:11:52 AM Dez said:
Hi and thanks for putting these great articles out there!!

I was wondering if and how your implementation could be used in conjunction with the Enterprise Library Application Blocks?

thanks!
On Tuesday, July 01, 2008 3:41:07 PM Imar Spaanjaars said:
@Thomas: You could also create an intermediate object that displays basic information, like a MailAddressSummary with a ContactPersonName property.

@Dez: You can use the application blocks where you see fit. You could for example replace the DAL code with the Enterprise DAAB.

Cheers,

Imar
On Sunday, July 06, 2008 2:35:22 AM Travis said:
One of the best articles on the internet for building a typical three layered application in .NET

The date selection tool was a bit painful to wind back 36 years.

I have $19,000 to build my application, so this qualifies. Tahir, are you listening ?
On Sunday, July 06, 2008 10:54:59 AM Imar Spaanjaars said:
Hi Travis,

>> One of the best articles on the internet for building a typical three layered application in .NET

Thank you.

>> The date selection tool was a bit painful to wind back 36 years.

I know ;-) That's why I said:

"Note that the current implementation for the calendar isn't very user-friendly for a birth date. Imagine your contact person is 35. This means you'll need to click around 420 times (35 * 12) to get at the right month back in 1971. Instead, you could add an additional drop down with the years that could allow a user to quickly select the relevant year. As other alternatives, you could drop the entire Calendar and use three drop down instead for the year, month and day or use the new Calendar control from the Ajax Toolkit that features some cool ways to browse through the calendar. "

>>I have $19,000 to build my application, so this qualifies

Qualifies for what?

Imar
On Wednesday, July 09, 2008 8:47:22 PM Lucas said:
Excelent. Thank you
On Thursday, July 10, 2008 2:21:37 PM Vladimir Kelman said:
I would change "This marks the GetItem method as the default Select method of the Address class" to "This marks the GetList method as the default Select method of the AddressList class", because an example above uses public static AddressList GetList().
On Saturday, July 12, 2008 10:04:33 AM Imar Spaanjaars said:
Hi Vladimir,

Fixed. Thank you.

Imar
On Friday, July 18, 2008 12:25:03 PM Rodolfo (from Brazil) said:
Hi Imar. Your article about  "Layered Web Applications" was really great, and the templates of myGeneration will be useful for me, because I will use them in my "final examination" on college. But I've a doubt about your architecture. If I wanna implement a option for search in my webforms. How do I make this? where I put the sql query? Because if I make the sql query dynamically according the filled fields in the presentation layer, I'll broke the hierarchy of your architecture.
On Saturday, July 19, 2008 11:24:28 AM Imar Spaanjaars said:
Hi Rodolfo,

I don't see the problem. First of all, I don't see why you need dynamic SQL to search for records. You can still use parameterized searches for complex criteria and use technologies like Full Text Search from inside stored procedures.

Secondly, I don't see why dynamic SQL would "break the hierarchy". Why can't you - if you must - build up a dynamic SQL statement in the DAL based on your search criteria?

Imar
On Monday, July 21, 2008 7:49:43 PM allan said:
"Emad?? Who's Emad?"  (hint:  look at the post from 5/31/2008...my bad).

So... IMAR!

Hey, I used your code as a model and built a code generator that uses the database (for whichever application) as the model.  But...I have a simple problem, and I think this may have been answered above but I couldn't find the whole reference.  Here's the issue:  I can't insert records.  The website doesn't throw an error, it just acts like it's all working.  I'm trying to INSERT from a detailsview (like I said, I think this was answered above somewhere).  Is this not best practice for this model?  What's the recommendation without "code generating" insert pages (web controls) for each object?

Oh.  Not sure why you can't reply via email.  (??)
On Monday, July 21, 2008 8:11:34 PM Imar Spaanjaars said:
Hi allan,

Your mail account has a verification system. So, when my web site sent you an e-mail about a new message being posted here it was rejected. I had to manually go to some web site to allow my address as a white listed address. I hate such systems so I didn't approve my address so you won't get replies. Sorry.

Anyway, difficult to tell without seeing your code. Maybe you can post this on a forum like p2p.wrox.com so it's easier to post code and find out what the problem is?

Cheers,

Imar
On Friday, July 25, 2008 11:42:16 AM Rodolfo (from Brazil) said:
Hello Imar, I have another doubt.
Imagine I have two tables: customers and products
So, using your myGeneration templates I have created the customersBO, customersManager, customersDB and productsBO, productsManager and productDB.
For isolated pages, this architecture works fine, I mean, one page only for customers and another page only for products.
But now, I have to make a page that contains a grid with the two information, this grid will show the customers and products. The SQL query is too simple(select a.customerName, b.productName from customers a, products b where a.id = b.id).
My question is: How I make this? Because all calls to database pass through BLL, and the BLL layer constains one class(xxManager) for each table. But in this case, my query isn't especifc for a table, and this query won't either return a list of managers or list of customers, so, my gridView won't use the ObjectDataSource. If I return a DataTable, I might break the architecture.
How can I solve this issue?
On Monday, July 28, 2008 9:52:39 PM Imar Spaanjaars said:
Hi Rodolfo,

You can solve this with a "Summary" type of object that only contains the data from both classes that you need.

Alternatively, you can use LINQ to create such an object (in the form of an anonymous type) on the fly.

Cheers,

Imar
On Tuesday, July 29, 2008 6:49:30 PM Michael E. said:
As I have said before. I love the articles.  On occassion I go back and re read them to see if I can glean anymore information.  The comment I am about to make is not a critism, but a question.  Since the basic design is one set of classes per table, I was wondering if the ContactPerson which contains collections of objects from other tables violates this design?

Would it be better to create a ContractPerson that contains only information from the ContactPerson table and then create a class that contains a ContractPerson along with the required collections or is this idea overkill?

Thanks
On Saturday, August 02, 2008 9:38:32 AM Imar Spaanjaars said:
Hi Michael,

I wouldn't call it overkill, but I don't think it's a great design either. One class per table is a result, not a goal of this design. In fact, when you start modelling many to many relations you may end up with classes that get data from more than one table. For example, a relation between two entities can be modelled as a class with data from two tables, and data from a junction table that links the two.

Personally, I find it much more intuitive and easy to use to write this:

myPerson.Addresses

than it is to write:

myPersonContainer.Person
myPersonContainer.Addresses

IMO, addresses belong to the Person, so myPerson.Addresses makes perfect sense.

What would be the benefits of the separation you propose?

Imar
On Monday, August 04, 2008 12:14:04 PM Michael E. said:
I see your point.  I have to keep reminding myself that the classes represent a business relationship and not the database schema. Do you plan on writing any articles that provide examples for more complex modeling?  I know I would find it helpful.

Thanks
On Monday, August 04, 2008 12:34:54 PM Imar Spaanjaars said:
Hi Michael,

Not any time soon, I think. I have lots of other stuff on my Todo list... ;-)

Imar
On Tuesday, August 05, 2008 6:29:39 PM Vasantha said:
Imar,
Your articles are full of information and explain the best practices of programming. Thanks a lot. I have a question though.
In one of my SQL tables, i have 2 columns that store Images (with data type as image). I created a class called "Photo" with "OriginalImage" and "ThumbImage" as 2 properties of type Image.
When i fill the Photo object using FillDataRecord method, how do i fill these Image properties?
Thanks,
Vasantha
On Tuesday, August 05, 2008 6:39:17 PM Imar Spaanjaars said:
Hi Vasantha,

I would lazy-load the properties. That is, don't load them in FillDataObject, but only when the property is accessed the first time (e.g. check if the private field is null, then get the image from the database).

For more info about storing and retrieving images in a SQL Server database, take a look here:

http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=414

Hope this helps,

Imar
On Wednesday, August 06, 2008 2:38:30 PM Vasantha said:
Thanks a lot Imar. That worked!
On Friday, August 08, 2008 8:30:12 AM sanjeev Kumar said:
Hi Imar

Your article helped me lot in starting n-tier web application,
Now i am facing problem to show the SqlInfomessages, that raises while executing the stored procedure, it captures in DAL  and can be shown using Message box, but i want that has to be displayed in webform as a message, so that client can make out that there is some process going behind, how can achieve that, your early reply will be appreciated

Regards
Sanjeev
On Thursday, August 21, 2008 8:11:37 AM Imar Spaanjaars said:
Hi sanjeev,

Search Google for "asp.net progress bar" to find out how to provide feedback to the user. You also need to find a way to capture the messages from the stored procedure. Not sure how to do that exactly, but Google may again be of help.

Cheers,

Imar
On Saturday, August 23, 2008 1:50:21 PM kurt schroeder said:
Hi sanjeev,
as i have said before these articals have been a GREAT help to me and i am now using this system for most medium to large site developemnt.

1. I'm wondering what it would take to make this Serializable?

2. I may be talking about layered developemnt at a dot.net group and i 'd like your permission to reference these articals. Where i note items here i would want to note you as the source. Would you mind?
On Saturday, August 23, 2008 2:04:13 PM Imar Spaanjaars said:
Hi Kurt,

Sanjeev? Are you sure you're talking to the right person? Don't you mean Imar?

Anyway, feel free to pass around links or references to these articles. Please use Quick Doc ID 416 as the starting point.

To make your objects Serializable, all you need to do is apply the

[Serializable]

attribute to the class.

Cheers,

Imar (not sanjeev)
On Saturday, August 23, 2008 2:45:01 PM kurt schroeder said:
it's Saturday. sorry , not truely awake when i wrote that. and thanks!!
lol, consider it comic relief for that day!!
On Thursday, August 28, 2008 1:12:35 PM Michael E. said:
As I have stated before I am using this article for the basis of my project. Since you have helped me so much I wanted to pass along a minor point that I discovered that might be of help to you.  Recently I spent some time reading about the validation controls.  One of the things that I found was that certain controls including the button control execute the Page.Validate method internally.  Therefore you don't need to execute the Page.Validate method in your btnSave_Click event.  While this is an extremely minor point I wanted to pass it along as a small way to repay you for all of your help.

On Wednesday, September 17, 2008 7:00:15 PM Kris said:
In the project "N-LayerDesign" a new row is added in different screen(AddEditContactPerson.aspx).
                 How do we add to the existing grid which is displayed in "Default.aspx"
On Wednesday, September 17, 2008 7:06:02 PM Imar Spaanjaars said:
Hi Kris,

Typically, a separate DetailsView control is used. Alternatively, look at these different methods to do in-line inserting in the GridView:

http://geekswithblogs.net/mnf/archive/2006/09/02/90066.aspx

Cheers,

Imar
On Wednesday, September 17, 2008 7:07:26 PM Imar Spaanjaars said:
Hi Michael,

Sorry I overlooked your comment.

You are absolutely right; thank you. It's fixed for the upcoming V-Next release of these articles.

Cheers,

Imar
On Wednesday, September 17, 2008 7:18:04 PM Imar Spaanjaars said:
BTW Kris: if you're using ASP.NET 3.5 you can also use the new ListView control.

Cheers,

Imar
On Thursday, September 18, 2008 2:56:22 AM M. Eichner said:
Glad I could be of some help.

Take care
On Thursday, September 18, 2008 1:37:27 PM Kris said:
Hi Eichner,

    Using the GridView i display AvailableLocation(textbox),Products(dropdown),Status(Check box).On click of "Add" button,new row should be displayed at the top of the existing GridView,User keyins the textbox value,selects a value from the dropdown,and checks the Check box and Saves the row.

          How do i dynamically add a new row to a existing GridView as first row.

Regards
Kris
On Thursday, September 18, 2008 1:42:24 PM Imar Spaanjaars said:
Hi Kris,

Did you look at the articles I linked to? It's all in there.
And who's Eichner?

Imar
On Friday, September 19, 2008 2:47:05 PM Kris said:
Hi Imar,

   Firstly i am sorry for the typo.I read the articles in the link you shared.In Alex Furmanski article he's using customized grid and binding hard coded data.I want to bind data from a Data source(sql server in this case).would you pls guide me in this regard.
       some articles hold good if the table has an empty row.The one by Fredrik,uses a button on the footer.
             In my case the button exists the same way as "N-LayerDesign" only difference is when i click on "Add new row" i will should the empty row in the existing grid as the first row and the existing rows will follow.

Regards
Kris
On Saturday, September 20, 2008 2:14:45 PM Imar Spaanjaars said:
Hi Kris,

Can you please post this on a forum like http://p2p.wrox.com? It's a bit too off-topic for me to discuss here.

Did you try out the ListView?

Imar
On Saturday, September 20, 2008 3:57:15 PM ragu said:
A well defined, usefull & pragmatic article.

I have read all your 3 article and comments in one shot
It will really help in my next application
On Wednesday, September 24, 2008 4:43:10 PM M. Eichner said:
I found a problem when highlighting the gridview using GridView.skin theme.  This problem occurs when the gridview contains multiple pages.  

If I select a row on the second page of the grid (let's say the 4th row) and then I go back to the previous page, that same row on the first page (in this case the 4th row) is highlighted even though it was never selected.

I fixed this in the gvContactPersons_PageIndexChanged event in the following manner:

gvContactPersons.SelectedIndex = -1;

I am not sure of two things:

1. Is there a better way to fix this problem?
2. Is there a way so that when I go back to the second page that the row that I had selected remains highlighted?

Thanks
On Wednesday, September 24, 2008 9:36:47 PM Imar Spaanjaars said:
Hi Eichner,

This is by design; the control keeps track of its SelectedIndex and doesn't care about the underlying data. All it knows is that it's displaying a single page, with one selected row which it'll maintain for you. Resetting it to -1 is a logical thing to do.

To keep track of the index over multiple pages, you need to store a combination of PageIndex and SelectedIndex in ViewState. Then when you are displaying a matching PageIndex, you can set the correct SelectedIndex.

Hope this helps,

Imar
On Thursday, September 25, 2008 12:11:02 PM M. Eichner said:
My mistake.   It seems a little odd to me that the SelectedIndex remains unchanged when the page changes.   No matter, resetting the SelectedIndex to -1 when the page changes is a simple fix.

Thanks for clarifying this.
On Monday, October 20, 2008 5:27:17 AM Daren said:
Mapping tables to business objects is good, but I seem to run into troubles when I want to join tables and return a single result set (as an object). The problem is that the business object naturally doesn't have to the extra column(s) that have been returned when I do a join.

Is there a good way to deal with this with the N-Layer approach?
On Monday, October 20, 2008 7:03:18 PM Imar Spaanjaars said:
Hi Daren,

This is certainly possible. You have a couple of options:

1. Do as I have done in the ContactManager application. That is, let nested collections (Addresses and so on) load themselves into the properties of the container class.

2. Have one SELECT statement with the JOIN, use the Reader to fill the container object and then also fill child objects (you can do this in FillDataRecord for example). You may need to create specialized constructors on the child classes to make it easier to construct them.

3. Use a stored procedure with multiple select statements, like this:

SELECT Id, Name .... FROM ContactPerson WHERE Id = @id
SELECT Id, Street ... FROM Address WHERE ContactPersonId = @id

You can then use NextResult on the SqlDataReader to get the second result set which you can then pass to the constructor for the AddressCollection for example.

All in all, you have many different options. Most of it comes down to how you've designed your database and application.
On Thursday, December 04, 2008 2:45:10 PM M. Eichner said:
I am sorry to bother you again regarding MS DTC. I wouldn't have if I was not stumped. I am having intermittent problems with
MS DTC (Communication with the underlying transaction manager has failed.).  

My system follows your design with my DB classes opening and closing connections.  According to the following article http://scott.klueppel.net/2008/10/30/CommunicationWithTheUnderlyingTransactionManagerHasFailed.aspx you cannot open and close a connection within a transaction.  I was under the impression that you could.

Is this guy wrong or am I mis understanding something. If I don't get to the bottom of these MS DTC errors soon I could be in real trouble at work.

I have also noticed that these problems happen more frequently in transactions that contain GetItem calls. I am not sure if that is part of the problem or not as the problem happens without the GetItem calls as well.

Thanks
On Sunday, December 07, 2008 10:19:38 AM Imar Spaanjaars said:
Hi Eichner,

From the article:

If ShouldContinue() opens and closes an SqlConnection, the TransactionScope object has no means by which to commit or rollback this part of the transaction.

This makes sense to me...

Also, why are you calling read methods like GetItem inside a transaction?

Imar
On Sunday, December 07, 2008 8:09:01 PM Vladimir Kelman said:
Eichner,
I know nothing about Web Services, which are used in that Scott Klueppel's example, but within the architecture described here by Imar MS DTC works  fine and allows to begin and commit transactions in BLL layer, encompassing several DAL calls, where DAL methods open and close SqlConnection as necessary. It gives a real freedom when you need to save data for more than one BO objects. I remember there was some issue with opening more than one SqlConnection per time... are you doing that?
We suggessfully set up DTC on WinXP. Server 2003 and Server 2008 machines with database and application being hosted on different servers.
Look at http://pro-thoughts.blogspot.com/2008/11/configuring-ms-dtc-on-winxp-server-2003.html
On Monday, December 08, 2008 1:34:15 PM M. Eichner said:
Imar

First thanks for getting back to me on this. I appreciate it.

I am confused by one thing (actually more then one, but that's life).  You said the statement about opening and closing a connection will cause a problem with the transaction scope makes sense to you.  Doesn't each of the DB classes in your design open and close it's own connection?  Won't this then cause a problem when your BOManager classes execute multiple DB calls within a TransactionScope?

As far as the GetItem question goes, I don't normally execute a GetItem within a TransactionScope. I did do this one time as a test just to see if it could be done.

Thanks again

On Monday, December 08, 2008 1:51:20 PM M. Eichner said:
Vladimir Kelman

Thank you for the information. I don't have multiple connections open at the same time.  

I have been trying to find out why I have this problem sporadically.  I have two quesses:

1.  The problem has never happened in my production environment.  It has only happened in development. My guess is that since development is  running against the filesystem instead of IIS that this could be the problem.

2.  I am wondering if this problem could be somehow due to connection pooling.

If I was sure that this was only a development issue I could easily live with it.  If this problem creeps its way into production I am in real trouble.

Thanks for the help.
On Monday, December 08, 2008 8:32:02 PM Imar Spaanjaars said:
Hi Eichner,

Yes, that's true. I think - although I am not 100% sure - that the transaction manager is smart enough to keep track of things; it keeps track of connections that are opened within its scope so I guess it signals the participating servers to wait with finalizing the transaction.

Not 100% sure though; cases where I've used it have always worked for me, so it's difficult for me to say why it doesn't work for you...

Imar
On Monday, December 08, 2008 8:52:21 PM M. Eichner said:
Thanks for the help.  So far this is only a development issue. I came across someone else who had the problem in development only. When he went into production and everything ran under IIS, it all worked fine.  As Vladimir Kelman stated, your design (and my use of it) should work.  Hopefully it will.

Thanks again
On Wednesday, December 10, 2008 3:53:02 PM Vladimir Kelman said:
Imar, I'm sorry for over-using your private site. Eichner, I don't have your contact info,  I would appreciate if you put your comments on DTC issues somewhere (in http://pro-thoughts.blogspot.com/2008/11/configuring-ms-dtc-on-winxp-server-2003.html). It could help other people. How is it that on your developer's machine it is "running against the *filesystem* instead of IIS"?
In my experience it works fine against IIS6 on Win 2003 Server and against internal Web Server of Visual Studio 2008 on Win 2008 Server and WinXP.
On Tuesday, January 06, 2009 6:19:32 AM omar said:
Dear imar
Thanks in advance for your help and efforts
My question is about your series (N-Layered Web Applications with ASP.NET 2.0)
How to make inner join between 2 tables and get multiple column values
And how to return these multiple values in getList() Method to bind to data grid
Example
I have two tables Articles, Zones
[Articles] {id int , title string , body string , zone int}
--------------------------------------------------------------------
Id   title body zone
0 some title some body 1
1 another title another body 2

[Zones] {id int , name string , template string}
---------------------------------------------------------------------
Id name template
1 sport sport.htm
2 arts arts.htm

My question is how to write  ArticleManager.GetList() to bind to the grid view as follow
Id   title body zone name
0 some title some body sport
1 another title another body arts
Note that zone property in Article BO is integer but in above  want it string (from zones table)
Note that GetList Return List of  Articles Objects (which have zone as integer )
On Tuesday, January 06, 2009 7:46:54 PM Imar Spaanjaars said:
Hi omar,

I would give Article a Zone property and load the data in the zone. E.g.:

myArticle.Zone = new Zone(myReader.GetInt32("ZoneId"), myReader.GetString("ZoneName"));

Check out my book ASP.NET 2.0 Instant Results, last chapter, for an implementation of this.

Alternatively, you can create an intermediate object called ArtileZone that loads the data from the database with a JOIN.


Hope this helps,

Imar
On Tuesday, January 27, 2009 8:36:41 PM Hector said:
Hi Imar, i've using your template with MyGeneration for the Csharp version.

But, I have a little problem that I can't undestand. The problem is when I try to generate my BusinessObject, I run your template with myGeneration but in the "Private Variables" appear some types like "Unknow" for example:

private Unknow _id;

Thanks.
On Wednesday, January 28, 2009 7:22:30 AM Imar Spaanjaars said:
Hi Hector,

I haven't written the templates so I don't know exactly what is going on. Maybe your database contains a column with a type that is not supported by MyGeneration or by the template?

Imar
On Monday, February 23, 2009 6:21:50 PM Judith Barer said:
If you are using formview or detailsview for inserting records, and you are using business objects, if you have default values for the business object, how would you populate the business object with the default values on a new object? It does seems like on a detailsview or formview new , that the business object is not created until update is selected. I would like to create a new object when the user hits new so that the defaults can be populated.
Do you know how I could I do this?
Thank you so much.
On Monday, February 23, 2009 6:36:48 PM Imar Spaanjaars said:
Hi Judith,

Take a look here: http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=368

Imar
On Monday, February 23, 2009 6:55:33 PM Imar Spaanjaars said:
Hi Judith,

On second thought, that will probably not work as the Creating event isn't executed when you click the new button.

You may need to hook into one of the event that the Details event exposes and then use FindControls to get references to relevant controls and set the defaults.

That's one of the reasons I usually stay away from the DetailsView for complex tasks. I much rather have a separate details page with my own controls which I can control (no pun intended) based on the defaults of an BO.

Cheers,

Imar
On Monday, February 23, 2009 7:05:57 PM Vladimir Kelman said:
"I much rather have a separate details page with my own controls which I can control"
Couldn't agree more. We implemented a first page in our application (which uses N-Layer design) with a ListView control... and it turned out to be a big mistake. After gaining that experience with gigantic code-behind file providing very diverse operations, we switched to separate list pages (using GridView in a read-only mode) and detail pages (using either FormView or, better, just assembled manually). This pages have almost no code in code-behind and are much easier to manage.
On Monday, February 23, 2009 7:43:38 PM Imar Spaanjaars said:
Yeah, that's exactly what I do in: AddEditContactPerson.aspx

Cheers,

Imar
On Monday, March 02, 2009 6:53:35 PM Judith Barer said:
When a record is added on the detail page and then the program is redirected to the gridview page, what is the best way to have the focus of the gridview be the new record - the page in the gridview with the new record should be displayed.
I tried searching through the records in the gridview to match the datakey of the new record but  a databind is required to search thru each page and that is very very slow.
I suppose you need a stored procedure that would give you the page number that a particular record would appear on.
Is that right?
thanks for your help.
Judith
On Monday, March 02, 2009 8:05:33 PM Imar Spaanjaars said:
Hi Judith,

It's a bit too off-topic for me to answer here in detail, but yes, that's probably what you need to do. In short:

1. Return the ID of the new record to the list page

2. Use the ID to determine the index of the record in the "pages" of the GridView using a stored procedure or direct SQL statement.

3. Get the required page of data and bind it to the grid.

Cheers,

Imar
On Tuesday, June 02, 2009 8:10:12 AM Fredrik said:
I am wondering why you are using stored procedures instead of having the sql in the code?

Is there some kind of perfomance reason or to separate the code from the sql or something else?

I am also wondering if you (or someone else) has built a code generator for your n-layered backend?

Thanks in advance!
On Tuesday, June 02, 2009 4:50:48 PM Imar Spaanjaars said:
Hi Fredrik,

Personally, I find stored procedures easier to write, manage and secure. However, in-line SQL would work equally well.

With regards to the templates: did you see the MyGeneration templates?

Cheers,

Imar
On Monday, June 22, 2009 6:39:10 PM Pedro said:
Imar, I loved all three articles. They really helped me understand what a DAL and BLL are, and I needed that understanding for my current project.

We have over 100 different databases on a single instance of SQL Server, and though most of them share the same structure, consolidating them is not an option for now. Which database will be used to provide the data depends on what URL the user chooses to view, so instead of using a single connection string from web.config, I'm creating a connection string based on the the current Request.Path property and passing it to the Manager class so it knows which database to populate the business objects from.

Since I need to pass a parameter to the Manager constructor in order to pick the database to use, I can't set the TypeName and DataObjectTypeName properties in the markup; I'd have to do it programmatically. How can I feed my GridView a List&lt;T&gt; of business objects that I generate on the code-behind?

Thanks!
On Monday, June 22, 2009 7:07:08 PM Imar Spaanjaars said:
Hi Pedro,

You *can* still set these properties in markup, and then handle the Selecting event to pass the right information:

http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=368

Cheers,

Imar
On Tuesday, June 23, 2009 1:32:53 PM Pedro said:
Imar, thank you! That's a great fix for what I need.
On Friday, June 26, 2009 4:54:49 AM SAM said:
I've created a class Employee and its collection class through generics Employees.

Now I've created instance of Employees object as-

Employees oEmployees = new Employees();

now i'm trying to filtering items from oEmployees as-

oEmployees.FindAll(delegate(Employees E) { return E.EmployeeId > 7; });

oEmployees.FindAll returns  List of Employee. now I want oEmployees.FindAll will return Employees generics collection. what should i do to achieve this?

On Saturday, June 27, 2009 9:54:21 AM Imar Spaanjaars said:
Hi SAM,

Take a look at the validation framework introduced in this article: http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=477

It features a class called BrokenRulesCollection that inherits Collection[BrokenRule]

It has a constructor that initializes itself with an IList[BrokenRule] to store its inner collection. You can then call ToList on the LINQ query to get an IList compatible collection, and pass it into a new collection. E.g. something like this:

new Employees (oEmployees.FindAll(.....).ToList[Employee]));

Hope this helps,

Imar
On Friday, January 22, 2010 5:52:00 PM Tulsi said:
Hi Imar,

Thanks for writing these articles.  They are very helpful.

I'm trying to recreate the examples you have posted but am unable to figure out why I am unable to see updates I make to the css file.  So for ex, I changed color(GridView.HeaderStyle in the Styles.css file) to color: Fuchsia.  But I don't see this update when I run the project.

Is there anything else I need to do to see this update?

Thanks.
Tulsi
On Friday, January 22, 2010 7:13:02 PM Imar Spaanjaars said:
Hi Tulsi,

Maybe the browser is using a cached copy of the stylesheet? Try pressing Ctrl+F5...

Imar
On Tuesday, January 26, 2010 4:22:57 AM Tulsi said:
I tried pressing Ctrl+F5, but still had no luck.  

I got it to work if I put the Styles.css file in the same folder as the skin file so for ex, both files are under >App_themes>Default.  Is this common practice for real-world applications?

Thanks.
Tulsi

On Tuesday, January 26, 2010 7:34:26 AM Imar Spaanjaars said:
Hi Tulsi,

It depends on where you had the CSS file before. CSS files in the theme folder are included automatically. In all other locations you need to manually hook them up, for example to a page or master page.

Cheers,

Imar
On Monday, February 01, 2010 4:33:10 PM Tusli said:
Thanks for the info.

Once I added, link href="Css/Styles.css" rel="stylesheet" type="text/css" , to the default.aspx I was able to see the formatting as described in the article.

Thanks again.
Tulsi
On Tuesday, February 09, 2010 2:18:47 PM capster said:
can you please explain the process how this data binding exrpression is working. and why its using the helper class
'<%# Helpers.GetTranslation(Eval("Type")) %>
also whenever i hit the add contact person button it takes me to Login page and the username ans password doesn't seem to work it give me a sql error which database its using to get the username and pasword. please let me know.
Thanks
Capster
On Tuesday, February 09, 2010 2:40:48 PM Imar Spaanjaars said:
Hi capster,

It simply calls a static member on the Helpers class to convert the value of the enum to a localized version.

With regards to security: use the WSAT in Visual Studio (choose Web Site | ASP.NET Configuration) and create your own accounts. The app uses all standard ASP.NET stuff so whatever you know about that applies here.

Cheers,

Imar
On Tuesday, February 09, 2010 5:04:10 PM capster said:
Hi  
why is attribute
DataObjectTypeName="Spaanjaars.ContactManager.BusinessEntities.ContactPerson" added when u define the ObjectDataSource for contact person.  Is TypeName="Spaanjaars.ContactManager.Bll.ContactPersonManager"  not enough.  can we not remove DataObjectTypeName attribute  and still make the datagrid work. can u give a detail explaintion.
Thanks
On Tuesday, February 09, 2010 5:30:30 PM Imar Spaanjaars said:
Hi capster,

Again, this is all standard ASP.NET stuff. This article is not meant to teach you ASP.NET but rarther to teach you N-Layer design with ASP.NET.

Please take a look at the ODS documentation: http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.objectdatasource.aspx or post question like these in a forum such as this one: http://p2p.wrox.com/index.php?referrerid=385

And BTW: the best way to find answers to these kind of questions is to simply try it out. Did you do that?

Cheers,

Imar
On Friday, February 19, 2010 6:01:46 AM Nahom said:
Hi imar, i first would like to thank you so much for sharing your knowledge,
I have one small problem, I added a Gridview and tried to bind it with the one of my business entities, but I could not find my business entity classes in the dropdown menu like in Figure 3 of this article. Could u advise me on how to get my business entities without moving it to APP_Code as the wizard suggests like the way you did it by including the “Spaanjaars.ContactManager.BusinessEntities.resources.dll” in the Bin folder?
Thank you imar again for all your help.
On Monday, February 22, 2010 10:05:36 AM Imar Spaanjaars said:
Hi Nahom,

If you are using a Web Site Project, you *have* to use App_Code.

Cheers,

Imar
On Friday, March 19, 2010 4:10:34 PM Tulsi said:
Hi Imar,

I'm having a problem with the following functionality...

To clarify the process that takes place when a user enters a new e-mail address, here's a detailed description of all the steps --->

Step 9 --> The DAL saves the EmailAddress in the database and returns the new ID of the e-mail address to the calling code.

When I try to add a new email address, the db is not autogenerating the new ID of the email-address and returning it to the calling code.  I'm getting the following exception...

Cannot insert the value NULL into column 'Id', table <snip by="Imar" />; column does not allow nulls. INSERT fails.
The statement has been terminated.
The 'sprocEmailAddressInsertUpdateSingleItem' procedure attempted to return a status of NULL, which is not allowed. A status of 0 will be returned instead

I compared the emailaddress table I created with the one you provided and noticed that "Identity Specification" under column properties was set to "No".  I'm assuming this is the problem.  The designer will not allow me to set this to "Yes" and treats it as a read only field.  

I have a few questions......

(1) Does "Identity Specification" need to be set to yes?  If so, how do I do this?

(2) Is there anything else I need to do to autogenerate this Id?

Thanks so much for you help.

Take care,

Tulsi


On Friday, March 19, 2010 4:52:36 PM Imar Spaanjaars said:
Hi Tulsi,

Re 1) The simplest thing to do is delete the Id column, save the table, and then add it again.

2) No; an identity column is automatically incremented by the dabase...

Cheers,

Imar
On Monday, March 22, 2010 3:19:36 PM Tulsi said:
Thanks Imar.   Your suggestion works great.

Tulsi
On Saturday, March 27, 2010 6:25:23 AM alaa aldeen mirgani hassan said:
all your work it great
On Tuesday, April 27, 2010 9:20:31 PM Mike said:
hi Imar

Thanks for your great articles.I follow them religiously.

i am having a problem using your code to read only one instance of the contacts that i have saved to the database. say i have 5 names in the database and at the click of a button i want to show a contact with id =4. I keep getting only the first name in the database even if i pass id 2 or 3 or any other number.its like id is set staticly at 1.please provid me a short code on how i would be able to retrieve a specific entry using its database id.I want to have a page where if i pass the database id i can get that persons contact details only output to the screen. I have managed to use the list to view all contacts but failing ot view only 1.



regards
Mike
On Wednesday, April 28, 2010 6:12:46 AM Imar Spaanjaars said:
Hi Mike,

Something must go wrong somewhere in your own code. Check out the page UseApi.aspx in the Alternatives folder. It shows you how to retrieve an item by its ID.

>>I follow them religiously

Don't follow things religiously; use what you think makes sense, change what you think can be improved.

Cheers,

Imar
On Thursday, May 06, 2010 12:12:53 PM Kurt Schroeder said:
I still use this in most of my site development.
On Tuesday, May 18, 2010 11:43:59 PM sms said:
Im new to VB, asp.net and sql.  There's so much I'm new to on this day, but your tutorial has been rediculously clear and helpful.  thank you Thank you .... THANK YOU!  ;-)
shari
On Wednesday, June 30, 2010 6:21:39 PM Sly said:
Your code seems to be exactly what we are looking for. I downloaded it and created a project using visual studio 2008, on .net 3.5.

When I look at the class diagrams, all the Names are in red with an erro that the "Type cannot be found.

Additionally, the code is throwing the error "The type or namespace name 'Spaanjaars' could not be found(are you missing a using directive or an assembly reference"

Any idea what is going on?
On Wednesday, June 30, 2010 6:40:54 PM Imar Spaanjaars said:
Hi Sly,

Did you look at the 3.5 version of this article series and try to open the source that ships with it?

How are you opening this 2.0 version of the source? As a Web Site Project? Can you provide more details about the exact behavior you're seeing? E.g. how do you view the diagram and what error do youu get where?

You shoud be able to open the source for this series as a web site project in Visual Studio and the source that ships with the 3.5 series as a web application project without any problems.

Imar
On Wednesday, June 30, 2010 7:15:46 PM Sly said:
Missed the part about the 3.5 version...let me try that.

THANK YOU for your quick response.
On Thursday, July 01, 2010 10:56:16 AM Mike said:
Hi Imar, good day !

How does the object datasource knows which records to delete when we click the delete button ? How does it know the record id of the row iteslf ? we did not specified anywhere in our code...

Previously I am used to of fetching the row id, datakeys, its current index etc and then passing them to DB functions. I am really surprised that this works automatically with the command name 'delete'. Can you explain me a little if I don't waste your time.

Thank you, Mike.
On Thursday, July 01, 2010 10:59:30 AM Imar Spaanjaars said:
Hi Mike,

It keeps track of the ID using DataKeyNames="Id" that is set on the GridView.

Cheers,

Imar
On Monday, July 05, 2010 5:43:14 AM Mike said:
Hi Imar, good day. Thanks for the reply.
One thing is confusing me...

1) If the address/phone numbers tables had a lot of columns (say about 20), would you still fetch a whole object for the GetAddressList() to display in gridview ? Would'nt that mean a lot of unused object attributes ?

2) One would only be showing about 5 or 10 fields in a gridview row so fetching all the fields in an object would not justify in that case?

3) Do we create sub objects with only gridview needed fields for that case ? What would be the best approach ?

Thanks, Mike.

On Monday, July 05, 2010 6:16:34 AM Imar Spaanjaars said:
Hi Mike,

Yes, that's what I would do; create something like an AddressSummary or AddressViewModel (to be more in line with how MVC does and call things) which would only get some of the fields. You could create a separate GetList method on that class.

Cheers,

Imar
On Monday, July 05, 2010 9:48:12 AM Mike said:
Thanks Imar. Would you apply inheritance on the summary classes (from base classes) or should these be just individual classes with different managers/DB etc?
On Monday, July 05, 2010 11:10:47 AM Imar Spaanjaars said:
It depends, but usually I create simple DTO's with just a few automatic properties. You can then create a separate manager, or add methods like GetSummaryList in the existing manager classes.

Cheers,

Imar
On Tuesday, October 05, 2010 3:29:27 PM Tulsi said:
Hi Imar,

Thanks for these articles.

I'm trying to apply this design to a current project and I need to ask if there's a way to do the following....

Looking at step 10 under section, "Managing EmailAddress Objects for a ContactPerson" --> Step 10 (The Save method in the business layer assigns the new ID returned from the data access layer to the Id property of the EmailAddress object and then returns that ID.),

is there a way to the grab the new ID returned in this step and then use it in the fvEmail_ItemInserted event?   How can I access this new ID so I can use it in the corresponding FormView ItemInserted event?

Thanks,
Tulsi
On Tuesday, October 05, 2010 3:44:28 PM Imar Spaanjaars said:
Hi Tulsi,

Nope, there isn't.

The FormViewInsertedEventArgs exposes a collection called Values that contains the items being inserted (e-mail, ContactpersonId and so on). However, there's no way to communicate back the new ID.

You could create a separate page (or a User Control) that uses hand coded pages as is shown in AddEditContactPerson.aspx.

Cheers,

Imar
On Tuesday, October 05, 2010 4:00:02 PM Tulsi said:

How about caching the return id after it is returned from the db save method?  

Or would be a bad design practice in this situation?

Thanks.

Tulsi
On Tuesday, October 05, 2010 4:18:05 PM Imar Spaanjaars said:
Hi Tulsi,

You could do that, but it feels awkward for two reasons:

1) Cache (or Session) are web specific. That makes it difficult at least to reuse the same BLL in another type of application.

2) You may run into synchronization issues. What if the user has two tabs open and switches between them? You may end up with the wrong ID.

Cheers,

Imar
On Thursday, August 18, 2011 1:28:55 AM kapil said:
great article...
On Wednesday, February 01, 2012 12:46:28 PM Praveen said:
Hi Imar,
Thanks for the article,
i am using VS2010,how can i apply this article to an application using 3 tier architecture with C #.

Thanks,
Praveen.
On Wednesday, February 01, 2012 1:04:54 PM Imar Spaanjaars said:
Hi Praveen,

This article series deals with n-layer, not n-tier, and as such the principles cannot be used directly in an n-tier scenario.

If you actually mean n-layer, but you're targeting VS 2010, take a look at the follow up series targeting .NET 3.5:

http://imar.spaanjaars.com/476/n-layered-web-applications-with-aspnet-35-part-1-general-introduction

You'll should be able to use all of the code from the new series in .NET 4 as-is.

Cheers,

Imar
On Wednesday, March 21, 2012 4:15:33 PM Paula Severn said:
Hi Imar,  firstly, thank you, your 3 tutorials have been so helpful.  I have closely followed your design and code to implement my project.  I however need to edit and insert from a gridview like Benny above.  
I am using the (save)updateinsert for editing that takes my BO (Record).

After updating it fires this error "Cannot convert value of parameter 'startTime' from 'System.String' to 'System.DateTime'"  Do I need to convert all the string fields from the gridview as I have int and datetime types.  If so in what handler?  You had said that the Business Object is sent from a row update so I didnt think casting or converting would be necessary.

I will also need to put masks in the gridview and dropboxes, will these be entered into the inputparameters collection without any extra code on my part

Any help would appreciated.
Paula
On Wednesday, March 21, 2012 7:37:11 PM Imar Spaanjaars said:
Hi Paula,

Yes, if your database expects a DateTime, you need to convert the value first. You can't just simply send a string.

>>  You had said that the Business Object is sent from a row update so I didnt think casting or converting would be necessary.

Not sure what you mean by that.

>> I will also need to put masks in the gridview and dropboxes, will these be entered into the inputparameters collection without any extra code on my part

Same here. Not sure what this means.

If you need more technical help, may I suggest you post this on the Wrox forum at http://p2p.wrox.com along with relevant sample code?

Cheers,

Imar
On Wednesday, March 21, 2012 9:57:48 PM Paula said:
I have resolved the issue, thanks.  
The gridview was bound to an objectdatasurce.
            
After the RowUpdating method was finished executing, the exception was then thrown when my Business Object class's setter method was executed:

        public DateTime startDate
        {
            get { return StartDate; }
            set { StartDate = value;}
        }

I put the conversion for datetime here. Im not sure if it's normal practice?
  protected void GridView1_RowUpdating1(object sender, GridViewUpdateEventArgs e)
    {
        e.NewValues[4] = Convert.ToDateTime(e.NewValues[4])  
}
On Wednesday, March 21, 2012 10:31:05 PM Imar Spaanjaars said:
Hi Paula,

>> Im not sure if it's normal practice?

No, it's not. Normally the GridView should take care of that. Maybe the column type isn't a DateTime?

Imar
On Monday, July 23, 2012 7:12:57 PM Felipe Duayer said:
Hi Imar,

I saw that you use the Bind() function for the address, email address and phone number dropDownLists with the enum types. I have the same layout: insert template inside a form view, trying to bind a dropDownList (selected value) with a enum type. However, when I run the page, I get this:

"Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control".

Any ideas? Would be of great help!

By the way, great book the Beginning ASP.NET 4.0.

Felipe.
On Monday, July 23, 2012 7:22:42 PM Felipe Duayer said:
Continuing from message above:

Actually, I had my dropDownList (DDL) inside a [td] tag. When I put the DDL outside of the table it worked fine.

Any ideas on how to get it working inside of the table?

Thank you,

Felipe.
On Monday, July 23, 2012 7:24:59 PM Imar Spaanjaars said:
Hi Felipe,

Where are you calling Bind, and on which control? How do you define its data source?

Try posting this in a general ASP.NET category (not a book related one) on a forum such as the one run by Wrox (at http://p2p.wrox.com) where it's easier to share code. If you send me a link, I'll take a look.

Cheers,

Imar
On Sunday, September 30, 2012 7:35:00 AM Michael said:
Imar,

1) Why do you retrieve records from database on every update event ? First you retrieve from database then you set the new values & then you update. This makes 2 trips to the database (fetch + update) . Can we just create a new object altogether & somehow send that to the update method?

2) Does your application uses a certain design pattern? If yes then which one?

Thanks.
On Sunday, September 30, 2012 9:51:33 AM Imar Spaanjaars said:
Hi Michael,

1) You could, but you would miss data that is stored in the database such as a create date, or miss changes made by other users.

2) You'll find some design patterns in the application (such as PRG - Post Redirect Get) for editing a contact person. However, the core architecture is not based on a single pattern. I intend to write an updated 4.5 version of this series some day and apply better design patterns.

Cheers,

Imar
On Monday, October 01, 2012 3:23:15 PM Michael said:
Hey, thanks Imar!
I tried what I wrote yesterday to you & got into all sorts of problem (Could not fetch id of record to update without storing it in a hidden field). I understand very well now why you fetched the ContactPerson before saving =)

The architecture is pretty solid still.  I wish you all the best with all your future writings. Whatever you come up with, its always fascinating to read your articles.

Regards, Michael.
On Wednesday, July 03, 2013 1:40:27 PM atinuke said:
how do i write a program that collects data from a user and displays the content of the data using a datagrid view on another window
BREAK DOWN
(A). a form collects the following data from a user
First name,middle name,last name,sex,age,state of origin,nationality,email,contact address,Phone number.
(B). when the user submits a data,the submit button ask if the user wants to input another data,if YES,clears the former data entry,for the user to enter a new one.If NO,takes the user to a new window that displays all the data the user has entered on the form window.
On Wednesday, July 03, 2013 1:56:34 PM Imar Spaanjaars said:
Hi atinuke,

Is this a home work assignment? I believe all the answers can be found by studying these articles and source code.

Cheers,

Imar
On Thursday, February 13, 2014 2:49:50 PM Louise Eggleton said:
Hi Imar

Thanks so much for these articles and replies to the comments. I have read all three articles and all the comments. The comments have addressed many of the questions I had.

I like the approach that all layers share common business objects, but what do you recommend when the objects returned by your business layer need to be different than the objects returned by the DAL?

For example, in my app in some cases in my Business Layer I need to take the object returned from the DAL, use the properties in the object to perform calculations and return a new object to my presentation layer. Just to be clear, the new object I am creating is a reusable business object not a view model.

So far I am just dealing with all my business objects in the same way, that is they are all saved in the same namespace and there is nothing  that distinguishes objects transformed by the business layer form those returned from the DAL. One thing I considered is for the DAL and Business Layer to each have their own objects. Eg. DAL returns DataObject, Business Layer returns BusinessObject. That would require a lot of mapping, but things like automapper would help. Another scenario I considered is just to do as I am currently doing, (lump all model classes together), but use some naming convention to distinguish these special transformed objects from the objects returned from the DAL. Another approach would just be to use the comments section in a class to distinguish these special classes. Can you comment on my approaches or offer any other suggestions?

Thanks

Louise
On Thursday, February 13, 2014 10:26:25 PM Imar Spaanjaars said:
Hi Louise,

This article series is 7 years old, and is no longer representing current back practices. Instead, I would take a look at my updated series using new practices targeting current versions of .NET and other frameworks. You can find the series here:

http://imar.spaanjaars.com/573/aspnet-n-layered-applications-introduction-part-1

In the MVC application you see how I implemented View Models, a layer between the UI and the underlying model which may be what you're after.

Cheers,

Imar
On Friday, February 14, 2014 11:47:05 AM Louise Eggleton said:
I do implement view models. From my post. "Just to be clear, the new object I am creating is a reusable business object not a view model." My structure is like this:

Business Objects,
Data Access Layer,
Business Logic Layer,
Mapping Layer,
ViewModels,
Controllers,
Views

But even with that structure, I occasionally find the need for a reusable business object that is not a view model, but is also not the same as the object returned from my DAL. In essence I believe the issue is that in some cases how we store data does not always resemble what the true business object should look like.

I do plan on reading your later articles, but for my purposes the basic 3-tier structure you have demonstrated is still valid. I am working on converting a legacy classic asp app to asp.net mvc. There are 250 tables in a very well normalized database and the SQL used in the old app is very mature. Further, the legacy app is nowhere near a basic CRUD application. For those reasons I do not plan on using an ORM. Your n-tier approach with a DAL that uses pure SQL allows me to reuse as much of the old code as possible. I am aware of lite ORM's such as Dapper and PetaPoco that use SQL queries, but to be honest I don't see anything in them that offers an advantage of what you came up with 7 years ago.

Talk Back! Comment on Imar.Spaanjaars.Com

I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.

When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.

If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the QuickDocId of the document.

For more information about the Talk Back feature, check out this news item.