Monday, March 3, 2008

Executing method in the class library using ASP.NET AJAX with no WebService or PageMethod call - Part2.

This post covers second part of my investigations in how we can execute some class library method remotely from javascript without using WebService call or PageMethod call. First part of the article is available on this link.

Saying shortly I created assembly that needs minimum configuration to redirect call to any class library referenced in the web site !

I placed source code on my website - so you can download it and see how I did it, the code is too large to describe it in this post. But saying shortly I used ASP.NET AJAX framework source code, and especially the part of it where AJAX framework is using AppService.axd path to access Profile and Membership web services.

Ok lets see how you can include this functionality to your web site.

Sample solution looks like this:

image

Here you can see three Projects:

1) AjaxServiceLoader is the assembly that allows you to call http handlers the same way you call web services (almost the same).

2) WizardFramework - is some website on which I need to call methods from some another assembly right from the browser.

3) MyCustomAssembly - is the assembly I want to call methods in, from my web site.

 

MyCustom assembly contains only one simple class, the listing shows it's contents:

using System.Web.Services;
usingSystem.Web.Script.Services;
namespaceMyCustomAssembly
{
    [ScriptService]
    public classSomeClass
  
{
        [WebMethod]
        public objectMyMethod1(int someInteger, int anotherInteger)
        {
            returnsomeInteger + anotherInteger;
        }

        [WebMethod]
        public objectMyMethod2(stringsomeString, stringanotherString)
        {
            returnsomeString + " "+ anotherString;
        }
    }
}

Ok thats all about MyCustomAssembly, now the most interesting part, how I configure WizardFramework website to use that assembly.

First to use methods inside assembly we need reference MyCustomAssembly in the website. AjaxServiceLoader should be also referenced, because we will use it to achieve what we need.

Web.config file should contain the following sections:

        <httpHandlers>
            ...
      <add verb="*" path="*_LoaderService.axd" validate="false" type="AjaxServiceLoader.LoaderServiceHandler, AjaxServiceLoader"/>
      ...
        </httpHandlers>

This is needed configuration entry - it says to AjaxServiceLoader assembly that all requests ending with _LoaderService.axd should be served as script service calls.

And the second configuration where we actually define where to search for Type and method we want to execute:

first register section as follows:

 

    <section name="ServiceTypeDefinitions" 
type="AjaxServiceLoader.ServiceTypeDefinitionsSection, AjaxServiceLoader" 
allowDefinition="Everywhere" 
allowExeDefinition="MachineToApplication" restartOnExternalChanges="true" />

And second just list which calls will be redirected to which types:

<ServiceTypeDefinitions>
    <serviceTypes>
      <clear />
      <add name="SomeName" serviceKey="SomeKey" serviceType="MyCustomAssembly.SomeClass, MyCustomAssembly" />
    </serviceTypes>
  </ServiceTypeDefinitions>  

Here serviceType attribute points to type that will be executed for request SomeKey_LoaderService.axd .

That's it, we finished configuration - you can see how easy it is, you can list there multiply types with multiply keys - even from different assemblies.

Last thing is how we use the service, so there is a listing for Default.aspx :

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
           <Scripts>
                <asp:ScriptReference Path="~/SomeKey_LoaderService.axd/js" />
           </Scripts>
        </asp:ScriptManager>
        <script type="text/javascript">
            function GetMethod1() {
                MyCustomAssembly.SomeClass.MyMethod1(
                    2,
                    3,
                    GotMethod1,
                    null,
                    null
                    );
            }
            // result should be 5
            function GotMethod1(result) {
                $get("resultsDiv").innerHTML = result;
            }
            function GetMethod2() {
                MyCustomAssembly.SomeClass.MyMethod2(
                    "Hello",
                    "world",
                    GotMethod2,
                    null,
                    null
                    );
            }
            // result should be "Hello world
            function GotMethod2(result) {
                $get("resultsDiv").innerHTML = result;
            }
        </script>
        <div>
            <input type="button" value="Get Method1 from SomeClass" onclick="GetMethod1();return false;" />
            <input type="button" value="Get Method2 from SomeClass" onclick="GetMethod2();return false;" />
        </div>
        <div id="resultsDiv">
        </div>
    </form>
</body>
</html>

Also very simple, the script reference we add to ScriptManager control:

<Scripts> <asp:ScriptReference Path="~/SomeKey_LoaderService.axd/js" /> </Scripts>

does all for us, The corresponding Handler is loaded, it reads the information from web.config file and finds that SomeKey key is attached to the SomeClass class in the MyCustomAssembly assembly. Then it uses reflection to reflect methods, and their expected parameters with types, and generates a client proxy script that allows us to use the type right from javascript.

As you see I use MyCustomAssembly.SomeClass.MyMethod1 and pass some integer parameters that will be passed to actual method in that assembly.

I hope you like the simplicity and will use the code as you wish. If you like the idea please provide some feedback.

Hope this helps.

Technorati Tags:

2 comments:

Robba said...

Hey Kirill,

I was just looking at the source code of your solution and created an example project of my own. I am running into a small issue though; the value that is returned on the request is the exact JSON representation of the object, but the client side parser requires everything to be part of the "d" property.

For instance, when I return a simple class with one property named "Prop" and assign it a value of "temp" the result on the wire will be { "Prop" : "temp" }.

The JavaScript parser will give an error complaining that the JSON result must have a "d" property. What it expects is the result of the example above to be:
{ "d" : {"Prop" : "temp" }}

Have you had this problem? If so, how did you fix it? The easy way would be to change the RestHandler to add the {"d":} to the responseString at line 194, but that doesn't really feel like a great solution if you ask me...

Anyway, I realise this is an old topic, was just hoping you'd still be around to answer the question.

Thanks,

Robert

Kirill Chilingarashvili said...

Hi Robba,

I used reflector to open AJAX library source code and modify it for my needs (not sure if I can use it production because of this - I used it for learning purposes).

I guess you can modify the code where the result string is written to response, anyway this needs checking with reflector of new AJAX framework (.NET 3.5), and replicating this change, this should be an easy fix.

I wrote this post based on AJAX 1.0 library, and as I remember, "d" was introduced in 3.5, and it fixes security vulnerability

see for example this post http://encosia.com/2009/02/10/a-breaking-change-between-versions-of-aspnet-ajax/ from encosia, and section named "Why did it change?", I guess there can be found another descriptions of change in the net as well.