6

I have registered a plugin for our quote products. The plugin worked well in our test environment. I have tested it lots of times. Then registered the plugin in the main server. However, the below scenario occurs: When I create or update the quote products first the quote product form greys out:

enter image description here

After I click on the quote form the error appears. No log files are available (as you see). I debugged the plugin, but there is no error also. After I click the OK, the error disappears and the required business takes place on the quote product (for tax field). Means that the code of plugin has no errors and does its job well. The code is as:

using System;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Xrm;
using System.Collections.Generic;
using Microsoft.Xrm.Sdk.Deployment;

public class Plugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        IPluginExecutionContext context = (IPluginExecutionContext)
        serviceProvider.GetService(typeof(IPluginExecutionContext));
        Entity entity;
        if (context.InputParameters.Contains("Target") &&
        context.InputParameters["Target"] is Entity)
        {
            entity = (Entity)context.InputParameters["Target"];
            if (entity.LogicalName != "quotedetail") { return; }
        }
        else
        {
            return;
        }
        try
        {
            IOrganizationServiceFactory serviceFactory =
                (IOrganizationServiceFactory)serviceProvider.GetService(
            typeof(IOrganizationServiceFactory));
            IOrganizationService service =
            serviceFactory.CreateOrganizationService(context.UserId);
            if (context.MessageName == "Create")
            {
                Entity QuoteProduct = (Entity)context.InputParameters["Target"];
                Guid QPID = QuoteProduct.Id;
                TaxCreator(service, QPID);
            }
            if (context.MessageName == "Update" && context.Depth < 3)
            {
                Entity QuoteProduct = (Entity)context.PostEntityImages["Target"];
                Guid QPID = QuoteProduct.Id;
                TaxUpdater(service, QPID);
            }
        }
        catch (FaultException<OrganizationServiceFault> ex)
        {
            throw new InvalidPluginExecutionException(
            "An error occurred in the plug-in.", ex);
        }
    }
    private static void TaxCreator(IOrganizationService service, Guid QPID)
    {
        using (var crm = new XrmServiceContext(service))
        {
            var QuoteProduct = crm.QuoteDetailSet.Where(c => c.QuoteDetailId == QPID).First();
            var SaleSetting = crm.new_salessettingSet.Where(c => c.statecode == 0).First();
            double TaxPercent = (Convert.ToDouble(SaleSetting.new_TaxPercent) / 100);
            if (QuoteProduct.IsPriceOverridden == false)
            {
                decimal Tax = (decimal)Convert.ToDecimal(Convert.ToDouble(QuoteProduct.BaseAmount - QuoteProduct.ManualDiscountAmount.GetValueOrDefault()) * TaxPercent);
                decimal PricePerUnit = (decimal)(QuoteProduct.PricePerUnit.GetValueOrDefault() - QuoteProduct.VolumeDiscountAmount.GetValueOrDefault());
                crm.UpdateObject(QuoteProduct);
                crm.SaveChanges();
                QuoteProduct.Attributes["tax"] = new Money(Tax);
                QuoteProduct.Attributes["new_result"] = new Money(PricePerUnit);
                crm.UpdateObject(QuoteProduct);
                crm.SaveChanges();
            }
        }
    }
    private static void TaxUpdater(IOrganizationService service, Guid QPID)
    {
        using (var crm = new XrmServiceContext(service))
        {
            var QuoteProduct = crm.QuoteDetailSet.Where(c => c.QuoteDetailId == QPID).First();
            var SaleSetting = crm.new_salessettingSet.Where(c => c.statecode == 0).First();
            double TaxPercent = (Convert.ToDouble(SaleSetting.new_TaxPercent) / 100);
            if (QuoteProduct.IsPriceOverridden == false)
            {
                decimal Tax = (decimal)Convert.ToDecimal(Convert.ToDouble(QuoteProduct.BaseAmount - QuoteProduct.ManualDiscountAmount.GetValueOrDefault()) * TaxPercent);
                decimal PricePerUnit = (decimal)(QuoteProduct.PricePerUnit.GetValueOrDefault() - QuoteProduct.VolumeDiscountAmount.GetValueOrDefault());
                crm.UpdateObject(QuoteProduct);
                crm.SaveChanges();
                QuoteProduct.Attributes["tax"] = new Money(Tax);
                QuoteProduct.Attributes["new_result"] = new Money(PricePerUnit);
                crm.UpdateObject(QuoteProduct);
                crm.SaveChanges();
            }
        }
    }
}

I also checked the event viewer on the server for the errors, and no result! I have registered my plugin on create and update of the quote product. Any help is greatly appreciated.

  • 2
    Have you enabled tracing on the server (if it's an on-premise that is) – Rickard N Mar 11 '13 at 15:59
  • have you tried to debug with VS set to pass you the first instance of CLR exceptions? To be honest, the subgrid views shouldn't be triggering plugins. – Mike_Matthews_II Mar 11 '13 at 19:02
  • What event have you registered the plugin against? – glosrob Mar 11 '13 at 19:31
  • @ glosrob: on create and update of the quote product. – Payman Biukaghazadeh Mar 12 '13 at 03:50
  • And have confirmed the plugin theory by de-registering the plugin to see that the error goes away? Although you still need to attempt to debug this, I would still wager that the plugin is not the source of the problem, since the error is showing up in the subgrid view – Mike_Matthews_II Mar 12 '13 at 14:01
  • @Mike_Matthews_II: Yes! I created the plugin from the first point and registered it again. But no changes! As I mentioned, I agree with you! The plugin throws no exception and furthermore, after the error the business completes. Would you please elaborate more the idea of subgrid? I could not understand what you say exactly. – Payman Biukaghazadeh Mar 12 '13 at 17:44
  • My assumption had been that you were receiving this error when you navigated to the Products grid from the left side navigation. If this were the case, then my other assertions would hold up. Posting possible answer in a moment. – Mike_Matthews_II Mar 12 '13 at 18:17
  • Yes Mike! It is like what you said. The scenario is as: when I save the quote product form, then it greys out and I have to wait! But waiting does not help and I could not see any error. When I click on the quote form (the subgrid), the error appears. It is greatly appreciated if you could help. – Payman Biukaghazadeh Mar 12 '13 at 18:21
  • Can you show a screenshot of the plugin registration tool. are you using the registration tool to register the plugins? – Glenn Ferrie Mar 20 '13 at 20:02
  • Right now I dont have access to the server. But I registered the plugin on the create and update of the quote product. I also registered an post image (Target) on the update. I used the plugin registration tool. – Payman Biukaghazadeh Mar 21 '13 at 12:45

2 Answers2

2
    public void Execute(IServiceProvider serviceProvider)
    {
        IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
        Entity entity;
        if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
        {
            entity = (Entity)context.InputParameters["Target"];
            if (entity.LogicalName != "quotedetail") { return; }
        }
        else
        {
            return;
        }

        try
        {
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
            QuoteDetail QuoteProduct = ((Entity)context.InputParameters["Target"]).ToEntity<QuoteDetail>();
            if (context.MessageName == "Create" || context.MessageName == "Update")
                // && context.Depth < 3) //try to avoid depth unless you have to have it
            {
                TaxSetter(service, QuoteProduct);
            }
        }
        catch (FaultException<OrganizationServiceFault> ex)
        {
            throw new InvalidPluginExecutionException( "An error occurred in the plug-in.", ex);
        }
    }

    private static void TaxSetter(IOrganizationService service, QuoteDetail product)
    {
        using (var crm = new TrainingContext(service))
        {
            var QuoteProduct = product.ToEntity<QuoteDetail>();
            if (QuoteProduct.IsPriceOverridden == false)
            {
                double TaxPercent = Convert.ToDouble(50 / 100);
                decimal Tax = (decimal)Convert.ToDecimal(Convert.ToDouble(QuoteProduct.BaseAmount - QuoteProduct.ManualDiscountAmount.GetValueOrDefault()) * TaxPercent);
                decimal PricePerUnit = (decimal)(QuoteProduct.PricePerUnit.GetValueOrDefault() - QuoteProduct.VolumeDiscountAmount.GetValueOrDefault());

                QuoteProduct.Tax = Tax; //depending on how you the parameters passed into CrmSvcUtil
                QuoteProduct.Attributes["new_result"] = new Money(PricePerUnit); //same deal here

                //crm.UpdateObject(QuoteProduct);
                //crm.SaveChanges();
                //code not needed, the Plugin context will take care of this for you
            }
        }
    }

The idea is, you want your plugin context to work for you; and you want to overtly do as little as possible. I changed the tax calculation because I don't have your attributes; you'll want to change them back. If I've still misread your problem, I'd be happy to delete my post (or try).

Mike_Matthews_II
  • 722
  • 6
  • 15
  • Thanks a lot Mike. I will test this and come back to let you know about the result. Before the test I have a question. I did not encounter this error in my test environment (similar to the actual environment). What do you think about this? – Payman Biukaghazadeh Mar 12 '13 at 18:26
  • I can offer the obvious suggestion: there's a difference between the two environments. This difference could be things such as workflows and plugins registered and enabled in Prod that do not exist in dev/test; it could be things such a difference in the way CrmSvcUtil was compiled in your Dev/Test environment. It could be a web.config setting...had a really nasty one of those crop up once where a set of code worked great in Dev because we were missing some config settings that were throwing exceptions in Test (and would have in Prod). Ultimately, I'm not sure =( – Mike_Matthews_II Mar 12 '13 at 18:33
  • Again thanks Mike. Tomorrow I will test it at work and let you know. – Payman Biukaghazadeh Mar 12 '13 at 18:53
  • Hi again Mike. I've checked your suggested code. It did not calculate tax and price per unit. On create and update (both) it has a null value. – Payman Biukaghazadeh Mar 14 '13 at 04:51
  • I do see some unnecessary code in the answer, but those bits should not be interfering with the results (things like casting QuoteDetail to QuoteDetail). If the code is not causing an update, you really do need to debug these and watch to see if the code is running and if the expected code paths are being hit. If they are being hit, then you may want to uncomment the crm.Save and crm.Update lines. – Mike_Matthews_II Mar 14 '13 at 14:05
  • Real thanks for your good support and help Mike. The plugin is running on the save and update of the quote product. But the base amount or manual discount amount, has the zero value. To overcome this issue, I have placed an update and save before the tax calculation, and then update and save to save the result. But in your suggested code, I could not use save changes and it throws exception. Since I am newcomer in these fields, I really do not know what to do! – Payman Biukaghazadeh Mar 14 '13 at 17:58
  • I would encourage you to only save once per entity record (and not per change); also, can you share the exception? It could be something like a context tracking issue, which get easier to solve the more times you trip them =) – Mike_Matthews_II Mar 14 '13 at 18:03
  • On the create event the error with updateobject line is: The context is not currently tracking the 'quotedetail' entity. On the update event the price overriden field is empty, and because of that it did not enter it to calculate the tax (and give the mentioned exception). – Payman Biukaghazadeh Mar 14 '13 at 18:23
  • Excellent. That is a great error. If you created your context correctly, you will have a .SaveChanges, .Update and .UpdateObject methods. Some combination of these will get you past that error. I think I most often use .UpdateObject followed by .Update (I see no instances of .SaveChanges in my code at the moment). – Mike_Matthews_II Mar 14 '13 at 18:30
  • You mean I have to use: crm.update(); and crm.updateobject(QuoteProduct); ? Without SaveChanges(); ? – Payman Biukaghazadeh Mar 14 '13 at 19:09
  • yes, only do it in this order: .UpdateObject(QuoteProduct) and then .Update(QuoteProduct) – Mike_Matthews_II Mar 14 '13 at 20:33
  • Again the error is: The context is not currently tracking the 'quotedetail' entity – Payman Biukaghazadeh Mar 14 '13 at 20:43
  • what if you try .Attach(QuoteProduct) first? it may or may not need the UpdateObject call. This is the part I always forget and spend minutes re-tooling until it works =/ – Mike_Matthews_II Mar 14 '13 at 20:48
  • Both (with updateobject and without it) throws exception: "An error occurred in the plug-in". The inner exception is: "EntityState must be set to null, Created (for Create message) or Changed (for Update message)" – Payman Biukaghazadeh Mar 14 '13 at 21:08
  • this [article](http://stackoverflow.com/questions/6187978/entitystate-must-be-set-to-null-created-for-create-message-or-changed-for-u) suggests the usage of UpdateObject and SaveChanges. The deal is, the context will not save anything it isn't tracking; this can sometimes be fixed with the Attach call. An Update is only valid if the EntityState is set to updated, which can necessitate the UpdateObject call. As for the method of saving, I think it depends on the way the context was generated...I'm pretty sure some combination of these will work. If not, we can brute force it into the contxt – Mike_Matthews_II Mar 14 '13 at 21:14
2

I'd go shotgun debug on it. Implement the following tracing. Usually, I place the declaration of the tracer as a class member (so it's visible to all my help methods within the class) and write to it (when desperate and frustrated) after every operation (more or less). Then, when the program crashes by itself (or when I intentionally crash it to see the trace log), I can usually pin-point the problem area.

public class MyPlugin _ IPlugin
{
  private ITracingService _trace;

  public void Execute(IServiceProvider service)
  {
    _trace = (ITracingService)service.GetService(typeof(ITracingService));
    _trace.Trace("Commencing.");
    ...
    _trace.Trace("Right before an operation. " + someValue);
    PerformAnOperation();
    _trace.Trace("Right before an other operation. " + someOtherValue);
    PerformAnOtherOperation();
    ...
    throw new Exception("Intentional!");
  }
}

The, you can follow through to see exactly where the problem originates. In my experience, once one knows where it aches, one can easily suggest how to remedy the issue.

EDIT:

Since the OP requested more details, I'm taking his code and put in the tracing algorithm into it for him. A bit redundant but apparently, CRM can be very confusing. Been there myself.

public class Plugin : IPlugin
{
  // Here we declare our tracer.
  private ITracingService _trace;

  public void Execute(IServiceProvider serviceProvider)
  {
    // Here we create it.
    _trace = (ITracingService)serviceProvider
      .GetService(typeof(ITracingService));
    _trace.Trace("Commencing.");

    // Here we crash our plugin just to make sure that we can see the logs 
    // with trace information. This statement should be gradually moved down
    // the code to discover where exactly the plugin brakes.
    throw new Exception("Intentional!");

    IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider
      .GetService(typeof(IPluginExecutionContext));
    Entity entity;
    ...

    try { ... }
    catch (FaultException<OrganizationServiceFault> ex)
    {
      throw new InvalidPluginExecutionException(
        "An error occurred in the plug-in.", ex);
    }

    // Here we catch all other kinds of bad things that can happen.
    catch(Exception exception) { throw exception; }
  }

  private static void TaxCreator(IOrganizationService service, Guid QPID)
  {
    // Here we add a line to the trace hoping that the execution gets here.
    _trace.Trace("Entered TaxCreator.");

    using (var crm = new XrmServiceContext(service))
    {
      var QuoteProduct = crm.QuoteDetailSet.Where(...).First();
      var SaleSetting = crm.new_salessettingSet.Where(...).First();
      double TaxPercent = (Convert.ToDouble(...) / 100);

      // Here we add a line to the trace to see if we get past this point.
      _trace.Trace("Approaching if statement.");

      if (QuoteProduct.IsPriceOverridden == false) { ... }
    }
  }

  private static void TaxUpdater(IOrganizationService service, Guid QPID)
  {
    // Here we add a line to the trace hoping that the execution gets here.
    _trace.Trace("Entered TaxUpdater.");

    ...
  }
}
Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438
  • you are an interesting man; and this is an interesting approach. – Mike_Matthews_II Mar 12 '13 at 19:25
  • @Mike_Matthews_II Not sure what to say here. Thank you? :D – Konrad Viltersten Mar 12 '13 at 19:27
  • @KonradViltersten: Since Im not so familiar with tracing, would you please guide me where exactly put the code you have provided me? – Payman Biukaghazadeh Mar 12 '13 at 20:42
  • @PaymanBiukaghazadeh See my edit. Let me know if it helps. Also, please note that I'm not in front of a computer so there might be a typo or two. But you've got intellisense so you'll be fine. – Konrad Viltersten Mar 12 '13 at 23:52
  • @PaymanBiukaghazadeh Check the addition I just made and let me know if it's enough information. I'm shamelessly aiming at that bounty, haha. – Konrad Viltersten Mar 14 '13 at 22:07
  • Thanks Konrad. Now, I am going to test it. Hope gives me good results. :) – Payman Biukaghazadeh Mar 15 '13 at 05:43
  • Hi again! I have tested your suggested code. It gives me error at intentional state. Because unreachable code detected at IPluginExecutionContext. What is this error? I did not encounter this error before! – Payman Biukaghazadeh Mar 15 '13 at 06:17
  • @PaymanBiukaghazadeh The intentional error mean that CRM didn't crash. It's **our code** that on purpose crashes it. Remember, we crash the execution by throwing an exception. That's the way we can see the log (crazy, right?) and see how far in the execution we've got. Just remember to move the *throw new Exception("Konrad")* down a couple of lines, recompile and execute again. Sooner or later you should see where exactly the CRM crashes (by itself, without you causing it intentionally). Then we'll know what to do. Hopefully. – Konrad Viltersten Mar 15 '13 at 07:33
  • 1
    @PaymanBiukaghazadeh The unreachable code is not an error. It's a warning that you may ignore. It occurs in C# when you have a *return*, *throw* or *break* statement that's executed unconditionally, being followed by some other code. Compiler will then hint you about that code never going to be executed. No worries there. – Konrad Viltersten Mar 15 '13 at 07:35
  • So, what I have to do now? – Payman Biukaghazadeh Mar 15 '13 at 07:37
  • Ok! Im going through it! Hope to find it! – Payman Biukaghazadeh Mar 15 '13 at 07:45
  • @PaymanBiukaghazadeh Do tell how it went. I want that rep-gain, haha. :) – Konrad Viltersten Mar 15 '13 at 07:51
  • Sure! Since Im amateur in the field of development, so I have to call help again! :) – Payman Biukaghazadeh Mar 15 '13 at 07:54
  • @PaymanBiukaghazadeh Sure thing. If it's a new question (even if it's a follo-up), you can post it as a new one. If it's clarification in this one, use the comments. ANd if you're happy with the reply (as in - you can't post any more comments because you're all-out), check one of the replies as an answer. – Konrad Viltersten Mar 15 '13 at 11:50
  • @PaymanBiukaghazadeh You'll have to assign the bounty to somebody soon. Otherwise you're losing the reputation to nobody's gain. Is there anything else missing in the existing answers? – Konrad Viltersten Mar 20 '13 at 17:13
  • Sorry Konrad. After I marked it as answer, the system did not allow me to give the bounty (7 hours of delay). After that, I did not come to give it. Thanks a lot for you responses. Although the tracing is used, but I could not find the origin of the error. – Payman Biukaghazadeh Mar 21 '13 at 12:43