2
I need help with a project I’ve been racking my brain about for two days. I’m using Microsoft C# MVC5 technology and framework 4.5, I also use Entity Framework 6 with Repository Pattern, Unit of Work and Unity to perform dependency injection.
I have a controller called AccountController
which is responsible for logging in and logging out the user on the system, this controller receives the application’s Repository methods by dependency injection via the same constructor.
Accountcontroller
public class AccountController : BaseController
{
private readonly IUsuarioApp _usuarioApp;
private readonly IUnitOfWorkAsync _unitOfWorkAsync;
public AccountController() { }
public AccountController(IUsuarioApp usuarioApp, IUnitOfWorkAsync unitOfWorkAsync)
{
_unitOfWorkAsync = unitOfWorkAsync;
_usuarioApp = usuarioApp;
}
// GET: Login
[AllowAnonymous]
public ActionResult Login()
{
return View();
}
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login([Bind(Include = "Email, Password")]LoginViewModel model, string returnUrl)
{
try
{
if (!ModelState.IsValid) return View(model);
var usuarioAutenticado = _usuarioApp.AutenticarUsuarioPor(model.Email, model.Password);
var logDeAcesso = new LogDeAcesso { DataDeAcesso = DateTime.Now, UsuarioId = usuarioAutenticado.Id };
usuarioAutenticado.DataDoUltimoAcesso = logDeAcesso.DataDeAcesso;
_usuarioApp.Update(usuarioAutenticado);
_usuarioApp.GetRepository<LogDeAcesso>().Insert(logDeAcesso);
_unitOfWorkAsync.SaveChanges();
SessionContext.SetAuthenticationToken(usuarioAutenticado.Id.ToString(), false, ConvertToUsuarioViewModel(usuarioAutenticado));
return RedirectToAction("Index", "Home");
}
catch (Exception ex)
{
ModelState.AddModelError("", "Tentativa de login inválido.");
return View(model);
}
}
public ActionResult LogOff(int id)
{
try
{
var ultimoLogsDeAcessoCriado = _usuarioApp.GetRepository<LogDeAcesso>().Query(model => model.UsuarioId == id).OrderBy(model => model.OrderByDescending(c => c.DataDeAcesso)).Select().FirstOrDefault();
if (ultimoLogsDeAcessoCriado == null || ultimoLogsDeAcessoCriado.DataDeSaida != DateTime.MinValue) throw new Exception("Erro ao tentar deslogar do sistema.");
ultimoLogsDeAcessoCriado.DataDeSaida = DateTime.Now;
_usuarioApp.GetRepository<LogDeAcesso>().Update(ultimoLogsDeAcessoCriado);
_unitOfWorkAsync.SaveChanges();
FormsAuthentication.SignOut();
Session.Clear(); //Pode não ser necessário, mas não é problemático o uso como prevenção
Session.Abandon();
//Limpar o cookie de Autenticação
var resetFormsCookie = new HttpCookie(FormsAuthentication.FormsCookieName, "");
resetFormsCookie.Expires = DateTime.Now.AddYears(-1);
Response.Cookies.Add(resetFormsCookie);
//Limpar a session cookie
var resetSessionCookie = new HttpCookie("ASP.NET_SessionId", "");
resetSessionCookie.Expires = DateTime.Now.AddYears(-1);
Response.Cookies.Add(resetSessionCookie);
//Invalida o Cache no lado do Cliente
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetNoStore();
}
catch (Exception ex)
{
Danger("Error: " + ex.Message);
}
return RedirectToAction("Login", "Account");
}
#region Helpers
private UsuarioViewModel ConvertToUsuarioViewModel(Usuario usuario)
{
return new UsuarioViewModel
{
Id = usuario.Id,
Nome = usuario.Nome,
UltimoAcesso = usuario.DataDoUltimoAcesso
};
}
#endregion
}
As it is seen, when logging in the user is authenticated through the email and password, an instance of the Access Log is created where we register the date of entry of the access and later the cookie is created where the cookie will be stored that will allow access to the pages of the Site.
After obtaining access, the user can click on the logoff button, which will trigger the method ActionResult LogOff
which will obtain the last log of access created based on the user id, update with the data of the Date of exit of the system, clean the Session and Cookies, redirecting it to the Login page. From there he will only have access again to the other pages if he logs in again.
It is dynamic works very well, but there is a problem, what if the user does not click the logoff button, and instead resolve to close the tab or the browser? As it was built, it will remain authenticated in the 20 min standards q the IIS standardizes for downtime, but the expiration time of the cookie, not to mention that in this way I will not be able to register the Exit Date in the Access Log.
Thinking about it I set up on Web.config a Session timeout time
Web.Config
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
<globalization culture="pt-BR" uiCulture="pt-BR" />
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" slidingExpiration="true" />
</authentication>
<sessionState
mode="InProc"
stateConnectionString="tcpip=127.0.0.1:42424"
stateNetworkTimeout="20"
sqlConnectionString="data source=127.0.0.1;Integrated Security=SSPI"
sqlCommandTimeout="20"
customProvider=""
cookieless="UseCookies"
cookieName="ASP.NET_SessionId"
timeout="1"
allowCustomSqlDatabase="false"
regenerateExpiredSessionId="true"
partitionResolverType=""
useHostingIdentity="true">
<providers>
<clear />
</providers>
</sessionState>
<machineKey validationKey="466AFE06F664B2E3662F97B81D30E87907F9921E51C95C618A670B396403AD98DD032BCE7610EEAE1FB1DA7B3ED7ACE56537E66FD6DF20E701351697E57C3D9C" decryptionKey="CD10ABC11246E6998AB7B9A8CC142CDD6C8AEF7FB12D15CF12158BEAD647C603" validation="SHA1" decryption="AES" />
</system.web>
</configuration>
Only when the timeout happens and I enter the Global.asax
in the method Session_End()
, I cannot access the repositories nor the FormsAuthentication
.
I wonder what I have to do to repeat inside the Session_End()
exactly what I do by clicking the logoff button. Could anyone help?
EDIT
Well, I think I can better clarify what I’m looking for. I seek to develop a way to register in the database and destroy the authentication ticket, Session and Cookie at the time of Timeout of Downtime.
Hello Gypsy. Well I tried to implement the code you passed only I received an error the moment I pass through Response.Clear(), Httpexception: Answer is not available in this context. Also when analyzing the code I think I will have problems instantiating the controller since it receives by injection two parameters (usuarioApp and unitOfWorkAsync), which has no way to get here, at least I do not know how. Thank you for your help.
– Alan Bessa
Comment on this line and see if the rest works.
– Leonel Sanches da Silva
In the Accountmaincontroller.Execute() line he gave the following exception: Argumentnullexception: Value cannot be null. Parameter name: httpContext
– Alan Bessa
@Alanbessa I updated the answer.
– Leonel Sanches da Silva
Kept the same exception in the same place. Argumentnullexception: "Value cannot be null. Parameter name: httpContext"
– Alan Bessa
There is no application lifecycle event at the end of the Timeout Session where I can call my methods before closing the session, no?
– Alan Bessa
@Alanbessa The event I know is this one. Another alternative would be to schedule an event that checks out the expired sessions.
– Leonel Sanches da Silva