Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

Parse HTML Table with RegEx in .NET

This is used to parse an html table. Practical to obtain results from a web page, formatted in a table.

The algorithm requires to know the number of colums to parse. Then, using regex, it loops in all the lines of the tables, and the content of each column of theeese line is inside the m.Groups[index].Value.


public void ParseHtmlTable(string html)
{

/// Basic html table pattern
string columnPattern = @"\<TD[^\>]*\>([^\<]*)\<\/TD\>";
string betweenColumnsPattern = @"[^\<]*";


/// Construct the global pattern for a given nomber of columns
int nbColums = 11;

StringBuilder sbPattern = new StringBuilder();
for (int i = 0; i < nbColums; i++)
{
sbPattern.Append(columnPattern);
sbPattern.Append(betweenColumnsPattern);
}
sbPattern.Append(columnPattern);

/// Parse the result
Regex r = new Regex(sbPattern.ToString(), RegexOptions.IgnoreCase);

foreach (Match m in r.Matches(html))
{
// example
string insideCell1 = m.Groups[0].Value;
string insideCell2 = m.Groups[2].Value;

/// do things..


}
}

Simple XML validation helper using XmlReader

Purpose: simply validate a xml file against a xsd file, in C#.

I would appreciate a one single line validation method from one of the multiple xml .NET classes, but there seems to be none with the framework. I fouded some examples too complicated for nothing on the web. So I created this helper. 

It can be used in two ways:

1- Static single line call:


bool success = XMLValidationHelper.Validate("myFile.xml", "mySchema.xsd");


2- Using helper object to collect the first error message occured:


XMLValidationHelper validator = new XMLValidationHelper("myFile.xml", "mySchema.xsd");
validator.Validate();
if (!validator.IsValid)
{
// do something...

}

Here is the class:



using System;
using System.Xml;
using System.Xml.Schema;
using System.Text;

/// <summary>
/// Helper class to check a XML against a XSD file
/// </summary>
public class XMLValidationHelper
{

string xmlFileName, xsdFileName;
string exceptionMessage;
bool isValid = false;

public string ExceptionMessage
{
get { return exceptionMessage; }
set { exceptionMessage = value; }
}

public XMLValidationHelper(string xmlFileName, string xsdFileName)
{
this.xmlFileName = xmlFileName;
this.xsdFileName = xsdFileName;
}

/// <summary>
/// Global validation state of the xml against the xsd
/// </summary>
public bool IsValid
{
get { return isValid; }
set { isValid = value; }
}

/// <summary>
/// Read the document and collects the validation exceptions if any.
/// </summary>
/// <returns></returns>
public bool Validate()
{

XmlReader schemaReader = XmlReader.Create(this.xsdFileName);

/// validation settings
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.ValidationFlags =
System.Xml.Schema.XmlSchemaValidationFlags.ReportValidationWarnings;
settings.Schemas.Add("", schemaReader);


XmlReader myReader = XmlReader.Create(this.xmlFileName, settings);
this.isValid = true;
try
{
while (!myReader.EOF)
myReader.Read();

}

catch (System.Xml.Schema.XmlSchemaException validationException)
{
this.isValid = false;
this.exceptionMessage = validationException.Message;
}
finally
{
schemaReader.Close();
myReader.Close();
}
return this.isValid;
}


public static bool Validate(string xmlFileName, string xsdFileName)
{
XMLValidationHelper instantHelper = new XMLValidationHelper(xmlFileName, xsdFileName);
return instantHelper.Validate();
}


}






Capturing strings between stopwords



private string StringBetween(string beginTag, string endTag, string texteToParse)
{
System.Collections.Specialized.StringCollection coll = StringsBetween(beginTag, endTag, texteToParse);
if (coll.Count == 0) return "";
else return coll[0];
}
private System.Collections.Specialized.StringCollection StringsBetween(string beginTag, string endTag, string texteToParse)
{
System.Collections.Specialized.StringCollection result = new System.Collections.Specialized.StringCollection();
int pos1, pos2;
pos1 = 0;
pos2 = -1;
pos1 = texteToParse.IndexOf(beginTag);
while (pos1 > -1)
{
pos2 = texteToParse.IndexOf(endTag, pos1 + beginTag.Length);
if (pos2 > -1)
{
result.Add(texteToParse.Substring(pos1 + beginTag.Length, pos2 - (pos1 + beginTag.Length)));
pos1 = texteToParse.IndexOf(beginTag, pos2 + endTag.Length);

}
else
pos1 = -1;

}
return result;
}

Simple way to create a Culture in .NET

Here is the simplest way to create a new culture in .NET, from an existing culture.

I needed a way to have french format dates (YYYY-MM-DD) and dot symbol (.) as decimal separator in an web application. Since javascript only recognize dot as decimal separator, having to use french cultures, either "fr-FR" or "fr-CA", is a pain because the decimal specified in theese is the coma. I wanted everything from the french culture, but a dot as decimal separator, and there is no way to do this without creating a new culture.

A new culture can be created from scratch. Here is a detailed article about this.

However, the following code simply copy an existing culture, changes it, and register in with a new name ("fr-CA-QC").

The process of revistering required administrator privilege on the machine where the code is running. The code below only have to be run once.



/// Original culture
CultureInfo cultureInfo = new CultureInfo("fr-CA");
RegionInfo regionInfo = new RegionInfo(cultureInfo.Name);

/// New Culture, with a new name ("fr-CA-QC")
/// if culture already exists, we need to unregister it for overwriting:
/// CultureAndRegionInfoBuilder.Unregister("fr-CA-QC");
CultureAndRegionInfoBuilder builder =
new CultureAndRegionInfoBuilder("fr-CA-QC",
CultureAndRegionModifiers.None);

// load in the data from the original culture and region
builder.LoadDataFromCultureInfo(cultureInfo);
builder.LoadDataFromRegionInfo(regionInfo);

// make custom changes to the culture
builder.NumberFormat.NumberDecimalSeparator = ".";
builder.NumberFormat.CurrencyDecimalSeparator = ".";

/// Create this culture on this computer (NEED ADMINISTRATOR PRIVILEGES)
builder.Register();



This can be executed in a simple windows or console application. After that, any application running on the same computer or server can use this new culture with the name defined above.


Now, I just have to include theese attributes in my web.config files and I will no more have culture problems:


<globalization culture="fr-CA-QC" uiCulture="fr-CA-QC" ...

Object property/fields cloner

Here is a class using reflection to transfer property or field values from an object to another, instead of copying values one by one like this:

b.Address = a.Address
b.Age = a.Age
b.Color = a.Color
b.Surname = a.Surname
b.Boss = a.Boss
[...]

Instead, this helper method will be used like this:
PropertyCopier.CopyPropertiesToTargetObject(a,b)

That's it. Object b now have same property values as a.



Imports System.Reflection

''' <summary>
''' Helper class using reflection to copy all fiels and/or properties
''' from a object A to object B
''' </summary>
''' <remarks></remarks>
Public Class PropertyCopier

''' flags to get all instance fields, even privates
Private Shared flags As BindingFlags = BindingFlags.NonPublic Or BindingFlags.[Public] Or BindingFlags.Instance

#Region " To copy object property values "

''' <summary>
''' Copy all property values from sourceObject to targetObject
''' Private and public properties values will be cloned from sourceObject to targetObject when property name exists on the two object
''' </summary>
''' <param name="sourceObject">Oject from which fields values will be copied</param>
''' <param name="targetObject">Object which will have copied property values</param>
''' <remarks>
''' By default, only properties having same value types are cloneed
''' </remarks>
Public Shared Sub CopyPropertiesToTargetObject(ByVal sourceObject As Object, ByVal targetObject As Object)
CopyPropertiesToTargetObject(sourceObject, targetObject, True)
End Sub

''' <summary>
''' Copy all property values from sourceObject to targetObject
''' Private and public properties values will be cloned from sourceObject to targetObject when property name exists on the two object
''' </summary>
''' <param name="sourceObject">Oject from which fields values will be copied</param>
''' <param name="targetObject">Object which will have copied property values</param>
''' <param name="sameValueTypesOnly">If true, only property having the same type of value (ex: String, CustomClassType, etc) will be cloned</param>
''' <remarks></remarks>
Public Shared Sub CopyPropertiesToTargetObject(ByVal sourceObject As Object, ByVal targetObject As Object, ByVal sameValueTypesOnly As Boolean)
'''' getting all properties from two objects
Dim sourceFields As FieldInfo() = sourceObject.[GetType]().GetFields(flags)
Dim targetFields As FieldInfo() = targetObject.[GetType]().GetFields(flags)

Dim tempValue As Object
For Each sourceField As FieldInfo In sourceFields
For Each targetField As FieldInfo In targetFields
If sourceField.Name = targetField.Name Then
'' Same property name => value to copy?
tempValue = sourceField.GetValue(sourceObject)
' If same value type between object, or not strict copy asked
If tempValue.GetType() Is targetField.GetValue(targetObject).GetType() Or Not sameValueTypesOnly Then
Try
targetField.SetValue(targetObject, tempValue)
Catch ex As Exception
If sameValueTypesOnly Then Throw
System.Diagnostics.Debug.WriteLine(ex.ToString())
End Try
End If
End If
Next
Next
End Sub
#End Region

#Region " To copy object fields values "
''' <summary>
''' Copy all fields values from sourceObject to targetObject
''' Private and public field values will be cloned from sourceObject to targetObject when fields name exists on the two object
''' </summary>
''' <param name="sourceObject">Oject from which fields values will be copied</param>
''' <param name="targetObject">Object which will have copied values</param>
''' <remarks>
''' By default, only fields having same value types are cloneed
''' </remarks>
Public Shared Sub CopyFieldsToTargetObject(ByVal sourceObject As Object, ByVal targetObject As Object)
CopyFieldsToTargetObject(sourceObject, targetObject, False)

End Sub
''' <summary>
''' Copy all fields values from sourceObject to targetObject
''' Private and public field values will be cloned from sourceObject to targetObject when fields name exists on the two object
''' </summary>
''' <param name="sourceObject">Oject from which fields values will be copied</param>
''' <param name="targetObject">Object which will have copied values</param>
''' <param name="sameValueTypesOnly">If true, only fields having the same type of value (ex: String, CustomClassType, etc) will be cloned</param>
''' <remarks></remarks>
Public Shared Sub CopyFieldsToTargetObject(ByVal sourceObject As Object, ByVal targetObject As Object, ByVal sameValueTypesOnly As Boolean)
'''' Getting fields from source & target objects
Dim sourceFields As PropertyInfo() = sourceObject.[GetType]().GetProperties(flags)
Dim targetFields As PropertyInfo() = targetObject.[GetType]().GetProperties(flags)
Dim tempValue As Object

'''' Searching match between objects property names
For Each sourceField As PropertyInfo In sourceFields
For Each targetField As PropertyInfo In targetFields
If sourceField.Name = targetField.Name Then
'' Same field name => value to copy?
tempValue = sourceField.GetValue(sourceObject, Nothing)

' If same value type between object, or not strict copy asked
If tempValue.GetType() Is targetField.GetValue(targetObject, Nothing).GetType() Or Not sameValueTypesOnly Then
Try
targetField.SetValue(targetObject, tempValue, Nothing)
Catch ex As Exception
If sameValueTypesOnly Then Throw
System.Diagnostics.Debug.WriteLine(ex.ToString())
End Try
End If

End If
Next
Next
End Sub

#End Region


End Class

Binding a Windows form to a TableAdapter through a Web Service

How can I use a TableAdapter object in a Windows Form aplication, when this TableAdapter object is returned from a Web Service? It's simple, but...

I wanted a simple thing:
1- define a TableAdapter in a Web Service project, to get a typed DataSet from the BD
2- return this TableAdater in a WebMethod
3- use the TableAdapter returned by the web service from a DataGridView in a Windows form application (referencing the web service, of course).

I tried many combinations before getting this worked, playing with objects and properties, and googling, and playing again... There is finally a recipe that works:

On the web service side:

  1. Create a TableAdapter (by dragging adatabase table from the werver explorer and settinga select method). This create a DataSet (name of the *.xsd file), a DataTable (name in the top of the box) and a TableAdapter (with a "TableAdapter" suffix after the DataTable name).
  2. Create a web method returning an instance of the new DataTable.
On the windows application side:
  1. In the Windows client project, add a Web Reference to the class of the web service (like the default name "Service1").
  2. Next, go on the design view of the web form. On the toolbox pane, some items are automatically added. There should be an item with the DataSet used in the web service; the name will be the "*.xsd" file name, not the DataTable or the TableAdapter name (default will be "DataSet1"). This should be in a section of the toolbox labeled with the web service namespace ("MyWebService components"). Drag this component on the web form. A DataSet object will be created.
  3. Drag a BindingSource on the web form
  4. In the properties of the new BindingSource object, set the datasource to the new DataSet objet. In the DataMember drop down list, you can now choose the name of the DataTable (similar to the TableAdapter name) within the DataSet.
  5. In the DataGridView control properties, set the DataSource to the new DataBindingSource object.
  6. All properties shoud now be ok.

To get the data populating the DataGridView on load, there is a line to write.In the Load() event of the form, add something like
Me.BindingSource1.DataSource = New [WebReferenceAliasName].[ServiceName]().[WebMethodName]()

It works fine this way.

Upcasting an object "for real"

This week, I had a problem with serialization in a web service. Theorically, any serializable object or object collection can be send or received by a web service. The problem occured when the web service's tried to send a object collection including objects of type A and objects of type B inherited from A. While it tried to serialize the collection (an array or a typed list) as a collection of type A, the runtime throwed this exception:

System.InvalidOperationException: The type [inheritedType] was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.

Here is what happened (as I undersand): The SOAP protocol defines a strict serialisation format constructed with the declared type in the [WebMethod] functions, and this format is in the web service's contract. That's why the formatter refuses to serialise inherited types. For example, if type A have a Name and a Id attributes, the web service returning a A object defines theese two attributes it it's SOAP contract. But if the service tries to send a B object, inherited from A but having a added Employer attribute, the formatter will normally want to serialize it with this Employer attribute, whitch is not in the SOAP contract. That is why the exception is throwned.

Normally, upcasting objects is about upcasting reference, not instances. In other words, the program can use an object of type B as if it was an object of type A, but the .net runtime kows it is a object of type B. If you observe an upcasted or downcasted object with the compiler, you will see that the runtime can tell the original type.

Thas is why a object of type B can be upcasted to its parent class type A, then downcasted to type B and still preserve it's properties restricted to class B.

Now, how can I face my web service serialization problem without touching the serialisation process itself ? The serialization can be customized, but that's not what I wanted. I needed a process to handle this particular casting problem in a generic way. That's why I made a class to transform an object of type b in a new object of it's parent type and still preserve it's values. The class uses generic and reflection to convert an object B to a new object A having it's original field values. Note that by reflection, we can access objects private fields and set their values.

Here is the class:


/// <summary>
/// Helper class to Upcast an object to it's parent type in a definitive way.
/// To get an objet with a runtime type of the target parent type.
///
/// By reflection, it gets child instance fields values (event privates)
/// and assign them to a new instance of the given parent class.
/// </summary>
/// <typeparam name="ParentClass">Class to upcast object to</typeparam>
public class InstanceUpCaster<ParentType>
where ParentType : new()
{
/// parent class fields
FieldInfo[] fParents;

/// flags to get all instance fields, even privates
BindingFlags flags = BindingFlags.NonPublic |
BindingFlags.Public | BindingFlags.Instance;

/// <summary>
/// Getting target class fields
/// </summary>
public InstanceUpCaster()
{
fParents = typeof(ParentType).GetFields(flags);
}

/// <summary>
/// Convert the object to a new object of the parent class.
/// Assign field values of the child instance to the new instance,
/// if theese fiels exists in the parent type.
/// (public and private fields can be set by reflection)
/// </summary>
/// <param name="instance">Object to upcast to ParentType</param>
/// <returns>New object with same values (where possible in this type)</returns>
public ParentType ToParentClassInstance(ParentType childTypeObject)
{
/// Object already of the wright type (not derived) ?
if (childTypeObject.GetType() == typeof(ParentType)) return childTypeObject;

// No -> new object to upcast to
ParentType newObject = new ParentType();

/// transfering child field values to new object
FieldInfo[] fChilds = childTypeObject.GetType().GetFields(flags);
object tempValue;
foreach (FieldInfo fParent in fParents)
{
foreach (FieldInfo fChild in fChilds)
{
if (fParent.Name == fChild.Name)
{
tempValue = fChild.GetValue(childTypeObject);
if (tempValue != null)
fParent.SetValue(newObject, tempValue);
}
}
}

return newObject;
}
}

Creating a HTTP POST from in C#

Here is a way to make a HTTP request to a web page with the POST method.
Use this method with parameters formatted like in GET method.

Example : result = PostPage("www.mypage.aspx", "id=2&name=Paul&amp;age=33");

public string PostPage(string url, string params)
{
byte[] dataToPost = new ASCIIEncoding().GetBytes(params);


HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(url);
myRequest.Method = "POST";

//Request properties
myRequest.ContentType = "application/x-www-form-urlencoded";
myRequest.ContentLength = dataToPost.Length;

//Writing data in stream
Stream myStream = myRequest.GetRequestStream();
myStream.Write(dataToPost, 0, dataToPost.Length);
myStream.Close();

//Getting response
WebResponse myResponse = myRequest.GetResponse();

//Reading it
Stream responseStream = myResponse.GetResponseStream();
StreamReader streamReader = new StreamReader(responseStream);
string result = streamReader.ReadToEnd();


//closing all
streamReader.Close();
responseStream.Close();
myResponse.Close();
myStream.Close();

return result;
}

Simple HTTP request and response

For getting programmatically the HTML content of a web page:


using System.Net;

public string GetPage(string pageToGet)
{
HttpWebRequest request= (HttpWebRequest)HttpWebRequest.Create(pageToGet);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();

return new streamReader(response.GetResponseStream()).ReadToEnd();

}