抑制某些集线器或方法的代理生成

时间:2013-12-11 15:01:16

标签: javascript proxy signalr

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

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

3 个答案:

答案 0 :(得分:7)

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

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

    public void GeneratedOnClient()
    {
    }
}

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

答案 1 :(得分:2)

我们今天无法从代理中排除特定方法。您必须重新实现自己的代理生成器,它基本上执行我们在默认impl中所做的操作,但具有某些属性的知识,可以跳过特定方法的生成。

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

这是默认实现(如果我们将更多方法设置为虚拟和非静态,那会更容易。)

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

答案 2 :(得分:0)

这是经过修改的 DefaultJavaScriptProxyGenerator ,具有以下更改:

  1. 它将使用新的 [HubMethodExcludeFromProxy] 属性从Javascript代理生成中排除函数。
  2. 私有静态功能已更改为受保护的虚拟,以供将来使用。
  3. GenerateProxy()函数具有一个重载以包括DocComments,但这并未像非DocComments版本那样缓存结果。现在它们都缓存了。
  4. Resources.DynamicComment_CallsMethodOnServerSideDeferredPromise Resources.DynamicComment_ServerSideTypeIs 这两个资源是另一个程序集的专用资源,因此为了进行编译,我直接从资源文件中复制了文本。仅当DocComments为true时才使用这两个资源。
  5. 所有 DefaultJavaScriptProxyGenerator 引用均更改为 CustomJavaScriptProxyGenerator ,唯一的引用是用来查找资源脚本 Microsoft.AspNet.SignalR.Scripts.hubs的引用.js ,位于其他程序集中。

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

map.RunSignalR(
    new HubConfiguration() {
        Resolver = new CustomDependencyResolver()
    }
);

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

namespace Microsoft.AspNet.SignalR
{
    public class CustomDependencyResolver : DefaultDependencyResolver
    {
        MyDependencyResolver() : base()
        {
            var proxyGenerator = new Lazy(() => new CustomJavaScriptProxyGenerator(this));
            Register(typeof(IJavaScriptProxyGenerator), () => proxyGenerator.Value);
        }
    }
}

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

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Mods by Brain2000
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNet.SignalR.Json;
using Microsoft.AspNet.SignalR.Hubs;
using Newtonsoft.Json;

namespace Microsoft.AspNet.SignalR.Hubs
{
    public class 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";

        protected readonly IHubManager _manager;
        protected readonly IJavaScriptMinifier _javaScriptMinifier;
        protected readonly Lazy _generatedTemplate;
        protected readonly Lazy _generatedTemplateWithComments;

        public CustomJavaScriptProxyGenerator(IDependencyResolver resolver) :
            this(resolver.Resolve(),
                 resolver.Resolve())
        {
        }

        public CustomJavaScriptProxyGenerator(IHubManager manager, IJavaScriptMinifier javaScriptMinifier)
        {
            _manager = manager;
            _javaScriptMinifier = javaScriptMinifier ?? NullJavaScriptMinifier.Instance;
            _generatedTemplate = new Lazy(() => GenerateProxy(_manager, _javaScriptMinifier, includeDocComments: false));
            _generatedTemplateWithComments = new Lazy(() => GenerateProxy(_manager, _javaScriptMinifier, includeDocComments: true));
        }

        public string GenerateProxy(string serviceUrl)
        {
            serviceUrl = JavaScriptEncode(serviceUrl);
            return _generatedTemplate.Value.Replace("{serviceUrl}", serviceUrl);
        }

        public string GenerateProxy(string serviceUrl, bool includeDocComments)
        {
            if (!includeDocComments) return GenerateProxy(serviceUrl); //use the includeDocComments: false cached version

            serviceUrl = JavaScriptEncode(serviceUrl);
            return _generatedTemplateWithComments.Value.Replace("{serviceUrl}", serviceUrl);
        }

        protected virtual string GenerateProxy(IHubManager hubManager, IJavaScriptMinifier javaScriptMinifier, bool includeDocComments)
        {
            string script = _templateFromResource.Value;

            var hubs = new StringBuilder();
            var first = true;
            foreach (var descriptor in hubManager.GetHubs().OrderBy(h => h.Name))
            {
                if (!first)
                {
                    hubs.AppendLine(";");
                    hubs.AppendLine();
                    hubs.Append("    ");
                }
                GenerateType(hubManager, hubs, descriptor, includeDocComments);
                first = false;
            }

            if (hubs.Length > 0)
            {
                hubs.Append(";");
            }

            script = script.Replace("/*hubs*/", hubs.ToString());

            return javaScriptMinifier.Minify(script);
        }

        protected virtual void GenerateType(IHubManager hubManager, StringBuilder sb, HubDescriptor descriptor, bool includeDocComments)
        {
            // Get only actions with minimum number of parameters.
            var methods = GetMethods(hubManager, descriptor);
            var hubName = GetDescriptorName(descriptor);

            sb.AppendFormat("    proxies['{0}'] = this.createHubProxy('{1}'); ", hubName, hubName).AppendLine();
            sb.AppendFormat("        proxies['{0}'].client = {{ }};", hubName).AppendLine();
            sb.AppendFormat("        proxies['{0}'].server = {{", hubName);

            bool first = true;

            foreach (var method in methods)
            {
                if (!first)
                {
                    sb.Append(",").AppendLine();
                }
                GenerateMethod(sb, method, includeDocComments, hubName);
                first = false;
            }
            sb.AppendLine();
            sb.Append("        }");
        }

        protected virtual string GetDescriptorName(Descriptor descriptor)
        {
            if (descriptor == null)
            {
                throw new ArgumentNullException("descriptor");
            }

            string name = descriptor.Name;

            // If the name was not specified then do not camel case
            if (!descriptor.NameSpecified)
            {
                name = JsonUtility.CamelCase(name);
            }

            return name;
        }

        protected virtual IEnumerable GetMethods(IHubManager manager, HubDescriptor descriptor)
        {
            return from method in manager.GetHubMethods(descriptor.Name).Where(md => md.Attributes.FirstOrDefault(a => (a.GetType() == typeof(HubMethodExcludeFromProxyAttribute))) == null)
                   group method by method.Name into overloads
                   let oload = (from overload in overloads
                                orderby overload.Parameters.Count
                                select overload).FirstOrDefault()
                   orderby oload.Name
                   select oload;
        }

        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("            /// Calls the {0} method on the server-side {1} hub.\nReturns a jQuery.Deferred() promise.", method.Name, method.Hub.Name).AppendLine();
                var parameterDoc = method.Parameters.Select(p => String.Format(CultureInfo.CurrentCulture, "            /// Server side type is {2}", p.Name, MapToJavaScriptType(p.ParameterType), p.ParameterType)).ToList();
                if (parameterDoc.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("             }");
        }

        protected virtual string MapToJavaScriptType(Type type)
        {
            if (!type.IsPrimitive && !(type == typeof(string)))
            {
                return "Object";
            }
            if (type == typeof(string))
            {
                return "String";
            }
            if (_numberTypes.Contains(type))
            {
                return "Number";
            }
            if (typeof(IEnumerable).IsAssignableFrom(type))
            {
                return "Array";
            }
            if (_dateTypes.Contains(type))
            {
                return "Date";
            }
            return String.Empty;
        }

        protected virtual string Commas(IEnumerable values)
        {
            return Commas(values, v => v);
        }

        protected virtual string Commas(IEnumerable values, Func selector)
        {
            return String.Join(", ", values.Select(selector));
        }

        protected static string GetTemplateFromResource()
        {
            //this must remain "DefaultJavaScriptProxyGenerator" because the resource "Microsoft.AspNet.SignalR.Scripts.hubs.js" lives there
            using (Stream resourceStream = typeof(DefaultJavaScriptProxyGenerator).Assembly.GetManifestResourceStream(ScriptResource))
            {
                var reader = new StreamReader(resourceStream);
                return reader.ReadToEnd();
            }
        }

        protected virtual string JavaScriptEncode(string value)
        {
            value = JsonConvert.SerializeObject(value);

            // Remove the quotes
            return value.Substring(1, value.Length - 2);
        }
    }

    [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
    public sealed class HubMethodExcludeFromProxyAttribute : Attribute
    {
    }
}

现在,您所需要做的就是装饰所有集线器方法,例如:

public class MyHub : Hub
{
    [HubMethodExcludeFromProxy]
    public void NotGeneratedOnClient()
    {
    }

    public void GeneratedOnClient()
    {
    }
}

EDIT :依赖项注入存在一个问题,如果您有两个不同的解析器实例,一个在GlobalHost.DependencyResolver中,一个在Signalr配置中,则有时会导致远程方法不行。解决方法如下:

//use only !ONE! instance of the resolver, or remote SignalR functions may not run!
var resolver = new CustomDependencyResolver();
GlobalHost.Configuration.DependencyResolver = resolver;
map.RunSignalR(
    new HubConfiguration() {
        Resolver = resolver;
    }
);

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