Search code examples
c#.net-corecontrollermiddleware

How can I prepopulate certain properties in a model before a method is executed within a Controller


I have multiple input models, and the majority of them include 'OwnerId' and 'CreatedBy' properties. I am seeking a mechanism to automatically populate these properties with default values. I attempted to use middleware, but I encountered difficulties in locating the mentioned properties to populate them.

[HttpPost]
public async Task<bool> ExportPackingListProductBulkInsert(PackingListProductFromSalePermitInput input)
{
    // input.OwnerId = 10;
    // input.CreatedBy = get current date
    // Inserting process
    return await true;
} 

I have a substantial number of Actions where their inputs need to be populated before the Action is executed. Is there a mechanism to automatically populate these inputs?


Solution

  • You could inherit from ActionFilterAttribute and tag your API method. Then, override OnActionExecuting method on this attribute.

    OnActionExecuting is called right before entering the tagged API method.

    You can access method arguments via ActionExecutingContext.ActionArguments which is a Dictionary<string, object> object. Its key is the argument name, here "input". You will need to cast the value to the right type, ie PackingListProductFromSalePermitInput or one of its base classes.

    Something like this:

    [AttributeUsage(AttributeTargets.Method)]
    public class FillAttribute : ActionFilterAttribute
    {
    
        public override void OnActionExecuting(ActionExecutingContext pContext)
        {
            var found = pContext.ActionArguments.TryGetValue("input", out var argument);
    
            if (found && argument is PackingListProductFromSalePermitInput input)
            {
                input.OwnerId = 10;
                input.CreatedBy = DateTime.UtcNow;//get current date
            }
    
            base.OnActionExecuting(pContext);
        }
    }
    

    For your API method:

    [HttpPost]
    [Fill]
    public async Task<bool> ExportPackingListProductBulkInsert(PackingListProductFromSalePermitInput input)
    {
        // Inserting process
        return await true;
    }
    

    You can also inherit from IObjectModelValidator and add it as middleware in ConfigureServices method

    .ConfigureServices(services =>
    {
       services.AddSingleton<IObjectModelValidator, FillValidator>();
    }
    

    You would not have to tag every API method in this case, but it will be called for every endpoint.

    public class FillValidator : IObjectModelValidator
    {
        public void Validate(ActionContext pActionContext, ValidationStateDictionary? pValidationState, string pPrefix, object? pModel)
        {
            if(pModel is PackingListProductFromSalePermitInput input)
            {
                input.OwnerId = 10;
                input.CreatedBy = DateTime.UtcNow;//get current date
            }
        }
    }
    

    The third option, and certainly the easiest, is to fill in the properties in the object's constructor. But I'm sure you already are aware of this :)

    EDIT

    In order to use IObjectModelValidator efficiently with many models, you could use polymorphism this way

    public class BaseModel
    {
        public virtual void Fill() 
        {
        }
    }
    
    
    public class PackingListProductFromSalePermitInput : BaseModel
    {
        public override void Fill()
        {
            this.OwnerId = 10;
            this.CreatedBy = DateTime.UtcNow;//get current date
        }
    }
    
    public class OtherModel : BaseModel
    {
        public override void Fill()
        {
            //TODO fill in what you want
        }
    }
    
    public class FillValidator : IObjectModelValidator
    {
        public void Validate(ActionContext pActionContext, ValidationStateDictionary? pValidationState, string pPrefix, object? pModel)
        {
            if(pModel is BaseModel model)
            {
                model.Fill();
            }
        }
    }