Yes, there are a few ways you can attempt to prevent the CalculatePrice
message from firing in Plugin B after you've updated the quote totals. However, it's important to understand the limitations and potential side effects of each approach.
Here are the common strategies:
1. Using SharedVariables
to Control Execution Flow (Recommended):
This is generally the cleanest and most controlled approach. You can set a flag in SharedVariables
within Plugin B to indicate that the price calculation has already been handled or should be skipped. Plugin A can then check this flag before executing its price calculation logic.
Plugin B (Updating Totals):
public class UpdateQuoteTotals : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
if (context.PrimaryEntityName == "quote" && context.MessageName == "Update" && context.Stage == 40) // Post-Operation Update
{
try
{
// Your logic to update quote totals
// Set a shared variable to indicate that price calculation is handled
context.SharedVariables["SuppressCalculatePrice"] = true;
tracingService.Trace("Plugin B: Suppressed CalculatePrice via SharedVariable.");
}
catch (Exception ex)
{
tracingService.Trace($"Plugin B Error: {ex.ToString()}");
throw;
}
}
}
}
Plugin A (Calculating Price):
public class CalculateQuotePrice : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
if (context.PrimaryEntityName == "quotedetail" || context.PrimaryEntityName == "quote")
{
if (context.MessageName == "CalculatePrice" && context.Stage == 40) // Post-Operation CalculatePrice
{
if (context.SharedVariables.ContainsKey("SuppressCalculatePrice") && (bool)context.SharedVariables["SuppressCalculatePrice"])
{
tracingService.Trace("Plugin A: CalculatePrice suppressed by Plugin B.");
return; // Exit the plugin execution
}
try
{
// Your logic to calculate and update quote detail and quote prices
tracingService.Trace("Plugin A: CalculatePrice executed.");
}
catch (Exception ex)
{
tracingService.Trace($"Plugin A Error: {ex.ToString()}");
throw;
}
}
}
}
}
Explanation:
- Plugin B, after updating the quote totals, sets a boolean value to
true
in the context.SharedVariables
dictionary with the key SuppressCalculatePrice
.
- Plugin A, at the beginning of its
CalculatePrice
execution, checks if this key exists in SharedVariables
and if its value is true
.
- If the flag is set, Plugin A simply returns, effectively preventing its price calculation logic from running.
Important Considerations for SharedVariables
:
SharedVariables
are only available within the same transaction pipeline. If Plugin B and the subsequent CalculatePrice
execution happen in separate transactions (which is less likely for immediate post-operation events but possible in asynchronous scenarios), this method won't work.
- Ensure the execution order of your plugins is correctly set. Plugin B (updating totals) should execute before the instance of Plugin A that you want to suppress.
2. Using a Custom Flag Field on the Quote Entity (Less Ideal for System Messages):
You could add a custom boolean field (e.g., new_suppresspricecalculation
) to the Quote entity. Plugin B would set this field to true
after updating the totals. Plugin A would then check the value of this field before proceeding with the price calculation.
Plugin B:
// ... (rest of Plugin B code)
service.Update(new Entity("quote", context.PrimaryEntityId) { ["new_suppresspricecalculation"] = true });
Plugin A:
// ... (rest of Plugin A code)
Entity quote = service.Retrieve("quote", context.PrimaryEntityId, new ColumnSet("new_suppresspricecalculation"));
if (quote.Contains("new_suppresspricecalculation") && (bool)quote["new_suppresspricecalculation"])
{
tracingService.Trace("Plugin A: CalculatePrice suppressed by custom flag.");
return;
}
// ... (rest of Plugin A's price calculation logic)
Important Considerations for Custom Flag Field:
- This approach involves an extra database update in Plugin B.
- You need to ensure Plugin A resets this flag if the price calculation should run again later (e.g., on other quote updates).
- It tightly couples the logic of the two plugins to a specific field on the entity.
3. Modifying the Execution Context (Less Recommended and Potentially Unreliable):
While technically possible to manipulate the execution context (e.g., by setting the OperationCanceled
property), this is generally not recommended for suppressing system messages like CalculatePrice
. It can lead to unexpected behavior and might not be reliably supported.
Why Modifying Execution Context is Discouraged:
- It can interfere with the platform's internal processes and potentially cause instability.
- The behavior might change with future updates.
- It makes your code harder to understand and maintain.
Which Method to Choose:
The SharedVariables
approach (Method 1) is generally the preferred and most robust way to control the execution flow between plugins within the same transaction. It avoids extra database updates and keeps the suppression logic within the plugin execution pipeline.
The custom flag field approach (Method 2) can work but introduces coupling and requires careful management of the flag's value.
Modifying the execution context (Method 3) should be avoided for suppressing standard system messages.
To implement the SharedVariables
approach:
- Ensure your Plugin B (updating totals) executes before the instance of Plugin A that you want to suppress for that specific quote update. You can control this through the plugin registration tool by setting the execution order.
- Implement the code snippets provided above in your respective plugins.
- Thoroughly test the scenario to ensure that the
CalculatePrice
message is suppressed as expected after Plugin B updates the totals.
By using SharedVariables
, you can achieve your goal of preventing the CalculatePrice
message from firing after your custom logic in Plugin B has updated the quote totals, leading to a more controlled and efficient process. Remember to prioritize the execution order of your plugins.