Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replacing DefaultModelBinder in ASP.net MVC core

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!

like image 344
Nathan Greneaux Avatar asked Jun 17 '18 03:06

Nathan Greneaux


People also ask

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

In this article 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.

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 FromForm?

The FromForm attribute is for incoming data from a submitted form sent by the content type application/x-www-url-formencoded while the FromBody will parse the model the default way, which in most cases are sent by the content type application/json , from the request body.

What is the default model binder in MVC?

The default model binder provided by ASP.NET Core MVC supports most of the common data types and would meet most of our needs. We can extend the built-in model binding functionality by implementing custom model binders and can transform the input prior to binding it.

What is custom model binding in ASP NET Core?

Custom Model Binding 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.

What is the purpose of the defaultmodelbinder class?

This class provides a concrete implementation of a model binder. Initializes a new instance of the DefaultModelBinder class. Gets or sets the model binders for the application. Gets or sets the name of the resource file (class key) that contains localized string values. Binds the model by using the specified controller context and binding context.

Is there a book on ASP NET Core modelbinders?

NET Core Part 08: ModelBinders Customizing ASP. NET Core Part 08: ModelBinders This series is pretty much outdated! As asked by a reader, I compiled the entire series into a book and updated the contents to the latest version of ASP.NET Core. This book is now ready to get ordered on Amazon:


2 Answers

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 SettingsBinder(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

like image 100
Alex from Jitbit Avatar answered Oct 18 '22 19:10

Alex from Jitbit


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; }
    }
}
like image 34
Dmitry Pavlov Avatar answered Oct 18 '22 17:10

Dmitry Pavlov