Activities of "ageiter"

I found the problem in the configuration of the MobileMenuSelector:

Not working: options.MobileMenuSelector = items => items.Where(x => x.MenuItem.Name == "Home");

Working: options.MobileMenuSelector = items => items.Where(x => x.MenuItem.Name == MyProjectName.Home);

Since the MenuItem.Name is composed of Prefix + ".Home", it does not work with just "Home".

Actually I should have noticed this earlier, but I took the code from the documentation and did not suspect the fault there.

Please correct the documentation accordingly here for MVC and here for Blazor

Thanks.

Hi @enisn

Thanks for your reply.

Please be sure to write such things in the documentation if any options do not work for LeptonX. When I look at this page in the documentation, I would never get the idea that this only applies to the Basic Theme. Because there is the sentence "The Basic Theme currently doesn't implement the breadcrumbs." in there, I would assume that breadcrumbs don't exist there and then this option wouldn't make sense. Do you know what I mean?

With which version did you do the test with the MobileMenuSelector? 7.3.3 or newer?

  • ABP Framework version: v7.3.3
  • LeptonX version: v2.3.3
  • UI Type: Blazor Server

I have created a new Blazor server project with the latest version 7.3.3. I use the LeptonX 2.3.3 theme with the TopMenu layout. I saw in the documentation that I can disable the breadcrumbs by doing the following:

        Configure<PageHeaderOptions>(options =>
        {
            options.RenderBreadcrumbs = false;
        });

When I look with the debugger, this value is already initialized with "false". And still the breadcrumbs are not hidden. Also the other options (PageTitle & Toolbar) do not work.

By the way, another bug with LeptonX options: I already wrote this in the thread, the options for MobileMenuSelector don't work either.... Is there any news about this? Could the bug be reproduced?

        Configure<LeptonXThemeBlazorOptions>(options =>
        {
            options.Layout = LeptonXBlazorLayouts.TopMenu;
            options.MobileMenuSelector = items => items.Where(x => x.MenuItem.Name == "Home");
        });

Since I think this is a bug, I would appreciate it if you would credit me back the points.

Thanks, Adrian

Commercial LeptonX menu's not drawing properly for Blazor-Server when set to TopMenu

I'm mentioning this here for visibility, as we can see no sign of any Issue having been added to either the repo issues or milestones to address this as we reported it about a month ago in 7.3.2 and it still exists in 7.3.3 and also the current 7.4.0 release candidate, and the bot has unhelpfully locked the Support thread so we can no longer post there!

[LeptonX top menu issues #5635](https://support.abp.io/QA/Questions/5635/LeptonX-top-menu-issues)

I won't repeat all the details here as they are in the Support thread, but to summarize the menu's don't render correctly after logging in e.g.

It appears to be related to the delayed .css loading mechanism.

If someone could point us to an issue that we could at least monitor progress, it would be most helpful as we've had to drop back to the Lepton theme for all new development while this issue exists, and having no visibility of whether this is being addressed makes working with the ABP commercial framework rather difficult.

Thank you.

I've noticed this problem too, just haven't been able to verify exactly when it occurs.

New Blazor project made with LeptonX (7.3.3). MobileMenuSelector does not seem to work correctly anymore. Added it exactly as in the documentation, but the menu buttons are not displayed. If I remove this configuration, then it works (but I want to have other buttons in it). With MVC the same behavior, also does not work anymore. In version 7.2.2 it did work.

    private void ConfigureTheme()
    {
        Configure<LeptonXThemeOptions>(options =>
        {
            options.DefaultStyle = LeptonXStyleNames.System;
        });

        Configure<LeptonXThemeMvcOptions>(options =>
        {
            options.ApplicationLayout = LeptonXMvcLayouts.TopMenu;
            options.MobileMenuSelector = items => items.Where(x => x.MenuItem.Name == "Home");
        });

        Configure<LeptonXThemeBlazorOptions>(options =>
        {
            options.Layout = LeptonXBlazorLayouts.TopMenu;
            options.MobileMenuSelector = items => items.Where(x => x.MenuItem.Name == "Home");
        });
    }

Ok, I have done it now. I'll post the code in case anyone else can use it.

I find it a bit uncomfortable that I had to implement an extra class for the AuthenticationProvider with interface IAccessTokenProvider. @maliming, do you know a more elegant solution?

public class MyAuthenticationProvider : IAccessTokenProvider
{
    private readonly AuthenticationToken _accessToken;
    
    public AllowedHostsValidator AllowedHostsValidator { get; }

    public MyAuthenticationProvider(AuthenticationToken accessToken)
    {
        _accessToken = accessToken;
    }

    public Task<string> GetAuthorizationTokenAsync(Uri uri, Dictionary<string, object>? additionalAuthenticationContext = null, CancellationToken cancellationToken = default)
    {
        return Task.FromResult(_accessToken.Value);
    }
}

In class MyLoginModel.cs:

public override async Task<IActionResult> OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null)
{
    ...
    var externalUser = await UserManager.FindByEmailAsync(email);
    if (externalUser == null)
    {
        // Create a new user with data from AzureAD profile
        loginInfo.Principal.AddClaim(AbpClaimTypes.Email, loginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? string.Empty);
        loginInfo.Principal.AddClaim(AbpClaimTypes.UserName, loginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? string.Empty);
        loginInfo.Principal.AddClaim(AbpClaimTypes.Name, loginInfo.Principal.FindFirstValue(ClaimTypes.GivenName) ?? string.Empty);
        loginInfo.Principal.AddClaim(AbpClaimTypes.SurName, loginInfo.Principal.FindFirstValue(ClaimTypes.Surname) ?? string.Empty);

        externalUser = await CreateExternalUserAsync(loginInfo);

        await SetProfilePictureAsync(loginInfo, externalUser);
    }
    ...
}
/// < summary>
/// Save user's profile picture from AzureAD as ABP profile picture.
/// Note: Code copied and modified from Volo.Abp.Account.AccountAppService
/// < /summary>
private async Task SetProfilePictureAsync(ExternalLoginInfo loginInfo, IdentityUser user)
{
    try
    {
        var accessToken = loginInfo.AuthenticationTokens?.FirstOrDefault(x => x.Name == "access_token");
        if (accessToken == null)
        {
            return;
        }

        var authenticationProvider = new BaseBearerTokenAuthenticationProvider(new MyAuthenticationProvider(accessToken));
        var graphClient = new GraphServiceClient(authenticationProvider);

        // Get user photo from Azure
        using (var photoStream = await graphClient.Me.Photo.Content.GetAsync())
        {
            if (photoStream == null)
            {
                return;
            }

            var input = new ProfilePictureInput { ImageContent = new RemoteStreamContent(photoStream), Type = ProfilePictureType.Image };

            await SettingManager.SetForUserAsync(user.Id, AccountSettingNames.ProfilePictureSource, input.Type.ToString());

            var userIdText = user.Id.ToString();

            if (input.Type != ProfilePictureType.Image)
            {
                if (await AccountProfilePictureContainer.ExistsAsync(userIdText))
                {
                    await AccountProfilePictureContainer.DeleteAsync(userIdText);
                }
            }
            else
            {
                if (input.ImageContent == null)
                {
                    throw new NoImageProvidedException();
                }

                await AccountProfilePictureContainer.SaveAsync(userIdText, input.ImageContent.GetStream(), true);
            }
        }
    }
    catch (Exception ex)
    {
        Logger.LogWarning($"Exception when retrieving or storing the profile picture: {ex.Message}");
    }
}

I have now solved this a little differently, as I do not need the manual registration and prefer to create the user automatically. Therefore I have now copied the OnGetExternalLoginCallbackAsync method and adapted it accordingly (see below).

loginInfo.Principal.AddClaim(AbpClaimTypes.SurName, loginInfo.Principal.FindFirstValue(ClaimTypes.Surname) ?? string.Empty);

        public override async Task<IActionResult> OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null)
        {
            if (remoteError != null)
            {
                Logger.LogWarning($"External login callback error: {remoteError}");
                return RedirectToPage("./Login");
            }

            await IdentityOptions.SetAsync();

            var loginInfo = await SignInManager.GetExternalLoginInfoAsync();
            if (loginInfo == null)
            {
                Logger.LogWarning("External login info is not available");
                return RedirectToPage("./Login");
            }

            var result = await SignInManager.ExternalLoginSignInAsync(
                loginInfo.LoginProvider,
                loginInfo.ProviderKey,
                isPersistent: false,
                bypassTwoFactor: true
            );

            if (!result.Succeeded)
            {
                await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
                {
                    Identity = IdentitySecurityLogIdentityConsts.IdentityExternal,
                    Action = "Login" + result
                });
            }

            if (result.IsLockedOut)
            {
                Logger.LogWarning($"External login callback error: user is locked out!");
                throw new UserFriendlyException("Cannot proceed because user is locked out!");
            }

            if (result.IsNotAllowed)
            {
                Logger.LogWarning($"External login callback error: user is not allowed!");
                throw new UserFriendlyException("Cannot proceed because user is not allowed!");
            }

            if (result.Succeeded)
            {
                return RedirectSafely(returnUrl, returnUrlHash);
            }

            //TODO: Handle other cases for result!

            var email = loginInfo.Principal.FindFirstValue(ClaimTypes.Email);
            if (string.IsNullOrWhiteSpace(email))
            {
                return RedirectToPage("./Register", new {
                    IsExternalLogin = true,
                    ExternalLoginAuthSchema = loginInfo.LoginProvider,
                    ReturnUrl = returnUrl
                });
            }

            var user = await UserManager.FindByEmailAsync(email);
            if (user == null)
            {
                // Get additional infos from AzureAD Claims and set them to AbpClaims
                loginInfo.Principal.AddClaim(AbpClaimTypes.Email, loginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? string.Empty);
                loginInfo.Principal.AddClaim(AbpClaimTypes.UserName, loginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? string.Empty);
                loginInfo.Principal.AddClaim(AbpClaimTypes.Name, loginInfo.Principal.FindFirstValue(ClaimTypes.GivenName) ?? string.Empty);
                loginInfo.Principal.AddClaim(AbpClaimTypes.SurName, loginInfo.Principal.FindFirstValue(ClaimTypes.Surname) ?? string.Empty);

                user = await CreateExternalUserAsync(loginInfo);
            }
            else
            {
                if (await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey) == null)
                {
                    CheckIdentityErrors(await UserManager.AddLoginAsync(user, loginInfo));
                }
            }

            await SignInManager.SignInAsync(user, false);

            await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
            {
                Identity = IdentitySecurityLogIdentityConsts.IdentityExternal,
                Action = result.ToIdentitySecurityLogAction(),
                UserName = user.Name
            });

            return RedirectSafely(returnUrl, returnUrlHash);
        }

How do I get the profile picture now? I think the only way is through the Microsoft Graph, right? Can you tell me how to create the GraphServiceClient? I don't know what I need to pass there as parameters and have available in this method.

I see that I can retrieve the claims here.

        public override async Task<IActionResult> OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null)
        {
            var loginInfo = await SignInManager.GetExternalLoginInfoAsync();
            var claims = loginInfo.Principal.Claims;

            return await base.OnGetExternalLoginCallbackAsync(returnUrl, returnUrlHash, remoteError);
        }

But then comes the page for registration, in which only the mail address is added to the new user. How can I add this additional information there? So that at the end the new user is completed with data from the claims?

  • ABP Framework version: v7.3.2
  • UI Type: Blazor Server
  • Database System: EF Core (SQL Server)
  • Tiered (for MVC) or Auth Server Separated (for Angular): no

I use the login with the external provider "Microsoft" (Azure AD). If the user does not exist yet, it will be registered with the email address by default. However, I would like to automatically fill the other information as well: firstname, lastname and profile picture...

How can I do this? I have tried with a custom LoginModel but have not figured out how and which method I would need to hook into.

Thank you for the explanation. This is how it worked. What was missing was the @inherits... in Razor file.

The documentation contains the sentence "Don't forget to remove repeated attributes from the razor page!". I must have deleted one line too much...

Showing 161 to 170 of 243 entries
Made with ❤️ on ABP v9.0.0-preview Updated on September 20, 2024, 08:30