1
\$\begingroup\$

I recently created a custom model binder as a training exercise and wanted to see how I could improve it. It currently uses recursion and works great, but I was wondering how I could convert this to be non-recursive (maybe by using stack collection?). Any other ways this could be improved? Thank you for your feedback!

    public class ViewModelBinder<T> : IModelBinder where T: class 
{
    #region Private Fields

    private ModelBindingContext _bindingContext;
    private ControllerContext _controllerContext;

    #endregion

    /// <summary>
    /// ADDS MODEL STATE ERRORS
    /// </summary>
    /// <param name="name">Name of Error</param>
    /// <param name="message">Error Message</param>
    private void AddModelError(string name, string message)
    {
        _bindingContext.ModelState.AddModelError(name, message);
    }

    /// <summary>
    /// Recursively Create View Model Class, Instantiating classes along the way and populating their values
    /// </summary>
    /// <param name="_returnValue">Class to create - first = Type of T</param>
    private void CreateModel(ref object _returnValue)
    {
        foreach (var item in _returnValue.GetType().GetProperties().ToList())
        {
            var itemType = item.PropertyType;
            if (!itemType.IsPrimitive && !itemType.FullName.StartsWith("System."))
            {
                var innerClass = Activator.CreateInstance(itemType);
                CreateModel(ref innerClass);
                item.SetValue(_returnValue, innerClass, null);
            }
            else
            {
                if (HasValue(_controllerContext.HttpContext.Request.Form, item.Name))
                    // if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {…}  
                    item.SetValue(_returnValue, Convert.ChangeType(_controllerContext.HttpContext.Request.Form[item.Name], item.PropertyType), null);
            }
        }

        // VALIDATE MAIN VIEW MODEL
        ValidateModel(_returnValue);
    }

    /// <summary>
    /// GENERATE VIEW MODEL
    /// </summary>
    /// <param name="controllerContext">Contains controller information</param>
    /// <param name="bindingContext">Contains binding information</param>
    /// <returns>vIEW MODEL OF TYPE T</returns>
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        _bindingContext = bindingContext;
        _controllerContext = controllerContext;
        var returnValue = Activator.CreateInstance(typeof(T));

        CreateModel(ref returnValue);

        return returnValue;
    }

    /// <summary>
    /// Check to see if key is in form collection
    /// </summary>
    /// <param name="form">Request Form</param>
    /// <param name="key">Key to key to check for (name of property)</param>
    /// <returns>bool - if true then key is in collection</returns>
    private bool HasValue(NameValueCollection form, string key)
    {
        return form.AllKeys.ToList().Contains(key);
    }

    /// <summary>
    /// create a validation context for a given model and check for validation errors
    /// </summary>
    /// <param name="model">model to validate</param>
    private void ValidateModel(object model)
    {
        var vc = new ValidationContext(model);
        var results = new List<ValidationResult>();
        var isValid = Validator.TryValidateObject(model, vc, results, true);

        if (isValid) return;

        foreach (var error in results)
            AddModelError(error.ErrorMessage, error.ErrorMessage);
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.