Пишем надстройку для Excel на .NET с помощью библиотеки Excel-DNA

Excel-DNA

Excel-DNA — библиотека для .NET, с помощью которой можно написать и собрать полностью самодостаточный файл надстройки для Excel с расширением .xll
Этот файл достаточно положить в пользовательскую папку, без прав администратора, и просто включить в настройках Excel.

Такая надстройка будет иметь доступ к COM-модели Excel, C API Excel — позволяет взаимодействовать с интерфейсом программы и рабочими книгами. Сможет реализовать пользовательские формулы и добавить свое меню на интерфейсную ленту Ribbon UI.

В статье я расскажу как написать свою формулу для Excel на C#. Собрать и установить свою первую надстройку.

Подготовка

Создадим новый проект библиотеки классов для версии .NET 6

dotnet new classlib --framework net6.0 -o ExcelAddIn

В файле .csproj нужно изменить значение версии фреймворка на

    net6.0-windows

Теперь установим базовый пакет ExcelDna.AddIn

dotnet add package ExcelDna.AddIn

Писать можно и в Visual Studio, и в VSCode.

Чтобы дебажить код в VSCode нужно создать файл launch.json и в нем изменить две строки, "program" и "args":

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": ".NET Core Launch (console)",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build",
            "program": "C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE",
            "args": [
                "${workspaceFolder}\\bin\\Debug\\net6.0-windows\\ExcelAddIn-AddIn64.xll"
            ],
            "cwd": "${workspaceFolder}",
            "console": "internalConsole",
            "stopAtEntry": false
        },
        {
            "name": ".NET Core Attach",
            "type": "coreclr",
            "request": "attach"
        }
    ]
}

Пути к файлу программы Excel и собранной надстройки нужно поставить свои.

Первая формула

Напишем простую формулу, которая складывает два числа:

global using ExcelDna.Integration; 

namespace ExcelAddIn;  

public static class ExcelFunctions
{
    [ExcelFunction]
    public static double DNASUM(double a, double b)
    {
        return a + b;
    }
}

Все статические методы помеченные атрибутом [ExcelFunction] теперь принимаются Excel как пользовательские формулы.

Нажмем F5, откроется Excel, который уже откроет файл нашей надстройки. Поскольку у нас нет цифровой подписи появится предупреждение.

Извещение системы безопасности

Извещение системы безопасности

Нажимаем «Включить» и проверяем работу нашей формулы

Первая формула работает!

Первая формула работает!

Интерфейс IExcelAddIn

Для работы нашей надстройки может понадобится, чтобы она при открытии и закрытии выполняла какие-либо полезные вещи. Например читала настройки из реестра, подключала базы данных.

Cоздадим класс MyAddIn, реализующий интерфейс IExcelAddIn

public class MyAddIn : IExcelAddIn
{
    public void AutoClose()
    {
        throw new NotImplementedException();
    }  

    public void AutoOpen()
    {
        throw new NotImplementedException();
    }
}

При открытии надстройки будет создан экземпляр класса MyAddIn и выполнен метод AutoOpen()

Делаем формулу запроса курса валют с сайта ЦБ

Напишем теперь формулу, которая делает что-то полезное, например запрашивает курс валют с сайта ЦБ.

Интерфейс для клиента:

namespace ExcelAddIn.Services; 

public interface ICurrencyClient
{
    public Task GetExchangeRate(DateTime date);
}

Класс клиента API сайта ЦБ. Парсим XML с курсами на нужную дату:

using System.Diagnostics;
using System.Text;
using System.Xml.Linq;  

namespace ExcelAddIn.Services;  

public class CurrencyClient : ICurrencyClient
{
    private readonly HttpClient _httpClient;
    private const string _requestAddress = @"https://www.cbr.ru/scripts/XML_daily.asp?date_req=";  

    public CurrencyClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }  

    public async Task GetExchangeRate(DateTime date)
    {
        string request = $"{_requestAddress}{date.Date:dd/MM/yyyy}";
        HttpResponseMessage response = await _httpClient.GetAsync(request);
        try
        {
            response.EnsureSuccessStatusCode();
            var bytes = await response.Content.ReadAsByteArrayAsync();
            var xml = Encoding.GetEncoding(1251).GetString(bytes);
            XElement valCourses = XElement.Parse(xml);  

            decimal? exchangeRate = decimal.Parse(valCourses.Elements("Valute")
                .Where(e => e.Element("Name").Value == "Евро")
                .FirstOrDefault()
                .Element("Value").Value);
            return exchangeRate;
        }

        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
            return null;
        }
    }
}

Добавляем нужные сервисы и создаем провайдер сервисов

using Microsoft.Extensions.DependencyInjection;
using ExcelAddIn.Services;
using System.Text;

public class MyAddIn : IExcelAddIn
{
    public static ServiceProvider ServiceProvider { get; private set; }
    public void AutoOpen()
    {        
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        IServiceCollection Services = new ServiceCollection();
        Services.AddHttpClient()
            .AddSingleton();
        ServiceProvider = Services.BuildServiceProvider();
    }

    public void AutoClose()
    {
    }
}

И, наконец, определяем метод для формулы:

using ExcelAddIn.Services;
using Microsoft.Extensions.DependencyInjection;  

namespace ExcelAddIn;  

public static class ExcelFunctions
{  	
    [ExcelFunction]
    public static object ExchangeRate(double dateField)
    {
        ICurrencyClient currencyClient = MyAddIn.ServiceProvider.GetService();
        DateTime date = dateField == 0 ? DateTime.Today : DateTime.FromOADate(dateField);  

        if (ExcelAsyncUtil.Run(nameof(ExchangeRate), dateField, delegate
        {
            return currencyClient.GetExchangeRate(date)
                .GetAwaiter()
                .GetResult() ?? -1m;
        }) is not decimal requestResult)
        {
            return "Загрузка...";
        }  

        else if (requestResult < 0)
        {
            return ExcelError.ExcelErrorNA;
        }

        else
        {
            return Math.Round(requestResult, 2);
        }
    }
}

Теперь формула =ExchangeRate( ) возвращает сегодняшний курс евро, а если сослаться на ячейку с датой в формате Excel, на нужную дату.

Сборка и установка

Чтобы собрать нашу надстройку в один файл со всеми зависимостями, нужно добавить в файл .csproj строки

  
    
      
    
    
      @(References)
    
  

Теперь делаем

dotnet build

и в папке \bin\Debug\net6.0-windows забираем два собранных файла для 32 и 64-разрядных версий Excel

Эти файлы нужно поместить в папку %AppData%\Microsoft\AddIns и в настройках Excel поставить галочку напротив нашего файла. Готово!

Заключение

В этой статье описывается как написать и собрать простую надстройку для Excel средствами .NET.

Код можно посмотреть по ссылке https://gitea.cebotari.ru/chebser/ExcelAddIn/

© Habrahabr.ru