在ASP.NET MVC4中,是否有可能将C#或VB函数标记为输出JavaScript?

10

我有一个HtmlHelper扩展方法,它将JavaScript回调函数作为参数传递..例如:

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

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

如您所见,JavaScript 回调函数名称被传递给 HtmlHelper 扩展方法。 这将导致开发人员不得不参考文档以了解 jsCallbackFunction 函数需要哪些参数。

我更希望像这样:

@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

SomethingCoolCallbackDelegate提供目标函数的代码约定。然后编译器将在MVC页面上将jsCallbackFunction编译为JavaScript。

是否内置于.NET 4 / ASP.NET MVC 4 / Razor 2中或其他技术可以实现类似功能?

示例使用VB编写,但C#的解决方案也非常可接受。

澄清:

@gideon:请注意,jsCallbackFunction需要两个参数ei。但是,HtmlHelper扩展方法只请求一个字符串(javascript回调函数的名称),并没有指示此函数可能采用哪些参数。我要解决的问题有两个。

  • 首先是缺少的参数提示。传递代替“javascript回调名称”字符串的.NET委托类型将完成此操作。我愿意接受其他解决方案来实现此目标。我知道XML注释。它们不是真正的解决方案。

  • 其次,尝试让页面程序员工作在单一语言中。在JavaScript和VB(或js和C#)之间切换(至少对我来说)需要昂贵的上下文切换。我的大脑不能快速地进行转换。使我保持在VB或C#中工作更具生产力和成本效益。因此,能够编写.NET语言中的函数并将其编译为JavaScript,在ASP.NET MVC / Razor视图的上下文中运行,是我在这里寻找的。

@TyreeJackson:SomethingCool是我将编写的HtmlHelper扩展方法,用于输出HTML和JavaScript。JavaScript的一部分需要调用用户(程序员)提供的函数来做出一些决策。类似于您向ajax调用提供的成功或失败函数。


这是不可能的。例如,你如何用VB描述window.alert - kavun
@Kavun:Script#声称可以做到这一点,但我不知道如何使用它。我正在寻找内置于.NET框架中或具有良好文档的技术。 - Sam Axe
是的,ScriptSharp似乎可以从.NET代码生成JavaScript字符串 - http://stackoverflow.com/questions/17282522/how-to-dynamically-generate-javascript-using-scriptsharp - kavun
这会导致开发人员不得不参考文档以确定jsCallbackFunction函数需要哪些参数。我不完全理解你的问题。你想要解决什么问题?这段代码是做什么的?:@Html.SomethingCool("containerName", "jsCallbackFunction") - gideon
我不明白你想要实现什么,能否举个例子展示一下你遇到的问题? - Kaushik Thanki
显示剩余11条评论
3个回答

5

虽然我不能为您提供完整的转译器/编译器选项,因为那将是一项巨大的工作,但我可以建议以下措施来协助 IntelliSense 支持和发出函数和调用。

这里是基础设施代码。您需要完成 getArgumentLiteral 和 getConstantFromArgument 函数以处理您遇到的其他情况,但这是一个不错的起点。

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) {}
}

这是一个标准函数支持包装器的示例:
public class alert : CoreJSFunction<alert, alert.signature>
{
    public delegate void signature(string message);
}

以下是几个示例Javascript函数支持包装器:
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!' : ''") {}
}

这是一个演示如何使用的控制台应用程序:

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();
    }
}

这是控制台应用程序的输出:

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!');

更新:

你可能也想要查看JSIL。我与该项目无关,无法对其稳定性、准确性和有效性进行评价,但它听起来很有趣,可能能够帮助你。


虽然这是一段足够不错的代码,但我不确定将js编写为C#字符串是否有助于上下文切换...这可能会使情况变得更糟。我喜欢参数/智能感知支持。非常好。我一定会尝试这个概念。谢谢。 - Sam Axe
@SamAxe 如果你想承担支持完整的ECMAScript标准的负担,你可以使用我在答案中概述的技术来重新创建所有常见JS和HTMLDom构造的方法签名,然后像我在答案中使用Linq.Expressions一样发出对它们的调用。只需跳过这些部分的定义发出即可。当然,你的C#代码看起来不会完全像编译后的Javascript,但你将获得智能感知。你需要为任何你使用的第三方JS库创建自己的包装器,比如JQuery。 - Tyree Jackson
@SamAxe 为了方便起见,我已经添加了CoreJSFunction类和alert示例包装支持类。 我应该提到,仅花费我所花费的少量时间,我觉得我为您提供的解决方案不值得花费完成它所需的时间。 您很可能会更好地训练自己和您的开发人员在JS和C#之间切换的艺术。 有一些策略可以缓解上下文切换的困扰,例如分离您的关注点,使您永远不会在同一文件中同时查看两种语言。 - Tyree Jackson
我打算把问题保持开放几天,看看还有什么其他的答案。 - Sam Axe
@SamAxe 我已经更新了我的答案,并附上了 JSIL 项目的链接。这可能是一个替代方案,可以帮助您成功地完成您想要实现的目标。 - Tyree Jackson
显示剩余2条评论

0

0
这是我的SignalR实现测试(请阅读问题中的注释):

ChatHub类:

 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

客户端:

 $(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");
        });
  });

这在我的网站上产生了以下输出:

enter image description here


这如何解决Intellisense(签名提示)或心理上下文切换问题? - Sam Axe
似乎我错过了你问题的那一部分...但是这种方法在某种程度上实现了你想要完成的目标,而不需要重新发明轮子(is-it-possible-to-mark-a-c-sharp-or-vb-function-for-output-as-javascript-in-asp)。 - Hackerman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接