As an architect, providing a framework for 4 developers at our company, I am ever watchful for code that deviates from the proper patterns...for example, someone trying to access the database without going through the DataPortal. Of course developers mostly develop in the Client/Server all in one environment...so it's easy to be forgetfull and write code that would break in distributed setup.
Testing would catch that...but why not catch it right when it happens on the developer machine?
So this morning I thought "Hey why can't I just check the ApplicationContext.ExecutionLocation in our DataAccessFactory to make sure it says 'Server'". OOPS it is always 'Client' when developing on same machine. Hmmm so I thought "OK if it is 'Client' I will check also check the Application.DataPortalProxy". OOPS it says 'Local' on both sides of the portal. DANGIT!
Can you gents think of a built in way to tell if you are on the other side of the portal regardless of distributed environment or not?
Thanks
Sean
Um... change the connection string to point to a database on another machine?
Not as spiff as your original idea, but hey, it's still better than slogging thru the code line by line after the fact!
Interesting idea...something similar would be to force the developers to develop in 3tier mode where they can't hit the database from their machine...but debugging would be horrendous and ultimately hurt production.
What I really need is a flag that says "BeenThroughPortal=True"...this would allow 2 tier development but would catch the bad code right at run-time.
Sean
Interesting idea Sean. As you probably know, the whole premise behind Rocky's portal is to hide that distinction.
And I'm not sure I fully understand where you'd like to place the check. You could easily enough check to see if there was a DataPortal configured in the app.config, but if I understand correctly you want to make sure some errant developer didn't just write his own ADO.NET code and go to the database directly. That would require having SQL Server be able to check where the connection came from somehow, wouldn't it? What ain't I gettin'?
"but if I understand correctly you want to make sure some errant developer didn't just write his own ADO.NET code and go to the database directly"
Kinda & Kinda Not...Lets see if I can 'splain it better.
If you had a "IHaveBeenThroughThePortal" Property to check then you would know it is safe to make calls that would work in 2 and 3 tier mode. If you think about it, all Rocky's database access code is on one side of the portal because he is careful to write it that way. But if he wasn't you could potentially let code out the door that would have a time bomb set to go off in 3tier mode.
Since my DataAccessFactory is scoped as Friend...it is protected only from public access. But BO developers can too easily forget to go through the portal (Using CommandObject or the like) before calling DAL. Since Friend doesn't protect this from happening at design time...I thought maybe I could devise a way to check at runtime.
So my check for IHaveBeenThroughPortal would happen INSIDE the factory which the BO developers don't touch. All DB access goes through that factory since we use LLBLGen adapter pattern for our DAL.
Sean
eric.oldre:I don't have any experience with code obfuscation, but, what if you marked your dataportal_xxx methods with custom attributes so that you could easily identify them in the stack?
Eric
That should work Eric. That wouldn't be too hard, although probably with one line in the framework this same thing could be accomplished. I just hate to alter the framework in any way though. So it seems like nobody knows a way that's "Built in" it seems.
ajj3085:Sean,
Is this a problem you are encountering a lot, or just one that concerns you?
If its the former, I don't think technology is the answer... you have to get your programmers to be more disciplined. Perhaps a code review process, if you don't have one, or you can do random reviews, just to make sure? Going back to technology, perhaps having them start from templates would help?
If its the latter, I wouldn't worry too much... but you may want to remind your staff on the proper use of the framework occasionally.
HTH
Andy
Andy, I encountered it once and it scared me because that means it could be elsewhere. I tried to implement code reviews and I was almost burned at the stake! Politics with old-timers who have been here years more than me :-( So, like you suggested, I did as much with templates as possible. But templates cannot generate all of your business logic. I tried to lock down as much as possible protecting from the public world...now I am thinking about how to protect from ourselves given the "Real World" limits I am facing politically.
swegele:Andy, I encountered it once and it scared me because that means it could be elsewhere.
swegele:I tried to implement code reviews and I was almost burned at the stake! Politics with old-timers who have been here years more than me :-(
swegele:So, like you suggested, I did as much with templates as possible. But templates cannot generate all of your business logic. I tried to lock down as much as possible protecting from the public world...now I am thinking about how to protect from ourselves given the "Real World" limits I am facing politically.
Rocky's solution of subclassing csla objects and overriding the OnInvoke and OnInvokeComplete methods worked perfectly!
I put the boolean in the Application.Context and test it inside my DataAccessFactory...throws a nice error if they made the mistake. Very smooth.
Here is the subclassed vbcode for anyone coming along later
Namespace
JTBaseObjects<Serializable()> _
Public MustInherit Class JTBusinessBase(Of T As JTBusinessBase(Of T)) Inherits BusinessBase(Of T) Protected Overrides Sub DataPortal_OnDataPortalInvoke(ByVal e As Csla.DataPortalEventArgs) MyBase.DataPortal_OnDataPortalInvoke(e) 'boolean used to track whether the dataportal was called by the client code...this can be used to test ' whether it is safe to make calls that in 3 tier mode would fail if a businessobject developer didn't follow ' design pattern and just called into a friend method and tried to hit the databaseApplicationContext.LocalContext(CONST_THROUGHPORTAL) =
True End Sub Protected Overrides Sub DataPortal_OnDataPortalInvokeComplete(ByVal e As Csla.DataPortalEventArgs) MyBase.DataPortal_OnDataPortalInvokeComplete(e)ApplicationContext.LocalContext(CONST_THROUGHPORTAL) =
False End Sub End Class<Serializable()> _
Public MustInherit Class JTBusinessListBase(Of T As JTBusinessListBase(Of T, C), C As Csla.Core.IEditableBusinessObject) Inherits BusinessListBase(Of T, C) Protected Overrides Sub DataPortal_OnDataPortalInvoke(ByVal e As Csla.DataPortalEventArgs) MyBase.DataPortal_OnDataPortalInvoke(e) 'boolean used to track whether the dataportal was called by the client code...this can be used to test ' whether it is safe to make calls that in 3 tier mode would fail if a businessobject developer didn't follow ' design pattern and just called into a friend method and tried to hit the databaseApplicationContext.LocalContext(CONST_THROUGHPORTAL) =
True End Sub Protected Overrides Sub DataPortal_OnDataPortalInvokeComplete(ByVal e As Csla.DataPortalEventArgs) MyBase.DataPortal_OnDataPortalInvokeComplete(e)ApplicationContext.LocalContext(CONST_THROUGHPORTAL) =
False End Sub End Class<Serializable()> _
Public MustInherit Class JTCommandBase Inherits Csla.CommandBase Protected Overrides Sub DataPortal_OnDataPortalInvoke(ByVal e As Csla.DataPortalEventArgs) MyBase.DataPortal_OnDataPortalInvoke(e) 'boolean used to track whether the dataportal was called by the client code...this can be used to test ' whether it is safe to make calls that in 3 tier mode would fail if a businessobject developer didn't follow ' design pattern and just called into a friend method and tried to hit the databaseApplicationContext.LocalContext(CONST_THROUGHPORTAL) =
True End Sub Protected Overrides Sub DataPortal_OnDataPortalInvokeComplete(ByVal e As Csla.DataPortalEventArgs) MyBase.DataPortal_OnDataPortalInvokeComplete(e)ApplicationContext.LocalContext(CONST_THROUGHPORTAL) =
False End Sub End Class<Serializable()> _
Public MustInherit Class JTReadOnlyBase(Of T As JTReadOnlyBase(Of T)) Inherits ReadOnlyBase(Of T) Protected Overrides Sub DataPortal_OnDataPortalInvoke(ByVal e As Csla.DataPortalEventArgs) MyBase.DataPortal_OnDataPortalInvoke(e) 'boolean used to track whether the dataportal was called by the client code...this can be used to test ' whether it is safe to make calls that in 3 tier mode would fail if a businessobject developer didn't follow ' design pattern and just called into a friend method and tried to hit the databaseApplicationContext.LocalContext(CONST_THROUGHPORTAL) =
True End Sub Protected Overrides Sub DataPortal_OnDataPortalInvokeComplete(ByVal e As Csla.DataPortalEventArgs) MyBase.DataPortal_OnDataPortalInvokeComplete(e)ApplicationContext.LocalContext(CONST_THROUGHPORTAL) =
False End Sub End Class<Serializable()> _
Public MustInherit Class JTReadOnlyListBase(Of T As JTReadOnlyListBase(Of T, C), C As Csla.Core.IBusinessObject) Inherits ReadOnlyListBase(Of T, C) Protected Overrides Sub DataPortal_OnDataPortalInvoke(ByVal e As Csla.DataPortalEventArgs) MyBase.DataPortal_OnDataPortalInvoke(e) 'boolean used to track whether the dataportal was called by the client code...this can be used to test ' whether it is safe to make calls that in 3 tier mode would fail if a businessobject developer didn't follow ' design pattern and just called into a friend method and tried to hit the database ' The DataAccessAdapterFactory uses it to not attempt a database hit if someone is accessing it from client side of dataportalApplicationContext.LocalContext(CONST_THROUGHPORTAL) =
True End Sub Protected Overrides Sub DataPortal_OnDataPortalInvokeComplete(ByVal e As Csla.DataPortalEventArgs) MyBase.DataPortal_OnDataPortalInvokeComplete(e)ApplicationContext.LocalContext(CONST_THROUGHPORTAL) =
False End Sub End Class<Serializable()> _
Public MustInherit Class JTEditableRootListBase(Of T As JTBusinessBase(Of T)) Inherits EditableRootListBase(Of T) Protected Overrides Sub DataPortal_OnDataPortalInvoke(ByVal e As Csla.DataPortalEventArgs) MyBase.DataPortal_OnDataPortalInvoke(e) 'boolean used to track whether the dataportal was called by the client code...this can be used to test ' whether it is safe to make calls that in 3 tier mode would fail if a businessobject developer didn't follow ' design pattern and just called into a friend method and tried to hit the databaseApplicationContext.LocalContext(CONST_THROUGHPORTAL) =
True End Sub Protected Overrides Sub DataPortal_OnDataPortalInvokeComplete(ByVal e As Csla.DataPortalEventArgs) MyBase.DataPortal_OnDataPortalInvokeComplete(e)ApplicationContext.LocalContext(CONST_THROUGHPORTAL) =
False End Sub End Class 'Chose not to do this one...since I never use it NameValueListBase<K,V>End
Namespace
Great "out of the box" thinking...I think that would work in theory
BUT as I understand it that wouldn't work with code obfuscation since the method names would have been changed right?
Sean
You can't use obfuscation with CSLA .NET, because it makes use of reflection to do certain operations.
The broader question here, as I understand it, is how to ensure that developers don't make database calls from client-side code. If you follow the CSLA .NET class design patterns this should never be an issue, because all the data access code goes in the DataPortal_XYZ methods, or in the internal/Friend data methods of child objects.
Code reviews, potentially even automated ones, can be used to correct anyone putting any data access code in any other method.
It isn't clear to me how having an ApplicationContext.PassedThroughDataPortal would help, because the same code that didn't belong in a client-side method would be written to not do this check. Bad code is bad code. A coder that wants to talk to the database on the client simply won't wrap their data code in the appropriate if..else statement.
Presumably they know that they've broken the rules, because their method name isn't DataPortal_XYZ, so they are already conciously cheating.
But here's a quick answer for you, if you want to explore the possibility:
ApplicationContext.LocalContext (version 2.1+) exists purely within the local environment. Also, the data portal calls pre- and post-processing methods before/after calling any DataPortal_XZY method.
If you have your own business base class (which I recommend in any case), you can simply override the pre- and post-processing methods and set something into ApplicationContext.LocalContext to indicate you've "passed through" the data portal. Then just remove it in the post-processing method:
protected override void DataPortal_OnDataPortalInvoke()
{
ApplicationContext.LocalContext["dp_flag"] = true;
}
protected override void DataPortal_OnDataPortalInvokeComplete()
{
ApplicationContext.LocalContext["dp_flag"] = false;
}
You'll want to set this to false as your app starts (on the client) too, so then you can universally check it in your code:
if (ApplicationContext.LocalContext["dp_flag"])
// running inside the data portal
RockfordLhotka:You can't use obfuscation with CSLA .NET, because it makes use of reflection to do certain operations.
Good to know the final word on that.
RockfordLhotka:The broader question here, as I understand it, is how to ensure that developers don't make database calls from client-side code. If you follow the CSLA .NET class design patterns this should never be an issue, because all the data access code goes in the DataPortal_XYZ methods, or in the internal/Friend data methods of child objects.
Right...it is the Friend methods that concern me. Here is my pattern:
On the EditableRoot object here is the DataPortal method
Private Overloads Sub DataPortal_Fetch(ByVal criteria As Criteria) Using adapter As IDataAccessAdapter = JTDataAccessAdapterFactory.GetNewAdapterApplicationContext.LocalContext(CONST_ADAPTER) = adapter
'do some database stuff here on your parent 'now call some internal/friend stuff here for children (see below)ApplicationContext.LocalContext.Remove(CONST_ADAPTER)
End Using
End Sub
Example Code on the Child object
Friend Sub DoSomehtingHereWithChild() Dim adapter As IDataAccessAdapter = CType(ApplicationContext.LocalContext(CONST_ADAPTER), IDataAccessAdapter)adapter.DoSomething
End SubYou can see that a developer might inadvertantly call the method not realizing they forgot to call the parents dataportal.
RockfordLhotka:Code reviews, potentially even automated ones, can be used to correct anyone putting any data access code in any other method.
Ugh I agree...but my previous post explains the dillema I find myself in
RockfordLhotka:But here's a quick answer for you, if you want to explore the possibility:
ApplicationContext.LocalContext (version 2.1+) exists purely within the local environment. Also, the data portal calls pre- and post-processing methods before/after calling any DataPortal_XZY method.
If you have your own business base class (which I recommend in any case), you can simply override the pre- and post-processing methods and set something into ApplicationContext.LocalContext to indicate you've "passed through" the data portal. Then just remove it in the post-processing method:
protected override void DataPortal_OnDataPortalInvoke()
{
ApplicationContext.LocalContext["dp_flag"] = true;
}protected override void DataPortal_OnDataPortalInvokeComplete()
{
ApplicationContext.LocalContext["dp_flag"] = false;
}You'll want to set this to false as your app starts (on the client) too, so then you can universally check it in your code:
if (ApplicationContext.LocalContext["dp_flag"])
// running inside the data portal
BEAUTIFULL!! That is exactly what I need...one code change without changing the framework and then my dataaccess factory will tell them they are an idiot when they go to run their code!!!
THANKS
Sean
Copyright (c) Marimer LLC