Activities of "Sturla"

I´m trying to figure out how to use the CancellationTokenProvider insted of adding CancellationToken to all my ApplicationService methods and then passing CancellationToken to them in my UI.

I might be misunderstanding how this is supposed to work but the documentation on this is serverly lacking

When navigating from the page should cancel the reqest and make IsCancellationRequested == true or do I have to do something in the UI?

Here is my code where I added a Task.Delay to the repository method to be able to then quickly navigate from that page.

Could this be related to Blazor Server? IF it doesn´t work with BS then it would be great to add that to the documentation.

UPDATE: I was also unsuccesfull passing token manually into the methods (something I was hoping I didn´t need to do)

Blazor

@inject INavigationInterception NavigationInterception

private CancellationTokenSource _cts = new();

protected override async Task OnInitializedAsync()
{
    await NavigationInterception.EnableNavigationInterceptionAsync();
    NavigationManager.LocationChanged += HandleLocationChanged;
}

private async Task OpenEditBookModalAsync(BookDto input)
{
    var book = await BooksAppService.GetAsync(input.Id, _cts.Token); // Pass CancellationToken

    EditingBookId = book.Id;
    EditingBook = ObjectMapper.Map<BookDto, BookUpdateDto>(book);
    await EditingBookValidations.ClearAll();
    await EditBookModal.Show();
}

private void HandleLocationChanged(object sender, LocationChangedEventArgs e)
{
    _cts.Cancel(); // Cancel the current operations
}

public async ValueTask DisposeAsync()
{
    NavigationManager.LocationChanged -= HandleLocationChanged;
    _cts?.Cancel();
    _cts?.Dispose();
}
public virtual async Task<PagedResultDto<BookDto>> GetListAsync(GetBooksInput input, CancellationToken cancellationToken)
{
    var totalCount = await _bookRepository.GetCountAsync(input.FilterText, input.Name, input.Email, cancellationToken);
    var items = await _bookRepository.GetListAsync(input.FilterText,
     input.Name,
      input.Email,
       input.Sorting,
        input.MaxResultCount,
         input.SkipCount,
          cancellationToken);
    return new PagedResultDto<BookDto>
    {
        TotalCount = totalCount,
        Items = ObjectMapper.Map<List<Book>, List<BookDto>>(items)
    };
}

Repository

    public abstract class EfCoreBookRepositoryBase : EfCoreRepository<CasaDbContext, Book, Guid>
    {
        public EfCoreBookRepositoryBase(IDbContextProvider<CasaDbContext> dbContextProvider)
            : base(dbContextProvider)
        {

        }

        public virtual async Task<List<Book>> GetListAsync(
            string? filterText = null,
            string? name = null,
            string? email = null,
            string? sorting = null,
            int maxResultCount = int.MaxValue,
            int skipCount = 0,
            CancellationToken cancellationToken = default)
        {
            Task.Delay(10000).Wait();
            var query = ApplyFilter((await GetQueryableAsync()), filterText, name, email);
            query = query.OrderBy(string.IsNullOrWhiteSpace(sorting) ? BookConsts.GetDefaultSorting(false) : sorting);
            return await query.PageBy(skipCount, maxResultCount).ToListAsync(cancellationToken);
        }
       }

How should this work? 🤷‍♂️🤨

  • ABP Framework version: 8.0.5
  • UI Type: Blazor Server
  • Database System: EF Core

Can we get "Add post" in this Blogs dropdown for quickly adding blogs? Less clicking around.

and "Go to post" in the posts

Add "Add blog to menu".. I was trying out various urls to figure out what the url should be

Thanks for the reply. Very informative.

CMS Kit public side is implemented in MVC only because of SEO capabilities.

Will you implement this in Blazor server https://github.com/abpframework/abp/issues/18289 (and if now, when?) or is my only change of having one application running using MVC?

CMS Kit is not designed for this.

Maybe the easier way is to have two applications.. one for my product/landing and one for all the subdomains tenants using the same database. I will explore this a bit further and try out your suggestions.

I´m just trying to figure out how to have the lowest operational cost while not complicating anything. I´m e.g. aiming on using Azure Container Apps (managed kubernetes).

Please add comments to your nuget packages (interfaces if missing). Its super easy https://stackoverflow.com/a/57731750

Currently there are none but I don´t doubt that you have comments on the methods them selfs but having them on the interfaces is also must for better self service

It is almost working but something is off..

I updated my code example with yours.

Am I pulling the correct values? I have connected gg and ss..

Can you check if is there a console error? It seems like it's a text input in your case.

I checked and its not working in Blazor but is working in MVC

The auto only kicks in if I enter the first letter of the blogs name.

p.s Can we do something about the css of the dropdown? It cramped and hard to view...

I implemented this as I understood you guys. I think it would be rather easy to add the users into the dropdown so you would only need one click to switch.

This modal has a button to switch

@using Volo.Abp.Account
@using Volo.Abp.Data
@using Volo.Abp.Domain.Repositories
@using Volo.Abp.Identity
@using Volo.Abp.MultiTenancy
@using Volo.Abp.Users
@inject ICurrentTenant CurrentTenant
@inject ICurrentUser CurrentUser
@inject NavigationManager NavigationManager
@inject IIdentityLinkUserAppService IdentityLinkUserAppService
@inject IJSRuntime JsRuntime

<form method="post" action="Account/LinkLogin" id="LinkLoginForm" hidden>
    <input type="hidden" name="SourceLinkUserId" value="@CurrentUser.Id">
    <input type="hidden" name="SourceLinkTenantId" value="@CurrentTenant.Id">
    <input type="hidden" id="SourceLinkToken" name="SourceLinkToken">
    <input type="hidden" id="TargetLinkUserId" name="TargetLinkUserId">
    <input type="hidden" id="TargetLinkTenantId" name="TargetLinkTenantId">
    <input type="hidden" name="ReturnUrl">
</form>

@if (linkedAccounts != null)
{
    <li class="outer-menu-item" id="MenuItem_LinkedAccounts" @onclick="@ShowModal" style="cursor:pointer;">
        <a class="lpx-menu-item-link">
            <span class="lpx-menu-item-icon">
                <i class="lpx-icon fa fa-exchange-alt" aria-hidden="true"></i>
            </span>
            <span class="lpx-menu-item-text">Switch Account</span>
        </a>
    </li>
}

<Modal @ref="modalRef">
    <ModalContent>
        <ModalHeader>
            <ModalTitle>Switch Account</ModalTitle>
            <CloseButton Clicked="@HideModal" />
        </ModalHeader>
        <ModalBody>
            @if (linkedAccounts == null)
            {
                <Text> Loading... </Text>
            }
            else
            {
                foreach (var account in linkedAccounts)
                {
                    <Button Color="Color.Light" Size="Size.ExtraSmall" Clicked="() => SwitchToAccount(account)">
                        Switch to Tenant: @account.TargetTenantName (user: @account.TargetUserName)
                    </Button>
                }
            }
        </ModalBody>
        <ModalFooter>
            <Button Color="Color.Secondary" Clicked="@HideModal">Close</Button>
        </ModalFooter>
    </ModalContent>
</Modal>

@code {
    private List<LinkUserDto> linkedAccounts = new();

    public LinkedAccountComponent(IRepository<IdentityUser, Guid> userRepository)
    {
        this.userRepository = userRepository;
    }

    private readonly IRepository<IdentityUser, Guid> userRepository;
    private Modal modalRef = new();

    protected override async Task OnInitializedAsync()
    {
        linkedAccounts = (await IdentityLinkUserAppService.GetAllListAsync()).Items.ToList();
    }

    public async Task SwitchToAccount(LinkUserDto linkedAccount)
    {
        // Generating SourceLinkToken
        var sourceLinkToken = await IdentityLinkUserAppService.GenerateLinkLoginTokenAsync();

        await JsRuntime.InvokeVoidAsync("eval", "document.getElementById('SourceLinkToken').value = '" + sourceLinkToken + "'");
        await JsRuntime.InvokeVoidAsync("eval", "document.getElementById('TargetLinkUserId').value = '" + linkedAccount.TargetUserId + "'");
        await JsRuntime.InvokeVoidAsync("eval", "document.getElementById('TargetLinkTenantId').value = '" + linkedAccount.TargetTenantId + "'");
        await JsRuntime.InvokeVoidAsync("eval", "document.getElementById('LinkLoginForm').submit()");

        await HideModal();
    }

    private Task ShowModal()
    {
        return modalRef.Show();
    }

    private Task HideModal()
    {
        return modalRef.Hide();
    }
}

and ConfigureMenuAsync in the IMenuContributor

    public async Task ConfigureMenuAsync(MenuConfigurationContext context)
   {
       if (context.Menu.Name == StandardMenus.Main)
       {
           await ConfigureMainMenuAsync(context);
       }
       else if (context.Menu.Name == StandardMenus.User)
       {
           await ConfigureUserMenuAsync(context);
       }
   }
   
   private async Task ConfigureUserMenuAsync(MenuConfigurationContext context)
   {
       context.Menu.Items.Add(
         new ApplicationMenuItem("LinkedAccounts", "Linked Accounts", "#")
           .UseComponent(typeof(LinkedAccountComponent)));
           
       await Task.CompletedTask.ConfigureAwait(false);
   }

Is this code valid @liangshiwei AND how should you do the actual switching?

Please add some help information (?) bubbles to settings. E.g. the OpenId settings where I don´t know what things do... What is "Consent Type" etc?

It would be great to have a short text like "Consent type does....". Having a link to abp.io documentation would be super nice (this would also give abp.io valuable inbound SEO links)

Host and teant CMS strangness

If I create a page (test) CMS as the Host I can see it if I log out, if I create a page (xxx) under a tenant (Rambo) and log out I will not see the first page (test)

F5 does nothing to refresh...

Expected behaviour when no user is logged in

  • test page would always bee shown (on root domain (https://www.root.com) and subdomain (https://rambo.root.com)) since this is host CMS. Update: I´m doubting this.. it should probably also just be visible on root domain.
  • xxx page only shown on subdomain (https://rambo.root.com)

In my local test there is currently no subdomain setup so I ask if this is a normal behavior?

I´m btw trying to see if I can have one application with host and tenant specific CMS (https://support.abp.io/QA/Questions/6885/One-application-Admin-and-public-in-one-CMS-setup)

This was btw tested with abp.io 8.0.5 MVC

Is it possible to have one running application that has

  • both the admin and public functionality in one place (not backend/frontend application)?

Something like:

  • Tenants would control the CMS (when logged in) (www.tenant.domain.com)
    • Not logged in users would see the result (blog/pages/menus/etc)
  • Host admin have CMS for the host domain ((www.domain.com)
    • plush would be that the host could create menu items that would apply for all tenants

Tenant could create a Blog page like this

and view it like this on the same application, not having to have another application instance (public web)?

It is cheaper and easier to have it all in one solution.

p.s I tried this in Blazor Server.. is that maybe not supported? And if so are there any other restrictions or limitations for Blazor?

e.s I tried this also with MVC and had some different strangeness https://support.abp.io/QA/Questions/6711/Bugs--Issues-v81x#answer-3a115a09-806f-bf69-6f41-319509df51ef

Abp.io Blazor Server 8.0.5

Showing 11 to 20 of 203 entries
Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11