How do I upgrade ChromeDriver on a C# Selenium project? - c#

I have a C#-based Selenium test successfully running in a Github Actions container. It worked until recently, where presumably Github upgraded the ChromeDriver version of the ubuntu-latest image to 87.
After this(started failing on Dec. 2nd), I got the following:
Starting ChromeDriver 85.0.4183.87 (<uuid>-refs/branch-heads/4183#{#1689}) on port 35539
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.
X SomeTestThatShouldpass [5s 387ms]
Error Message:
System.InvalidOperationException : session not created: This version of ChromeDriver only supports Chrome version 85 (SessionNotCreated)
Following this, I upgraded the ChromeDriver dependency in my *.csproj:
Before:
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="85.0.4183.8700" />
After:
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="87.0.4280.8800" />
However, after bumping the version I'm left with the very same error as above. It still starts ChromeDriver 85, and still brings forth an error in relation to version mismatches.
Apparently, bumping the dependency and building the .NET application again did not upgrade the ChromeDriver version.
How do I upgrade the ChromeDriver version from 85 to 87?

class Program
{
static void Main(string[] args)
{
string ChromeDriverPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
string ChromDriverVersion = GetChromDriverVersion(ChromeDriverPath);
string ChromeWebVersion = GetWebChromeVersion();
if (ChromDriverVersion != ChromeWebVersion)
{
var urlToDownload = GetURLToDownload(ChromeWebVersion);
KillAllChromeDriverProcesses();
DownloadNewVersionOfChrome(urlToDownload, ChromeDriverPath);
string extract = ExtructZip(ChromeDriverPath);
}
}
static string GetChromDriverVersion(string ChromeDriverePath)
{
string driverversion = "";
if (File.Exists(ChromeDriverePath + "\\chromedriver.exe"))
{
IWebDriver driver = new ChromeDriver(ChromeDriverePath);
ICapabilities capabilities = ((ChromeDriver)driver).Capabilities;
driverversion = ((capabilities.GetCapability("chrome") as Dictionary<string, object>)["chromedriverVersion"]).ToString().Split(' ').First();
driver.Dispose();
}
else
{
Console.WriteLine("ChromeDriver.exe missing !!");
}
return driverversion;
}
static string GetChromeWebPath()
{
//Path originates from here: https://chromedriver.chromium.org/downloads/version-selection
using (RegistryKey key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe"))
{
if (key != null)
{
Object o = key.GetValue("");
if (!String.IsNullOrEmpty(o.ToString()))
{
return o.ToString();
}
else
{
throw new ArgumentException("Unable to get version because chrome registry value was null");
}
}
else
{
throw new ArgumentException("Unable to get version because chrome registry path was null");
}
}
}
static string GetWebChromeVersion()
{
string productVersionPath = GetChromeWebPath();
if (String.IsNullOrEmpty(productVersionPath))
{
throw new ArgumentException("Unable to get version because path is empty");
}
if (!File.Exists(productVersionPath))
{
throw new FileNotFoundException("Unable to get version because path specifies a file that does not exists");
}
var versionInfo = FileVersionInfo.GetVersionInfo(productVersionPath);
if (versionInfo != null && !String.IsNullOrEmpty(versionInfo.FileVersion))
{
return versionInfo.FileVersion;
}
else
{
throw new ArgumentException("Unable to get version from path because the version is either null or empty: " + productVersionPath);
}
}
static string GetURLToDownload(string version)
{
if (String.IsNullOrEmpty(version))
{
throw new ArgumentException("Unable to get url because version is empty");
}
//URL's originates from here: https://chromedriver.chromium.org/downloads/version-selection
string html = string.Empty;
string urlToPathLocation = #"https://chromedriver.storage.googleapis.com/LATEST_RELEASE_" + String.Join(".", version.Split('.').Take(3));
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(urlToPathLocation);
request.AutomaticDecompression = DecompressionMethods.GZip;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (Stream stream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(stream))
{
html = reader.ReadToEnd();
}
if (String.IsNullOrEmpty(html))
{
throw new WebException("Unable to get version path from website");
}
return "https://chromedriver.storage.googleapis.com/" + html + "/chromedriver_win32.zip";
}
static void KillAllChromeDriverProcesses()
{
var processes = Process.GetProcessesByName("chromedriver");
foreach (var process in processes)
{
try
{
process.Kill();
}
catch
{
}
}
}
static void DownloadNewVersionOfChrome(string urlToDownload,string ChromDriverPath)
{
if (String.IsNullOrEmpty(urlToDownload))
{
throw new ArgumentException("Unable to get url because urlToDownload is empty");
}
using (var client = new WebClient())
{
if (File.Exists(ChromDriverPath + "\\chromedriver.zip"))
{
File.Delete(ChromDriverPath + "\\chromedriver.zip");
}
client.DownloadFile(urlToDownload, "chromedriver.zip");
if (File.Exists(ChromDriverPath + "\\chromedriver.zip") && File.Exists(ChromDriverPath + "\\chromedriver.exe"))
{
File.Delete(ChromDriverPath + "\\chromedriver.exe");
}
}
}
static string ExtructZip(string ChromeDriverPath)
{
string errorMsg = "";
string zipFileName = "";
try
{
zipFileName= System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + "\\chromedriver.zip";
string orgFileName = "";
using (ZipFile zip = ZipFile.Read(zipFileName))
{
foreach (ZipEntry e in zip)
{
e.Extract(ChromeDriverPath, ExtractExistingFileAction.OverwriteSilently);
orgFileName = e.FileName;
}
}
File.Delete(zipFileName);
errorMsg = "Done";
}
catch (Exception ex)
{
errorMsg = "ExtructZip file " + zipFileName.Split('\\').Last() + " - " + ex.Message;
}
return errorMsg;
}
}
}

https://github.com/rosolko/WebDriverManager.Net
Use WebDriver manager to automatically install the version , you want
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
namespace Test
{
[TestFixture]
public class Tests
{
private IWebDriver _webDriver;
[SetUp]
public void SetUp()
{
new DriverManager().SetUpDriver(new ChromeConfig(), "2.25");
_webDriver = new ChromeDriver();
}
[TearDown]
public void TearDown()
{
_webDriver.Quit();
}
[Test]
public void Test()
{
_webDriver.Navigate().GoToUrl("https://www.google.com");
Assert.True(_webDriver.Title.Contains("Google"));
}
}
}

Related

How to use C# HTTPClient with Windows Credentials

I wrote a console app that will read a text file of links and test them using the HttpClient class to check if the links exist. It works wonderfully at home when tested against common links such as google.com.
When I run the app at under my work intranet, though, I get "Forbidden" errors when it checks links on our company Sharepoint and "Unauthorized" errors when it checks links on the Azure Website. My hope was that running it under my authorized Windows desktop PC would be all I needed for it to work, but nope.
Any hints one how to pass my Network credentials when I access the links with HttpClient?
EDIT: Added code to handle passed console argument (string of authentication cookies)
using System;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;
namespace linkbot
{
class Program
{
private static async Task ProcessRepositories(string the_url, string the_cookies)
{ int the_index=the_url.IndexOf("/",9);
string base_string=the_url;
string the_rest="/";
if (the_index>=0)
{
base_string=the_url.Substring(0,the_index);
the_rest=the_url.Substring(the_index);
}
try {
var baseAddress = new Uri(base_string);
using (var handler = new HttpClientHandler { UseCookies = false })
using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
{
var message = new HttpRequestMessage(HttpMethod.Get, the_rest);
message.Headers.Add("Cookie", the_cookies);
var result = await client.SendAsync(message);
result.EnsureSuccessStatusCode();
}
Write("\n" + the_url + " - WORKED!");
}
catch(Exception e )
{
Write("\nFailed: " + the_url + "||||||" +e.ToString() );
//throw e;
}
}
static async Task Main(string[] args)
{
if (args.Length<2){
Console.Write("\n###################\nLinkChecker by Sean J. Miller 2022\n\nusage:\n linkchecker.exe <linkfile> <cookies>\nwhere <linkfile> contains a text file of fully specified links (URLs) with one link per line. Example, https://www.google.com\n\nAn output file is generated titled brokenlinks.txt in the same directory where LinkChecker was launched. <cookies> should be a string such as \"cookie1=value1;cookie2=value2;cookie3=value3;\"\n###################\n\n\n");
return;
}
System.IO.File.Delete("brokenlinks.txt");
System.IO.File.WriteAllText("brokenlinks.txt", "Last Check Started " + (DateTime.Now).ToString());
int counter=0;
int total_lines=TotalLines(#args[0]);
Console.Write("Started " + (DateTime.Now).ToString() + "\n");
foreach (string line in System.IO.File.ReadLines(#args[0]))
{
Console.Write("Processing Link " + (++counter) + "/" + total_lines + "\n");
await ProcessRepositories(line, args[1]);
}
Console.Write("Finished " + (DateTime.Now).ToString() + "\n");
Write("\n");
}
static void Write(string the_text)
{
//Console.Write(the_text);
try
{
System.IO.File.AppendAllText("brokenlinks.txt", the_text);
}
catch (Exception ex)
{
throw ex;
}
}
static int TotalLines(string filePath)
{
using (StreamReader r = new StreamReader(filePath))
{
int i = 0;
while (r.ReadLine() != null) { i++; }
return i;
}
}
}
}
You want to set UseDefaultCredentials to true to use the current logged-on user credentials in your request. You can do that by instantiating your HttpClient like so:
private static readonly HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });

Problem with SQLite in iOS App with Xamarin forms

I have created my app in Visual Studio 2019 for Android and iOS.
If I DEBUG my iOS app it is working perfectly as it should. It is debugged with iPhoneSimulator.
But if I rollout my RELEASE and test in on for example iPad, it seems that it can not find the SQLite DB whitch is stored like this:
string dbpath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "MyDatabase.db");
Is there a problem maybe with the SpecialFolder 'Personal' on iOS?
This code will be processes during start up:
if (!File.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "MyDatabase.db")))
{
DataHelper.Database db = new DataHelper.Database();
if (!db.CreateDatabase())
{
DisplayAlert("Error", "DB could not be loaded !", "OK");
}
}
this is my DatabaseHelper class:
namespace VoTa.DataHelper
{
public class Database
{
readonly string dbpath = Data.GetPath();
private static readonly string ftpurl = "myURL";
public bool CreateDatabase()
{
try
{
var connection = new SQLiteConnection(dbpath);
connection.CreateTable<Belohnungen>();
connection.CreateTable<Vokabeln>();
connection.CreateTable<EigeneVokabel>();
connection.Close();
return true;
}
catch (SQLiteException)
{
//Log.Info("SQLite Fehler!", ex.Message);
return false;
}
}
public void InsertIntoVokabeln(string dateiname)
{
String voakbelURL = ftpurl + dateiname;
string id;
string fremdsprache;
string deutsch;
string info1;
string info2;
var connection = new SQLiteConnection(dbpath);
connection.Query<Vokabeln>("Delete from Vokabeln");
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(voakbelURL);
var vList = xmldoc.SelectNodes("/Vokabel/VokabelModel");
try
{
foreach (XmlNode node in vList)
{
id = node["Id"].InnerText;
fremdsprache = node["fremdsprache"].InnerText;
deutsch = node["deutsch"].InnerText;
info1 = "";
info2 = "";
if (node["info1"] != null)
{
info1 = node["info1"].InnerText;
}
if (node["info2"] != null)
{
info1 = node["info2"].InnerText;
}
Vokabeln vokabel = new Vokabeln
{
Fremdsprache = fremdsprache,
Deutsch = deutsch,
Info1 = info1,
Info2 = info2
};
connection.Insert(vokabel);
}
}
catch (Exception)
{
return;
}
finally
{
connection.Close();
}
}
public void InsertIntoBelohnungen(string dateiname)
{
String belohunngURL = ftpurl + dateiname;
string id;
string beloh;
string info1;
string info2;
var connection = new SQLiteConnection(dbpath);
connection.Query<Belohnungen>("Delete from Belohnungen");
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(belohunngURL);
var vList = xmldoc.SelectNodes("/Belohnung/BelohnungModel");
try
{
foreach (XmlNode node in vList)
{
id = node["Id"].InnerText;
beloh = node["Belohnung"].InnerText;
info1 = "";
info2 = "";
if (node["info1"] != null)
{
info1 = node["info1"].InnerText;
}
if (node["info2"] != null)
{
info1 = node["info2"].InnerText;
}
Belohnungen belohnung = new Belohnungen
{
Belohnung = beloh,
Info1 = info1,
Info2 = info2
};
connection.Insert(belohnung);
}
}
catch (Exception)
{
return;
}
finally
{
connection.Close();
}
}
It is working on iPhoneSimulator but not in "real".
Add DB file in the AppDelegate.cs file like:
string sqliteFilename = "MyDatabase.db";
string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal),"..","Library");
string dbpath = Path.Combine(folderPath, sqliteFilename);
LoadApplication(new App(dbpath));
My App.xaml.cs:
public App(string dbpath)
{
InitializeComponent();
Data.SetPath(dbpath);
MainPage = new NavigationPage(new MainPage());
}
My MainView:
switch (Device.RuntimePlatform)
{
case Device.iOS:
if (!File.Exists(Data.GetPath()))
{
DataHelper.Database db = new DataHelper.Database();
if (!db.CreateDatabase())
{
DisplayAlert("Fehler", "Datenbank konnte nicht erstellt werden !", "OK");
}
}
break;
case Device.Android:
if (!File.Exists(Data.GetPath()))
{
DataHelper.Database db = new DataHelper.Database();
if (!db.CreateDatabase())
{
DisplayAlert("Fehler", "Datenbank konnte nicht erstellt werden !", "OK");
}
}
break;
}
This does not gave me exception.
I get this exception:
private void Starten()
{
DataHelper.Database db = new DataHelper.Database();
try
{
List<Vokabeln> myvokabel = db.SelectTableVokabeln(anzahlVokabel).ToList();
frage.Clear();
antwort.Clear();
info1.Clear();
info2.Clear();
belohnung.Clear();
int test = myvokabel.Count();
foreach (var item in myvokabel)
{
if (richtung == 1)
{
frage.Add(item.Deutsch);
antwort.Add(item.Fremdsprache);
}
else
{
frage.Add(item.Fremdsprache);
antwort.Add(item.Deutsch);
info1.Add(item.Info1);
info2.Add(item.Info2);
}
}
List<Belohnungen> mybelohnung = db.SelectTableBelohnungen().ToList();
foreach (var bel in mybelohnung)
{
belohnung.Add(bel.Belohnung);
}
//DisplayAlert("Info", "Vokabeln und Belohnungen wurden geladen!", "OK");
}
catch (Exception)
{
//Log.Info("interner Fehler!", ex.Message);
DisplayAlert("Fehler", "Es ist ein Fehler aufgetreten", "OK");
}
}
Database.cs
public List<Vokabeln> SelectTableVokabeln(int anzahl)
{
try
{
var connection = new SQLiteConnection(dbpath);
var abfrage = connection.Query<Vokabeln>(string.Format("SELECT ID, Fremdsprache, Deutsch, Info1, Info2 FROM Vokabeln ORDER BY RANDOM() LIMIT {0}", anzahl));
//return connection.Table<Vokabeln>().ToList();
return abfrage;
}
catch (SQLiteException)
{
//Log.Info("SQLite Fehler!", ex.Message);
return null;
}
}
public List<Belohnungen> SelectTableBelohnungen()
{
try
{
var connection = new SQLiteConnection(dbpath);
return connection.Table<Belohnungen>().ToList();
}
catch (SQLiteException)
{
//Log.Info("SQLite Fehler!", ex.Message);
return null;
}
}
Any help?
Thank you.
I remember having had similar issues when trying to handle the file in shared code.
Strangely enough, creating an interface in my shared code with a native implementation on both iOS and Android resolved the issue.
Here is my code to retrieve the connection from within my iOS project in a native implementation of an interface from the shared project called IFileService:
public SQLite.SQLiteConnection GetDbConnection()
{
try
{
string sqliteFilename = "MyAppDb.db3";
string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal) + sqliteFilename);
SQLiteConnection conn = new SQLite.SQLiteConnection(path, SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.NoMutex);
return conn;
}
catch (Exception ex)
{
Logger.Fatal(ex, "An exception occurred at GetConnection");
Logger.Trace("Stack Trace: {0}", ex.StackTrace);
}
return null;
}
Also I would suggest that you don't access your connection the way you are doing it right now, as
var connection = new SQLiteConnection(dbpath);
will keep your connection open, which will most probably cause problems, if you want to use another connection.
In order to ensure that your connection is properly closed, I would highly recommend to manage it in a using block:
using(SQLiteConnection connection = iFileService.GetDbConnection())
{
connection.CreateTable<Belohnungen>();
connection.CreateTable<Vokabeln>();
connection.CreateTable<EigeneVokabel>();
}
This way the connection will be properly closed and disposed when your database access is finished. Also ensure that you always access your database connections that way whenever you get or store data in SQLite.
P.S.: if you are using a newer version of sqlite, you might need to construct your database with
SQLiteConnection connection = new SQLiteConnection(dbpath);
instead of the way I am creating it.

The type or namespace name 'BluetoothAddress' could not be found

I use vs 2017 and windows 7.
I have installed 32feet.NET following this: Looking to write Bluetooth 'hcitool' equivelant in Windows.
But I get the errors:
The type or namespace name 'BluetoothAddress' could not be found
The type or namespace name 'BluetoothClient' could not be found
The type or namespace name 'BluetoothSecurity' could not be found
The type or namespace name 'BluetoothDeviceInfo' could not be found
The type or namespace name 'ServiceRecord' could not be found
I have successfully installed InTheHand.Devices.Bluetooth and There is no warning in the using sentence so the namespace is successfully referenced.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using InTheHand.Devices.Bluetooth;
using InTheHand.Networking;
using InTheHand.Networking.Sockets;
using InTheHand;
using System.Diagnostics;
using System.Net.Sockets;
namespace hcitool
{
partial class Program
{
static bool infoRatherThanName;
static BluetoothAddress _searchAddress;
static int Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Please specify command.");
return 2;
}
var cmd = args[0];
switch (cmd)
{
case "name":
infoRatherThanName = false;
break;
case "info":
infoRatherThanName = true;
break;
//-
case "dev":
return ShowRadios();
//case "auth":
// return CauseAuth(GETADDRESS());
default:
throw new NotImplementedException("Command: '" + cmd + "'");
}
if (args.Length < 2)
{
Console.WriteLine("Please specify device address.");
return 2;
}
var addrS = args[1];
_searchAddress = BluetoothAddress.Parse(addrS);
//
//var dev = new BluetoothDeviceInfo(_searchAddress);
var dev = new BluetoothDevice();
bool isInRange = GetCanConnectTo(dev);
if (isInRange)
{
PrintDevice(dev);
}
else
{
Console.WriteLine("Can't see that device.");
}
//
Console.WriteLine("simple");
return Simple();
//return Fancier();
}
//----
private static int ShowRadios()
{
BluetoothRadio[] list;
try
{
list = BluetoothRadio.AllRadios;
}
catch (Exception)
{
return 1;
}
Debug.Assert(list.Length != 0, "Expect zero radios case to raise an error.");
foreach (var curR in list)
{
Console.WriteLine("* {0} '{1}'", curR.LocalAddress, curR.Name);
Console.WriteLine("{0}", curR.SoftwareManufacturer);
Console.WriteLine("{0}", curR.Manufacturer);
Console.WriteLine("{0}", curR.Mode);
}//for
return 0;
}
private static int CauseAuth(BluetoothAddress addr)
{
BluetoothSecurity.PairRequest(addr, null);
return 0;
}
//----
static int Simple()
{
BluetoothDeviceInfo[] devices;
BluetoothDeviceInfo foundDev = null;
var cli = new BluetoothClient();
// Fast: Remembered/Authenticated
devices = cli.DiscoverDevices(255, true, true, false, false);
SimpleCheckDevice(devices, ref foundDev);
if (foundDev == null)
{
// Slow: Inquiry
cli.DiscoverDevices(255, false, false, true, false);
SimpleCheckDevice(devices, ref foundDev);
}
//
if (foundDev != null)
{
return 0;
}
else
{
return 1;
}
}
private static void SimpleCheckDevice(IEnumerable<BluetoothDeviceInfo> devices,
ref BluetoothDeviceInfo foundDev)
{
foreach (var cur in devices)
{
if (cur.DeviceAddress == _searchAddress)
{
foundDev = cur;
PrintDevice(cur);
}
}//for
}
private static void PrintDevice(BluetoothDeviceInfo cur)
{
Console.WriteLine("* Found device: '{0}' ", cur.DeviceName);
if (infoRatherThanName)
{
try
{
var vs = cur.GetVersions();
Console.WriteLine(vs.Manufacturer);
Console.WriteLine(vs.LmpVersion);
Console.WriteLine(vs.LmpSubversion);
Console.WriteLine(vs.LmpSupportedFeatures);
}
catch (Exception ex)
{
Console.WriteLine("Failed to get remote device versions info: "
+ ex.Message);
}
}
}
//----
private static bool GetCanConnectTo(BluetoothDeviceInfo device)
{
bool inRange;
Guid fakeUuid = new Guid("{F13F471D-47CB-41d6-9609-BAD0690BF891}");
try
{
ServiceRecord[] records = device.GetServiceRecords(fakeUuid);
Debug.Assert(records.Length == 0, "Why are we getting any records?? len: " + records.Length);
inRange = true;
}
catch (SocketException)
{
inRange = false;
}
return inRange;
}
}
}
The BluetoothAddress is in the 32feet.NET nuget package.
You're using the preview version of InTheHand which is missing the BluetoothAddress, the BluetoothRadio, BluetoothDeviceInfo and others.
I think you're referencing the wiki for the 32feet.NET, which is the legacy nuget package.
If you install 32feet.NET v3.5.0.0, you will find all your missing bindings.
Adding 32feet.NET and changing a couple of things:
var dev = new BluetoothDevice();
//this becomes this:
var dev = new BluetoothDeviceInfo(_searchAddress);
it will compile succesfully.
Try to add the following namespaces to your code:
using InTheHand.Net;
using InTheHand.Net.Sockets;
using InTheHand.Net.Bluetooth;

OpenXml Excel: throw error in any word after mail address

I read Excel files using OpenXml. all work fine but if the spreadsheet contains one cell that has an address mail and after it a space and another word, such as:
abc#abc.com abc
It throws an exception immediately at the opening of the spreadsheet:
var _doc = SpreadsheetDocument.Open(_filePath, false);
exception:
DocumentFormat.OpenXml.Packaging.OpenXmlPackageException
Additional information:
Invalid Hyperlink: Malformed URI is embedded as a
hyperlink in the document.
There is an open issue on the OpenXml forum related to this problem: Malformed Hyperlink causes exception
In the post they talk about encountering this issue with a malformed "mailto:" hyperlink within a Word document.
They propose a work-around here: Workaround for malformed hyperlink exception
The workaround is essentially a small console application which locates the invalid URL and replaces it with a hard-coded value; here is the code snippet from their sample that does the replacement; you could augment this code to attempt to correct the passed brokenUri:
private static Uri FixUri(string brokenUri)
{
return new Uri("http://broken-link/");
}
The problem I had was actually with an Excel document (like you) and it had to do with a malformed http URL; I was pleasantly surprised to find that their code worked just fine with my Excel file.
Here is the entire work-around source code, just in case one of these links goes away in the future:
void Main(string[] args)
{
var fileName = #"C:\temp\corrupt.xlsx";
var newFileName = #"c:\temp\Fixed.xlsx";
var newFileInfo = new FileInfo(newFileName);
if (newFileInfo.Exists)
newFileInfo.Delete();
File.Copy(fileName, newFileName);
WordprocessingDocument wDoc;
try
{
using (wDoc = WordprocessingDocument.Open(newFileName, true))
{
ProcessDocument(wDoc);
}
}
catch (OpenXmlPackageException e)
{
e.Dump();
if (e.ToString().Contains("The specified package is not valid."))
{
using (FileStream fs = new FileStream(newFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
UriFixer.FixInvalidUri(fs, brokenUri => FixUri(brokenUri));
}
}
}
}
private static Uri FixUri(string brokenUri)
{
brokenUri.Dump();
return new Uri("http://broken-link/");
}
private static void ProcessDocument(WordprocessingDocument wDoc)
{
var elementCount = wDoc.MainDocumentPart.Document.Descendants().Count();
Console.WriteLine(elementCount);
}
}
public static class UriFixer
{
public static void FixInvalidUri(Stream fs, Func<string, Uri> invalidUriHandler)
{
XNamespace relNs = "http://schemas.openxmlformats.org/package/2006/relationships";
using (ZipArchive za = new ZipArchive(fs, ZipArchiveMode.Update))
{
foreach (var entry in za.Entries.ToList())
{
if (!entry.Name.EndsWith(".rels"))
continue;
bool replaceEntry = false;
XDocument entryXDoc = null;
using (var entryStream = entry.Open())
{
try
{
entryXDoc = XDocument.Load(entryStream);
if (entryXDoc.Root != null && entryXDoc.Root.Name.Namespace == relNs)
{
var urisToCheck = entryXDoc
.Descendants(relNs + "Relationship")
.Where(r => r.Attribute("TargetMode") != null && (string)r.Attribute("TargetMode") == "External");
foreach (var rel in urisToCheck)
{
var target = (string)rel.Attribute("Target");
if (target != null)
{
try
{
Uri uri = new Uri(target);
}
catch (UriFormatException)
{
Uri newUri = invalidUriHandler(target);
rel.Attribute("Target").Value = newUri.ToString();
replaceEntry = true;
}
}
}
}
}
catch (XmlException)
{
continue;
}
}
if (replaceEntry)
{
var fullName = entry.FullName;
entry.Delete();
var newEntry = za.CreateEntry(fullName);
using (StreamWriter writer = new StreamWriter(newEntry.Open()))
using (XmlWriter xmlWriter = XmlWriter.Create(writer))
{
entryXDoc.WriteTo(xmlWriter);
}
}
}
}
}
The fix by #RMD works great. I've been using it for years. But there is a new fix.
You can see the fix here in the changelog for issue #793
Upgrade OpenXML to 2.12.0.
Right click solution and select Manage NuGet Packages.
Implement the fix
It is helpful to have a unit test. Create an excel file with a bad email address like test#gmail,com. (Note the comma instead of the dot).
Make sure the stream you open and the call to SpreadsheetDocument.Open allows Read AND Write.
You need to implement a RelationshipErrorHandlerFactory and use it in the options when you open. Here is the code I used:
public class UriRelationshipErrorHandler : RelationshipErrorHandler
{
public override string Rewrite(Uri partUri, string id, string uri)
{
return "https://broken-link";
}
}
Then you need to use it when you open the document like this:
var openSettings = new OpenSettings
{
RelationshipErrorHandlerFactory = package =>
{
return new UriRelationshipErrorHandler();
}
};
using var document = SpreadsheetDocument.Open(stream, true, openSettings);
One of the nice things about this solution is that it does not require you to create a temporary "fixed" version of your file and it is far less code.
Unfortunately solution where you have to open file as zip and replace broken hyperlink would not help me.
I just was wondering how it is posible that it works fine when your target framework is 4.0 even if your only installed .Net Framework has version 4.7.2.
I have found out that there is private static field inside System.UriParser that selects version of URI's RFC specification. So it is possible to set it to V2 as it is set for .net 4.0 and lower versions of .Net Framework. Only problem that it is private static readonly.
Maybe someone will want to set it globally for whole application. But I wrote UriQuirksVersionPatcher that will update this version and restore it back in Dispose method. It is obviously not thread-safe but it is acceptable for my purpose.
using System;
using System.Diagnostics;
using System.Reflection;
namespace BarCap.RiskServices.RateSubmissions.Utility
{
#if (NET20 || NET35 || NET40)
public class UriQuirksVersionPatcher : IDisposable
{
public void Dispose()
{
}
}
#else
public class UriQuirksVersionPatcher : IDisposable
{
private const string _quirksVersionFieldName = "s_QuirksVersion"; //See Source\ndp\fx\src\net\System\_UriSyntax.cs in NexFX sources
private const string _uriQuirksVersionEnumName = "UriQuirksVersion";
/// <code>
/// private enum UriQuirksVersion
/// {
/// V1 = 1, // RFC 1738 - Not supported
/// V2 = 2, // RFC 2396
/// V3 = 3, // RFC 3986, 3987
/// }
/// </code>
private const string _oldQuirksVersion = "V2";
private static readonly Lazy<FieldInfo> _targetFieldInfo;
private static readonly Lazy<int?> _patchValue;
private readonly int _oldValue;
private readonly bool _isEnabled;
static UriQuirksVersionPatcher()
{
var targetType = typeof(UriParser);
_targetFieldInfo = new Lazy<FieldInfo>(() => targetType.GetField(_quirksVersionFieldName, BindingFlags.Static | BindingFlags.NonPublic));
_patchValue = new Lazy<int?>(() => GetUriQuirksVersion(targetType));
}
public UriQuirksVersionPatcher()
{
int? patchValue = _patchValue.Value;
_isEnabled = patchValue.HasValue;
if (!_isEnabled) //Disabled if it failed to get enum value
{
return;
}
int originalValue = QuirksVersion;
_isEnabled = originalValue != patchValue;
if (!_isEnabled) //Disabled if value is proper
{
return;
}
_oldValue = originalValue;
QuirksVersion = patchValue.Value;
}
private int QuirksVersion
{
get
{
return (int)_targetFieldInfo.Value.GetValue(null);
}
set
{
_targetFieldInfo.Value.SetValue(null, value);
}
}
private static int? GetUriQuirksVersion(Type targetType)
{
int? result = null;
try
{
result = (int)targetType.GetNestedType(_uriQuirksVersionEnumName, BindingFlags.Static | BindingFlags.NonPublic)
.GetField(_oldQuirksVersion, BindingFlags.Static | BindingFlags.Public)
.GetValue(null);
}
catch
{
#if DEBUG
Debug.WriteLine("ERROR: Failed to find UriQuirksVersion.V2 enum member.");
throw;
#endif
}
return result;
}
public void Dispose()
{
if (_isEnabled)
{
QuirksVersion = _oldValue;
}
}
}
#endif
}
Usage:
using(new UriQuirksVersionPatcher())
{
using(var document = SpreadsheetDocument.Open(fullPath, false))
{
//.....
}
}
P.S. Later I found that someone already implemented this pathcher: https://github.com/google/google-api-dotnet-client/blob/master/Src/Support/Google.Apis.Core/Util/UriPatcher.cs
I haven't use OpenXml but if there's no specific reason for using it then I highly recommend LinqToExcel from LinqToExcel. Example of code is here:
var sheet = new ExcelQueryFactory("filePath");
var allRows = from r in sheet.Worksheet() select r;
foreach (var r in allRows) {
var cella = r["Header"].ToString();
}

How to implement and do OCR in a C# project?

I ve been searching for a while and all that i ve seen some OCR library requests. I would like to know how to implement the purest, easy to install and use OCR library with detailed info for installation into a C# project.
If posible, I just wanna implement it like a usual dll reference...
Example:
using org.pdfbox.pdmodel;
using org.pdfbox.util;
Also a little OCR code example would be nice, such as:
public string OCRFromBitmap(Bitmap Bmp)
{
Bmp.Save(temppath, System.Drawing.Imaging.ImageFormat.Tiff);
string OcrResult = Analyze(temppath);
File.Delete(temppath);
return OcrResult;
}
So please consider that I'm not familiar to OCR projects and give me an answer like talking to a dummy.
Edit:
I guess people misunderstood my request. I wanted to know how to implement those open source OCR libraries to a C# project and how to use them. The link given as dup is not giving answers that I requested at all.
If anyone is looking into this, I've been trying different options and the following approach yields very good results. The following are the steps to get a working example:
Add .NET Wrapper for tesseract to your project. It can be added via NuGet package Install-Package Tesseract(https://github.com/charlesw/tesseract).
Go to the Downloads section of the official Tesseract project (https://code.google.com/p/tesseract-ocr/ EDIT: It's now located here: https://github.com/tesseract-ocr/langdata).
Download the preferred language data, example: tesseract-ocr-3.02.eng.tar.gz English language data for Tesseract 3.02.
Create tessdata directory in your project and place the language data files in it.
Go to Properties of the newly added files and set them to copy on build.
Add a reference to System.Drawing.
From .NET Wrapper repository, in the Samples directory copy the sample phototest.tif file into your project directory and set it to copy on build.
Create the following two files in your project (just to get started):
Program.cs
using System;
using Tesseract;
using System.Diagnostics;
namespace ConsoleApplication
{
class Program
{
public static void Main(string[] args)
{
var testImagePath = "./phototest.tif";
if (args.Length > 0)
{
testImagePath = args[0];
}
try
{
var logger = new FormattedConsoleLogger();
var resultPrinter = new ResultPrinter(logger);
using (var engine = new TesseractEngine(#"./tessdata", "eng", EngineMode.Default))
{
using (var img = Pix.LoadFromFile(testImagePath))
{
using (logger.Begin("Process image"))
{
var i = 1;
using (var page = engine.Process(img))
{
var text = page.GetText();
logger.Log("Text: {0}", text);
logger.Log("Mean confidence: {0}", page.GetMeanConfidence());
using (var iter = page.GetIterator())
{
iter.Begin();
do
{
if (i % 2 == 0)
{
using (logger.Begin("Line {0}", i))
{
do
{
using (logger.Begin("Word Iteration"))
{
if (iter.IsAtBeginningOf(PageIteratorLevel.Block))
{
logger.Log("New block");
}
if (iter.IsAtBeginningOf(PageIteratorLevel.Para))
{
logger.Log("New paragraph");
}
if (iter.IsAtBeginningOf(PageIteratorLevel.TextLine))
{
logger.Log("New line");
}
logger.Log("word: " + iter.GetText(PageIteratorLevel.Word));
}
} while (iter.Next(PageIteratorLevel.TextLine, PageIteratorLevel.Word));
}
}
i++;
} while (iter.Next(PageIteratorLevel.Para, PageIteratorLevel.TextLine));
}
}
}
}
}
}
catch (Exception e)
{
Trace.TraceError(e.ToString());
Console.WriteLine("Unexpected Error: " + e.Message);
Console.WriteLine("Details: ");
Console.WriteLine(e.ToString());
}
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
private class ResultPrinter
{
readonly FormattedConsoleLogger logger;
public ResultPrinter(FormattedConsoleLogger logger)
{
this.logger = logger;
}
public void Print(ResultIterator iter)
{
logger.Log("Is beginning of block: {0}", iter.IsAtBeginningOf(PageIteratorLevel.Block));
logger.Log("Is beginning of para: {0}", iter.IsAtBeginningOf(PageIteratorLevel.Para));
logger.Log("Is beginning of text line: {0}", iter.IsAtBeginningOf(PageIteratorLevel.TextLine));
logger.Log("Is beginning of word: {0}", iter.IsAtBeginningOf(PageIteratorLevel.Word));
logger.Log("Is beginning of symbol: {0}", iter.IsAtBeginningOf(PageIteratorLevel.Symbol));
logger.Log("Block text: \"{0}\"", iter.GetText(PageIteratorLevel.Block));
logger.Log("Para text: \"{0}\"", iter.GetText(PageIteratorLevel.Para));
logger.Log("TextLine text: \"{0}\"", iter.GetText(PageIteratorLevel.TextLine));
logger.Log("Word text: \"{0}\"", iter.GetText(PageIteratorLevel.Word));
logger.Log("Symbol text: \"{0}\"", iter.GetText(PageIteratorLevel.Symbol));
}
}
}
}
FormattedConsoleLogger.cs
using System;
using System.Collections.Generic;
using System.Text;
using Tesseract;
namespace ConsoleApplication
{
public class FormattedConsoleLogger
{
const string Tab = " ";
private class Scope : DisposableBase
{
private int indentLevel;
private string indent;
private FormattedConsoleLogger container;
public Scope(FormattedConsoleLogger container, int indentLevel)
{
this.container = container;
this.indentLevel = indentLevel;
StringBuilder indent = new StringBuilder();
for (int i = 0; i < indentLevel; i++)
{
indent.Append(Tab);
}
this.indent = indent.ToString();
}
public void Log(string format, object[] args)
{
var message = String.Format(format, args);
StringBuilder indentedMessage = new StringBuilder(message.Length + indent.Length * 10);
int i = 0;
bool isNewLine = true;
while (i < message.Length)
{
if (message.Length > i && message[i] == '\r' && message[i + 1] == '\n')
{
indentedMessage.AppendLine();
isNewLine = true;
i += 2;
}
else if (message[i] == '\r' || message[i] == '\n')
{
indentedMessage.AppendLine();
isNewLine = true;
i++;
}
else
{
if (isNewLine)
{
indentedMessage.Append(indent);
isNewLine = false;
}
indentedMessage.Append(message[i]);
i++;
}
}
Console.WriteLine(indentedMessage.ToString());
}
public Scope Begin()
{
return new Scope(container, indentLevel + 1);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
var scope = container.scopes.Pop();
if (scope != this)
{
throw new InvalidOperationException("Format scope removed out of order.");
}
}
}
}
private Stack<Scope> scopes = new Stack<Scope>();
public IDisposable Begin(string title = "", params object[] args)
{
Log(title, args);
Scope scope;
if (scopes.Count == 0)
{
scope = new Scope(this, 1);
}
else
{
scope = ActiveScope.Begin();
}
scopes.Push(scope);
return scope;
}
public void Log(string format, params object[] args)
{
if (scopes.Count > 0)
{
ActiveScope.Log(format, args);
}
else
{
Console.WriteLine(String.Format(format, args));
}
}
private Scope ActiveScope
{
get
{
var top = scopes.Peek();
if (top == null) throw new InvalidOperationException("No current scope");
return top;
}
}
}
}
Here's one: (check out http://hongouru.blogspot.ie/2011/09/c-ocr-optical-character-recognition.html or http://www.codeproject.com/Articles/41709/How-To-Use-Office-2007-OCR-Using-C for more info)
using MODI;
static void Main(string[] args)
{
DocumentClass myDoc = new DocumentClass();
myDoc.Create(#"theDocumentName.tiff"); //we work with the .tiff extension
myDoc.OCR(MiLANGUAGES.miLANG_ENGLISH, true, true);
foreach (Image anImage in myDoc.Images)
{
Console.WriteLine(anImage.Layout.Text); //here we cout to the console.
}
}
I'm using tesseract OCR engine with TessNet2 (a C# wrapper - http://www.pixel-technology.com/freeware/tessnet2/).
Some basic code:
using tessnet2;
...
Bitmap image = new Bitmap(#"u:\user files\bwalker\2849257.tif");
tessnet2.Tesseract ocr = new tessnet2.Tesseract();
ocr.SetVariable("tessedit_char_whitelist", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.,$-/#&=()\"':?"); // Accepted characters
ocr.Init(#"C:\Users\bwalker\Documents\Visual Studio 2010\Projects\tessnetWinForms\tessnetWinForms\bin\Release\", "eng", false); // Directory of your tessdata folder
List<tessnet2.Word> result = ocr.DoOCR(image, System.Drawing.Rectangle.Empty);
string Results = "";
foreach (tessnet2.Word word in result)
{
Results += word.Confidence + ", " + word.Text + ", " + word.Left + ", " + word.Top + ", " + word.Bottom + ", " + word.Right + "\n";
}
Some online API's work pretty well: ocr.space and Google Cloud Vision. Both of these are free, as long as you do less than 1000 OCR's per month. You can drag & drop an image to do a quick manual test to see how they perform for your images.
I find OCR.space easier to use (no messing around with nuget libraries), but, for my purpose, Google Cloud Vision provided slightly better results than OCR.space.
Google Cloud Vision example:
GoogleCredential cred = GoogleCredential.FromJson(json);
Channel channel = new Channel(ImageAnnotatorClient.DefaultEndpoint.Host, ImageAnnotatorClient.DefaultEndpoint.Port, cred.ToChannelCredentials());
ImageAnnotatorClient client = ImageAnnotatorClient.Create(channel);
Image image = Image.FromStream(stream);
EntityAnnotation googleOcrText = client.DetectText(image).First();
Console.Write(googleOcrText.Description);
OCR.space example:
string uri = $"https://api.ocr.space/parse/imageurl?apikey=helloworld&url={imageUri}";
string responseString = WebUtilities.DoGetRequest(uri);
OcrSpaceResult result = JsonConvert.DeserializeObject<OcrSpaceResult>(responseString);
if ((!result.IsErroredOnProcessing) && !String.IsNullOrEmpty(result.ParsedResults[0].ParsedText))
return result.ParsedResults[0].ParsedText;
A new API is OcrEngine.RecognizeAsync from WinRT/UWP. It can also be used in WinForms:
...
//for AsBuffer
using System.Runtime.InteropServices.WindowsRuntime;
...
async private void button5_Click(object sender, EventArgs e)
{
OcrEngine ocrEngine = null;
ocrEngine = OcrEngine.TryCreateFromUserProfileLanguages();
if (ocrEngine == null) return;
//convert the image to BGRA8 format which is needed by SoftwareBitmap
//is there a better method for this?
Bitmap img = new Bitmap(#"1.png");
byte[] ba = new byte[img.Width * img.Height * 4];
int o = 0;
for (int y = 0; y < img.Height; y++)
{
for (int x = 0; x < img.Width; x++)
{
var p = img.GetPixel(x, y);
ba[o++] = p.B;
ba[o++] = p.G;
ba[o++] = p.R;
ba[o++] = p.A;
}
}
var buffer = ba.AsBuffer();
var outputBitmap = SoftwareBitmap.CreateCopyFromBuffer(
buffer,
BitmapPixelFormat.Bgra8,
img.Width,
img.Height);
var ocrResult = await ocrEngine.RecognizeAsync(outputBitmap);
}
To use WinRT/UWP API in WinForms, add Nuget package "Microsoft.Windows.SDK.Contracts" (version 10.0.17134.100 for Win10 1803 SDK tested here) as described here.

Categories

Resources