英文:
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("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; }
}
Then I used auto-mapper for the mapping:
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();
}
}
Response:
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);
// Store the response in the MemoryCache for faster subsequent access
_memoryCache.Set(cacheKey, StudentDto);
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) // 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<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"
},
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<IConnectionMultiplexer>(provider =>
{
var configuration = ConfigurationOptions.Parse(config.GetConnectionString("MyRedisCon"));
configuration.AbortOnConnectFail = false; // Allow retrying connection
configuration.ConnectTimeout = 5000; // Set connection timeout (milliseconds)
return ConnectionMultiplexer.Connect(configuration);
});
services.AddSingleton<IDistributedCache, RedisCache>();
services.AddMemoryCache();
}
}
DIServiceExtension:
public static class DIServiceExtension
{
public static void AddDependencyInjection(this IServiceCollection services)
{
// Add Service Injections Here -- HTTP Helpers
services.AddTransient<IStudentsRepository, StudentsRepository>();
services.AddTransient<IStudentsService, StudentsService>();
}
}
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<IEnumerable<StudentDto>> 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<IDistributedCache, RedisCache>();
用这个
services.AddOptions<RedisCacheOptions>().Configure<IServiceProvider>((options, serviceProvider) =>
{
options.ConnectionMultiplexerFactory = () => Task.FromResult(serviceProvider.GetService<IConnectionMultiplexer>());
});
services.AddStackExchangeRedisCache(_ => { });
英文:
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<IDistributedCache, RedisCache>();
with this
services.AddOptions<RedisCacheOptions>().Configure<IServiceProvider>((options, serviceProvider) =>
{
options.ConnectionMultiplexerFactory = () => Task.FromResult(serviceProvider.GetService<IConnectionMultiplexer>());
});
services.AddStackExchangeRedisCache(_ => { });
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论