Is there a way to use any IDistributedCache as a ResponseCache in .net core? - asp.net-core

I want to cache responses from APIs to DistributedSqlServerCache.
The default ResponseCaching only uses a memory cache. There is a constructor which allows to configure what cache to use, but it's internal.
I wrote a filter. If the response is not cached and the http response is OK and the ActionResult is an ObjectActionResult, it serializes the value as JSON and saves it to SQL cache.
If the response is cached, it deserializes it and sets the result as an OkObject result with the deserielized object.
It works ok, but it has some clumsy things (like, to use the attribute, you have to specify the type which will be de/serialized, with typeof()).
Is there a way to cache responses to a distributed sql cache, which doesn't involve me hacking together my own mostly working solution?
Another option would be to copy-pasta the netcore ResponseCacheMiddleWare, and modify it to use a diffirent cache. I could even make it a nuget package maybe.
Are there any other solutions out there?
Here's the filter I put together (simplified for display purposes)
namespace Api.Filters
{
/// <summary>
/// Caches the result of the action as data.
/// The action result must implement <see cref="ObjectResult"/>, and is only cached if the HTTP status code is OK.
/// </summary>
public class ResponseCache : IAsyncResourceFilter
{
public Type ActionType { get; set; }
public ExpirationType ExpirationType;
private readonly IDistributedCache cache;
public ResponseCache(IDistributedCache cache)
{
this.cache = cache;
}
public async Task OnResourceExecutionAsync(ResourceExecutingContext executingContext, ResourceExecutionDelegate next)
{
var key = getKey(executingContext);
var cachedValue = await cache.GetAsync(key);
if (cachedValue != null && executingContext.HttpContext.Request.Query["r"] == "cache")
{
await cache.RemoveAsync(key);
cachedValue = null;
}
if (cachedValue != null)
{
executingContext.Result = new OkObjectResult(await fromBytes(cachedValue));
return;
}
var executedContext = await next();
// Only cache a successful response.
if (executedContext.HttpContext.Response.StatusCode == StatusCodes.Status200OK && executedContext.Result is ObjectResult result)
{
await cache.SetAsync(key, await toBytes(result.Value), getExpiration());
}
}
private async Task<byte[]> toBytes(object value)
{
using var stream = new MemoryStream();
await JsonSerializer.SerializeAsync(stream, value, ActionType);
return stream.ToArray();
}
private async Task<object> fromBytes(byte[] bytes)
{
using var stream = new MemoryStream(bytes);
using var reader = new BinaryReader(stream, Encoding.Default, true);
return await JsonSerializer.DeserializeAsync(stream, ActionType);
}
}
public class ResponseCacheAttribute : Attribute, IFilterFactory
{
public bool IsReusable => true;
public ExpirationType ExpirationType;
public Type ActionType { get; set; }
public ResponseCacheAttribute(params string[] queryParameters)
{
this.queryParameters = queryParameters;
}
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var cache = serviceProvider.GetService(typeof(IDistributedCache)) as IDistributedCache;
return new ResponseCache(cache)
{
ExpirationType = ExpirationType,
ActionType = ActionType
};
}
}
}

In the end I made a nuget package, sourced on github. See this issue for some more context as to why a new package was made.

Related

.Net Core integration tests with mock keeps returning null

I am trying to be a good boy and write some unit and integration tests but I am slowly giving up. On the controller side, I have:
public class PointController : BaseApiController
{
private readonly IPointL _pointL;
public PointController(IMapper mapper, IPrincipal principal, IPointL pointL) : base(mapper, principal)
{
_pointL = pointL;
}
// GET: api/<PointController>
/// <summary>
/// Get point by id.
/// </summary>
/// <param name="id">Point id.</param>
/// <returns></returns>
[HttpGet("{id}")]
public async Task<PointDTO> GetPointAsync(int id)
{
// Get point.
var pointModel = await _pointL.GetPointAsync(id);
var pointDTO = _mapper.Map<PointDTO>(pointModel);
return pointDTO;
}
}
So the line var pointModel = await _pointL.GetPointAsync(id); in fact calls my logic layer:
public class PointL : BaseL, IPointL
{
private readonly IBaseDAL _baseDAL;
public PointL(IMapper mapper, IPrincipal principal, IBaseDAL baseDAL) : base(mapper, principal)
{
_baseDAL = baseDAL;
}
///<inheritdoc/>
public async Task<PointModel> GetPointAsync(int id)
{
_logger.Info($"Get point with id {id}.");
var pointDBModel = await _baseDAL.GetPointAsync(id);
if (pointDBModel is null)
{
throw new Exception($"Point with id {id} not found.");
}
var pointModel = _mapper.Map<PointModel>(pointDBModel);
return pointModel;
}
}
which drops to the final, DAL layer with the await _baseDAL.GetPointAsync(id);
public partial class BaseDAL
{
///<inheritdoc/>
public async Task<Point> GetPointAsync(int id)
{
_logger.Info($"Get point from database with id {id}.");
using var dbGlistaContext = new GlistaContext(_options);
var point = await dbGlistaContext.Point.FindAsync(id);
return point;
}
}
Now my plan was to write an integration test for the GetPointAsync(int id) method on the controller. I guess, this is the usual way or am I mistaken?
Either way, this is my not working attempt:
private readonly PointController _sut;
private readonly Mock<IPointL> _pointLMock = new Mock<IPointL>();
protected readonly Mock<IMapper> _mapperMock = new Mock<IMapper>();
protected readonly Mock<IPrincipal> _principalMock = new Mock<IPrincipal>();
public PointControllerTests()
{
_sut = new PointController(_mapperMock.Object, _principalMock.Object, _pointLMock.Object);
}
[Fact]
public async System.Threading.Tasks.Task GetPointAsync_ShouldReturnPoint_WhenPointExists()
{
// Arrange.
var pointId = 1;
var pointModel = new PointModel
{
Id = pointId,
Name = "T01",
};
_pointLMock.Setup(x => x.GetPointAsync(pointId))
.ReturnsAsync(pointModel);
// Act.
var point = await _sut.GetPointAsync(pointId); // Keeps returning null no matter what! :(
// Assert.
Assert.Equal(pointId, point.Id);
}
This is for some reason NOT working as mock keeps returning null values! Does anybody have an idea why? I have watched some tutorials but I can't figure out the solution.
EDIT:
I was able to find out that the controller
var pointModel = await _pointL.GetPointAsync(id);
var pointDTO = _mapper.Map<PointDTO>(pointModel);
gets the pointModel, however, the mapping to DTO returns null. Thus I tried setting the mapper mock
var pointDTO = new PointDTO();
mapperMock.Setup(m => m.Map<PointModel, PointDTO>(It.IsAny<PointModel>())).Returns(pointDTO);
However, without any success.
You need to setup the Map method of the mapper mock object.
_mapperMock.Setup(x => x.Map<PointDTO>(It.IsAny<PointDTO>()))
.Returns(...);

How to change api return result in asp.net core 2.2?

My requirement is when the return type of the action is void or Task, I'd like to return my custom ApiResult instead. I tried the middleware mechanism, but the response I observed has null for both ContentLength and ContentType, while what I want is a json representation of an empty instance of ApiResult.
Where should I make this conversion then?
There are multiple filter in .net core, and you could try Result filters.
For void or Task, it will return EmptyResult in OnResultExecutionAsync.
Try to implement your own ResultFilter like
public class ResponseFilter : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
// do something before the action executes
if (context.Result is EmptyResult)
{
context.Result = new JsonResult(new ApiResult());
}
var resultContext = await next();
// do something after the action executes; resultContext.Result will be set
}
}
public class ApiResult
{
public int Code { get; set; }
public object Result { get; set; }
}
And register it in Startup.cs
services.AddScoped<ResponseFilter>();
services.AddMvc(c =>
{
c.Filters.Add(typeof(ResponseFilter));
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
All you have to do is to check the return type and on the basis of the return you can perform whatever operations you want.
Here is the abstract demo:
You have a method:
public Action SomeActionMethod()
{
var obj = new object();
return (Action)obj;
}
Now in your code you can use the following code to get the name of the method:
MethodBase b = p.GetType().GetMethods().FirstOrDefault();
var methodName = ((b as MethodInfo).ReturnType.Name);
Where p in the above code is the class which contains the methods whose return type you want to know.
after having the methodname you can decide on variable methodName what to return.
Hope it helps.

Create a Helper class from a Controller's method thats getting fat

This method receive a CSV file (Sale, Date) POSTed from front-end, cleans a table and inserts csv file record into the table, then gets latest Date and returns it to the front-end. I know the code looks a bit ugly and I have a lot to learn but what I am trying to figure out here is how to create a Helper class from this code since the method is getting too fat I gess?
So I tried to migrate some of the code to a Helper class then I created a static Class but the problem is that I couldn't inject dependencies into its constructor since it was an static Class... then no database service from the Helper class and this is not too "helper".
So in your opinion, is this method too long/ fat?
Is there a need for a Helper Class?
How can I build it?
Cheers
Here my Controller
private IDataService<Sale> _SaleDataService;
private readonly MyOptions _myOptions;
public DateTime LastWindowDay;
public ForecastApiController(IDataService<Sale> service, IOptions<MyOptions> optionsAccessor)
{
_SaleDataService = service;
_myOptions = optionsAccessor.Value;
}
[HttpPost("api/Sales/uploadFile")]
public async Task<IActionResult> UploadFiles()
{
try
{
//clean table
var all = _SaleDataService.GetAll();
if (all.Count() > 0)
{
foreach (var item in all)
{
_SaleDataService.Delete(item);
}
}
var files = Request.Form.Files;
//Read Request for the unique uploaded file (method can process multiple but frontend will post just one)
foreach (var file in files)
{
using (System.IO.StreamReader sr = new StreamReader(file.OpenReadStream()))
{
//Reads the file one line at a time until its end
String line = await sr.ReadLineAsync();
while (sr.Peek() >= 0)
{
//Split the values on the line and convert them into a propper format
var fileLine = sr.ReadLine();
var lineToArray = fileLine.Split(',').ToArray();
var timeElement = DateTime.Parse(lineToArray[0]);
var saleAmauntElement = float.Parse(lineToArray[1], System.Globalization.CultureInfo.InvariantCulture);
//Discard negative values and store data into database
if (saleAmauntElement >= 0)
{
//Store line into database
Sale sl = new Sale
{
SaleDate = timeElement,
SaleAmount = saleAmauntElement
};
_SaleDataService.Create(sl);
LastWindowDay = sl.SaleDate;
}
}
// Simple Moving Average method will be used and the restriction is that it will calculate only within a week after -
// - last day of historical data.
// Create an array and stores the next 7 days from the last day that there is data
string[] predictionDays = new string[7];
for (int i = 0; i < 7; i++)
{
predictionDays[i] = LastWindowDay.AddDays(i + 1).ToString("dd/MM/yyyy");
}
//returns the array to frontend to let the user select a prediction day
return Json(predictionDays);
}
}
return Json(new { message = "Error trying to process information" });
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new { message = ex.Message });
}
}
Following the Repository pattern helps to maintain the controllers and the project in general, it is like having a helper class with all your operations (business logic and DB).
In your Startup.cs
services.AddScoped<IRepository, Repository>();
Create an Interface
public interface IRepository
{
IEnumerable<MyData> GetData();
}
Create your helper class
public partial class Repository : IRepository
{
private DBContext _context;
private ILogger<Repository> _logger;
private IHttpContextAccessor _httpContextAccessor;
public Repository(DBContext context, ILogger<Repository> logger, IHttpContextAccessor httpContextAccessor)
{
_context = context;
_logger = logger;
_httpContextAccessor = httpContextAccessor;
}
public async Task<bool> SaveChangesAsync()
{
return (await _context.SaveChangesAsync()) > 0;
}
public IEnumerable<MyData> GetData()
{
_logger.LogInformation("Getting All Data from the Database");
return _context.Data.ToList();
}
}
Finally inject it in your Controller
public class RequestsController : Controller
{
private IRepository _repository;
private ILogger<RequestsController> _logger;
private IConfiguration _config;
public RequestsController(IRepository repository,ILogger<RequestsController> logger,IConfiguration config)
{
_repository = repository;
_logger = logger;
_config = config;
}
// GET: Requests
public IActionResult Index()
{
var data = _repository.GetData()
return View(data);
}
}
The best practice approach is to follow the repository pattern as advised by Steve Tolba's answer. The repository pattern alleviates the bulk of query logic, transformation of view Models and the calling of business models from the controller.
However, if for whatever reason you do not want to follow the repository pattern and just want to split up your controller, you may pass the reference to the controller as a parameter to another action method:
[HttpGet("myActionMethodThatUsedToBeFat")]
public int MyActionMethodThatUsedToBeFat()
{
await HelperAction(this);
//Do stuff...
}
private async Task<byte[]> HelperAction(Controller controller)
{
//Do stuff in here that would have made the calling action method too bulky
}

Multipart body length limit exceeded exception

Although having set the MaxRequestLength and maxAllowedContentLength to the maximum possible values in the web.config section, ASP.Net Core does not allow me to upload files larger than 134,217,728 Bytes. The exact error coming from the web server is:
An unhandled exception occurred while processing the request.
InvalidDataException: Multipart body length limit 134217728 exceeded.
Is there any way to work this around? (ASP.Net Core)
I found the solution for this problem after reading some posts in GitHub. Conclusion is that they have to be set in the Startup class. For example:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.Configure<FormOptions>(x => {
x.ValueLengthLimit = int.MaxValue;
x.MultipartBodyLengthLimit = int.MaxValue; // In case of multipart
})
}
This will solve the problem. However they also indicated that there is a [RequestFormSizeLimit] attribute, but I have been unable to reference it yet.
Alternatively use the attribute, so the equivalent for an action as resolved by Transcendant would be:
[RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = int.MaxValue)]
If you use int.MaxValue (2,147,483,647) for the value of MultipartBodyLengthLimit as suggested in other answers, you'll be allowing file uploads of approx. 2Gb, which could quickly fill up disk space on a server. I recommend instead setting a constant to limit file uploads to a more sensible value e.g. in Startup.cs
using MyNamespace.Constants;
public void ConfigureServices(IServiceCollection services)
{
... other stuff
services.Configure<FormOptions>(options => {
options.MultipartBodyLengthLimit = Files.MaxFileUploadSizeKiloBytes;
})
}
And in a separate constants class:
namespace MyNamespace.Constants
{
public static class Files
{
public const int MaxFileUploadSizeKiloBytes = 250000000; // max length for body of any file uploaded
}
}
in case some one still face this problem i've created a middle-ware which intercept the request and create another body
public class FileStreamUploadMiddleware
{
private readonly RequestDelegate _next;
public FileStreamUploadMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (context.Request.ContentType != null)
{
if (context.Request.Headers.Any(x => x.Key == "Content-Disposition"))
{
var v = ContentDispositionHeaderValue.Parse(
new StringSegment(context.Request.Headers.First(x => x.Key == "Content-Disposition").Value));
if (HasFileContentDisposition(v))
{
using (var memoryStream = new MemoryStream())
{
context.Request.Body.CopyTo(memoryStream);
var length = memoryStream.Length;
var formCollection = context.Request.Form =
new FormCollection(new Dictionary<string, StringValues>(),
new FormFileCollection()
{new FormFile(memoryStream, 0, length, v.Name.Value, v.FileName.Value)});
}
}
}
}
await _next.Invoke(context);
}
private static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// this part of code from https://github.com/aspnet/Mvc/issues/7019#issuecomment-341626892
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
}
}
and in the controller we can fetch the files form the request
[HttpPost("/api/file")]
public IActionResult GetFile([FromServices] IHttpContextAccessor contextAccessor,
[FromServices] IHostingEnvironment environment)
{
//save the file
var files = Request.Form.Files;
foreach (var file in files)
{
var memoryStream = new MemoryStream();
file.CopyTo(memoryStream);
var fileStream = File.Create(
$"{environment.WebRootPath}/images/background/{file.FileName}", (int) file.Length,
FileOptions.None);
fileStream.Write(memoryStream.ToArray(), 0, (int) file.Length);
fileStream.Flush();
fileStream.Dispose();
memoryStream.Flush();
memoryStream.Dispose();
}
return Ok();
}
you can improve the code for your needs eg: add form parameters in the body of the request and deserialize it.
its a workaround i guess but it gets the work done.

Querying for RavenDB documents using multiple properties

I need to make a query against a document collection that matches several properties.
(Cross post from the mailing list: https://groups.google.com/forum/?fromgroups=#!topic/ravendb/r5f1zr2jd_o)
Here is the document:
public class SessionToken
{
[JsonProperty("jti")]
public string Id { get; set; }
[JsonProperty("aud")]
public Uri Audience { get; set; }
[JsonProperty("sub")]
public string Subject { get; set; }
[JsonProperty("claims")]
public Dictionary<string, string> Claims { get; set; }
}
And here is the test:
[TestFixture]
public class RavenDbTests
{
private IDocumentStore documentStore;
[SetUp]
public void SetUp()
{
this.documentStore = new EmbeddableDocumentStore() { RunInMemory = true };
this.documentStore.Initialize();
}
[Test]
public async void FirstOrDefault_WhenSessionTokenExists_ShouldReturnSessionToken()
{
var c = new SessionToken()
{
Audience = new Uri("http://localhost"),
Subject = "NUnit",
Claims = new Dictionary<string, string>()
{
{ ClaimTypes.System, "NUnit" }
}
};
using (var session = this.documentStore.OpenAsyncSession())
{
await session.StoreAsync(c);
await session.SaveChangesAsync();
// Check if the token exists in the database without using Where clause
var allTokens = await session.Query<SessionToken>().ToListAsync();
Assert.That(allTokens.Any(x => x.Subject == "NUnit" && x.Audience == new Uri("http://localhost")));
// Try getting token back with Where clause
var token = await session.Query<SessionToken>().Customize(x => x.WaitForNonStaleResults()).Where(x => x.Subject == "NUnit" && x.Audience == new Uri("http://localhost")).ToListAsync();
Assert.IsNotNullOrEmpty(token.First().Id);
}
}
}
The last Assert is the one that is failing.
I must admit Im not sure whether this is a bug or a failure on my part.
As far as I understand, this is supposed to work.
PS. I´ve tried with a standalone document store as well as embedded without running in memory, but with same result.
You are getting stale results. In a unit test, you need to allow time for indexing to occur.
Add .Customize(x=> x.WaitForNonStaleResults()) to your queries and the test should pass.
Also, I think you left the Id property off your question when you cut/paste because it doesn't compile as-is.
UPDATE
Per discussion in comments, the issue was that you were applying the [JsonProperty] attribute to the Id property. Since the Id property represents the document key, and is not serialized as part of the JSON document, you can't apply the [JsonProperty] attribute to it.