System.ArgumentNullException: Value cannot be null. (Parameter 'configuration') in ASP.NET Core Redis Implementation

huangapple go评论80阅读模式
英文:

System.ArgumentNullException: Value cannot be null. (Parameter 'configuration') in ASP.NET Core Redis Implementation

问题

在我的ASP.NET Core-6 Web API应用程序中,我正在实现Redis内存缓存,我正在使用Dapper与DTO一起连接以返回查询的数据库结果。

我已安装了这个Redis包:StackExchange.Redis

模型:

[Table("students")]
public class Student
{
    public string registration_no { get; set; }
    public string first_name { get; set; }
    public string last_name { get; set; }
    public Date birth_date { get; set; }
    public Date entry_date { get; set; }
}

DTO:

public class StudentDto
{
    public string RegistrationNo { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Date BirthDate { get; set; }
    public Date EntryDate { get; set; }
}

然后,我使用了AutoMapper进行映射:

public class StudentMapperProfile : Profile
{
    public StudentMapperProfile() 
    {
        CreateMap<Student, StudentDto>()
            .ForMember(
                dest => dest.RegistrationNo,
                opt => opt.MapFrom(src => src.registration_no)
            )
            .ForMember(
                dest => dest.FirstName,
                opt => opt.MapFrom(src => src.first_name)
            )
            .ForMember(
                dest => dest.LastName,
                opt => opt.MapFrom(src => src.last_name)
            )
            .ForMember(
                dest => dest.BirthDate,
                opt => opt.MapFrom(src => src.birth_date)
            )
            .ForMember(
                dest => dest.EntryDate,
                opt => opt.MapFrom(src => src.entry_date)
            )
            .ReverseMap();
    }
}

响应:

public class Response<T>
{
    public T Data { get; set; }
    public bool Successful { get; set; }
    public string Message { get; set; }
    public int StatusCode { get; set; }

    public Response(int statusCode, bool success, string msg, T data)
    {
        Data = data;
        Successful = success;
        StatusCode = statusCode;
        Message = msg;
    }

    public Response()
    {
    }

    public static Response<T> Success(string successMessage, T data, int statusCode = 200)
    {
        return new Response<T> { Successful = true, Message = successMessage, Data = data, StatusCode = statusCode };
    }

    public override string ToString() => JsonConvert.SerializeObject(this);
}

StudentsRepository:

public async Task<Response<IEnumerable<StudentDto>>> GetAllStudentsAsync()
{
    var response = new Response<IEnumerable<StudentDto>>();
    var cacheKey = "Student";
    using IDbConnection _dbConnection = Connection;
    if (_memoryCache.TryGetValue(cacheKey, out Response<IEnumerable<StudentDto>> cachedResponse))
    {
        return cachedResponse;
    }
    var cachedData = await _distributedCache.GetStringAsync(cacheKey);
    if (!string.IsNullOrEmpty(cachedData))
    {
        var studentsDto = JsonSerializer.Deserialize<Response<IEnumerable<StudentDto>>>(cachedData);

        // 在MemoryCache中存储响应以供后续更快速的访问
        _memoryCache.Set(cacheKey, studentsDto);

        return studentsDto;
    }
    try
    {
        var sQuery = "SELECT * FROM students";
        _dbConnection.Open();
        var students = await _dbConnection.QueryAsync<Student>(sQuery);
        var studentsDtos = _mapper.Map<IEnumerable<StudentDto>>(students);

        response.Data = studentsDtos;
        response.Successful = true;

        var cacheOptions = new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) // 设置缓存过期时间
        };

        // 序列化并将响应存储在Redis缓存中
        var serializedData = JsonSerializer.Serialize(response);
        await _distributedCache.SetStringAsync(cacheKey, serializedData, cacheOptions);

        // 在MemoryCache中存储响应以供后续更快速的访问
        _memoryCache.Set(cacheKey, response);

        return response;
    }
    catch (Exception ex)
    {
        response.Successful = false;
        response.StatusCode = (int)HttpStatusCode.BadRequest;
        return response;
    }
    finally
    {
        _dbConnection.Close();
    }
}

StudentsService:

public async Task<Response<IEnumerable<StudentDto>>> GetAllStudentsAsync()
{
    return await _studentsRepository.GetAllStudentsAsync();
}

StudentController:

public async Task<ActionResult<Response<IEnumerable<StudentDto>>>> GetAllStudentsAsync()
{
    var result = await _studentsService.GetAllStudentsAsync();
    return Ok(result);
}

appsettings.json:

"ConnectionStrings": {
    "MyRedisCon": "localhost:3500"
}

依赖注入 (DI):

AutoMapper

public static class AutoMapperServiceExtension
{
    public static void ConfigureAutoMappers(this IServiceCollection services)
    {
        services.AddAutoMapper(typeof(StudentProfile));
    }
}

Redis:

public static class CacheExtension
{
    public static void AddCacheInjection(this IServiceCollection services, IConfiguration config)
    {
        services.AddSingleton<IConnectionMultiplexer>(provider =>
        {
            var configuration = ConfigurationOptions.Parse(config.GetConnectionString("MyRedisCon"));
            configuration.AbortOnConnectFail = false; // 允许重试连接
            configuration.ConnectTimeout = 5000; // 设置连接超时时间(毫秒)
            return ConnectionMultiplexer.Connect(configuration);
        });
        services.AddSingleton<IDistributedCache, RedisCache>();
        services.AddMemoryCache();
    }
}

DIServiceExtension:

public static class DIServiceExtension
{
    public static void AddDependencyInjection(this IServiceCollection services)
    {
        // 添加服务注入在这里 -- HTTP辅助类
        services.AddTransient<IStudentsRepository, StudentsRepository>();
        services.AddTransient<IStudentsService, StudentsService>();
    }
}

Program.cs:

builder.Services.AddDependencyInjection();
builder.Services.ConfigureAutoMappers();
builder.Services.AddCacheInjection(configuration);

然而,当我在Swagger上启动应用程序并提交HttpGet请求时,我收到了这个错误:

System.ArgumentNullException: Value cannot be null. (Parameter 'configuration')
at StackExchange.Redis.ConfigurationOptions.DoParse(String configuration, Boolean ignoreUnknown) in /_/src/StackExchange.Redis/ConfigurationOptions.cs:line 810
at Microsoft.Extensions.Caching.StackExchangeRedis.RedisCache.ConnectAsync(CancellationToken token)
at Microsoft.Extensions.Caching.StackExchangeRedis.RedisCache.GetAndRefreshAsync(String key, Boolean getData, CancellationToken token)
at Microsoft.Extensions.Caching.StackExchangeRedis.RedisCache.GetAsync(String key, CancellationToken token)
at Microsoft.Extensions.Caching.Distributed.DistributedCacheExtensions.GetStringAsync(IDistributedCache cache, String key, CancellationToken token)
at StudentsRepository.GetAllStudentsAsync() in C:\StudentsRepository.cs:line 69
at StudentsService.GetAllStudentsAsync() in C:\StudentsService.cs:line 24
at

英文:

I am implementing Redis Memory Cache in my ASP.NET Core-6 Web API application, I am using Dapper in connection with DTO to return results of queried database.

I have installed this Redis Package: StackExchange.Redis

Model:

[Table(&quot;students&quot;)]
public class Student
{
    public string registration_no { get; set; }
    public string first_name { get; set; }
    public string last_name { get; set; }
    public Date birth_date { get; set; }
    public Date entry_date { get; set; }
}

DTO:

public class StudentDto
{
    public string RegistrationNo { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Date BirthDate { get; set; }
    public Date EntryDate { get; set; }
}

Then I used auto-mapper for the mapping:

public class StudentMapperProfile : Profile
{
    public StudentMapperProfile() 
    {
        CreateMap&lt;Student, StudentDto&gt;()
            .ForMember(
                dest =&gt; dest.RegistrationNo,
                opt =&gt; opt.MapFrom(src =&gt; src.registration_no)
            )
            .ForMember(
                dest =&gt; dest.FirstName,
                opt =&gt; opt.MapFrom(src =&gt; src.first_name)
            )
            .ForMember(
                dest =&gt; dest.LastName,
                opt =&gt; opt.MapFrom(src =&gt; src.last_name)
            )
            .ForMember(
                dest =&gt; dest.BirthDate,
                opt =&gt; opt.MapFrom(src =&gt; src.birth_date)
            )
            .ForMember(
                dest =&gt; dest.EntryDate,
                opt =&gt; opt.MapFrom(src =&gt; src.entry_date)
            )
            .ReverseMap();
    }
}

Response:

public class Response&lt;T&gt;
{
    public T Data { get; set; }
    public bool Successful { get; set; }
    public string Message { get; set; }
    public int StatusCode { get; set; }

    public Response(int statusCode, bool success, string msg, T data)
    {
        Data = data;
        Successful = success;
        StatusCode = statusCode;
        Message = msg;
    }

    public Response()
    {
    }

    public static Response&lt;T&gt; Success(string successMessage, T data, int statusCode = 200)
    {
        return new Response&lt;T&gt; { Successful = true, Message = successMessage, Data = data, StatusCode = statusCode };
    }
    public override string ToString() =&gt; JsonConvert.SerializeObject(this);
}

StudentsRepository:

public async Task&lt;Response&lt;IEnumerable&lt;StudentDto&gt;&gt;&gt; GetAllStudentsAsync()
{
    var response = new Response&lt;IEnumerable&lt;StudentDto&gt;&gt;();
    var cacheKey = &quot;Student&quot;;
    using IDbConnection _dbConnection = Connection;
    if (_memoryCache.TryGetValue(cacheKey, out Response&lt;IEnumerable&lt;StudentDto&gt;&gt; cachedResponse))
    {
        return cachedResponse;
    }
    var cachedData = await _distributedCache.GetStringAsync(cacheKey);
    if (!string.IsNullOrEmpty(cachedData))
    {
        var studentsDto = JsonSerializer.Deserialize&lt;Response&lt;IEnumerable&lt;StudentDto&gt;&gt;&gt;(cachedData);

        // Store the response in the MemoryCache for faster subsequent access
        _memoryCache.Set(cacheKey, StudentDto);

        return studentsDto;
    }
    try
    {
        var sQuery = @&quot;SELECT * FROM students&quot;;
        _dbConnection.Open();
        var students = await _dbConnection.QueryAsync&lt;Student&gt;(sQuery);
        var studentsDtos = _mapper.Map&lt;IEnumerable&lt;StudentDto&gt;&gt;(students);

        response.Data = studentsDtos;
        response.Successful = true;

        var cacheOptions = new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) // Set cache expiration time
        };

        // Serialize and store the response in Redis cache
        var serializedData = JsonSerializer.Serialize(response);
        await _distributedCache.SetStringAsync(cacheKey, serializedData, cacheOptions);

        // Store the response in the MemoryCache for faster subsequent access
        _memoryCache.Set(cacheKey, response);

        return response;
    }
    catch (Exception ex)
    {
        response.Successful = false;
        response.StatusCode = (int)HttpStatusCode.BadRequest;
        return response;
    }
    finally
    {
        _dbConnection.Close();
    }
}

StudentsService:

public async Task&lt;Response&lt;IEnumerable&lt;StudentDto&gt;&gt;&gt; GetAllStudentsAsync()
{
    return await _studentsRepository.GetAllStudentsAsync();
}

StudentController:

public async Task&lt;ActionResult&lt;Response&lt;IEnumerable&lt;StudentDto&gt;&gt;&gt;&gt; GetAllStudentsAsync()
{
    var result = await _studentsService.GetAllStudentsAsync();
    return Ok(result);
}

appsettings.json:

 &quot;ConnectionStrings&quot;: {
    &quot;MyRedisCon&quot;: &quot;localhost:3500&quot;
  },

Dependency Injection (DI):

AutoMapper

public static class AutoMapperServiceExtension
{
    public static void ConfigureAutoMappers(this IServiceCollection services)
    {
        services.AddAutoMapper(typeof(StudentProfile));
    }
}

Redis:

public static class CacheExtension
{
    public static void AddCacheInjection(this IServiceCollection services, IConfiguration config)
    {
        services.AddSingleton&lt;IConnectionMultiplexer&gt;(provider =&gt;
        {
            var configuration = ConfigurationOptions.Parse(config.GetConnectionString(&quot;MyRedisCon&quot;));
            configuration.AbortOnConnectFail = false; // Allow retrying connection
            configuration.ConnectTimeout = 5000; // Set connection timeout (milliseconds)
            return ConnectionMultiplexer.Connect(configuration);
        });
        services.AddSingleton&lt;IDistributedCache, RedisCache&gt;();
        services.AddMemoryCache();
    }
}

DIServiceExtension:

public static class DIServiceExtension
{
    public static void AddDependencyInjection(this IServiceCollection services)
    {
        // Add Service Injections Here -- HTTP Helpers
        services.AddTransient&lt;IStudentsRepository, StudentsRepository&gt;();
        services.AddTransient&lt;IStudentsService, StudentsService&gt;();
    }
}

Program.cs:

builder.Services.AddDependencyInjection();
builder.Services.ConfigureAutoMappers();
builder.Services.AddCacheInjection(configuration);

However, when I launched the application on Swagger and submitted the HttpGet Request, I got this error:

System.ArgumentNullException: Value cannot be null. (Parameter 'configuration')
at StackExchange.Redis.ConfigurationOptions.DoParse(String configuration, Boolean ignoreUnknown) in /_/src/StackExchange.Redis/ConfigurationOptions.cs:line 810
at Microsoft.Extensions.Caching.StackExchangeRedis.RedisCache.ConnectAsync(CancellationToken token)
at Microsoft.Extensions.Caching.StackExchangeRedis.RedisCache.GetAndRefreshAsync(String key, Boolean getData, CancellationToken token)
at Microsoft.Extensions.Caching.StackExchangeRedis.RedisCache.GetAsync(String key, CancellationToken token)
at Microsoft.Extensions.Caching.Distributed.DistributedCacheExtensions.GetStringAsync(IDistributedCache cache, String key, CancellationToken token)
at StudentsRepository.GetAllStudentsAsync() in C:\StudentsRepository.cs:line 69
at StudentsService.GetAllStudentsAsync() in C:\StudentsService.cs:line 24
at StudentsController.GetAllStudentsAsync() in C:\StudentController.cs:line 31

Note:

C:\StudentsRepository.cs:line 69 is

var cachedData = await _distributedCache.GetStringAsync(cacheKey);

When I put breakpoints, the code below returns null:

if (_memoryCache.TryGetValue(cacheKey, out Response&lt;IEnumerable&lt;StudentDto&gt;&gt; cachedResponse))
{
    return cachedResponse;
}

The problem has to do with Redis. When I commented out all the Redis Cache Code, I got success response

How do I resolve this?

答案1

得分: 1

问题出在将 Redis 添加到服务中的方式上。在当前代码中,您没有设置RedisCacheOptions的配置。

尝试用以下代码替换

services.AddSingleton&lt;IDistributedCache, RedisCache&gt;();

用这个

services.AddOptions&lt;RedisCacheOptions&gt;().Configure&lt;IServiceProvider&gt;((options, serviceProvider) =&gt;
{
    options.ConnectionMultiplexerFactory = () =&gt; Task.FromResult(serviceProvider.GetService&lt;IConnectionMultiplexer&gt;());
});
services.AddStackExchangeRedisCache(_ =&gt; { });
英文:

The problem is the way Redis is added to the services. In the current code you didn't set the configuration for the RedisCacheOptions.

Try replacing this code

services.AddSingleton&lt;IDistributedCache, RedisCache&gt;();

with this

services.AddOptions&lt;RedisCacheOptions&gt;().Configure&lt;IServiceProvider&gt;((options, serviceProvider) =&gt;
{
    options.ConnectionMultiplexerFactory = () =&gt; Task.FromResult(serviceProvider.GetService&lt;IConnectionMultiplexer&gt;());
});
services.AddStackExchangeRedisCache(_ =&gt; { });

huangapple
  • 本文由 发表于 2023年5月28日 16:38:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/76350618.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定