Недавно решал задачу сканирования изображений в Asp.Net приложении. Что из этого получилось я сейчас расскажу.
Введение
Как вы понимаете, работа серверных страниц построена таким образом, что я не могу используя Asp.Net заставить клиента, коим выступает браузер, начать сканирование. Для этого мне нужны какие то инструменты на стороне клиента. Что мы имеет на стороне клиента в стандартном Asp.Net приложении? Только JavaScript. Но при выполнении какого либо кода JS в рамках браузера, мы очень сильно ограничены в функционале из-за соображений безопасности. И обратиться к какому либо устройству мы не можем. Единственное средство, которое поможет нам в этом деле, это RIA. Что мы имеет для реализации RIA приложений:
- Flash
- Silverlight
- JavaFX или просто JavaApplet
- HTML5 + JS
В рамках решаемой задачи были следующие требования:
- Решение кроссплатформенное
- Не дорогое, а лучше бесплатное
В итоге выбор пал на Java. Кроссплатформенное решение с большим количеством опенсурсных библиотек. Да, Silverlight вроде тоже как работает и на маках, но очень туго идет под Moonlight в Mono. Плюс, т.к. разрабатывается корпоративное приложение, на машинах заказчика и так стоит Java. В итоге Flash даже не рассматривали.
Стучимся к сканеру
Итак, теперь нам надо понять как же нам постучаться к сканеру из Java апплета.
TWAIN
Т.к. сканеров очень много, то в итоге было разработано некое обобщенное API, под названием TWAIN. Через TWAIN можно работать со многими девайсами по захвату изображений (камеры, сканеры). Получается, что мне теперь надо найти библиотеку Java, которая выполнит роль обертки для работы с TWAIN. И таких оказалось очень не мало. Нашел я их много, и не только для Java. Но две библиотеки я рассмотрел подробно:
В итоге выбор пал на Morena, больше платформ поддерживается, плюс есть поддержка протокола SANE.
Как лучше?
Первым делом изучим библиотеку Morena. После того как нам стало ясно, что там все достаточно легко, начинаем думать как мы будем работать со сканером. В результате я понимаю следующее:
- Пилить весь фейс на Java не разумно. При потребности в изменении фейса придется переписывать Java библиотеку. Что есть плохо.
- Было бы круто иметь на стороне клиента, в JS окружении, набор функций для работы со сканером и изображениями.
И после этих раздумий появляется следующее решение. Разрабатываем Java Applet (может быть JavaFX приложение), который будет реализовывать некий API для работы со сканером. Благо дергать функции апплета из JS окружения очень легко. Что должен позволять делать апплет (или JavaFX приложение):
- Сканировать
- Выбирать устройство сканирования
- Показывать список доступных устройств
- Отображать полученное изображение - эта часть ложится полностью на апплет, т.к. полученное изображение будет на машине пользователя, и отобразить средствами браузера я его не могу
- Аплоадить изображение на сервер
- При аплоаде передавать любой набор параметров
Это основное. Что еще? Было бы неплохо иметь некий Asp.Net контрол, для работы со сканером на стороне сервера. И который создаст на стороне клиента JS объект (используя MS AJAX), и предоставит набор функций, которые уже будут оборачивать работу с апплетом. Зачем? А для удобства использования на стороне клиента в JS окружении.
Что надо сделать?
Подведем итог. Что мы будем делать?
- JavaApplet или JavaFX приложение, которое будет реализовывать работу со сканерами посредством TWAIN протокола, а также умеющий отобразить полученное изображение и залить по указанному урлу
- Создать Asp.Net контрол, для удобной настройки и компоновки апплета на стороне сервера
- Создать MS AJAX класс на стороне клиента, для удобного взаимодействия с апплетом на стороне клиента
JavaApplet
Выбор в итоге пал на JavaApplet, а не на JavaFX. Потому что JavaFX требует установки на машину клиента дополнительного плагина.
Как мы уже поняли, работа с Morena достаточно легкая. Получили MorenaSource, после чего можно легко получить картинку. Но при разработке апплетов есть ряд проблем связанных с безопасностью. Первое, надо апплет подписать. Но даже если апплет подписать, его надо еще подписать доверенным сертификатом, а это уже сложнее. Поэтому наш апплет всегда будет загружаться после предупреждения. Но, даже подписав апплет, необходимо весь код, который является небезопасным, выполнять в AccessController'e.
Пример:
AccessController.doPrivileged(new PrivilegedAction() { public Object run() { try { return TwainManager.getDefaultSource(); } catch (TwainException ex) { Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, null, ex); } return null; } });
Morena License
Как я уже говорил, Morena не бесплатна. Поэтому необходимо передавать лицензию. Это делать несложно. Получить лицензию можно на сайте Morena. Файл с лицензией называется morena_license.jar, этот файл надо будет добавить в папку со всеми Jar пакетами.
Хостинг апплета
Хостить апплет достаточно легко:
Additional_Params - строка с параметрами, которые будут передаваться при загрузке файла. Upload_Url - куда лить файл и Cookies - строка, содержащая кукисы. Кукисы нужны для аутентификации на стороне сервера при аплоаде файла. Как вы понимаете сейчас поддерживается только аутентификация через кукисы.<applet code="scanner.applet.Scanner.class" width="100%" height="350px" archive="ScannerApplet.jar,morena.jar,morena_license.jar,morena_osx.jar,morena_windows.jar,flexjson-2.1.jar" name="scanner"> <param name="COOKIES" value=""> <param name="UPLOAD_URL" value=""> <param name="ADDITIONAL_PARAMS" value=""> К сожалению ваш браузер не поддерживает Java приложения. Необходимо установить виртуальную машину Java. </applet>
Asp.Net Control и MS AJAX
Для удобства работы в Asp.Net, создадим серверный Ajax контрол. Т.к. это серверный Ajax контрол, то на стороне клиента, мы получим JS объект, связанный с нашим DOM элементом. И в дальнейшем можем легко взаимодействовать с апплетом через JS. Так же используя JS мы делаем постбэк при загрузке картинки. Еще мы можем получить список всех доступных устройств. И т.д.
Используя серверный контрол мы можем указать контролы, по нажатию на которые будет происходить сканирование изображения дефолтовым сканером (ButtonDefaultScanId) или сканирование с выбором сканера (ButtonSelectSourceScanId). А так же можно указать контрол, по нажатию на который, будет происходить загрузка картинки (ButtonUploadImageId). Из основных свойств контрола на стороне сервера стоит выделить:
Cookies - сюда можно добавить кукисы, которые будут переданы при аплоаде файла на сервер. Используется для аутентификации.
JarPath - путь до папки содержащей основные Jar файлы:
- ScannerApplet.jar - реализация апплета
- morena.jar
- morena_license.jar - пакет, который вы получите при загрузке Morenа
- morena_osx.jar
- morena_windows.jar
- flexjson-2.1.jar - пакет с функциями по работе с Json
UploadUrl - URL, по которому загружать файлы. Указывать полностью!
Name - не обязательный параметр, по дефолту 'scanner'.
AutoPostBackOnUpload - делать ли сразу постбэк после загрузки файла. По дефолту true.
Также контрол содержит несколько основных методов:
public void SetDataObject(object dataObject) { JavaScriptSerializer ser = new JavaScriptSerializer(); Data = ser.Serialize(dataObject); } public T GetDataObject<T>() { JavaScriptSerializer ser = new JavaScriptSerializer(); return ser.Deserialize<T>(Data); }
Эти два метода позволяют установить объект с данными и получить объект с данными после того, как файл будет залит.
GenericHandler для сохранения файла
Для сохранения файла удобнее всего реализовать GenericHandler. Мы сможем указывать в качестве URL для загрузки файла путь до этого хэндлера. В хэндлере мы можем сохранить файл используя параметры, которые будет передавать наш апплет. Для удобства я решил создать базовый абстрактный GenericHandler, который будет доставать файл и параметры, после чего передавать их в виртуальный метод. Т.е. переопределив метод в наследнике, мы сразу начинаем работать с файлом и параметрами.
Ну что же, пора переходить к практической части.
JS API
Просто список функций:
checkScanApi() - проверить загружен ли апплет;
getScanApi() - вернет апплет;
scanImageTwainBySource(sourceName) - сканировать используя указанный источник;
scanImageTwainDefault() - сканировать используя дефолтовый сканер;
scanImageTwain() - сканировать с выбором сканера;
getTwainSources() - список доступных сканеров;
scanImage() - сканировать с выбором типа источника (TWAIN, SANE) и выбором самого источника;
showLastImage() - отобразить последнее отсканированное изображение;
uploadLastImageToUrl(url) - передать картинку по урлу и вернет объект с параметрами;
uploadLastImage() - зааплоадить картинку на урл, указанный в ScanControl'e и вернет объект с параметрами;
get_dataObject() - вернет объект с параметрами.
Пример использования
1. Для начала скачиваем архив со всем необходимым.
2. В архиве есть готовая сборка с контролом, Jar файл апплета и тестовый проект.
4. В своем сайте, создаете папку в которую кладутся Jar пакет апплета и Jar пакеты Morena. Должно получиться примерно следующее:
5. Для использования контрола добавляем его на страницу.
5. Для использования контрола добавляем его на страницу.
7. Для передачи параметров в хэндлер загрузки файлов делаем так:<asp:Button runat="server" Text="Сканировать дефолтовым сканером" ID="btnScanDefault"/> <asp:Button runat="server" Text="Сканировать с выбором сканера" ID="btnScanSelect"/> <asp:Button runat="server" Text="Загрузить" ID="btnUpload"/> <kv:ScannerControl runat="server" ID="scanner" OnFileUploaded="scanner_OnFileUploaded" JarPath="~/Resources/" ButtonDefaultScanId="btnScanDefault" ButtonSelectSourceScanId="btnScanSelect" ButtonUploadImageId="btnUpload" AutoPostBackOnUpload="true" Height="600px"/> <asp:Label runat="server" ID="result"></asp:Label>
protected void Page_Load(object sender, EventArgs e) { if(!IsPostBack) { var dto = new ScannerDTO() { Text = "Some params" }; scanner.SetDataObject(dto); } }
8. Что бы апплет прошел аутентификацию через куки, добавляем в Cookies необходимые кукисы для аутентификации.
9. Реализуем HttpHandler для сохранения файла, и указываем путь до него в параметре UploadUrl. Пример реализации (очень простой):
10. Что бы обработать событие загрузки файла используем событие FileUploaded.public class ScannerHandler : ScannerHandlerBase { protected override void FileSave(HttpPostedFile fileForSaving, string parameters) { var dto = GetObjectParameter<ScannerDTO>(parameters); //Используя параметры и файл делаем что нужно с файлом //Подготавливаем и устанавливаем ответ dto.UploadFileSuccess = true; SetResponseData(dto); } }
protected void scanner_OnFileUploaded(object sender, EventArgs e) { var dto = scanner.GetDataObject<ScannerDTO>(); //Здесь уже те данные, которые прислал нам ScannetHandler! if (dto.UploadFileSuccess) result.Text = string.Format("Файл с параметрами: '{0}' успешно обработан!", dto.Text); }
И после сканирования:
Заключение
Как вы понимаете всегда есть что улучшать. Апплет можно сделать стабильнее, добавить функционала по работе с картинками. Можно добавить поддержку прокси. В общем много чего можно еще сделать. Так что если есть желание помочь - пишите.
Если у вас есть вопросы или вы нашли какие то ошибки, тоже пишите.
Ссылки
Спасибо за внимание.
Как у Вас так получается, что Вы используете некоммерческий продукт в коммерческих целях? Могут ли на Вас подать в суд? Есть какие-либо механизмы наказания за подобное в России?
ОтветитьУдалитьmorena_license.jar - пакет, который вы получите при загрузке Morenа. Продукт коммерческий и использовать его можно в коммерческих целях при покупке лицензии. Но есть временная лицензия.
ОтветитьУдалитьНаказать можно легко. :) Подаешь в суд и все.
ОтветитьУдалитьСлова про то, что бесплатно в коммерческих целях и в учебных, никак не говорят о том, что мы используем бесплатно. Конечно моя компания купила лицуху. Но для меня есть возможность использовать это решения в OpenSource проектах.