OK, I'm *almost* there with implementing some custom authentication for my windows forms using csla4.
I'm struggling somewhat with the Dal implementation of it though. The sample projects use a MockDb and i'm having difficulties trying to make the correct references between interfaces and objects.
Here are the classes/interfaces I have currently:
DataAccess.IIdentityDal:
Public Interface IIdentityDal
Function VerifyUser(username As String, password As String) As Boolean
Function GetUser(username As String) As IDataReader
Function GetRoles(username As String) As IDataReader
End Interface
DataAccess.IdentityDal: (I can't put this in DataAccess.SQL as I would have to make a reference from my library to the specific class library for SQL - which means no portability - why?)
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports Csla.Data
Imports System.Data.SqlClient
Public Class IdentityDal
Implements IIdentityDal
Public Function VerifyUser(username As String, password As String) As Boolean Implements IIdentityDal.VerifyUser
Using ctx = ConnectionManager(Of SqlConnection).GetManager("Security")
Using cm = ctx.Connection.CreateCommand()
cm.CommandType = System.Data.CommandType.StoredProcedure
cm.CommandText = "VerifyUser"
cm.Parameters.AddWithValue("@ps_Username", username)
cm.Parameters.AddWithValue("@ps_Password", password)
cm.Parameters.AddWithValue("@pn_IsValid", 0)
cm.Parameters("@pn_IsValid").Direction = ParameterDirection.Output
Dim r = cm.ExecuteScalar()
Dim newId As Boolean = cm.Parameters("@pn_IsValid").Value
Return newId
End Using
End Using
End Function
Public Function GetUser(username As String) As System.Data.IDataReader Implements IIdentityDal.GetUser
Using ctx = ConnectionManager(Of SqlConnection).GetManager("Security")
Dim cm = ctx.Connection.CreateCommand()
cm.CommandType = System.Data.CommandType.Text
cm.CommandText = _
"SELECT " + _
" u.USERNAME " + _
" , u.PASSWORD " +
"FROM dbo.USERS u ( NOLOCK ) " + _
"WHERE ( u.USERNAME = @ps_Username )"
cm.Parameters.AddWithValue("@ps_Username", username)
Return cm.ExecuteReader()
End Using
End Function
Public Function GetRoles(username As String) As System.Data.IDataReader Implements IIdentityDal.GetRoles
Using ctx = ConnectionManager(Of SqlConnection).GetManager("Security")
Dim cm = ctx.Connection.CreateCommand()
cm.CommandType = System.Data.CommandType.Text
cm.CommandText = _
"SELECT " + _
" r.USERNAME " + _
" , r.ROLE " +
"FROM dbo.Roles r ( NOLOCK ) " + _
"WHERE ( r.USERNAME = @ps_Username )"
cm.Parameters.AddWithValue("@ps_Username", username)
Return cm.ExecuteReader()
End Using
End Function
End Class
Library.Net.CustomPrincipal:
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports Csla
Imports Csla.Security
Imports Csla.Serialization
Namespace Security
<Serializable()> _
Public Class CustomPrincipal
Inherits CslaPrincipal
Private Sub New(identity As CustomIdentity)
MyBase.New(identity)
End Sub
Public Shared Sub BeginLogin(username As String, password As String, completed As Action(Of Exception))
CustomIdentity.GetCustomIdentity(username, password, Sub(o, e)
If e.[Error] IsNot Nothing Then
Logout()
Else
Csla.ApplicationContext.User = New CustomPrincipal(e.[Object])
End If
completed(e.[Error])
End Sub)
End Sub
#If Not Silverlight Then
Public Shared Sub Login(username As String, password As String)
Dim identity = CustomIdentity.GetCustomIdentity(username, password)
Csla.ApplicationContext.User = New CustomPrincipal(identity)
End Sub
Public Shared Sub Load(username As String)
Dim identity = CustomIdentity.GetCustomIdentity(username)
Csla.ApplicationContext.User = New CustomPrincipal(identity)
End Sub
#End If
Public Shared Sub Logout()
Csla.ApplicationContext.User = New UnauthenticatedPrincipal()
End Sub
End Class
End Namespace
Library.Net.CustomIdentity:
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports Csla
Imports Csla.Security
Imports Csla.Serialization
Namespace Security
<Serializable()> _
Public Class CustomIdentity
Inherits CslaIdentityBase(Of CustomIdentity)
Public Shared ReadOnly FullNameProperty As PropertyInfo(Of String) = RegisterProperty(Of String)(Function(c) c.FullName)
Public Property FullName() As String
Get
Return GetProperty(FullNameProperty)
End Get
Private Set(value As String)
LoadProperty(FullNameProperty, value)
End Set
End Property
Public Shared Sub GetCustomIdentity(username As String, password As String, callback As EventHandler(Of DataPortalResult(Of CustomIdentity)))
DataPortal.BeginFetch(Of CustomIdentity)(New UsernameCriteria(username, password), callback)
End Sub
#If Not Silverlight Then
Public Shared Function GetCustomIdentity(username As String, password As String) As CustomIdentity
Return DataPortal.Fetch(Of CustomIdentity)(New UsernameCriteria(username, password))
End Function
Friend Shared Function GetCustomIdentity(username As String) As CustomIdentity
Return DataPortal.Fetch(Of CustomIdentity)(username)
End Function
Private Sub DataPortal_Fetch(criteria As UsernameCriteria)
AuthenticationType = "Custom"
Dim dal = New DataAccess.IdentityDal
If dal.VerifyUser(criteria.Username, criteria.Password) Then
LoadUserData(criteria.Username, dal)
End If
End Sub
Private Sub DataPortal_Fetch(username As String)
AuthenticationType = "Custom"
Dim dal = New DataAccess.IdentityDal
LoadUserData(username, dal)
End Sub
Private Sub LoadUserData(username As String, dal As DataAccess.IdentityDal)
Dim userData As CustomIdentity = dal.GetUser(username)
IsAuthenticated = (userData IsNot Nothing)
If IsAuthenticated Then
Name = userData.Name
FullName = userData.FullName
Roles = New Csla.Core.MobileList(Of String)(dal.GetRoles(Name))
End If
End Sub
#End If
End Class
End Namespace
The problem I have is when the call is made here: -
Public Shared Function GetCustomIdentity(username As String, password As String) As CustomIdentity
Return DataPortal.Fetch(Of CustomIdentity)(New UsernameCriteria(username, password))
End Function
I get a "DataPortal.Fetch failed (Unable to cast object of type 'System.Data.SqlClient.SqlDataReader' to type Library.Security.CustomIdentity'.)"
I can see WHY it blows up, but I have no clue how to rectify it. It seems that I'm goinf around in circles with creating the Identity, IIdentity and CustomIdentity pieces - and I'm pretty sure something is in the wrong place, as I my IdentityDal should be in my Database-specific Dal library (.SQL)
This is obviously the offending code:>
Public Function GetUser(username As String) As System.Data.IDataReader Implements IIdentityDal.GetUser
Using ctx = ConnectionManager(Of SqlConnection).GetManager("Security")
Dim cm = ctx.Connection.CreateCommand()
cm.CommandType = System.Data.CommandType.Text
cm.CommandText = _
"SELECT " + _
" u.USERNAME " + _
" , u.PASSWORD " +
"FROM dbo.USERS u ( NOLOCK ) " + _
"WHERE ( u.USERNAME = @ps_Username )"
cm.Parameters.AddWithValue("@ps_Username", username)
Return cm.ExecuteReader() '<*************** Idatareader, but I need 'customidentity'(??)
End Using
End Function
Any ideas?
Thanks,
Graham
Dim userData As CustomIdentity = dal.GetUser(username)
dal.GetUser returns an IDataReader - and you cannot cast an IDataReader to CustomIdentiy (just as the exception says).
Thanks Jonny. I realized that - and was posting as you replied.
Still have an issue though... ^
Thanks,
Graham
Getting closer (plus I figured out the structure of the classes so that the interface IIdentity is in DataAccess and IdentityDal is in DataAccess.SQL)
I corrected the Fetch(es) in CustomIdentity:
Private Sub DataPortal_Fetch(criteria As UsernameCriteria)
AuthenticationType = "Custom"
Using dalManager = DataAccess.DalFactory.GetManager()
Dim dal = dalManager.GetProvider(Of DataAccess.IIdentityDal)()
If dal.VerifyUser(criteria.Username, criteria.Password) Then
LoadUserData(criteria.Username, dal)
End If
End Using
End Sub
Private Sub DataPortal_Fetch(username As String)
AuthenticationType = "Custom"
Using dalManager = DataAccess.DalFactory.GetManager()
Dim dal = dalManager.GetProvider(Of DataAccess.IIdentityDal)()
LoadUserData(username, dal)
End Using
End Sub
Still get an exception though..... I'll investigate a bit further see if I can;t nail down where it's comingfrom.
Thanks,
Graham
2 steps forward, and 3 back.
I isolated my latest issue to the get of the DalManager type.
The implementation of DalManager/IDalManager previous used the App.settings to retrieve a database connection for my application.
Because the security tables are in a different database, it wasn't marrying up correctly. Are we supposed to have a different IDalManager interface for the authentication objects?
Now though, I'm getting a
(There is already an open DataReader associated with this Command which must be closed first.)
When the GetCustomIdentity is fired....
Grrrrrrrrrrr...
another step forward.
I get the "not closed" error when it attempts to do THIS call:
If IsAuthenticated Then
Name = data.GetString("USERNAME")
FullName = data.GetString("USERNAME")
Roles = New Csla.Core.MobileList(Of String)(dal.GetRoles(Name)) '****< error
End If
calling the IdentityDal's GetRoles is trying to use another connection.
How *should* I be implementing this? Should I be passing the ctx as a parameter to get roles?
Seems strange...
G.
Long discussion here on the same theme:
http://forums.lhotka.net/forums/p/11065/51435.aspx#51435
The thing with building a DAL interface based on IDataReader is that you
have to fully understand how data readers and connections work, and
live within those constraints.
Rocky uses this technique in the Data Access samples so have a look there.
OK, will take a look and let you know how I make out.
Thanks,
Graham
Nope Jonny.
I still don't get it.
My code around LoadUserData is exactly the same as Rocky's in the DataPortal book which discusses implmentation of CustomIdentity - but the sample project only shows a MockDb implementation. I'm guessing that has no connection pooling issues - this does.
If I were creating a parent/Child bo I'd have obviously strong-typed classes for say, User and UserRole - but this is custom authentication and we should need thsm as we are using the identity base.
I can hack away at this code but I'd like to see the correct, recommended solution for the problem - which I just don;t see in the samples OR in the book.
Thanks,
Graham
OK, I figured it out. I *believe*...
If you could have a look over what I've done and verify that it's a reasonable approach.
My LoadUserData now looks like:
Private Sub LoadUserData(username As String, dal As ParabolaSols.DataAccess.IIdentityDal)
Using data As New Csla.Data.SafeDataReader(dal.GetUser(username))
data.Read()
IsAuthenticated = (data IsNot Nothing)
If IsAuthenticated Then
Name = data.GetString("USERNAME")
FullName = data.GetString("USERNAME")
End If
End Using
Roles = New Csla.Core.MobileList(Of String)(dal.GetRoles(username))
End Sub
I moved the Roles outside of the using and also realized that the List implementation of Roles needed to be handled correctly, so in my GetRoles I now do.
Public Function GetRoles(username As String) As Csla.Core.MobileList(Of String) Implements IIdentityDal.GetRoles
Using ctx = ConnectionManager(Of SqlConnection).GetManager("ParabolaSols_Security")
Dim cm = ctx.Connection.CreateCommand()
cm.CommandType = System.Data.CommandType.Text
cm.CommandText = _
"SELECT " + _
" r.USERNAME " + _
" , r.ROLE " +
"FROM dbo.Roles r ( NOLOCK ) " + _
"WHERE ( r.USERNAME = @ps_Username )"
cm.Parameters.AddWithValue("@ps_Username", username)
Dim roles = New Csla.Core.MobileList(Of String)
Using data As New Csla.Data.SafeDataReader(cm.ExecuteReader)
While data.Read
data.Read()
roles.Add(data("Name"))
End While
End Using
Return roles
End Using
End Function
That works!
Now, what I'd really like to do is NOT use a list, but if that's too much like hard work, I'll settle.
Does that look correct? Could it be optimized?
Thanks,
Graham
I *thought* I'd figured it out.
I changed the LoadUserData in CustomIdentity to:
Private Sub LoadUserData(username As String, dal As ParabolaSols.DataAccess.IdentityDal)
Using data As New Csla.Data.SafeDataReader(dal.GetUser(username))
data.Read()
IsAuthenticated = (data IsNot Nothing)
If IsAuthenticated Then
Name = data.GetString("Name")
FullName = data.GetString("FullName")
Roles = New Csla.Core.MobileList(Of String)(dal.GetRoles(Name))
End If
End Using
End Sub
Clearly, I need to load the data from the datareader (duh).
But now I get the error:
DataPortal.Fetch failed (Invalid attempt to call Read when reader is closed.)
I still think something is wrong with the way my classes are structured though...
Thanks,
Graham
Copyright (c) Marimer LLC