Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 487
Sam Axe Avatar asked Nov 04 '13 02:11

Sam Axe


1 Answers

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.

like image 113
Tyree Jackson Avatar answered Oct 07 '22 17:10

Tyree Jackson