Open Closed

Modify ProfileAppService #8724


0
ArturCorreia created

Hi,

Can you point me in the right direction on how I could connect a new table to an existing zero table in the user profile?

I think i need to modify ProfileAppService.cs and join the zero User table with my UserDetails table using LinQ.

But I need to modify the output DTO of the GetCurrentUserProfileForEdit method and return necessary information to profile edit modal. Then, I need to modify input DTO class of UpdateCurrentUserProfile method and update both User entity and my UserDetails entity in this method.

I’ve tried to figure it out but don’t think I’m on the right track can you point me exactly how I can achieve this within the 2 methods ?

I have also used my classes as a properties in your zero classes, dont know if that was the right thing to do and/or if joining the tables/classes based on ID's.

Could you show how & where I can achieve this?

Thanks


23 Answer(s)
  • 0
    ismcagdas created
    Support Team

    Hi @ArturCorreia

    Could you share your related entity for new table ? I can write a sample query and share it with you.

    Thanks,

  • 0
    ArturCorreia created

    Hi ismcagdas sure here you go, thank you.

    [Table("UserDetails")]
    public class UserDetail : FullAuditedEntity
    {
        public const int MaxJobTitleLength = 50;
        public const int MaxPrefersToBeCalledByNameLength = 50;
        public const int MaxTitleLength = 50;
        public const int MaxEmployeeNumberLength = 50;
        public const int MaxDepartmentLength = 50;
        public const int MaxSiteLength = 50;
        public const int MaxPhoneNumberLength = 50;
        public const int MaxMobileNumberLength = 50;
        public const int MaxLinkedInProfileURLLength = 200;
        public const int MaxTwitterProfileURLLength = 50;
        public const int MaxHobbiesLength = 100;
        public const int MaxFavouriteSportsTeamLength = 100;
        public const int MaxFavouriteBandsOrMusicsLength = 100;
        public const int MaxFavouriteArtistsOrMoviesLength = 100;
        public const int MaxMoreAboutMeLength = 4000;
        public const int MaxExternalContactCompanyNameLength = 50;
        public const int MaxExternalContactWebsiteLength = 200;
    
        public int UserId { get; set; }
    
        [MaxLength(MaxJobTitleLength)] 
        public string JobTitle { get; set; }
    
        public int LineManagerId { get; set; }
    
        [MaxLength(MaxPrefersToBeCalledByNameLength)]
        public string PrefersToBeCalledByName { get; set; }
    
        [MaxLength(MaxTitleLength)]
        public string Title { get; set; } 
    
        [MaxLength(MaxEmployeeNumberLength)]
        public string EmployeeNumber { get; set; }
    
        public DateTime DateOfBirth { get; set; }
    
        [MaxLength(MaxDepartmentLength)]
        public string Department{ get; set; }
    
        public int CountryId { get; set; }
    
        [MaxLength(MaxSiteLength)]
        public string Site { get; set; }
    
        [MaxLength(MaxPhoneNumberLength)]
        public string PhoneNumber { get; set; }
    
        [MaxLength(MaxMobileNumberLength)]
        public string MobileNumber { get; set; }
    
        [MaxLength(MaxLinkedInProfileURLLength)]
        public string LinkedInProfileURL { get; set; }
    
        [MaxLength(MaxTwitterProfileURLLength)]
        public string TwitterProfileURL { get; set; }
    
        public DateTime JoinedTheCompanyOnDate { get; set; }
    
        public DateTime JoinedTheDepartmentOnDate { get; set; }
    
        [MaxLength(MaxHobbiesLength)]
        public string Hobbies { get; set; }
    
        [MaxLength(MaxFavouriteSportsTeamLength)]
        public string FavouriteSportsTeam { get; set; }
    
        [MaxLength(MaxFavouriteBandsOrMusicsLength)]
        public string FavouriteBandsOrMusics { get; set; }
    
        [MaxLength(MaxFavouriteArtistsOrMoviesLength)]
        public string FavouriteArtistsOrMovies { get; set; } 
    
        [MaxLength(MaxMoreAboutMeLength)]
        public string MoreAboutMe { get; set; }
    
        public bool ExternalContactFlag { get; set; }
    
        [MaxLength(MaxExternalContactCompanyNameLength)]
        public string ExternalContactCompanyName { get; set; }
    
        public int ExternalContactTypeID { get; set; }
    
        [MaxLength(MaxExternalContactWebsiteLength)]
        public string ExternalContactWebsite { get; set; } 
    }
    
  • 0
    ismcagdas created
    Support Team

    Hi,

    You can basically write something like this;

    var query = (from user in _userRepository.GetAll()
    join userDetail in _userDetailRepository.GetAll() on user.Id equals userDetail.UserId
    select new {
    	UserName = user.Username,
    	...
    	Title = userDetail.Title,
    	...
    });
    
  • 0
    ArturCorreia created

    Hi ismcagdas

    Now that i have then join in place and no errors showing, what i notice now is that when running the server side app and when i execute the https://localhost:44301/api/services/app/Profile/GetCurrentUserProfileForEdit and dont seem to get all the joined data back from my own table and i do have data in both tables.

    Is there something else i'm missing like another map?

    Thanks.

  • 0
    ismcagdas created
    Support Team

    Hi @ArturCorreia

    Could you share your AppService method and it's output DTO Class ?

  • 0
    ArturCorreia created

    sure...

        public async Task<CurrentUserProfileEditDto> GetCurrentUserProfileForEdit()
        {
            var user = await GetCurrentUserAsync();
            var userProfileEditDto = ObjectMapper.Map<CurrentUserProfileEditDto>(user);
    
            userProfileEditDto.QrCodeSetupImageUrl = user.GoogleAuthenticatorKey != null
                ? _googleTwoFactorAuthenticateService.GenerateSetupCode("EZNow",
                    user.EmailAddress, user.GoogleAuthenticatorKey, 300, 300).QrCodeSetupImageUrl
                : "";
            userProfileEditDto.IsGoogleAuthenticatorEnabled = user.GoogleAuthenticatorKey != null;
    
            if (Clock.SupportsMultipleTimezone)
            {
                userProfileEditDto.Timezone = await SettingManager.GetSettingValueAsync(TimingSettingNames.TimeZone);
    
                var defaultTimeZoneId = await _timeZoneService.GetDefaultTimezoneAsync(SettingScopes.User, AbpSession.TenantId);
                if (userProfileEditDto.Timezone == defaultTimeZoneId)
                {
                    userProfileEditDto.Timezone = string.Empty;
                }
            }
    
            var query =  from u in _userRepository.GetAll()
                         join ud in _userDetailRepository.GetAll() on u.Id equals ud.UserId 
                         select new 
                         {
                             u.UserName,
                             u.Name,
                             u.Surname,
                             u.Id,
                             u.EmailAddress,
                             u.PhoneNumber,
                             u.IsPhoneNumberConfirmed,
                             ud.JobTitle,
                             ud.LineManagerId,
                             ud.PrefersToBeCalledByName,
                             ud.Title,
                             ud.EmployeeNumber,
                             ud.DateOfBirth,
                             ud.Department,
                             ud.Site,
                             ud.MobileNumber,
                             ud.LinkedInProfileURL,
                             ud.TwitterProfileURL,
                             ud.JoinedTheCompanyOnDate,
                             ud.JoinedTheDepartmentOnDate,
                             ud.Hobbies,
                             ud.FavouriteSportsTeam,
                             ud.FavouriteBandsOrMusics,
                             ud.FavouriteArtistsOrMovies,
                             ud.MoreAboutMe,
                             ud.ExternalContactCompanyName,
                             ud.ExternalContactWebsite
                         };
    
            ObjectMapper.Map(userProfileEditDto, user);
    
            return userProfileEditDto;
        }
        
        
    public class CurrentUserProfileEditDto
    {
        [Required]
        [StringLength(AbpUserBase.MaxNameLength)]
        public string Name { get; set; }
    
        [Required]
        [StringLength(AbpUserBase.MaxSurnameLength)]
        public string Surname { get; set; }
    
        [Required]
        [StringLength(AbpUserBase.MaxUserNameLength)]
        public string UserName { get; set; }
    
        [Required]
        [StringLength(AbpUserBase.MaxEmailAddressLength)]
        public string EmailAddress { get; set; }
    
        [StringLength(UserConsts.MaxPhoneNumberLength)]
        public string PhoneNumber { get; set; }
    
        public virtual bool IsPhoneNumberConfirmed { get; set; }
    
        public string Timezone { get; set; }
    
        public string QrCodeSetupImageUrl { get; set; }
    
        public bool IsGoogleAuthenticatorEnabled { get; set; }
    
        public DetailDto Detail { get; set; }
    }
    
  • 0
    ismcagdas created
    Support Team

    Would you like to return those fields as well ?

  • 0
    ArturCorreia created

    Hi ismcagdas

    Yes please.

    Thanks.

  • 0
    ismcagdas created
    Support Team

    Hi,

    Then, you need to modify your code like below. Of cours,e you also need to add missing fields to CurrentUserProfileEditDto if there are any.

    public async Task<CurrentUserProfileEditDto> GetCurrentUserProfileForEdit()
        {
            var user = await GetCurrentUserAsync();
            var userProfileEditDto = ObjectMapper.Map&lt;CurrentUserProfileEditDto&gt;(user);
    
            userProfileEditDto.QrCodeSetupImageUrl = user.GoogleAuthenticatorKey != null
                ? _googleTwoFactorAuthenticateService.GenerateSetupCode("EZNow",
                    user.EmailAddress, user.GoogleAuthenticatorKey, 300, 300).QrCodeSetupImageUrl
                : "";
            userProfileEditDto.IsGoogleAuthenticatorEnabled = user.GoogleAuthenticatorKey != null;
    
            if (Clock.SupportsMultipleTimezone)
            {
                userProfileEditDto.Timezone = await SettingManager.GetSettingValueAsync(TimingSettingNames.TimeZone);
    
                var defaultTimeZoneId = await _timeZoneService.GetDefaultTimezoneAsync(SettingScopes.User, AbpSession.TenantId);
                if (userProfileEditDto.Timezone == defaultTimeZoneId)
                {
                    userProfileEditDto.Timezone = string.Empty;
                }
            }
    
            var query =  (from u in _userRepository.GetAll()
                         join ud in _userDetailRepository.GetAll() on u.Id equals ud.UserId 
    					 where u.Id == user.Id
                         select new 
                         {
                             u.UserName,
                             u.Name,
                             u.Surname,
                             u.Id,
                             u.EmailAddress,
                             u.PhoneNumber,
                             u.IsPhoneNumberConfirmed,
                             ud.JobTitle,
                             ud.LineManagerId,
                             ud.PrefersToBeCalledByName,
                             ud.Title,
                             ud.EmployeeNumber,
                             ud.DateOfBirth,
                             ud.Department,
                             ud.Site,
                             ud.MobileNumber,
                             ud.LinkedInProfileURL,
                             ud.TwitterProfileURL,
                             ud.JoinedTheCompanyOnDate,
                             ud.JoinedTheDepartmentOnDate,
                             ud.Hobbies,
                             ud.FavouriteSportsTeam,
                             ud.FavouriteBandsOrMusics,
                             ud.FavouriteArtistsOrMovies,
                             ud.MoreAboutMe,
                             ud.ExternalContactCompanyName,
                             ud.ExternalContactWebsite
                         }).single();
    
            // ObjectMapper.Map(userProfileEditDto, user);
    
    		userProfileEditDto.Department = query.Department;
    		userProfileEditDto.Site = query.Site;
    		// and so on...
    
            return userProfileEditDto;
        }
    
  • 0
    ArturCorreia created

    Ok i have added all the new properties to the userProfileEditDto class.

    When i run the app and execute swagger for that API method I get a 500 status and doesnt evern get to my breakpoint.

    code now looks like this...

    var query = (from u in _userRepository.GetAll() join ud in _userDetailRepository.GetAll() on u.Id equals ud.UserId where u.Id == user.Id select new { u.UserName, u.Name, u.Surname, u.Id, u.EmailAddress, u.PhoneNumber, u.IsPhoneNumberConfirmed, ud.JobTitle, ud.LineManagerId, ud.PrefersToBeCalledByName, ud.Title, ud.EmployeeNumber, ud.DateOfBirth, ud.Department, ud.Site, ud.MobileNumber, ud.LinkedInProfileURL, ud.TwitterProfileURL, ud.JoinedTheCompanyOnDate, ud.JoinedTheDepartmentOnDate, ud.Hobbies, ud.FavouriteSportsTeam, ud.FavouriteBandsOrMusics, ud.FavouriteArtistsOrMovies, ud.MoreAboutMe, ud.ExternalContactCompanyName, ud.ExternalContactWebsite }).Single();

            userProfileEditDto.UserName = query.UserName;
            userProfileEditDto.Name = query.Name;
            userProfileEditDto.Surname = query.Surname;
            userProfileEditDto.EmailAddress = query.EmailAddress;
            userProfileEditDto.PhoneNumber = query.PhoneNumber;
            userProfileEditDto.IsPhoneNumberConfirmed = query.IsPhoneNumberConfirmed;
            userProfileEditDto.JobTitle = query.JobTitle;
            userProfileEditDto.LineManagerId = query.LineManagerId;
            userProfileEditDto.PrefersToBeCalledByName = query.PrefersToBeCalledByName;
            userProfileEditDto.Title = query.Title;
            userProfileEditDto.EmployeeNumber = query.EmployeeNumber;
            userProfileEditDto.DateOfBirth = query.DateOfBirth;
            userProfileEditDto.Site = query.Site;
            userProfileEditDto.MobileNumber = query.MobileNumber;
            userProfileEditDto.LinkedInProfileURL = query.LinkedInProfileURL;
            userProfileEditDto.TwitterProfileURL = query.TwitterProfileURL;
            userProfileEditDto.JoinedTheCompanyOnDate = query.JoinedTheCompanyOnDate;
            userProfileEditDto.JoinedTheDepartmentOnDate = query.JoinedTheDepartmentOnDate;
            userProfileEditDto.Hobbies = query.Hobbies;
            userProfileEditDto.FavouriteSportsTeam = query.FavouriteSportsTeam;
            userProfileEditDto.FavouriteBandsOrMusics = query.FavouriteBandsOrMusics;
            userProfileEditDto.FavouriteArtistsOrMovies = query.FavouriteArtistsOrMovies;
            userProfileEditDto.MoreAboutMe = query.MoreAboutMe;
            userProfileEditDto.ExternalContactCompanyName = query.ExternalContactCompanyName;
            userProfileEditDto.ExternalContactWebsite = query.ExternalContactWebsite;
    
            return userProfileEditDto;
    
  • 0
    ArturCorreia created

    Its the query code its this that causes the 500 and debug doesnt get to my breakpoint for the query.

  • 0
    ArturCorreia created

    Hi

    If my query for the joins are not working, would it be best to extend the zero user table and add all the new columns into that table so i dont need to use the join? If i did extend existing table is this likely to cause other dependencies to break?

    Or can you tell me why this query and code does not work?

    Thanks.

  • 0
    ismcagdas created
    Support Team

    Hi @ArturCorreia,

    Sorry for the delay. Could you check server side Log file to see error details ? Your code is correcty actually, probably there is a minor problem.

  • 0
    ArturCorreia created

    Hi

    Yes i see this in relation to the abpusers table for the isDeleted column.

    INFO 2020-04-05 08:47:43,013 [37 ] c.Infrastructure.ControllerActionInvoker - Route matched with {area = "app", action = "GetCurrentUserProfileForEdit", controller = "Profile"}. Executing controller action with signature System.Threading.Tasks.Task1[EZNow.Authorization.Users.Profile.Dto.CurrentUserProfileEditDto] GetCurrentUserProfileForEdit() on controller EZNow.Authorization.Users.Profile.ProfileAppService (EZNow.Application). ERROR 2020-04-05 08:47:43,647 [29 ] Mvc.ExceptionHandling.AbpExceptionFilter - Invalid column name 'IsDeleted'. Invalid column name 'Id'. Invalid column name 'CreationTime'. Invalid column name 'CreatorUserId'. Invalid column name 'DeleterUserId'. Invalid column name 'DeletionTime'. Invalid column name 'IsDeleted'. Invalid column name 'LastModificationTime'. Invalid column name 'LastModifierUserId'. Microsoft.Data.SqlClient.SqlException (0x80131904): Invalid column name 'IsDeleted'. Invalid column name 'Id'. Invalid column name 'CreationTime'. Invalid column name 'CreatorUserId'. Invalid column name 'DeleterUserId'. Invalid column name 'DeletionTime'. Invalid column name 'IsDeleted'. Invalid column name 'LastModificationTime'. Invalid column name 'LastModifierUserId'. at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action1 wrapCloseInAction) at Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action1 wrapCloseInAction) at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose) at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) at Microsoft.Data.SqlClient.SqlDataReader.TryConsumeMetaData() at Microsoft.Data.SqlClient.SqlDataReader.get_MetaData() at Microsoft.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString, Boolean isInternal, Boolean forDescribeParameterEncryption, Boolean shouldCacheForAlwaysEncrypted) at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean isAsync, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest) at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry, String method) at Microsoft.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior) at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReader(RelationalCommandParameterObject parameterObject) at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable1.Enumerator.InitializeReader(DbContext _, Boolean result) at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func3 operation, Func3 verifySucceeded) at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable1.Enumerator.MoveNext() at System.Linq.Enumerable.Single[TSource](IEnumerable1 source) at lambda_method(Closure , QueryContext ) at System.Linq.Queryable.Single[TSource](IQueryable1 source) at EZNow.Authorization.Users.Profile.ProfileAppService.GetCurrentUserProfileForEdit() in E:\Projects\EZNow\EZNow-API\src\EZNow.Application\Authorization\Users\Profile\ProfileAppService.cs:line 99 at lambda_method(Closure , Object ) at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult() at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) ClientConnectionId:01ecb555-b2ca-4105-a877-34687c9fcae9 Error Number:207,State:1,Class:16

  • 0
    ArturCorreia created

    in fact all those column names of abpusers in invalid.

    'IsDeleted'. Invalid column name 'Id'. Invalid column name 'CreationTime'. Invalid column name 'CreatorUserId'. Invalid column name 'DeleterUserId'. Invalid column name 'DeletionTime'. Invalid column name 'IsDeleted'. Invalid column name 'LastModificationTime'. Invalid column name 'LastModifierUserId'. Microsoft.Data.SqlClient.SqlException (0x80131904): Invalid column name 'IsDeleted'. Invalid column name 'Id'. Invalid column name 'CreationTime'. Invalid column name 'CreatorUserId'. Invalid column name 'DeleterUserId'. Invalid column name 'DeletionTime'. Invalid column name 'IsDeleted'. Invalid column name 'LastModificationTime'. Invalid column name 'LastModifierUserId'.

  • 0
    ismcagdas created
    Support Team

    Hi,

    It seems like the problem is related to something else. Did you or someone else modify User.cs class ? To see this, you can try to add a new EF Core migration and see the changes.

    By default, User entity and AbpUsers table must have LastModifierUserId field. And other fields metioned in the error message.

  • 0
    ArturCorreia created

    Doesnt look like its changed.

    public class User : AbpUser<User> {

        public virtual Guid? ProfilePictureId { get; set; }
    
        public virtual bool ShouldChangePasswordOnNextLogin { get; set; }
    
        public DateTime? SignInTokenExpireTimeUtc { get; set; }
    
        public string SignInToken { get; set; }
    
        public string GoogleAuthenticatorKey { get; set; }
    
        public List&lt;UserOrganizationUnit&gt; OrganizationUnits { get; set; }
    
        //Can add application specific user properties here
    
        public User()
        {
            IsLockoutEnabled = true;
            IsTwoFactorEnabled = true;
        }
        
        public static User CreateTenantAdminUser(int tenantId, string emailAddress)
        {
            var user = new User
            {
                TenantId = tenantId,
                UserName = AdminUserName,
                Name = AdminUserName,
                Surname = AdminUserName,
                EmailAddress = emailAddress,
                Roles = new List&lt;UserRole&gt;(),
                OrganizationUnits = new List&lt;UserOrganizationUnit&gt;()
            };
    
            user.SetNormalizedNames();
    
            return user;
        }
    
        public override void SetNewPasswordResetCode()
        {
            /* This reset code is intentionally kept short.
             * It should be short and easy to enter in a mobile application, where user can not click a link.
             */
            PasswordResetCode = Guid.NewGuid().ToString("N").Truncate(10).ToUpperInvariant();
        }
    
        public void Unlock()
        {
            AccessFailedCount = 0;
            LockoutEndDateUtc = null;
        }
    
        public void SetSignInToken()
        {
            SignInToken = Guid.NewGuid().ToString();
            SignInTokenExpireTimeUtc = Clock.Now.AddMinutes(1).ToUniversalTime();
        }
    }
    
  • 0
    ismcagdas created
    Support Team

    Is it possible to share your project with info@aspnetzero.com ? We can find the problem faster in that way.

  • 0
    ArturCorreia created

    sure i will do.

  • 0
    ArturCorreia created

    I dropboxed the zip

  • 0
    ismcagdas created
    Support Team

    Hi @ArturCorreia

    Thanks, we got the project and we will check it in a short time.

  • 0
    ArturCorreia created

    Hi, any find on fix from my suggested change?

  • 0
    ismcagdas created
    Support Team

    Hi,

    We have replied to your email. We can continue via email for this specific problem.