Wednesday, May 23, 2012

Foray into custom model binders

I have a complex viewmodel
public class ScenarioCreationDTO
  {
    [Required]
    [StringLength(500)]
    public string Comments { get; set; }
    [Required]
    public IEnumerable<int> PriceLists { get; set; }
    public IEnumerable<PriceListCurrencyDTO> Currencies { get; set; }
    public IEnumerable<PriceListCountryDTO> Countries { get; set; }
  }
Currencies has a compound key. How do I get binding to work for either checkBoxList or RadioButtons? Sometimes the user is allowed to select multiple, other times not. Perhaps the problem was in the view?
<tbody>
                                    @foreach (SelectListItem item in ViewBag.CurrencySelect)
                                    { 
                                        <tr>
                                            <td>
                                                @if (ViewBag.MultipleCurrencies)
                                                {
                                                    @Html.RadioButton("currencies", item.Value, item.Selected)
                                                }
                                                @Html.Label(item.Text)
                                            </td>
                                        </tr>
                                    }
                                </tbody>
I did not find a RadioButtonList helper, nor a RadioButtonFor<T> that took multiple values. Thanks to Msdn magazine and Buildstarted.com I now have a custom model binder.
protected override void OnApplicationStarted()
    {
      base.OnApplicationStarted();
      ModelBinders.Binders.Add(typeof(PriceListCurrencyDTO),new PriceListCurrencyBinder());

- global.asax.cs
public class PriceListCurrencyBinder:IModelBinder
  {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      ValueProviderResult value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
      var selected = value.AttemptedValue;
      var result = Shared.SyntaxSugar.Helpers.Deserialize<PriceListCurrencyDTO>(selected);
      return result;
    }
  }

var currencySelect=currencies.Select(c => new SelectListItem()
      {
        Text = c.CurrencyCode,
        Value = Shared.SyntaxSugar.Helpers.Serialize(c),
        Selected = priceListCurrenciesSelected != null && priceListCurrenciesSelected.Any(plc=>c.PriceListID==plc.PriceListID && c.CurrencyCode==plc.CurrencyCode)
      }).ToArray();
      ViewBag.CurrencySelect = currencySelect;
[HttpPost]
    public virtual ActionResult Create(byte regionID, Int64 dealID, Model.ViewModel.Scenarios.ScenarioCreationDTO data){
//...
}
and finally the view
<tbody>
                                    @foreach (SelectListItem item in ViewBag.CurrencySelect)
                                    { var i=-1; i++;
                                        <tr>
                                            <td>
                                                @if (ViewBag.MultipleCurrencies)
                                                {
                                                    @Html.RadioButton("currencies["+i+"]", item.Value, item.Selected)
                                                }
                                                @Html.Label(item.Text)
                                            </td>
                                        </tr>
                                    }
                                </tbody>

Thursday, May 10, 2012

Clean Repository pattern in EF4


  1. The View
    1. only post the key fields and changed fields
  2. The controller action
    1. transform the post into a DTO and ensure key is set
    2. validate changed fields
  3. The domain layer
    1. accept the DTO and the list of changed properties
    2. validate all business logic
  4. The Service layer
    1. accept the DTO
    2. update ONLY the changed columns.
      1. without fetching a new copy of the object
Goal completed.

1. In the View every input has a wrapper div marked with `data-original="@Model.oldValue"`
OnClick for the save button(s): javascript marks all input fields that have changed to `disabled='disabled'`

2. In the controller action the params required are simply the key fields. I new-up a DTO, and TryUpdateModel on it. For some reason I had to manually set the key property.

3. Domain layer is a simple pass through for now, no business logic is present yet.

4. Service Layer takes in the DTO (as an interface), maps it into a concrete type, and iterates the list of changed property names, setting the ObjectStateManager to modified for each property.

1. For Instance:
 <div class="editor-label">
            @Html.LabelFor(model => model.Threshold3)
        </div>
        <div class="editor-field" data-original="@Model.Threshold3">
            @Html.EditorFor(model => model.Threshold3)
            @Html.ValidationMessageFor(model => model.Threshold3)
        </div>

        <p>
            <input type="submit" value="Save" />
        </p>


 and
<script type="text/javascript">
    $(document).ready(function () {
        $('input[type="submit"]').on('click', function () {
            $('.editor-field').each(function (index,e) {
                var input = $('input', e);
                
                var old = $(e).attr('data-original');
                if (typeof (old) !== 'undefined' && old !== false && old == $(input).val()) {
                    input.attr('disabled', 'disabled');
                }
                
            });
        });
    });
    </script>

More after the break...

Friday, May 4, 2012

Turn off custom errors mode asap

an environment is giving an error, and you need to turn off custom errors mode in the web.config.

Navigate via filesystem, open the file, find the section, edit the section, all by hand? No.

LinqPad to the rescue!

http://ideone.com/Opagg

It could stand to have the trace localonly=false feature too, but it's not there yet, feel free to try to add that feature =)