Unable to write response in Excel cells / range using ExcelDNA in C#

355 views
Skip to first unread message

Aniket Karhadkar

unread,
Jul 19, 2022, 3:39:51 AM7/19/22
to Excel-DNA

I'm unable to write my REST API response in Excel using ExcelDNA in C#. It is working fine with reading values from specific column.

xlCell[i, 1] = "TEST"; // fails with System.Runtime.InteropServices.COMException: 'Exception from HRESULT: 0x800A03EC' 

currentSheet.Cells[i, 2].Value = "TEST"; // same issue


Excel reading code --

Excel.Range xlCell; 

Excel.Application xlapp = (Excel.Application)ExcelDnaUtil.Application; 

xlapp.Visible = true;

Worksheet currentSheet; 

xlCell = xlapp.ActiveCell; 

Excel.Workbook wbook = xlapp.ActiveWorkbook; 

currentSheet = wbook.ActiveSheet;


Please note that same code piece is working fine with VSTO addin project (through ribbon button event click) but not in .net class library (for UDF).

But I need UDF support explicitly that's the reason , went ahead with .net class library project with (.xll)

Appreciate if please can someone help.


Thanks,

Aniket Karhadkar


Govert van Drimmelen

unread,
Jul 19, 2022, 8:01:21 AM7/19/22
to exce...@googlegroups.com

Hi Aniket,

 

Excel does not allow you to write to a cell using during a calculation (i.e. from a UDF being evaluated from a cell).

The idea is that your UDF returns a value, which Excel will then display and use as the cell value.

 

So you’d have:

 

public static object GetMyData(string info)

{

    // Calculate or get the result

    var result = info + " – Result”;

    return result;

}

 

And not the macro version where your write to a cell:

 

public static void PutMyDataIntoActive(string info)

{

    // Calculate or get the result

    // Write to a cell using the COM object model

}

 

 

There is some support in Excel-DNA for making async functions, if your back-end calls are slow.

But for a start, I suggest just doing a synchronous call, and caching the result into a local Dictionary to speed things up when recalculating.

Once that works you can refine more.

 

-Govert

--
You received this message because you are subscribed to the Google Groups "Excel-DNA" group.
To unsubscribe from this group and stop receiving emails from it, send an email to exceldna+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/exceldna/7273cea9-fe0f-4bec-930d-517b9c943aebn%40googlegroups.com.

Aniket Karhadkar

unread,
Jul 19, 2022, 8:51:07 AM7/19/22
to Excel-DNA
Hi Govert,

Thanks for the quick response. I tried a work-around as below which seems to be writing the data into cells. 

ExcelAsyncUtil.QueueAsMacro(
              () =>
              {
                  xlCell.Value2 = cellValue;
              });

But for writing huge REST response, this does not seem to be ideal case. Is there a way to write response into Excel cells directly in Excel DNA. Please note, I would be passing an inputs to UDF and expecting same sheet to populate response.

Hope my query is making sense. The reason I go for Excel DNA is for its UDF support which I need instead of VSTO / Excel Addin.

Thanks,
Aniket 

Govert van Drimmelen

unread,
Jul 19, 2022, 11:10:08 AM7/19/22
to exce...@googlegroups.com

Hi Aniket,

 

Writing to a cell from a UDF is a bad idea – one problem is that the active cell might not be the caller of the UDF, another is that the dependency tree the Excel manages can’t track things correctly.

 

If you have a version of Excel which supports Dynamic Arrays – Excel 2022 or an Office 365 subscription – then you can return a large array of data from your UDF and it will expand and work correctly on the sheet.

Aniket Karhadkar

unread,
Jul 20, 2022, 2:30:37 AM7/20/22
to Excel-DNA
Hi Govert,

Yes, I guess so that's a bad idea and not easily manageable. Is there any way you can suggest to have UDF support which will eventually write large REST API data to Excel sheet ? The thing is I need my UDF to be called from Excel formula only so that it can be used across every excel application having my addin.

Thanks,
Aniket

Govert van Drimmelen

unread,
Jul 20, 2022, 9:26:26 AM7/20/22
to exce...@googlegroups.com

Hi Aniket,

 

> Is there any way you can suggest to have UDF support which will eventually write large REST API data to Excel sheet ?

 

If you have a version of Excel which supports Dynamic Arrays – Excel 2022 or an Office 365 subscription – then you can return a large array of data from your UDF and it will expand and work correctly on the sheet.

Kedar Kulkarni

unread,
Jul 20, 2022, 10:12:42 AM7/20/22
to Excel-DNA
Hi Aniket,

Please read Govert's answer first and implement that if all users are on office 365.

For office with no DynamicArray support - you can try the following.

1. Please make sure you write a custom function that solely depends on the parameters of the function. Understand how excel UDFs are written and what ExcelAsyncUtil.Run does.
2. Get the cell where the excel formula is entered using below in the function call and in main thread. Do not access excel application / range from background thread.
var caller = XlCall.Excel(XlCall.xlfCaller) as ExcelReference;
3. Convert the caller to excel range using the method ReferenceToRange explained at https://docs.excel-dna.net/range-parameters/ in QueueAsMacro / main thread context.
4. Create an object array from the data you receive from your service and write all data at once just below the formula cell. (do not overwrite the formula cell) .
5. below code is copied from https://stackoverflow.com/questions/536636/write-array-to-excel-range and is modified a bit to use excelreference. I have not tested the code myself.

        [ExcelFunction("GetMyData")]
        public static object GetMyData(string info)
        {
            var caller = XlCall.Excel(XlCall.xlfCaller) as ExcelReference;

            // should be queued in a background thread
            return ExcelAsyncUtil.Run("GetMyData", info, () => GetDataAndRenderTask(info, caller));
        }

        private static object GetDataAndRenderTask(string info, ExcelReference caller)
        {
            var dt = GetDataFromRestApi(info);
            ExcelAsyncUtil.QueueAsMacro(()
                =>
            {
                var arr = new object[dt.Rows.Count, dt.Columns.Count];
                for (var r = 0; r < dt.Rows.Count; r++)
                {
                    var dr = dt.Rows[r];
                    for (var c = 0; c < dt.Columns.Count; c++)
                    {
                        arr[r, c] = dr[c];
                    }
                }


                var c1 = ReferenceToRange(caller).Offset[1, 0];
                var ws = c1.Worksheet;
                Excel.Range c2 = ws.Cells[c1.Row + dt.Rows.Count - 1, dt.Columns.Count];
                var range = ws.Range[c1, c2];
                range.Value = arr;

            });

            return DateTime.Now.ToString("dd-MMM-yyyy hh:mm:ss"); // current time (can return cache creation time) as a return value so that you know when this function last calculated / hit the service.
        }

        private static Excel.Range ReferenceToRange(ExcelReference caller)
        {
               // https://docs.excel-dna.net/range-parameters/
                var app = (Application)ExcelDnaUtil.Application;
                var refText = (string)XlCall.Excel(XlCall.xlfReftext, caller, true);
                var range = app.Range[refText];
                return range;
        }

        private static DataTable GetDataFromRestApi(string info)
        {
        // get data from rest API / from cache if already called once and cache hasnt expired. Return date time of cache creation from here as an out parameter.
            throw new NotImplementedException();
        }


6. I have used a similar technique a lot of times and it works - but it comes with some careful planning - It is possible that excel could calculate your formula again and again if there are volatile formulas in the precedents so you might need caching done for the web service calls. Also, it is possible that user gives hundreds of functions and your server may suddenly receive too many rest api service calls so you must make sure that your excel code would queue the requests in such a way that it does not overwhelm your backend server and server also has a mechanism to expect REST api calls from multiple users and it is able to load balance.

thanks
Kedar

Aniket Karhadkar

unread,
Jul 21, 2022, 4:14:57 AM7/21/22
to Excel-DNA
Thanks Kedar for detailed response. That's exactly what I used in my use case. So key takeaway is  'ExcelAsyncUtil.QueueAsMacro' I feel (for .net / Excel DNA newbie) which of course has to be used with careful measures.
Reply all
Reply to author
Forward
0 new messages