Open Closed

IValidatableObject on DTO ISSUES #580


User avatar
0
mel@quadsoftpa.com created
  • ABP Framework version: 4RC
  • UI type: MVC
  • Tiered (MVC) or Identity Server Seperated (Angular): no

I really don't like doing this validation in the DTO, but I was unable to get it to work any other way. It would be great if the second half of your validation page (https://docs.abp.io/en/abp/latest/Validation) had more detail and specific usage on an alternative method.

Regardless, I'm dealing with a few issues with this approach:

  • there really should be a ValidateAsync(ValidationContext validationContext) method
  • when calling the service (IsIdUnique for example) I'm getting
    probably a simple fix, but the only way I found around it is removing authorization from the service which is obviously not a real fix.

Thanks

  public class CountryCreateDto : IValidatableObject
    {
        private string _id;

        private string _name;

        [Required]
        [StringLength(CountryConsts.IdMaxLength, MinimumLength = CountryConsts.IdMinLength)]
        [RegularExpression(@"^[A-Z''-'\s]{2,3}$", ErrorMessage = "The Country Code must be between two and three uppercase letters.")]
        [DisplayName("Code")]
        public string Id
        {
            get => _id;
            set => _id = value.Trim()
                              .ToUpper();
        }

        [Required]
        [StringLength(CountryConsts.NameMaxLength, MinimumLength = CountryConsts.NameMinLength)]
        public string Name
        {
            get => _name;
            set => _name = value.Trim().ToUpper();
        }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            var service = validationContext.GetRequiredService<ICountryAppService>();

            var validationResults = new List<ValidationResult>();

            var isIdUnique = Task.Run(async () => await service.IsIdUnique(Id))
                                 .Result;

            if (!isIdUnique)
            {
                validationResults.Add(new ValidationResult("Country Code already exists", new[]
                {
                    "Id"
                }));
            }

            var isNameUnique = Task.Run(async () => await service.IsNameUnique( Name))
                                   .Result;

            if (!isNameUnique)
            {
                validationResults.Add(new ValidationResult("Country Name already exists", new[]
                {
                    "Name"
                }));
            }

            return validationResults;
        }
    }

9 Answer(s)
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    In fact, it is not a good practice to do business verification in DTO, DTO should check the value is basically valid(Non-empty, length, range...). You can verify business logic in applications or domain services.

    By the way , don't use Task.Run(async () => await service.IsIdUnique(Id)).Result, If you have to use asynchronous method in synchronous method, please use AsyncHelper.run()...;

  • User Avatar
    0
    mel@quadsoftpa.com created

    I reverted back to my original implementation which does the validation in the application service. However I still need my second question answered because I have the same issue.

    when calling the service from the web project I can successfully use Service.UpdateAsync but Service.ValidateAsync needs permissions. How do I configure a proper permission?

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    No way. You can write a private method without permission. like :

    private IBookStoreRepository _bookStoreRepository;
    
    .....
    
    public async Task Create(CreateBookDto createDto)
    {
         await CheckNameExists(createDto.Name);
         
         .....
    }
    
    private async Task CheckNameExists(string bookName)
    {
        var book = await  _bookStoreRepository.GetByName(bookName);
        if(book!=null) {
           throw new UserfriendException("the book already exists ")
        }
    }
    
  • User Avatar
    0
    mel@quadsoftpa.com created

    I find that hard to believe that the only methods you can call our the base CRUD. Certainly you should be able to add a permission and be able to call a custom method in the Application Service.

    Without this flexibility, that means having repetitive code many many times. I want to use the base CRUD but validate. This should be very very basic.

  • User Avatar
    0
    mel@quadsoftpa.com created

    I don't want to have to duplicate every base crud like create below just to do validation. There's got to be a better way. Please advise.

        public virtual async Task&lt;TGetOutputDto&gt; CreateAsync(TCreateInput input)
        {
            await CheckCreatePolicyAsync();
    

    *** VALIDATE***

            var entity = await MapToEntityAsync(input);
    
            TryToSetTenantId(entity);
    
            await Repository.InsertAsync(entity, autoSave: true);
    
            return await MapToGetOutputDtoAsync(entity);
        }
    
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    when calling the service from the web project I can successfully use Service.UpdateAsync but Service.ValidateAsync needs permissions. How do I configure a proper permission?

    If ValidateAsync requires permissions, then you must configure this permission for the current user. I think this is correct. If you don't have permission but you can call the method, it will be a bug.

    You don't have to completely override the CreateAsync method.

    private IBookStoreRepository _bookStoreRepository;
    
    .....
    
    public async override Task CreateAsync(CreateBookDto createDto)
    {
         // VALIDATE
         await CheckNameExists(createDto.Name);
         
         // Call base createAsync method
         await base.CreateAsync(createDto);
    }
    
    private async Task CheckNameExists(string bookName)
    {
        var book = await  _bookStoreRepository.GetByName(bookName);
        if(book!=null) {
           throw new UserfriendException("the book already exists ")
        }
    }
    
  • User Avatar
    0
    mel@quadsoftpa.com created

    Thank you for your response. I actually just switched to that. I'd rather be able to call it separately, but this will work.

    my goal was to get a List<ValidationResult>. but I'm hoping the following will work instead

                var exceptions = new List<Exception>();
                var isIdUnique = await IsCodeUnique(dto.Id, dto.Code);
    
                if (!isIdUnique)
                {
                    exceptions.Add(new UserFriendlyException("Country Code already exists"));
                }
    
                var isNameUnique = await IsNameUnique(dto.Id, dto.Name);
    
                if (!isNameUnique)
                {
                    exceptions.Add(new UserFriendlyException("Country Name already exists"));
                }
    
                if (exceptions.Any())
                {
                    throw new AggregateException(exceptions);
                }
    
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Of course you can, Do you still have questions?

  • User Avatar
    0
    mel@quadsoftpa.com created

    no, ty

Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.0.0-preview. Updated on June 20, 2025, 07:57