I have this OnGet method that runs in a razorpage when I click the approve button.
As you can see below the actual code that runs is in a utility class and as I do not want to break the threading procedure the utility class method also returns the Task
When the execution returns I write the "message" to the browser. My problem is that it is not the Id I want to display for the user. it is another column in sql which is available in the utility class.
So my question here is how can change my call to return a value from the Utility class method?
public async Task<IActionResult> OnGetApproveAsync(int id) {
try {
await Utils.ApproveOrder(Request, HttpContext, _context1, _context2, _cache, id);
Message += "Order #" + id + " Approved\n";
return Redirect("~/Orders/Orders");
} catch (Exception e) {
Message += e.StackTrace.ToString();
return Redirect("~/Orders/Orders");
}
}
Change Utils.ApproveOrder to return Task<Utility> and update model code
public async Task<IActionResult> OnGetApproveAsync(int id) {
try {
Utility util = await Utils.ApproveOrder(Request, HttpContext, _context1, _context2, _cache, id);
//use util
//..
}
Related
I working on a Quarkus + MongoDB Reactive+ Mutiny application. I have a Person object and Event Object. I am creating a new event for a person. My uri looks like this
POST /person/{personId}/event
I need to first check if the person exists in MongoDB. If the person exists then save event. If person does not exist then create a Error Status and return. I am tried everything but I am stuck and getting error that required return type is Uni but required type is Uni. I tried with transformToUni as well but it did not work. Also tried few other ways like onItemOrFailure() etc. but nothing seems to work.
Here's the full Code.
public class EventResource {
#Inject
EventRepository eventRepository;
#Inject
PersonRepository personRepository;
#POST
#Path("/{person_id}/event")
public Uni<Response> create(Event event, #PathParam("person_id") String personId){
//Check if personId exist.
Uni<Person> uniPerson = personRepository.getPersonById(personId);
//THIS WORKS BUT ON FAILURE IS TREATED WHEN ERROR IS RAISED FOR EeventRepository.craete() and not if person is not found.
/*return uniPerson.onItem().ifNotNull()
.transformToUni(pid -> eventRepository.create(event, pid.getId()))
.onItem().transform(e -> Response.ok().entity(e).build())
.onFailure()
.recoverWithItem(f-> {
AStatus status = createErrorStatus(f.getMessage());
return Response.serverError().entity(status).build();
});
*/
Uni<Response> eventResp = uniPerson.onItem().transform(person -> {
if(person==null)
return Response.serverError().build();
else{
return eventRepository.create(event, person.getId())
.onItem().transform(event1 -> Response.ok(event1).build());
}
});
return eventResp;
}
You can use mutiny ifNull:
#POST
#Path("/{person_id}/event")
public Uni<Response> create(Event event, #PathParam("person_id") String personId){
return personRepository
.getPersonById(personId)
.onItem().ifNotNull().transformToUni(person -> createEvent(event, person))
.onItem().ifNull().continueWith(this::personNotFound)
// This onFailure will catch all the errors
.onFailure()
.recoverWithItem(f-> {
AStatus status = createErrorStatus(f.getMessage());
return Response.serverError().entity(status).build();
});
}
private Uni<Response> createEvent(Event event, Person person) {
return eventRepository
.create(event, person.getId())
.map( e -> Response.ok().entity(e).status(CREATED).build())
}
private Response personNotFound() {
return Response.serverError().build();
}
The error you are seeing is because when the item is not null, you are returning a Uni<Uni<Response>>. This is one way to fix it:
Uni<Response> eventResp = uniPerson
.chain(person -> {
if (person==null)
return Uni.createFrom().item(Response.serverError().build());
else {
return eventRepository
.create(event, person.getId())
.map(event1 -> Response.ok(event1).build());
}
});
I'm using map and chain because they are shorter, but you can replace them with onItem().transform(...) and onItem().transformToUni(...).
I have a little problem. But I dont know why it doesnt work. And I dont know how to post all ids by postman.
I am using unit of work with generic repository. I want to send int[] ids to my controller. I dont want to send entity. I searched a lot it today. And I changed my code. But what is problem now?
This is my repostiroy:
public async Task DeleteRangeAsync(Expression<Func<T, bool>> predicate)
{
IQueryable<T> query = _dbSet.Where(predicate);
await Task.Run(() => { _dbSet.RemoveRange(query.AsNoTracking()); });
}
This is my KulturManager:
public async Task<IResult> HardDeleteRangeAsync(int[] ids)
{
await UnitOfWork.Kulturs.DeleteRangeAsync(c => ids.Contains(c.Id));
await UnitOfWork.SaveAsync();
return new Result(ResultStatus.Success, Messages.Info("Kultur", "HardDelete"));
}
And this is my KulturController:
[HttpDelete("{ids}")]
public async Task<IActionResult> HardDeleteRangeAsync(int[] ids)
{
var result = await _kulturManager.HardDeleteRangeAsync(ids);
return Ok(result.Message);
}
Thank you for help
You shouldn't fetch all the entities you want to delete. Instead create stub entities for RemoveRange. If you don't have a common base class, this requires reflection, but with a common entity base class you can do it like this:
public void DeleteRange<T>(int[] ids) where T: BaseEntity, new()
{
_dbSet.RemoveRange(ids.Select(i => new T() { Id = i }).ToList());
}
or if the method is in a generic class, the method would look like
public void DeleteRange(int[] ids)
{
_dbSet.RemoveRange(ids.Select(i => new T() { Id = i }).ToList());
}
And there's no reason to mark this as Async now since it doesn't do any database access.
We are using ASP.NET Zero and are running into issues with parallel processing from an AppService. We know requests must be transactional, but unfortunately we need to break out to slow running APIs for numerous calls, so we have to do parallel processing.
As expected, we are running into a DbContext contingency issue on the second database call we make:
System.InvalidOperationException: A second operation started on this context
before a previous operation completed. This is usually caused by different
threads using the same instance of DbContext, however instance members are
not guaranteed to be thread safe. This could also be caused by a nested query
being evaluated on the client, if this is the case rewrite the query avoiding
nested invocations.
We read that a new UOW is required, so we tried using both the method attribute and the explicit UowManager, but neither of the two worked.
We also tried creating instances of the referenced AppServices using the IocResolver, but we are still not able to get a unique DbContext per thread (please see below).
public List<InvoiceDto> CreateInvoices(List<InvoiceTemplateLineItemDto> templateLineItems)
{
List<InvoiceDto> invoices = new InvoiceDto[templateLineItems.Count].ToList();
ConcurrentQueue<Exception> exceptions = new ConcurrentQueue<Exception>();
Parallel.ForEach(templateLineItems, async (templateLineItem) =>
{
try
{
XAppService xAppService = _iocResolver.Resolve<XAppService>();
InvoiceDto invoice = await xAppService
.CreateInvoiceInvoiceItem();
invoices.Insert(templateLineItems.IndexOf(templateLineItem), invoice);
}
catch (Exception e)
{
exceptions.Enqueue(e);
}
});
if (exceptions.Count > 0) throw new AggregateException(exceptions);
return invoices;
}
How can we ensure that a new DbContext is availble per thread?
I was able to replicate and resolve the problem with a generic version of ABP. I'm still experiencing the problem in my original solution, which is far more complex. I'll have to do some more digging to determine why it is failing there.
For others that come across this problem, which is exactly the same issue as reference here, you can simply disable the UnitOfWork through an attribute as illustrated in the code below.
public class InvoiceAppService : ApplicationService
{
private readonly InvoiceItemAppService _invoiceItemAppService;
public InvoiceAppService(InvoiceItemAppService invoiceItemAppService)
{
_invoiceItemAppService = invoiceItemAppService;
}
// Just add this attribute
[UnitOfWork(IsDisabled = true)]
public InvoiceDto GetInvoice(List<int> invoiceItemIds)
{
_invoiceItemAppService.Initialize();
ConcurrentQueue<InvoiceItemDto> invoiceItems =
new ConcurrentQueue<InvoiceItemDto>();
ConcurrentQueue<Exception> exceptions = new ConcurrentQueue<Exception>();
Parallel.ForEach(invoiceItemIds, (invoiceItemId) =>
{
try
{
InvoiceItemDto invoiceItemDto =
_invoiceItemAppService.CreateAsync(invoiceItemId).Result;
invoiceItems.Enqueue(invoiceItemDto);
}
catch (Exception e)
{
exceptions.Enqueue(e);
}
});
if (exceptions.Count > 0) {
AggregateException ex = new AggregateException(exceptions);
Logger.Error("Unable to get invoice", ex);
throw ex;
}
return new InvoiceDto {
Date = DateTime.Now,
InvoiceItems = invoiceItems.ToArray()
};
}
}
public class InvoiceItemAppService : ApplicationService
{
private readonly IRepository<InvoiceItem> _invoiceItemRepository;
private readonly IRepository<Token> _tokenRepository;
private readonly IRepository<Credential> _credentialRepository;
private Token _token;
private Credential _credential;
public InvoiceItemAppService(IRepository<InvoiceItem> invoiceItemRepository,
IRepository<Token> tokenRepository,
IRepository<Credential> credentialRepository)
{
_invoiceItemRepository = invoiceItemRepository;
_tokenRepository = tokenRepository;
_credentialRepository = credentialRepository;
}
public void Initialize()
{
_token = _tokenRepository.FirstOrDefault(x => x.Id == 1);
_credential = _credentialRepository.FirstOrDefault(x => x.Id == 1);
}
// Create an invoice item using info from an external API and some db records
public async Task<InvoiceItemDto> CreateAsync(int id)
{
// Get db record
InvoiceItem invoiceItem = await _invoiceItemRepository.GetAsync(id);
// Get price
decimal price = await GetPriceAsync(invoiceItem.Description);
return new InvoiceItemDto {
Id = id,
Description = invoiceItem.Description,
Amount = price
};
}
private async Task<decimal> GetPriceAsync(string description)
{
// Simulate a slow API call to get price using description
// We use the token and credentials here in the real deal
await Task.Delay(5000);
return 100.00M;
}
}
Below is my code:
Controller/Action:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(CustomerViewModel model, string returnUrl = null)
{
try
{
ViewData["ReturnUrl"] = returnUrl;
// when debugging the test, _dbContext.Customers throws exception
CustomerDoc existingCustomer = await _dbContext.Customers.Find(o => o.email == model.email).FirstOrDefaultAsync();
if (existingCustomer != null)
{
ModelState.AddModelError("Email", "email already used.");
}
// other checkings
if (!ModelState.IsValid)
{
return View(model);
}
// if model state is valid, do something here
}
catch (Exception ex)
{
return View(model);
}
return View(model);
}
And my unit test code is:
[Fact]
public async Task should_return_view_with_errors_when_email_already_exists()
{
IEnumerable<CustomerDoc> customers = new List<CustomerDoc>
{
new CustomerDoc
{
email = "test#test.com"
}
};
_dbContextMock.SetupAllProperties();
// below line is causing the error
_dbContextMock.Setup(c => c.Customers).Returns(() =>(IMongoCollection<CustomerDoc>)customers);
CustomerViewModel model = new CustomerViewModel
{
email = "test#test.com"
};
CreateController();
var result = await _controller.Register(model);
Assert.IsType<ViewResult>(result);
Assert.False(_controller.ModelState.IsValid);
Assert.True(_controller.ModelState.ContainsKey("Email"));
}
As you can see in my unit test code comment, I am trying to mock a IMongoCollection to return some data. But I am not able to do so because _dbContext.Customers is throwing exception.
How can I mock IMongoCollection to return some predefined data?
I am using
asp.net core 2.1.0
mongodb driver 2.7.0
You declare customers as a List:
IEnumerable<CustomerDoc> customers = new List<CustomerDoc>
but then try to cast it to IMongoCollection
() =>(IMongoCollection<CustomerDoc>)customers
There's two immediate directions (but both have further issues to deal with):
1) Just return the list without the cast
() => customers
but I can't see the type of c.Customers so I suspect this will just move the issue. I'll take a guess that it's IMongoCollection<CustomerDoc> which is why you're trying to do the cast in the first place? This is problematic as the .Returns would need to be be associated with a function performing the equivalent of c.Customer.Find(). Even so, it's probably better than the alternative.
2) Changing the customers variable to a type that implements IMongoCollection.
Option 1 feels like the way to go as option 2 forces you to start dealing with lots of logic that really shouldn't be relevant to this piece of code.
Is it acceptable to re-use a reference to a IReliableCollection or should I request from IReliableStateManager every time I want to use it?
For example, if I have a dictionary that is widely used in my application, is it acceptable to retrieve it once in the RunAsync method and then pass this reference to any method that requires it, e.g:
protected override async Task RunAsync(CancellationToken cancellationToken)
{
_someCollection = StateManager.GetOrAddAsync<IReliableDictionary<int, string>>(
"SomeName");
}
public async Task DoSomething(int id, string message)
{
_someClass.DoSomething(_someCollection, id, message);
}
And then use in a class like so:
public class SomeClass
{
public void DoSomething(IReliableDictionary<int, string> dict, int id, string msg)
{
using (ITransaction tx = StateManager.CreateTransaction())
{
await dict.AddAsync(tx, id, msg);
await tx.CommitAsync();
}
}
}
Or should I request from IReliableStateManager on each call, e.g,
public class SomeClass
{
public void DoSomething(int id, string msg)
{
var dict = StateManager.GetOrAddAsync<IReliableDictionary<int, string>>("SomeName");
using (ITransaction tx = StateManager.CreateTransaction())
{
await dict.AddAsync(tx, id, msg);
await tx.CommitAsync();
}
}
}
Passing a reference seems to work fine from what I can tell but I'm not sure whether this would be considered bad practice and I can't find a definitive answer in the guidelines or documentation
Yes. You can.
You can check this to see how you can even receive notifications when new instance of IReliableState is added to ReliableStateManager.
You can also subscribe for events (if this is a dictionary).