static void Main(string[] args) { // To try to imitate the issue in the Engine as closely as possible, the following is done: // 1. A WSC is created (this is like a Control on Page) // 2. This WSC gets a Response reference and its Go method is called, both over IDispatch // 3. Inside the WSC, it will call Response.CreateObject to get another WSC - this one is wrapped in a FromFileWSCWrapper (so it is like a Booking Component) // 4. That WSC gets its Response reference set by VBScript in the first WSC and then "Go" is called on the second WSC, which calls Response.Redirect, which throws a RedirectException // 5. The error is caught in the IDispatchAccess layer, which was used when the first component called the second's "Go" method (since that invocation went through IReflect and so // through the FromFileWSCWrapper and called into the second component over IDispatch). The error has all of the message content, so it may be succesfully translated back into // a RedirectException. // 6. If we call the first component using IDispatchAccess then the error is caught again there - the error code is correct (it may be mapped back onto a RedirectException) but its // bstrDescription is null and so we can not retrieve the URL to redirect to (using COMSurvivableException.RethrowAsOriginalIfPossible will result in a RedirectException being // raised with a generic message "Exception of type 'System.Runtime.InteropServices.COMException' was thrown" which is not good // - However, if we call the first component's "Go" method using dynamic then we DO get the full RedirectException being throw, which is very good // Note: If the second component is not wrapped in a FromFileWSCWrapper then we get the full RedirectException whether we use IDispatch or dynamic var callOuterComponentMethodsUsingDynamic = false; var wscFile = new FileInfo("TestComponent.wsc"); var wsc = Interaction.GetObject("script:" + wscFile.FullName, null); try { var response = new WrappedResponse(new Response()); IDispatchAccess.SetProperty(wsc, "Response", response); // Have to use IDispatchAccess here since dynamic sets the property using PutDispProperty instead of PutRefDispProperty (.net bug, I think) if (callOuterComponentMethodsUsingDynamic) { ((dynamic)wsc).Go(); } else { IDispatchAccess.CallMethod(wsc, "Go"); } } catch (RedirectException e) { Console.WriteLine("Redirect: " + e.Message); if (e.Message.StartsWith("Exception of type")) { Console.WriteLine("^ ********** This is a disappointment"); } else { Console.WriteLine("^ ********** THIS IS WHAT WE WANT!"); } } catch (Exception e) { Console.WriteLine("FAIL: " + e.Message + " (" + e.GetType().Name + ")"); Console.WriteLine("^ ********** This is a disappointment"); } Console.ReadLine(); }
private object InvokeMemberInner(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) { if (name == null) { throw new ArgumentNullException("name"); } if (args == null) { throw new ArgumentNullException("args"); } var requireDefaultMember = string.IsNullOrWhiteSpace(name) || (name == DispIdZeroIdentifier); if (!requireDefaultMember && invokeAttr.HasFlag(BindingFlags.GetProperty) && invokeAttr.HasFlag(BindingFlags.InvokeMethod)) { // When VBScript tries to access a member that is not clearly a property or a method (eg. "a = x.Name") then it will include binding flags GetProperty AND // AND InvokeMethod but we need to try to work out which of the two it is now because the IDispatchAccess code doesn't like that ambiguity. var nameComparison = invokeAttr.HasFlag(BindingFlags.IgnoreCase) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; if (_template.GetProperties().Any(p => p.Name.Equals(name, nameComparison))) { invokeAttr = invokeAttr ^ BindingFlags.InvokeMethod; // It's a property so remove InvokeMethod by xor'ing } else { invokeAttr = invokeAttr ^ BindingFlags.GetProperty; // It's not a property so remove GetProperty by xor'ing } } if (invokeAttr.HasFlag(BindingFlags.GetProperty)) { object value; if (requireDefaultMember) { value = IDispatchAccess.GetDefaultProperty <object>(_target, args); } else { value = IDispatchAccess.GetProperty(_target, name, args); } return(CurrencyToFloatWhereApplicable(value)); } if (requireDefaultMember) { throw new Exception("Currently there is only support for default member (DispId zero) on GetProperty requests"); } if (invokeAttr.HasFlag(BindingFlags.SetProperty) || invokeAttr.HasFlag(BindingFlags.PutDispProperty) || invokeAttr.HasFlag(BindingFlags.PutRefDispProperty)) { var value = args[args.Length - 1]; if ((value == null) && invokeAttr.HasFlag(BindingFlags.PutRefDispProperty)) { // When a VBScript statement "Set x = Nothing" becomes an IReflect.InvokeMember call, the invokeAttr will have value PutRefDispProperty (because it's // a property setter call for an *object* reference, rather than PutDispProperty - which is for a non-object type) but the value will have been // interpreted as null (.net null, not VBScript null). This will be problematic if we try to pass it on to the underlying reference through // IDispatch because it will be like us saying "Set x = Empty", which will fail because Empty is not an object. So we need to look out // for this case and transform the .net null back into VBScript Nothing. value = new DispatchWrapper(null); } var argsWithoutValues = new object[args.Length - 1]; Array.Copy(args, argsWithoutValues, args.Length - 1); IDispatchAccess.SetProperty(_target, name, value, argsWithoutValues); return(null); } if (invokeAttr.HasFlag(BindingFlags.InvokeMethod)) { return(CurrencyToFloatWhereApplicable(IDispatchAccess.CallMethod(_target, name, args))); } throw new Exception("Don't know what to do with invokeAttr " + invokeAttr); }