Barcode Reader SDK is created to assist both expert and entry-level developers to read barcodes from a live camera (C# – MVC). This Barcode Reader SDK also comes with an extensive library to simplify the process of common tasks of barcodes processing in ASP.NET.
Barcode Reader SDK is also loaded with state-of-the-art OCR and image recognition technology. Thus, it can be utilized in reading barcodes from images, scanned documents, and PDFs.
To enable you to utilize this Barcode Reader SDK without spending much time and energy in writing and testing codes, we provide you here with a sample code. All you got to do is copy and paste it into your ASP.NET project and let it do the barcode reading work for you. This ASP.NET code snippet is also available on our GitHub.
A downloadable FREE ByteScout trial version is available on our website. For your convenience, it also comes with programming tutorials to go along with source code samples.
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace BarcodeReader.Controllers { using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Text; using Bytescout.BarCodeReader; public class HomeController : Controller { public ActionResult Index() { return View(); } [HttpPost] public ActionResult Index(HttpPostedFileBase file) { if (file != null) { string name = file.FileName; string path = Path.Combine(Server.MapPath("/"), Path.GetFileName(name)); file.SaveAs(path); ViewBag.Barcodes = Barcode.FindBarcodes(path); ViewBag.Img = name; } return View(); } [HttpPost] public ActionResult UploadHtml5(string image, string type) { try { StringBuilder send = new StringBuilder(); // lock by send variable lock (send) { // convert base64 string from the client side into byte array byte[] bitmapArrayOfBytes = Convert.FromBase64String(image); // convert the byte array into the bitmap Bitmap bp = (Bitmap)Bitmap.FromStream(new MemoryStream(bitmapArrayOfBytes)); bp.Save("c:\\temp\\barcode.jpg",ImageFormat.Jpeg); // new bytescout barcode reader sdk object Reader reader = new Reader(); // get the type to find from user's selection in the combobox reader.BarcodeTypesToFind = Barcode.GetBarcodeTypeToFindFromCombobox(type); // read image to get barcodes reader.ReadFrom(bp); // check if we have got barcodes from the image if (reader.FoundBarcodes != null) { // add each barcode into the string foreach (FoundBarcode barcode in reader.FoundBarcodes) { // add barcodes as text into the output string send.AppendLine(String.Format("{0} : {1}", barcode.Type, barcode.Value)); } } // return the output string return Json(new { d = send.ToString() }); } // end of lock } catch (Exception ex) { // return the exception instead return Json(new { d = ex.Message + "\r\n" + ex.StackTrace }); } } [HttpPost] public ActionResult UploadFlash(string type) { try { String send = ""; System.Drawing.Image originalimg; // read barcode type set MemoryStream log = new MemoryStream(); byte[] buffer = new byte[1024]; int c; // read input buffer with image and saving into the "log" memory stream while ((c = Request.InputStream.Read(buffer, 0, buffer.Length)) > 0) { log.Write(buffer, 0, c); } // create image object originalimg = System.Drawing.Image.FromStream(log); // resample image originalimg = originalimg.GetThumbnailImage(640, 480, new System.Drawing.Image.GetThumbnailImageAbort(() => { return false; }), IntPtr.Zero); Bitmap bp = new Bitmap(originalimg); bp.Save("c:\\temp\\barcode.jpg", ImageFormat.Jpeg); // create bytescout barcode reader object Reader reader = new Reader(); // set barcode type to read reader.BarcodeTypesToFind = Barcode.GetBarcodeTypeToFindFromCombobox(type); // read barcodes from image reader.ReadFrom(bp); // if there are any result then convert them into a single stream if (reader.FoundBarcodes != null) { foreach (FoundBarcode barcode in reader.FoundBarcodes) { // form the output string send = send + (String.Format("{0} : {1}", barcode.Type, barcode.Value)); } } // close the memory stream log.Close(); // dispose the image object originalimg.Dispose(); // write output return Content("<d>" + send + "</d>"); } catch (Exception ex) { // write the exception if any return Content("<d>" + ex + "</d>"); } } } }
Barcode.cs
namespace BarcodeReader { using Bytescout.BarCodeReader; public class Barcode { public static FoundBarcode[] FindBarcodes(string fileName) { Reader reader = new Reader(); // Limit search to 1D barcodes only (exclude 2D barcodes to speed up the search). // Change to reader.BarcodeTypesToFind.SetAll() to scan for all supported 1D and 2D barcodes // or select specific type, e.g. reader.BarcodeTypesToFind.PDF417 = True reader.BarcodeTypesToFind.SetAll(); // reader.MediumTrustLevelCompatible = true; // uncomment this line to enable Medium Trust compatible mode (slows down the recognition process as direct image data access is disabled in Medium Trust mode) reader.ReadFromFile(fileName); return reader.FoundBarcodes; } public static BarcodeTypeSelector GetBarcodeTypeToFindFromCombobox(string barType) { string selectedItemText = barType.Trim().ToUpper(); BarcodeTypeSelector barcodeTypeToScan = new BarcodeTypeSelector(); if (selectedItemText.IndexOf("ALL BARCODES") > -1) barcodeTypeToScan.SetAll(); else if (selectedItemText.IndexOf("ALL LINEAR BARCODES") > -1) barcodeTypeToScan.SetAll1D(); else if (selectedItemText.IndexOf("ALL 2D BARCODES") > -1) barcodeTypeToScan.SetAll2D(); else if (selectedItemText.IndexOf("AZTEC") > -1) barcodeTypeToScan.Aztec = true; else if (selectedItemText.IndexOf("CODABAR") > -1) barcodeTypeToScan.Codabar = true; else if (selectedItemText.IndexOf("CODE 39") > -1) barcodeTypeToScan.Code39 = true; else if (selectedItemText.IndexOf("CODE 128") > -1) barcodeTypeToScan.Code128 = true; else if (selectedItemText.IndexOf("DATAMATRIX") > -1) barcodeTypeToScan.DataMatrix = true; else if (selectedItemText.IndexOf("EAN 13") > -1) barcodeTypeToScan.EAN13 = true; else if (selectedItemText.IndexOf("GS1-128") > -1) barcodeTypeToScan.GS1 = true; else if (selectedItemText.IndexOf("GS1DATABAREXPANDED") > -1) barcodeTypeToScan.GS1DataBarExpanded = true; else if (selectedItemText.IndexOf("GS1DATABAREXPANDEDSTACKED") > -1) barcodeTypeToScan.GS1DataBarExpandedStacked = true; else if (selectedItemText.IndexOf("GS1DATABARLIMITED") > -1) barcodeTypeToScan.GS1DataBarLimited = true; else if (selectedItemText.IndexOf("GS1DATABAROMNIDIRECTIONAL") > -1) barcodeTypeToScan.GS1DataBarOmnidirectional = true; else if (selectedItemText.IndexOf("GS1DATABARSTACKED") > -1) barcodeTypeToScan.GS1DataBarStacked = true; else if (selectedItemText.IndexOf("I2OF5") > -1) barcodeTypeToScan.Interleaved2of5 = true; else if (selectedItemText.IndexOf("PATCH") > -1) barcodeTypeToScan.PatchCode = true; else if (selectedItemText.IndexOf("PDF 417") > -1) barcodeTypeToScan.PDF417 = true; else if (selectedItemText.IndexOf("QR CODE") > -1) barcodeTypeToScan.QRCode = true; else if (selectedItemText.IndexOf("UPCA") > -1) barcodeTypeToScan.UPCA = true; else if (selectedItemText.IndexOf("UPCE") > -1) barcodeTypeToScan.UPCE = true; else if (selectedItemText.IndexOf("MAXICODE") > -1) barcodeTypeToScan.MaxiCode = true; return barcodeTypeToScan; } } }
index.js
var canvas, context, timer; // (HTML5 based camera only) this portion of code will be used when browser supports navigator.getUserMedia ********* */ window.addEventListener("DOMContentLoaded", function () { canvas = document.getElementById("canvasU"), context = canvas.getContext("2d"), video = document.getElementById("video"), videoObj = { "video": true }, errBack = function (error) { console.log("Video capture error: ", error.code); }; // check if we can use HTML5 based camera (through .getUserMedia() function) if (navigator.getUserMedia) { // Standard browser (Opera) // display HTML5 camera document.getElementById("userMedia").style.display = ''; // adding click event to take photo from webcam document.getElementById("snap").addEventListener("click", function () { context.drawImage(video, 0, 0, 640, 480); }); navigator.getUserMedia(videoObj, function (stream) { video.src = window.URL.createObjectURL(stream); video.play(); }, errBack); } // check if we can use HTML5 based camera (through .getUserMedia() function in Webkit based browser) else if (navigator.webkitGetUserMedia) { // WebKit-prefixed for Google Chrome // display HTML5 camera document.getElementById("userMedia").style.display = ''; // adding click event to take photo from webcam document.getElementById("snap").addEventListener("click", function () { context.drawImage(video, 0, 0, 640, 480); }); navigator.webkitGetUserMedia(videoObj, function (stream) { video.src = window.webkitURL.createObjectURL(stream); video.play(); }, errBack); } // check if we can use HTML5 based camera (through .getUserMedia() function in Firefox based browser) else if (navigator.mozGetUserMedia) { // moz-prefixed for Firefox // display HTML5 camera document.getElementById("userMedia").style.display = ''; // adding click event to take photo from webcam document.getElementById("snap").addEventListener("click", function () { context.drawImage(video, 0, 0, 640, 480); }); navigator.mozGetUserMedia(videoObj, function (stream) { video.mozSrcObject = stream; video.play(); }, errBack); } else // if we can not use any of HTML5 based camera ways then we use Flash based camera { // display Flash camera document.getElementById("flashDiv").style.display = ''; document.getElementById("flashOut").innerHTML = (webcam.get_html(640, 480)); } }, false); // (all type of camera) set the default selection of barcode type var selection = "All barcodes (slow)"; // (all type of camera) gets the selection text of "barcode type to scan" combobox function selectType(type) { selection = type.options[type.selectedIndex].text; } // (HTML5 based camera only) // uploads the image to the server function UploadToCloud() { document.getElementById('report').innerHTML = "scanning the current frame......"; context.drawImage(video, 0, 0, 640, 480); $("#upload").attr('disabled', 'disabled'); $("#upload").attr("value", "Uploading..."); var img = canvas.toDataURL('image/jpeg', 0.9).split(',')[1]; // send AJAX request to the server with image data $.ajax({ url: "/Home/UploadHtml5", type: "POST", data: "{ 'image': '" + img + "' , 'type': '" + selection + "'}", contentType: "application/json; charset=utf-8", dataType: "json", // on success output the result returned by the server side (See HTML5Camera.aspx) success: function (data, status) { $("#upload").removeAttr('disabled'); $("#upload").attr("value", "Upload"); if (data.d.length != 0) { var htmlSelect = document.getElementById('OutListBoxHTML5'); var selectBoxOption = document.createElement("option"); selectBoxOption.text = data.d; selectBoxOption.id = "child"; htmlSelect.insertBefore(selectBoxOption, htmlSelect.childNodes[0]); } }, // on error just show the message that no barcodes were found error: function (data) { document.getElementById('report').innerHTML = "no barcode found, scanning....."; $("#upload").removeAttr('disabled'); $("#upload").attr("value", "Upload"); }, async: false }); timer = setTimeout(UploadToCloud, 3000); // will capture new image to detect barcode after 3000 mili second } // (flash based camera only) stop the capturing function stopCall() { document.getElementById('report').innerHTML = "STOPPED Scanning." clearTimeout(timer); } // (flash based camera only) sets the handler for callback completition to output the result webcam.set_hook('onComplete', 'my_completion_handler'); // (flash based camera only) this function will start flash to capture image and send the image to the server for further processing (for IE) function take_snapshot() { // set api to be called by flash camera webcam.set_api_url('/home/UploadFlash?type=' + selection); webcam.set_quality(100); // enable the shutter sound webcam.set_shutter_sound(true); document.getElementById('upload_results').innerHTML = '<h4>Scanning...</h4>'; // capture image from the webcam webcam.snap(); // set timout to capter new image (interval between captures) timer = setTimeout(take_snapshot, 3000); } // (flash based camera only) this one will work after recieving barcode from server // this function writes the output result back to the front page (from server side) function my_completion_handler(msg) { var str = msg; // encode into html compatible string var result = str.match(/<d>(.*?)<\/d>/g).map(function (val) { return val.replace(/<\/?d>/g, ''); }); // add new result into the listbox var htmlSelect = document.getElementById('OutListBoxFlash'); var selectBoxOption = document.createElement("option"); selectBoxOption.text = result; selectBoxOption.id = "child"; htmlSelect.insertBefore(selectBoxOption, htmlSelect.childNodes[0]); // reset webcam and flash to capture new image. this reset process flickers a little webcam.reset(); } // (flash based camera only) stop the scan and set the message on the page function stopScanning() { document.getElementById('upload_results').innerHTML = "STOPPED Scanning." clearTimeout(timer); }
webcam.js
/* JPEGCam v1.0.9 */ /* Webcam library for capturing JPEG images and submitting to a server */ /* Copyright (c) 2008 - 2009 Joseph Huckaby <jhuckaby@goldcartridge.com> */ /* Licensed under the GNU Lesser Public License */ /* http://www.gnu.org/licenses/lgpl.html */ /* Usage: <script language="JavaScript"> document.write( webcam.get_html(320, 240) ); webcam.set_api_url( 'test.php' ); webcam.set_hook( 'onComplete', 'my_callback_function' ); function my_callback_function(response) { alert("Success! PHP returned: " + response); } </script> <a href="javascript:void(webcam.snap())">Take Snapshot</a> */ // Everything is under a 'webcam' Namespace window.webcam = { version: '1.0.9', // globals ie: !!navigator.userAgent.match(/MSIE/), protocol: location.protocol.match(/https/i) ? 'https' : 'http', callback: null, // user callback for completed uploads swf_url: 'webcam.swf', // URI to webcam.swf movie (defaults to cwd) shutter_url: 'shutter.mp3', // URI to shutter.mp3 sound api_url: '', // URL to upload script loaded: false, // true when webcam movie finishes loading quality: 90, // JPEG quality (1 - 100) shutter_sound: true, // shutter sound effect on/off stealth: false, // stealth mode (do not freeze image upon capture) hooks: { onLoad: null, onComplete: null, onError: null }, // callback hook functions set_hook: function(name, callback) { // set callback hook // supported hooks: onLoad, onComplete, onError if (typeof(this.hooks[name]) == 'undefined') return alert("Hook type not supported: " + name); this.hooks[name] = callback; }, fire_hook: function(name, value) { // fire hook callback, passing optional value to it if (this.hooks[name]) { if (typeof(this.hooks[name]) == 'function') { // callback is function reference, call directly this.hooks[name](value); } else if (typeof(this.hooks[name]) == 'array') { // callback is PHP-style object instance method this.hooks[name][0][this.hooks[name][1]](value); } else if (window[this.hooks[name]]) { // callback is global function name window[ this.hooks[name] ](value); } return true; } return false; // no hook defined }, set_api_url: function(url) { // set location of upload API script this.api_url = url; }, set_swf_url: function(url) { // set location of SWF movie (defaults to webcam.swf in cwd) this.swf_url = url; }, get_html: function(width, height, server_width, server_height) { // Return HTML for embedding webcam capture movie // Specify pixel width and height (640x480, 320x240, etc.) // Server width and height are optional, and default to movie width/height if (!server_width) server_width = width; if (!server_height) server_height = height; var html = ''; var flashvars = 'shutter_enabled=' + (this.shutter_sound ? 1 : 0) + '&shutter_url=' + escape(this.shutter_url) + '&width=' + width + '&height=' + height + '&server_width=' + server_width + '&server_height=' + server_height; if (this.ie) { html += '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="'+this.protocol+'://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="'+width+'" height="'+height+'" id="webcam_movie" align="middle"><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="false" /><param name="movie" value="'+this.swf_url+'" /><param name="loop" value="false" /><param name="menu" value="false" /><param name="quality" value="best" /><param name="bgcolor" value="#ffffff" /><param name="flashvars" value="'+flashvars+'"/></object>'; } else { html += '<embed id="webcam_movie" src="'+this.swf_url+'" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="'+width+'" height="'+height+'" name="webcam_movie" align="middle" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="'+flashvars+'" />'; } this.loaded = false; return html; }, get_movie: function() { // get reference to movie object/embed in DOM if (!this.loaded) return alert("ERROR: Movie is not loaded yet"); var movie = document.getElementById('webcam_movie'); if (!movie) alert("ERROR: Cannot locate movie 'webcam_movie' in DOM"); return movie; }, set_stealth: function(stealth) { // set or disable stealth mode this.stealth = stealth; }, snap: function(url, callback, stealth) { // take snapshot and send to server // specify fully-qualified URL to server API script // and callback function (string or function object) if (callback) this.set_hook('onComplete', callback); if (url) this.set_api_url(url); if (typeof(stealth) != 'undefined') this.set_stealth( stealth ); this.get_movie()._snap( this.api_url, this.quality, this.shutter_sound ? 1 : 0, this.stealth ? 1 : 0 ); }, freeze: function() { // freeze webcam image (capture but do not upload) this.get_movie()._snap('', this.quality, this.shutter_sound ? 1 : 0, 0 ); }, upload: function(url, callback) { // upload image to server after taking snapshot // specify fully-qualified URL to server API script // and callback function (string or function object) if (callback) this.set_hook('onComplete', callback); if (url) this.set_api_url(url); this.get_movie()._upload( this.api_url ); }, reset: function() { // reset movie after taking snapshot this.get_movie()._reset(); }, configure: function(panel) { // open flash configuration panel -- specify tab name: // "camera", "privacy", "default", "localStorage", "microphone", "settingsManager" if (!panel) panel = "camera"; this.get_movie()._configure(panel); }, set_quality: function(new_quality) { // set the JPEG quality (1 - 100) // default is 90 this.quality = new_quality; }, set_shutter_sound: function(enabled, url) { // enable or disable the shutter sound effect // defaults to enabled this.shutter_sound = enabled; this.shutter_url = url ? url : 'shutter.mp3'; }, flash_notify: function(type, msg) { // receive notification from flash about event switch (type) { case 'flashLoadComplete': // movie loaded successfully this.loaded = true; this.fire_hook('onLoad'); break; case 'error': // HTTP POST error most likely if (!this.fire_hook('onError', msg)) { alert("JPEGCam Flash Error: " + msg); } break; case 'success': // upload complete, execute user callback function // and pass raw API script results to function this.fire_hook('onComplete', msg.toString()); break; default: // catch-all, just in case alert("jpegcam flash_notify: " + type + ": " + msg); break; } } };
Web.config
<?xml version="1.0"?> <configuration> <configSections> <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" /> <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" /> </sectionGroup> </configSections> <system.web.webPages.razor> <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <pages pageBaseType="System.Web.Mvc.WebViewPage"> <namespaces> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Optimization"/> <add namespace="System.Web.Routing" /> <add namespace="BarcodeReader" /> </namespaces> </pages> </system.web.webPages.razor> <appSettings> <add key="webpages:Enabled" value="false" /> </appSettings> <system.webServer> <handlers> <remove name="BlockViewHandler"/> <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" /> </handlers> </system.webServer> <system.web> <compilation> <assemblies> <add assembly="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> </assemblies> </compilation> </system.web> </configuration>
Web.config
<?xml version="1.0" encoding="utf-8"?> <!-- For more information on how to configure your ASP.NET application, please visit http://go.microsoft.com/fwlink/?LinkId=301880 --> <configuration> <appSettings> <add key="webpages:Version" value="3.0.0.0" /> <add key="webpages:Enabled" value="false" /> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" /> </appSettings> <system.web> <compilation debug="true" targetFramework="4.5.2" /> <httpRuntime targetFramework="4.5.2" /> <httpModules> <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" /> </httpModules> </system.web> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed" /> <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" /> <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" /> <bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" /> <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" /> <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" /> <bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0" /> </dependentAssembly> </assemblyBinding> </runtime> <system.codedom> <compilers> <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" /> <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+" /> </compilers> </system.codedom> <system.webServer> <validation validateIntegratedModeConfiguration="false" /> <modules> <remove name="ApplicationInsightsWebTracking" /> <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" preCondition="managedHandler" /> </modules> </system.webServer> </configuration>