Base solution for your next web application

Activities of "Web2workNL"

Hi, thanks for preparing a blog post and a sample project. However, the link you sent leads to a 404 page.

Thank you for the response. I tried the solution. 2 issues:

  1. The IServiceCollection does not contain a definition for AddApplication on builder.Services.AddApplication<MyFunctionsModule>().
  2. The type or namespace IAbpApplicationWithExternalServiceProvider could not be found on serviceProvider.GetRequiredService<IAbpApplicationWithExternalServiceProvider>().Initialize(serviceProvider);.

After further debugging, I discovered that moving all the Initialize() into the function it allows it to start properly. The function project is runnig the In-process model. When using the Initialize() in my startup.cs, I get the following error: Cannot access a disposed object. Object name: 'IServiceProvider'.

If using the Initialize() inside the function you can successfully execute a test method from my controller inside the Core project. However, when I run my method with a UnitOfWorkManager, I encounter the following error: System.Private.CoreLib: Exception while executing function: MyFunction. Castle.Windsor: Component Abp.Domain.Uow.UnitOfWorkDefaultOptions could not be registered. There is already a component with that name. Did you want to modify the existing component instead? If not, make sure you specify a unique name.

My Function:

public class MyFunction
{
    private readonly ILogger<MyFunction> _logger;

    public MyFunction(ILogger<MyFunction> logger)
    {
        _logger = logger;
    }

    [FunctionName(nameof(MyFunction))]
    public async Task Run([QueueTrigger("my-queue", Connection = "myConnString")] QueueMessage message)
    {
        using (var bootstrapper = AbpBootstrapper.Create<MyFunctionModule>())
        {
            // initialize here or in startup.cs - performance for starting function?
            bootstrapper.Initialize();

            using (var myCoreController = bootstrapper.IocManager.ResolveAsDisposable<IMyCoreController>())
            {
                // works fine
                var test = await myCoreController.Object.Test();
                _logger.LogInformation(test);

                // generates error:
                await myCoreController.Object.MyMethod();

            }
        }
    }
}

Startup: (if used, it fails to start function)

[assembly: FunctionsStartup(typeof(Startup))]

namespace MyFunctionsNameSpace
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            using (var bootstrapper = AbpBootstrapper.Create<MyFunctionModule>())
            {
                bootstrapper.Initialize();
            }
        }
    }
}

MyMethod - inside MyCoreController

private async Task MyMethod()
{
    using (var uow = UnitOfWorkManager.Begin())
    {
        // code fails on first call the repository

        ...
        
        uow.Complete();
    }
}

MyFunctionModule

[DependsOn(typeof(MyProjectEntityFrameworkCoreModule))]
public class MyFunctionModule: AbpModule
{
    private readonly IConfigurationRoot _appConfiguration;

    public MyFunctionModule(MyProjectEntityFrameworkCoreModule abpZeroTemplateEntityFrameworkCoreModule)
    {
        abpZeroTemplateEntityFrameworkCoreModule.SkipDbSeed = true;

        _appConfiguration = AppConfigurations.Get(
            typeof(MyFunctionModule).GetAssembly().GetDirectoryPathOrNull()
        );
    }

    public override void PreInitialize()
    {
        Configuration.DefaultNameOrConnectionString = _appConfiguration.GetConnectionString("...");
        Configuration.Modules.AspNetZero().LicenseCode = _appConfiguration["..."];
    }

    public override void Initialize()
    {
        IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
        ServiceCollectionRegistrar.Register(IocManager);
    }
}

Hi,

I've been trying to get my Azure Function (project is running Abp 9.2.2) to work, but I'm encountering some issues.

I followed the code example above, but I ran into a couple of problems. The _bootstrapper.IocManager.Resolve<>() method requires the arguments to be explicitly specified, as does the AbpBootstrapper.Create() method. For AbpBootstrapper.Create(), I assume you can use your own ProjectModule : AbpModule.

When I implement the standard approach for creating Console application projects (similar to the Migrator project), I encounter dependency injection errors related to IServiceProvider or IUnitOfWorkManager.

Is there an example Azure Functions project available, or could you provide guidance on how to resolve these issues?

Thanks.

We're experiencing problems with the UserTokenExpirationWorker. We are running ABP 7.2.1 This is the Call Stack:

Microsoft.Data.SqlClient.SqlException (0x80131904): The connection is broken and recovery is not possible. The connection is marked by the server as unrecoverable. No attempt was made to restore the connection. at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action 1 wrapCloseInAction) at Microsoft.Data.SqlClient.SqlConnection.ValidateAndReconnect(Action beforeDisconnect, Int32 timeout) at Microsoft.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean isAsync, Int32 timeout, Boolean asyncWrite) at Microsoft.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource 1 completion, Boolean sendToPipe, Int32 timeout, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry, String methodName) at Microsoft.Data.SqlClient.SqlCommand.ExecuteNonQuery() at Microsoft.EntityFrameworkCore.Storage.RelationalTransaction.CreateSavepoint(String name) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable 1 commandBatches, IRelationalConnection connection) at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IList 1 entries) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList 1 entriesToSave) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(DbContext _, Boolean acceptAllChangesOnSuccess) at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func 3 operation, Func 3 verifySucceeded) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess) at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess) at Abp.EntityFrameworkCore.AbpDbContext.SaveChanges() at Abp.Zero.EntityFrameworkCore.AbpZeroCommonDbContext 3.SaveChanges() at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesInDbContext(DbContext dbContext) at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChanges() at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.CompleteUow() at Abp.Domain.Uow.UnitOfWorkBase.Complete() at Abp.Authorization.Users.UserTokenExpirationWorker 2.DoWork() at Abp.Threading.BackgroundWorkers.PeriodicBackgroundWorkerBase.Timer_Elapsed(Object sender, EventArgs e) ClientConnectionId:022efa77-2eb9-4ee0-ac08-47d0cc25d538

Our normal Timer Jobs do not have the same issue, nor do the Background Jobs that are being kicked off through the AbpBackgroundJobs table

An alternative solution would be to make our own TimerJob that has the same purpose, clean up expired user tokens, but I am unable to find a way to completely kill the UserTokenExpirationWorker from abp Zero. Most I can do is set Configuration.BackgroundJobs.UserTokenExpirationPeriod = TimeSpan.FromDays(24); as apparently it is being parsed to Int32 somewhere and a TimeSpan for 24.8 days is the most that can fit in there.

Please offer assistance with either fixing the UserTokenExpirationWorker itself, or finding a way for us to completely disable this worker.

thanks for the hint. solution implemented

product version : Abp.AspNetCore 6.6.1 product type: Angular framework type: .net core

I woudl like to add caching for AbpUsers table. please advice a proper method to do this

product version : Abp.AspNetCore 6.6.1 product type: Angular framework type: .net core

our applicaiton That is build on ABP framework makes useless requests to database and this implies unneded costs for our database that is HOsted in Cloud.

for last month we ahve over 100 millions requests made to tables [AbpUserOrganizationUnits] [AbpOrganizationUnitRoles] that we do not use and have no data there.

i need a to find a way to disable this requests as it Brings aditional cost to MSSQL server hosting and has no value for our product

please advice how this can be acheived

Thanks in advance Regards

Thanks for the suggestion! I was unaware that ABP had something similar out of the box 👍.

I have a few questions about the implementation of the PerRequestRedisCache though

  • Does this cache stuff to memory like the MemoryCache implementation does, or does it cache for the specific current connection so THIS user doesn't have to go to redis if they need the same value again
  • We have a scale out solution with multiple instances, so we've set the expiry on the memory cache really low so it expires quickly and is updated from Redis frequently. Is it possible to set the expiry of the memorycache with PerRequestRedisCache?
  • What is your product version? ABP Nuget packages 6.6.1
  • What is your product type (Angular or MVC)? ANGULAR
  • What is product framework type (.net framework or .net core)? .NET 5

Yesterday we rolled out a new version of our application where we updated from using version 4.12 of the ABP packages to 6.6.1 We found an unexpected issue with our caching. We implemented our own hybrid solution, where we try to get values from the memory cache which has a small expiry and if it's not there, we get it from Redis and put in the Memory cache for a next time. Previously we had a small amount of redis cache hits and most of the work was done by memory cache. We made some alterations to make it possible to upgrade from 4.12 to 6.6.1, but unfortunately the colleague that made these changes for the caching has since left the company.

Can you help us understand what might have changed in ABP that could cause this, or how our solution might be responsible for this. If you need any further info, please let me know!

`namespace xxx.xxx.Web.Caching { using System; using System.Collections.Generic; using System.Threading.Tasks; using Abp.Runtime.Caching; using Abp.Runtime.Caching.Memory; using Abp.Runtime.Caching.Redis; using Abp.Timing;

/// <summary>
/// Create a cache existing of two caching layers, the first being a memory cache, the second being a Redis cache.
/// The memory cache expires quickly, so a change on another AppService instance is -through the use of the Redis cache- propagated quickly to the memory cache of this instance
/// Create, update and delete actions are always applied to the memory cache AND the Redis cache
/// Getting an item from cache will try to get it from the memory cache. If this doesn't succeed, it will try to get it from the Redis cache.
/// </summary>
public class MemoryAndRedisCache : CacheBase
{
    private readonly AbpMemoryCache _memoryCache;
    private readonly AbpRedisCache _redisCache;

    /// <summary>
    /// Constructor.
    /// </summary>
    public MemoryAndRedisCache(string name, AbpMemoryCacheManager memoryCacheManager, AbpRedisCacheManager redisCacheManager) : base(name)
    {
        // Set expiry to 1 minute for the memory cache, so a change on another AppService instance is propagated quickly to the memory cache of this instance
        _memoryCache = (AbpMemoryCache)memoryCacheManager.GetCache(name + "_memory");
        _memoryCache.DefaultAbsoluteExpireTime = Clock.Now.AddMinutes(1);

        // Expiry of the Redis cache is done through normal configuration in ABP
        _redisCache = (AbpRedisCache)redisCacheManager.GetCache(name);
    }

    public override bool TryGetValue(string key, out object value)
    {
        // Try to get the value from memory
        var memorySuccess = _memoryCache.TryGetValue(key, out value);
        if (memorySuccess)
        {
            var expiryInfo = _memoryCache.DefaultAbsoluteExpireTime != null ? _memoryCache.DefaultAbsoluteExpireTime.ToString() : _memoryCache.DefaultSlidingExpireTime.ToString();
            Logger.Debug($"Get from {this.Name}: {key} [memory] [{expiryInfo}]");
            return true;
        }

        // Getting from memory failed, try to get it from Redis
        var redisSuccess = _redisCache.TryGetValue(key, out value);
        if (redisSuccess)
        {
            var expiryInfo = _redisCache.DefaultAbsoluteExpireTime != null ? _redisCache.DefaultAbsoluteExpireTime.ToString() : _redisCache.DefaultSlidingExpireTime.ToString();
            Logger.Debug($"Get from {this.Name}: {key} [redis] [{expiryInfo}]");
            // We got the value from Redis, now store it in memory cache for repeated use
            _memoryCache.Set(key, value, null, null);
            return true;
        }

        return false;
    }

    public override object GetOrDefault(string key)
    {
        // Try to get the value from memory
        var value = _memoryCache.GetOrDefault(key);
        if (value != null)
        {
            var expiryInfo = _memoryCache.DefaultAbsoluteExpireTime != null ? _memoryCache.DefaultAbsoluteExpireTime.ToString() : _memoryCache.DefaultSlidingExpireTime.ToString();
            Logger.Debug($"Get from {this.Name}: {key} [memory] [{expiryInfo}]");
            return value;
        }

        // Getting from memory failed, try to get it from Redis
        value = _redisCache.GetOrDefault(key);
        if (value != null)
        {
            var expiryInfo = _redisCache.DefaultAbsoluteExpireTime != null ? _redisCache.DefaultAbsoluteExpireTime.ToString() : _redisCache.DefaultSlidingExpireTime.ToString();
            Logger.Debug($"Get from {this.Name}: {key} [redis] [{expiryInfo}]");
            // We got the value from Redis, now store it in memory cache for repeated use
            _memoryCache.Set(key, value, null, null);
        }
        else
        {
            Logger.Debug($"Get from {this.Name}: {key} [source]");
            // The caching logic of ABP will call the function to get the data from the source - no need to do anything
        }

        return value;
    }

    public override object[] GetOrDefault(string[] keys)
    {
        var values = _memoryCache.GetOrDefault(keys);

        for (var t = 0; t < keys.Length; t++)
        {
            if (values[t] == null)
            {
                values[t] = _redisCache.GetOrDefault(keys[t]);
            }
        }

        return values;
    }

    public override async Task<object> GetOrDefaultAsync(string key)
    {
        // Try to get the value from memory
        var value = await _memoryCache.GetOrDefaultAsync(key);
        if (value != null)
        {
            var expiryInfo = _memoryCache.DefaultAbsoluteExpireTime != null ? _memoryCache.DefaultAbsoluteExpireTime.ToString() : _memoryCache.DefaultSlidingExpireTime.ToString();
            Logger.Debug($"Get from {this.Name}: {key} [memory] [{expiryInfo}]");
            return value;
        }

        // Getting from memory failed, try to get it from Redis
        value = await _redisCache.GetOrDefaultAsync(key);
        if (value != null)
        {
            var expiryInfo = _redisCache.DefaultAbsoluteExpireTime != null ? _redisCache.DefaultAbsoluteExpireTime.ToString() : _redisCache.DefaultSlidingExpireTime.ToString();
            Logger.Debug($"Get from {this.Name}: {key} [redis] [{expiryInfo}]");
            // We got the value from Redis, now store it in memory cache for repeated use
            await _memoryCache.SetAsync(key, value, null, null);
        }
        else
        {
            Logger.Debug($"Get from {this.Name}: {key} [source]");
            // The caching logic of ABP will call the function to get the data from the source - no need to do anything
        }

        return value;
    }

    public override async Task<object[]> GetOrDefaultAsync(string[] keys)
    {
        var values = await _memoryCache.GetOrDefaultAsync(keys);

        for (var t = 0; t < keys.Length; t++)
        {
            if (values[t] == null)
            {
                values[t] = await _redisCache.GetOrDefaultAsync(keys[t]);
            }
        }

        return values;
    }

    public override void Set(string key, object value, TimeSpan? slidingExpireTime = null, DateTimeOffset? absoluteExpireTime = null)
    {
        _memoryCache.Set(key, value, null, null);
        _redisCache.Set(key, value, null, null);
    }

    public override async Task SetAsync(string key, object value, TimeSpan? slidingExpireTime = null, DateTimeOffset? absoluteExpireTime = null)
    {
        await _memoryCache.SetAsync(key, value, null, null);
        await _redisCache.SetAsync(key, value, null, null);
    }

    public override void Set(KeyValuePair<string, object>[] pairs, TimeSpan? slidingExpireTime = null, DateTimeOffset? absoluteExpireTime = null)
    {
        _memoryCache.Set(pairs, null, null);
        _redisCache.Set(pairs, null, null);
    }

    public override async Task SetAsync(KeyValuePair<string, object>[] pairs, TimeSpan? slidingExpireTime = null, DateTimeOffset? absoluteExpireTime = null)
    {
        await _memoryCache.SetAsync(pairs, null);
        await _redisCache.SetAsync(pairs, null, null);
    }

    public override void Remove(string key)
    {
        _memoryCache.Remove(key);
        _redisCache.Remove(key);
    }

    public override async Task RemoveAsync(string key)
    {
        await _memoryCache.RemoveAsync(key);
        await _redisCache.RemoveAsync(key);
    }

    public override void Remove(string[] keys)
    {

        _memoryCache.Remove(keys);
        _redisCache.Remove(keys);
    }

    public override async Task RemoveAsync(string[] keys)
    {
        await _memoryCache.RemoveAsync(keys);
        await _redisCache.RemoveAsync(keys);

    }

    public override void Clear()
    {
        _memoryCache.Clear();
        _redisCache.Clear();
    }

    public override void Dispose()
    {
        _memoryCache.Dispose();
        _redisCache.Dispose();
    }
}

} `

Showing 1 to 10 of 18 entries