Search code examples
c#asp.net-corelogicbusiness-logic

How can I generalize my recurring business rules in .NET Core?


I am looking for a better approach for my recurring business rules. For example, when adding a category or adding a user, I check the existence of the same user every time.

Sample code:

var users = await _userService.GetAll();

if (users.Any(createUserDto.UserName))
{
    // ...
}

var products = await _productService.GetAll();

if (products.Any(createProductDto.ProductModelKey))
{
    // ...
}

From here, I want to generalize the same monotonous operations. For example, how can I do it with a method that I can use as attribute or with cross cutting?

What I want to do here is to put an attribute in the controller and create a method that will automatically query the relevant table, the column name of the table to be queried, and the query type (is it unique, has it been created before, etc.) from the enums, based on this attribute.


Solution

  • You can do it many ways, however as mentioned the validation needs to be done on the database side, not on the client side for many performance reasons.

    Here are two ways to generalise it:

    1. Using entity repository

    If you choose this, it's mandatory that all of your entities must be able to do exactly the same thing, otherwise, this solution will go haywire fast.

    You create a generic entity repository, which is capable of saving, loading, and manipulating your entities with your database.

    1. Using validator class

    In this case, you create a generic class which you will use to validate certain generic things for your classes. You inject this validator class to the other classes where you have to do the validation.

    In both cases, you have to implement a specific interface for your classes that will allow you to enable these classes (one of them) to handle your class.

    For example:
    To be able to validate a user's existence, you will use email likely, but for content, you will use uuid. Somehow your generic class needs to know, and one easy way to provide this information is if your user and content class implements that specific interface which will define methods which allow you to get these data. Once you have it you wire it together the right way and you are done.

    A poor concept (which is not the best implementation):

    interface ICanValidated {
        string GetValidation(string typeIdentifier);
    }
    
    public class EntityValidator<T>
        where T : ICanValidated {
        
        private SqlConnection _database;
        
        public EntityValidator(SqlConnection database) {
            _database = database;
        }
        
        public bool ValidateIfExists(T entity) {
            using var command = _database.CreateCommand()
            
            command.CommandText = $"SELECT count(*) FROM {entity.GetValidation(\"table\")} WHERE {entity.GetValidation(\"field\")} = @field";
            command.Parameters.Add(new SqlParameter("@field", entity.GetValidation("value")));
            
            var count = command.Execute();
            
            return count > 0;
        }
    }
    

    Make sure that the table and field are safe, also not a user-editable thing, otherwise, you highly risking SQL injection!