Activities of "cstobler"

When I run DbMigrator locally, I am receiving an exception:

I have never encountered this before. I suspect something is misconfigured or a reference is missing, but I have no idea what it might be.

I am going to email my project files over in case you need to take a look.

Thanks

I am trying to override the Account template based on this documentation here: https://dev.to/enisn/you-do-it-wrong-customizing-abp-login-page-correctly-l2k, and when I get to the part where I need to download the Account template so I can override it, I am not able to download it:

I have updated ABP CLI, I have updated ABP Studio, I have logged out and logged back in in the CLI, nothing seems to work. It keeps throwing an error, and the error message doesn't print out any useful information that helps me troubleshoot what is going on.

Additionally, I read somewhere that I can grab source files from ABP Suite, but ABP Suite isn't working for me at all. I go to add my solution, and it throws this:

I would ideally like to get this fixed so I can download files via CLI, but worst case scenario I would be ok if you just zipped it up and sent it to my email on file.

Thanks,

Charlie

Hi, I am attempting to setup self-service registration and subscription for customers. I am having an issue where when I try to go to the payment screen, it throws an error saying "Invalid URL", and when I go to Stripe, it shows a relative URL that I cannot seem to adjust.

For context, here is my register page model OnPostAsync:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        await OnGetAsync(); // reload editions
        return Page();
    }

    // Confirm we are NOT in tenant context
    if (_currentTenant.Id != null)
    {
        throw new Exception("Cannot register a tenant while already in a tenant context.");
        //return Forbid(); // Registration should only be done as host
    }

    StartSubscriptionResultDto resultDto = await _multiTenancyAppService.RegisterAndSubscribeAsync(new SaasTenantCreateDto
    {
        Name = Input.TenantName,
        AdminEmailAddress = Input.Email,
        AdminPassword = Input.Password,
        EditionId = Input.EditionId,
        ActivationState = Volo.Saas.TenantActivationState.Passive
    });

    return LocalRedirectPreserveMethod("/Payment/GatewaySelection?paymentRequestId=" + resultDto.PaymentRequestId);
}

And here is my RegisterAndSubscribeAsync method:

public async Task<StartSubscriptionResultDto> RegisterAndSubscribeAsync(SaasTenantCreateDto input)
{
    if (input.EditionId == null || input.EditionId == Guid.Empty)
    {
        throw new UserFriendlyException("Please select a valid edition.");
    }

    // 1) Create tenant via domain layer (no host permission needed)
    var tenant = await _tenantManager.CreateAsync(input.Name, editionId: input.EditionId);

    tenant.SetActivationState(input.ActivationState); // keep passive until payment succeeds
    await _tenantRepository.InsertAsync(tenant, autoSave: true);

    // 2) Publish TenantCreatedEto to seed admin user (same as TenantAppService does)
    await _eventBus.PublishAsync(new TenantCreatedEto
    {
        Id = tenant.Id,
        Name = tenant.Name,
        Properties =
    {
        {"AdminEmail", input.AdminEmailAddress},
        {"AdminPassword", input.AdminPassword}
    }
    });

    // 3) Start subscription (creates PaymentRequest with TenantId/EditionId extra props)
    PaymentRequestWithDetailsDto paymentRequest = await _subscriptionAppService.CreateSubscriptionAsync(input.EditionId ?? Guid.Empty, tenant.Id);

    return new StartSubscriptionResultDto
    {
        TenantId = tenant.Id,
        PaymentRequestId = paymentRequest.Id,
    };
}

I have tried to set the callbackurl, prepaymenturl, and postpaymenturl in appsettings, but that doesn't seem to do anything (stripe keys redacted for security):

"PaymentWebOptions": {
  "RootUrl": "https://armadasoftware.io",
  "CallBackUrl": "https://armadasoftware.io/Payment/Stripe/PostPayment",
  "PaymentGatewayWebConfigurationDictionary": {
    "Stripe": {
      "PrePaymentUrl": "https://armadasoftware.io/Payment/Stripe/PrePayment",
      "PostPaymentUrl": "https://armadasoftware.io/Payment/Stripe/PostPayment"
    }
  }
},
"Payment": {
  "Stripe": {
    "PublishableKey": "",
    "SecretKey": "",
    "WebhookSecret": ""
  }
},

When I get this error, I go to Stripe Webhook logs, and I find this: So it is saying the URL is invalid, and the success URL is a relative URL, which seems problematic, but I cannot seem to find a configuration or anything in the documentation that allows me to set a success URL such that it overrides this. I tried setting the success url in the stripe section of appsettings, but that didn't work either. Please advise me on how to set this so it overrides whatever default URL is being sent to Stripe here.

I have been working off of this documentation: https://abp.io/docs/latest/solution-templates/layered-web-application/deployment/azure-deployment/step3-deployment-github-action?UI=MVC&DB=EF&Tiered=No, and it does work to deploy to Azure, but if I have the line there to generate openiddict.pfx, it always fails, and it doesn't throw any error message that helps me understand what is going on.

For reference, here is my workflow (I also have never been able to make the DbMigrator work, and I may have to open a new ticket for that too if I can't get it to work):

# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions

name: Build and deploy ASP.Net Core app to Azure Web App - armada

on:
  push:
    branches:
      - Armada-ABP-Pro
  workflow_dispatch:

jobs:
  build:
    runs-on: windows-latest
    permissions:
      contents: read #This is required for actions/checkout

    steps:
      - uses: actions/checkout@v4

      - name: Set up .NET Core
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 'v9.0'

      - name: Install ABP CLI
        run: |
          dotnet tool install -g Volo.Abp.Cli
          abp install-libs
        shell: bash

      - name: Build with dotnet
        run: dotnet build --configuration Release

      - name: dotnet publish
        run: dotnet publish -c Release -r win-x64 --self-contained false -o "$env:DOTNET_ROOT\myapp"
        shell: pwsh
        working-directory: ./src/ArmadaIO.Web # Replace with your project name
        env:
          ASPNETCORE_ENVIRONMENT: Production

      - name: Generate openiddict.pfx
        run: dotnet dev-certs https -v -ep ${{env.DOTNET_ROOT}}\myapp\openiddict.pfx -p c41eb3e7-8a8e-429f-9052-0850406f2f11 # Replace with your password

      - name: Upload artifact for deployment job
        uses: actions/upload-artifact@v4
        with:
          name: .net-app
          path: ${{env.DOTNET_ROOT}}/myapp

  deploy:
    runs-on: windows-latest
    needs: build
    environment:
      name: 'Production'
      url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
    permissions:
      id-token: write #This is required for requesting the JWT
      contents: read #This is required for actions/checkout

    steps:
      - name: Download artifact from build job
        uses: actions/download-artifact@v4
        with:
          name: .net-app
      
      - name: Login to Azure
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_D0E4A82237114C8FB52A40680A31F7B7 }}
          tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_AF7C7E6475CD4DE9A6FC1907FEBD8DF6 }}
          subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_9000905F725645769C73D60324653A76 }}

      - name: Deploy to Azure Web App
        id: deploy-to-webapp
        uses: azure/webapps-deploy@v3
        with:
          app-name: 'armada'
          slot-name: 'Production'
          package: .

If I comment out the openiddict line, it runs and deploys (but ofc doesn't actually run and throws a startup error saying it can't find openiddict.pfx). If I uncomment the line, it just shows me this: and if I pull up the raw logs for that line it shows me this:

2025-08-20T20:23:04.5490958Z ##[group]Run dotnet dev-certs https -v -ep C:\Program Files\dotnet\myapp\openiddict.pfx -p [mypassword]
2025-08-20T20:23:04.5491858Z dotnet dev-certs https -v -ep C:\Program Files\dotnet\myapp\openiddict.pfx -p [mypassword]
2025-08-20T20:23:04.5526676Z shell: C:\Program Files\PowerShell\7\pwsh.EXE -command ". '{0}'"
2025-08-20T20:23:04.5526989Z env:
2025-08-20T20:23:04.5527153Z   DOTNET_ROOT: C:\Program Files\dotnet
2025-08-20T20:23:04.5527379Z ##[endgroup]
2025-08-20T20:23:05.1704144Z Specify --help for a list of available options and commands.
2025-08-20T20:23:05.3106943Z ##[error]Process completed with exit code 1.

So I feel stuck here. I did find this blog post: https://codejack.com/2022/12/deploying-abp-io-to-an-azure-appservice/, and I used it for testing, but that didn't work for me either. Here is my WebModule with the default code, as well as the commented code that I was using to test based on the blog post above:

public override void PreConfigureServices(ServiceConfigurationContext context)
{
    var hostingEnvironment = context.Services.GetHostingEnvironment();
    var configuration = context.Services.GetConfiguration();

    context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
    {
        options.AddAssemblyResource(
            typeof(ArmadaResource),
            typeof(ArmadaIODomainModule).Assembly,
            typeof(ArmadaIODomainSharedModule).Assembly,
            typeof(ArmadaIOApplicationModule).Assembly,
            typeof(ArmadaIOApplicationContractsModule).Assembly,
            typeof(ArmadaIOWebModule).Assembly
        );
    });

    PreConfigure<OpenIddictBuilder>(builder =>
    {
        builder.AddValidation(options =>
        {
            options.AddAudiences("ArmadaIO");
            options.UseLocalServer();
            options.UseAspNetCore();
        });
    });

    if (!hostingEnvironment.IsDevelopment())
    {
        PreConfigure<AbpOpenIddictAspNetCoreOptions>(options =>
        {
            options.AddDevelopmentEncryptionAndSigningCertificate = false;
        });

        PreConfigure<OpenIddictServerBuilder>(serverBuilder =>
        {
            serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!);
            serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));

            //serverBuilder.AddEncryptionCertificate(GetEncryptionCertificate(hostingEnvironment, configuration));
            //serverBuilder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration));
        });
    }
}

//private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv,
//                        IConfiguration configuration)
//{
//    var fileName = $"cert-signing.pfx";
//    var passPhrase = configuration["ArmadaCertificate:X590:PassPhrase"];
//    var file = Path.Combine(hostingEnv.ContentRootPath, fileName);
//    if (File.Exists(file))
//    {
//        var created = File.GetCreationTime(file);
//        var days = (DateTime.Now - created).TotalDays;
//        if (days > 180)
//        {
//            File.Delete(file);
//        }
//        else
//        {
//            try
//            {
//                return new X509Certificate2(file, passPhrase, X509KeyStorageFlags.MachineKeySet);
//            }
//            catch (CryptographicException)
//            {
//                File.Delete(file);
//            }
//        }
//    }
//    // file doesn't exist, was deleted because it expired, or has an invalid passphrase
//    using var algorithm = RSA.Create(keySizeInBits: 2048);
//    var subject = new X500DistinguishedName("CN=Armada Signing Certificate");
//    var request = new CertificateRequest(subject, algorithm,
//                        HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
//    request.CertificateExtensions.Add(new X509KeyUsageExtension(
//                        X509KeyUsageFlags.DigitalSignature, critical: true));
//    var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow,
//                        DateTimeOffset.UtcNow.AddYears(2));
//    File.WriteAllBytes(file, certificate.Export(X509ContentType.Pfx, passPhrase));
//    return new X509Certificate2(file, passPhrase, X509KeyStorageFlags.MachineKeySet);
//}
//private X509Certificate2 GetEncryptionCertificate(IWebHostEnvironment hostingEnv,
//                             IConfiguration configuration)
//{
//    var fileName = $"cert-encryption.pfx";
//    var passPhrase = configuration["ArmadaCertificate:X590:PassPhrase"];
//    var file = Path.Combine(hostingEnv.ContentRootPath, fileName);
//    if (File.Exists(file))
//    {
//        var created = File.GetCreationTime(file);
//        var days = (DateTime.Now - created).TotalDays;
//        if (days > 180)
//        {
//            File.Delete(file);
//        }
//        else
//        {
//            try
//            {
//                return new X509Certificate2(file, passPhrase, X509KeyStorageFlags.MachineKeySet);
//            }
//            catch (CryptographicException)
//            {
//                File.Delete(file);
//            }
//        }
//    }
//    // file doesn't exist, was deleted because it expired, or has an invalid passphrase
//    using var algorithm = RSA.Create(keySizeInBits: 2048);
//    var subject = new X500DistinguishedName("CN=Armada Encryption Certificate");
//    var request = new CertificateRequest(subject, algorithm,
//                        HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
//    request.CertificateExtensions.Add(new X509KeyUsageExtension(
//                        X509KeyUsageFlags.KeyEncipherment, critical: true));
//    var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow,
//                        DateTimeOffset.UtcNow.AddYears(2));
//    File.WriteAllBytes(file, certificate.Export(X509ContentType.Pfx, passPhrase));
//    return new X509Certificate2(file, passPhrase, X509KeyStorageFlags.MachineKeySet);
//}

I was able to get this working one time in the past, but I can't seem to recreate it unfortunately. I did create an openiddict.pfx using the command in the terminal, then I uploaded it to my Azure certificates. That doesn't seem to work either. Perhaps I need to upload openiddict.pfx directly into the project root in Azure? I am not very knowledgeable about this, so I'm not sure what is safe or what could cause security issues.

Any advice here would be appreciated. Thanks.

I have an open ticket for this (#9614), but I haven't received help so I am opening another to see if I can get help here and maybe get refunded for that ticket.

I created a custom registration page, which I am planning on using to allow customers to sign up themselves, creating a tenant and signing up for an edition at the same time.

Here is my page model for my registration page that will handle creating the tenant and subscribing the edition to that tenant:

[AllowAnonymous]
public class RegisterModel : ArmadaPageModel
{
    private readonly ITenantAppService _tenantAppService;
    private readonly IMultiTenancyAppService _multiTenancyAppService;
    private readonly IEditionAppService _editionAppService;
    private readonly ISubscriptionAppService _subscriptionAppService;
    private readonly ICurrentTenant _currentTenant;

    public RegisterModel(
        ITenantAppService tenantAppService,
        IMultiTenancyAppService multiTenancyAppService,
        IEditionAppService editionAppService,
        ISubscriptionAppService subscriptionAppService,
        ICurrentTenant currentTenant)
    {
        _tenantAppService = tenantAppService;
        _multiTenancyAppService = multiTenancyAppService;
        _editionAppService = editionAppService;
        _subscriptionAppService = subscriptionAppService;
        _currentTenant = currentTenant;
    }

    [BindProperty]
    public RegisterTenantViewModel Input { get; set; } = new();

    public async Task OnGetAsync()
    {
        List<EditionDto> editions = await _multiTenancyAppService.GetEditionsAsync();
        List<SelectListItem> editionSelectItems = editions.Select(e => new SelectListItem
        {
            Text = e.DisplayName,
            Value = e.Id.ToString()
        }).ToList();

        Input.AvailableEditions = editionSelectItems;
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            await OnGetAsync(); // reload editions
            return Page();
        }

        // Confirm we are NOT in tenant context
        if (_currentTenant.Id != null)
        {
            throw new Exception("Cannot register a tenant while already in a tenant context.");
            //return Forbid(); // Registration should only be done as host
        }

        //var tenantDto = await _tenantAppService.CreateAsync(new SaasTenantCreateDto
        //{
        //    Name = Input.TenantName,
        //    AdminEmailAddress = Input.Email,
        //    AdminPassword = Input.Password,
        //    EditionId = Input.EditionId
        //});

        var tenantDto = await _multiTenancyAppService.CreateTenantAsync(new SaasTenantCreateDto
        {
            Name = Input.TenantName,
            AdminEmailAddress = Input.Email,
            AdminPassword = Input.Password,
            EditionId = Input.EditionId
        });

        // 2. Manually switch to new tenant context
        using (_currentTenant.Change(tenantDto.Id))
        {
            // 3. Create subscription with edition ID
            var subscription = await _subscriptionAppService.CreateSubscriptionAsync(
                Input.EditionId,
                tenantDto.Id
            );

            // 4. Redirect to Stripe or confirmation
            //return Redirect(subscription.PaymentUrl);
        }

        return Page();
    }
}

I also created an appservice called MultiTenancyAppService which helps me populate the editions dropdown, as well as attempt to create a tenant (since calling tenantAppService.CreateAsync() was throwing an authorization error):

[AllowAnonymous]
public class MultiTenancyAppService : ArmadaIOAppService, IMultiTenancyAppService
{
    private readonly IRepository<Edition, Guid> _editionRepository;
    private readonly ITenantAppService _tenantAppService;
    private readonly IRepository<Tenant, Guid> _tenantRepository;

    public MultiTenancyAppService(IRepository<Edition, Guid> editionRepository, ITenantAppService tenantAppService, IRepository<Tenant, Guid> tenantRepository)
    {
        _editionRepository = editionRepository;
        _tenantAppService = tenantAppService;
        _tenantRepository = tenantRepository;
    }

    public async Task<List<EditionDto>> GetEditionsAsync()
    {
        List<Edition> editions = await _editionRepository.GetListAsync();
        List<EditionDto> dtos = ObjectMapper.Map<List<Edition>, List<EditionDto>>(editions);
        return dtos;
    }

    public async Task<SaasTenantDto> CreateTenantAsync(SaasTenantCreateDto input)
    {
        if (string.IsNullOrWhiteSpace(input.Name) || string.IsNullOrWhiteSpace(input.AdminEmailAddress) || string.IsNullOrWhiteSpace(input.AdminPassword))
        {
            throw new UserFriendlyException("Please fill all required fields before submission");
        }
        
        return await _tenantAppService.CreateAsync(input);
    }
}

It is still throwing a "Exception of type 'Volo.Abp.Authorization.AbpAuthorizationException' was thrown" error on the "await _tenantAppService.CreateAsync(input);" line when trying to create a tenant, even though I have the [AllowAnonymous] decorator on the page model and the app service. I am under the assumption that by default, a register page is creating a tenant when in host mode, since a tenant isn't logged in, so this seems like unintuitive functionality. I'm assuming part of this is because the page itself is accessible to non-tenant users, but that is necessary figuring the only people registering will be those who are not yet tenants.

Please advise me on a potential solution to this.

Charlie

I am hoping to get a little knowledge to piece together some of these modules into a cohesive unit. I have all of the modules installed, and I followed the documentation to add a Stripe webhook, product and price, and I then used that to configure my StripeOptions. I also setup an Plan, GatewayPlan, and Edition, so theoretically, once I have a page set up to allow customers to subscribe to an edition, it should work. However, I have a few questions that don't seem to be elucidated in the documentation:

  1. In this example code in the documentation: var paymentRequest = await SubscriptionAppService.CreateSubscriptionAsync(editionId, CurrentTenant.GetId());, it seems to imply that the tenant already needs to be a tenant and be signed in to be able to subscribe to an edition. This seems confusing to me. I need my users to be able to sign up for an edition at the same time they sign up to be a tenant (it doesn't make sense to have a tenant without an edition assigned to them). Would I just do this all at the same time?

  2. Related to 1, I don't have a use for the "host" section of the app, and really only need the app accessible via the admin and tenants. Is there a way to "disable" the host (non-tenant) functionality and just force the user to login or register before being redirected to the homepage? Unless host does something else I am unaware of, but I don't want users to be able to access anything without being logged in.

I am stuck on these at the moment, so any advice on how to structure this would be greatly appreciated.

Charlie

When execution reaches my 'throw new UserFriendlyException("string")' line, it throws an actual exception rather than showing as a dialog as it used to do before i upgraded my project to Pro and .NET 9:

I assume there is some misconfiguration here, but I can't find it in any documentation online. Please show me how I can get back to having this display to the user in a dialog.

I just upgraded to Pro and also just upgraded to .NET 9. I went to run my Web project and ran into this: I had updated that line based on this documentation: https://abp.io/docs/latest/release-info/migration-guides/abp-9-0. The intellisense shows it as a nullable argument, so I'm not sure why it is throwing this error.

If you need to see my project, I uploaded it for another ticket (9542) via email.

Thanks in advance for any help you can provide,

Charlie

Not sure of ABP version (can't find solution configuration in ABP Studio (along with other features documentation says I am supposed to have, could be related to other issues below)

  • Exception message and full stack trace:
    • Exception thrown: Microsoft.Data.SqlClient.SqlException: 'Invalid object name 'SaasTenants'. This exception was originally thrown at this call stack: [External Code] Armada.Data.ArmadaDbMigrationService.MigrateAsync() in ArmadaDbMigrationService.cs
    • This threw an exception directly on the line "var tenants = await _tenantRepository.GetListAsync(includeDetails: true);" and stopped execution.
  • Steps to reproduce the issue:
    • Set DbMigrator as startup project
    • Run DbMigrator
    • Get stopped with error above
  • Explanation:
    • I think something might have gone wrong when I went to migrate my project from open source to pro. First, I wasn't able to find the "Upgrade to Pro" button in ABP Studio, so I just called "abp upgrade -t app". This threw some errors and warnings in the console, but when I checked, it appeared that the changes were made to the project. However, I think some things weren't executed properly in the upgrade.
    • First, there were a lot of errors, most of which were because it installed the 4.2.1 version of LeptonX, but all of the other modules were on v8.x.x, which seemed to be causing issues. I then went into NuGet for each project and updated all of the Volo packages to v9.2.1, and that seemed to resolve those errors. I also resolved various errors that were expected (breaking changes due to the ABP upgrade or .NET upgrade).
    • With all of the errors resolved, I went to run my DbMigrator to make sure that the database was up-to-date, at which point I was presented with the error above. I checked my migrations directory and found that there were no new migrations present, and when I checked my tables in SSMS, there were no Saas tables as I was expecting to see. I do believe my DbContext.cs is configured correctly. I did have to make a few adjustments based on this article: https://abp.io/docs/commercial/latest/migrating-from-open-source?DB=EF (which further leads me to believe that something went wrong in the upgrade), but DbContext does contain everything that article lists except builder.ConfigureOpenIddictPro(), which is shown as not existing, so I have defaulted to builder.ConfigureOpenIddict() (probably separate issue but also concerning).
    • At this point, I'm not sure what to do. I tried upgrading the project again, but that doesn't do anything. I know I need those Saas tables in the database, but I don't know how to force a migration since the migration doesn't even exist under Migrations and trying to run DbMigrator just returns the above error.
    • Please let me know what additional information you need to help troubleshoot this. Any help would be greatly appreciated!

Charlie

Showing 1 to 9 of 9 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.1.0-preview. Updated on October 30, 2025, 06:33