【问题标题】:Suppress proxy generation for some hubs or methods禁止某些集线器或方法的代理生成
【发布时间】:2013-12-11 15:01:15
【问题描述】:

我从 SignalR 开始,我有一个 SignalR 站点将向客户端广播消息,但我还需要一个管理界面来实际触发这些消息。管理页面将调用服务器端方法,而服务器端方法又会为普通用户调用客户端 Javascript 方法。所以我想我可以设置两个独立的集线器(一个用于管理员,一个用于其他所有人),或者我可以在一个集线器中拥有只能由管理员调用的方法,以检查授权。

但除了授权之外,我希望 SignalR 在生成的 Javascript 代理类中不包含管理方法或管理中心,这样我就不会宣传它们的存在(再次强调 - 这不是唯一的安全措施,我将检查授权)。是否可以在单个集线器或集线器内的方法上设置属性或属性,以阻止它们包含在代理中(但仍然可以从 Javascript 调用它们)?我知道您可以在您的HubConfiguration 中将EnableJavaScriptProxies 设置为false,但这似乎是全球性的,我想保留我希望常规客户端使用的东西的代理。

【问题讨论】:

  • 不确定 SignalR 生成器中是否有等效项,但可以使用 ProxyAPI 作为生成器,它可以让您使用 [ProxyExclude] 方法
  • 一种选择是使用 signalr.exe 在构建时生成代理并编辑输出。
  • @el_tone:是的,我也是这么想的。对于这个特定的项目,它可能不会太复杂(不是很大),但对于更复杂的项目,这将是一个巨大的痛苦。

标签: javascript proxy signalr


【解决方案1】:

使用接口有一个技巧。由于代理只会在代理中生成公共方法,因此您可以使用如下接口创建集线器:

public class MyHub : Hub, IMyHub
{
    void IMyHub.NotGeneratedOnClient()
    {
    }

    public void GeneratedOnClient()
    {
    }
}

如果使用 MyHub 类型的对象,NotGeneratedOnClient 方法将不可见,您只能通过接口访问它。由于方法不是公共代理生成器,因此不会将其添加到客户端代理

【讨论】:

  • 不幸的是,这似乎不起作用。虽然我没有在代理中看到该方法,但我也无法使用 Invoke() 手动调用它。将其标记为私有或受保护似乎具有相同的效果。我正在研究派生 ReflectedMethodDescriptorProvider(真的,没有虚函数!?!?)
【解决方案2】:

我们今天没有办法从代理中排除特定方法。您必须重新实现自己的代理生成器,该生成器基本上完成了我们在默认 impl 中所做的工作,但知道某些属性以跳过特定方法的生成。

我们可以想象在 SignalR 的未来版本中添加它。如果您对此有强烈的感觉,请在 github 上提交问题。

这是默认实现(如果我们创建更多虚拟和非静态方法会更容易)。

https://github.com/SignalR/SignalR/blob/master/src/Microsoft.AspNet.SignalR.Core/Hubs/DefaultJavaScriptProxyGenerator.cs

【讨论】:

  • 谢谢大卫。它看起来确实很有用。目前我可能只是抓取生成的代理,手动编辑它,然后作为静态类添加回来。当然,如果我以后需要更换集线器,那会很烦人。
【解决方案3】:

这是一个修改后的 DefaultJavaScriptProxyGenerator,有以下变化:

  1. 它将使用新的 [HubMethodExcludeFromProxy] 属性从 Javascript 代理生成中排除函数。
  2. private static 函数已更改为 protected virtual 以供未来衍生。
  3. GenerateProxy( ) 函数具有包含 DocComments 的重载,但它不像非 DocComments 版本那样缓存结果。现在它们都缓存了。
  4. 两个资源,Resources.DynamicComment_CallsMethodOnServerSideDeferredPromiseResources.DynamicComment_ServerSideTypeIs 是另一个程序集的私有资源,所以为了编译,我直接从资源文件中复制了文本。仅当 DocComments 为 true 时才使用这两个资源。
  5. 所有 DefaultJavaScriptProxyGenerator 引用都更改为 CustomJavaScriptProxyGenerator,除了一个用于定位资源脚本 Microsoft.AspNet.SignalR.Scripts.hubs .js,位于不同的程序集中。

首先,您需要更新依赖解析器以使用新的 CustomJavaScriptProxyGenerator 用于 IJavaScriptProxyGenerator 接口。如果您使用的是默认解析器,您可以像这样设置自定义解析器:

map.RunSignalR( 新的 HubConfiguration() { 解析器 = 新的 CustomDependencyResolver() } );

这是一个从 DefaultDependecyResolver 派生的自定义解析器:

命名空间 Microsoft.AspNet.SignalR { 公共类 CustomDependencyResolver : DefaultDependencyResolver { MyDependencyResolver() : 基础() { var proxyGenerator = new Lazy(() => new CustomJavaScriptProxyGenerator(this)); Register(typeof(IJavaScriptProxyGenerator), () => proxyGenerator.Value); } } }

最后,这是新的 CustomJavaScriptProxyGenerator.cs 文件(HubMethodExcludeFromProxyAttribute 类在底部):

// 版权所有 (c) .NET 基金会。版权所有。 // 根据 Apache 许可证 2.0 版获得许可。有关许可证信息,请参阅项目根目录中的 License.txt。 // Brain2000 的模组 使用系统; 使用 System.Collections; 使用 System.Collections.Generic; 使用 System.Globalization; 使用 System.IO; 使用 System.Linq; 使用 System.Text; 使用 Microsoft.AspNet.SignalR.Json; 使用 Microsoft.AspNet.SignalR.Hubs; 使用 Newtonsoft.Json; 命名空间 Microsoft.AspNet.SignalR.Hubs { 公共类 CustomJavaScriptProxyGenerator : IJavaScriptProxyGenerator { protected static readonly Lazy _templateFromResource = new Lazy(GetTemplateFromResource); protected static readonly Type[] _numberTypes = new[] { typeof(byte), typeof(short), typeof(int), typeof(long), typeof(float), typeof(decimal), typeof(double) }; protected static readonly Type[] _dateTypes = new[] { typeof(DateTime), typeof(DateTimeOffset) }; protected const string ScriptResource = "Microsoft.AspNet.SignalR.Scripts.hubs.js"; 受保护的只读 IHubManager _manager; 受保护的只读 IJavaScriptMinifier _javaScriptMinifier; protected readonly Lazy _generatedTemplate; 受保护的只读 Lazy _generatedTemplateWithComments; 公共 CustomJavaScriptProxyGenerator(IDependencyResolver 解析器): 这(解析器。解析(), 解析器.Resolve()) { } 公共 CustomJavaScriptProxyGenerator(IHubManager 管理器,IJavaScriptMinifier javaScriptMinifier) { _manager = 经理; _javaScriptMinifier = javaScriptMinifier ?? NullJavaScriptMinifier.Instance; _generatedTemplate = new Lazy(() => GenerateProxy(_manager, _javaScriptMinifier, includeDocComments: false)); _generatedTemplateWithComments = new Lazy(() => GenerateProxy(_manager, _javaScriptMinifier, includeDocComments: true)); } 公共字符串 GenerateProxy(字符串 serviceUrl) { serviceUrl = JavaScriptEncode(serviceUrl); return _generatedTemplate.Value.Replace("{serviceUrl}", serviceUrl); } 公共字符串 GenerateProxy(string serviceUrl, bool includeDocComments) { if (!includeDocComments) return GenerateProxy(serviceUrl); //使用 includeDocComments: false 缓存版本 serviceUrl = JavaScriptEncode(serviceUrl); return _generatedTemplateWithComments.Value.Replace("{serviceUrl}", serviceUrl); } 受保护的虚拟字符串 GenerateProxy(IHubManager hubManager, IJavaScriptMinifier javaScriptMinifier, bool includeDocComments) { 字符串脚本 = _templateFromResource.Value; var hubs = new StringBuilder(); var first = true; foreach(hubManager.GetHubs().OrderBy(h => h.Name) 中的 var 描述符) { 如果(!第一) { hubs.AppendLine(";"); hubs.AppendLine(); hubs.Append(""); } GenerateType(hubManager, hubs, 描述符, includeDocComments); 第一=假; } if (hubs.Length > 0) { hubs.Append(";"); } script = script.Replace("/*hubs*/", hubs.ToString()); 返回 javaScriptMinifier.Minify(脚本); } protected virtual void GenerateType(IHubManager hubManager, StringBuilder sb, HubDescriptor 描述符, bool includeDocComments) { // 仅获取具有最少参数数量的操作。 var 方法 = GetMethods(hubManager, 描述符); var hubName = GetDescriptorName(描述符); sb.AppendFormat(" proxies['{0}'] = this.createHubProxy('{1}'); ", hubName, hubName).AppendLine(); sb.AppendFormat("代理['{0}'].client = {{ }};", hubName).AppendLine(); sb.AppendFormat("代理['{0}'].server = {{", hubName); 布尔优先=真; foreach(方法中的var方法) { 如果(!第一) { sb.Append(",").AppendLine(); } GenerateMethod(sb, method, includeDocComments, hubName); 第一=假; } sb.AppendLine(); sb.Append(" }"); } 受保护的虚拟字符串 GetDescriptorName(描述符描述符) { 如果(描述符 == 空) { 抛出新的 ArgumentNullException("descriptor"); } 字符串名称 = 描述符。名称; // 如果未指定名称,则不要使用驼峰式大小写 if (!descriptor.NameSpecified) { 名称 = JsonUtility.CamelCase(名称); } 返回名称; } 受保护的虚拟 IEnumerable GetMethods(IHubManager 管理器,HubDescriptor 描述符) { 从 manager.GetHubMethods(descriptor.Name).Where(md => md.Attributes.FirstOrDefault(a => (a.GetType() == typeof(HubMethodExcludeFromProxyAttribute))) == null) 中的方法返回 按 method.Name 将方法分组为重载 让 oload = (从重载中的重载 orderby 重载.Parameters.Count 选择重载).FirstOrDefault() orderby oload.Name 选择负载; } protected virtual void GenerateMethod(StringBuilder sb, MethodDescriptor method, bool includeDocComments, string hubName) { var parameterNames = method.Parameters.Select(p => p.Name).ToList(); sb.AppendLine(); sb.AppendFormat(" {0}: function ({1}) {{", GetDescriptorName(method), Commas(parameterNames)).AppendLine(); if (includeDocComments) { sb.AppendFormat(" /// 调用服务器端 {1} 集线器上的 {0} 方法。\n返回 jQuery.Deferred() 承诺。", method.Name, method.Hub.Name).AppendLine() ; var parameterDoc = method.Parameters.Select(p => String.Format(CultureInfo.CurrentCulture, " /// 服务器端类型为 {2}", p.Name, MapToJavaScriptType(p.ParameterType), p.ParameterType))。列表(); 如果(参数Doc.Any()) { sb.AppendLine(String.Join(Environment.NewLine, parameterDoc)); } } sb.AppendFormat(" return proxies['{0}'].invoke.apply(proxies['{0}'], $.merge([\"{1}\"], $.makeArray(arguments))) ;", hubName, method.Name).AppendLine(); sb.Append(" }"); } 受保护的虚拟字符串 MapToJavaScriptType(类型类型) { if (!type.IsPrimitive && !(type == typeof(string))) { 返回“对象”; } if (type == typeof(string)) { 返回“字符串”; } if (_numberTypes.Contains(type)) { 返回“数字”; } if (typeof(IEnumerable).IsAssignableFrom(type)) { 返回“数组”; } if (_dateTypes.Contains(type)) { 归期”; } 返回字符串。空; } 受保护的虚拟字符串逗号(IEnumerable 值) { 返回逗号(值,v => v); } 受保护的虚拟字符串逗号(IEnumerable 值,Func 选择器) { 返回 String.Join(", ", values.Select(selector)); } 受保护的静态字符串 GetTemplateFromResource() { //这必须保留为“DefaultJavaScriptProxyGenerator”,因为资源“Microsoft.AspNet.SignalR.Scripts.hubs.js”在那里 使用 (Stream resourceStream = typeof(DefaultJavaScriptProxyGenerator).Assembly.GetManifestResourceStream(ScriptResource)) { var reader = new StreamReader(resourceStream); 返回 reader.ReadToEnd(); } } 受保护的虚拟字符串 JavaScriptEncode(字符串值) { 值 = JsonConvert.SerializeObject(值); // 删除引号 返回 value.Substring(1, value.Length - 2); } } [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] 公共密封类 HubMethodExcludeFromProxyAttribute :属性 { } }

现在您需要做的就是为您的 hub 方法添加一个装饰器,例如:

公共类 MyHub : 集线器 { [HubMethodExcludeFromProxy] 公共无效NotGeneratedOnClient() { } 公共无效 GeneratedOnClient() { } }

编辑:依赖注入存在一个问题,如果您有两个不同的解析器实例,一个在 GlobalHost.DependencyResolver 中,一个在 Signalr 配置中,有时会导致远程方法不行。这是修复:

//仅使用!一个!解析器的实例,或远程 SignalR 函数可能无法运行! var resolver = new CustomDependencyResolver(); GlobalHost.Configuration.DependencyResolver = 解析器; map.RunSignalR( 新的 HubConfiguration() { 解析器=解析器; } );

参考:https://github.com/SignalR/SignalR/issues/2807

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-18
    • 1970-01-01
    • 1970-01-01
    • 2010-10-29
    相关资源
    最近更新 更多