Yes I thought so. I’m, my self, on the fence with Blazor (to be used for everything) so I will just use it for the backend for me and my tenants to manage things but will use MVC as the public facing front.
So two applications at this point and if/when Blazor SSR will contain all the bits I can considder having one.
P.s Sorry I mixed the SEO bit into this. That was just for the CMS blog part and isnt related to Blazor in any way. 😅 BUT for my idea to work I need that part so I look forware hearing from you.
@enisin
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?
Will the new Blazor SSR not have the CMS Kit public side functionality since you plan to add SEO https://github.com/abpframework/abp/issues/16342#issuecomment-1997078207 ? Any eta for it?
I need to know if my only (none Angular/React) option is then to use MVC, if Im starting something new for the next 3 months?
Can you use something from here? https://support.abp.io/qa/questions/6852/3a115e97-a34f-d9c6-6848-7adc94262fc1
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? 🤷♂️🤨
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).
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?