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?
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.
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?
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".
Related
I've created an asp.net page for work that allows customers to look up users in AD, and then request to have them added as delegates to Rightfax numbers, which can also be searched for. Rightnow I'm storing public variables used by the back end and front end of the project in a CurrentSession class inherited by the pages thats supposed to be unique to each user, but I'm still seeing occasional issues where variables will 'bleed' from one session to another. Sometimes I'll go to the page and the list of AD users is already populated with users from another session\user.
I'm wondering what the best method is for storing variables in this scenario. Should I be using cookies rather than a current session class? Are there any good guides/tutorials that go over variable management for asp.net pages? I'm typically a desktop developer so I'm not particularly familiar with this kind of issue.
the whole jump to the web, and that of session managment, or varible managment is a huge topic. And it is often a challenge, since the concpets are very different then desktop.
So, I mean, when a user clicks a button, the web page is posted up to the server. The page is found, loaded, variables start from scratch. Code behind runs, page is sent back down to client AND THE PAGE SERVER SIDE IS TOSSED out!
So, the above is a new challenge, due to that so called "state-less" nature of web pages.
As for session bleeing to other users? Hum, that should not occur. However, session() can be VERY fragle. Due to time outs, due to some execution error in code, the app pool can and will often re-start.
So, I strong recommend that you run the script and turn on SQL servre based sesison management. Once done, then session() becomes bullet proof - and always works.
As for values bleeding to other users? No way - I not seen that.
(of course, you did not mention or note what authentication provider you are using (or even if users have to logon).
However, while I OFTEN use session, and even to pass values from one page to the next? (absolute hate parameters in the URL - messy and often security risk if things like PK id etc. are included)).
However, some big sites say like amazon use parameters and values in the URL a lot. They do this since then the server side does not get over-loaded and have to keep track or hold those values. However, unless you building the next facebook, or huge + high volume web site, then session() is quite much the standard approch to keep values active for your code.
However, lets assume we toss up a grid, and the user selects that product?
We set in session() say that PK id, and hten jump to the next page say to buy that house?
Well, now if you open a new tab - even a different browser, launch that grid, select a house and jump to the page to display that information? You can't use session since as noted it will overwrite the values in the other page.
So, you can try and build a bunch of fancy classes and all kinds of handstands, but I just simply transfer the session() values to ViewState.
ViewState is per page, and session() is global to that one user.
So, if I need 10 variables and value for a given part of the application, say like this:
<Serializable>
Public Class clsProjectInfo
Public ContactID As Integer = 0
Public ContactGeneralID As Integer = 0
Public PortalComp As String = ""
Public ProjectHeaderID As Integer = 0
Public QuoteNum As Integer = 0
Public UploadGroup As Integer = 0
Public txtNotes As String = ""
Public ProofID As Integer = 0
End Class
So say in the page that we select the project - setup a whole buch of values?
Then the above var will be in session(), but ONLY for pasing to the next page.
So the project view page that needs all of the aobve values to work?
The page load code will look like this:
Dim cPinfo As New clsProjectInfo
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If IsPostBack = False Then
cPinfo = Session("Pinfo")
ViewState("Pinfo") = cPinfo
Else
cPinfo = ViewState("Pinfo")
End If
So we only EVER use session to pass that "bunch of values" to the next page, but always first thing we do is transfer from session() to viewstate. That way if the user opens another tab, or selects a different house in that 2nd browser, jumps to the view details, we only ever used session() to pass the values to the next page, but from that point on, always used ViewState.
Now the above simple idea might not work for all cases but it does for most.
So, don't adopt huge number of session() values, and as always even in desktop, global vars and values should not be required.
So session() for a given user will most certainly often "stomp" on top of other parts of the application. If session() is spilling over between different users? That should not occur, never occur and means as noted something else is going wrong here.
So even for desktop software? Each form, or page or part of the application tend to have and need a set of values. So, I build plane jane simple class as per above. And then you can with great ease pass ONE class thing with the 5-10 variables in it. that way I don't wind up with 50 variables in session() - which is a nightmare from coding point of view (let alone to remember the varaiables). But with above passing the one class, then you just passing ONE thing and you get intel-sense too boot.
And no doubt that group of variables often has to be passed to routine. So beofre above, I often like had to pass like 5-6 values to some function or sub - and what a pan.
So old way:
Call SaveAfterUpload(AjaxFileUpload1, session("QuoteNum", session("ContactID",
session("UploadGroup"), strCleanFile, session("txtNotes"), session("PortalComp")
but now we can go:
Call SaveAfterUpload(AjaxFileUpload1, cPinfo.QuoteNum, cPinfo.ContactID,
cPinfo.UploadGroup, strCleanFile, cPinfo.txtNotes, cPinfo.PortalComp)
But, then again, since we have that class, then above now becomes
Call SaveAfterUpload(AjaxFileUpload1, cPinfo)
So, don't put a truck load of values into session().
Create "groupings" of the values.
And what is even SUPER great?
often some of the client side JavaScript code needs those values.
So, you THEN wind up dropping in a boatload of hidden fields or hidden cnotrols for those values.
But, with the above class? I can pass + have the whole mess client side like this:
cPinfo = ViewState("Pinfo") ' this no doubt occured on page load
' copy Pinfo to browser side
MyUpLoadInfo.Value = JsonConvert.SerializeObject(cPinfo)
MyUpLoadInfo is just a simple asp.net hidden field like this:
<asp:HiddenField ID="MyUpLoadInfo" runat="server" ClientIDMode="Static" />
But, now in place of those 5-6 hidden fields, I have the above cPinfo now for use in the client side.
eg this:
MyUpLoadInfo = document.getElementById("MyUpLoadInfo")
Pinfo = JSON.parse(MyUpLoadInfo.value)
// now I have all values such as
Pinfo.txtNotes
Pinfo.QuoteNum
So, by building that class or set of variables, I can now pass down the WHOLE mess in one shot to the client side, and now my JavaScript code can with great ease use all those variables client side!!
And it turns out that for each section of a typical application?
about 5-10 variables are only required
often they are required client side - and with above we can
Most if not ALL of the routines in that application part need those vars
(including subs and functions, so now we can pass 5-10 values, and we don't
have huge long list of messy parameters in all of those subs and functions).
We can modify the class - add more variables, and the dozen routines now all have that extra variable - yet we don't change the code, or even change the sub/function calls to have that extra new variable in that code.
(and this applies to client side js code if we need/require that group of values for the browser code.).
So we thus don't have huge numbers of global vars.
we don't wind up with a gazillion number of separate session values.
we vast improve the ability to pass those values to subs/functions.
And we even can pass that set of values to the client side with great ease.
So any global var can be in session, but those global vars are NEVER to be used for passing value from page to page and code calls for a given part of your application.
And if you want to support more then one page or browser by the user? Then adopt the standard that on first page load you transfer that session class to viewstate.
I have an ASP.NET c# project.
I have to pass a list of values (id numbers such as "23,4455,21,2,765,...) from one form to another. Since QueryString is not possible because the list could be long, which is the best way to do it?
Thanks in advance.
Thanks for all your answers, you are helping a lot !!!
I decided to do this:
On the first form:
List lRecipients = new List();
.....
Session["Recipients"] = lRecipients;
On the final form:
List lRecipients = (List)Session["Recipients"];
Session.Remove("Recipients");
You could use Session collection.
In the first page, use:
List<int> listOfInts = new List<int>();
...
Session["someKey"] = listOfInts
And in the second page, retrieve it like this:
List<int> listOfInts = Session["someKey"] as List<int>;
If your using asp.net webforms you can put it into a session variable to pass stuff from page to page. You've got to be concise of the potential performance issues of putting lots of stuff into session mind.
Session["ListOfStff"] = "15,25,44.etc";
There are any number of ways to pass this data. Which you choose will depend on your environment.
Session state is useful, but is constrained by the number of concurrent users on the system and the amount of available memory on the server. Consider this when deciding whether or not to use Session state. If you do choose session state for this operation, be sure to remove the data when you're done processing the request.
You could use a hidden input field, with runat="server" applied to it. This will make its data available server-side, and it will only last for the duration of the request. The pros of this technique are that it's accessible to both the server code and the client-side JavaScript. However, it also means that the size of your request is increased, and it may take more work to get the data where you want it (and back out).
Depending on how much data's involved, you could implement a web service to serialize the data to a temporary storage medium (say, a database table), and get back a "request handle." Then, you could pass the request handle on the query string to the next form and it could use the "handle" to fetch the data from your medium.
There are all kinds of different ways to deal with this scenario, but the best choice will depend on your environment, time to develop, and costs.
For Asp.NET MVC you can use ViewData.
ViewData["ID"] = "";
I will try to be as concise as possible. I'm using Flex/Hibernate technologies for my app. I also use Cairngorm micro-architecture for Flex. Because i'm beginner, i have probably misunderstand something about Caringorm's ModelLocator purpose. I have following problem...
Suppose that we have next data model:
USER ----------------> TOPIC -------------> COMMENT
1 M 1 M
User can start many topics, topics can have many comments etc. It is pretty simple model, just for example. In hibernate, i use EAGER fetching strategy for unidirectional USER->TOPIC and TOPIC->COMMENT relations(here is no question about best practices etc, this is just example of problem).
My ModelLocator looks like this:
...
public class ModelLocator ....
{
//private instance, private constructor, getInstance() etc...
...
//app state
public var users:ArrayCollection;
public var selectedUser:UserVO;
public var selectedTopic:TopicVO;
}
Because i use eager fetching, i can 'walk' through all object graph on my Flex client without hitting the database. This is ok as long as i don't need to insert, update, or delete some of the domain instances. But when that comes, problems with synchronization arise.
For example, if i want to show details about some user from some UserListView, when user(actor) select that user in list, i will take selected index in UserList, get element from users ArrayCollection in ModelLocator at selected index and show details about selected user.
When i want to insert new User, ok, I will save that user in database and in IResponder result method i will add that user in ModelLocator.users ArrayCollection.
But, when i want to add new topic for some user, if i still want to use convenience of EAGER fetching, i need to reload user list again... And to add topic to selected user... And if user is in some other location(indirectly), i need to insert topic there also.
Update is even worst. In that case i need to write even some logic...
My question: is this good way of using ModelLocator in Cairngorm? It seems to me that, because of mentioned, EAGER fetching is somehow pointless. In case of using EAGER fetching, synchronization on Flex client can become big problem. Should I always hit database in order to manipulate with my domain model?
EDIT:
It seems that i didn't make myself clear enough. Excuse me for that.
Ok, i use Spring in technology stack also and DTO(DVO) pattern with flex/spring (de)serializer, but i just wanted to stay out of that because i'm trying to point out how do you stay synchronized with database state in your flex app. I don't even mention multi-user scenario and poling/pushing topic which is, maybe, my solution because i use standard request-response mechanism. I didn't provide some concrete code, because this seems conceptual problem for me, and i use standard Cairngorm terms in order to explain pseudo-names which i use for class names, var names etc.
I'll try to 'simplify' again: you have flex client for administration of above mentioned domain(CRUD for each of domain classes), you have ListOfUsersView(shows list of users with basic infos about them), UserDetailsView(shows user details and list of user topics with delete option for each of topic), InsertNewUserTopicView(form to insert new topic) etc.
Each of view which displays some infos is synchronized with ModelLocator state variables, for example:
ListOfUsersView ------binded to------> users:ArrayCollection in ModelLocator
UserDetailsView ------binded to------> selectedUser:UserVO in ModelLocator
etc.
View state transition look like this:
ListOfUsersView----detailsClick---->UserDetailsView---insertTopic--->InsertTopicView
So when i click on "Details" button in ListOfUsersView, in my logic, i get index of selected row in ListOfUsers, after that i take UserVO object from users:ArrayCollection in ModelLocator at mentioned index, after that i set that UserVO object as selectedUser:UserVO in ModelLocator and after that i change view state to UserDetailsView(it shows user details and selectedUser.topics) which is synchronized with selectedUser:UserVO in ModelLocator.
Now, i click "Insert new topic" button on UserDetailsView which results in InsertTopicView form. I enter some data, click "Save topic"(after successful save, UserDetailsView is shown again) and problem arise.
Because of my EAGER-ly fetched objects, i didn't hit the database in mentioned transitions and because of that there are two places for which i need to be concerned when insert new topic for selected user: one is instance of selectedUser object in users:ArrayCollection (because my logic select users from that collection and shows them in UserDetailsView), and second is selectedUser:UserVO(in order to sync UserDetailsView which comes after successfull save operation).
So, again my question arises... Should i hit database in every transition, should i reload users:ArrayCollection and selectedUser:UserVO after save in order to synchronize database state with flex client, should i take saved topic and on client side, without hitting the database, programmatically pass all places which i need to update or...?
It seems to me that EAGER-ly fetched object with their associations is not good idea. Am i wrong?
Or, to 'simplify' :) again, what should you do in the mentioned scenario? So, you need to handle click on "Save topic" button, and now what...?
Again, i really try to explain this as plastic as possible because i'm confused with this. So, please forgive me for my long post.
From my point of view the point isn't in fetching mode itself but in client/server interaction. From my previous experience with it I've finally found some disadvantages of using pure domain objects (especially with eager fetching) for client/server interaction:
You have to pass all the child collections maybe without necessity to use them on a client side. In your case it is very likely you'll display topics and comments not for all users you get from server. The most like situation you need to display user list then display topics for one of the selected users and then comments for one of the selected topics. But in current implementation you receive all the topics and comments even if they are not needed to display. It is very possible you'll receive all your DB in a single query.
Another problem is it can be very insecure to get all the user data (or some other data) with all fields (emails, addresses, passwords, credit card numbers etc).
I think there can be other reasons not to use pure domain objects especially with eager fetching.
I suggest you to introduce some Mapper (or Assembler) layer to convert your domain objects to Data Transfer Objects aka DTO. So every query to your service layer will receive data from your DAO or Active Record and then convert it to corresponding DTO using corresponding Mapper. So you can get user list without private data and query some additional user details with a separate query.
On a client side you can use these DTOs directly or convert them into client domain objects. You can do it in your Cairngorm responders.
This way you can avoid a lot of your client side problems which you described.
For a Mapper layer you can use Dozer library or create your own lightweight mappers.
Hope this helps!
EDIT
What about your details I'd prefer to get user list with necessary displayable fields like first name and last name (to display in list). Say a list of SimpleUserRepresentationDTO.
Then if user requests user details for editing you request UserDetailsDTO for that user and fill tour selectedUser fields in model with it. The same is for topics.
The only problem is displaying list of users after user details editing. You can:
Request the whole list again. The advantage is you can display changes performed by other users. But if the list is too long it can be very ineffective to query all the users each time even if they are SimpleUserRepresentationDTO with minimal data.
When you get success from server on user details saving you can find corresponding user in model's user list and replace changed details there.
Tell you the truth, there's no good way of using Cairngorm. It's a crap framework.
I'm not too sure exactly what you mean by eager fetching (or what exactly is your problem), but whatever it is, it's still a request/response kind of deal and this shouldn't be a problem per say unless you're not doing something right; in which case I can't see your code.
As for frameworks, I recommend you look at RobotLegs or Parsley.
Look at the "dpHibernate" project. It implements "lazy loading" on the Flex client.
I am a VB.net winforms programmer attempting to build an ASP.Net app. I use data classes(objects) through reflection in most of my vb projects and was trying to adapt it to ASP.net using the VB code behind. I have a webpage that serves as an add/edit page for contact info. I instatiate my class which grabs the contact data from the data base then I have a process that loops through the controls on the form and matches up with a property in the data class. I can display data no problem. When I edit data and click the submit button my code calls a then loops through the controls on the form again and matches the control to the property of the data class to update the property of the class. However, my data class is no longer valid. I know web programming is different then winforms but I can't seem to get over the hump on this one. Is this the wrong way to go about this? Is my data class only available on the server side? Do I just reinstantiate the initial class and then loop through the propeties and change what the user changed and then call the update method (see redundant)? How can I get data class into a session object (I made an attempt in the past but was under tight deadlines and had to abandon it, maybe I need to revisit it?)?
Thanks
If you decide to keep some of your data in Session, you owe it to yourself to look at this post. Your code will be much cleaner and easier to maintain.
Yes, you need to reload the data class from the database as one option, or use an alternative approach. The reason is web is stateless, so all local variables are destroyed then the server side page unload process occurs. This means that in between requests, you need something to store your data.
You can read/write an object via the Session colleciton, as so:
Session["A"] = myobj;
myobj = (ObjType)Session["A"];
And so session stores an object for a specific user. Alternatively, cache stores application level data, so one instance of an object is available to all users (where session is unique to each user). You can make cache unique to a user by appending a user ID to the cache string.
var o = Cache.Get("A");
if (o != null) { .. }
Cache.Add("A", o, ...);
And so these mechanisms help you temporarily retain data.
You need to save your data class somewhere, usually in a session variable, otherwise it goes away as soon as the page gets sent back the user. Or else you need to recreate the data class again upon posting.
I am using web forms, C#, Asp.net.
As we all know, in this model UI and business logic are often mixed in. How do I separate these effectively?
The example I would like to use is:
I have a GridView and a DataTable (GridView binds to the DataTable and DataTable is fed from the stored procedure).
I would like the GridView (UI) and DataTable (business logic) to be decoupled.
Is it worth it to write an wrapper for DataTable? Are there practical patterns that have been proved and tested that you could recommend to be followed?
If someone with experience could shed some light, that would be awesome.
And, as a final note I would like to say that ASP MVC is not an option right now, so don't recommend it.
My database access layer returns a DataTable.
Note that I HAVE to use this database layer as this is a company policy.
I went through this recently while decoupling much the same thing from our UI layer.
You can see my progress here and here.
In my opinion, A DataTable does not represent business logic. Specifically, it's data pulled directly from the database. Business logic turns that data into a truly useful business object.
The first step, then, is to decouple the DataTable from the Business object.
You can do that by creating objects and List<object> that make up DataTables and Collections of DataTables, and then you can make a ListView that displays those Objects. I cover the latter steps in the links I posted above. And the former steps are as easy as the following:
Create a class that will represent your object.
iterate through your DataTable (or DataSet, or however you retrieve the data) and shove those fields into properties of that object (or that List<T>);
return that List to the Gridview or ListView to display.
This way your ListView or Gridview won't be tightly coupled to the method that you are retrieving your data. What happens if you decide to get your data from a JSON query or a XML file later on? Then you'd have to build this into there.
Step 1 - Getting Data From Database
There are multiple methods to get data from a database, there's no way I can go through all of them here. I assume that you already know how to retrieve data from a database, and if you don't, there are quite a few links to follow. Let's pretend you've connected to the database, and are using an SQLDataReader to retrieve data. We'll pick up there.
Class Diagram
Foo
----
id
Name
Description
And here's the method:
private void FillDefault(SqlDataReader reader, Foos foo)
{
try
{
foo.id = Convert.ToInt32(reader[Foo.Properties.ID]);
foo.Name = reader[Foo.Properties.NAME].ToString();
if (!string.IsNullOrEmpty(
reader[Foo.Properties.DESCRIPTION].ToString()))
foo.Description =
reader[Foo.Properties.DESCRIPTION].ToString();
else foo.Description = string.Empty;
}
catch (Exception ex)
{
throw new Exception(
string.Format("Invalid Query.
Column '{0}' does not exist in SqlDataReader.",
ex.Message));
}
}
Once that happens, you can return a list by going through that process in a while loop that targets the SQLDataReader.Read() function.
Once you do that, let's pretend that your Foo being returned is a List. If you do that, and follow the first link I gave above, you can replace Dictionary<TKey, TValue> with List<T> and achieve the same result (with minor differences). The Properties class just contains the column names in the database, so you have one place to change them (in case you were wondering).
DataTable - Update Based on Comment
You can always insert an intermediate object. In this instance, I'd insert a Business Layer between the DataTable and the UI, and I've discussed what I'd do above. But a DataTable is not a business object; it is a visual representation of a database. You can't transport that to the UI layer and call it de-coupled. They say you have to use a DataTable, do they say that you have to transport that DataTable to the UI? I can't imagine they would. If you do, then you'll never be de-coupled. You'll always need an intermediate object in between the DataTable and the UI layer.
I'd start by decoupling the data table right into the trash can. Build a domain layer, and then some type of data access layer which deals with the DB (ORM recommended).
Then build a servicing layer which provides the data to the UI. All business logic should be within the service or the entities themself.
Consider implementing MVP (model view presenter) pattern. It gives you separation of biz logic through presenter interface, which also allow better unit testing capabilities. Your codebehind of aspx page is then just connector of events and getter/setter of properties. You can find it in MS pattern&practices enterprise application blocks (CAB - composite application block - if i'm not mistaking).
You can read more about it here: http://msdn.microsoft.com/en-us/magazine/cc188690.aspx
But also going from DataTable/DataSets to objects (POCO) is preferred.