Several of my tools generate Microsoft Excel and Word documents as part of their output and for the longest time, I have used various open-source libraries to provide me with OpenDocumentXML functionality to generate real worksheets and documents, instead of just CSV and plain text files.

The only problem, especially with my SnapShot! documentation utility, was the amount of time it was taking to generate documents. SnapShot! generates a ton of worksheets and documents and I was growing increasing concerned with the generation time.

So I started looking around for alternatives and I kept coming back to a company celled Aspose.  I have been looking at their components for years and they have two products that I needed:

Aspose.Cells, for generating Microsoft Excel files and Aspose.Words for generating Microsoft Word documents.

Full disclosure: They offer a free copy of their component libraries to Microsoft MVPs so I took them up on their offer.

Let me just say that I am extremely impressed with both Cells and Words.  Extremely impressed.

Not only are their components easy to use, but they are blazingly fast.

Working on SnapShot!, I took me about about 1.5 hours to move both my worksheet generation and document generation from the OpenDocumentXML libraries to Cells and Words. And that is including reading their documentation to figure out how to do things like:

  • Automatically generate tables of content
  • Insert figures into a Word document
  • Word headers and footers
  • Turning on filtering on an Excel worksheet
  • Creating a workbook containing multiple worksheets
  • etc.

And did I mention fast?  Oh My Gosh! Fast.

Cells generates Excel worksheets like it is being paid by the row and it’s almost 5:00pm on Friday night.

The combination of Cells and Words sped up the document generation process between 40% and 50%, depending on the data being extracted from Dynamics CRM.

Again, I am very impressed with the quality and speed of this product.  I don’t give out recommendations often, but this is surely one of them.

And don’t forget that Cells and Words are just two components in their library. I can’t wait to get to use some of the others, like Aspose.Diagram, which generates Microsoft Visio diagrams. Think about that as you are creating your own documentation…

Anyway, that’s my recommendation for today.  Take a look if you have time and interest.

As most of you know, I have a several add-on products for Dynamics CRM developers, administrators, and consultants.

They all share common user interface and application frameworks that I developed several years ago.

When I first started writing these tools all I had to worry about was Dynamics CRM 4.0, which was pretty easy. Then Dynamics CRM 2011 shipped, which made things a bit more difficult, but not impossible.

I just had to separate my internal components so that one section worked with CRM 4.0, and the other with CRM 2011.  Once I got the unified connection user interface component working correctly, everything else just fell into place and things were all warm and cozy.

CRM 4.0 and CRM 2011 communicate over very different means so even though you ask for a single set of information from the user, you must appropriately handle the connection specific to the platform on the back-end.  That took a little bit of work to make happen flawlessly every time.

Then Microsoft shipped Dynamics 2013.  Still not a big deal as I could use the CRM 2011 SDK assemblies to communicate without any issue.

Then Microsoft shipped Dynamics 2015 and I started thinking that maybe I need to re-examine my methodology.  And I am quite happy with my solution.

 

Requirements

I need to communicate with Dynamics CRM 2011, 2013, and 2015 while changing the smallest amount of code possible.

I also had to stay with .NET 4.0, since my user interface framework does not support .NET 4.5 and I was not in a position to write the entire UI for a handful of products.

 

Solution

The solution was to upgrade my SDK .NET assemblies to the Dynamics CRM 2013 level. 

That gave me access to all three platforms as long as I kept in mind that I could never ask for something that did not exist on a specific platform. 

Like SLAs or Entitlements from CRM 2011. Since those do not exist until 2013, it would throw an error.

 

Further Thoughts

For some reason, I had never tried connecting to a lower-version environment using the newest SDK assemblies.  Turns out that works just fine. (silly me for never asking the question till a month ago).

If my UI framework was .NET 4.5 compatible, I would have jumped up to the CRM 2015 assemblies because that is what they are written with.

The only side-effect to this is that I am stuck using Visual Studio 2010 because that same UI framework doesn’t work with Visual Studio 2013, my normal environment.

 

Conclusion

This process was quite eye-opening for me and just about as painless as it could be.  The Microsoft SDK team has done a huge amount of really great work that makes our jobs as developers, much less work than it could be, or even once was.

Let me know if you have had similar or even different experiences.

So one of my customers ran into this issue while opening an Opportunity.  As you can see, a custom plugin was throwing an exception because it could not find a Account with a specified ID.

SNAGHTML5c035ae

That part is 100% correct.  The Account with that ID indeed does not exist in the database.

The problem is, this plugin should never have been fired. As I mentioned, this was happening on a Retrieve operation and there are no plugins registered against the Retrieve Message.

So I waited until tonight so I could turn on Tracing (on-premise, of course), and this is what I found:

[2014-10-16 20:12:57.600] Process: w3wp |Organization:d98bbf24-8eed-4cb4-a379-6aa848499cb0 |
    Thread:   25 |Category: Exception |User: fb9a439c-b578-4a4b-83e0-39ea0059ad2a |
    Level: Error |ReqId: eb0bc9e4-e6e6-4552-b322-1c2fa0bdbc10 | 
    CrmException..ctor  ilOffset = 0x7

    at CrmException..ctor(String message, Exception innerException, Int32 errorCode,
       Boolean isFlowControlException)  ilOffset = 0x7

    at CrmException..ctor(String message, Exception innerException, Int32 errorCode)  ilOffset = 0x5
    at RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig,
       Boolean constructor)  ilOffset = 0xFFFFFFFF

    at RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder,
       Object[] parameters, CultureInfo culture)  ilOffset = 0xF7

    at RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args,
       CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark)  ilOffset = 0x1E8

    at Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args,
       CultureInfo culture, Object[] activationAttributes)  ilOffset = 0xBB

    at Activator.CreateInstance(Type type, Object[] args)  ilOffset = 0xA
    at VersionedPluginProxyStepBase.WrapExceptionToThrow(CrmException exception)  ilOffset = 0xD3
    at VersionedPluginProxyStepBase.Execute(PipelineExecutionContext context)  ilOffset = 0x65
    at Pipeline.Execute(PipelineExecutionContext context)  ilOffset = 0x65
    at MessageProcessor.Execute(PipelineExecutionContext context)  ilOffset = 0x1C5
    at InternalMessageDispatcher.Execute(PipelineExecutionContext context)  ilOffset = 0xE4
    at ExternalMessageDispatcher.ExecuteInternal(IInProcessOrganizationServiceFactory serviceFactory,
       IPlatformMessageDispatcherFactory dispatcherFactory, String messageName, String requestName, 
       Int32 primaryObjectTypeCode, Int32 secondaryObjectTypeCode, ParameterCollection fields, 
       CorrelationToken correlationToken, CallerOriginToken originToken, UserAuth userAuth, Guid callerId,
       Guid transactionContextId, Int32 invocationSource, Nullable`1 requestId, Version endpointVersion)  ilOffset = 0x156

    at OrganizationSdkServiceInternal.ExecuteRequest(OrganizationRequest request, CorrelationToken correlationToken,
       CallerOriginToken callerOriginToken, WebServiceType serviceType, UserAuth userAuth, Guid targetUserId, 
       Boolean traceRequest, OrganizationContext context, Boolean returnResponse)  ilOffset = 0x145

    at OrganizationSdkServiceInternal.ExecuteRequest(OrganizationRequest request, CorrelationToken correlationToken,
       CallerOriginToken callerOriginToken, WebServiceType serviceType)  ilOffset = 0x3D

    at OrganizationSdkServiceInternal.Retrieve(String entityName, Guid id, ColumnSet columnSet,
       CorrelationToken correlationToken, CallerOriginToken callerOriginToken, WebServiceType serviceType)  ilOffset = 0x66

    at InprocessServiceProxy.RetrieveCore(String entityName, Guid id, ColumnSet columnSet)  ilOffset = 0x28
    at OrganizationServiceProxy.Retrieve(String entityName, Guid id, ColumnSet columnSet)  ilOffset = 0x4
    at Utility.UpdateAccountKeyword(IOrganizationService service, Guid accountId)  ilOffset = 0xE8 
        C:\Plugins\SupportingClasses\Utility.cs(205)

    at InvoiceCancelOpportunityCloseKeywords.Execute(IServiceProvider serviceProvider)  ilOffset = 0x164 
        C:\Plugins\InvoiceCancelOpportunityCloseKeywords.cs(70)

    at V5PluginProxyStep.ExecuteInternal(PipelineExecutionContext context)  ilOffset = 0xD0
    at VersionedPluginProxyStepBase.Execute(PipelineExecutionContext context)  ilOffset = 0x65
    at Pipeline.Execute(PipelineExecutionContext context)  ilOffset = 0x65
    at MessageProcessor.Execute(PipelineExecutionContext context)  ilOffset = 0x1FB
    at InternalMessageDispatcher.Execute(PipelineExecutionContext context)  ilOffset = 0xE4
    at ExtensiblePlatformMessageDispatcher.Execute(PipelineExecutionContext pluginContext)  ilOffset = 0x0
    at ExtensiblePlatformMessageDispatcher.UpdateWithInvocationSource(BusinessEntity entity, FilterExpression filter,
       Int32 invocationSource, ExecutionContext context)  ilOffset = 0xCE

    at ExtensiblePlatformMessageDispatcher.Update(BusinessEntity entity, FilterExpression filter, ExecutionContext context) 
       ilOffset = 0x5

    at BusinessProcessObject.UpdateWithPipelineAndExtensions(IBusinessEntity entity, ExecutionContext context)  ilOffset = 0x78
    at QOIPriceService.UpdateEntity(BusinessEntity newQoi, BusinessEntity oldQoi, ExecutionContext context)  ilOffset = 0x87
    at PriceService.CalculatePrice(BusinessEntity entity, Guid lineItemId, Boolean skipQOIDetailPricing,
       Boolean overridePricePerUnitLock, Boolean overrideDiscountLock, ExecutionContext context)  ilOffset = 0x6E1

    at OpportunityPriceService.CalculatePrice(BusinessEntity entity, Guid lineItemId, Boolean skipQOIDetailPricing,
       Boolean overridePricePerUnitLock, Boolean overrideDiscountLock, ExecutionContext context)  ilOffset = 0x70

    at QOIPriceService.CalculatePrice(Guid qoiId, Guid lineItemId, Boolean skipLineItemPricing, Boolean overridePricePerUnitLock,
       Boolean overrideDiscountLock, Boolean isModifiedBySystem, ExecutionContext context)  ilOffset = 0x2E

    at OpportunityService.CalculatePrice(Guid opportunityId, Guid opportunityProductId, Boolean skipOpportunityProductPricing,
       Boolean overridePricePerUnitLock, Boolean overrideDiscountLock, Boolean isModifiedBySystem, ExecutionContext context) 
       ilOffset = 0x12

    at QOIService.Retrieve(BusinessEntityMoniker moniker, EntityExpression entityExpression, ExecutionContext context,
       Boolean isModifiedBySystem, Boolean calculatePrice, Int32 state)  ilOffset = 0x2D

    at OpportunityService.Retrieve(BusinessEntityMoniker moniker, EntityExpression entityExpression,
       ExecutionContext context)  ilOffset = 0x14

    at RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig,
       Boolean constructor)  ilOffset = 0xFFFFFFFF

    at RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments) 
       ilOffset = 0x25

    at RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder,
       Object[] parameters, CultureInfo culture)  ilOffset = 0x89

    at LogicalMethodInfo.Invoke(Object target, Object[] values)  ilOffset = 0x4F
    at InternalOperationPlugin.Execute(IServiceProvider serviceProvider)  ilOffset = 0x57
    at V5PluginProxyStep.ExecuteInternal(PipelineExecutionContext context)  ilOffset = 0x58
    at VersionedPluginProxyStepBase.Execute(PipelineExecutionContext context)  ilOffset = 0x65
    at Pipeline.Execute(PipelineExecutionContext context)  ilOffset = 0x65
    at MessageProcessor.Execute(PipelineExecutionContext context)  ilOffset = 0x1C5
    at InternalMessageDispatcher.Execute(PipelineExecutionContext context)  ilOffset = 0xE4
    at ExternalMessageDispatcher.ExecuteInternal(IInProcessOrganizationServiceFactory serviceFactory,
        IPlatformMessageDispatcherFactory dispatcherFactory, String messageName, String requestName, 
        Int32 primaryObjectTypeCode, Int32 secondaryObjectTypeCode, ParameterCollection fields, 
        CorrelationToken correlationToken, CallerOriginToken originToken, UserAuth userAuth, Guid callerId, 
        Guid transactionContextId, Int32 invocationSource, Nullable`1 requestId, Version endpointVersion)  ilOffset = 0x156

    at OrganizationSdkServiceInternal.ExecuteRequest(OrganizationRequest request, CorrelationToken correlationToken,
        CallerOriginToken callerOriginToken, WebServiceType serviceType, UserAuth userAuth, Guid targetUserId, 
        Boolean traceRequest, OrganizationContext context, Boolean returnResponse)  ilOffset = 0x145

    at OrganizationSdkServiceInternal.ExecuteRequest(OrganizationRequest request, CorrelationToken correlationToken,
       CallerOriginToken callerOriginToken, WebServiceType serviceType)  ilOffset = 0x3D

    at OrganizationSdkServiceInternal.Execute(OrganizationRequest request, CorrelationToken correlationToken,
       CallerOriginToken callerOriginToken, WebServiceType serviceType)  ilOffset = 0x24

    at InprocessServiceProxy.ExecuteCore(OrganizationRequest request)  ilOffset = 0x34
    at PlatformCommand.XrmExecuteInternal()  ilOffset = 0xF6
    at RetrieveCommand.Execute()  ilOffset = 0x2
    at EntityProxy.Retrieve(String[] columns, Guid auditingTransactionId, Boolean addRequiredColumns)  ilOffset = 0x69
    at EntityProxy.Retrieve(String[] columns, Guid auditingTransactionId)  ilOffset = 0x4
    at EntityProxy.Retrieve(String columnSet, Guid auditingTransactionId)  ilOffset = 0xB
    at EntityProxy.Retrieve(String columnSet)  ilOffset = 0x7
    at AppForm.FormLoadEvent()  ilOffset = 0x11
    at AppForm.RaiseDataEvent(FormEventId eventId)  ilOffset = 0xC7
    at EndUserForm.Initialize(Entity entity)  ilOffset = 0x1F
    at CustomizableForm.Execute(Entity entity, FormDescriptor fd)  ilOffset = 0x62
    at RecordPageHandler.ConfigureFormWrapper()  ilOffset = 0xC
    at GenericEventProcessor.RaiseEvent(String eventName)  ilOffset = 0x2D
    at PageManager.OnPreRender(EventArgs e)  ilOffset = 0x47
    at Control.PreRenderRecursiveInternal()  ilOffset = 0x54
    at Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)  ilOffset = 0x6D3
    at Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)  ilOffset = 0x3C
    at Page.ProcessRequest()  ilOffset = 0x14
    at Page.ProcessRequest(HttpContext context)  ilOffset = 0x33
    at CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()  ilOffset = 0x18D
    at HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)  ilOffset = 0x15
    at ApplicationStepManager.ResumeSteps(Exception error)  ilOffset = 0x10A
    at HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData)  ilOffset = 0x5C
    at HttpRuntime.ProcessRequestInternal(HttpWorkerRequest wr)  ilOffset = 0x16A
    at ISAPIRuntime.ProcessRequest(IntPtr ecb, Int32 iWRType)  ilOffset = 0x4B

That is 75 calls, if you didn’t feel like counting, and in the middle of all of that was the failing call to my plugin.

The nearest thing I can figure is that this Opportunity is set to be System Calculated and when it was opened, a update to the revenue numbers. This ended up firing my plugin which failed because data that was passed to it turned out to be invalid.

I temporarily disabled the plugin and was able to save the Opportunity and correct the invalid data issue.

I could not have done this troubleshooting without the following tools:

If you do not have these tools in your toolbox, you need to fix that today.

Like most Xamarin developers I have a Mac setting on my desk.  Unfortunately I do not find myself to be as productive on a Mac keyboard as I do on the Windows Keyboard.

When Xamarin’s Visual Studio integration was released, and it became stable enough to develop with Visual Studio on my Windows workstation instead of Xamarin Studio on the Mac, I could not have been happier.

The problem still remained that I needed to access the Mac for at least some testing on the iOS simulator.  At first I was just turning my chair and using the Macbook directly.

Then I remembered that GoToMyPC has a Mac client, and I happen to have a two-host license.  So, I installed GotoMyPC on the Mac, and have really enjoyed how much code I can write, and test, using this setup.

I am comfortable with my development tools (Visual Studio, Resharper, etc)., and when I need test my iOS apps, I just open a GotoMyPC session to my Mac and run the simulator.

When I work from home I actually use GotoMyPC to connect to my desktop at the office and when doing iOS development, just start another session to my Mac, which is also at the office.

I mention this now because I don’t think a lot of people think about a remote desktop tool being a productivity aid for developers – and it may not be for everyone – but it sure was for me.

Fellow MVP Jason Lattimer (@JLattimer) has released a really cool tool for us Dynamics CRM JavaScript developers: CRM Rest Builder

This is a solution you install inside of Dynamics CRM with the main interface looking like this:

 

Features

We can utilize the following SDK methods:

  • Retrieve
  • RetrieveMultiple
  • Create
  • Update
  • Delete
  • Associate
  • Disassociate

 

Generated JavaScript

CRM Rest Builder will generate code that utilizes the following JavaScript libraries to actually execute the methods:

  • XrmSvcToolkit,
  • jQuery
  • SDK.REST
  • SDK.JQUERY
  • XMLHTTP

It can also generate Asynchronous or Synchronous method calls, depending on your requirements.

 

Data Selection and Filters

You can select a variety of data points to utilize including:

  • The Entity
  • Set the maximum number of values to be returned
  • Fields
  • One to Many relationships
  • Many to One relationships
  • Many to Many relationships

You can specify the data be filtered much as you would with the Dynamics CRM Advanced Find feature including:

  • Field-level filters
  • Sort orders

 

Output

The generated code is displayed in a window like this:

Conclusion

I actually quite mad at Jason right now.  He would have saved me a couple of hours banging my head against the wall last week.  Actually, I can’t blame Jason for me not paying attention. Smile

Anyway, this is a great tool to add to your development toolbox and I for one, am excited to see it released.

Here is a small method to help you determine if a User is a member of a specified Team:

Hi Everyone,

Just wanted to send you a reminder that next month we are holding two workshops for Dynamics CRM developers:

Plugin Development with Dynamics CRM

JavaScript Development with Dynamics CRM

This may be the last offerings of these workshops for the remainder of the year.

Let me know if you have any questions.

Thanks, Mitch

I thought I’d put together a few reasons why you, the Dynamics CRM developer, should be attending my Extending Dynamics CRM 2013 workshop on July 21st.

 

Reason #1: Hands-on Instruction

Can you type?  Good; because you’ll be doing a lot of exercises.

While I like to hear the sound of my own voice, nothing beats practical practice and all of my courses have lots and lots of labs.

 

Reason #2: The Goodies

My main job is Dynamics CRM architecture and development. I have lots of tools that help me do my job and I share some of those with my development students.

Mostly they are code samples and frameworks to help you become more productive as a Dynamics CRM developer.

 

Reason #3: What Other People Have To Say

Don’t take my word for it, here are what some former students had to say about the workshops they attended:

Mitch’s Plug-in Development workshop helped me get out of the gate and a good way down the path of C# development for Microsoft Dynamics CRM – including both Plug-ins and Workflow Assemblies.

Mitch is an excellent instructor who responds to all questions and helps people along at their own pace. 

I highly recommend this workshop for anyone who is looking to get into the Microsoft CRM development game. It will accelerate your process and save you more than enough time to pay for itself.

Aron F.

Your class was an eye opening experience.  In addition to learning about Plug-In Development this class exposed me to so many other aspects of Microsoft Dynamics CRM that I was not aware of.

Marlon R. Joseph

Application Analyst III

Houston Baptist University

I found Mitch Milam’s workshop to be a great way to jump-start my plug-in development.  Besides presenting class material in such a way that it was easy to comprehend, Mitch also provided Visual Studio templates.  These templates proved to be extremely useful because they take care of the necessary plumbing when writing plug-ins, speeding up the development time.  Additionally, the labs we worked on in class covered real-life scenarios directly applicable to my day-to-day work.  In fact soon after the workshop, I solved a business problem by building a plug-in that was based on one of the labs we worked on in class.

Natalya Pinsker

Baltimore, MD,

 

Reason #4: There Are Only 15 Student Openings

As I mentioned, this is all hands-on so I have to put a limit to the number of students that can be part of the class.  15 people is at the upper limit of my typical classroom size, but since Microsoft doesn’t offer this course as Instructor-led, I want to make sure I can get as many people trained as possible.

 

Reason #5: A Bonus. Maybe.

I have a Dynamics CRM Architecture course under development and if I get enough students in the Extending course, I might just give you a free preview the week of July 28th.

 

So, what are you waiting for?

Sign up today. Review the original announcement for more information and a link to the registration page:

http://www.infinite-x.net/2014/07/01/extending-dynamics-crm-2013-workshop

And drop me a line if you have any questions:

mitch at xrmcoaches dot com

Hi Everyone,

Since I think a lot of people never get around to searching the archives, I thought I’d mention a project I started a while ago to aid the .NET developer in moving your code to the Dynamics CRM 201x version.

The project may be found here:

https://crmdotnetmigrator.codeplex.com/

Project Description
This project is designed to assist the .NET developer who is migrating their C# code from the CRM 4.0 object model to the CRM 2011 object model.

The initial purpose of the project is to generate Visual Studio macros which perform search and replace operations to which change the code from one syntax to the other.

It is planned that in a later stage of the project, we will read and covert source files directly.

 

This project contains all of the knowledge I accumulated during a couple of CRM 4.0 migrations I performed.

If you have some time, take a look.  If you have some knowledge to share and wish to make additions, then please let me know.

Thanks, Mitch

I still occasionally run into hidden greatness inside of the Dynamics CRM SDK.  Last week I found a great addition to the QueryExpression and QueryByAttribute classes: The TopCount property.

This property was introduced into Dynamics CRM 2011 around the UR10-UR11 timeframe, so if you are on UR11, or later, or have Dynamics CRM 2013, you have this capability.

TopCount works exactly like the TOP operator from a SQL SELECT statement:

SELECT TOP 50 * FROM FilteredAccount

 

Here is how you use it:

Note: I have a small function called IsTopCountSupportedByServer that checks the version number against UR11 to make sure we can use TopCount.  If not, we drop back to the standard PagingInfo class.

var maxBatchSize = 50;

QueryExpression queryExpression = new QueryExpression(Account.EntityLogicalName);

if (IsTopCountSupportedByServer())
{
    queryExpression.TopCount = new int?(maxBatchSize);
}
else
{
    queryExpression.PageInfo.Count = maxBatchSize;
    queryExpression.PageInfo.PageNumber = 1;
}

This is just a really cool addition to our toolboxes and I am shocked I didn’t see this before.

Sign up for our new KnowledgeBits service and get news, tips and tricks and more, delivered straight to your inbox.