OrderHed Image Attachment

The ask is to create automation for when a License plate label is printed (on a custom screen) for a specific PCID (container number), an image is captured as .jpeg, via API, from the assigned crating area and have that image attached to all Sales Orders that are assigned that PCID # at the OrderHed level.

Here is the first version of what I was trying to do: Step 2 was moved to the top to see if the steps before it were breaking the function but it was originally after step 1 (naturally).
Step 0 will be modified later since the all the cameras have yet to be set up and API’s built for them.


// ================================================================
// CONFIGURATION
// ================================================================
//string company       = Session.CompanyID;
//CurrentUser          = Session.UserID;
//CurrentWorkstation   = Session.WorkstationID;
resultMessage        = "";
string docTypeID     = "IMAGE";
//string cameraName    = "TestCamera";
string cameraApiUrl  = $"**removed for security**"; 

// ================================================================
// STEP 0: Look up camera name from WorkStation BAQ
// ================================================================
//try
//{
//    var wsResults = (from ws in Db.WorkStation
//                     where ws.Company == company
//                        && ws.WorkStationID == CurrentWorkstation
//                     select ws)
//                    .FirstOrDefault();
//
//    if (wsResults == null)
//    {
//        resultMessage = $"No workstation record found for WorkStationID: {CurrentWorkstation}";
//        return;
//    }
//
//    cameraName = wsResults.Camera_c?.Trim();
//
//    if (string.IsNullOrEmpty(cameraName))
//    {
//        resultMessage = $"No camera assigned to workstation: {CurrentWorkstation}";
//        return;
//    }
// 
//    cameraApiUrl = $"removed for secuirty(cameraName)}";
//}
//catch (Exception ex)
//{
//    resultMessage = $"Workstation lookup error: {ex.Message}";
//    return;
//}

// ================================================================
// STEP 2: Call the camera API and retrieve raw JPEG bytes
// ================================================================
byte[] imageBytes = null;

try
{
    using (var httpClient = new System.Net.Http.HttpClient())
    {
        httpClient.Timeout = TimeSpan.FromSeconds(30);
        //httpClient.DefaultRequestHeaders.Add("x-functions-key", "**removed for security**");

        var response = httpClient.GetAsync(cameraApiUrl).Result;

        if (!response.IsSuccessStatusCode)
        {
            resultMessage = $"Camera API failed: HTTP {(int)response.StatusCode} {response.ReasonPhrase}";
            return;
        }

        var contentType = response.Content.Headers.ContentType?.MediaType ?? "";
        if (!contentType.Contains("image") && !contentType.Contains("octet-stream"))
        {
            resultMessage = $"Unexpected content type: {contentType}";
            return;
        }

        imageBytes = response.Content.ReadAsByteArrayAsync().Result;

        if (imageBytes == null || imageBytes.Length == 0)
        {
            //resultMessage = $"Camera API returned empty image for camera: {cameraName}";
            return;
        }

        resultMessage = $"Image captured successfully | Size: {imageBytes.Length} bytes";
    }
}
catch (Exception ex)
{
    resultMessage = $"Camera API error: {ex.Message}";
    return;
}


// ================================================================
// STEP 1: Find Sales Orders linked to the PCID
// ================================================================
List<int> orderNums = new List<int>();

try
{
    var orders = (from hdr in Db.PkgControlHeader
                  join itm in Db.PkgControlItem
                      on new { hdr.Company, hdr.PCID }
                      equals new { itm.Company, itm.PCID }
                  where hdr.Company == company
                     && hdr.PCID == pcidString
                     && itm.OrderNum != 0
                  select itm.OrderNum)
                 .Distinct()
                 .ToList();

    if (orders == null || orders.Count == 0)
    {
        resultMessage = $"No Sales Orders found for PCID: {pcidString}";
        return;
    }

    orderNums = orders;
    resultMessage = $"Found {orderNums.Count} order(s): {string.Join(", ", orderNums)}";
}
catch (Exception ex)
{
    resultMessage = $"Order lookup error: {ex.Message}";
    return;
}




// ================================================================
// STEP 3: Upload to DocStar via AttachmentSvc.DocStarUploadFile
// ================================================================
string uploadedFilePath = "";
string attachFileName   = $"PCID_{pcidString}_{DateTime.Now:yyyyMMdd_HHmmss}.jpg";

try
{
    var metaData = new System.Collections.Generic.Dictionary<string, string>
    {
        { "PCID",     pcidString },
        { "Camera",   cameraName },
        { "DocType",  docTypeID }
    };

    using (var attachSvc = Ice.Assemblies.ServiceRenderer
               .GetService<Ice.Contracts.AttachmentSvcContract>(this.Db as Ice.IceDataContext))
    {
        uploadedFilePath = attachSvc.DocStarUploadFile(
            attachFileName,  // fileName - file name without path
            imageBytes,      // data - raw JPEG bytes
            docTypeID,       // docTypeID - "IMAGE"
            "OrderHed",      // parentTable - table where file is attached
            metaData         // metadata
        );
    }

    if (string.IsNullOrEmpty(uploadedFilePath))
    {
        resultMessage = "DocStarUploadFile returned empty file path.";
        return;
    }

    resultMessage = $"DocStar upload success | Path: {uploadedFilePath}";
}
catch (Exception ex)
{
    resultMessage = $"DocStar upload error: {ex.Message}";
    return;
}

// ================================================================
// STEP 4: Link image to each Sales Order via GetNewOrderHedAttch
// ================================================================
List<string> successOrders = new List<string>();
List<string> failedOrders  = new List<string>();
int orderIndex = 1;

foreach (int orderNum in orderNums)
{
    try
    {
        // Get the OrderHed record
        var orderHedRow = (from oh in Db.OrderHed
                           where oh.Company == company
                              && oh.OrderNum == orderNum
                           select oh).FirstOrDefault();

        if (orderHedRow == null)
        {
            failedOrders.Add($"{orderNum} (OrderHed record not found)");
            orderIndex++;
            continue;
        }

        using (var soSvc = Ice.Assemblies.ServiceRenderer
                   .GetService<Erp.Contracts.SalesOrderSvcContract>(this.Db as Ice.IceDataContext))
        {
            // Get full Sales Order dataset
            var soDS = soSvc.GetByID(orderNum);

            if (soDS == null || soDS.OrderHed.Count == 0)
            {
                failedOrders.Add($"{orderNum} (Sales Order not found)");
                orderIndex++;
                continue;
            }

            // Use GetNewOrderHedAttch to properly initialize DocStar fields
            soSvc.GetNewOrderHedAttch(ref soDS, orderNum);

            // Find the newly added row
            var newAttach = soDS.OrderHedAttch
                                .FirstOrDefault(r => r.RowMod == "A");

            if (newAttach == null)
            {
                failedOrders.Add($"{orderNum} (no new attachment row found)");
                orderIndex++;
                continue;
            }

            newAttach.Company   = company;
            newAttach.OrderNum  = orderNum;
            newAttach.DrawDesc  = $"PCID Camera Capture | PCID: {pcidString} " +
                                  $"| Camera: {cameraName} " +
                                  $"| Order: {orderIndex} of {orderNums.Count} " +
                                  $"| {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
            newAttach.DocTypeID = docTypeID;
            newAttach.FileName  = uploadedFilePath;
            newAttach.RowMod    = "A";

            soSvc.Update(ref soDS);

            successOrders.Add(orderNum.ToString());
        }
    }
    catch (Exception ex)
    {
        failedOrders.Add($"{orderNum} (error: {ex.Message})");
    }

    orderIndex++;
}

// ================================================================
// STEP 5: Build result summary
// ================================================================
resultMessage = $"PCID: {pcidString} | " +
                $"Camera: {cameraName} | " +
                $"Image: {attachFileName} | " +
                $"Attached to orders: {(successOrders.Count > 0 ? string.Join(", ", successOrders) : "none")}";

if (failedOrders.Count > 0)
    resultMessage += $" | FAILED orders: {string.Join(", ", failedOrders)}";

Because I couldn’t figure out why this function was not working originally, I decided to try the route of having one of our SE’s build a separate API to do the work that steps 3, 4, and 5 are supposed to do. But considering we cant figure out why its not hitting at all we are trying to first figure out how to get it to hit an API.

These are the Assemblies that I have added since I started working/testing this function, I understand that not all of them are necessary but I am trying everything:

The code is pretty good. I would focus on the camera. I believe that’s where your failure lies.

I would implement some retry logic there.

I notice you are on prem - if your request isn’t even getting out to the API endpoint perhaps your IT department has your server really locked down?

We have figured out that in App Studio we were not passing in the necessary Epicor API key which is why it wasnt hitting the API in the first place.

this was a punch to the gut ill tell ya that for free

the function is finally being called on the network:
image

But still no attachments

That is no longer required, and is not recommended.

Your library is promoted right?

For some reason it is required for this function only, mayhaps because i was building the function in Kinetic Powertools rather than the client (im unsure). yes when I test this i ensure that the library is promoted to production.

I have the method parameters set up correctly I think, but somehow its not attaching to the orders,




Where is this being triggered from?

It is being triggered on a custom slide out for Label printing on the MES page:

I don’t know how much I can help today, at home on my phone lol.

But anyway, let’s break this into pieces and find the problem.

First, let’s generate some data, like a string. Convert it to bytes and see if you can add an attachment to a single order.

So I have stripped the function down to just this and i get a Correlation ID error

// ================================================================
// CONFIGURATION
// ================================================================
string company    = Session.CompanyID;

// ================================================================
// STEP 0: Validate inputs
// ================================================================
if (string.IsNullOrEmpty(pcidString))
{
    throw new Exception("STEP 0 FAILED: pcidString is null or empty!");
}

throw new Exception($"STEP 0 OK | company: {company} | pcidString: {pcidString}");

Correlation error message: We apologize, but an unexpected internal problem occurred. Please provide your System Admin or Technical Support this Correlation ID for further details. Correlation ID: 9eca9301-06d7-46a9-8895-7f035e658326

Throwing an exception will do that - you will have to check your sever logs to see which of those went off.

I think you would also get one if you forgot to promote your library to production

I am doing too much testing at once forgive me, when I tested the entire code:


// ================================================================
// CONFIGURATION
// ================================================================
string company       = Session.CompanyID;
//CurrentUser          = Session.UserID;
//CurrentWorkstation   = Session.WorkstationID;
resultMessage        = "";
string docTypeID     = "IMAGE";
string cameraName    = "TestCamera";
string cameraApiUrl  = $"**removed for security**{cameraName}"; 

// ================================================================
// STEP 0: Look up camera name from WorkStation BAQ
// ================================================================
//try
//{
//    var wsResults = (from ws in Db.WorkStation
//                     where ws.Company == company
//                        && ws.WorkStationID == CurrentWorkstation
//                     select ws)
//                    .FirstOrDefault();
//
//    if (wsResults == null)
//    {
//        resultMessage = $"No workstation record found for WorkStationID: {CurrentWorkstation}";
//        return;
//    }
//
//    cameraName = wsResults.Camera_c?.Trim();
//
//    if (string.IsNullOrEmpty(cameraName))
//    {
//        resultMessage = $"No camera assigned to workstation: {CurrentWorkstation}";
//        return;
//    }
// 
//    cameraApiUrl = $"https:// hydatanetv2-dev.azurewebsites.net/api/GetCameraSnapshot?CameraID={Uri.EscapeDataString(cameraName)}";
//}
//catch (Exception ex)
//{
//    resultMessage = $"Workstation lookup error: {ex.Message}";
//    return;
//}

// ================================================================
// STEP 2: Call the camera API and retrieve raw JPEG bytes
// ================================================================
byte[] imageBytes = null;

try
{
    using (var httpClient = new System.Net.Http.HttpClient())
    {
        httpClient.Timeout = TimeSpan.FromSeconds(30);
        httpClient.DefaultRequestHeaders.Add("x-functions-key", "**removed for security**");

        var response = httpClient.GetAsync(cameraApiUrl).Result;

        if (!response.IsSuccessStatusCode)
        {
            resultMessage = $"Camera API failed: HTTP {(int)response.StatusCode} {response.ReasonPhrase}";
            return;
        }

        var contentType = response.Content.Headers.ContentType?.MediaType ?? "";
        if (!contentType.Contains("image") && !contentType.Contains("octet-stream"))
        {
            resultMessage = $"Unexpected content type: {contentType}";
            return;
        }

        imageBytes = response.Content.ReadAsByteArrayAsync().Result;

        if (imageBytes == null || imageBytes.Length == 0)
        {
            //resultMessage = $"Camera API returned empty image for camera: {cameraName}";
            return;
        }

        resultMessage = $"Image captured successfully | Size: {imageBytes.Length} bytes";
    }
}
catch (Exception ex)
{
    resultMessage = $"Camera API error: {ex.Message}";
    return;
}


// ================================================================
// STEP 1: Find Sales Orders linked to the PCID
// ================================================================
List<int> orderNums = new List<int>();

try
{
    var orders = (from hdr in Db.PkgControlHeader
                  join itm in Db.PkgControlItem
                      on new { hdr.Company, hdr.PCID }
                      equals new { itm.Company, itm.PCID }
                  where hdr.Company == company
                     && hdr.PCID == pcidString
                     && itm.OrderNum != 0
                  select itm.OrderNum)
                 .Distinct()
                 .ToList();

    if (orders == null || orders.Count == 0)
    {
        resultMessage = $"No Sales Orders found for PCID: {pcidString}";
        return;
    }

    orderNums = orders;
    resultMessage = $"Found {orderNums.Count} order(s): {string.Join(", ", orderNums)}";
}
catch (Exception ex)
{
    resultMessage = $"Order lookup error: {ex.Message}";
    return;
}




// ================================================================
// STEP 3: Upload to DocStar via AttachmentSvc.DocStarUploadFile
// ================================================================
string uploadedFilePath = "";
string attachFileName   = $"PCID_{pcidString}_{DateTime.Now:yyyyMMdd_HHmmss}.jpg";

try
{
    var metaData = new System.Collections.Generic.Dictionary<string, string>
    {
        { "PCID",     pcidString },
        { "Camera",   cameraName },
        { "DocType",  docTypeID }
    };

    using (var attachSvc = Ice.Assemblies.ServiceRenderer
               .GetService<Ice.Contracts.AttachmentSvcContract>(this.Db as Ice.IceDataContext))
    {
        uploadedFilePath = attachSvc.DocStarUploadFile(
            attachFileName,  // fileName - file name without path
            imageBytes,      // data - raw JPEG bytes
            docTypeID,       // docTypeID - "IMAGE"
            "OrderHed",      // parentTable - table where file is attached
            metaData         // metadata
        );
    }

    if (string.IsNullOrEmpty(uploadedFilePath))
    {
        resultMessage = "DocStarUploadFile returned empty file path.";
        return;
    }

    resultMessage = $"DocStar upload success | Path: {uploadedFilePath}";
}
catch (Exception ex)
{
    resultMessage = $"DocStar upload error: {ex.Message}";
    return;
}

// ================================================================
// STEP 4: Link image to each Sales Order via GetNewOrderHedAttch
// ================================================================
List<string> successOrders = new List<string>();
List<string> failedOrders  = new List<string>();
int orderIndex = 1;

foreach (int orderNum in orderNums)
{
    try
    {
        // Get the OrderHed record
        var orderHedRow = (from oh in Db.OrderHed
                           where oh.Company == company
                              && oh.OrderNum == orderNum
                           select oh).FirstOrDefault();

        if (orderHedRow == null)
        {
            failedOrders.Add($"{orderNum} (OrderHed record not found)");
            orderIndex++;
            continue;
        }

        using (var soSvc = Ice.Assemblies.ServiceRenderer
                   .GetService<Erp.Contracts.SalesOrderSvcContract>(this.Db as Ice.IceDataContext))
        {
            // Get full Sales Order dataset
            var soDS = soSvc.GetByID(orderNum);

            if (soDS == null || soDS.OrderHed.Count == 0)
            {
                failedOrders.Add($"{orderNum} (Sales Order not found)");
                orderIndex++;
                continue;
            }

            // Use GetNewOrderHedAttch to properly initialize DocStar fields
            soSvc.GetNewOrderHedAttch(ref soDS, orderNum);

            // Find the newly added row
            var newAttach = soDS.OrderHedAttch
                                .FirstOrDefault(r => r.RowMod == "A");

            if (newAttach == null)
            {
                failedOrders.Add($"{orderNum} (no new attachment row found)");
                orderIndex++;
                continue;
            }

            newAttach.Company   = company;
            newAttach.OrderNum  = orderNum;
            newAttach.DrawDesc  = $"PCID Camera Capture | PCID: {pcidString} " +
                                  $"| Camera: {cameraName} " +
                                  $"| Order: {orderIndex} of {orderNums.Count} " +
                                  $"| {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
            newAttach.DocTypeID = docTypeID;
            newAttach.FileName  = uploadedFilePath;
            newAttach.RowMod    = "A";

            soSvc.Update(ref soDS);

            successOrders.Add(orderNum.ToString());
        }
    }
    catch (Exception ex)
    {
        failedOrders.Add($"{orderNum} (error: {ex.Message})");
    }

    orderIndex++;
}

// ================================================================
// STEP 5: Build result summary
// ================================================================
resultMessage = $"PCID: {pcidString} | " +
                $"Camera: {cameraName} | " +
                $"Image: {attachFileName} | " +
                $"Attached to orders: {(successOrders.Count > 0 ? string.Join(", ", successOrders) : "none")}";

if (failedOrders.Count > 0)
    resultMessage += $" | FAILED orders: {string.Join(", ", failedOrders)}";

The function runs in the network but this is the response back:
{
“resultMessage”: “DocStar upload error: Exception has been thrown by the target of an invocation.”
}

I think it may have to do with the DocStarUploadFile() function call something is mismatched somehow.

This is the actual problem that I am now trying to fix

I FINALLY GOT IT WORKING!!!

here is the updated code:

// ================================================================
// CONFIGURATION
// ================================================================
string company       = Session.CompanyID;
resultMessage        = "";
string docTypeID     = "IMAGE";
string cameraName    = "TestCamera";
string cameraApiUrl  = $"removed for security{cameraName}";

// ================================================================
// STEP 0: Look up camera name from WorkStation BAQ (commented out)
// ================================================================
//try
//{
//    var wsResults = (from ws in Db.WorkStation
//                     where ws.Company == company
//                        && ws.WorkStationID == Session.WorkstationID
//                     select ws)
//                    .FirstOrDefault();
//
//    if (wsResults == null)
//    {
//        resultMessage = $"No workstation record found for WorkStationID: {Session.WorkstationID}";
//        return;
//    }
//
//    cameraName = wsResults.Camera_c?.Trim();
//
//    if (string.IsNullOrEmpty(cameraName))
//    {
//        resultMessage = $"No camera assigned to workstation: {Session.WorkstationID}";
//        return;
//    }
//
//    cameraApiUrl = $"removed for security";
//}
//catch (Exception ex)
//{
//    var inner = ex;
//    var messages = new List<string>();
//    while (inner != null) { messages.Add($"[{inner.GetType().Name}] {inner.Message}"); inner = inner.InnerException; }
//    resultMessage = $"Workstation lookup error: {string.Join(" --> ", messages)}";
//    return;
//}

// ================================================================
// STEP 1: Find Sales Orders linked to the PCID
// ================================================================
List<int> orderNums = new List<int>();

try
{
    var orders = (from hdr in Db.PkgControlHeader
                  join itm in Db.PkgControlItem
                      on new { hdr.Company, hdr.PCID }
                      equals new { itm.Company, itm.PCID }
                  where hdr.Company == company
                     && hdr.PCID == pcidString
                     && itm.OrderNum != 0
                  select itm.OrderNum)
                 .Distinct()
                 .ToList();

    if (orders == null || orders.Count == 0)
    {
        resultMessage = $"No Sales Orders found for PCID: {pcidString}";
        return;
    }

    orderNums = orders;
    resultMessage = $"Found {orderNums.Count} order(s): {string.Join(", ", orderNums)}";
}
catch (Exception ex)
{
    var inner = ex;
    var messages = new List<string>();
    while (inner != null) { messages.Add($"[{inner.GetType().Name}] {inner.Message}"); inner = inner.InnerException; }
    resultMessage = $"Order lookup error: {string.Join(" --> ", messages)}";
    return;
}

// ================================================================
// STEP 2: Call the camera API and retrieve raw JPEG bytes
// ================================================================
byte[] imageBytes = null;

try
{
    using (var httpClient = new System.Net.Http.HttpClient())
    {
        httpClient.Timeout = TimeSpan.FromSeconds(30);
        httpClient.DefaultRequestHeaders.Add("x-functions-key", "removed for security");

        var response = httpClient.GetAsync(cameraApiUrl).Result;

        if (!response.IsSuccessStatusCode)
        {
            resultMessage = $"Camera API failed: HTTP {(int)response.StatusCode} {response.ReasonPhrase}";
            return;
        }

        var contentType = response.Content.Headers.ContentType?.MediaType ?? "";
        if (!contentType.Contains("image") && !contentType.Contains("octet-stream"))
        {
            resultMessage = $"Unexpected content type: {contentType}";
            return;
        }

        imageBytes = response.Content.ReadAsByteArrayAsync().Result;

        if (imageBytes == null || imageBytes.Length == 0)
        {
            resultMessage = $"Camera API returned empty image for camera: {cameraName}";
            return;
        }

        resultMessage = $"Image captured successfully | Size: {imageBytes.Length} bytes";
    }
}
catch (Exception ex)
{
    var inner = ex;
    var messages = new List<string>();
    while (inner != null) { messages.Add($"[{inner.GetType().Name}] {inner.Message}"); inner = inner.InnerException; }
    resultMessage = $"Camera API error: {string.Join(" --> ", messages)}";
    return;
}

// ================================================================
// STEP 3: Upload to DocStar via AttachmentSvc.DocStarUploadFile
// ================================================================
string uploadedFilePath = "";
string attachFileName   = $"PCID_{pcidString}_{DateTime.Now:yyyyMMdd_HHmmss}.jpg";

try
{
    var metaData = new System.Collections.Generic.Dictionary<string, string>
    {
        { "additionalProp1", pcidString },
        { "additionalProp2", cameraName },
        { "additionalProp3", docTypeID  }
    };

    // ContextFactory.CreateContext() takes no arguments
    // ServiceRenderer.GetService() requires IceDataContext + ignoreFacade bool
    using (var iceCtx = Ice.Services.ContextFactory.CreateContext<Ice.IceDataContext>())
    using (var attachSvc = Ice.Assemblies.ServiceRenderer
                               .GetService<Ice.Contracts.AttachmentSvcContract>(iceCtx, false))
    {
        uploadedFilePath = attachSvc.DocStarUploadFile(
            attachFileName,   // fileName    - file name without path
            imageBytes,       // data        - raw byte[]
            docTypeID,        // docTypeID   - must exist in DocStar config
            "OrderHed",       // parentTable
            metaData          // metadata
        );
    }

    if (string.IsNullOrEmpty(uploadedFilePath))
    {
        resultMessage = "DocStarUploadFile returned empty file path. " +
                        "Verify that docTypeID 'IMAGE' exists in DocStar configuration.";
        return;
    }

    resultMessage = $"DocStar upload success | Path: {uploadedFilePath}";
}
catch (Exception ex)
{
    var inner = ex;
    var messages = new List<string>();
    while (inner != null) { messages.Add($"[{inner.GetType().Name}] {inner.Message}"); inner = inner.InnerException; }
    resultMessage = $"DocStar upload error: {string.Join(" --> ", messages)}";
    return;
}

// ================================================================
// STEP 4: Link image to each Sales Order via GetNewOrderHedAttch
// ================================================================
List<string> successOrders = new List<string>();
List<string> failedOrders  = new List<string>();
int orderIndex = 1;

foreach (int orderNum in orderNums)
{
    try
    {
        var orderHedRow = (from oh in Db.OrderHed
                           where oh.Company == company
                              && oh.OrderNum == orderNum
                           select oh).FirstOrDefault();

        if (orderHedRow == null)
        {
            failedOrders.Add($"{orderNum} (OrderHed record not found)");
            orderIndex++;
            continue;
        }

        // ContextFactory.CreateContext() takes no arguments
        // ServiceRenderer.GetService() requires IceDataContext + ignoreFacade bool
        using (var iceCtx = Ice.Services.ContextFactory.CreateContext<Ice.IceDataContext>())
        using (var soSvc = Ice.Assemblies.ServiceRenderer
                               .GetService<Erp.Contracts.SalesOrderSvcContract>(iceCtx, false))
        {
            var soDS = soSvc.GetByID(orderNum);

            if (soDS == null || soDS.OrderHed.Count == 0)
            {
                failedOrders.Add($"{orderNum} (Sales Order dataset not found)");
                orderIndex++;
                continue;
            }

            soSvc.GetNewOrderHedAttch(ref soDS, orderNum);

            var newAttach = soDS.OrderHedAttch
                                .FirstOrDefault(r => r.RowMod == "A");

            if (newAttach == null)
            {
                failedOrders.Add($"{orderNum} (no new attachment row found after GetNewOrderHedAttch)");
                orderIndex++;
                continue;
            }

            newAttach.Company   = company;
            newAttach.OrderNum  = orderNum;
            newAttach.DrawDesc  = $"PCID Camera Capture | PCID: {pcidString} " +
                                  $"| Camera: {cameraName} " +
                                  $"| Order: {orderIndex} of {orderNums.Count} " +
                                  $"| {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
            newAttach.DocTypeID = docTypeID;
            newAttach.FileName  = uploadedFilePath;
            newAttach.RowMod    = "A";

            soSvc.Update(ref soDS);

            successOrders.Add(orderNum.ToString());
        }
    }
    catch (Exception ex)
    {
        var inner = ex;
        var messages = new List<string>();
        while (inner != null) { messages.Add($"[{inner.GetType().Name}] {inner.Message}"); inner = inner.InnerException; }
        failedOrders.Add($"{orderNum} (error: {string.Join(" --> ", messages)})");
    }

    orderIndex++;
}

// ================================================================
// STEP 5: Build result summary
// ================================================================
resultMessage = $"PCID: {pcidString} | " +
                $"Camera: {cameraName} | " +
                $"Image: {attachFileName} | " +
                $"Attached to orders: {(successOrders.Count > 0 ? string.Join(", ", successOrders) : "none")}";

if (failedOrders.Count > 0)
    resultMessage += $" | FAILED orders: {string.Join("; ", failedOrders)}";

1. Using this.Db as Ice.IceDataContext silently returned null because the as keyword in C# never throws an exception when a cast fails — it just quietly returns null. Since Db is a LibraryContext and not an IceDataContext, it was passing null into ServiceRenderer the entire time, which is what caused the ArgumentNullException: Value cannot be null (Parameter 'dataContext') error.

2. Every overload we tried for ContextFactory.CreateContext() was wrong. We attempted three different argument combinations — passing Session.CompanyID and Session.UserID, then passing callContextClient on its own, then passing Session on its own — and all of them failed. The diagnostic revealed that the actual method signature takes no arguments at all, meaning the correct call is simply CreateContext<Ice.IceDataContext>() with nothing passed in.

3. ServiceRenderer.GetService() required a second argument that we didn’t know existed. We were only passing the iceCtx context object, but the actual signature requires two parameters — IceDataContext dataContext and Boolean ignoreFacade. Without running the reflection diagnostic there was no way to know ignoreFacade was required, which is why every previous attempt failed even when the context object itself was valid.

You could have just used CallService.