You are viewing limited content. For full access, please sign in.

Question

Question

Workflow Custom Activity - Could not load file or assembly 'Laserfiche.DocumentServices'

asked on March 20, 2015

I am working at creating my Workflow custom activity.  I have gotten to a point where I have loaded the activity successfully into the Workflow Administration Console and Workflow and added it into a test workflow. 

The workflow works at taking a document and checks each page for a black background with white text.  If it finds this, it replace the page with an inverted copy of the page. 

To make it all work, I need the DocumentServices library.

When I try running this, I get the following error message:

 

  3/20/2015 11:19:41 AM LF Workflow Image Inverter Could not load file or assembly 'Laserfiche.DocumentServices, Version=9.0.0.0, Culture=neutral, PublicKeyToken=3f98b3eaee6c16a6' or one of its dependencies. The system cannot find the file specified.

 

I verified that the LF SDK is loaded on the WF server.  Is there something else I am missing to get this to work?

 

Thanks!

Jonathan

0 0

Answer

APPROVED ANSWER
replied on March 23, 2015 Show version history

Ah, shoot. Looks like an oversight... we didn't add an extension for the wrapper for 9.2. However, the call to CreateConnectionRA92 does exist. You will need to do a bit extra work here. Bear with me (this might take a bit of going back and forth). If this gets too complicated I'll dust off ye olde WF SDK and try this myself, but I'm going straight off the SDK code itself here. Anyways, to get the ConnectionWrapper for 9.2, try the following... bear with me:

 

1) Somewhere in your code define this private static method:

 

private static ConnectionWrapper OpenConnectionRA92(ICustomActivity activity, ConnectionProfile profile)
        {            
            ConnectionWrapper wrapper = ConnectionWrapper.CreateConnectionRA92(profile);
            if (activity != null)
            {
                activity.AddDisposableRuntimeItem(wrapper);
            }

            return wrapper;
        }

 

2) To use that we need to get the connection profile manually (usually the utility method did that for us). This isn't much work at all tho: 

private static ConnectionProfile GetConnectionProfile(ActivityExecutionContext context) {
            ConnectionProfile profile = null;
            IProfileActivityInternal profileActivity = context.Activity as IProfileActivityInternal;
            if (profileActivity != null)
            {
                profile = profileActivity.GetProfile(context);
            }

            return profile;
}

 

3) Now we use both of these. In your code, replace 

 

using (ConnectionWrapper wrapper = executionContext.OpenConnection83())

with

ConnectionProfile profile = GetConnectionProfile(context);
using (ConnectionWrapper wrapper = OpenConnectionRA92(context.Activity, profile))

Basically I'm doing what the extension functions themselves are doing. It might take some additional tweaking... I'm writing this whole thing only by analysis of the code itself =). You may also want to do some null checking there, particularly when doing type checks such as "context.Activity as IProfileActivityInternal" and such, for the sake of robustness.

 

Let me know if this works, or which error you get instead. Sorry about how tricky this is... I filed a bug fix request about the missing OpenConnectionRA92 method. Can't believe nobody noticed this till now... Doh!

0 0

Replies

replied on March 20, 2015 Show version history

Jonathan: is the LF SDK installed in the machine that is running the WF Server? Did you check that the version you have installed of the LF SDK matches 9.0.0.0?

Edit:

If the LF SDK is installed in the machine and is of the correct version, have you tried referencing the assembly depencency directly through the WF Admin Console as pictured below? 

What needs to happen is for WF to somehow find that assembly. I think it might be able to find it thru the GAC but the method pictured above is more reliable.

1 0
replied on March 20, 2015

Flavio,

Thank you for replying to my question.  I finally got past that initial problem by recreating the application from template and pasting my code back into it to make sure all the dependencies/settings were configured correctly.  So, I no longer get the error on the assembly.   Of course, there is another error.  Now I get:

 

  3/20/2015 3:05:56 PM Invert Black Background Not a serialized LFConnection.
Parameter name: serializedConn

 

Here is what I am trying to do to get my connection:

ILFDatabase database = (ILFDatabase)wrapper.Database;
using (Session mysession = Laserfiche.RepositoryAccess.Session.CreateFromSerializedLFConnection(database.CurrentConnection.SerializedConnection))
      {
             DocumentInfo mydoc = Document.GetDocumentInfo(GetEntryInformation(executionContext).Id, mysession);

Sorry about all the questions.  I'm struggling with my notes from ACI301 and the online documentation/forum comments and trying to get everything to work together.

 

Thanks again!

Jonathan

0 0
replied on March 20, 2015

Ick, this is slowly entering into LF SDK territory. Let's see... what is the base class for your custom activity? Are you using LaserficheActivity80 or one of those? Can you show me the code that is setting the wrapper? 

My guess is that the "current" connection is not properly set, and thus the SerializedConnection might be null or some other invalid value. It might help to see a bit more of the code above these lines. If you don't feel comfortable posting it publicly, you could email it to me (flavio.kuperman@laserfiche.com) 

0 0
replied on March 20, 2015 Show version history

I am fairly comfortable once I am dealing with RA and DocumentServices part of the SDK.  It is jumping between LFSO and RA to make the Activity work that is making this tricky. 

I don't have a problem posting my code, as it is pretty generic and will be pretty cool when it runs.  I first had this running correctly in a stand alone Window forms application just to make sure the actual manipulation of the graphics worked as expected.  Anyway, here is the code of my class up until I start making my image processing calls:

    public class LFWFImageInverterV5Activity : CustomSingleEntryActivity
    {
        public bool ToWhite { get; set; }

        private const int BLACK = -16777216;
        private const int WHITE = -1;


        /// <summary>
        /// Called when the activity is run by the workflow server. Implement the logic of your activity in this method. 
        /// Access methods for setting tokens, getting token values, and other functions from the base class or the execution 
        /// context parameter. 
        /// </summary>
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            using (ConnectionWrapper wrapper = executionContext.OpenConnection83())
            {
                ILFDatabase database = (ILFDatabase)wrapper.Database;
                
                using (Session mysession = Laserfiche.RepositoryAccess.Session.CreateFromSerializedLFConnection(database.CurrentConnection.SerializedConnection))
                {
                    DocumentInfo mydoc = Document.GetDocumentInfo(GetEntryInformation(executionContext).Id, mysession);
                    DocumentExporter exporter = new DocumentExporter();
                    exporter.PageFormat = DocumentPageFormat.Bmp;

                    for (int x = 1; x <= mydoc.PageCount; x++)
                    {
                        System.IO.MemoryStream outdata = new System.IO.MemoryStream();
                        exporter.ExportPage(mydoc, x, outdata);
                        Bitmap temp = new Bitmap(outdata);
                        Dictionary<int, int> results = GetBMPColors(temp);
                        if ((ToWhite == true && results[BLACK] > results[WHITE]) ||
                             (ToWhite == false && results[WHITE] > results[BLACK]))
                        {
                            // Invert Image
                            Bitmap fixedimage = this.InvertPage(temp);
                            DocumentImporter importer = new DocumentImporter();
                            mydoc.Lock(LockType.Exclusive);
                            System.IO.MemoryStream indata = new System.IO.MemoryStream();
                            fixedimage.Save(indata, System.Drawing.Imaging.ImageFormat.Tiff);
                            importer.Document = mydoc;
                            importer.ImportImages(indata);
                            mydoc.DeletePage(x);
                            if (mydoc.PageCount != x)
                                mydoc.MovePagesTo(new PageRange(mydoc.PageCount, mydoc.PageCount), mydoc, x);
                            mydoc.Unlock();
                        }

                    }
                }
            }
            return base.Execute(executionContext);

        }

 

0 0
replied on March 20, 2015

Have you tried using OpenConnectionRA instead of OpenConnection83? I'm a bit rusty on the ConnectionWrappers but I personally would expect to start with an RA connection directly rather than an LFSO one. I honestly have no idea whether or not the serialized LFSO83 connection would be "accepted" for creating an RA Session (it's all LF SDK territory at that point).

Also, I'd hate to do a "is your computer plugged in" type of question... but you do have a connection profile set, right?

One thing that's worth doing is to try to debug your custom activity by attaching Visual Studio to the WF Server in console mode. By doing so you might be able to better gather useful information such as whether or  database.CurrentConnection is connected and whether or not the serialized connection has any info that might be useful in some way.

0 0
replied on March 20, 2015

Is there a place you can point me detailing the ConnectionWrappers and how they are supposed to be used?  They are not in the Workflow SDK or the Laserfiche SDK documentation (at least that I could find).  It looks like maybe if I use the OpenConnectionRA connection, I can use the GetRepository command to access the repository directly. 

 

My assumption on how this works is that Workflow passes the connection profile to the Custom Activity and I just need to access it.  My test workflow does have the profile set within the Profile manager.

 

I was trying to debug earlier.  Instructions from the class suggested connecting Visual Studio to the remote Workflow Service and Workflow Designer.  Doing this and running the workflow did not cause anything to happen in Visual Studio.

 

Anyway, thanks again for your help and have a good weekend!

 

Jonathan

 

0 0
replied on March 20, 2015

I'm not sure about the ConnectionWrappers documentation but I can try to find out if it exists. I have a feeling we currently do not have such documentation, at least not for using it with RA.

Let me know if you are able to do what you're trying to do with OpenConnectionRA. I'd say the other connection calls should be used more to preserve regression with old custom activities than to implement new ones; Instead I'd recommend using an RA connection directly.

0 0
replied on March 20, 2015

As for debugging... the process can be a bit complicated if you are debugging remotely. If VS is on the same machine as the WF Server, then you can just run the server as a console application (there is documentation on how to do that on various more recent WF SDK white papers...) and attach VS to the WF Server, and as long as you have the symbol files (pdbs) for you code you should be able to hit breakpoints. If you see a hollow breakpoint (red outline with nothing inside) instead of a full red dot, it means VS is not able to properly match up your code with the runtime. If you hover over it you will get a note about why this happens; the most likely cases are 

1) Could not find the PDB file

2) PDB file is for a different build

3) You have changed the code since building the dll

4) Some issue with remote debugging

 

As for remote debugging itself... well, that's a bit complicated. I could give some tips on it but it can be tricky even for us sometimes =). The most typical issue with remote debugging if you manage to actually attach would be with not being able to find the PDB files. You can try loading them directly with the modules window if that's the case. It's in Debug => Windows => Modules. Find your dll in the list, right click it, and try to load symbols directly from there. This sometimes works but sometimes it doesn't. 

0 0
replied on March 20, 2015

Jonathon - I hope this isn't too basic but I believe the problem might have started when you created the project.  When you first created your new project from the Laserfiche Custom Workflow Activity template a 'wizard' like dialog box should have appeared where you can select the 'LFSO Support'.  If you select any of the Repository Access options you will automatically get the correct ConnectionWrapper to start using RA.  Here is a screen shot of that wizard with Repository Access 9.1 selected;

Once the project explorer opens up you can see that the correct references are already included to start using the RA session object; (Obviously VB but the same applies to a C# project)


 

1 0
replied on March 20, 2015

Yep, I second what Cliff said (having myself written that little wizard about 2 or 3 years ago =)). The wizard was actually created to facilitate the use of those ConnectionWrappers since people had trouble figuring out how to make the LF connections in the past.

I strongly recommend creating a ConnectionWrapper for RA if you want to open an RA Session; I'm not sure whether or not it works using LFSO, but I'm leaning towards no. Check what happens if you run your code as you have it but opening the connection to RA instead (and using the Connection directly rather than trying to create a connection from the Database property). Looks like you can basically get the Session directly from the ConnectionWrapper, as the code generated by the template seems to suggest.

0 0
replied on March 23, 2015

 

Flavio/Cliff,

 

Thank you for your continued suggestions/support.  I went ahead and recreated the project again exactly as shown in Cliff's screenshot (8.3/Single/Repository Access 9.1).  I pasted my code into the appropriate area, rebuilt the code/proxy and tried again.

At least this time, I am getting a different error.  Now I am getting this:

  3/23/2015 10:01:35 AM LF Workflow Image Inverter V6

[A]Laserfiche.RepositoryAccess.Session cannot be cast to [B]Laserfiche.RepositoryAccess.Session. Type A originates from 'Laserfiche.RepositoryAccess, Version=9.1.0.0, Culture=neutral, PublicKeyToken=3f98b3eaee6c16a6' in the context 'Default' at location 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Laserfiche.RepositoryAccess\v4.0_9.1.0.0__3f98b3eaee6c16a6\Laserfiche.RepositoryAccess.dll'. Type B originates from 'Laserfiche.RepositoryAccess, Version=9.2.0.0, Culture=neutral, PublicKeyToken=3f98b3eaee6c16a6' I

 

 

 

       

I am not sure what I am doing wrong this time.  As far as references go, the only thing I did  was add the DocumentServices assembly. 

 

Thanks!

Jonathan

 

0 0
replied on March 23, 2015

Jonathan, this error indicates that the activity is attempting to create a Session using the class Session from RA 9.1.0.0, but the reference in your custom activity is to the assembly for 9.2.0.0. This is because the template is referencing RA 9.1 while your reference is for 9.2. 

In your template wizard, is there an option for Repository Access 9.2 under LFSO Support? If so, try using that. If not, we can try to reference 9.1 directly, though it may be a bit trickier since you have an install of LF SDK 9.2 already in the GAC.

1 0
replied on March 23, 2015

When I try to select 9.2 from the wizard, there is an issue with the wrapper.  'OpenConnectionRA92'.  I get the following error before I do anything with the newly created application by wizard:

 

System.Workflow.ComponentModel.ActivityExecutionContext' does not contain a definition for 'OpenConnectionRA92' and no extension method 'OpenConnectionRA92' accepting a first argument of type 'System.Workflow.ComponentModel.ActivityExecutionContext' could be found (are you missing a using directive or an assembly reference?)

 

Jonathan

0 0
replied on March 24, 2015

Makes sense.  Glad it wasn't something I was doing wrong this time :).

 

As for the code, what assembly does 'IProfileActivityInternal' belong to?  I can't find a reference to that class, and cannot test this code until I do.

 

Thanks!

Jonathan

0 0
replied on March 24, 2015

IProfileActivityInternal is an interface implemented by CustomProfileActivity which in turn is the abstract base class of CustomSingleEntryActivity. So if you dig 2 levels into the definition of the CustomSingleEntryActivity you will see IProfileActivityInternal. All this interface does is expose the GetProfile method, which extension functions like CreateConnectionRA92 call "internally". Basically the code I wrote up above mirrors what the extension functions are doing themselves. It's part of the Laserfiche.Custom.Activities namespace in the Laserfiche.Workflow.Activities.Custom assembly, the same as the CustomSingleEntryActivity

You can probably just cast it to CustomSingleEntryActivity instead of IProfileActivityInternal, though I think using the interface is more correct.

0 0
replied on March 24, 2015

It works!

I don't think that I can directly make use of IProfileActivityInternal as an end user of the API.  However, you were correct that I was able to get away using CustomSingleEntryActivity.  Now on to enhancing my code to add some error checking, feedback when and page is replaced, etc.

Thanks for all of your help on this!  For anyone else reading this thread, here is the final code that worked:

 


namespace LFWFImageInverterV7
{
    using Laserfiche.Custom.Activities;
    using Laserfiche.Workflow.ComponentModel;
    using Laserfiche.Custom.Activities.Design;
    using Laserfiche.RepositoryAccess;
    using LFSO90Lib;

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Diagnostics;
    using System.ComponentModel;
    using System.Workflow.ComponentModel;
    using System.Drawing;
    using Laserfiche.Workflow.Activities;
using Laserfiche.Connection;
    using Laserfiche.DocumentServices;

    public class LFWFImageInverterV7Activity : CustomSingleEntryActivity
    {

        public bool ToWhite { get; set; }

        private const int BLACK = -16777216;
        private const int WHITE = -1;


        /// <summary>
        /// Called when the activity is run by the workflow server. Implement the logic of your activity in this method. 
        /// Access methods for setting tokens, getting token values, and other functions from the base class or the execution 
        /// context parameter. 
        /// </summary>
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            ConnectionProfile profile = GetConnectionProfile(executionContext);
            using (ConnectionWrapper wrapper = OpenConnectionRA92((ICustomActivity)executionContext.Activity,profile))
            {
                Session session = (Session)wrapper.Connection;
                DocumentInfo mydoc = Document.GetDocumentInfo(GetEntryInformation(executionContext).Id, session);
                DocumentExporter exporter = new DocumentExporter();
                exporter.PageFormat = DocumentPageFormat.Bmp;

                for (int x = 1; x <= mydoc.PageCount; x++)
                {
                    System.IO.MemoryStream outdata = new System.IO.MemoryStream();
                    exporter.ExportPage(mydoc, x, outdata);
                    Bitmap temp = new Bitmap(outdata);
                    Dictionary<int, int> results = GetBMPColors(temp);
                    if ((ToWhite == true && results[BLACK] > results[WHITE]) ||
                         (ToWhite == false && results[WHITE] > results[BLACK]))
                    {
                        // Invert Image
                        Bitmap fixedimage = this.InvertPage(temp);
                        DocumentImporter importer = new DocumentImporter();
                        mydoc.Lock(LockType.Exclusive);
                        System.IO.MemoryStream indata = new System.IO.MemoryStream();
                        fixedimage.Save(indata, System.Drawing.Imaging.ImageFormat.Tiff);
                        importer.Document = mydoc;
                        importer.ImportImages(indata);
                        mydoc.DeletePage(x);
                        if (mydoc.PageCount != x)
                            mydoc.MovePagesTo(new PageRange(mydoc.PageCount, mydoc.PageCount), mydoc, x);
                        mydoc.Unlock();
                    }

                }

            }

            return base.Execute(executionContext);

        }
        
        private static ConnectionWrapper OpenConnectionRA92(ICustomActivity activity, ConnectionProfile profile)
        {
            ConnectionWrapper wrapper = ConnectionWrapper.CreateConnectionRA92(profile);
            if (activity != null)
            {
                activity.AddDisposableRuntimeItem(wrapper);
            }

            return wrapper;
        }

        private static ConnectionProfile GetConnectionProfile(ActivityExecutionContext context)
        {
            ConnectionProfile profile = null;
            CustomSingleEntryActivity profileActivity =  (CustomSingleEntryActivity)context.Activity;
            if (profileActivity != null )
            {
                profile = (ConnectionProfile)profileActivity.GetProfile(context);
            }
            return profile;
        }

        private Dictionary<int, int> GetBMPColors(Bitmap mypage)
        {
            Dictionary<int, int> dictColors = new Dictionary<int, int>();
            Color c;
            for (int x = 0; x < mypage.Width; x++)
            {
                for (int y = 0; y < mypage.Height; y++)
                {
                    c = mypage.GetPixel(x, y);
                    if (dictColors.ContainsKey(c.ToArgb()))
                    {
                        dictColors[c.ToArgb()] = dictColors[c.ToArgb()] + 1;
                    }
                    else
                    {
                        dictColors.Add(c.ToArgb(), 1);
                    }
                }
            }
            return (dictColors);
        }

        private Bitmap InvertPage(Bitmap mypage)
        {
            Bitmap nonindexed = CreateNonIndexedImage(mypage);
            Color c;
            for (int x = 0; x < nonindexed.Width; x++)
            {
                for (int y = 0; y < nonindexed.Height; y++)
                {
                    c = nonindexed.GetPixel(x, y);
                    nonindexed.SetPixel(x, y, Color.FromArgb(255 - c.R, 255 - c.G, 255 - c.B));
                }
            }
            return (nonindexed);
        }

        private Bitmap CreateNonIndexedImage(Bitmap source)
        {
            Bitmap newBmp = new Bitmap(source.Width, source.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            newBmp.SetResolution(source.HorizontalResolution, source.VerticalResolution);
            using (Graphics gfx = Graphics.FromImage(newBmp))
            {
                gfx.DrawImage(source, 0, 0);
            }
            return (newBmp);
        }


    }
}

 

1 0
replied on March 24, 2015 Show version history

No problem, glad to see the magical words "It works!" =)

Sorry this was so tricky to set up. We will do our best to include the OpenConnectionRA92 extension functions in a near release, as they were intended, so this should be easier going forward.

 

Edit: Oops, I should have realized that IProfileActivityInternal is actually marked as internal. My bad. That would explain why you couldn't find it, heh. Also explains why the extension functions are good to have here. But yeah, it should be no different to cast to the actual activity type if you know its type (CustomSingleEntryActivity, etc), though I do recommend doing a type check for the sake of robustness if you plan on reusing the same static functions elsewhere.

0 0
You are not allowed to follow up in this post.

Sign in to reply to this post.