views:

358

answers:

3

Just trying to gather thoughts on what works/doesn't work for manipulating Business/Domain objects through an ASP.NET (2.0+) UI/Presentation layer. Specifically in classic ASP.NET LOB application situations where the ASP.NET code talks directly to the business layer. I come across this type of design quite often and wondering what is the ideal solution (i.e. implementing a specific pattern) and what is the best pragmatic solution that won't require a complete rewrite where no "pattern" is implemented.

Here is a sample scenario.

A single ASP.NET page that is the "Edit/New" page for a particular Business/Domain object, let's use "Person" as an example. We want to edit Name and Address information from within this page. As the user is making edits or entering data, there are some situations where the form should postback to refresh itself. For example, when editing their Address, they select a "Country". After which a State/Region dropdown becomes enabled and refreshed with relevant information for the selected country. This is essentially business logic (restricting available selections based on some dependent field) and this logic is handled by the business layer (remember this is just one example, there are lots of business situations where the logic is more complex during the post back - for example insurance industry when selecting certain things dictates what other data is needed/required).

Ideally this logic is stored only in the Business/Domain object (i.e. not having the logic duplicated in the ASP.NET code). To accomplish this, I believe the Business/Domain object would need to be reinitialized and have it's state set based on current UI values on each postback.

For example:

private Person person = null;

protected void Page_Load()
{
  person = PersonRepository.Load(Request.QueryString["id"]);

  if (Page.IsPostBack)
    SetPersonStateFromUI(person);
  else
    SetUIStateFromPerson(person);
}


protected void CountryDropDownList_OnChange()
{
  this.StateRegionDropDownList.Enabled = true;
  this.StateRegionDropDownList.Items.Clear();
  this.StateRegionDropDownList.DataSource = person.AvailableStateRegions;
  this.StateRegionDropDownList.DataBind();
}

Other options I have seen are storing the Business object in SessionState rather than loading it from the repository (aka database) each time the page loads back up.

Thoughts?

A: 

I'd put your example in my 'UI Enhancement' bucket rather than BL, verifying that the entries are correct is BL but easing data entry is UI in my opinion.

Lazarus
That would be duplicating the logic, don't you think? That is, if I select a country, given that object state, my Address object knows what the available State/Regions are for the selected country. Maybe with Country/Regions we can get away with it, but what about something more complicated like what fields are required based on another field's selection.
Brian
Not really, the logic would only existing the UI layer. While you will probably verify the input in the BL layer (assuming you have an interface with your UI that's means they aren't part of the same monolithic app) but you wouldn't implement the state/region cascade in the business objects, it just doesn't fit. For me the BL layer does things that could be called with machine generated or user inputs, it's not tied to a user interface. I concede this is one opinion and I could be wrong :)
Lazarus
A: 

For very simple things I wouldn't bother with a regular post back but would use an ajax approach. For example if I need to get a list of Cities, I might have a Page Method (Or web service) that given a state gives me a list of cities.

If your options depends on a wide variety of parameters, what your doing would work well. As for storing things in Session there are benefits. Are your entities visible to multiple at the same time? If so what happens when User A and User B both edit the same. Also if your loading each time are you savign to the database each time? What happens if I am editing my name, and then select country, but now my browser crashes. Did you update the name in the DB?

JoshBerke
Concurrency is handled through generally through the DAL and bubbles up. That is if User A and User B are editing the same "person", first in wins the save operation. The second in would get presented with an error message and the latest copy of the Business object displayed.
Brian
Not saving to database until user takes an explicit save action (think "Save" button). Only loading current state from database on each load. ....Ahhhh now I see what you were asking about concurrency...what if User A and User B are editing at same time, user A saves, and User B does soemthning that results in a postback, my load from repository will load User A's edit...would have to somehow track the current version of the object with a Concurrency ID somehow.
Brian
Yep that's what I was talking about.
JoshBerke
A: 

This is the line I disagree with slightly:

this.StateRegionDropDownList.DataSource = person.AvailableStateRegions;

Person is a business/domain object, but it's not the object that should be handling state/region mapping (for example), even if that's where the information to make the decision lives.

In more complicated examples where multiple variables are needed to make a decision, what you want to do in general is start from the domain object you're trying to end up with, and call a function on that object that can be given all the required information to make a business decision.

So maybe (using a static function on the State class):

this.StateRegionDropDownList.DataSource = State.GetAvailableStateRegions(person, ipAddress);

As a consequence of separating out UI helper concerns from the Person domain object, this style of programming tends to be much "more testable".

ifatree
I sort of am moving away from my original thinking as well. I've been reading up on the MVVM design pattern, and it seems that this logic should really be in the View Model. This is essentially helping facilitate data entry into the Business layer (the Address object).
Brian