Friday, April 22, 2011

Update on jQuery Ajax ComboBox ASP.NET MVC helper

Added on_selected callback

To use:

<div class="editor-label">
 @Html.LabelFor(model => model.CategoryId, "Category")
</div>
<div class="editor-field">
 <table>
 <tr>                    
  <td>@Html.AjaxComboBoxFor(model => model.CategoryId,
   "/Category/Lookup",
   "/Category/Caption",
   new { style = "width: 300px " },
   new { sub_info = true, on_selected = "thisIsOk();" })
  </td>
  <td>@Html.ValidationMessageFor(model => model.CategoryId)</td>
 </tr>
 </table>
</div>


<script>
    function thisIsOk() {
        alert('This is great!');
    }
</script>


Download it from Google Code

Or get an SVN copy

Thursday, April 21, 2011

NHibernate Error: "Could not initialize proxy - no Session"

If you happen to encounter this kind of error in NHibernate...

Initializing[JqueryAjaxComboBoxAspNetMvcHelperDemo.Models.Category#777]-Could not initialize proxy - no Session.

...even you already have a Fetch clause in your controller code:

public ViewResult Index()
{
 using (var s = SessionFactoryBuilder.GetSessionFactory().OpenSession())
 {
  return View(s.Query<Product>().Fetch(x => x.Category).ToList());
 }
}

Worry not much, if your code has a Fetch clause, the problem lies in the data, your data is inconsistent, your table has a foreign key that has no matching primary key in the table it is referring to. Typical of database with no foreign key capability (e.g. MySQL's MyISAM) or with foreign key but not enforced(I'm using Sqlite when I encountered that error)

In our example, there is no primary key 777 in Category table, so we just need to correct our data

You can also encounter the same error(verbatim) even if your data is correct, it happens when you forgot to include the Fetch clause in your query.


Again, NHibernate does a poor job on stating the root cause of error. Another example of vague error from NHibernate http://www.ienablemuch.com/2010/10/nhibernate-orm-how-to-object-relational.html, scroll the down to the last portion of the article

Visual Studio nifty shortcut

Commenting (keyboard shortcut: Ctrl+KC) is more cool when you are editing a page. When the cursor is inside of tags, it will comment the enclosing tag on both ends(i.e. beginning tag and closing tag) even you didn't highlight the beginning tag and closing tag.

<div class="editor-field">
 <table>
 <tr>                    
  <td>@Html.AjaxComboBoxFor(model => model.CategoryId,
   "/Category/Lookup",
   "/Category/Caption",
   new { style = "width: 300px " },
   new { sub_info = true } )
  </td>
  <td>@Html.ValidationMessageFor(model => model.CategoryId)</td>
 </tr>
 </table>
</div>



If you position your cursor after of <table> then you press Ctrl+KC, Visual Studio will put comment around your table tags


<div class="editor-field">
 @*<table>
 <tr>                    
  <td>@Html.AjaxComboBoxFor(model => model.CategoryId,
   "/Category/Lookup",
   "/Category/Caption",
   new { style = "width: 300px " },
   new { sub_info = true } )
  </td>
  <td>@Html.ValidationMessageFor(model => model.CategoryId)</td>
 </tr>
 </table>*@
</div>

Tuesday, April 19, 2011

Please create a model for your inputs. Model guidelines for ASP.NET MVC

Please create a model for your inputs; even if a FormCollection or Request.Form seems expedient at some pressured moment(e.g. deadline), track down what causes the ASP.NET's MVC engine not populating your models. By using models, our program is more "discoverable" so-to-speak, so any new or existing programmers who will review or continue our code can easily infer the intent of code, they would be able to know what other informations goes with the model, FormCollection and Request.Form doesn't carry any business logic semantics, those who are new to our programs could not easily infer if FormCollection carries a Bonus field, they won't be able to know if we overlooked some fields, they won't be able to know if the program needed to include other fields when performing computations from data before saving the data to database.



What I said is not a draconian rule; really, I'm an old man trapped in a young body. If we still cannot figure out why MVC cannot populate our models well, we will just use FormCollection for the meantime(though not pansamantagal, there's something exhilirating tracking the causes of errors), but eventually we shall have to discover what causes ASP.NET MVC engine not populating our models well


"Young men know the rules, but old men know the exceptions." -- Oliver Wendell Holmes



One of the causes why ASP.NET MVC cannot populate the object's property well is when the field source is in invalid format. Example, passing Feb 31, 1976 to a birthday field. Another example is passing 1,234 to a decimal type, whoa..! wait! wait! 1,234 is a valid decimal, and a quick trip to SnippetCompiler showed me that Convert.ToDecimal("1,234") doesn't causes any runtime error, and it returns 1234. Unfortunately for us, web is a different beast, it is public-facing most of the time, so there's the multi-culture(i.e. different nations uses different money format) issues. For instance, a German string input(every inputs on web are strings) of 1,234 means 1 dollar(or euro) and 234 cents; 1.234 is one thousand two hundred thirty four bucks for them. So ASP.NET MVC team didn't make an assumption that a 1,234 string has a universal meaning for a decimal type.


There's a two solution on that issue, one is to clean the data prior to submitting:

$(function () {
    $('#theFormIdHere').submit(function() {
        var uxSalary = $('input[name=Salary]');
        uxSalary.val(uxSalary.val().replace(',', ''));
    });
});

But that causes an ugly artifact on UI, the decimal type's formatting on textbox will be altered. So this is a no-go proposition for some developers.


Another solution is to use a ModelBinder for decimal. In this case, we don't reinvent the wheel, let's just use Phil Haack's code. Get it here: http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx


To see things in action and make 1,234 worked, download the code here: http://code.google.com/p/test-aspnetmvc-decimal-binding-problem/downloads/list

And do either of the following:

Uncomment line 22 to 27 of Index.cshtml
/*$(function () {
        $('#theDataHolder').submit(function() {
            var uxSalary = $('input[name=Salary]');
            uxSalary.val(uxSalary.val().replace(',', ''));
        });
    });*/

Or uncomment line 36 of Global.asax
// ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());



Regarding the duplication of model, one in WCF Data Services project, another in our web project. We are just being pragmatic, we are not able find a way to make WCF Data Services' models carry other semantics(e.g. validation attributes). I tried Scott Gu's buddy-class approach, but for the life of me I don't know why WCF Data Services can't carry data validation attributes I added to an entity in a WCF Data Services. The duplicity, as evident on FbPerson module, I'm not using WCF Data Services' FbPerson model on FbPerson's form; instead, I created a separate FbPerson model on our web project's Models folder and uses that model on FbPerson's form. Using that approach, in its barest essence, WCF Data Services merely acts as a data access layer, it can't (or we just can't figure it out yet) carry semantics loaded to models like Required, Range, etc.



Perhaps we have a better chance of loading semantics to models if we do a code-first(instead of using designers, just do POCOs) models on WCF Data Service, don't want? :-)


Another approach is to use WCF Service and also do a code-first of models, i.e. POCO; and let the ORM(say Entity Framework or NHibernate) persist those models to database. We can load semantics(lol I'm not contented with the word attribute/validation) to models using this approach; also by using this approach we can tap the full potential of an ORM, we can get server-generated id(e.g. newsequentialid) for example, and better handling of concurrency(of which I know NHibernate is capable of including rowversion field on UPDATE statement's WHERE clause, on Entity Framework I have yet to google it), I noticed we didn't introduce a rowversion field (for concurrency stuff) on all our tables :-)


Speaking of NHibernate stuff, did you know we can just merely do one statement for both insert and update with NH? We just push the model to it and it will do its job well. That method is Merge. And when NH's Merge is doing an UPDATE, it includes the rowversion(for handling concurrent update) field to WHERE clause as well, neat :-)

Example WCF Service using NHibernate:
public KeyAndRowversion FbPerson_Save(FbPerson fromInput)
{
    using(var s = OurSystem.SessionFactory.OpenSession())
    {
        // NHibernate doesn't alter the model you passed to it; instead, it returns a new object which contains the current state of your model
        FbPerson currentValues = (FbPerson) s.Merge(fromInput);
        
        return new KeyAndRowversion { HasError = false, GeneratedGuid = currentValues.PersonId, CurrentRowversion = currentValues.TheRowVersion };
    }
}

Contrast that with Entity Framework approach.

Example WCF Service using EntityFramework:
public KeyAndRowversion FbPerson_Save(FbPerson fromInput)
{
    using(var s = new OurSystem.ErpDbContext())
    {
        FbPerson fromExisting = s.FbPersons.Find(fromInput.PersonId);

        if (fromExisting == null)
        {
            s.Persons.Add(fromInput);
        }
        else
        {
            fromExisting.Firstname = fromInput.Firstname;
            fromExisting.Lastname = fromInput.Lastname;
            fromExisting.Age = fromInput.Age;
            fromExisting.CountryId = fromInput.CountryId;
        }

        s.SaveChanges();

        // Entity Framework alters the model you passed to it, example: primary key; I haven't checked yet if EF also loads the current rowversion  on altered model
        return new KeyAndRowversion { HasError = false, GeneratedGuid = fromInput.PersonId };        
    }
}

Monday, April 18, 2011

Added Html and Json attributes on ASP.NET MVC Helper for jQuery Ajax ComboBox

On view:

<div class="editor-label">
 @Html.LabelFor(model => model.CategoryId, "Category")
</div>
<div class="editor-field">
 <table>
 <tr>                    
  <td>@Html.AjaxComboBoxFor(model => model.CategoryId,
   "/Category/Lookup",
   "/Category/Caption",
   new { style = "width: 300px " } // html attributes for span
   new { sub_info = true } ) // other json attributes for jQuery Ajax ComboBox 
  </td>
  <td>@Html.ValidationMessageFor(model => model.CategoryId)</td>
 </tr>
 </table>
</div>

On controller:

[HttpPost]
public JsonResult Lookup(string q_word, string primary_key, int per_page, int page_num)
{


    using (var svc = SessionFactoryBuilder.GetSessionFactory().OpenSession())
    {

        var FilteredCategory = svc.Query<Category>()
                                .Where(x => q_word == "" || x.CategoryName.Contains(q_word));


        var PagedFilter = FilteredCategory.OrderBy(x => x.CategoryName)
                          .LimitAndOffset(per_page, page_num)
                          .ToList();

        return Json(
            new
            {
                cnt = FilteredCategory.Count()

                ,primary_key = PagedFilter.Select(x => x.CategoryId)

                ,candidate = PagedFilter.Select(x => x.CategoryName)

                ,cnt_page = PagedFilter.Count()

                // these are the sub info:
                ,attached = PagedFilter.Select(x =>                    
                        new[]
                        {
                            new string[] { "Code", x.CategoryCode } ,
                            new string[] { "Ranking", x.Ranking.ToString() }
                        }
                    )
            }
            );

    }//using
}//Lookup


[HttpPost]
public string Caption(string q_word)
{
    using (var svc = SessionFactoryBuilder.GetSessionFactory().OpenSession())
    {
        if (string.IsNullOrEmpty(q_word))
            return "";
        else
            return 
                svc.Query<Category>()
                .Where(x => x.CategoryId == int.Parse(q_word))
                .Select(x => x.CategoryName)
                .SingleOrDefault();
    }

}


Working code available on: http://code.google.com/p/jquery-ajax-combobox-aspnet-mvc-helper/downloads/list