Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Model Binder for a custom property type

On an ASP.NET Core project I have the following action:

public async Task<IActionResult> Get(ProductModel model) {
}

public class ProductModel {
  public Filter<Double> Price { get; set; }
}    

I have a Filter base class and a RangeFilter as follows:

public class Filter<T> { }

public class RangeFilter<T> : Filter<T> { 
  public abstract Boolean TryParse(String value, out Filter<T> filter);  
}

I am passing a String ("[3.34;18.75]") to the Action as Price.

I need to create a ModelBinder where I use the TryParse method to try to convert that String into a RangeFilter<Double> to define the ProductModel.Price property.

If TryParse fails, e.g., returns false then the ProductModel.Price property becomes null.

How can this be done?

like image 671
Miguel Moura Avatar asked Jul 22 '16 10:07

Miguel Moura


People also ask

Can we create custom model binder?

We can apply custom model binder using ModelBinder attribute by defining attributes on action method or model. If we are using this method (applying attribute on action method), we need to define this attribute on every action methods those want use this custom binding.

What is custom model binder in MVC?

Custom Model Binder provides a mechanism using which we can map the data from the request to our ASP.NET MVC Model.

What is a model binder?

Model binding is a well-designed bridge between the HTTP request and the C# action methods. Data from HTTP requests are used by controllers and Razor pages. Route data, for example, may serve as a record key, while posted form fields may serve as values for model properties.

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

Model binding allows controller actions to work directly with model types (passed in as method arguments), rather than HTTP requests. Mapping between incoming request data and application models is handled by model binders.


1 Answers

If you're willing to move the parsing method into a static class, this would become a bit more feasible.

Parser

public static class RangeFilterParse
{
    public static Filter<T> Parse<T>(string value)
    {
        // parse the stuff!
        return new RangeFilter<T>();
    }
}

public abstract class Filter<T> { }

public class RangeFilter<T> : Filter<T> { }

Model

public class ProductModel
{
    [ModelBinder(BinderType = typeof(RangeModelBinder))]
    public Filter<double> Price { get; set; }
}

Binder

public class RangeModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        try
        {
            var input = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString();
            var inputType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0];

            // invoke generic method with proper type
            var method = typeof(RangeFilterParse).GetMethod(nameof(RangeFilterParse.Parse), BindingFlags.Static);
            var generic = method.MakeGenericMethod(inputType);
            var result = generic.Invoke(this, new object[] { input });

            bindingContext.Result = ModelBindingResult.Success(result);
            return Task.CompletedTask;
        }
        catch(Exception) // or catch a more specific error related to parsing
        {
            bindingContext.Result = ModelBindingResult.Success(null);
            return Task.CompletedTask;
        }
    }
}
like image 62
Will Ray Avatar answered Sep 16 '22 20:09

Will Ray