Power Tools version 2.4.0.1 was used to create the following entity and code.
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Abp.Domain.Entities.Auditing;
using Abp.Domain.Entities;
namespace ngTTM.TtmDataModel
{
[Table("TtmExamStates")]
public class ExamState : Entity
{
[Required]
public virtual string Guid { get; set; }
public virtual int ItemsProcesssed { get; set; }
public virtual int ItemsTotal { get; set; }
public virtual double MathNotationRenderDuration { get; set; }
}
}
public async Task<PagedResultDto<GetExamStateForViewDto>> GetAll(GetAllExamStatesInput input)
{
var filteredExamStates = _examStateRepository.GetAll()
.WhereIf(!string.IsNullOrWhiteSpace(input.Filter), e => false || e.Guid.Contains(input.Filter))
.WhereIf(!string.IsNullOrWhiteSpace(input.GuidFilter), e => e.Guid == input.GuidFilter);
var pagedAndFilteredExamStates = filteredExamStates
.OrderBy(input.Sorting ?? "id asc")
.PageBy(input);
var examStates = from o in pagedAndFilteredExamStates
select new GetExamStateForViewDto() {
ExamState = new ExamStateDto
{
Guid = o.Guid,
ItemsProcesssed = o.ItemsProcesssed,
ItemsTotal = o.ItemsTotal,
MathNotationRenderDuration = o.MathNotationRenderDuration,
Id = o.Id
}
};
var totalCount = await filteredExamStates.CountAsync();
return new PagedResultDto<GetExamStateForViewDto>(
totalCount,
await examStates.ToListAsync()
);
}
Clicking the columns for the integer values will sort ascending and descending as expected. Clicking the column for the string value (Guid) causes an Exception on the server. Logs.txt:
ERROR 2020-06-29 22:00:06,436 [44 ] Mvc.ExceptionHandling.AbpExceptionFilter - '.' or '(' or string literal expected
'.' or '(' or string literal expected (at index 5)
INFO 2020-06-29 22:00:06,437 [44 ] .Mvc.Infrastructure.ObjectResultExecutor - Executing ObjectResult, writing value of type 'Abp.Web.Models.AjaxResponse'.
INFO 2020-06-29 22:00:06,438 [44 ] c.Infrastructure.ControllerActionInvoker - Executed action ngTTM.TtmDataModel.ExamStatesAppService.GetAll (ngTTM.Application) in 102.9678ms
INFO 2020-06-29 22:00:06,438 [44 ] ft.AspNetCore.Routing.EndpointMiddleware - Executed endpoint 'ngTTM.TtmDataModel.ExamStatesAppService.GetAll (ngTTM.Application)'
INFO 2020-06-29 22:00:06,438 [44 ] Microsoft.AspNetCore.Hosting.Diagnostics - Request finished in 120.4762ms 500 application/json; charset=utf-8
DEBUG 2020-06-29 22:00:08,053 [37 ] HttpRequestEntityChangeSetReasonProvider - Unable to get URL from HttpRequest, fallback to null
The fault occurs on this statement:
var pagedAndFilteredExamStates = filteredExamStates
.OrderBy(input.Sorting ?? "id asc")
.PageBy(input);
I answer my own question.
Use AppConsts.remoteServiceBaseUrl
directly in .ts code.
service-proxies.ts uses this trick:
constructor(@Inject(HttpClient) http: HttpClient, @Optional() @Inject(API_BASE_URL) baseUrl?: string) {
this.http = http;
this.baseUrl = baseUrl ? baseUrl : "";
}
I also copied this code (and supporting code):
providers: [
{ provide: API_BASE_URL, useFactory: getRemoteServiceBaseUrl },
from root.module.ts to main.module.ts
When I use the same code in my app, baseUrl is always null. What might I have missed?
That solution works! Thank you!
I had to make a few minor changes to get the code to build. Here is the complete solution for anyone who might want to do something similar.
.Core/TtmUserLoginAttempts.cs (perform Add-Migration, Update-Database)
using Abp.Authorization.Users;
namespace ngTTM.Authorization.Users
{
public class TtmUserLoginAttempt : UserLoginAttempt
{
public string ClientInfo { get; set; }
}
}
.EntityFrameworkCore/EntityFrameworkCore/ntTTMDbContext.cs
public virtual DbSet<TtmUserLoginAttempt> TtmUserLoginAttempts { get; set; }
.Web.Core/Models/TokenAuth/AuthenticateModel.cs
public string ClientInfo { get; set; }
.Web.Core/Controllers/TokenAuthController.cs
private async Task<AbpLoginResult<Tenant, User>> GetLoginResultAsync(string usernameOrEmailAddress,
string password,
string tenancyName,
string clientInfo)
{
var loginResult = await _logInManager.LoginAsync(clientInfo,
usernameOrEmailAddress,
password,tenancyName);
switch (loginResult.Result)
{
case AbpLoginResultType.Success:
return loginResult;
default:
throw _abpLoginResultTypeHelper
.CreateExceptionForFailedLoginAttempt(loginResult.Result,
usernameOrEmailAddress,
tenancyName);
}
}
.Application/Authorization/LogInManager.cs
using Abp.Authorization;
using Abp.Authorization.Users;
using Abp.Configuration;
using Abp.Configuration.Startup;
using Abp.Dependency;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Abp.Zero.Configuration;
using Microsoft.AspNetCore.Identity;
using ngTTM.Authorization.Roles;
using ngTTM.Authorization.Users;
using ngTTM.MultiTenancy;
using System.Threading.Tasks;
using System.Transactions;
namespace ngTTM.Authorization
{
public class LogInManager : AbpLogInManager<Tenant, Role, User>
{
private IRepository<TtmUserLoginAttempt, long> _ttmUserLoginAttemptRepository;
public LogInManager(
UserManager userManager,
IMultiTenancyConfig multiTenancyConfig,
IRepository<Tenant> tenantRepository,
IUnitOfWorkManager unitOfWorkManager,
ISettingManager settingManager,
IRepository<UserLoginAttempt, long> userLoginAttemptRepository,
IUserManagementConfig userManagementConfig,
IIocResolver iocResolver,
RoleManager roleManager,
IPasswordHasher<User> passwordHasher,
UserClaimsPrincipalFactory claimsPrincipalFactory,
IRepository<TtmUserLoginAttempt, long> ttmUserLoginAttemptRepository)
: base(
userManager,
multiTenancyConfig,
tenantRepository,
unitOfWorkManager,
settingManager,
userLoginAttemptRepository,
userManagementConfig,
iocResolver,
passwordHasher,
roleManager,
claimsPrincipalFactory)
{
_ttmUserLoginAttemptRepository = ttmUserLoginAttemptRepository;
}
[UnitOfWork]
public virtual async Task<AbpLoginResult<Tenant, User>> LoginAsync(string clientInfo,
string userNameOrEmailAddress,
string plainPassword,
string tenancyName = null,
bool shouldLockout = true)
{
var result = await LoginAsyncInternal(userNameOrEmailAddress, plainPassword, tenancyName, shouldLockout);
await SaveLoginAttemptAsync(clientInfo, result, tenancyName, userNameOrEmailAddress);
return result;
}
protected virtual async Task SaveLoginAttemptAsync(string clientInfo,
AbpLoginResult<Tenant, User> loginResult,
string tenancyName,
string userNameOrEmailAddress)
{
using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress))
{
var tenantId = loginResult.Tenant != null ? loginResult.Tenant.Id : (int?)null;
using (UnitOfWorkManager.Current.SetTenantId(tenantId))
{
var loginAttempt = new TtmUserLoginAttempt
{
TenantId = tenantId,
TenancyName = tenancyName,
UserId = loginResult.User != null ? loginResult.User.Id : (long?)null,
UserNameOrEmailAddress = userNameOrEmailAddress,
Result = loginResult.Result,
BrowserInfo = ClientInfoProvider.BrowserInfo,
ClientIpAddress = ClientInfoProvider.ClientIpAddress,
ClientName = ClientInfoProvider.ComputerName,
ClientInfo = clientInfo
};
await _ttmUserLoginAttemptRepository.InsertAsync(loginAttempt);
await UnitOfWorkManager.Current.SaveChangesAsync();
await uow.CompleteAsync();
}
}
}
}
}
/src/account/login/login.service.ts
authenticate(finallyCallback?: () => void, redirectUrl?: string, captchaResponse?: string): void {
finallyCallback = finallyCallback || (() => {
this.spinnerService.hide();
});
// We may switch to localStorage instead of cookies
this.authenticateModel.twoFactorRememberClientToken = this._utilsService.getCookieValue(LoginService.twoFactorRememberClientTokenName);
this.authenticateModel.singleSignIn = UrlHelper.getSingleSignIn();
this.authenticateModel.returnUrl = UrlHelper.getReturnUrl();
this.authenticateModel.captchaResponse = captchaResponse;
this.authenticateModel.clientInfo = this.getClientInfo();
this._tokenAuthService
.authenticate(this.authenticateModel)
.subscribe({
next: (result: AuthenticateResultModel) => {
this.processAuthenticateResult(result, redirectUrl);
finallyCallback();
},
error: (err: any) => {
finallyCallback();
}
});
}
I was not able to find a LoginAsync
method to override so used the closest match LoginAsyncInternal
.
This is what I have so far:
LoginManager.cs
using Abp.Authorization;
using Abp.Authorization.Users;
using Abp.Configuration;
using Abp.Configuration.Startup;
using Abp.Dependency;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Abp.Zero.Configuration;
using Microsoft.AspNetCore.Identity;
using ngTTM.Authorization.Roles;
using ngTTM.Authorization.Users;
using ngTTM.MultiTenancy;
using System.Threading.Tasks;
using System.Transactions;
namespace ngTTM.Authorization
{
public class LogInManager : AbpLogInManager<Tenant, Role, User>
{
private IRepository<TtmUserLoginAttempt, long> _ttmUserLoginAttemptRepository;
private string _clientInfo;
public LogInManager(
UserManager userManager,
IMultiTenancyConfig multiTenancyConfig,
IRepository<Tenant> tenantRepository,
IUnitOfWorkManager unitOfWorkManager,
ISettingManager settingManager,
IRepository<UserLoginAttempt, long> userLoginAttemptRepository,
IUserManagementConfig userManagementConfig,
IIocResolver iocResolver,
RoleManager roleManager,
IPasswordHasher<User> passwordHasher,
UserClaimsPrincipalFactory claimsPrincipalFactory,
IRepository<TtmUserLoginAttempt, long> ttmUserLoginAttemptRepository)
: base(
userManager,
multiTenancyConfig,
tenantRepository,
unitOfWorkManager,
settingManager,
userLoginAttemptRepository,
userManagementConfig,
iocResolver,
passwordHasher,
roleManager,
claimsPrincipalFactory)
{
_ttmUserLoginAttemptRepository = ttmUserLoginAttemptRepository;
}
protected override Task<AbpLoginResult<Tenant, User>> LoginAsyncInternal(UserLoginInfo login, string tenancyName)
{
TtmUserLoginInfo ttmUserLoginInfo = login as TtmUserLoginInfo;
_clientInfo = ttmUserLoginInfo.ClientInfo;
return base.LoginAsyncInternal(login, tenancyName);
}
protected override async Task SaveLoginAttemptAsync(AbpLoginResult<Tenant, User> loginResult, string tenancyName, string userNameOrEmailAddress)
{
using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress))
{
var tenantId = loginResult.Tenant != null ? loginResult.Tenant.Id : (int?)null;
using (UnitOfWorkManager.Current.SetTenantId(tenantId))
{
var loginAttempt = new TtmUserLoginAttempt
{
TenantId = tenantId,
TenancyName = tenancyName,
UserId = loginResult.User != null ? loginResult.User.Id : (long?)null,
UserNameOrEmailAddress = userNameOrEmailAddress,
Result = loginResult.Result,
BrowserInfo = ClientInfoProvider.BrowserInfo,
ClientIpAddress = ClientInfoProvider.ClientIpAddress,
ClientName = ClientInfoProvider.ComputerName,
ClientInfo = _clientInfo
};
await _ttmUserLoginAttemptRepository.InsertAsync(loginAttempt);
await UnitOfWorkManager.Current.SaveChangesAsync();
await uow.CompleteAsync();
}
}
}
}
}
TtmUserLoginInfo.cs
using Microsoft.AspNetCore.Identity;
namespace ngTTM.Authorization.Users
{
public class TtmUserLoginInfo : UserLoginInfo
{
public string ClientInfo { get; set; }
public TtmUserLoginInfo(string loginProvider, string providerKey, string displayName, string clientInfo): base(loginProvider, providerKey, displayName)
{
ClientInfo = clientInfo;
}
}
}
TokenAuthController.cs
private async Task<AbpLoginResult<Tenant, User>> GetLoginResultAsync(string usernameOrEmailAddress, string password, string tenancyName, string clientInfo)
{
// the old way which works without clientInfo
//AbpLoginResult<Tenant, User> loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName);
// the new way as recommended by @aaron
TtmUserLoginInfo ttmUserLoginInfo = new TtmUserLoginInfo("", "", "", clientInfo);
AbpLoginResult<Tenant, User> loginResult = await _logInManager.LoginAsync(ttmUserLoginInfo, tenancyName);
switch (loginResult.Result)
{
case AbpLoginResultType.Success:
return loginResult;
default:
throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(loginResult.Result, usernameOrEmailAddress, tenancyName);
}
}
While I am able to pass clientInfo into LogInManager, the login fails (no surprise) since I don't know what values to use for loginProvider, providerKey, and displayName for UserLoginInfo.
What are the argument values to use to login to my site (and localhost)?
@ismcagdas -
Adding my ClientInfo string to the loginResult.Identity
claim list is too late in the process, as
LogInManager.SaveLoginAttemptAsync
is called before logInManager.LoginAsync
returns loginResult
.
@support - Thank you for the code sample via email.
I have successfully written the constant string "test..." to my new column ClientInfo in AbpUserLoginAttempts table.
protected override async Task SaveLoginAttemptAsync(AbpLoginResult<Tenant, User> loginResult, string tenancyName, string userNameOrEmailAddress)
{
using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress))
{
var tenantId = loginResult.Tenant != null ? loginResult.Tenant.Id : (int?)null;
using (UnitOfWorkManager.Current.SetTenantId(tenantId))
{
var loginAttempt = new TtmUserLoginAttempt
{
TenantId = tenantId,
TenancyName = tenancyName,
UserId = loginResult.User != null ? loginResult.User.Id : (long?)null,
UserNameOrEmailAddress = userNameOrEmailAddress,
Result = loginResult.Result,
BrowserInfo = ClientInfoProvider.BrowserInfo,
ClientIpAddress = ClientInfoProvider.ClientIpAddress,
ClientName = ClientInfoProvider.ComputerName,
ClientInfo = "test..."
};
await _myUserLoginAttemptRepository.InsertAsync(loginAttempt);
await UnitOfWorkManager.Current.SaveChangesAsync();
await uow.CompleteAsync();
}
}
}
The only question remaining is how to replace the constant "test..." string with my client data?
Getting client data to the server via TokenAuthController.Authenticate(...) is easily done.
The signature of SaveLoginAttemptAsync can't be changed.
ANZ app doesn't call SaveLoginAttemptAsync directly, so adding a parameter would be useless anyway.
The my string can't be piggybacked with an existing parameter.
The LogInManager
class is instantiated in Abp library, not by ANZ app. So, no adding parameters there either.
Sorry if this sounds like a dumb question that any decent C# programmer could answer. I don't see any mechanism that would allow the string presented during authentication to be available in SaveLoginAttemptAsync.
@troyfernando - building is a disk-intensive process. Upgrading from HDD to SSD made all the difference for me.
Try adding the [string] to *.xml localization dictionaries in .Core/Localization
npm start 15% building 47/69 modules 22 active ... build time under 2 minutes.
My build time improved significantly after upgrading to i7 CPU, SSD, and faster SRAM.