sl3dg3
sl3dg3

Reputation: 5190

Call Default Model Binder from a Custom Model Binder?

I have written a Custom Model Binder which is supposed to map Dates, coming from URL-Strings (GET) according to the current culture (a sidenote here: the default model binder does not consider the current culture if you use GET as http-call...).

public class DateTimeModelBinder : IModelBinder
{

    #region IModelBinder Members
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {

        if (controllerContext.HttpContext.Request.HttpMethod == "GET")
        {
            string theDate = controllerContext.HttpContext.Request.Form[bindingContext.ModelName];
            DateTime dt = new DateTime();
            bool success = DateTime.TryParse(theDate, System.Globalization.CultureInfo.CurrentUICulture, System.Globalization.DateTimeStyles.None, out dt);
            if (success)
            {
                return dt;
            }
            else
            {
                return null;
            }
        }

        return null; // Oooops...

    }
    #endregion
}

I registered the model binder in global.asax:

ModelBinders.Binders.Add(typeof(DateTime?), new DateTimeModelBinder());

Now the problem occurs in the last return null;. If I use other forms with POST, it would overwrite the already mapped values with null. How can I avoid this?

Thx for any inputs. sl3dg3

Upvotes: 8

Views: 4580

Answers (3)

Olga Rondareva
Olga Rondareva

Reputation: 61

One more possible solution is pass some of the best default model bidners into custom and call it there.

public class BaseApiRequestModelBinder : IModelBinder
{
    private readonly IModelBinder _modelBinder;

    public BaseApiRequestModelBinder(IModelBinder modelBinder)
    {
        _modelBinder = modelBinder;
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        //calling best default model binder
        await _modelBinder.BindModelAsync(bindingContext);

        var model = bindingContext.Result.Model as BaseApiRequestModel;
        //do anything you want with a model that was bind with default binder
    }
}


public class BaseApiRequestModelBinderProvider : IModelBinderProvider
{
    private IList<IModelBinderProvider> _modelBinderProviders { get; }

    public BaseApiRequestModelBinderProvider(IList<IModelBinderProvider> modelBinderProviders)
    {
        _modelBinderProviders = modelBinderProviders;
    }

    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (context.Metadata.ModelType == typeof(BaseApiRequestModel) || context.Metadata.ModelType.IsSubclassOf(typeof(BaseApiRequestModel))) 
        {
            //Selecting best default model binder. Don't forget to exlude the current one as it is also in list
            var defaultBinder = _modelBinderProviders
                    .Where(x => x.GetType() != this.GetType())
                    .Select(x => x.GetBinder(context)).FirstOrDefault(x => x != null);

            if (defaultBinder != null)
            {
                return new BaseApiRequestModelBinder(defaultBinder);
            }
        }

        return null;
    }



 //Register model binder provider in ConfigureServices in startup
        services
            .AddMvc(options => {                 
                options.ModelBinderProviders.Insert(0, new BaseApiRequestModelBinderProvider(options.ModelBinderProviders));
            })

Upvotes: 4

sl3dg3
sl3dg3

Reputation: 5190

Well, it is actually a trivial solution: I create a new instance of the default binder and pass the task to him:

public class DateTimeModelBinder : IModelBinder
{

#region IModelBinder Members
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{

    if (controllerContext.HttpContext.Request.HttpMethod == "GET")
    {
        string theDate = controllerContext.HttpContext.Request.Form[bindingContext.ModelName];
        DateTime dt = new DateTime();
        bool success = DateTime.TryParse(theDate, System.Globalization.CultureInfo.CurrentUICulture, System.Globalization.DateTimeStyles.None, out dt);
        if (success)
        {
            return dt;
        }
        else
        {
            return null;
        }
    }

    DefaultModelBinder binder = new DefaultModelBinder();
    return binder.BindModel(controllerContext, bindingContext);

}
#endregion
}

Upvotes: 5

Darin Dimitrov
Darin Dimitrov

Reputation: 1039428

Derive from DefaultModelBinder and then invoke the base method:

public class DateTimeModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        // ... Your code here

        return base.BindModel(controllerContext, bindingContext);
    }

}

Upvotes: 7

Related Questions