Archive for October, 2009

Reusable Editable Fields for ASP.net MVC Using jQuery

Thursday, October 8th, 2009

A friend recently asked me about editing items inline using ASP.net MVC, the kind of thing that was auto magically wired up with post backs in “old fashioned” asp.net so I’ve whipped up a small example showing how you can use jQuery to declaratively set up interactive field editing with a sprinkling of Ajax and JSON.

I’m basing this example on the default ASP.net MVC starter project for brevity (download attached) but here’s an overview:

First you need to set up an Action method (or multiple action methods) on your controller to accept the modification of data.  In my example I’ve added an unimaginative method called “SetField” to the HomeController that looks like this:

public ActionResult SetField(string fieldName, string fieldValue)
{
    var response = Json(fieldValue);
    return response;
}

As you can see, it doesn’t do very much (useful implementation left to the reader) but it accepts the parameters of a field name, and a field value.  You’ll need to roll your own validation and sanity checking here.  It then returns the fieldValue using the MVC Json helper object, as a Json object.  In a real world example, you’d want to call and update in this method.

Now, in the view, jQuery does most of the hard work.

First I added a few CSS classes to the header on the master page (for the sake of example):

<style type="text/css">
    .editableItem { display: block; }
    .fieldViewer { display: block; }
    .fieldEditor { display: none; }
    .editableItemCancel { display: block; }
    .editableItemBox { display: block; }
</style>

I then added an example to the view that looked like this:

<div class="editableItem" id="editable_FieldName">
    <div class="fieldViewer">Click me to edit me!</div>
    <div class="fieldEditor"><input class="editableItemBox" type="text"/><span class="editableItemCancel">cancel</span></div>
</div>

With this HTML I set up some conventions that I’ll rely on when using jQuery.  Firstly, every editable item should use the class “editableItem” and have the id “editable_FieldName”.  I use the class in a jQuery selector and the Id to establish which field is being edited.  Inside the editableItem should be a fieldViewer, containing the current data, and a fieldEditor, which is hidden by default, and contains some kind of editable controller and a cancel button.  You could insert these elements at runtime if you wished, but in order to keep the example simple I’ve declared them in the HTML.

Next I added some jQuery… The jQuery defines some Javascript behaviour associated with the classes used in the HTML, this way, the mark-up can be reused to edit multiple fields rather than being keyed to the Id of a specific field.

<script src="/Scripts/jquery-1.3.2.js" type="text/javascript"></script>
<script type="text/javascript">
    jQuery(document).ready(function() {

        $(".editableItem .fieldViewer").click(function() {
            var parentId = $(this).parent().attr("id");
            $(‘#’ + parentId + " .fieldEditor .editableItemBox").val($(‘#’ + parentId + " .fieldViewer").text());
            $(‘#’ + parentId + " .fieldViewer").toggle();
            $(‘#’ + parentId + " .fieldEditor").toggle();
        });

        $(".editableItem .fieldEditor .editableItemCancel").click(function() {
            var parentId = $(this).parent().parent().attr("id");
            $(‘#’ + parentId + " .fieldViewer").toggle();
            $(‘#’ + parentId + " .fieldEditor").toggle();
        });

        $(‘.editableItem .editableItemBox’).keypress(function(e) {
            if (e.which == 13) {
                var parentId = $(this).parent().parent().attr("id");
                var fieldName = parentId.replace(/editable_/, "");

                $.post(‘/Home/SetField’,
                {
                    fieldName: fieldName, fieldValue: $(‘#’ + parentId + " .editableItemBox").val()
                },
                    function(data) {
                        $(‘#’ + parentId + " .fieldViewer").text(eval(‘(‘ + data + ‘)’));
                        $(‘#’ + parentId + " .fieldViewer").toggle();
                        $(‘#’ + parentId + " .fieldEditor").toggle();
                    })
            }
        });

    });
</script>

Quite simply, if you click the editable field, it toggles into a textbox.  If you hit enter on the textbox, the value is posted to the previously defined Action on the Controller.  If you hit cancel, the display is toggled back.

I’d not recommend copy and pasting this exact example into a production system, but hopefully it’ll guide you through a simple scenario.  You can use a similar technique to add all sorts of little Ajax tricks (auto-suggest, lookups, dynamic menus) to your ASP.net MVC site using jQuery and Json (both of which are included in the core asp.net MVC framework).

Download the example solution here

Creating a WCF Proxy to talk to Magento

Monday, October 5th, 2009

I got a message from a friend who was struggling to do an integration piece with the Magento eCommerce Platform using the SOAP endpoint available at http://yourserver.co.uk/api/v2_soap?wsdl.

He brought an interesting problem to me, namely that the WCF svcutil executable (and built in Visual Studio 2008) was failing to generate any proxy code when supplied with a seemingly valid wsdl.

I did a quick test and managed to instantly reproduce the error.

Weirder still, when using the “old” .Net 2.0 add web reference method rather than the .Net 3.0+ “Add Service Reference”, the framework managed to create a non-WCF reference just fine.

Dropping down to the command line I saw some unusual messages being displayed by svcutil.exe:

c:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin>SvcUtil.exe http://yourserver.co.uk/api/v2_soap?wsdl
Attempting to download metadata from ‘http://yourserver.co.uk/api/v2_soap?wsdl’ using WS-Metadata Exchange or DISCO.

(Lots of error messages here…)

Error: Cannot import wsdl:portType
Detail: An exception was thrown while running a WSDL import extension: System.Se
rviceModel.Description.XmlSerializerMessageContractImporter
Error: The ‘ ‘ character, hexadecimal value 0x20, cannot be included in a name.
Parameter name: name
XPath to Error Source: //wsdl:definitions[@targetNamespace=’urn:Magento’]/wsdl:p
ortType[@name=’Mage_Api_Model_Server_V2_HandlerPortType’]

Error: Cannot import wsdl:binding
Detail: There was an error importing a wsdl:portType that the wsdl:binding is de
pendent on.
XPath to wsdl:portType: //wsdl:definitions[@targetNamespace=’urn:Magento’]/wsdl:
portType[@name=’Mage_Api_Model_Server_V2_HandlerPortType’]
XPath to Error Source: //wsdl:definitions[@targetNamespace=’urn:Magento’]/wsdl:b
inding[@name=’Mage_Api_Model_Server_V2_HandlerBinding’]

Error: Cannot import wsdl:port
Detail: There was an error importing a wsdl:binding that the wsdl:port is depend
ent on.
XPath to wsdl:binding: //wsdl:definitions[@targetNamespace=’urn:Magento’]/wsdl:b
inding[@name=’Mage_Api_Model_Server_V2_HandlerBinding’]
XPath to Error Source: //wsdl:definitions[@targetNamespace=’urn:Magento’]/wsdl:s
ervice[@name=’MagentoService’]/wsdl:port[@name=’Mage_Api_Model_Server_V2_Handler
Port’]

Generating files…
Warning: No code was generated.
If you were trying to generate a client, this could be because the metadata docu
ments did not contain any valid contracts or services
or because all contracts/services were discovered to exist in /reference assembl
ies. Verify that you passed all the metadata documents to the tool.

Warning: If you would like to generate data contracts from schemas make sure to
use the /dataContractOnly option.

c:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin>

So I did a little digging through the WSDL and found an undocumented bug in Magneto’s schema.

First I saved a local copy of the WSDL and used visual studio to reformat the document into some sort of readable state, then I had to make a few corrections to the WSDL to allow SvcUtil to correctly parse the malformed document.

Change 1:  Replace a badly encoded apostrophe – I removed the “’s” from the following operation definition…

<operation name="customerGroupList">
<documentation>Retrieve customer’s groups</documentation>
<input message="typens:customerGroupListRequest"/>
<output message="typens:customerGroupListResponse"/>
</operation>

Change 2: Replace a trailing space in an operation name

<message name="catalogProductGetSpecialPriceRequest">
  <part name="sessionId" type="xsd:string"></part>
  <part name="product" type="xsd:string"></part>
  <part name="storeView " type="xsd:string"></part>
</message>

If you look carefully at the above message definition, you’ll notice that name=”storeView “ contains a space, making the wsdl invalid.  Remove the space so it reads “storeView”.

With these two errors corrected, SvcUtil had no problem generating an appropriate WCF proxy from the corrected wsdl file.

Magneto will hopefully fix this error in the WSDL, but until this time, it’s probably quite safe to follow these steps to generate your own proxy.

To reproduce:

  • Go to http://yourserver.co.uk/api/v2_soap?wsdl and save the contents of your file to the local disk (c:\test\main.wsdl)
  • Open the file in visual studio, and reformat the document for readability (CTRL+K, CTRL+D).
  • Remove the apostrophe from the documentation tag for the customerGroupList operation.
  • Remove the space after the name=”storeView “ in the catalogProductGetSpecialPriceRequest message definition.
  • Open a command prompt and enter
  • c:\test>c:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\svcutil.exe main.wsdl
  • SvcUtil will produce two files, Magento.cs (your WCF proxy) and output.config, your endpoint configuration.