- ABP Framework version: v7.2.2
- UI Type: MVC
- Database System: EF Core (SQL Server)
- Tiered (for MVC) or Auth Server Separated (for Angular): no
In our AppService methods like CreateAsync and UpdateAsync we have custom logic that may throw an AbpValidationException
. We also have fluent validation logic on Domain entities that may throw an AbpValidationException
where we explicitly validate the entity after mapping using IObjectValidator.ValidateAsync. Here is an example UpdateAsync method showing the common pattern we are using:
public async Task UpdateAsync(Guid id, UpdateCustomerDto input)
{
var entity = await _customerRepository.GetAsync(id);
entity = ObjectMapper.Map<UpdateCustomerDto, Customer>(input, entity);
entity = await _customerManager.UpdateAsync(
entity
);
// Validate the entity.
await _objectValidator.ValidateAsync(entity);
await _customerRepository.UpdateAsync(entity);
}
When we call this AppService method via REST api we get the expected behavior. Any AbpValidationException
causes a 400 and shows the validation messages in the api JSON response. However, we are experiencing an issue in our Razor Page UI that injects this AppService and calls UpdateAsync in a Post page handler.
In our Razor Page, the user experience we want when an AbpValidationException
is thrown should be the same as a traditional ModelState validation error. The default behavior shows the user a 400 error page. We have tried catching the AbpValidationException
in our page handler and adding the errors to the ModelState. This gives us the desired UI behavior but has other side effects. Here is an example page handler implementation.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
try
{
await _customerAppService.UpdateAsync(CustomerGuid, EditCustomer);
}
catch (AbpValidationException ex)
{
foreach (var error in ex.ValidationErrors)
{
ModelState.AddModelError($"EditCustomer.{error.MemberNames.FirstOrDefault()}", error.ErrorMessage);
}
return Page();
}
return RedirectToPage("Index");
}
The problem we are experiencing with this implementation is that even though the exception was thrown, because we catch it, the changes are still being saved to the database. This is being caused because we are mapping to the domain entity then validating it. At this point we are stuck on how to get our desired user experience without allowing the entity to be incorrectly saved.
What guidance or advise do you have on how to properly implement our use case?
7 Answer(s)
-
0
Hi,
You can consider call the
UnitOfWorkManager.Current.RollbackAsync();
method. -
0
Ok, I thought we might be overlooking a more automatic way to take care of it. If our fluent validation rules were also on the Dto and not just the domain object would the app service automatic validation also apply those rules? Is duplicating the rules from the domain in that way recommended so we don't have to explicitly call
await _objectValidator.ValidateAsync(entity);
? -
0
If our fluent validation rules were also on the Dto and not just the domain object would the app service automatic validation also apply those rules? Is duplicating the rules from the domain in that way recommended so we don't have to explicitly call await _objectValidator.ValidateAsync(entity);?
Yes, and validate DTO is the recommended way.
ABP will automatically find this class and associate with the CreateUpdateBookDto on object validation.
https://docs.abp.io/en/abp/latest/FluentValidation#using-the-fluentvalidation
-
0
Thank you. I have one more follow-up related to this. In our Domain Managers we confirm that key references to other entities are valid. In some cases these are loose relationships to entities defined in other modules. For example, a Customer may have a SalesRepId. Currently we verify these are valid Ids in our domain manager CreateAsync and UpdateAsync using repositories for entities in the same module and app services for those in other modules and throw an exception if they are not. Is there a recommended approach to this?
We specifically used this method of implementation because the Validation documentation recommends not to add this type of validation on the Dto. https://docs.abp.io/en/abp/latest/Validation#resolving-a-service
-
0
Hi,
Yes, you shouldn't validate on the Dto.
We recommend validating on domain service: https://docs.abp.io/en/abp/latest/Domain-Services#application-services-vs-domain-services
-
0
Ok, that is how we are doing it. If the related keys are not valid do you typically throw a
BusinessException
or aAbpValidationException
? We originally implemented customBusinessException
classes but are thinking of swapping to anAbpValidationException
for consistency with the other errors. -
0
both do the job.