In WCF BehaviorExtension BeforeSendRequest method is not getting called?I m stuck please help me...below is code - wcf

Here is code for the which I m trying.I want to modify the request packet of service. Genarally the request header,body and action
[CustomBehavior]
public class Service1 : IService1
{
.....
......
}
public class CustomHeader : MessageHeader
{
.....
.....
....
}
/// <summary>
/// This class is used to inspect the message and headers on the server side,
/// This class is also used to intercept the message on the
/// client side, before/after any request is made to the server.
/// </summary>
public class CustomMessageInspector : IClientMessageInspector, IDispatchMessageInspector
{
#region Message Inspector of the Service
/// <summary>
/// This method is called on the server when a request is received from the client.
/// </summary>
/// <param name="request"></param>
/// <param name="channel"></param>
/// <param name="instanceContext"></param>
/// <returns></returns>
public object AfterReceiveRequest(ref Message request,
IClientChannel channel, InstanceContext instanceContext)
{
// Create a copy of the original message so that we can mess with it.
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
Message messageCopy = buffer.CreateMessage();
// Read the custom context data from the headers
ServiceHeader customData = CustomHeader.ReadHeader(request);
// Add an extension to the current operation context so
// that our custom context can be easily accessed anywhere.
ServerContext customContext = new ServerContext();
if (customData != null)
{
customContext.KerberosID = customData.KerberosID;
customContext.SiteminderToken = customData.SiteminderToken;
}
OperationContext.Current.IncomingMessageProperties.Add(
"CurrentContext", customContext);
return null;
}
/// <summary>
/// This method is called after processing a method on the server side and just
/// before sending the response to the client.
/// </summary>
/// <param name="reply"></param>
/// <param name="correlationState"></param>
public void BeforeSendReply(ref Message reply, object correlationState)
{
// Do some cleanup
OperationContext.Current.Extensions.Remove(ServerContext.Current);
}
#endregion
#region Message Inspector of the Consumer
/// <summary>
/// This method will be called from the client side just before any method is called.
/// </summary>
/// <param name="request"></param>
/// <param name="channel"></param>
/// <returns></returns>
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
// Prepare the request message copy to be modified
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
ServiceHeader customData = new ServiceHeader();
customData.KerberosID = ClientContext.KerberosID;
customData.SiteminderToken = ClientContext.SiteminderToken;
CustomHeader header = new CustomHeader(customData);
// Add the custom header to the request.
request.Headers.Add(header);
return null;
}
/// <summary>
/// This method will be called after completion of a request to the server.
/// </summary>
/// <param name="reply"></param>
/// <param name="correlationState"></param>
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
#endregion
}
/// <summary>
/// This class will act as a custom context in the client side to hold the context information.
/// </summary>
public class ClientContext
{
public static string EmployeeID;
public static string WindowsLogonID;
public static string KerberosID;
public static string SiteminderToken;
}
/// <summary>
/// This custom behavior class is used to add both client and server inspectors to
/// the corresponding WCF end points.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class CustomBehavior : Attribute, IEndpointBehavior
{
#region IEndpointBehavior Members
void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint,
System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
return;
}
void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
CustomMessageInspector inspector = new CustomMessageInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
ChannelDispatcher channelDispatcher = endpointDispatcher.ChannelDispatcher;
if (channelDispatcher != null)
{
foreach (EndpointDispatcher ed in channelDispatcher.Endpoints)
{
CustomMessageInspector inspector = new CustomMessageInspector();
ed.DispatchRuntime.MessageInspectors.Add(inspector);
}
}
}
void IEndpointBehavior.Validate(ServiceEndpoint endpoint) { return; }
#endregion
}
public class MyBehaviorExtensionElement : BehaviorExtensionElement
{
public MyBehaviorExtensionElement() { }
public override Type BehaviorType
{
get { return typeof(CustomBehavior); }
}
protected override object CreateBehavior()
{
return new CustomBehavior();
}
}

You need to add the extension to either your code or configuration: (config example below):
<extensions>
<behaviorExtensions>
<add name="yourMessageInspector"
type="MyBehaviorExtensionElement" />
</behaviorExtensions>
</extensions>
The following references should provide the background you need:
https://msdn.microsoft.com/en-us/library/ms730137%28v=vs.110%29.aspx
wcf :custom message inspector does not get wired up

Related

WCF - Error Handling - Creator of this service did not specify a Reason

There are lot of threads with the same error but I could not find a suitable answer so putting it again.
I have a wcf service and I have error handling in place and when I test the service from the wcftestclient all I get is this error info and not the original error that actually occurred.
The creator of this fault did not specify a Reason. Server stack trace: at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc) at ...
I am not sure why its not returning me the actual error. My error handling code is given below.
/// <summary>
/// Gets the holidays.
/// </summary>
/// <returns>List<HolidayProfile></returns>
[OperationContract]
[FaultContract(typeof(ExceptionFaultContract))]
List<HolidayProfile> GetHolidays();
public List<HolidayProfile> GetHolidays()
{
List<HolidayProfile> holidayProfileList = new List<HolidayProfile>();
try
{
customerBL = new CustomerBL(new CustomerRepository());
holidayProfileList = customerBL.GetHolidays();
}
catch (CustomException ex)
{
throw new FaultException<ExceptionFaultContract>(DBHelper.MapException(ex));
}
return holidayProfileList;
}
I have used two custom Exception classes here. One which is derived from Exception and other is the one which will be passed between the service and client
/// <summary>
/// Class CustomException.
/// </summary>
/// <seealso cref="System.Exception" />
public class CustomException : Exception
{
/// <summary>
/// Gets or sets the error code.
/// </summary>
/// <value>The error code.</value>
public string ErrorCode { get; set; }
/// <summary>
/// Gets or sets the error detail.
/// </summary>
/// <value>The error detail.</value>
public string ErrorDescription { get; set; }
/// <summary>
/// Gets or sets the error number.
/// </summary>
/// <value>The error number.</value>
public int ErrorNumber { get; set; }
}
and another which is the datacontract.
/// <summary>
/// Class ExceptionFaultContract.
/// </summary>
[DataContract]
public class ExceptionFaultContract : Exception
{
/// <summary>
/// Gets or sets the error code.
/// </summary>
/// <value>The error code.</value>
[DataMember]
public string ErrorCode { get; set; }
/// <summary>
/// Gets or sets the error number.
/// </summary>
/// <value>The error number.</value>
[DataMember]
public int ErrorNumber { get; set; }
/// <summary>
/// Gets or sets the error description.
/// </summary>
/// <value>The error description.</value>
[DataMember]
public string ErrorDescription { get; set; }
}
My method in the dataacess.
public List<HolidayProfile> GetHolidays()
{
Initializations
try
{
Code here...
}
catch (OracleException ox)
{
throw (DBHelper.HandleOracleException(ox));
}
catch (Exception ex)
{
throw (DBHelper.HandleException1(ex));
}
finally
{
if (connection != null)
connection.Close();
if (command != null)
command = null;
if (dataAdapter != null)
dataAdapter = null;
dataSet.Dispose();
}
return holidayProfileList;
}
My helper class which does the mapping.
/// <summary>
/// Handles the exception.
/// </summary>
/// <param name="ex">The ex.</param>
/// <returns>ExceptionFaultContract.</returns>
public static CustomException HandleException(Exception ex)
{
CustomException customException = new CustomException();
customException.ErrorDescription = ex.Message;
return customException;
}
/// <summary>
/// Maps the exception.
/// </summary>
/// <param name="ex">The ex.</param>
/// <returns>ExceptionFaultContract.</returns>
public static ExceptionFaultContract MapException(CustomException ex)
{
ExceptionFaultContract exceptionFaultContract = new ExceptionFaultContract();
exceptionFaultContract.ErrorCode = ex.ErrorCode;
exceptionFaultContract.ErrorNumber = ex.ErrorNumber;
exceptionFaultContract.ErrorDescription = ex.ErrorDescription;
return exceptionFaultContract;
}
I m getting an error if I m using the ExceptionFaultContract to be returned from data access ("Error : should be a type derived from exception") and If I try to derive the same from Exception another error saying it cannot be both serializable and derived member or something like that..
I tried lot of things nothing is clear to me...could somebody please help.

IPrincipal should be ClaimsPrincipal but is WindowsPrincipal

I've got a controller with 3 action methods on it, two of which are regular OData calls and a third which is a function. Using Azure AD and ADAL to secure the WebAPI.
CustomAuthAttribute (IsAppAuthorizing Simply checks a web.config entry)
public class OpsmApiAuthorizeAttribute : AuthorizeAttribute
{
/// <summary>
/// Returns whether or not the user has authenticated with ADFS and whehter ornot we are configured to do authorization
/// </summary>
/// <param name="actionContext"></param>
/// <returns></returns>
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (HttpContext.Current.IsAppAuthorizing())
return base.IsAuthorized(actionContext);
return true;
}
}
Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"],
},
MetadataAddress = ConfigurationManager.AppSettings["ida:MetadataEndpoint"],
});
}
Controller (ByUser is the OData function that does not get proper IPrincple information, other two methods work fine)
[OpsmApiAuthorizeAttribute]
public class ProjectsController : BaseController
{
/// <summary>
/// Get a Project Detail for a given project id
/// </summary>
/// <returns>json</returns>
[EnableQuery]
public IQueryable<OPSM.DataAccess.Database.OpsM.PRJ> Get([FromODataUri] string key)
{
...
}
/// <summary>
/// Get all Projects
/// </summary>
/// <returns>json</returns>
[EnableQuery]
public IQueryable<OPSM.DataAccess.Database.OpsM.PRJ> Get()
{
...
}
[HttpGet]
//[CacheOutput(ServerTimeSpan = 60 * 60)]
public IHttpActionResult ByUser([FromODataUri]string userId)
{
...
}
}

Subclassing AuthorizeAttribute with WebApi not working returns 401?

I'm subclassing the AuthorizeAttribute so I can implement token authentication whereby the token is passed in the request header. I'm also using Ninject for IoC. The overriden OnAuthorization method gets called and validates the token but I still receive a 401.
Any ideas on why this is happening?
TokenAuthorisationAttribute.cs
public class TokenAuthorisationAttribute : AuthorizeAttribute
{
private readonly IRepository _repository;
public TokenAuthorisationAttribute(IRepository repository)
{
_repository = repository;
}
public override void OnAuthorization(
HttpActionContext actionContext)
{
if (!ValidateToken(actionContext.ControllerContext.Request))
HandleUnauthorizedRequest(actionContext);
base.OnAuthorization(actionContext);
}
private bool ValidateToken(HttpRequestMessage request)
{
const string authenticationToken = "Authentication-Token";
var token = request.Headers.GetValues(authenticationToken).FirstOrDefault();
if (token == null)
return false;
var device = _repository.FindSingleOrDefault<Device>(x => x.Id.Equals(token));
if (device == null || !token.Equals(device.Id))
return false;
return true;
}
}
NinjectWebCommon.cs
public static class NinjectWebCommon
{
private static readonly Bootstrapper Bootstrapper = new Bootstrapper();
/// <summary>
/// Starts the application
/// </summary>
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
Bootstrapper.Initialize(CreateKernel);
}
/// <summary>
/// Stops the application.
/// </summary>
public static void Stop()
{
Bootstrapper.ShutDown();
}
/// <summary>
/// Creates the kernel that will manage your application.
/// </summary>
/// <returns>The created kernel.</returns>
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
return kernel;
}
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
var connectionString = ConfigurationManager.ConnectionStrings["MONGOHQ_URL"].ConnectionString;
var databaseName = ConfigurationManager.AppSettings["Database"];
kernel.Bind<IRepository>().To<MongoRepository>()
.WithConstructorArgument("connectionString", connectionString)
.WithConstructorArgument("databaseName", databaseName);
kernel.BindHttpFilter<TokenAuthorisationAttribute>(FilterScope.Global);
}
I managed to resolve this issue by overriding the IsAuthorized method instead of OnAuthorization. Not 100% sure if this is the right approach? Any opinions ??
public class TokenAuthorisationAttribute : AuthorizeAttribute
{
private readonly IRepository _repository;
public TokenAuthorisationAttribute(IRepository repository)
{
_repository = repository;
}
protected override bool IsAuthorized(HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Authorization == null)
return false;
var authToken = actionContext.Request.Headers.Authorization.Parameter;
var decodedToken = Encoding.UTF8.GetString(Convert.FromBase64String(authToken));
var deviceToken = new DeviceToken(decodedToken);
var device = _repository.FindSingleOrDefault<Device>(x => x.Id.Equals(deviceToken.GetDeviceId()));
if (device != null)
{
HttpContext.Current.User = new GenericPrincipal(new ApiIdentity(device), new string[] {});
return true;
}
return base.IsAuthorized(actionContext);
}
}
The ASP.NET WebAPI is an open source project. So you can read the correspondingly codes here:
AuthorizationFilterAttribute http://aspnetwebstack.codeplex.com/SourceControl/changeset/view/03357424caf5#src%2fSystem.Web.Http%2fFilters%2fAuthorizationFilterAttribute.cs
AuthorizeAttribute
http://aspnetwebstack.codeplex.com/SourceControl/changeset/view/03357424caf5#src%2fSystem.Web.Http%2fAuthorizeAttribute.cs
There are two facts an AuthorizationFilterAttribute takes in to consider when it is making decision:
Is OnAuthorization throw exception; or
Is the actionContext's Response field is filled;
Either one is fulfilled, the remaining action filters and action are shortcut.
Based on your code, I'm curious if the function HandleUnauthorizedRequest does either of the operation above.
The reason why overriding IsAuthorized works, is because it works at AuthorizeAttribute level. The OnAuthorization call to the IsAuthorized overload and set value to actionContext's Request property.
Thanks,
Troy

Ninject ActivationException

I'm getting the infamous "Error activating XYZ. No matching bindings are available blah blah blah" from Ninject 3.0.1.10 in an ASP.NET MVC project.
The following is how I've setup my binding (very simple):
public static class NinjectWebCommon
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper();
/// <summary>
/// Starts the application
/// </summary>
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
bootstrapper.Initialize(CreateKernel);
}
/// <summary>
/// Stops the application.
/// </summary>
public static void Stop()
{
bootstrapper.ShutDown();
}
/// <summary>
/// Creates the kernel that will manage your application.
/// </summary>
/// <returns>The created kernel.</returns>
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
return kernel;
}
private static void RegisterServices(IKernel kernel)
{
string connectionString = WebConfigurationManager.ConnectionStrings["STAGING"].ConnectionString;
kernel.Bind<IUserAccountRepository>()
.To<SqlUserAccountRepository>()
.WithConstructorArgument("connectionString", connectionString);
kernel.Bind<IRecipeRepository>()
.To<SqlRecipeRepository>()
.WithConstructorArgument("connectionString", connectionString);
kernel.Bind<HomeViewModel>().ToSelf();
}
}
When the controller Index action is invoked it attempts to create a HomeViewModel object as such:
public ActionResult Index()
{
IKernel kernel = new StandardKernel();
HomeViewModel model = kernel.Get<HomeViewModel>();
...
Which triggers the exception:
Error activating IRecipeRepository No matching bindings are available, and the type is not self-bindable.
Activation path:
2) Injection of dependency IRecipeRepository into parameter recipeRepository of constructor of type HomeViewModel
1) Request for HomeViewModel
But if I attempt to get an instance of HomeViewModel directly after calling kernel.Bind in RegisterServices then it works fine.
Here's what the HomeViewModel looks like (some details removed for sake of brevity):
public class HomeViewModel
{
public HomeViewModel(IRecipeRepository recipeRepository,
IUserAccountRepository userAccountRepository)
{
this.recipeRepository = recipeRepository;
this.userAccountRepository = userAccountRepository;
}
//
// Some details removed for sake of brevity
//
private readonly IRecipeRepository recipeRepository;
private readonly IUserAccountRepository userAccountRepository;
}
Any idea what I'm missing? Why is the controller "special" in this case?

Applying same interaction trigger to multiple TextBoxes

I have multiple textboxes bound to different data, but I would like every one to launch the same command when the TextChanged event is fired. I could copy the interaction line under every textbox but I'm guessing there must be a way to use a template or style to get this working on all of them.
Here is the code for the first textbox
<TextBox Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" Height="28" HorizontalAlignment="Stretch" Margin="0,12,12,0" Name="TextBox_Description" VerticalAlignment="Top" TabIndex="3" Text="{Binding Item.Description, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding DataChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
If your UpdateMode is always going to be PropertyChanged then there really isn't any reason not to listen to your own PropertyChangedEvent in your ViewModel. It will definitely improve testability.
private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
switch (args.PropertyName)
{
case "Prop1":
case "Prop2":
.
.
.
DataChangedCommand.Execute(null);
break;
}
}
I'm not positive how to do it with Interaction.Triggers, but in the past I've used the AttachedCommand dependency properties found here and simply used a Style
Normal XAML
<TextBox local:CommandBehavior.Event="TextChanged"
local:CommandBehavior.Command="{Binding DataChangedCommand}"/>
Using a Style
<Style TargetType="TextBox">
<Setter Property="local:CommandBehavior.Event" Value="TextChanged" />
<Setter Property="local:CommandBehavior.Command" Value="{Binding DataChangedCommand}" />
</Style>
EDIT: Since you can't download files at work, here's the code
CommandBehavior.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Markup;
using System.Windows;
using System.Windows.Input;
// Code to attach a command to any event
// From http://marlongrech.wordpress.com/2008/12/04/attachedcommandbehavior-aka-acb/
namespace Keys.Controls.Helpers.CommandHelpers
{
/// <summary>
/// Defines the attached properties to create a CommandBehaviorBinding
/// </summary>
public class CommandBehavior
{
#region Behavior
/// <summary>
/// Behavior Attached Dependency Property
/// </summary>
private static readonly DependencyProperty BehaviorProperty =
DependencyProperty.RegisterAttached("Behavior", typeof(CommandBehaviorBinding), typeof(CommandBehavior),
new FrameworkPropertyMetadata((CommandBehaviorBinding)null));
/// <summary>
/// Gets the Behavior property.
/// </summary>
private static CommandBehaviorBinding GetBehavior(DependencyObject d)
{
return (CommandBehaviorBinding)d.GetValue(BehaviorProperty);
}
/// <summary>
/// Sets the Behavior property.
/// </summary>
private static void SetBehavior(DependencyObject d, CommandBehaviorBinding value)
{
d.SetValue(BehaviorProperty, value);
}
#endregion
#region Command
/// <summary>
/// Command Attached Dependency Property
/// </summary>
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(CommandBehavior),
new FrameworkPropertyMetadata((ICommand)null,
new PropertyChangedCallback(OnCommandChanged)));
/// <summary>
/// Gets the Command property.
/// </summary>
public static ICommand GetCommand(DependencyObject d)
{
return (ICommand)d.GetValue(CommandProperty);
}
/// <summary>
/// Sets the Command property.
/// </summary>
public static void SetCommand(DependencyObject d, ICommand value)
{
d.SetValue(CommandProperty, value);
}
/// <summary>
/// Handles changes to the Command property.
/// </summary>
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandBehaviorBinding binding = FetchOrCreateBinding(d);
binding.Command = (ICommand)e.NewValue;
}
#endregion
#region CommandParameter
/// <summary>
/// CommandParameter Attached Dependency Property
/// </summary>
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(CommandBehavior),
new FrameworkPropertyMetadata((object)null,
new PropertyChangedCallback(OnCommandParameterChanged)));
/// <summary>
/// Gets the CommandParameter property.
/// </summary>
public static object GetCommandParameter(DependencyObject d)
{
return (object)d.GetValue(CommandParameterProperty);
}
/// <summary>
/// Sets the CommandParameter property.
/// </summary>
public static void SetCommandParameter(DependencyObject d, object value)
{
d.SetValue(CommandParameterProperty, value);
}
/// <summary>
/// Handles changes to the CommandParameter property.
/// </summary>
private static void OnCommandParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandBehaviorBinding binding = FetchOrCreateBinding(d);
binding.CommandParameter = e.NewValue;
}
#endregion
#region Event
/// <summary>
/// Event Attached Dependency Property
/// </summary>
public static readonly DependencyProperty EventProperty =
DependencyProperty.RegisterAttached("Event", typeof(string), typeof(CommandBehavior),
new FrameworkPropertyMetadata((string)String.Empty,
new PropertyChangedCallback(OnEventChanged)));
/// <summary>
/// Gets the Event property. This dependency property
/// indicates ....
/// </summary>
public static string GetEvent(DependencyObject d)
{
return (string)d.GetValue(EventProperty);
}
/// <summary>
/// Sets the Event property. This dependency property
/// indicates ....
/// </summary>
public static void SetEvent(DependencyObject d, string value)
{
d.SetValue(EventProperty, value);
}
/// <summary>
/// Handles changes to the Event property.
/// </summary>
private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandBehaviorBinding binding = FetchOrCreateBinding(d);
//check if the Event is set. If yes we need to rebind the Command to the new event and unregister the old one
if (binding.Event != null && binding.Owner != null)
binding.Dispose();
//bind the new event to the command if newValue isn't blank (e.g. switching tabs)
if (e.NewValue.ToString() != "")
binding.BindEvent(d, e.NewValue.ToString());
}
#endregion
#region Helpers
//tries to get a CommandBehaviorBinding from the element. Creates a new instance if there is not one attached
private static CommandBehaviorBinding FetchOrCreateBinding(DependencyObject d)
{
CommandBehaviorBinding binding = CommandBehavior.GetBehavior(d);
if (binding == null)
{
binding = new CommandBehaviorBinding();
CommandBehavior.SetBehavior(d, binding);
}
return binding;
}
#endregion
}
}
CommandBehaviorBinding.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Reflection;
using System.Windows;
// Code to attach a command to any event
// From http://marlongrech.wordpress.com/2008/12/04/attachedcommandbehavior-aka-acb/
namespace Keys.Controls.Helpers.CommandHelpers
{
/// <summary>
/// Defines the command behavior binding
/// </summary>
public class CommandBehaviorBinding : IDisposable
{
#region Properties
/// <summary>
/// Get the owner of the CommandBinding ex: a Button
/// This property can only be set from the BindEvent Method
/// </summary>
public DependencyObject Owner { get; private set; }
/// <summary>
/// The command to execute when the specified event is raised
/// </summary>
public ICommand Command { get; set; }
/// <summary>
/// Gets or sets a CommandParameter
/// </summary>
public object CommandParameter { get; set; }
/// <summary>
/// The event name to hook up to
/// This property can only be set from the BindEvent Method
/// </summary>
public string EventName { get; private set; }
/// <summary>
/// The event info of the event
/// </summary>
public EventInfo Event { get; private set; }
/// <summary>
/// Gets the EventHandler for the binding with the event
/// </summary>
public Delegate EventHandler { get; private set; }
#endregion
//Creates an EventHandler on runtime and registers that handler to the Event specified
public void BindEvent(DependencyObject owner, string eventName)
{
EventName = eventName;
Owner = owner;
Event = Owner.GetType().GetEvent(EventName, BindingFlags.Public | BindingFlags.Instance);
if (Event == null)
throw new InvalidOperationException(String.Format("Could not resolve event name {0}", EventName));
//Create an event handler for the event that will call the ExecuteCommand method
EventHandler = EventHandlerGenerator.CreateDelegate(
Event.EventHandlerType, typeof(CommandBehaviorBinding).GetMethod("ExecuteCommand", BindingFlags.Public | BindingFlags.Instance), this);
//Register the handler to the Event
Event.AddEventHandler(Owner, EventHandler);
}
/// <summary>
/// Executes the command
/// </summary>
public void ExecuteCommand()
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
}
#region IDisposable Members
bool disposed = false;
/// <summary>
/// Unregisters the EventHandler from the Event
/// </summary>
public void Dispose()
{
if (!disposed)
{
Event.RemoveEventHandler(Owner, EventHandler);
disposed = true;
}
}
#endregion
}
}
EventHandlerGenerator.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;
// Code to attach a command to any event
// From http://marlongrech.wordpress.com/2008/12/04/attachedcommandbehavior-aka-acb/
namespace Keys.Controls.Helpers.CommandHelpers
{
/// <summary>
/// Generates delegates according to the specified signature on runtime
/// </summary>
public static class EventHandlerGenerator
{
/// <summary>
/// Generates a delegate with a matching signature of the supplied eventHandlerType
/// This method only supports Events that have a delegate of type void
/// </summary>
/// <param name="eventInfo">The delegate type to wrap. Note that this must always be a void delegate</param>
/// <param name="methodToInvoke">The method to invoke</param>
/// <param name="methodInvoker">The object where the method resides</param>
/// <returns>Returns a delegate with the same signature as eventHandlerType that calls the methodToInvoke inside</returns>
public static Delegate CreateDelegate(Type eventHandlerType, MethodInfo methodToInvoke, object methodInvoker)
{
//Get the eventHandlerType signature
var eventHandlerInfo = eventHandlerType.GetMethod("Invoke");
Type returnType = eventHandlerInfo.ReturnParameter.ParameterType;
if (returnType != typeof(void))
throw new ApplicationException("Delegate has a return type. This only supprts event handlers that are void");
ParameterInfo[] delegateParameters = eventHandlerInfo.GetParameters();
//Get the list of type of parameters. Please note that we do + 1 because we have to push the object where the method resides i.e methodInvoker parameter
Type[] hookupParameters = new Type[delegateParameters.Length + 1];
hookupParameters[0] = methodInvoker.GetType();
for (int i = 0; i < delegateParameters.Length; i++)
hookupParameters[i + 1] = delegateParameters[i].ParameterType;
DynamicMethod handler = new DynamicMethod("", null,
hookupParameters, typeof(EventHandlerGenerator));
ILGenerator eventIL = handler.GetILGenerator();
//load the parameters or everything will just BAM :)
LocalBuilder local = eventIL.DeclareLocal(typeof(object[]));
eventIL.Emit(OpCodes.Ldc_I4, delegateParameters.Length + 1);
eventIL.Emit(OpCodes.Newarr, typeof(object));
eventIL.Emit(OpCodes.Stloc, local);
//start from 1 because the first item is the instance. Load up all the arguments
for (int i = 1; i < delegateParameters.Length + 1; i++)
{
eventIL.Emit(OpCodes.Ldloc, local);
eventIL.Emit(OpCodes.Ldc_I4, i);
eventIL.Emit(OpCodes.Ldarg, i);
eventIL.Emit(OpCodes.Stelem_Ref);
}
eventIL.Emit(OpCodes.Ldloc, local);
//Load as first argument the instance of the object for the methodToInvoke i.e methodInvoker
eventIL.Emit(OpCodes.Ldarg_0);
//Now that we have it all set up call the actual method that we want to call for the binding
eventIL.EmitCall(OpCodes.Call, methodToInvoke, null);
eventIL.Emit(OpCodes.Pop);
eventIL.Emit(OpCodes.Ret);
//create a delegate from the dynamic method
return handler.CreateDelegate(eventHandlerType, methodInvoker);
}
}
}