13

I am converting an MVC 5 project over to core. I currently have a custom model binder that I use as my nhibernate entity model binder. I have the option to fetch and bind by fetching the entity out of the database then calling the base DefaultModelBinder to bind modified data from the request into the entity.

Now I am trying to implement IModelBinder... I can fetch the entity just fine. But how do I call the "default model binder" in order to bind the rest of the form data when I no longer have a base DefaultModelBinder to call?

Thanks in advance!

Nathan Greneaux
  • 141
  • 1
  • 5
  • You can achieve the same result using JsonConverter: https://stackoverflow.com/questions/1718501/best-way-to-trim-strings-after-data-entry-should-i-create-a-custom-model-binder – Milan Raval Jul 10 '18 at 16:42
  • This link may help https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-2.1 – Fernando Gonzalez Sanchez Aug 30 '18 at 22:07

2 Answers2

10

You can do something like this:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;

namespace Media.Onsite.Api.Middleware.ModelBindings
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(options =>
            {
                // add the custom binder at the top of the collection
                options.ModelBinderProviders.Insert(0, new MyCustomModelBinderProvider());
            });
        }
    }

    public class MyCustomModelBinderProvider : IModelBinderProvider
    {
        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context.Metadata.ModelType == typeof(MyType))
            {
                return new BinderTypeModelBinder(typeof(MyCustomModelBinder));
            }

            return null;
        }
    }

    public class MyCustomModelBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }

            if (bindingContext.ModelType != typeof(MyType))
            {
                return Task.CompletedTask;
            }

            string modelName = string.IsNullOrEmpty(bindingContext.BinderModelName)
                ? bindingContext.ModelName
                : bindingContext.BinderModelName;

            ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
            if (valueProviderResult == ValueProviderResult.None)
            {
                return Task.CompletedTask;
            }

            bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

            string valueToBind = valueProviderResult.FirstValue;

            if (valueToBind == null /* or not valid somehow*/)
            {
                return Task.CompletedTask;
            }

            MyType value = ParseMyTypeFromJsonString(valueToBind);

            bindingContext.Result = ModelBindingResult.Success(value);

            return Task.CompletedTask;
        }

        private MyType ParseMyTypeFromJsonString(string valueToParse)
        {
            return new MyType
            {
                // Parse JSON from 'valueToParse' and apply your magic here
            };
        }
    }

    public class MyType
    {
        // Your props here
    }

    public class MyRequestType
    {
        [JsonConverter(typeof(UniversalDateTimeConverter))]
        public MyType PropName { get; set; }

        public string OtherProp { get; set; }
    }
}
Dmitry Pavlov
  • 30,789
  • 8
  • 97
  • 121
2

I was in exactly same situation. I needed to instantiate object from database and then let MVC/Core bind all the properties for me. After hours of googling and "stackoverflowing" this is what I built:

public class MyBinder : ComplexTypeModelBinder
{
    public MyBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders) : base(propertyBinders, new LoggerFactory()) { }

    protected override object CreateModel(ModelBindingContext bindingContext)
    {
        return Get_Object_From_Database(); //custom creation logic
    }
}

public class MyBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        //make sure I use it with my type only
        if (context.Metadata.ModelType == typeof(My_Object))
        {
            //get property binders for all the stuff
            var propertyBinders = context.Metadata.Properties.ToDictionary(p => p, p => context.CreateBinder(p));

            return new MyBinder(propertyBinders);
        }

        return null;
    }
}

//startup.cs
services.AddMvc(option =>
{
    option.ModelBinderProviders.Insert(0, new MyBinderProvider());
});

ComplexTypeModelBinder is marked obsolete, but MS team has said on github that this means the type should not be used internally by MS, but it will stay.

Tested successfully with .NET 5

Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149