Search code examples
c#asp.net-corerazor-pagesdefaultmodelbinder

InvalidOperationException Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder.CreateModel(ModelBindingContext bindingContext)


I am trying to bind a complex type to a property to edit the property values and save the changes in memory, for now for testing.

I get an exception when I execute Post. 

This is my Edit razor view.

<h1>Edit:</h1>






        <form method="post">




        <label asp-for="EditClient.Name"> </label>
        <input asp-for="EditClient.Name"
               class="form-control"
               readonly="readonly" />



        <h3 class="text text-info">Pull Configuration</h3>
        <div>

            <label asp-for="EditClient.Pull.Protocol"> </label>
            <input asp-for="EditClient.Pull.Protocol" class="form-control" />
            <span asp-validation-for="EditClient.Pull.Protocol" class="text-danger"></span>

            <label asp-for="EditClient.Pull.Host"> </label>
            <input asp-for="EditClient.Pull.Host" class="form-control" />
            <span asp-validation-for="EditClient.Pull.Host" class="text-danger"></span>


            <label asp-for="EditClient.Pull.User"> </label>
            <input asp-for="EditClient.Pull.User" class="form-control" />
            <span asp-validation-for="EditClient.Pull.User" class="text-danger"></span>

            <label asp-for="EditClient.Pull.Password"> </label>
            <input asp-for="EditClient.Pull.Password" class="form-control" />
            <span asp-validation-for="EditClient.Pull.Password" class="text-danger"></span>


            <label asp-for="EditClient.Pull.PrivateKeyPath"> </label>
            <input asp-for="EditClient.Pull.PrivateKeyPath" class="form-control" />
            <span asp-validation-for="EditClient.Pull.PrivateKeyPath" class="text-danger"></span>

            <label asp-for="EditClient.Pull.Port"> </label>
            <input asp-for="EditClient.Pull.Port" class="form-control" />
            <span asp-validation-for="EditClient.Pull.Port" class="text-danger"></span>


            <label asp-for="EditClient.Pull.RemoteDirectory"> </label>
            <input asp-for="EditClient.Pull.RemoteDirectory" class="form-control" />
            <span asp-validation-for="EditClient.Pull.RemoteDirectory" class="text-danger"></span>


            <label asp-for="EditClient.Pull.IsDecrypt"> </label>
            <input asp-for="EditClient.Pull.IsDecrypt" class="form-control" />
            <span asp-validation-for="EditClient.Pull.IsDecrypt" class="text-danger"></span>



        </div>


        <h3 class="text text-info">Push Configuration</h3>
        <label asp-for="EditClient.Push.Protocol"> </label>
        <input asp-for="EditClient.Push.Protocol" class="form-control" />
        <span asp-validation-for="EditClient.Push.Protocol" class="text-danger"></span>

        <label asp-for="EditClient.Push.Host"> </label>
        <input asp-for="EditClient.Push.Host" class="form-control" />
        <span asp-validation-for="EditClient.Push.Host" class="text-danger"></span>


        <label asp-for="EditClient.Push.User"> </label>
        <input asp-for="EditClient.Push.User" class="form-control" />
        <span asp-validation-for="EditClient.Pull.User" class="text-danger"></span>

        <label asp-for="EditClient.Push.Password"> </label>
        <input asp-for="EditClient.Push.Password" class="form-control" />
        <span asp-validation-for="EditClient.Pull.Password" class="text-danger"></span>


        <label asp-for="EditClient.Push.PrivateKeyPath"> </label>
        <input asp-for="EditClient.Push.PrivateKeyPath" class="form-control" />
        <span asp-validation-for="EditClient.Pull.PrivateKeyPath" class="text-danger"></span>

        <label asp-for="EditClient.Push.Port"> </label>
        <input asp-for="EditClient.Push.Port" class="form-control" />
        <span asp-validation-for="EditClient.Pull.Port" class="text-danger"></span>


        <label asp-for="EditClient.Push.RemoteDirectory"> </label>
        <input asp-for="EditClient.Push.RemoteDirectory" class="form-control" />
        <span asp-validation-for="EditClient.Pull.RemoteDirectory" class="text-danger"></span>

        <label asp-for="EditClient.Push.IsZip"> </label>
        <input asp-for="EditClient.Push.IsZip" class="form-control" />
        <span asp-validation-for="EditClient.Push.IsZip" class="text-danger"></span>



        <label asp-for="EditClient.Push.IsEncrypt"> </label>
        <input asp-for="EditClient.Push.IsEncrypt" class="form-control" />
        <span asp-validation-for="EditClient.Push.IsEncrypt" class="text-danger"></span>

        <button type="submit">
            Submit
        </button>
    </form>







    This is my EditModel
    ``````````````````````````````````````````````````

    public class EditModel : PageModel
        {
            private readonly IClientConfiguration _config;



            [BindProperty(SupportsGet = false)]
            public ClientConfig EditClient { get; set; } 





            public EditModel(IClientConfiguration config)
            {
                this._config = config;

            }


            public IActionResult OnPost()
            {
                var _editedClient = 
                    new ClientConfig(this.EditClient.Name, this.EditClient.Email, this.EditClient.Push, this.EditClient.Pull);


                if(this._config.GetByName(_editedClient.Name) == null)
                {
                    this._config.Add(_editedClient);
                }
                else
                {
                    this._config.Edit(_editedClient);
                }

                return this.RedirectToPage("/DropBox/Detail", new { name = _editedClient.Name }); 
            }
            public IActionResult OnGet(string name)
            {
                this.EditClient = this._config.GetByName(name);


                return this.Page();
            }
        }









    This is my type
    ```````````````````````````````````````````````````````````
    [JsonObject]
        public class ClientConfig
        {

            [JsonProperty]
            public string Name { get; }

            [JsonProperty]
            public IEnumerable<string> Email { get; }


            [JsonProperty]
            public PushConfig Push { get; }

            [JsonProperty]
            public PullConfig Pull { get; }



            [JsonConstructor]
            public ClientConfig(string name, IEnumerable<string> email, PushConfig push, PullConfig pull)
            {
                this.Name = name;
                this.Email = email;
                this.Push = push;
                this.Pull = pull;
            }






        }


    This is my IClientConfig Interface and the class who implements it
    `````````````````````````````````````````````````````````````````````

     public class InMemoryClientConfig : IClientConfiguration
        {
            public List<ClientConfig> GetAll()
            {
                return this.LoadClientsConfig().Clients
                    .ToList();
            }
            public ClientConfig Add(ClientConfig clientConfig)
            {
                var allClients = this.GetAll();

                var exist = allClients.Contains(clientConfig);

                if(!exist)
                {
                    this.GetAll().Add(clientConfig);

                    return clientConfig;
                }

                return clientConfig; 


            }

            public ClientConfig Edit(ClientConfig clientConfig)
            {
                var _edit = this.GetByName(clientConfig.Name); 


                if (_edit != null)
                this.GetAll().Remove(_edit);

                this.GetAll()
                    .Add(new ClientConfig(clientConfig.Name, clientConfig.Email, clientConfig.Push, clientConfig.Pull));
                return clientConfig;
            }


            public ClientConfig Delete(int id)
            {
                throw new NotImplementedException();
            }



            Config LoadClientsConfig()
            {
                var deserialize = File.ReadAllText(@"C:\...\Path\ToJsonFile");
                return JsonConvert.DeserializeObject<Config>(deserialize);
            }
            public ClientConfig GetByName(string name)
            => this.GetAll
                     ().SingleOrDefault(x => x.Name == name);
        }






The problem child stems from The ClientConfig EditClient Property located in the EditModel: PageModel class line #18.

get works fine but when I attempt to post the changes made on the form page I get InvalidOperationException Could not create an instance of type 'ProjectName.Core.ClientConfig Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the 'EditClient' parameter a non-null default value.

I have been stuck on this for a day or so and reviewed other stack questions with answers but I do not understand the answers provided, terminologies or examples.

Sorry if I did not make my question clear but this is to the best of my ability.

Any assistance would greatly be appreciated.

Thank you again.


Solution

  • Finally figured it out.

    Do the following, 1. Ensure your label name matches the property you want to bind to. "

    1. Overload an empty constructor for the type you are binding to.

    2. Pass the binding model property from the razor view as an argument to the Post method, and don't forget to pass it from the razor view using tag helpers.

    3. The model must have a Get; Set; for the props, you are working with.

    4. The binding model property, add attribute [BindProperty(Support Get= False)] and make sure you have a get; set; on it.

    let me know if you need a code example for this.

    I hope this helps someone. Vola!