Suppress proxy generation for some hubs or methods

后端 未结 3 409
渐次进展
渐次进展 2021-01-03 07:21

I\'m starting out with SignalR and I have a situation where I\'m going to have a SignalR site that will be broadcasting messages to clients, but I also need an admin interfa

相关标签:
3条回答
  • 2021-01-03 07:39

    There is one trick using interfaces. As proxy will generate only public methods in proxy, you can create hub using interface like this:

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

    NotGeneratedOnClient method will not be visible if you use object of type MyHub, you can access it only using interface. As method is not public proxy generator is not going to add it to client proxy

    0 讨论(0)
  • 2021-01-03 07:48

    We don't have a way of excluding specific methods from the proxy today. You'd have to re-implement your own proxy generator that basically does what we do in our default impl but has knowledge of some attribute to skip generation of specific methods.

    We can conceivable add this in a future version of SignalR. File an issue on github if you feel strongly about having this.

    Here's the default implementation (it would have been easier if we made more methods virtual and non static).

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

    0 讨论(0)
  • 2021-01-03 07:52

    Here is a modified DefaultJavaScriptProxyGenerator with the following changes:

    1. It will exclude functions from Javascript proxy generation with a new [HubMethodExcludeFromProxy] attribute.
    2. The private static functions have changed to protected virtual for future derivatives.
    3. The GenerateProxy( ) function has an overload to include DocComments, but that was not caching the results like the non DocComments version. Now they both cache.
    4. Two resources, Resources.DynamicComment_CallsMethodOnServerSideDeferredPromise and Resources.DynamicComment_ServerSideTypeIs were private to another assembly, so to get things to compile, I copied the text from the resource file directly. These two resources are only used if DocComments is true.
    5. All of the DefaultJavaScriptProxyGenerator references were changed to CustomJavaScriptProxyGenerator except for one, which is used to locate the resource script Microsoft.AspNet.SignalR.Scripts.hubs.js, located in a different assembly.

    First, you will need to update the dependency resolver to use the new CustomJavaScriptProxyGenerator for the IJavaScriptProxyGenerator interface. If you are using the default resolver, you can set up a custom resolver like this:

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

    And here is a custom resolver that derives from the DefaultDependecyResolver:

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

    And finally, here is the new CustomJavaScriptProxyGenerator.cs file (the HubMethodExcludeFromProxyAttribute class is at the bottom):

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

    Now all you need to do is all a decorator to your hub methods, such as:

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

    EDIT : There is an issue with dependency injection where if you have two different instances of a resolver, one in the GlobalHost.DependencyResolver and one in the Signalr configuration, it will cause remote methods to sometimes not work. Here is the fix:

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

    Reference: https://github.com/SignalR/SignalR/issues/2807

    0 讨论(0)
提交回复
热议问题