从 HTTP 模块获取 ASMX Web 服务的自定义属性

Get Custom Attribute on ASMX Web Service from HTTP Module

情况如下:

旧版 ASP.NET 产品。许多旧的 ASMX 服务(以及其他类型的端点 - ASPX、ASHX 等)。

我们正在增强一些安全逻辑。部分更改要求定义每个 ASMX 服务所属的应用程序模块。为此,我们计划使用下面显示的自定义属性。

[AttributeUsage(AttributeTargets.Class)]
public class ModuleAssignmentAttribute : Attribute
{
    public Module[] Modules { get; set; }

    public ModuleAssignmentAttribute(params Module[] modules)
    {
        Modules = modules;
    }
}

下面是如何将模块应用于 ASMX 服务的示例。

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ModuleAssignment(Module.ApplicationModuleA)]
public class SomeService : System.Web.Services.WebService
{
    [WebMethod(true)]
    public string GetValue()
    {
        return "Some Service Value";
    }
}

下面的 HTTP 模块将用于授权访问该服务。

public class MyAuthorizationModule : IHttpModule { public无效处置() { //清理代码在这里。 }

    public void Init(HttpApplication context)
    {
        context.PreRequestHandlerExecute += new EventHandler(OnAuthorizeRequest);
    }

    public void OnAuthorizeRequest(object sender, EventArgs e)
    {
        if (HttpContext.Current.Handler == null) return;

        Attribute att = Attribute.GetCustomAttribute(HttpContext.Current.Handler.GetType(), typeof(ModuleAssignmentAttribute));

        if (att != null)
        {
            Module[] modules = ((ModuleAssignmentAttribute)att).Modules;

            // Simulate getting the user's active role ID
            int roleId = 1;

            // Simulate performing an authz check
            AuthorizationAgent agent = new AuthorizationAgent();
            bool authorized = agent.AuthorizeRequest(roleId, modules);
            if (!authorized)
            {
                HttpContext.Current.Response.Clear();
                HttpContext.Current.Response.StatusCode = 401;
                HttpContext.Current.ApplicationInstance.CompleteRequest();
            }
        }
    }

问题是,对于 ASMX Web 服务,来自 HTTP 模块的以下代码行 returns null(请注意,这适用于 ASPX 页面)。

Attribute att = Attribute.GetCustomAttribute(HttpContext.Current.Handler.GetType(), typeof(ModuleAssignmentAttribute));

这种情况下HttpContext.Current.Handler.GetType()的值为"System.Web.Script.Services.ScriptHandlerFactory+HandlerWrapperWithSession"。该类型显然不知道 ASMX 服务上定义的自定义属性。

关于如何在这种情况下从 ASMX 服务类型获取自定义属性的任何想法?

这是问题的解决方案。需要反思。丑陋和脆弱的代码——如果你不需要,我不会推荐使用它。我很想知道我是否忽略了一种更优雅的方式。

    public void Init(HttpApplication context)
    {
        context.PreRequestHandlerExecute += new EventHandler(OnAuthorizeRequest);
    }

    public void OnAuthorizeRequest(object sender, EventArgs e)
    {
        if (HttpContext.Current.Handler == null) return;

        Attribute att = null;

        // ScriptHandlerFactory+HandlerWrapperWithSession is the type of handler for ASMX web service calls to web methods that use session.
        // This class is internal, so need to do a string comparison here (is there another way?).
        if (HttpContext.Current.Handler.GetType().ToString() == "System.Web.Script.Services.ScriptHandlerFactory+HandlerWrapperWithSession")
        {
            // HandlerWrapperWithSession has a protected field named "_originalHandler" that it inherits from HandlerWrapper.
            FieldInfo originalHandlerField = HttpContext.Current.Handler.GetType().GetField("_originalHandler",BindingFlags.NonPublic | BindingFlags.Instance);
            object originalHandler = originalHandlerField.GetValue(HttpContext.Current.Handler);

            // The _originalHandler value is an instance of SyncSessionHandler.
            // The inheritance tree for SyncSessionHandler is:
            //
            //    WebServiceHandler
            //    ----> SyncSessionlessHandler
            //          ----> SyncSessionHandler
            //
            // We need to walk the tree up to the WebServiceHandler class.
            bool exitLoop = false;
            Type t = originalHandler.GetType();
            while (t != null)
            {
                // WebServiceHandler is internal, so again another string comparison.
                if (t.ToString() == "System.Web.Services.Protocols.WebServiceHandler")
                {
                    // WebServiceHandler has a private field named protocol.  This field has the type HttpGetServerProtocol.
                    FieldInfo protocolField = t.GetField("protocol", BindingFlags.NonPublic | BindingFlags.Instance);
                    object protocolValue = protocolField.GetValue(originalHandler);

                    // The inheritance tree for ServerProtocol is:
                    //
                    //    HttpServerProtocol
                    //    ----> HttpGetServerProtocol
                    //
                    // We need to walk the three up to the HttpServerProtocol class.
                    Type t2 = protocolValue.GetType();
                    while (t2 != null)
                    {
                        if (t2.ToString() == "System.Web.Services.Protocols.HttpServerProtocol")
                        {
                            // HttpServerProtocol has an internal property named MethodInfo.  This property has the type LogicalMethodInfo.
                            PropertyInfo methodInfoProperty = t2.GetProperty("MethodInfo", BindingFlags.NonPublic | BindingFlags.Instance);
                            object methodInfoValue = methodInfoProperty.GetValue(protocolValue);

                            // The LogicalMethodInfo class has a DeclaringType property.  This property stores the type of the ASMX service. 
                            att = Attribute.GetCustomAttribute(((LogicalMethodInfo)methodInfoValue).DeclaringType, typeof(ModuleAssignmentAttribute));
                            exitLoop = true;
                            break;
                        }

                        t2 = t2.BaseType;
                    }
                }

                if (exitLoop) break;
                t = t.BaseType;
            }
        }
        else
        {
            att = Attribute.GetCustomAttribute(HttpContext.Current.Handler.GetType(), typeof(ModuleAssignmentAttribute));
        }

        if (att != null)
        {
            Module[] modules = ((ModuleAssignmentAttribute)att).Modules;

            // Simulate getting the user's active role ID
            int roleId = 1;

            // Simulate performing an authz check
            AuthorizationAgent agent = new AuthorizationAgent();
            bool authorized = agent.AuthorizeRequest(roleId, modules);
            if (!authorized)
            {
                HttpContext.Current.Response.Clear();
                HttpContext.Current.Response.StatusCode = 401;
                HttpContext.Current.ApplicationInstance.CompleteRequest();
            }
        }
    }

我不得不使用 Reflector + 运行时调试来找到这个解决方案。