3

Say I have these classes:

public class User
{
    public int? Id { get; set; }
    ...
}

public class Wallet
{
    public int? Id { get; set; }
    public string Title {get; set;}
    public User Owner { get; set; }
    ...
}

public class WalletCreationDTO 
{
    [Required]
    public string Title {get; set;}
    [Required]
    public int OwnerId { get; set; }
}

[HttpPost]
public async Task<ActionResult> CreateWallet(WalletCreationDto walletDTO) {...}

I must report any erroneous parameters to the frontend like so:

{  
    "errors": {  
        "ownerId": "User by Id does not exist",  
        "title": "Must not exceed 8 characters"  
    }  
}

How do I go about validating the OwnerId?

  • Manually querying the database seems ineffective
  • The default ASP.NET Core validation doesn't seem to have any annotations related to database access
vernou
  • 6,818
  • 5
  • 30
  • 58
anxsns
  • 51
  • 3
  • 1
    I would suggest that you should try FluentValidation. For server side validation I find it quite useful. Just to make note that this is not advertisement. – dotnetstep Mar 29 '21 at 15:25
  • It seems good but it seems messy to include fluent validation in controllers, since i'm validating using dabatase context – anxsns Mar 29 '21 at 15:43
  • You can create a custom validator (implement IValidation) and [retrieve the context](https://stackoverflow.com/questions/39627956/inject-dependencies-into-validation-attribute-web-api-asp-net-core) to do the check – vernou Mar 29 '21 at 15:52
  • 1
    seems pretty messy – anxsns Mar 29 '21 at 16:20

2 Answers2

2

I ended up using fluent validation as suggested. It's not the most efficient way to do this but I'm taking the tradeoff for pretty errors and simple code.

public class WalletCreateDTO
{
    [Required]
    [MaxLength(20)]
    public string Title { get; set; }

    [Required]
    public int OwnerId { get; set; }

    [Required]
    public string CurrencyCode { get; set; }
}

public class WalletCreateDTOValidator : AbstractValidator<WalletCreateDTO>
{
    public WalletCreateDTOValidator(Data.Database database)
    {
        this.RuleFor(w => w.OwnerId)
            .Must(ownerId => database.Users.Any(user => user.Id == ownerId))
            .WithMessage("User does not exist");
        this.RuleFor(w=> w.CurrencyCode)
            .Must(currencyCode => database.Currencies.Any(currency => currency.Code == currencyCode))
            .WithMessage("Currency does not exist");
    }
}

--- To anyone reading this in the future ---

NuGet package: FluentValidation.AspNetCore

Configuration:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
        .AddFluentValidation(config =>
        {
            config.RegisterValidatorsFromAssembly(typeof(WalletCreateDTO).Assembly);
        });
}

Note: This seems to register all the validators in the assembly. For example, I have all my DTOs in Api.Dtos directory/namespace, and it seems to register them all, which is convenient.

Also, there's swagger integration: MicroElements.Swashbuckle.FluentValidation

anxsns
  • 51
  • 3
0

You can use this in simple mode.

        [HttpPost]
        public async Task<ActionResult> CreateWallet(WalletCreationDto walletDTO)
        {
            bool isValidOwnerId = /*validationLogic => can use service or Repository or etc... */

            if (isValidOwnerId == false)
                ModelState.AddModelError("OwnerId", "errorMessage");


            if (ModelState.IsValid)
            {
                //CreateWallet Loigc......
            }

            //we can get all errors from ModelState
            List<ModelError> allErrors = ModelState.Values.SelectMany(v => v.Errors).ToList();

            /*
             return .... 
            */
        }

I also suggest you read the following two links.

IValidatableObject

fluentvalidation : popular .NET library for building strongly-typed validation rules.

Reza Basereh
  • 103
  • 6
  • Seems nice, although I'd need references to other services that I'd only use to validate one property. Seems like there really is no way to utilize the database's own way of validating input – anxsns Mar 29 '21 at 16:19
  • If you mean of "database's own way" , this is =>`_context.Users.Any(e => e.Id == OwnerId);` , you can use the same method (_context type can be DbContext or IdentityDbContext ) – Reza Basereh Mar 29 '21 at 16:53