Is it possible to mark a C# or VB function for output as Javascript in ASP.NET MVC4?
Asked Answered
M

3

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.

Mcquoid answered 4/11, 2013 at 2:20 Comment(16)
This is not possible. For example, how would you describe window.alert in VB?Postconsonantal
@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.Mcquoid
Yea, ScriptSharp seems like it might be able to generate a javascript string from .NET code - #17283022Postconsonantal
"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") Baranowski
I am not getting what you want to achieve can u put some example that show what problem you haveMiddlesworth
What exactly is SomethingCool?Fiann
@gideon: added some clarification - hope that better explains.Mcquoid
@TyreeJackson: added some clarification - hope that is clearer.Mcquoid
One alternative to this would be to formulate the JavaScript on the server in a function in which the parameters can be validated. Then send that string to the client...six of one, half a dozen of the other.Descant
You can do something like that, with SignalR; real time communication between functions server side and client sideMacintosh
@lrb: If I understand correctly that would still have the programmer switching contexts.. not only switching, but mixing, because you would end up with a function that would have lines for building and outputting js like stringBuilder.AppendLine(" function jsCallback(arg1, arg2) {").Mcquoid
@Hackerman: hmmm. I've heard of SignalR but have never used it. Seems kind of heavy-handed to do a network call just to implement a simple javascript function though, no? In any case, if you think SignalR is a good solution then write it up as an answer and I'll look into it. Thanks for the idea.Mcquoid
This is the same reason that the Html.Action()'s controller, action and parameters can not be validated during compilation. They are not known at compile-time...there is a disconnect between the client and server.Descant
I have an implementation in VB code(MVC) and SignalR, i can post a little example as an answer if you wantMacintosh
@Hackerman: that would be helpful, thanks.Mcquoid
I think in generic case is unsolvable without enormous efforts. Would you consider passing in something like lambda and also issuing in the same HtmlHelper a client-side validation (assert) that ensures that js function exists and has correct signature? For example, check here how to get js function arguments by "reflection": #1008481Sapp
F
5

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.

Fiann answered 31/7, 2015 at 21:49 Comment(7)
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.Mcquoid
@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.Fiann
@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.Fiann
@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.Fiann
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.Mcquoid
I'm going to leave the question open for a couple days to see what else comes through.Mcquoid
@SamAxe I've updated my answer with a link to the JSIL project. It may be an alternative that may help you to be successful in what you are trying to accomplish.Fiann
P
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

Piacular answered 4/11, 2013 at 2:21 Comment(0)
M
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

Macintosh answered 3/8, 2015 at 16:44 Comment(2)
How does this solve the intellisense (signature hints) or mental context switching problems?Mcquoid
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)Macintosh

© 2022 - 2024 — McMap. All rights reserved.