Base solution for your next web application
Open Closed

ICustomValidate with DB access #6217


User avatar
0
rbarbosa created

Started reading https://support.aspnetzero.com/QA/Questions/2583 and could not find a way to open it again.

When sending a list or array of complex objects ABP will let you know the index of the object and the property that is having issues, I am trying to use ICustomValidate to hook up to this behavior and check for duplicates on the data, among other things that need a roundtrip to the DB

eg:

    public class AddSkuInput : ICustomValidate
    {
        [MaxLength(35)]
        [Required]
        public string Sku { get; set; }
        
        
        
        public void AddValidationErrors(CustomValidationContext context)
        {
        
            var skuManager = context.IocResolver.Resolve<ISkuManager>();
            var productTypeManager = context.IocResolver.Resolve<IProductTypeManager>();

            try
            {

                AsyncHelper.RunSync(() => skuManager.ValidateDuplicateSkuAsync(Sku)); 
            }
            catch (UserFriendlyException e)
            {
                context.Results.Add(new ValidationResult(e.Message));
            }
      }
    }
    
  
      
      //for brevity this is the ValidateDuplicateSkuAsync Method
      
      public async Task ValidateDuplicateSkuAsync(string input)
        {
            if (await _skuRepository.GetAll().AnyAsync(p => p.Sku == input))
            {
                throw new UserFriendlyException($"SKU {input} already exists.");
            }
        }

With or without AsyncHelper it produces DbContext.Disposed Errors

Users normally send 100s of these items and I would like to deal with the majority of errors in batches too so I'd like to maintain the validation error structure like this response example:

{
    "result": null,
    "targetUrl": null,
    "success": false,
    "error": {
        "code": 0,
        "message": "Your request is not valid!",
        "details": "The following errors were detected during validation.\r\n - The Sku field is required.\r\n - The Upc field is required.\r\n - The Upc field is required.\r\n",
        "validationErrors": [
            {
                "message": "The Sku field is required.",
                "members": [
                    "[0].Sku"
                ]
            },
            {
                "message": "The Upc field is required.",
                "members": [
                    "[0].Upc"
                ]
            },
             {
                "message": "The Upc field is required.",
                "members": [
                    "[1].Upc"
                ]
            }
        ]
    },
    "unAuthorizedRequest": false,
    "__abp": true
}

If keeping Database access out of the DTO is a must, how can i access the ValidationContext after the method has reached the AppService (or the DomainService) as I'd like to maintain the DataAnnotations style error result through every validation process.


3 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @rbarbosa

    You can manually begin a unitOfWork in ValidateDuplicateSkuAsync as exmplained here https://aspnetboilerplate.com/Pages/Documents/Unit-Of-Work#iunitofworkmanager.

    You can also consider using DbLevel unique index for preventing duplicate records.

  • User Avatar
    0
    rbarbosa created

    Thank you @ismcagdas that solves the UOW issue. Happy new year!

    However I dont seem to be able to find a way to get the index of the property's parent when an array of them are being sent, as shown here:

    https://github.com/aspnetboilerplate/aspnetboilerplate/blob/master/src/Abp.Web.Api/WebApi/Validation/WebApiActionInvocationValidator.cs#L33

    public void AddValidationErrors(CustomValidationContext context)
            {
    
                var skuManager = context.IocResolver.Resolve<ISkuManager>();
                var productTypeManager = context.IocResolver.Resolve<IProductTypeManager>();
               // var actionContext = context.IocResolver.Resolve<HttpActionContext>();
                try
                {
    
                    AsyncHelper.RunSync(() => skuManager.ValidateDuplicateSkuAsync(Sku));
                }
                catch (UserFriendlyException e)
                {
                    context.Results.Add(new ValidationResult(e.Message, new[] {"Sku" }));
                }
            }
    
    {
        "result": null,
        "targetUrl": null,
        "success": false,
        "error": {
            "code": 0,
            "message": "Your request is not valid!",
            "details": "The following errors were detected during validation.\r\n - SKU duplicateSku already exists.\r\n - UPC: duplicateUpc already exists for a different SKU\r\n - Division: BadDivision not found.\r\n - SKU duplicateSku already exists.\r\n - UPC: duplicateUPC already exists for a different SKU\r\n",
            "validationErrors": [
                {
                    "message": "SKU duplicateSku already exists.",
                    "members": [
                        "sku"
                    ]
                },
                {
                    "message": "UPC: duplicateUpc already exists for a different SKU",
                    "members": null
                },
                {
                    "message": "Division: badDivision not found.",
                    "members": null
                },
                {
                    "message": "SKU duplicateSku already exists.",
                    "members": [
                        "sku"
                    ]
                },
                {
                    "message": "UPC: duplicateUpc already exists for a different SKU",
                    "members": null
                }
            ]
        },
        "unAuthorizedRequest": false,
        "__abp": true
    }
    

    is it better to abandon this validation style?

  • User Avatar
    0
    ismcagdas created
    Support Team

    I think it is because you are not providing the second parameter when adding ValidationResult; context.Results.Add(new ValidationResult(e.Message));

    If there will be 100s of items in request or requests, I would try to find another approach for validation. In that case, this approach will slow down your requests.