10

I have an HtmlHelper extension method that takes javascript callback functions as parameters.. for example:

@Html.SomethingCool("containerName", "jsCallbackFunction")

<script type="javascript">
    function jsCallbackFunction(e, i) {
        alert(e.target.name + ' / ' + i);
    }
</script>

As you can see, the javascript callback function name is passed to the HtmlHelper extension method. This causes the developer to have to refer back to the documentation to figure out what parameters the jsCallbackFunction function needs.

I would much rather prefer something like this:

@Html.SomethingCool("containerName", New SomethingCoolCallbackDelegate(Address Of jsCallbackFunction))

<OutputAsJavascript>
Private Sub jsCallbackFunction(e, i)
    '    SOMETHING goes here.  some kind of html dom calls or ???
End Sub

The SomethingCoolCallbackDelegate would provide the code contract for the target function. Then the compiler would compile the jsCallbackFunction as javascript on the MVC page.

Is there anything like this built into .NET 4 / ASP.NET MVC 4 / Razor 2 ? Or any other technology that can achieve something similar?

Examples are in VB, but solutions in C# are quite acceptable as well.

Clarification:

@gideon: notice that jsCallbackFunction takes two parameters e, and i. However, the HtmlHelper extension method simply asks for a string (the name of the javascript callback function) and does not indicate what parameters this function might take. The problem I am trying to solve is two-fold.

  • First, the missing parameter hints. A .NET delegate type passed in place of the "javascript callback name" string would accomplish this. I am open to other solutions to accomplish this. I am aware of XML comments. They are not really a solution.

  • Second, trying to keep the page programmer working in a single language. Switching between javascript and VB (or js and C#) requires (for me at least) an expensive context switch. My brain doesn't make the transition quickly. Keeping me working in VB or C# is more productive and cost effective. So being able to write a function in a .NET language and have it compiled to javascript, in the context of an ASP.NET MVC/razor view, is what I am after here.

@TyreeJackson: SomethingCool is an HtmlHelper extension method that I would write that outputs html and javascript. Part of the javascript output needs to call into a user(programmer)-supplied function to make some decisions. Think of it similar to the success or failure function you supply to an ajax call.

16
  • This is not possible. For example, how would you describe window.alert in VB? Commented Nov 4, 2013 at 2:37
  • @Kavun: Script# claims to be capable of this, but I can't figure out how to use it. Was looking for something built into the .NET framework or a technology that had decent documentation. Commented Nov 4, 2013 at 2:38
  • Yea, ScriptSharp seems like it might be able to generate a javascript string from .NET code - stackoverflow.com/questions/17282522/… Commented Nov 4, 2013 at 2:49
  • "This causes the developer to have to refer back to the documentation to figure out what parameters the jsCallbackFunction function needs" what does this mean? I don't fully understand your question. What is the problem you're trying to solve? What does this code do ? : @Html.SomethingCool("containerName", "jsCallbackFunction") Commented Jul 31, 2015 at 7:01
  • I am not getting what you want to achieve can u put some example that show what problem you have Commented Jul 31, 2015 at 18:08

3 Answers 3

5
+200

While I can't give you a full transpiler/compiler option since that would be an enormous amount of work, I can suggest the following to assist with the intellisense support and emitting of the functions and calls.

Here is the infrastructure code. You would need to complete the getArgumentLiteral and getConstantFromArgument functions to handle other cases you come up with, but this is a decent starting point.

public abstract class JavascriptFunction<TFunction, TDelegate> where TFunction : JavascriptFunction<TFunction, TDelegate>, new()
{
    private static  TFunction   instance    = new TFunction();
    private static  string      name        = typeof(TFunction).Name;
    private         string      functionBody;

    protected JavascriptFunction(string functionBody) { this.functionBody = functionBody; }

    public static string Call(Expression<Action<TDelegate>> func)
    {
        return instance.EmitFunctionCall(func);
    }

    public static string EmitFunction()
    {
        return "function " + name + "(" + extractParameterNames() + ")\r\n{\r\n    " + instance.functionBody.Replace("\n", "\n    ") + "\r\n}\r\n";
    }

    private string EmitFunctionCall(Expression<Action<TDelegate>> func)
    {
        return name + "(" + this.extractArgumentValues(((InvocationExpression) func.Body).Arguments) + ");";
    }

    private string extractArgumentValues(System.Collections.ObjectModel.ReadOnlyCollection<Expression> arguments)
    {
        System.Text.StringBuilder   returnString    = new System.Text.StringBuilder();
        string                      commaOrBlank    = "";
        foreach(var argument in arguments)
        {
            returnString.Append(commaOrBlank + this.getArgumentLiteral(argument));
            commaOrBlank    = ", ";
        }
        return returnString.ToString();
    }

    private string getArgumentLiteral(Expression argument)
    {
        if (argument.NodeType == ExpressionType.Constant)   return this.getConstantFromArgument((ConstantExpression) argument);
        else                                                return argument.ToString();
    }

    private string getConstantFromArgument(ConstantExpression constantExpression)
    {
        if (constantExpression.Type == typeof(String))  return "'" + constantExpression.Value.ToString().Replace("'", "\\'") + "'";
        if (constantExpression.Type == typeof(Boolean)) return constantExpression.Value.ToString().ToLower();
        return constantExpression.Value.ToString();
    }

    private static string extractParameterNames()
    {
        System.Text.StringBuilder   returnString    = new System.Text.StringBuilder();
        string                      commaOrBlank    = "";

        MethodInfo method = typeof(TDelegate).GetMethod("Invoke");
        foreach (ParameterInfo param in method.GetParameters())
        {
            returnString.Append(commaOrBlank  + param.Name);
            commaOrBlank = ", ";
        }
        return returnString.ToString();
    }
}

public abstract class CoreJSFunction<TFunction, TDelegate> : JavascriptFunction<TFunction, TDelegate>
    where TFunction : CoreJSFunction<TFunction, TDelegate>, new()
{
    protected CoreJSFunction() : base(null) {}
}

Here is an example of a standard function support wrapper:

public class alert : CoreJSFunction<alert, alert.signature>
{
    public delegate void signature(string message);
}

Here are a couple of example Javascript function support wrappers:

public class hello : JavascriptFunction<hello, hello.signature>
{
    public delegate void signature(string world, bool goodByeToo);
    public hello() : base(@"return 'Hello ' + world + (goodByeToo ? '. And good bye too!' : ''") {}
}

public class bye : JavascriptFunction<bye, bye.signature>
{
    public delegate void signature(string friends, bool bestOfLuck);
    public bye() : base(@"return 'Bye ' + friends + (bestOfLuck ? '. And best of luck!' : ''") {}
}

And here is a console app demonstrating its use:

public class TestJavascriptFunctions
{
    static void Main()
    {
        // TODO: Get javascript functions to emit to the client side somehow instead of writing them to the console
        Console.WriteLine(hello.EmitFunction() + bye.EmitFunction());

        // TODO: output calls to javascript function to the client side somehow instead of writing them to the console
        Console.WriteLine(hello.Call(func=>func("Earth", false)));
        Console.WriteLine(bye.Call(func=>func("Jane and John", true)));
        Console.WriteLine(alert.Call(func=>func("Hello World!")));

        Console.ReadKey();
    }
}

And here is the output from the console app:

function hello(world, goodByeToo)
{
    return 'Hello ' + world + (goodByeToo ? '. And good bye too!' : ''
}
function bye(friends, bestOfLuck)
{
    return 'Bye ' + friends + (bestOfLuck ? '. And best of luck!' : ''
}

hello('Earth', false);
bye('Jane and John', true);
alert('Hello World!');

UPDATE:

You may also want to check out the JSIL. I'm not affiliated with the project and cannot speak to it's stability, accuracy nor efficacy, but it sounds interesting, and may be able to help you.

Sign up to request clarification or add additional context in comments.

7 Comments

While this is a decent enough piece of coding, I'm not sure writing js as C# strings will help with the context switching.. it will probably make it worse. I like the parameter/intellisense support though. Very nice. I'll definitely be playing around with the concept. Thank you.
@SamAxe If you want to take on the burden of supporting the full ecmascript standard, you may be able to use the techniques I've outlined in the answer to recreate all of the method signatures for all of the common JS and HTMLDom constructs and then use Linq.Expressions like I have in the answer to emit the calls to them. Simply skip the emitting of the definitions for those pieces. Of course, your c# code would not look exactly like the compiled Javascript, but you would get the intellisense. You would need to create your own wrappers for any 3rd party JS libraries that you use, like JQuery.
@SamAxe For your convenience, I've added the CoreJSFunction class and the alert example wrapper support class. I should mention, that just spending the little time I've spent on this, I don't feel that the solution I've provided you is worth spending the time it would take to complete it. You would likely be much better off training yourself and your developers in the art of switching between JS and C#. There are strategies to mitigate the context switch distress, such as separating your concerns such that you are never looking at both languages at the same time in the same file.
@SamAxe I should clarify, I don't think the solution is worth spending the time to complete in order to support full programming of JS in C#. If you are looking to use it for little one off function calls such as the ones in my examples, then that would likely be fine. But outright authoring of anything more complex would be difficult to maintain, debug and understand later.
I certainly do not want to make a generalized js-in-c# thing. I am just looking for something to produce small js functions that can interact with the Html DOM, with the constraints and features requested in the question. I think you have provided that fairly well. I appreciate your effort and comments.
|
0

Here is my SignalR implementation test(please read the comments in the question):

ChatHub Class:

 Public Class ChatHub : Inherits Hub
    Public Sub MyTest(ByVal message As String)
        Clients.All.clientFuncTest("Hello from here, your message is: " + message)
    End Sub
 End Class

Client Side:

 $(function () {
        // Reference the auto-generated proxy for the hub.
        var chat = $.connection.chatHub;
        //reference to Clients.All.clientFuncTest
        chat.client.clientFuncTest = function(messageFromServer){
            alert(messageFromServer);
        }

        // Start the connection.
        $.connection.hub.start().done(function () {
            //Reference to Public Sub MyTest
            chat.server.myTest("this is a test");
        });
  });

This produce the following output in my site:

enter image description here

2 Comments

How does this solve the intellisense (signature hints) or mental context switching problems?
It seems that i miss that part of your question....but this approach achieve in a certain way what you are trying to accomplish without reinventing the wheel(is-it-possible-to-mark-a-c-sharp-or-vb-function-for-output-as-javascript-in-asp)
0

It's not quite the same thing but WebMethod and PageMethod attributes can help this become much more manageable IMHO.

See also how to call an ASP.NET c# method using javascript

You could also use a WebBrowser control to create your object implementations, then this becomes just like Node.js for the most part.

See also Read Javascript variable from Web Browser control

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.