Hello,
I´ve been trying to create a plugin that triggers an event on add products on opportunity subgrid that change the discount and final price.
I tried with the "Create" opportunityproduct message, but when i add any product, error message is displayed "ISV code aborthe the operation". If i change the plugin message by "CalculatePrice" the plugin works fine but only when i change quantity or sales price in the subgrid, but a disadvantge of this is that if user only wants one piece of a product the event of calculate price never throws.
Could you told me what i am doing wrong, please?
I've been using the Visual Studio developer toolkit and the two C# classes are:
CLASS PLUGIN
namespace PluginOpportunityProduct { using System; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.ServiceModel; using Microsoft.Xrm.Sdk; /// <summary> /// Base class for all Plugins. /// </summary> public class Plugin : IPlugin { protected class LocalPluginContext { internal IServiceProvider ServiceProvider { get; private set; } internal IOrganizationService OrganizationService { get; private set; } internal IPluginExecutionContext PluginExecutionContext { get; private set; } internal ITracingService TracingService { get; private set; } private LocalPluginContext() { } internal LocalPluginContext(IServiceProvider serviceProvider) { if (serviceProvider == null) { throw new ArgumentNullException("serviceProvider"); } // Obtain the execution context service from the service provider. this.PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); // Obtain the tracing service from the service provider. this.TracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); // Obtain the Organization Service factory service from the service provider IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); // Use the factory to generate the Organization Service. this.OrganizationService = factory.CreateOrganizationService(this.PluginExecutionContext.UserId); } internal void Trace(string message) { if (string.IsNullOrWhiteSpace(message) || this.TracingService == null) { return; } if (this.PluginExecutionContext == null) { this.TracingService.Trace(message); } else { this.TracingService.Trace( "{0}, Correlation Id: {1}, Initiating User: {2}", message, this.PluginExecutionContext.CorrelationId, this.PluginExecutionContext.InitiatingUserId); } } } private Collection<Tuple<int, string, string, Action<LocalPluginContext>>> registeredEvents; /// <summary> /// Gets the List of events that the plug-in should fire for. Each List /// Item is a <see cref="System.Tuple"/> containing the Pipeline Stage, Message and (optionally) the Primary Entity. /// In addition, the fourth parameter provide the delegate to invoke on a matching registration. /// </summary> protected Collection<Tuple<int, string, string, Action<LocalPluginContext>>> RegisteredEvents { get { if (this.registeredEvents == null) { this.registeredEvents = new Collection<Tuple<int, string, string, Action<LocalPluginContext>>>(); } return this.registeredEvents; } } /// <summary> /// Gets or sets the name of the child class. /// </summary> /// <value>The name of the child class.</value> protected string ChildClassName { get; private set; } /// <summary> /// Initializes a new instance of the <see cref="Plugin"/> class. /// </summary> /// <param name="childClassName">The <see cref=" cred="Type"/> of the derived class.</param> internal Plugin(Type childClassName) { this.ChildClassName = childClassName.ToString(); } /// <summary> /// Executes the plug-in. /// </summary> /// <param name="serviceProvider">The service provider.</param> /// <remarks> /// For improved performance, Microsoft Dynamics CRM caches plug-in instances. /// The plug-in's Execute method should be written to be stateless as the constructor /// is not called for every invocation of the plug-in. Also, multiple system threads /// could execute the plug-in at the same time. All per invocation state information /// is stored in the context. This means that you should not use global variables in plug-ins. /// </remarks> public void Execute(IServiceProvider serviceProvider) { if (serviceProvider == null) { throw new ArgumentNullException("serviceProvider"); } // Construct the Local plug-in context. LocalPluginContext localcontext = new LocalPluginContext(serviceProvider); localcontext.Trace(string.Format(CultureInfo.InvariantCulture, "Entered {0}.Execute()", this.ChildClassName)); try { // Iterate over all of the expected registered events to ensure that the plugin // has been invoked by an expected event // For any given plug-in event at an instance in time, we would expect at most 1 result to match. Action<LocalPluginContext> entityAction = (from a in this.RegisteredEvents where ( a.Item1 == localcontext.PluginExecutionContext.Stage && a.Item2 == localcontext.PluginExecutionContext.MessageName && (string.IsNullOrWhiteSpace(a.Item3) ? true : a.Item3 == localcontext.PluginExecutionContext.PrimaryEntityName) ) select a.Item4).FirstOrDefault(); if (entityAction != null) { localcontext.Trace(string.Format( CultureInfo.InvariantCulture, "{0} is firing for Entity: {1}, Message: {2}", this.ChildClassName, localcontext.PluginExecutionContext.PrimaryEntityName, localcontext.PluginExecutionContext.MessageName)); entityAction.Invoke(localcontext); // now exit - if the derived plug-in has incorrectly registered overlapping event registrations, // guard against multiple executions. return; } } catch (FaultException<OrganizationServiceFault> e) { localcontext.Trace(string.Format(CultureInfo.InvariantCulture, "Exception: {0}", e.ToString())); // Handle the exception. throw; } finally { localcontext.Trace(string.Format(CultureInfo.InvariantCulture, "Exiting {0}.Execute()", this.ChildClassName)); } } } }
Class PostCreateOpportunityProduct
namespace Plugins2 { using System; using System.ServiceModel; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; /// <summary> /// PostProductodeoportunidadCreate Plugin. /// </summary> public class PostProductodeoportunidadCreate: Plugin { /// <summary> /// Initializes a new instance of the <see cref="PostProductodeoportunidadCreate"/> class. /// </summary> public PostProductodeoportunidadCreate() : base(typeof(PostProductodeoportunidadCreate)) { base.RegisteredEvents.Add(new Tuple<int, string, string, Action<LocalPluginContext>>(40, "Create", "opportunityproduct", new Action<LocalPluginContext>(ExecutePostProductodeoportunidadCreate))); // Note : you can register for more events here if this plugin is not specific to an individual entity and message combination. // You may also need to update your RegisterFile.crmregister plug-in registration file to reflect any change. } /// <summary> /// Executes the plug-in. /// </summary> /// <param name="localContext">The <see cref="LocalPluginContext"/> which contains the /// <see cref="IPluginExecutionContext"/>, /// <see cref="IOrganizationService"/> /// and <see cref="ITracingService"/> /// </param> /// <remarks> /// For improved performance, Microsoft Dynamics CRM caches plug-in instances. /// The plug-in's Execute method should be written to be stateless as the constructor /// is not called for every invocation of the plug-in. Also, multiple system threads /// could execute the plug-in at the same time. All per invocation state information /// is stored in the context. This means that you should not use global variables in plug-ins. /// </remarks> protected void ExecutePostProductodeoportunidadCreate(LocalPluginContext localContext) { if (localContext == null) { throw new ArgumentNullException("localContext"); } // TODO: Implement your custom Plug-in business logic. IOrganizationService service = localContext.OrganizationService; // Obtain the target entity from the input parmameters. EntityReference entity = (EntityReference)localContext.PluginExecutionContext.InputParameters["Target"]; // Verify that the target entity represents an appropriate entity. if (CheckIfNotValidEntity(entity)) return; // Calculate pricing depending on the target entity switch (entity.LogicalName) { case "opportunityproduct": CalculateOpportunityProduct(entity, service); return; default: localContext.Trace(entity.LogicalName.ToString()); return; } } private static void CalculateOpportunityProduct(EntityReference entity, IOrganizationService service) { try { ColumnSet columns = new ColumnSet(); Entity e = service.Retrieve(entity.LogicalName, entity.Id, new ColumnSet("quantity", "priceperunit")); decimal total = 0; //total = total + ((decimal)e["quantity"] * ((Money)e["priceperunit"]).Value); total = 33333; e["extendedamount"] = new Money(total); service.Update(e); } catch (FaultException<OrganizationServiceFault> ex) { System.Diagnostics.Debug.Write(ex.Message); } } private static bool CheckIfNotValidEntity(EntityReference entity) { switch (entity.LogicalName) { case "opportunity": case "quote": case "salesorder": case "invoice": case "opportunityproduct": case "invoicedetail": case "quotedetail": case "salesorderdetail": return false; default: return true; } } } }
Thanks for your attention