Activities of "cangunaydin"

  • ABP Framework version: v7.4.0
  • UI Type: No Ui
  • Database System: EF Core (SQL Server, Oracle, MySQL, PostgreSQL, etc..)
  • Tiered (for MVC) or Auth Server Separated (for Angular): no

Hello, I think razor text templating have a memory leak. And i suppose it is sth. with caching. To produce the problem here are the steps, I can also send a sample app for this.

  1. create a new app with abp cli. abp new Acme.BookStore -u none -csf
  2. then add Volo.Abp.TextTemplating.Razor module with cli. I added this inside application module but you can add it wherever you want. abp add-package Volo.Abp.TextTemplating.Razor
  3. Then create a model for the template I have added a model that takes a list. here it is.
public class Command
{
   
    public bool ReportIndicate { get; set; }

    public string CommandId { get; set; }
    public string MoCmd { get; set; }

    public MoSequence MoSequence { get; set; }


    public Command()
    {
    }

    public Command(bool reportIndicate, string commandId, string moCmd, MoSequence moSequence)
    {
        Check.NotNullOrWhiteSpace(commandId, nameof(commandId));
        Check.NotNullOrWhiteSpace(moCmd, nameof(moCmd));
        Check.NotNull(moSequence, nameof(moSequence));

        ReportIndicate = reportIndicate;
        CommandId = commandId;
        MoCmd = moCmd;
        MoSequence = moSequence;
    }


}
public class MoSequence
{
    public List<Mo> MoList { get; set; }


    public MoSequence()
    {
        MoList = new List<Mo>();
    }

    public void AddMo(string moPath, string moValue)
    {
        MoList.Add(new Mo(moPath, moValue));
    }

    public void AddMo(string moPath)
    {
        MoList.Add(new Mo(moPath));
    }
}

public class Mo
{
    public string MoPath { get; set; }

    public string MoValue { get; set; }

    public Mo()
    {

    }
    public Mo(string moPath)
    {
        MoPath = moPath;
    }

    public Mo(string moPath, string moValue)
    {
        MoPath = moPath;
        MoValue = moValue;
    }
}

  1. Now create a template for the command model. here you can find it.Add it as an embedded resource
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Acme.BookStore.Models.Command>
<srm:COMMAND>
    <srm:REPORT_INDICATE>@(Model.ReportIndicate ? "true" : "false")</srm:REPORT_INDICATE>
    <srm:COMMAND_ID>@Model.CommandId</srm:COMMAND_ID>
    <srm:MO_CMD>@Model.MoCmd</srm:MO_CMD>
    <srm:MO_SEQUENCE>
        @foreach (var item in Model.MoSequence.MoList)
        {
            <srm:MO>
                <srm:MO_PATH>@item.MoPath</srm:MO_PATH>
                @if (item.MoValue != null)
                {
                    <srm:MO_VALUE>@item.MoValue</srm:MO_VALUE>
                }
                else
                {
                    <srm:MO_VALUE />
                }
            </srm:MO>
        }
    </srm:MO_SEQUENCE>
</srm:COMMAND>
  1. Create a TemplateDefinitionProvider.
public class DemoTemplateDefinitionProvider : TemplateDefinitionProvider
{
    public override void Define(ITemplateDefinitionContext context)
    {
        context.Add(
            new TemplateDefinition("Demo") //template name: "Demo"
                .WithRazorEngine()
                .WithVirtualFilePath(
                    "/Template/Demo.cshtml", //template content path
                    isInlineLocalized: true
                )
        );
    }
}
  1. Add Configuration to your module
public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpAutoMapperOptions>(options =>
        {
            options.AddMaps<BookStoreApplicationModule>();
        });
        Configure<AbpRazorTemplateCSharpCompilerOptions>(options =>
        {
            options.References.Add(MetadataReference.CreateFromFile(typeof(BookStoreApplicationModule).Assembly.Location));
        });
        Configure<AbpVirtualFileSystemOptions>(options =>
        {
            options.FileSets.AddEmbedded<BookStoreApplicationModule>("Acme.BookStore");
        });
    }

7) Create an appservice that will return the output.

public class TestAppService : BookStoreAppService, ITestAppService
{
    private readonly ITemplateRenderer _templateRenderer;

    public TestAppService(ITemplateRenderer templateRenderer)
    {
        _templateRenderer = templateRenderer;
    }

    public async Task<string> GetOutput()
    {
        var moSequence = new MoSequence();
        moSequence.AddMo(".MO.MONITOR_OPERATION.BOOTSTRAP.DEV_CODE", "105");
        moSequence.AddMo(".MO.MONITOR_OPERATION.BOOTSTRAP.DEV_TYPE", "SPLAYER" + Random.Shared.Next().ToString());
        moSequence.AddMo(".MO.MONITOR_OPERATION.BOOTSTRAP.DEV_MDNM", "QB13R");
        var command = new Command(false,
            "31470ade5bc2fd42-60c7af5-4725-8703-33dd729f63cea7aa5e4fbc0e",
            ".MO.MONITOR_OPERATION.BOOTSTRAP",
            moSequence);
        var result = await _templateRenderer.RenderAsync(
           "Demo", //the template name
          command
       );
        return result;

    }
}
  1. Now you should be able to hit the endpoint. and it should return sth similar like this.
<srm:COMMAND>
    <srm:REPORT_INDICATE>false</srm:REPORT_INDICATE>
    <srm:COMMAND_ID>31470ade5bc2fd42-60c7af5-4725-8703-33dd729f63cea7aa5e4fbc0e</srm:COMMAND_ID>
    <srm:MO_CMD>.MO.MONITOR_OPERATION.BOOTSTRAP</srm:MO_CMD>
    <srm:MO_SEQUENCE>
            <srm:MO>
                <srm:MO_PATH>.MO.MONITOR_OPERATION.BOOTSTRAP.DEV_CODE</srm:MO_PATH>
                    <srm:MO_VALUE>105</srm:MO_VALUE>
            </srm:MO>
            <srm:MO>
                <srm:MO_PATH>.MO.MONITOR_OPERATION.BOOTSTRAP.DEV_TYPE</srm:MO_PATH>
                    <srm:MO_VALUE>SPLAYER737483572</srm:MO_VALUE>
            </srm:MO>
            <srm:MO>
                <srm:MO_PATH>.MO.MONITOR_OPERATION.BOOTSTRAP.DEV_MDNM</srm:MO_PATH>
                    <srm:MO_VALUE>QB13R</srm:MO_VALUE>
            </srm:MO>
    </srm:MO_SEQUENCE>
</srm:COMMAND>
  1. Now you need to call this endpoint with some frequency so you can see what is going on in memory. I have used dotMemory and k6 for it. here is my k6 .js file
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
    insecureSkipTLSVerify: true,
    noConnectionReuse: false,
    scenarios: {
        per_vu_scenario: {
            executor: "per-vu-iterations",
            vus: 5,
            iterations: 30,
            startTime: "0s",
            maxDuration: '2m',
        },
    },
};

export default function () {
    // Here, we set the endpoint to test.
    const response = http.get('https://localhost:44395/api/app/test/output');

    // An assertion
    check(response, {
        'is status 200': (x) => x.status === 200
    });

    sleep(3);
}

  1. if you run this js file from command line tool.
k6 run load.js

and check the memory spike in dotmemory you will see sth similar like this.

I analyze this with dotnet-dump and dotmemory. It has lots of free memory in unmanaged memory heap. mostly strings. I didn't check the source code. It could be sth wrong from caching so it caches the same thing in every request, but not sure, didn't deep dive into it.

If i switch to Volo.Abp.TextTemplating.Scriban, this doesn't happen and you can see the memory steady, no spikes. I switch to scriban and change all my templates with it. Hope i could clarify the problem if you need a sample app i can send it if you give an email address.

Hello, If you want, I can send you a sample app if you give me an email address ok here are the steps.

  1. Create the new project from abp cli abp new Doohlink -t app-pro -u angular -dbms PostgreSQL --separate-auth-server -m maui -csf

  2. Add Volo.FileManagement module (run the command inside aspnet-core folder) abp add-module Volo.FileManagement

  3. Arrange Postgres and Redis. Change appsettings.json according to that. (I use docker containers for that.)

  4. Run Dbmigrator.

  5. Run the Application (AuthServer and HttpApi.Host)

  6. do yarn install in angular app.

  7. Configure the angular app. Add Config Module and Feature Module.

  8. run angular app with yarn start

  9. now you should see file management menu item.

  10. upload an image.

  11. Then try to download that image.

  12. you will see an authentication error.

i hope this is enough information, as i say if you can not reproduce i can send you the sample app.

  • ABP Framework version: v7.4.0
  • UI Type: Angular
  • Database System: EF Core (SQL Server, Oracle, MySQL, PostgreSQL, etc..)

Hello, I recently upgraded my abp version from 7.3.2 to 7.4.0, I don't know if this is related with upgrade, maybe this wasn't working fine before.

In angular part of the gdpr module, when i look at the source code, it checks the cookie consent, if it is true it doesn't show the accept cookie bar as I understand.

here is the code for checking the cookie, here cookie key is '.AspNet.Consent'

 protected checkCookieConsent() {
    const hasCookieConsent =
      decodeURIComponent(this.document.cookie)
        .split(' ')
        .indexOf(this.cookieKey + '=true') > -1;

    if (hasCookieConsent) {
      this.showCookieConsent = false;
    }
  }

When i look at my cookies on chrome,I can see that it is true.

when i debug this, i can see that array from the code decodeURIComponent(this.document.cookie.split(' ') has an item like

here you can see indexOf shouldn't be .indexOf(this.cookieKey + '=true') but it should be .indexOf(this.cookieKey + '=true;') to make it work (';' semicolon at the end). I use chrome by the way. Maybe it works with other browsers, i didn't test it with others. Is there any trick that I can do to fix it fast?

  • ABP Framework version: v7.4.0
  • UI Type: Angular
  • Database System: EF Core (SQL Server, Oracle, MySQL, PostgreSQL, etc..)
  • Tiered (for MVC) or Auth Server Separated (for Angular): yes

Hello I recently upgraded to Abp v7.4.0, I am using file management module with abp, I customize it according to my needs and it was working fine. Now i have a problem with file download. I think there is a bug related with it. Since i customize it i couldn't be sure but I didn't override download part before, so it is likely from the new version.

Anyway here is the problem. For download to work first angular is doing a backend call to get a token. Then doing get request by using javascript window.open here is the file management module angular code.

  downloadFile(file: FileInfo) {
    return this.fileDescriptorService.getDownloadToken(file.id).pipe(
      tap((res) => {
        window.open(
          `${this.apiUrl}/api/file-management/file-descriptor/download/${file.id}?token=${res.token}`,
          '_self'
        );
      })
    );
  }

"this.fileDescriptorService.getDownloadToken" call is an authenticated but file-descriptor/download call is anonymous call.

On the backend side, when IDistributedCache is used it sets the token for the current tenant. so it normalizes the cache key. here is the code for it.

 public virtual async Task<DownloadTokenResultDto> GetDownloadTokenAsync(Guid id)
 {
     var token = Guid.NewGuid().ToString();

     await DownloadTokenCache.SetAsync(
         token,
         new FileDownloadTokenCacheItem { FileDescriptorId = id },
         new DistributedCacheEntryOptions
         {
             AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(60)
         });

     return new DownloadTokenResultDto
     {
         Token = token
     };
 }

here the current tenant id is set. So the problem is when you do the second call to /api/file-management/file-descriptor/download/ endpoint with window.open since it is anonymous and token is not set it doesn't get the current tenant id. here is the code for it.

 [AllowAnonymous]
 public virtual async Task<IRemoteStreamContent> DownloadAsync(Guid id, string token)
 {
     var downloadToken = await DownloadTokenCache.GetAsync(token);
     if (downloadToken == null || downloadToken.FileDescriptorId != id)
     {
         throw new AbpAuthorizationException("Invalid download token: " + token);
     }

     FileDescriptor fileDescriptor;
     using (DataFilter.Disable<IMultiTenant>())
     {
         fileDescriptor = await FileDescriptorRepository.GetAsync(id);
     }
     var stream = await BlobContainer.GetAsync(id.ToString());
     return new RemoteStreamContent(stream, fileDescriptor?.Name);
 }

here you get authorization exception since cache key is not normalized over here. even if i override and change the current tenant id to null, it gets the token but this time BlobContainer can not find the file since this file belongs to tenant. What i came up with is to send tenantId from user interface as a query string and override FileDescriptorController like this.

also i override the angular part and inject the new service as a download service. sth like

@Injectable()
export class CreativeDownloadService {
  apiName = 'FileManagement';

  get apiUrl() {
    return this.environment.getApiUrl(this.apiName);
  }

  constructor(
    private restService: RestService,
    private fileDescriptorService: FileDescriptorService,
    private environment: EnvironmentService,
    private configStateService: ConfigStateService
  ) {}

  downloadFile(file: FileInfo) {
    const currentUser = this.configStateService.getOne("currentUser"); 
    return this.fileDescriptorService.getDownloadToken(file.id).pipe(
      tap((res) => {
        window.open(
          `${this.apiUrl}/api/file-management/file-descriptor/download/${file.id}?token=${res.token}&__tenant=${currentUser?.tenantId}`,
          '_self'
        );
      })
    );
  }
}

and then provide it.

I don't know how this was working before. I wonder if new version changed sth, by the way i use redis cache. Also sth I didn't understand is according to docs

giving query string __tenant should set CurrentTenant but it doesn't do so. Is this related with [AllowAnonymous] attribute? Thanks for the help and waiting for your reply.

Thank you for the help.

Hello, I didn't understand how i can pass the authorization header while i am redirecting the user from angular. Is there any sample code how to do that? I would be happy if you can share it. I fixed the problem by implementing another authentication schema, so in that way i can challenge the user to authenticate and when it is done i can sign in the user and redirect the user to the page. sth like this.

 context.Services.AddAuthentication(options =>
        {
            options.DefaultScheme = "DoohlinkCustomPolicy";
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddPolicyScheme("DoohlinkCustomPolicy", "DoohlinkCustomPolicy", options =>
        {
            options.ForwardDefaultSelector = context =>
            {
                string authorization = context.Request.Headers[HeaderNames.Authorization];
                if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
                    return JwtBearerDefaults.AuthenticationScheme;
                else
                    return CookieAuthenticationDefaults.AuthenticationScheme;
            };
        })
        .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
        {
            options.Authority = configuration["AuthServer:Authority"];
            options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
            options.Audience = "Doohlink";
        })
        .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
         {
             options.ExpireTimeSpan = TimeSpan.FromDays(365);
         })
        .AddAbpOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
        {
            options.Authority = configuration["AuthServer:Authority"];
            options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
            options.ResponseType = OpenIdConnectResponseType.Code;

            options.ClientId = configuration["AuthServer:WebClientId"];
            options.ClientSecret = configuration["AuthServer:WebClientSecret"];

            options.UsePkce = true;
            options.SaveTokens = true;
            options.GetClaimsFromUserInfoEndpoint = true;

            options.Scope.Add("roles");
            options.Scope.Add("email");
            options.Scope.Add("phone");
            options.Scope.Add("Doohlink");
            options.Events.OnTicketReceived = async (TicketReceivedContext e) =>
            {
                await e.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
                    e.Principal,
                    new AuthenticationProperties
                    {
                        IsPersistent = true,
                        AllowRefresh = true,
                        ExpiresUtc = DateTime.UtcNow.AddDays(1)
                    });
            };
        });

and challenging the user to login in a controller.

public ActionResult Login()
    {
        return Challenge(new AuthenticationProperties { RedirectUri = "/hangfire" }, OpenIdConnectDefaults.AuthenticationScheme);
    }

this works for now. But I would happy to know how to do it from angular. Because as i know when you do redirect from client side like. window.open() you can not pass authorization header. Or maybe I am wrong?

Hello again, Thanks for the explanation, can i ask how can i do the same for hangfire dashboard? If i write a mvc controller action, how can i redirect the user to auth server log in page and then redirect to hangfire dashboard?

  • ABP Framework version: v7.1.0
  • UI type: Angular
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): yes

Hello, I am trying to implement hangfire to my project. I managed to do that, the only problem that i have is with hangfire dashboard. If i do not use authentication for my hangfire dashboard it works fine. And i can access it. But when i have enabled the filter, i can not access the page. In HttpContext i can not see the user claims, so it return unauthenticated inside AbpHangfireAuthorizationFilter.

I logged in with admin rights and when i call a method from swagger endpoint i can see that claims are there for user and CurrentUser.IsAuthenticated property returns true. I couldn't understand why current user is unauthenticated (or claims are not in the context) when AbpHangfireAuthorizationFilter is triggered. any tips about it?

here is the onApplicationInitialization

  public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var app = context.GetApplicationBuilder();
        var env = context.GetEnvironment();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseAbpRequestLocalization();
        app.UseStaticFiles();
        app.UseAbpSecurityHeaders();
        app.UseRouting();
        app.UseCors();
        app.UseAuthentication();

        if (MultiTenancyConsts.IsEnabled)
        {
            app.UseMultiTenancy();
        }

        app.UseAuthorization();
        app.UseSwagger();
        app.UseAbpSwaggerUI(options =>
        {
            options.SwaggerEndpoint("/swagger/v1/swagger.json", "Doohlink API");

            var configuration = context.GetConfiguration();
            options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
        });
        app.UseAuditing();
        app.UseAbpSerilogEnrichers();
        app.UseUnitOfWork();
        app.UseHangfireDashboard("/hangfire", new DashboardOptions
        {
            AsyncAuthorization = new[] { new DoohlinkAuthorizationFilter() }
        });
        app.UseConfiguredEndpoints();

    }

ps: to see the httpcontext I have changed AbpHangfireAuthorizationFilter to custom class inside my project here is DoohlinkAuthorizationFilter

public class DoohlinkAuthorizationFilter : IDashboardAsyncAuthorizationFilter
{
    private readonly bool _enableTenant;
    private readonly string _requiredPermissionName;

    public DoohlinkAuthorizationFilter(bool enableTenant = false, string requiredPermissionName = null)
    {
        _enableTenant = requiredPermissionName.IsNullOrWhiteSpace() ? enableTenant : true;
        _requiredPermissionName = requiredPermissionName;
    }

    public async Task<bool> AuthorizeAsync(DashboardContext context)
    {
        if (!IsLoggedIn(context, _enableTenant))
        {
            return false;
        }

        if (_requiredPermissionName.IsNullOrEmpty())
        {
            return true;
        }

        return await IsPermissionGrantedAsync(context, _requiredPermissionName);
    }

    private static bool IsLoggedIn(DashboardContext context, bool enableTenant)
    {
        var currentUser = context.GetHttpContext().RequestServices.GetRequiredService<ICurrentUser>();

        if (!enableTenant)
        {
            return currentUser.IsAuthenticated && !currentUser.TenantId.HasValue;
        }

        return currentUser.IsAuthenticated;
    }

    private static async Task<bool> IsPermissionGrantedAsync(DashboardContext context, string requiredPermissionName)
    {
        var permissionChecker = context.GetHttpContext().RequestServices.GetRequiredService<IPermissionChecker>();
        return await permissionChecker.IsGrantedAsync(requiredPermissionName);
    }
}

and this is httpcontext quickwatch

Thanks a lot, i have seen the fix now, I am closing the issue then.

Hello @liangshiwei. As you mention it works when you await, probably when i try to await i did sth wrong. It was late at night :) Thank you for sharing the issue link with me So it seems, collection has been updated from two threads. And collection that is updated in Volo.Abp.Uow.UnitOfWorkExtensions.GetOrAddItem is not thread safe. Maybe changing that to ConcurrentDictionary can help. https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2?view=net-5.0

Showing 131 to 140 of 166 entries
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.0.0-preview. Updated on September 18, 2025, 07:10