i'm a problem with the SetAsyncValidator inside a RuleForeach to validate a collection. I need the async Validator because i need to check if the record already exist.
I use a Mediatr validation's pipeline in async mode.
Surfing the net I haven't found similars scenario. How can i realize that ?
Product Category class
public class ProductCategory : AuditableEntity, IAggregateRoot
{
public string Name { get; private set; }
private List<ProductSubCategory> _productSubCategories;
//public IEnumerable<ProductSubCategory> ProductSubCategories => _productSubCategories.AsReadOnly();
public List<ProductSubCategory> ProductSubCategories => _productSubCategories;
public ProductCategory()
{
_productSubCategories = new List<ProductSubCategory>();
}
public void AddProductSubcategory(string name)
{
var productSubCategory = new ProductSubCategory(name);
_productSubCategories.Add(productSubCategory);
}
}
public class ProductSubCategory : AuditableEntity, IAggregateRoot
{
public string Name { get; private set; }
public ProductSubCategory(string name)
{
Name = name;
}
public ProductSubCategory()
{
}
}
This is the synchronous rule and work very well but i would like to run asynchronously
When(x => x.ProductSubCategories.Any(), () =>
{
RuleForEach(x => x.ProductSubCategories).SetValidator(new CreateProductSubCategoryCommandValidator(_repositorySub));
});
Validation Pipeline: Handle method
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var typeName = request.GetGenericTypeName();
_logger.LogDebug("----- Validating command {CommandType}", typeName);
if (!_validators.Any())
return await next();
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(_validators
.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults.SelectMany(r => r.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count == 0)
return await next();
_logger.LogDebug("----- Validating command {typeName} with failures {#failures}", typeName, failures );
throw new ValidationException(failures);
Thanks for any suggestion
Related
I am trying to apply patch operation in Data Layer Repository but due to have no Model State available in Data Layer i am unable to update it. I have debug the overall logic i come to the conclusion that patch.ApplyTo() operation update the Model but context.SaveChanges() is not updating the database context.
Below is the context class
public class ApplicationDbContext : DbContext, IApplicationDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> opts) : base(opts)
{
}
public override int SaveChanges()
{
return base.SaveChanges();
}
}
Below is the repositroy method code
public class SpotWriteOnly : ISpotWriteOnly {
private IApplicationDbContext _context;
public SpotWriteOnly(IApplicationDbContext context)
{
_context = context;
}
public int UpdateSpot(long id, JsonPatchDocument<SpotData> patch) {
Spot s = _context.Spots.Include(x => x.Agent)
.Include(x => x.Channel)
.Include(x => x.Timeband)
.Include(x => x.Region)
.Include(x => x.Sponsor)
.Include(x => x.SpotType)
.Include(x => x.Status)
.Include(x => x.SpotStatusReason)
.OrderByDescending(x => x.Version)
.FirstOrDefault(x => x.SpotId == id && x.IsDeleted == false);
SpotData sData = new SpotData { Spot = s };
patch.ApplyTo(sData);
int response = _context.SaveChanges();
return response;
}
}
Below is the Handler Class
public class UpdateSpotQueryHandler : IRequestHandler<UpdateSpotQuery, int> {
public ISpotWriteOnly _repository;
public IMapper _mapper;
public UpdateSpotQueryHandler(ISpotWriteOnly repository, IMapper mapper)
{
_repository = repository;
_mapper = mapper;
}
public Task<int> Handle(UpdateSpotQuery query, CancellationToken cancellationToken) {
return Task.FromResult(_repository.UpdateSpot(query.SpotId, query.Patch));
}
}
Below is the Query
public class UpdateSpotQuery : IRequest<int> {
public long SpotId { get; set; }
public JsonPatchDocument<SpotData> Patch { get; set; }
}
Below is the controller code
[HttpPatch("{id}")]
public IActionResult UpdateSpot(long id,[FromBody] JsonPatchDocument<SpotData> patch) {
if(ModelState.IsValid) {
var result = Mediator.Send(new UpdateSpotQuery { SpotId = id, Patch = patch} );
var response = new Envelop
{
IsSuccessful = true,
StatusCode = 200,
StatusMessage = "OK",
Response = result.Result,
isExceptionGenerated = false,
Exception = null,
};
return Ok(response);
}
else {
var response = new Envelop
{
IsSuccessful = false,
StatusCode = 200,
StatusMessage = "Error in Model",
Response = ModelState,
isExceptionGenerated = true,
Exception = new Exception[] { new ArgumentException()},
};
return BadRequest(response);
}
}
I am trying to update database using patch operation in data layer.
Test class:
public class PlantDataControllerTests
{
private Mock<PlantDataService> serviceStub = new Mock<PlantDataService>();
private Mock<ILogger<PlantDataController>> loggerStub = new Mock<ILogger<PlantDataController>>();
private PlantDataController controller;
public PlantDataControllerTests()
{
serviceStub = new Mock<PlantDataService>(MockBehavior.Strict);
serviceStub.Setup(service => service.GetItemAsync(It.IsAny<string>()))
.ReturnsAsync((PlantData)null);
loggerStub = new Mock<ILogger<PlantDataController>>();
controller = new PlantDataController(serviceStub.Object, loggerStub.Object);
}
[Fact]
public async Task GetItemAsync_WithExisitingItem_ReturnsExpectedItem()
{
var expectedItem = CreateRandomPlantData();
serviceStub.Setup(service => service.GetItemAsync(It.IsAny<string>()))
.ReturnsAsync(expectedItem);
var result = await controller.GetItemAsync(expectedItem.Id);
result.Value.Should().BeEquivalentTo(expectedItem, opt => opt.ComparingByMembers<PlantData>());
}
[Fact]
public async Task GetItemAsync_WithExisitingItemByTopic_ReturnsExpectedItem()
{
//Arrange
var expectedItems = new[] { CreateRandomPlantData(), CreateRandomPlantData(), CreateRandomPlantData() };
serviceStub.Setup(s => s.GetItemsFromTopicAsync(It.IsAny<string>())).ReturnsAsync(expectedItems.ToList());
//Act
var actualItems = await controller.GetItemsFromTopicAsync("plant/soilHumidity/2");
//Assert
actualItems.Should().BeEquivalentTo(expectedItems, opt => opt.ComparingByMembers<PlantData>());
}
[Fact]
public async Task GetItemsAsync_WithExisitingItems_ReturnsAllItems()
{
var expectedItems = new[] {CreateRandomPlantData(), CreateRandomPlantData(), CreateRandomPlantData()};
serviceStub.Setup(s => s.GetItemsAsync()).ReturnsAsync(expectedItems.ToList());
var actualItems = await controller.GetItemsAsync();
actualItems.Should().BeEquivalentTo(expectedItems, opt => opt.ComparingByMembers<PlantData>());
}
private PlantData CreateRandomPlantData()
{
return new()
{
Id = "60ae78fd9afed8cbe0b30336",
TimeStamp = DateTime.Now,
Topic = "plant/soilHumidity/2",
Value = new Random().Next(0, 3000)
};
}
}
Controller:
[Route("api/[controller]")]
[EnableCors("MyPolicy")]
[ApiController]
public class PlantDataController : ControllerBase
{
private readonly PlantDataService plantDataService;
private readonly ILogger<PlantDataController> logger;
public PlantDataController(PlantDataService plantDataService, ILogger<PlantDataController> logger)
{
this.plantDataService = plantDataService;
this.logger = logger;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<PlantDataDto>>> GetItemsAsync()
{
var plantData = (await plantDataService.GetItemsAsync()).Select(p => p.AsDto());
var plantDataDtos = plantData.ToList();
logger.LogInformation($"{DateTime.Now.ToString("HH:mm:ss")}: Retrieved {plantDataDtos.ToList()}");
return Ok(plantDataDtos);
}
[HttpGet("{topic}", Name = "GetPlantDataFromTopic")]
public async Task<ActionResult<IEnumerable<PlantDataDto>>> GetItemsFromTopicAsync(string topic)
{
if(string.IsNullOrEmpty(topic))
return NotFound();
var plantData = (await plantDataService.GetItemsFromTopicAsync(HttpUtility.UrlDecode(topic))).Select(p => p.AsDto());
var plantDataDtos = plantData.ToList();
logger.LogInformation($"{DateTime.Now.ToString("HH:mm:ss")}: Retrieved {plantDataDtos.ToList()}");
return Ok(plantDataDtos);
}
[HttpGet("{id:length(24)}", Name = "GetPlantData")]
public async Task<ActionResult<PlantDataDto>> GetItemAsync(string id)
{
if(string.IsNullOrEmpty(id))
return NotFound();
var plantData = await plantDataService.GetItemAsync(id);
if (plantData is null)
{
return NotFound();
}
logger.LogInformation($"{DateTime.Now.ToString("HH:mm:ss")}: Retrieved plantdata: {plantData.Id}");
return Ok(plantData.AsDto());
}
}
Service:
public class PlantDataService : IPlantDataService
{
private readonly IMongoCollection<PlantData> _plants;
public PlantDataService(IPlantStarterDatabaseSettings settings)
{
var client = new MongoClient(settings.ConnectionString);
var database = client.GetDatabase(settings.DatabaseName);
_plants = database.GetCollection<PlantData>(settings.PlantStarterCollectionName);
}
public PlantDataService()
{
}
public virtual async Task<List<PlantData>> GetItemsAsync() => await _plants.Find(plant => true).ToListAsync();
public virtual async Task<PlantData> GetItemAsync(string id) => await _plants.Find(p => p.Id == id).SingleOrDefaultAsync();
public virtual async Task<List<PlantData>> GetItemsFromTopicAsync(string topic) => await _plants.FindAsync(p => p.Topic == topic).Result.ToListAsync();
}
AsDto method:
public static class DtoExtensions
{
public static PlantDataDto AsDto(this PlantData plantdata)
{
return new()
{
Id = plantdata.Id,
TimeStamp = plantdata.TimeStamp,
Topic = plantdata.Topic,
Value = plantdata.Value
};
}
}
Every assert result returns null.
I do not really know what goes wrong here...
The test all went green until some point i changed the Value from int to double, but that seems very strange that is the cause of my unit tests failing because the calls itself just works in swagger and Postman.
I'm new to MediatR, trying to make request validation using pipeline behavior, all the examples that I came across were throwing ValidationException if any errors happening.
below code is an example of validation pipeline:
public class ValidationPipeline<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator> _validators;
public ValidationPipeline(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var context = new ValidationContext<TRequest>(request);
var validationFailures = _validators
.Select(validator => validator.Validate(context))
.SelectMany(validationResult => validationResult.Errors)
.Where(validationFailure => validationFailure != null)
.ToList();
if (validationFailures.Any())
{
throw new FluentValidation.ValidationException(validationFailures);
}
return next();
}
}
this method works fine, but I want to return the response with validation errors (without) throwing exception, so I tried this:
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, BaseResponse<TResponse>>
where TRequest : IRequest<BaseResponse<TResponse>>
{
private readonly IEnumerable<IValidator> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<BaseResponse<TResponse>> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<BaseResponse<TResponse>> next)
{
var context = new ValidationContext<TRequest>(request);
var validationFailures = _validators
.Select(validator => validator.Validate(context))
.SelectMany(validationResult => validationResult.Errors)
.Where(validationFailure => validationFailure != null)
.ToList();
if (validationFailures.Any())
{
return Task.FromResult(new BaseResponse<TResponse>
{
Code = 400,
Message = "Validation error",
Error = validationFailures.Select(err => err.ErrorMessage)
});
}
else
{
return next();
}
}
but now the validation pipeline code does not execute,
and execution go to regular handlers (ex: Register User Handler).
my response (used in all handlers):
public class BaseResponse<TResponse>
{
public int Code { get; set; }
public string Message { get; set; }
public TResponse Result { get; set; }
public object Error { get; set; }
public string TraceIdentifier { get; set; }
}
register the behaviour with DI like this:
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
any help will be appreciated.
I used next method. First of all I'm using Adrdalis.Result or via nuget Ardalist.Result i find it very usefull.
Pipeline code:
public class FluentValidationPipelineBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public FluentValidationPipelineBehaviour(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var requestType = request.GetType();
if (requestType != null)
{
var attribute = requestType.GetCustomAttribute<FluentValidationAttribute>();
if (attribute != null && attribute.IsEnabled)
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(
_validators.Select(v =>
v.ValidateAsync(context, cancellationToken)));
var failures = validationResults
.Where(r => r.Errors.Any())
.SelectMany(r => r.Errors)
.ToList();
if (failures.Any())
{
if (attribute.ThrowExceptionOnError)
{
throw new ValidationException(failures);
}
return GetValidatableResult(failures.AsErrors());
}
}
}
return await next();
}
private static TResponse GetValidatableResult(List<ValidationError> validationErrors)
{
#pragma warning disable CS8603
#pragma warning disable CS8602
#pragma warning disable CS8600
return (TResponse)(typeof(Result<>).MakeGenericType(typeof(TResponse).GetGenericArguments())
.GetMethod("Invalid").Invoke(null, new object?[] { validationErrors }));
#pragma warning restore CS8600
#pragma warning restore CS8602
#pragma warning restore CS8603
}
}
I'm using FluentValidationAttribute to configure fluentvalidation behaviour
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class FluentValidationAttribute : Attribute
{
public bool IsEnabled { get; set; } = true;
public bool ThrowExceptionOnError { get; set; } = false;
}
Use it on Command / Query etc;
It's doesn't trigger because this pipeline doesn't match your IRequest anymore.
In your case TResponse is already BaseResponse<> and you wrapping it once more.
I assume you have the following request structure:
public record TestDto(string Result);
public class TestCommand(int Id, string Name) : IRequest<BaseResponse<TestDto>>;
public class TestCommandHandler : IRequestHandler<TestCommand, BaseResponse<TestDto>>
{
public async Task<BaseResponse<TestDto>> Handle(TestCommand request, CancellationToken cancellationToken)
{
...
}
}
In this case TResponse is BaseResponse<TestDto>.
To solve the problem you can do the following:
Add a constructor with parameters to your BaseResponse<T> like this:
public class BaseResponse<TResponse>
{
public int Code { get; set; }
public string Message { get; set; }
public TResponse Result { get; set; }
public object Error { get; set; }
public string TraceIdentifier { get; set; }
public BaseResponse()
{
}
public BaseResponse(int code, string message, object error)
{
Code = code;
Message = message;
Error = error;
}
}
Then if validation fails you have to create this object. You might use Activator to achieve this.
public class ValidationPipeline<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator> _validators;
public ValidationPipeline(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var context = new ValidationContext<TRequest>(request);
var validationFailures = _validators
.Select(validator => validator.Validate(context))
.SelectMany(validationResult => validationResult.Errors)
.Where(validationFailure => validationFailure != null)
.ToList();
if (validationFailures.Any())
{
var code = 400;
var message = "Validation error";
var error = validationFailures.Select(err => err.ErrorMessage);
return (TResponse)Activator.CreateInstance(typeof(TResponse),
code,
message,
error);
}
return next();
}
}
PublishAsync not work
Example program.cs:
namespace MassTransitKafka
{
class Program
{
private static ServiceProvider _serviceProvider;
static async Task Main(string[] args)
{
var services = new ServiceCollection();
services.AddMassTransit(x =>
{
x.UsingInMemory((context, cfg) =>
{
cfg.ConfigureEndpoints(context);
});
x.AddRider(rider =>
{
rider.AddProducer<Enter1>(nameof(Enter1));
rider.AddProducer<Enter2>(nameof(Enter2));
rider.AddProducer<Enter3>(nameof(Enter3));
rider.AddProducer<EnterEnter>(nameof(EnterEnter));
rider.AddSagaStateMachine<TestSaga1StateMachine, TestSaga1State>(typeof(TestSaga1StateDefinition))
.InMemoryRepository();
rider.UsingKafka((context, k) =>
{
k.Host("localhost:9092");
k.TopicEndpoint<Null, Enter1>(nameof(Enter1), nameof(TestSaga1StateMachine), c =>
{
c.AutoOffsetReset = AutoOffsetReset.Earliest;
c.ConfigureSaga<TestSaga1State>(context);
});
k.TopicEndpoint<Null, Enter2>(nameof(Enter2), nameof(TestSaga1StateMachine), c =>
{
c.AutoOffsetReset = AutoOffsetReset.Earliest;
c.ConfigureSaga<TestSaga1State>(context);
});
k.TopicEndpoint<Null, Enter3>(nameof(Enter3), nameof(TestSaga1StateMachine), c =>
{
c.AutoOffsetReset = AutoOffsetReset.Earliest;
c.ConfigureSaga<TestSaga1State>(context);
});
k.TopicEndpoint<Null, EnterEnter>(nameof(EnterEnter), nameof(TestSaga1StateMachine), c =>
{
c.AutoOffsetReset = AutoOffsetReset.Earliest;
c.ConfigureSaga<TestSaga1State>(context);
});
});
});
});
_serviceProvider = services.BuildServiceProvider();
var busControl = _serviceProvider.GetRequiredService<IBusControl>();
var observer = new ReceiveObserver();
busControl.ConnectReceiveObserver(observer);
await busControl.StartAsync();
var tokenSource = new CancellationTokenSource();
ThreadPool.QueueUserWorkItem(s =>
{
Work(busControl, tokenSource.Token).GetAwaiter().GetResult();
});
while (true)
{
var quit = Console.ReadLine();
if (quit == "quit")
{
tokenSource.Cancel();
break;
}
}
}
private static async Task Work(IPublishEndpoint publisher, CancellationToken token)
{
var correlationId = Guid.NewGuid();
var enter1Producer = _serviceProvider.GetRequiredService<ITopicProducer<Enter1>>();
await enter1Producer.Produce(new {CorrelationId = correlationId, EnteredText = "1"}, token);
while (token.IsCancellationRequested == false)
{
var cancelled = token.WaitHandle.WaitOne(5000);
if (cancelled)
break;
}
}
private static Dictionary<string, string> Configuration
{
get
{
return new Dictionary<string, string>
{
{ "bootstrap.servers", "localhost:9092" },
{ "group.id", "saga.group.id" }
};
}
}
}
}
Example TestSaga1StateMachine.cs
public class TestSaga1StateMachine : MassTransitStateMachine<TestSaga1State>
{
public TestSaga1StateMachine()
{
InstanceState(_ => _.CurrentState);
Event(() => Enter1Event, x => x.CorrelateById(ctx => ctx.Message.CorrelationId));
Event(() => Enter2Event, x => x.CorrelateById(ctx => ctx.Message.CorrelationId));
Event(() => Enter3Event, x => x.CorrelateById(ctx => ctx.Message.CorrelationId));
Event(() => EnterEnterEvent, x => x.CorrelateById(ctx => ctx.Message.CorrelationId));
Initially(
When(Enter1Event)
.Then(context => context.Instance.SaveEnter1(context.Data))
// Messages are not sent here
.PublishAsync(context => context.Init<Enter2>(new {EnteredText = "2"}))
.TransitionTo(Entered1)
);
During(Entered1,
When(Enter2Event)
.Then(context => context.Instance.SaveEnter2(context.Data))
// Messages are not sent here
.PublishAsync(context => context.Init<Enter3>(new {EnteredText = "3"}))
.TransitionTo(Entered2)
);
During(Entered2,
When(Enter3Event)
.Then(context => context.Instance.SaveEnter3(context.Data))
// Messages are not sent here
.PublishAsync(context => context.Init<EnterEnter>(new {EnteredText = "Enter"}))
.TransitionTo(Entered3)
);
During(Entered3,
When(EnterEnterEvent)
.Then(context => context.Instance.Print())
.TransitionTo(EnteredEnter)
.Finalize());
SetCompletedWhenFinalized();
}
public State Entered1 { get; set; }
public State Entered2 { get; set; }
public State Entered3 { get; set; }
public State EnteredEnter { get; set; }
public Event<Enter1> Enter1Event { get; set; }
public Event<Enter2> Enter2Event { get; set; }
public Event<Enter3> Enter3Event { get; set; }
public Event<EnterEnter> EnterEnterEvent { get; set; }
}
This project is just for my learning.
I don't understand how to produce message up there
The bus configuration is identical to the one in the documentation. The first Enter1 message is published successfully and the saga receives it, but how to send a message to kafka from the saga is not clear
You would need to create a custom state machine activity, with a dependency on the producer interface (setup when Kafka is configured), in order to produce messages to Kafka topics. I recently did a video on this as part of Season 2.
You can see an example of producer setup in the unit tests
services.AddMassTransit(x =>
{
x.AddRider(rider =>
{
rider.AddProducer<KafkaMessage>(Topic);
rider.UsingKafka((context, k) =>
{
k.Host("localhost:9092");
});
});
});
Then, in your custom state machine activity, you'd add a constructor dependency on on ITopicProducer<KafkaMessage> and use that to produce the message. It may look similar to this one:
public class ProduceEnter2Activity :
Activity<TestSaga1State>
{
readonly ITopicProducer<Enter2> _producer;
public ProduceEnter2Activity(ITopicProducer<Enter2> producer)
{
_producer = producer;
}
public void Probe(ProbeContext context)
{
context.CreateScope("notifyMember");
}
public void Accept(StateMachineVisitor visitor)
{
visitor.Visit(this);
}
public async Task Execute(BehaviorContext<TestSaga1State> context, Behavior<TestSaga1State> next)
{
await Execute(context);
await next.Execute(context);
}
public async Task Execute<T>(BehaviorContext<TestSaga1State, T> context, Behavior<TestSaga1State, T> next)
{
await Execute(context);
await next.Execute(context);
}
public Task Faulted<TException>(BehaviorExceptionContext<TestSaga1State, TException> context, Behavior<TestSaga1State> next)
where TException : Exception
{
return next.Faulted(context);
}
public Task Faulted<T, TException>(BehaviorExceptionContext<TestSaga1State, T, TException> context, Behavior<TestSaga1State, T> next)
where TException : Exception
{
return next.Faulted(context);
}
async Task Execute(BehaviorContext<TestSaga1State> context)
{
await _producer.Produce(new Enter2(...));
}
}
Then, in your state machine, you would use:
.Activity(x => x.OfInstanceType<ProduceEnter2Activity>())
I have a model called notes that I am feeding into a kendo grid via calls to an interface / repository class. Everything works but it is running synchronously and I want to run it asynchronously.
I'm using .NET core 3.1 so IAsyncEnumerable etc should all be available if I can work out how to do it. I've tried a lot of variations but always get errors. Any help much appreciated.
This is the interface
namespace FliveRetry.Models.PTs
{
public interface IPtNoteRepository
{
IEnumerable<Note> GetAllNotes();
Note GetNoteById(int NoteId);
}
}
This is the repository
namespace FliveRetry.Models.PTs
{
public class PtNoteRepository : IPtNoteRepository
{
private readonly FliveRetryContext context;
public PtNoteRepository(FliveRetryContext context)
{
this.context = context ?? throw new ArgumentNullException(nameof(context));
}
public IEnumerable<Note> GetAllNotes()
{
return context.Note;
}
public Note GetNoteById(int itemId)
{
var note = context.Note.SingleOrDefault(i => i.ID == itemId);
return note;
}
}
}
and this is the index model where I'm calling it and feeding it to the grid via OnPostRead
namespace FliveRetry.Pages.Notes
{
public class IndexModel : NoteSelectPageModel
{
private const int CURRENT_USER_ID = 21; //Fake user id for demo
private readonly IPtNoteRepository rpsNotesRepo;
public static IList<Note> Notes { get; set; }
[BindProperty(SupportsGet = true)]
public NoteScreenEnum? PresetScreen { get; set; }
public IndexModel(IPtNoteRepository rpsNotesData)
{
rpsNotesRepo = rpsNotesData;
}
public void OnGet()
{
IEnumerable<Note> notes;
switch (PresetScreen)
{
case NoteScreenEnum.GeneralNotes:
notes = rpsNotesRepo.GetAllNotes();
break;
case NoteScreenEnum.ThisNote:
notes = rpsNotesRepo.GetNoteByID(CURRENT_USER_ID);
break;
default:
notes = rpsNotesRepo.GetAllNotes();
break;
}
Notes = notes.ToList();
}
public JsonResult OnPostRead([DataSourceRequest] DataSourceRequest request)
{
return new JsonResult(Notes.ToDataSourceResult(request));
}
}
}
In other pages like create or edit.cshtml.cs for example I am successfully using async to edit and create, e.g:
namespace FliveRetry.Pages.Notes
{
public class EditModel : NoteSelectPageModel
{
private readonly FliveRetry.Data.FliveRetryContext _context;
public EditModel(FliveRetry.Data.FliveRetryContext context)
{
_context = context;
}
[BindProperty]
public Note Note { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Note = await _context.Note
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (Note == null)
{
return NotFound();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(IFormCollection form, int? id, string[] selectedOrgs, string[] selectedClients, string[] selectedStaffs, string[] selectedNoteTypes)
{
if (!ModelState.IsValid)
{
return Page();
}
var noteToUpdate = await _context.Note
.FirstOrDefaultAsync(s => s.ID == id);
if (await TryUpdateModelAsync<Note>(noteToUpdate, "note", // Prefix for form value.
c => c.Title, c => c.NoteText, c => c.NoteDate, c => c.Amount, c => c.ImageURL, c => c.FileURL, c => c.Archived, c => c.DateSaved, c => c.UserID, c => c.StartTime, c => c.FinishTime))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
}
}
Try to use below code to convert synchronous action to asynchronous action:
IPtNoteRepository:
public interface IPtNoteRepository
{
Task<IEnumerable<Note>> GetAllNotesAsync();
Task<Note> GetNoteByIdAsync(int NoteId);
}
Repository:
public class PtNoteRepository : IPtNoteRepository
{
private readonly FliveRetryContext context;
public PtNoteRepository(FliveRetryContext context)
{
this.context = context ?? throw new ArgumentNullException(nameof(context));
}
public async Task<IEnumerable<Note>> GetAllNotesAsync()
{
return await context.Note.ToListAsync();
}
public async Task<Note> GetNoteByIdAsync(int itemId)
{
var note = await context.Note.SingleOrDefaultAsync(i => i.ID == itemId);
return note;
}
}
IndexModel:
public async Task OnGetAsync()
{
IEnumerable<Note> notes;
switch (PresetScreen)
{
case NoteScreenEnum.GeneralNotes:
notes = await rpsNotesRepo.GetAllNotesAsync();
break;
case NoteScreenEnum.ThisNote:
notes = await rpsNotesRepo.GetNoteByIdAsync(CURRENT_USER_ID);
break;
default:
notes = await rpsNotesRepo.GetAllNotesAsync();
break;
}
Notes = notes.ToList();
}