I have noticed that my ASP.NET
Web Forms application based on Identity 2.2.1
fetches logged in user data from the database on every page request. Is this normal and designed behaviour? For performance reasons, user data might be cached in Session. I have double checked if I have not added extra code in master page which might cause this behaviour. But no, I am using standard Web Form template with no code added to the master page.
SQL statement executed on every page request:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Email] AS [Email],
[Extent1].[EmailConfirmed] AS [EmailConfirmed],
[Extent1].[PasswordHash] AS [PasswordHash],
[Extent1].[SecurityStamp] AS [SecurityStamp],
[Extent1].[PhoneNumber] AS [PhoneNumber],
[Extent1].[PhoneNumberConfirmed] AS [PhoneNumberConfirmed],
[Extent1].[TwoFactorEnabled] AS [TwoFactorEnabled],
[Extent1].[LockoutEndDateUtc] AS [LockoutEndDateUtc],
[Extent1].[LockoutEnabled] AS [LockoutEnabled],
[Extent1].[AccessFailedCount] AS [AccessFailedCount],
[Extent1].[UserName] AS [UserName]
FROM [dbo].[AspNetUsers] AS [Extent1]
WHERE [Extent1].[Id] = @p0
UPDATE 1
As application works in Azure using App Service and Azure SQL, proof for database query behind each page request was Application Insights, as per attached screenshot.
I have started to investigate further and have moved database to local environment. SQL Server Profiler
shows there is in fact 10 queries to database on each page request. Those are SELECT to AspNetUsers
, AspNetUserClaims
, AspNetUserLogins
, etc. Some of them are executed twice. This does not depend on master page. Pages not based on master page trigger same 10 queries as those based on one.
I have done few modifications to default Visual Studio template, as per below source code. I have double checked that new project based on same template does not trigger any database queries once user is logged in.
Modifications done:
ApplicationUser
class, added to database table via migrationsSource code:
Global_asax
Public Class Global_asax
Inherits HttpApplication
Sub Application_Start(sender As Object, e As EventArgs)
' Fires when the application is started
RouteConfig.RegisterRoutes(RouteTable.Routes)
BundleConfig.RegisterBundles(BundleTable.Bundles)
End Sub
End Class
Startup
Partial Public Class Startup
' For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301883
Public Sub ConfigureAuth(app As IAppBuilder)
'Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(AddressOf ApplicationDbContext.Create)
app.CreatePerOwinContext(Of ApplicationUserManager)(AddressOf ApplicationUserManager.Create)
app.CreatePerOwinContext(Of ApplicationSignInManager)(AddressOf ApplicationSignInManager.Create)
' Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(New CookieAuthenticationOptions() With {
.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
.Provider = New CookieAuthenticationProvider() With {
.OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(Of ApplicationUserManager, ApplicationUser)(
validateInterval:=TimeSpan.FromMinutes(0),
regenerateIdentity:=Function(manager, user) user.GenerateUserIdentityAsync(manager))},
.LoginPath = New PathString("/Account/Login"),
.ExpireTimeSpan = TimeSpan.FromMinutes(20),
.SlidingExpiration = True})
' Use a cookie to temporarily store information about a user logging in with a third party login provider
'app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie)
' Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
'app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5))
' Enables the application to remember the second login verification factor such as phone or email.
' Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
' This is similar to the RememberMe option when you log in.
'app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie)
' Uncomment the following lines to enable logging in with third party login providers
'app.UseMicrosoftAccountAuthentication(
' clientId:= "",
' clientSecret:= "")
'app.UseTwitterAuthentication(
' consumerKey:= "",
' consumerSecret:= "")
'app.UseFacebookAuthentication(
' appId:= "",
' appSecret:= "")
'app.UseGoogleAuthentication(New GoogleOAuth2AuthenticationOptions() With {
' .ClientId = "",
' .ClientSecret = ""})
End Sub
End Class
IdentityConfig.vb
Public Class EmailService
Implements IIdentityMessageService
Public Function SendAsync(message As IdentityMessage) As Task Implements IIdentityMessageService.SendAsync
' Plug in your email service here to send an email.
'Return Task.FromResult(0)
Dim client As New Net.Mail.SmtpClient(DTAppSettings.SendGrid_SMTPServer, 587)
Dim credentials As New Net.NetworkCredential(DTAppSettings.SendGrid_Username, DTAppSettings.SendGrid_Password)
client.Credentials = credentials
client.EnableSsl = True
Dim mailmessage As New Net.Mail.MailMessage With {
.From = New Net.Mail.MailAddress(DTAppSettings.SendGrid_FromAddress, DTAppSettings.SendGrid_FromName),
.Subject = message.Subject,
.Body = message.Body,
.IsBodyHtml = True
}
mailmessage.To.Add(message.Destination)
Return client.SendMailAsync(mailmessage)
End Function
End Class
Public Class SmsService
Implements IIdentityMessageService
Public Function SendAsync(message As IdentityMessage) As Task Implements IIdentityMessageService.SendAsync
' Plug in your SMS service here to send a text message.
Return Task.FromResult(0)
End Function
End Class
' Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application.
Public Class ApplicationUserManager
Inherits UserManager(Of ApplicationUser)
Public Sub New(store As IUserStore(Of ApplicationUser))
MyBase.New(store)
End Sub
Public Shared Function Create(options As IdentityFactoryOptions(Of ApplicationUserManager), context As IOwinContext) As ApplicationUserManager
Dim manager = New ApplicationUserManager(New UserStore(Of ApplicationUser)(context.[Get](Of ApplicationDbContext)()))
' Configure validation logic for usernames
manager.UserValidator = New UserValidator(Of ApplicationUser)(manager) With {
.AllowOnlyAlphanumericUserNames = False,
.RequireUniqueEmail = True
}
' Configure validation logic for passwords
manager.PasswordValidator = New PasswordValidator() With {
.RequiredLength = 6,
.RequireNonLetterOrDigit = True,
.RequireDigit = True,
.RequireLowercase = True,
.RequireUppercase = True
}
' Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user.
' You can write your own provider and plug in here.
'manager.RegisterTwoFactorProvider("Phone Code", New PhoneNumberTokenProvider(Of ApplicationUser)() With {
' .MessageFormat = "Your security code is {0}"
'})
'manager.RegisterTwoFactorProvider("Email Code", New EmailTokenProvider(Of ApplicationUser)() With {
' .Subject = "Security Code",
' .BodyFormat = "Your security code is {0}"
'})
' Configure user lockout defaults
manager.UserLockoutEnabledByDefault = True
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5)
manager.MaxFailedAccessAttemptsBeforeLockout = 5
manager.EmailService = New EmailService()
manager.SmsService = New SmsService()
Dim dataProtectionProvider = options.DataProtectionProvider
If dataProtectionProvider IsNot Nothing Then
manager.UserTokenProvider = New DataProtectorTokenProvider(Of ApplicationUser)(dataProtectionProvider.Create("ASP.NET Identity")) With {
.TokenLifespan = TimeSpan.FromHours(1)
}
End If
Return manager
End Function
End Class
Public Class ApplicationSignInManager
Inherits SignInManager(Of ApplicationUser, String)
Public Sub New(userManager As ApplicationUserManager, authenticationManager As IAuthenticationManager)
MyBase.New(userManager, authenticationManager)
End Sub
Public Overrides Function CreateUserIdentityAsync(user As ApplicationUser) As Task(Of ClaimsIdentity)
Return user.GenerateUserIdentityAsync(DirectCast(UserManager, ApplicationUserManager))
End Function
Public Shared Function Create(options As IdentityFactoryOptions(Of ApplicationSignInManager), context As IOwinContext) As ApplicationSignInManager
Return New ApplicationSignInManager(context.GetUserManager(Of ApplicationUserManager)(), context.Authentication)
End Function
End Class
The problem is related to this bit of code:
.OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(Of ApplicationUserManager,
ApplicationUser)(validateInterval:=TimeSpan.FromMinutes(0),
Try a larger value, such as .FromMinutes(15)
Since the validateInterval
is 0, it's basically re-validating the Identity information on every page load.