Tuesday, December 10, 2013

A little JQuery <-> Json <-> Mvc

It has been quite awhile since I've dealt with complex model binding across a set of inputs in mvc.

If one controller action's params (or a property on one of those params) is an IEnumerable<T> then Mvc will not write the element names in a way that will properly go back to the server.

Short and sweet (and probably hacky way) to do it:

I Json serialized the parent model into an html attribute on to some parent of the value(s) I want to submit. Some questions allow many answers, and I want to save each time a question answer is changed.

I'm working on making that serialization not contain the about to be consumed IEnumerable<T>property.
var save = function (element, value, event, previousValue,debug) {
    console.log('saving:' + value + ' to ' + saveUrl);

    
    var demoText = $(element).closest('[data-demographic]').attr('data-demographic');
    var demoJson = htmlDecode(demoText);
    var demoModel = JSON.parse(demoJson);
    var oldAnswers = demoModel.PossibleAnswers;
    demoModel.PossibleAnswers = [];
    //TODO: handle multi-select, single-select radio, etc...
    if (Object.prototype.toString.call(value) === '[object Array]') {
        //value and previousValue could be arrays for multi-select answers
        $.each(value, function (i, e) {
            demoModel.PossibleAnswers.push({ Id: e });
        });
    } else {
        demoModel.PossibleAnswers.push({ Id: value,Text:$(element).parent().text().trim()  });
    }
    var data = JSON.stringify(demoModel);

    data=data.replace('][', '].[');
    $.ajax({
        dataType: 'json',
        url: saveUrl,
        contentType: 'application/json; charset=utf-8',
        accept: debug ? {
            json: 'application/json',
        } : {},
        type: 'POST', data: data,
        success: function (data, status, jqXhr) {
            if (debug) {
                var answerMirror = data.PossibleAnswers;
                delete data.PossibleAnswers;
                console.log(data);
                console.log(answerMirror);
            } else { //update dom to new read-only model?
                //TODO: adjust with new html
                console.log(data);
            }
            
        }
    });
};
There's the heart of it. Notice I don't actually have to use the special name=foo.bar[0].Id on my inputs, but that is probably still a good idea. This thing was almost working without doing any JSON.stringify but the IEnumerable<T> data was ignored by Mvc's model binder. With that in mind, I had to specify the contentType of the ajax request.

Wednesday, December 4, 2013

Inline markup delegates and razor helper delegate wars

So I had some markup that was being duplicated all over the place in MVC.

This is mostly boilerplate bootstrap paneling, with masonry on top.

How would you provide a generic razor to meet bootstrap/masonry without creating a bunch of classes or partials to account for special case(s)?  How in razor would you write a method that can take inline markup (for example any @<text> call)?

 One way is @helper Take note that HelperResult's documentation says specifically it is not intended to be used directly in your code. So I headed in down the path of inline razor helper delegates:

Tuesday, July 16, 2013

How do you like your noodle baked? medium or well done?

The second reason to avoid this is simply because it bakes the noodle of anyone who reads the code. - Eric Lippert
So when I see the following... I want to scream, yell, and run away.
Csla.BusinessListBase:

public abstract class BusinessListBase<T, C> : ExtendedBindingList<C>, IEditableCollection, IBusinessObject, ISupportUndo, IUndoableObject, ICloneable, ISavable, IParent
        where T : Csla.BusinessListBase<T, C>
        where C : Csla.Core.IEditableBusinessObject
    {
     // ... other code
    }
CompanyName.Library.Base:
public abstract class CompanyBusinessBaseCollection<T, TCollection> : BusinessListBase<T, TCollection>
        where T : Csla.BusinessListBase<T, TCollection>
        where TCollection : Csla.Core.IEditableBusinessObject
    {
     // ... other code
    }
Take note... that that's 2 levels of abstract class so far.. climbing the chain we have: Csla.Core.ExtendedBindingList<T> (where T now means C), System.ComponentModel<T>. I only stopped once I hit the framework inheritance chain start.
I just had to get that out...
Now to find references to cite on my rant:
  • since inheritance is a very intimate (and encapsulation breaking) relationship, it's easy for subclasses to effectively break their superclasses by, for instance, omitting necessary behavior when they the methods are called - Martin Fowler - Designed Inheritance
  • In fact, the Object-Oriented Design Patterns book from "the gang of four" has this to say about inheritance: Prefer object composition to class inheritance
  • Inheritance is pretty enticing especially coming from procedural-land and it often looks deceptively elegant. I mean all I need to do is add this one bit of functionality to another other class, right? Well, one of the problems is that inheritance is probably the worst form of coupling you can have. Your base class breaks encapsulation by exposing implementation details to subclasses in the form of protected members. This makes your system rigid and fragile. The more tragic flaw however is the new subclass brings with it all the baggage and opinion of the inheritance chain. Why does a model that enforces validation attributes care how the model is constructed? The answer is it shouldn’t, and to add insult to injury, the better design is actually easier to implement. Listen closely and you may hear the wise whispers of our forefathers… - Inheritance is Evil: The Epic Fail of the DataAnnotationsModelBinder
  • Prefer object composition to class inheritance - JsPatterns.com - A case against inheritance
  • Joshua Bloch hits it on the nail in his Effective Java book item about “Favoring composition over inheritance.” - The Shyam - Is Inheritance Overrated? Needed even?

Tuesday, July 9, 2013

Using a local source repo to work on vs2008 projects in vs2012

If the project type can't be opened or worked in the newer Visual Studio then you are still out of luck, but otherwise here we go. Assuming you have Git installed, go to the top level of a directory that would include both your .sln and .proj files.
$ git init
inside that directory then if you don't have a default set of ignores. you will want to set that up. Before opening Visual Studio, do your initial commit.
$ git checkout -b Vs2012
to create the new branch. Open in Visual Studio 2012. Commit the changes. Now you can make all your edits and commit them. When you want to pull the changes to the old VS branch change to that branch and do a git-cherry-pick
$ git cherry-pick SHA
replace SHA with the SHA hash of the commit you want to pick. Or to do them in bulk...
$ git cherry-pick SHA1..SHA2
replace SHA1 with the SHA hash of the commit you want to skip, and SHA2 with the last one you want.

Tuesday, May 14, 2013

High speed data scraping example

Situation, I wanted to get the names of the different playable champions to print out and discuss.
So went to http://na.leagueoflegends.com/champions and ran jquery to get the names: $('td.description span a[href]').text()

This produced all the names run together. Perhaps there's a more straightforward way to aggregate them in a short single line, but I was in a hurry.

Open My LinqPad (which includes the Inflector NuGetPackage) Paste in the string and add .Titleize() on the end. Job Done.

Next up marvelheroes.com at https://marvelheroes.com/game-info/game-guide/heroes/grid

jQuery is loaded but not as $ here

jQuery('div.field-content a[href*=/heroes]').map(function(i,e){return jQuery(e).attr('href').split('/')[2];}) in this case did more jquery, so the inflector wasn't needed.
Steam games on sale: Click specials tab: jQuery('div.tabbar div[onclick]:contains(Specials)').click()

Enter the unspeakable as the first found working solution to help load all the items: eval(jQuery('#tab_Discounts_next a[href]').attr('href'))

make it get all the pages var getNext=jQuery('#tab_Discounts_next a[href]').attr('href'); var times=Math.ceil(getNext.split(':')[1].split(', ')[2]/10); eval(getNext);for(var i=1;i>times-1;i++){setTimeout(function(){console.log('getting more'); eval(getNext);},1500*i);} Get all the names jQuery('#tab_Discounts_items div.tab_desc.with_discount h4').map(function(i,e){return jQuery(e).text();}) Done.