2

MVC 3, EntityFramework 4.1, Database First, Razor customization:

I have an old database that sometimes uses Int16 or Char types for a field that must appear as a CheckBox in the MVC _CreateOrEdit.cshtml View. If it is an Int, 1=true and 0=false. If it is a Char, "Y"=true and "N"=false. This is too much for the Entity Framework to convert automatically. For the Details View, I can use:

@Html.CheckBox("SampleChkInt", Model.SampleChkInt==1?true:false)

But this won't work in place of EditorFor in the _CreateOrEdit.cshtml View. How to do this? I was thinking of a custom HtmlHelper, but the examples I've found don't show me how to tell EntityFramework to update the database properly. There are still other such customizations that I might like to do, where the MVC View does not match the database cleanly enough for EntityFramework to do an update. Answering this question would be a good example. I am working on a sample project, using the following automatically generated (so I can't make changes to it) model class:

namespace AaWeb.Models
{
    using System;
    using System.Collections.Generic;

    public partial class Sample
    {
        public int SampleId { get; set; }
        public Nullable<bool> SampleChkBit { get; set; }
        public Nullable<short> SampleChkInt { get; set; }
        public Nullable<System.DateTime> SampleDate { get; set; }
        public string SampleHtml { get; set; }
        public Nullable<int> SampleInt { get; set; }
        public Nullable<short> SampleYesNo { get; set; }
        public string Title { get; set; }
        public byte[] ConcurrencyToken { get; set; }
    }
}
Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
Bruce Patin
  • 335
  • 7
  • 10

3 Answers3

1

I figured it out. Do not need a model binder or Html Helper extension:

In _CreateOrEdit.cshtml, I made up a new name SampleChkIntBool for the checkbox, and set it according to the value of the model SampleChkInt:

@Html.CheckBox("SampleChkIntBool", Model == null ? false : ( Model.SampleChkInt == 1 ? true : false ), new { @value = "true" })

Then, in the [HttpPost] Create and Edit methods of the Sample.Controller, I use Request["SampleChkIntBool"] to get the value of SampleChkIntBool and use it to set the model SampleChkInt before saving:

string value = Request["SampleChkIntBool"];
// @Html.CheckBox always generates a hidden field of same name and value false after checkbox,
// so that something is always returned, even if the checkbox is not checked.
// Because of this, the returned string is "true,false" if checked, and I only look at the first value.
if (value.Substring(0, 4) == "true") { sample.SampleChkInt = 1; } else { sample.SampleChkInt = 0; }
Bruce Patin
  • 335
  • 7
  • 10
  • Sure - that works. It's not clean at all though. I wouldn't recommend anyone writes a way to do it like that. I could provide several 'messy' ways to do this - and its one of them, but the idea here is to hopefully have clean code. You are left checking every type of parameter on there in every controller you want to use this in. Its not clean and relies on heavily embedded code which really gets away from the clean binding of MVC. my .02 anyway for what its worth. – Adam Tuliper Aug 02 '11 at 05:38
  • This is a "once and done" thing for now. However, if I can get a ModelBinder to work as you suggest, and remove these exceptions from my Controller and View code, and I haven't lost track of this thread, I'll post another answer. – Bruce Patin Aug 02 '11 at 15:57
0

I believe a custom model binder would be in order here to handle the various mappings to your model.

ASP.NET MVC Model Binder for Generic Type

etc etc

Community
  • 1
  • 1
Adam Tuliper
  • 29,982
  • 4
  • 53
  • 71
  • I don't see how the Model Binder is going to help me map one primitive type in the view to another primitive type in the model. When I tried to code a ModelBinder, I realized that I had no name for the checkbox in the view that I could access in the ModelBinder code to convert into an integer for the database SampleChkInt field. Please ignore for now the other fields in my Sample class. I am only interested in using a checkbox (boolean) in the view to set an integer value for the Int16 SampleChkInt field. I really need an end-to-end example. – Bruce Patin Aug 01 '11 at 15:31
  • How do I access the values of custom named HTML input fields in the view? I could use that to provide me a value to convert. – Bruce Patin Aug 01 '11 at 18:36
  • For example, I could put the following in the _CreateOrEdit.cshtml: @Html.CheckBox("SampleChkIntBool", Model.SampleChkInt == 1 ? true : false ) Then, I would need to know how to access my custom HTML element "SampleChkIntBool", to get the value to set into SampleChkInt – Bruce Patin Aug 01 '11 at 18:42
  • how reusable do you want this code to be? once and done or multiple places? if multiple places I would simply have a parsing routing to get the entries from the FormsCollection (Request.Form) and parse each one. If the destination type is a bool, then your routine has a better parsing call to use 'true', '1', 'on', etc – Adam Tuliper Aug 02 '11 at 05:40
0

Here is the way to go from checkbox to database, without the special code in the controller:

// The following statement added to the Application_Start method of Global.asax.cs is what makes this class apply to a specific entity:
// ModelBinders.Binders.Add(typeof(AaWeb.Models.Sample), new AaWeb.Models.SampleBinder());

// There are two ways to do this, choose one:
// 1. Declare a class that extends IModelBinder, and supply all values of the entity (a big bother).
// 2. Declare a class extending DefaultModelBinder, and check for and supply only the exceptions (much better).

// This must supply all values of the entity:
//public class SampleBinder : IModelBinder 
//{
//    public object BindModel(ControllerContext cc, ModelBindingContext mbc)
//    {
//        Sample samp = new Sample();
//        samp.SampleId = System.Convert.ToInt32(cc.HttpContext.Request.Form["SampleId"]);
//        // Continue to specify all of the rest of the values of the Sample entity from the form, as done in the above statement.
//        // ...
//        return samp;
//    }
//}

// This must check the property names and supply appropriate values from the FormCollection.
// The base.BindProperty must be executed at the end, to make sure everything not specified is take care of.
public class SampleBinder : DefaultModelBinder
{
    protected override void BindProperty( ControllerContext cc, ModelBindingContext mbc, System.ComponentModel.PropertyDescriptor pd)
    {
        if (pd.Name == "SampleChkInt")
        {
            // This converts the "true" or "false" of a checkbox to an integer 1 or 0 for the database.
            pd.SetValue(mbc.Model, (Nullable<Int16>)(cc.HttpContext.Request.Form["SampleChkIntBool"].Substring(0, 4) == "true" ? 1 : 0));

            // To do the same in the reverse direction, from database to view, use pd.GetValue(Sample object).

            return;
        }

        // Need the following to get all of the values not specified in this BindProperty method:
        base.BindProperty(cc, mbc, pd);
    } 
}
Bruce Patin
  • 335
  • 7
  • 10