Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 681
sl3dg3 Avatar asked Jun 20 '11 13:06

sl3dg3


People also ask

What is custom model binder in MVC?

In the MVC pattern, Model binding maps the HTTP request data to the parameters of a Controllers action method. The parameter can be of a simple type like integers, strings, double etc. or they may be complex types. MVC then binds the request data to the action parameter by using the parameter name.

How can we use custom model binder in asp net core?

You can apply the ModelBinder attribute to individual model properties (such as on a viewmodel) or to action method parameters to specify a certain model binder or model name for just that type or action.

Which interface should be implemented to create custom binder?

To create custom model binder class, it needs to inherit from IModelBinder interface. This interface has async method named "BindModelAsync" and it has parameter of type ModelBindingContext. The ModelBindingContext class provides the context that model binder functions.

What is model binder?

Model binding is a simplistic way to correlate C# code with an HTTP request. The model binding applies to transforming the HTTP request data in the query's form string and form collection of the action method parameters. We can consider these parameters to be primitive type or complex type.


3 Answers

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);
    }

}
like image 120
Darin Dimitrov Avatar answered Nov 11 '22 16:11

Darin Dimitrov


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
}
like image 44
sl3dg3 Avatar answered Nov 11 '22 16:11

sl3dg3


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));
            })
like image 25
Olga Rondareva Avatar answered Nov 11 '22 14:11

Olga Rondareva