Avoiding Global Data in Business Logic that is shared between Desktop and Web apps
I am building an ECommerce store that is configurable by admin users via c# desktop (Windows Forms) application, and is consumed by end-users via a website. As part of my design, quite a lot of business logic is shared between both the desktop and the web components. Use of either the desktop or web app requires authentication.
As I'm building it, I'm picking up on some code smells because I keep finding myself having to add a parameter to class constructors that identify information about what kind of user is using the business logic (ie IP address of开发者_JAVA技巧 user, enum for whether it is a desktop or web, System User ID number, etc).
This identifier class that stores this information is feeling like global data, which I try to avoid because of SOLID principles, etc, but I'm really having a difficult time coming up with a better solution for how a class can have quick access to all the information it needs.
Is there a canned solution for this problem?
That sounds like normal session data. I mean, that's a normal thing for a website to do. When a user makes a request, their login info and some other stuff is saved in the session data, which is associated only with that web request.
I agree, when I make data like that global, I usually end up regretting it. But if I pass that class around to every other class, it looks weird, and I end up tying each class to the implementation of that particular class, which isn't good either.
Whichever approach you take, global or passed-in, for key classes like this one, my recommendation would probably be to abstract it in some fashion, so that you can change the implementation easier later (and make it easier to test). You could do this by pulling out an interface from that class, and that way each of your various other classes only need to be tied to the interface, not to the implementation. If you wanted, you could also abstract it one level further out, and simply pass around a Factory class of some sort that returned an implementation of the underlying interface when required.
What I did recently in one instance was to create an IConfig interface that was a standard parameter to most of the classes in a particular project. I was then free to create multiple implementations of that particular IConfig interface, and pass those in from the outside. That helped quite a lot with unit testing, among other things.
I think you want to create your own IPrincipal object.
This object is common to both the winform or web application.
While you can assign an IIDentity to the IPrincipal.IIDentity property........You should be more interested in the IPrincipal (your custom concrete version) rather than the IIdentity.
Your custom IPrincipal will have the .IsInRole method. While I do not like Role based security, (RIGHTS or PERMISSIONS are better I believe), there are work arounds. See my discussion here: http://sholliday.spaces.live.com/blog/cns!A68482B9628A842A!846.entry and here is one work around to the .IsInRole stuff http://www.lhotka.net/weblog/CommentView,guid,9efcafc7-68a2-4f8f-bc64-66174453adfd.aspx
I would create a single IPrincipal (your own version) (ex MySuperCoolPrincipal.cs) , an abstract IIDentity (your own abstract version like MyAbractIIdenity : IIDentity) (
You can then refer to MyAbractIIdenity in most of your code. You can down convert in some cases if necessary. (Like, if you have to have the IPAddress of your MyWebUser).
Remember, if you want to check to see if an identity CAN DO SOMETHING, then you check the IIPrincipal, NOT THEIR IDENTITY.
Can I edit an employee? Check the IPrincipal. Can I see this report? Check the IPrincipal. Can I view the special/secret property of this object? Check the IPrincipal.
Most times you don't care about the who. You care about the what they can do.
.......
Now, let's say the user has the right/permission to "EDIT_EMPLOYEE". The user has that right, but the user cannot edit ~~every employee in the system. They can edit ~some employees.
So you might have two rights. EDIT_EMPLOYEE_ANY_DEPT EDIT_EMPLOYEE_HOME_DEPT_ONLY
See the difference? And when you're trying to bring up the employees the user can edit, you might have to check the custom IIDentity you have.
Perhaps you created a property called
MyAbractIIdenity.HomeDepartmentKey
You can check that property to determine which employees you can edit, based on the permission: EDIT_EMPLOYEE_HOME_DEPT_ONLY
If I'm missing something, let me know.
But I think you could pass down only the MySuperCoolPrincipal.
Check my article above, the anti pattern I developed helps understand what I'm saying about rights(permissions) vs roles. And I think will help with the IIdentity vs IPrincipal stuff I mention above.
Another method is to have a middle man hold some data, then you place it there, and retrieve it.
You'll still have to pass some kind of unique identifier down.
However, I quickly tested that the EnterpriseLibrary.Caching framework...if you configure the backendstore to be a db (and not just in memory), both a webform and winform app can get to the data.
Here is sample to get you going:
<add expirationPollFrequencyInSeconds="240" maximumElementsInCacheBeforeScavenging="1000"
numberToRemoveWhenScavenging="10" backingStoreName="CacheViaADatabase"
type="Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager, Microsoft.Practices.EnterpriseLibrary.Caching, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="IPrincipalCache" />
</cacheManagers>
<backingStores>
<add databaseInstanceName="CachingConnectionString" partitionName="CS" encryptionProviderName="" type="Microsoft.Practices.EnterpriseLibrary.Caching.Database.DataBackingStore, Microsoft.Practices.EnterpriseLibrary.Caching.Database, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="CacheViaADatabase"/>
</backingStores>
精彩评论