Saturday, June 30, 2007

Mobile Client Software Factory: Data Access Application Block - Part 2

This article is a second part on demonstrating a simple example using the Data Access Application Block. You have haven't read Part 1, then you can do so here: Mobile Client Software Factory: Data Access Application Block - Part 1.

As always, if you haven't already downloaded and installed the Smart Client Software Factory, you can get it here.

In this article I will build on the previous article which will demonstrate adding transaction support to the CustomersDB class. As in the last article I added a Save() method which accepted a CustomerInfo object. In this article, we will create an overloaded Save() method but this method will accept a CustomerCollection object which will hold a collection of CustomerInfo objects that need to be written to the Customers table.

1. Firstly we will need to add the CustomerCollection class. Again we would normally separate this class from the CustomersDB namespace but we have included it here for simplicity.

namespace MyCompany.DAL
{
public class CustomerCollection : CollectionBase
{
#region ctor
public CustomerCollection()
{
}
#endregion

#region Methods
public int Add(CustomerInfo customer)
{
return base.List.Add(customer);
}

public void Insert(int index, GatewayInfo gateway)
{
base.List.Insert(index, gateway);
}

public bool Contains(CustomerInfo Customer)
{
return base.List.Contains(customer);
}

public void Remove(CustomerInfo customer)
{
base.List.Remove(customer);
}
#endregion


#region Indexers
public CustomerInfo this[int index]
{
get
{
return (CustomerInfo)List[index];
}
set
{
List[index] = value;
}
}
#endregion
}
}



2. Now you'll need to add the new overloaded Save() method which accepts the collection of customer objects and wraps this process up in a transaction. There are a couple of things that has changed in the CustomersDB class in this step. The first is the Fields region. I have added a couple new variable objects. The Dispose method has also changed to ensure objects get finalized.

The code is as follows:-

using System;
using System.Collections.Generic;
using Microsoft.Practices.Mobile.DataAccess;
using System.Data.SqlServerCe;
using System.Data.Common;

namespace MyCompany.DAL
{
public class CustomersDB : IDisposable
{
#region Fields
private SqlDatabase database = null;
private DbTransaction transaction = null;
private DbCommand cmd = null;
#endregion

#region ctor
public CustomersDB()
{
database = new SqlDatabase("Data Source = \Storage Card" +
"\MyDatabase.sdf; password=mypassword;encrypt "+
"database = TRUE");
}
#endregion

#region Methods
public CustomerInfo GetCustomer(Guid guid)
{
CustomerInfo customer = null;
if (guid = Guid.Empty)
return customer;

DbCommand cmd =
database.DbProviderFactory.CreateCommand();
DbParameter pname = cmd.CreateParameter();
id.DbType = System.Data.DbType.Guid;
id.ParameterName = "@CustomerID";
id.Value = guid;

cmd.Parameters.Add(id);

//Now for the SQL.
cmd.CommandText =
"select * from Customers where CustomerID = @CustomerID";
DbDataReader reader = null;

try
{
reader = database.ExecuteReader(cmd, null);
if (reader.Read())
customer = GetCustomer(reader);
}
catch (SqlCeException ex)
{
throw ex;
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (reader != null)
reader.Close();
cmd.Dispose();
}
return customer;
}

public bool Save(ref CustomerInfo customer)
{
if (customer == null)
throw new ArgumentException
(Properties.Resources.CustMustNotBeNull);

DbCommand cmd =
database.DbProviderFactory.CreateCommand();
DbParameter id = cmd.CreateParameter();
DbParameter address = cmd.CreateParameter();
DbParameter name = cmd.CreateParameter();
DbParameter changeStamp = cmd.CreateParameter();
DbParameter phone = cmd.CreateParameter();

id.DbType = System.Data.DbType.Guid;
id.ParameterName = "@CustomerID";

address.DbType = System.Data.DbType.String;
address.ParameterName = "@Address";
address.Value = customer.Address;

name.DbType = System.Data.DbType.String;
name.ParameterName = "@Name";
name.Value = customer.Name;

phone.DbType = System.Data.DbType.String;
phone.ParameterName = "@Phone";
phone.Value = customer.Phone;

changeStamp.DbType = System.Data.DbType.DateTime;
changeStamp.ParameterName = "@ChangeStamp";
changeStamp.Value = DateTime.Now;

cmd.Parameters.Add(id);
cmd.Parameters.Add(address);
cmd.Parameters.Add(name);
cmd.Parameters.Add(phone);
cmd.Parameters.Add(changeStamp);

int i = 0;
try
{
if (customer.Id == Guid.Empty)
{
//Then we are adding a new
//customer to the database.
Guid guid = Guid.NewGuid();
id.Value = guid;
customer.Id = guid;
cmd.CommandText =
"insert into Customers(CustomerID, Address," +
" Name, ChangeStamp, Phone) " +
"Values(@CustomerID, @Address, @Name, " +
"@ChangeStamp, @Phone)";
}
else
{
//Then we are updating a database.
id.Value = customer.Id;
cmd.CommandText =
"update Customers set Address = " +
"@Address, Name = @Name, " +
"ChangeStamp = @ChangeStamp, "+
"Phone = @Phone where " +
"CustomerID = @CustomerID";
}
i = database.ExecuteNonQuery(cmd, null);
}
catch (SqlCeException ex)
{
throw ex;
}
catch (Exception ex)
{
throw ex;
}
finally
{
cmd.Dispose();
}

//Lets check if we saved the customer ok by
//evaluating the effected rows.
if (i > 0)
return true;
else
return false;
}

public bool Save(CustomerCollection customerCollection)
{
if (customerCollection == null)
throw new ArgumentException
(Properties.Resources.CustCollMustNotBeNull);

cmd =
database.DbProviderFactory.CreateCommand();
DbParameter id = cmd.CreateParameter();
DbParameter address = cmd.CreateParameter();
DbParameter name = cmd.CreateParameter();
DbParameter changeStamp = cmd.CreateParameter();
DbParameter phone = cmd.CreateParameter();

id.DbType = System.Data.DbType.Guid;
id.ParameterName = "@CustomerID";

address.DbType = System.Data.DbType.String;
address.ParameterName = "@Address";

name.DbType = System.Data.DbType.String;
name.ParameterName = "@Name";

phone.DbType = System.Data.DbType.String;
phone.ParameterName = "@Phone";

changeStamp.DbType = System.Data.DbType.DateTime;
changeStamp.ParameterName = "@ChangeStamp";

cmd.Parameters.Add(id);
cmd.Parameters.Add(address);
cmd.Parameters.Add(name);
cmd.Parameters.Add(phone);
cmd.Parameters.Add(changeStamp);
transaction = database.GetConnection().BeginTransaction();
cmd.Transaction = transaction;

foreach (CustomerInfo customer in customerCollection)
{
id.Value = customer.Id;
address.Value = customer.Address;
name.Value = customer.Name;
changeStamp.Value = DateTime.Now;
phone.Value = customer.Phone;

int i = 0;
try
{
if (customer.Id == Guid.Empty)
{
//Then we are adding a new
//customer to the database.
Guid guid = Guid.NewGuid();
id.Value = guid;
customer.Id = guid;
cmd.CommandText =
"insert into Customers(CustomerID, Address," +
" Name, ChangeStamp, Phone) " +
"Values(@CustomerID, @Address, @Name, " +
"@ChangeStamp, @Phone)";
}
else
{
//Then we are updating a database.
id.Value = customer.Id;
cmd.CommandText =
"update Customers set Address = " +
"@Address, Name = @Name, " +
"ChangeStamp = @ChangeStamp, " +
"Phone = @Phone where " +
"CustomerID = @CustomerID";
}
i = database.ExecuteNonQuery(cmd, null);

if (i <= 0) break; } catch (SqlCeException ex) { transaction.Rollback(); throw ex; } catch (Exception ex) { transaction.Rollback(); throw ex; } } } if (i > 0)
{
transaction.Commit();
return true;
}
else
{
transaction.Rollback();
return false;
}
}



#endregion

#region Private helper methods
private CustomerInfo GetCustomer(DbDataReader reader)
{
CustomerInfo customer = new CustomerInfo();
customer.Id = (Guid)reader["CustomerID"];
if (reader["Name"] != DBNull.Value)
customer.Name = (string)reader["Name"];
if (reader["ChangeStamp"] != DBNull.Value)
customer.ChangeStamp =
(DateTime)reader["ChangeStamp"];
if (reader["Address"] != DBNull.Value)
customer.Address = (int)reader["Address"];
if (reader["Phone"] != DBNull.Value)
customer.Phone = (string)reader["Phone"];
return customer;
}
#endregion

#region IDisable members
public void Dispose()
{
if (transaction != null)
{
transaction.Dispose();
transaction = null;
}

if (cmd != null)
{
cmd.Dispose();
cmd = null;
}

if (database != null)
{
database.Dispose();
database = null;
}
}
#endregion
}

public class CustomerInfo
{
#region Fields
private Guid _id = Guid.Empty;
private string _name = string.Empty;
private string _address = string.Empty;
private string _phone = string.Empty;
private Nullable<DateTime> _changeStamp = null;
#endregion

#region Properties
public Guid Id
{
get
{
return _id;
}
set
{
_id = value;
}
}

public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}

public string Address
{
get
{
return _id;
}
set
{
_id = value;
}
}

public string Phone
{
get
{
return _phone;
}
set
{
_phone = value;
}
}

public Nullable<DateTime> ChangeStamp
{
get
{
return _changeStamp;
}
set
{
_changeStamp = value;
}
}
}

public class CustomerCollection : CollectionBase
{
#region ctor
public CustomerCollection()
{
}
#endregion

#region Methods
public int Add(CustomerInfo customer)
{
return base.List.Add(customer);
}

public void Insert(int index, GatewayInfo gateway)
{
base.List.Insert(index, gateway);
}

public bool Contains(CustomerInfo Customer)
{
return base.List.Contains(customer);
}

public void Remove(CustomerInfo customer)
{
base.List.Remove(customer);
}
#endregion


#region Indexers
public CustomerInfo this[int index]
{
get
{
return (CustomerInfo)List[index];
}
set
{
List[index] = value;
}
}
#endregion
}

}


We can use the foreach keyword in the newly added Save method because the CustomerCollection inherits from CollectionBase which implements the IEnumerable interface for us so we don't have to.

The newly added Save method could of course be refactored to make use of the existing Save method as there is some repeated code there. My concentration was on providing an example on the transaction support.

Monday, June 25, 2007

Mobile Client Software Factory: Data Access Application Block - Part 1

There are very few simple examples on how to use the Data Access Application Block that comes with the Mobile Client Software Factory.

For those of you who do not know what the Mobile Client Software Factory is, it is a mobile version of the desktop Enterprise Library. You can download the Mobile Client Software Factory and read about it here. You can also see the patterns and practices Developer Centre for more information.

I will show a very simple example on reading and writing to a SQL Server Mobile database using the Mobile Client Software Factory Data Access Application Block.

I have removed XML comments from the sample code to make it easier to read in an HTML page.

1. Firstly you will need to add a reference to your project to include the Microsoft.Practices.Mobile.DataAccess.dll.

This assembly can be found in the default directory:
C:\Program Files\Microsoft Mobile Client Software Factory\ApplicationBlocks\DataAccess\Src\bin\release.

Right click the References folder for your project in Visual Studio and click Add Reference.



2. You need to add the declarations to the header of your class.



using System;
using System.Collections.Generic;
using Microsoft.Practices.Mobile.DataAccess;
using System.Data.SqlServerCe;
using System.Data.Common;




namespace MyCompany.DAL
{
public class CustomerDB
{
}
}


3. Create an instance of the SqlDatabase class which is part of the Microsoft.Practices.Mobile.DataAccess namespace. A
good place to put this is in a constructor for your data access layer class. ie:-

using System;
using System.Collections.Generic;
using Microsoft.Practices.Mobile.DataAccess;
using System.Data.SqlServerCe;
using System.Data.Common;

namespace MyCompany.DAL
{
public class CustomerDB
{
#region Fields
private SqlDatabase database = null;
#endregion

#region ctor
public CustomersDB()
{
database = new SqlDatabase("Data Source = \StorageCard\" +
"MyDatabase.sdf; password=mypassword;encrypt " +
"database = TRUE");
}
#endregion

}
}

4. Implement the IDisposable interface so that when the class is disposed the database is closed. This enables the client to use the using statement.

using System;
using System.Collections.Generic;
using Microsoft.Practices.Mobile.DataAccess;
using System.Data.SqlServerCe;
using System.Data.Common;

namespace MyCompany.DAL
{
public class CustomersDB : IDisposable
{
#region Fields
private SqlDatabase database = null;
#endregion

#region ctor
public CustomersDB()
{
database = new SqlDatabase("Data Source = \Storage Card\" +
"MyDatabase.sdf; password=mypassword;encrypt " +
"database = TRUE");
}
#endregion

#region IDisable members
public void Dispose()
{
if (database != null)
{
database.Dispose();
database = null;
}
}
#endregion
}
}


5. Implement a GetCustomer method which demonstrates reading from the database.

Also note, stored procedures are not supported in SQL Server Mobile, so the next best thing is parameterized queries as demonstrated below. Also note from the code, we use a simple object model to pass back the data and we use the DataReader class to do this for performance reasons. I firmly believe in object models rather than DataSets. I have included the CustomerInfo class within the same namespace as the data layer, in the real world you wouldn't do this, it's there for simplicity.

Read up on ADO.NET Performance for more information on using DataReaders and why they are best practice.

using System;
using System.Collections.Generic;
using Microsoft.Practices.Mobile.DataAccess;
using System.Data.SqlServerCe;
using System.Data.Common;

namespace MyCompany.DAL
{
public class CustomersDB : IDisposable
{
#region Fields
private SqlDatabase database = null;
#endregion

#region ctor
public CustomersDB()
{
database = new SqlDatabase("Data Source = \Storage Card\"+
"MyDatabase.sdf; password=mypassword;encrypt "+
"database = TRUE");
}
#endregion

#region Methods
public CustomerInfo GetCustomer(Guid guid)
{
CustomerInfo customer = null;
if (guid = Guid.Empty)
return customer;

DbCommand cmd =
database.DbProviderFactory.CreateCommand();

DbParameter pname = cmd.CreateParameter();
id.DbType = System.Data.DbType.Guid;
id.ParameterName = "@CustomerID";
id.Value = guid;

cmd.Parameters.Add(id);

//Now for the SQL.
cmd.CommandText = "select * from Customers where "+
"CustomerID = @CustomerID";
DbDataReader reader = null;

try
{
reader = database.ExecuteReader(cmd, null);
if (reader.Read())
customer = GetCustomer(reader);
}
catch (SqlCeException ex)
{
throw ex;
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (reader != null)
reader.Close();
cmd.Dispose();
}
return customer;
}
#endregion

#region Private helper methods
private CustomerInfo GetCustomer(DbDataReader reader)
{
CustomerInfo customer = new CustomerInfo();
customer.Id = (Guid)reader["CustomerID"];
if (reader["Name"] != DBNull.Value)
customer.Name = (string)reader["Name"];
if (reader["ChangeStamp"] != DBNull.Value)
customer.ChangeStamp =
(DateTime)reader["ChangeStamp"];
if (reader["Address"] != DBNull.Value)
customer.Address = (int)reader["Address"];
if (reader["Phone"] != DBNull.Value)
customer.Phone = (string)reader["Phone"];
return customer;
}
#endregion

#region IDisable members
public void Dispose()
{
if (database != null)
{
database.Dispose();
database = null;
}
}
#endregion
}

public class CustomerInfo
{
#region Fields
private Guid _id = Guid.Empty;
private string _name = string.Empty;
private string _address = string.Empty;
private string _phone = string.Empty;
private Nullable<DateTime> _changeStamp = null;
#endregion

#region Properties
public Guid Id
{
get
{
return _id;
}
set
{
_id = value;
}
}

public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}

public string Address
{
get
{
return _id;
}
set
{
_id = value;
}
}

public string Phone
{
get
{
return _phone;
}
set
{
_phone = value;
}
}

public Nullable<DateTime> ChangeStamp
{
get
{
return _changeStamp;
}
set
{
_changeStamp = value;
}
}
}
}

6. Now the last part is implementing a write example and updating an existing customer record. So below I have added the Save method to the CustomersDB class. Its job is to update the customer record or create a new one.

using System;
using System.Collections.Generic;
using Microsoft.Practices.Mobile.DataAccess;
using System.Data.SqlServerCe;
using System.Data.Common;

namespace MyCompany.DAL
{
public class CustomersDB : IDisposable
{
#region Fields
private SqlDatabase database = null;
#endregion

#region ctor
public CustomersDB()
{
database = new SqlDatabase("Data Source = \Storage Card" +
"\MyDatabase.sdf; password=mypassword;encrypt "+
"database = TRUE");
}
#endregion

#region Methods
public CustomerInfo GetCustomer(Guid guid)
{
CustomerInfo customer = null;
if (guid = Guid.Empty)
return customer;

DbCommand cmd =
database.DbProviderFactory.CreateCommand();
DbParameter pname = cmd.CreateParameter();
id.DbType = System.Data.DbType.Guid;
id.ParameterName = "@CustomerID";
id.Value = guid;

cmd.Parameters.Add(id);

//Now for the SQL.
cmd.CommandText =
"select * from Customers where CustomerID = @CustomerID";
DbDataReader reader = null;

try
{
reader = database.ExecuteReader(cmd, null);
if (reader.Read())
customer = GetCustomer(reader);
}
catch (SqlCeException ex)
{
throw ex;
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (reader != null)
reader.Close();
cmd.Dispose();
}
return customer;
}

public bool Save(ref CustomerInfo customer)
{
if (customer == null)
throw new ArgumentException
(Properties.Resources.CustMustNotBeNull);

DbCommand cmd =
database.DbProviderFactory.CreateCommand();
DbParameter id = cmd.CreateParameter();
DbParameter address = cmd.CreateParameter();
DbParameter name = cmd.CreateParameter();
DbParameter changeStamp = cmd.CreateParameter();
DbParameter phone = cmd.CreateParameter();

id.DbType = System.Data.DbType.Guid;
id.ParameterName = "@CustomerID";

address.DbType = System.Data.DbType.String;
address.ParameterName = "@Address";
address.Value = customer.Address;

name.DbType = System.Data.DbType.String;
name.ParameterName = "@Name";
name.Value = customer.Name;

phone.DbType = System.Data.DbType.String;
phone.ParameterName = "@Phone";
phone.Value = customer.Phone;

changeStamp.DbType = System.Data.DbType.DateTime;
changeStamp.ParameterName = "@ChangeStamp";
changeStamp.Value = DateTime.Now;

cmd.Parameters.Add(id);
cmd.Parameters.Add(address);
cmd.Parameters.Add(name);
cmd.Parameters.Add(phone);
cmd.Parameters.Add(changeStamp);

int i = 0;
try
{
if (customer.Id == Guid.Empty)
{
//Then we are adding a new
//customer to the database.
Guid guid = Guid.NewGuid();
id.Value = guid;
customer.Id = guid;
cmd.CommandText =
"insert into Customers(CustomerID, Address," +
" Name, ChangeStamp, Phone) " +
"Values(@CustomerID, @Address, @Name, " +
"@ChangeStamp, @Phone)";
}
else
{
//Then we are updating a database.
id.Value = customer.Id;
cmd.CommandText =
"update Customers set Address = " +
"@Address, Name = @Name, " +
"ChangeStamp = @ChangeStamp, "+
"Phone = @Phone where " +
"CustomerID = @CustomerID";
}
i = database.ExecuteNonQuery(cmd, null);
}
catch (SqlCeException ex)
{
throw ex;
}
catch (Exception ex)
{
throw ex;
}
finally
{
cmd.Dispose();
}

//Lets check if we saved the customer ok by
//evaluating the effected rows.
if (i > 0)
return true;
else
return false;
}

#endregion

#region Private helper methods
private CustomerInfo GetCustomer(DbDataReader reader)
{
CustomerInfo customer = new CustomerInfo();
customer.Id = (Guid)reader["CustomerID"];
if (reader["Name"] != DBNull.Value)
customer.Name = (string)reader["Name"];
if (reader["ChangeStamp"] != DBNull.Value)
customer.ChangeStamp =
(DateTime)reader["ChangeStamp"];
if (reader["Address"] != DBNull.Value)
customer.Address = (int)reader["Address"];
if (reader["Phone"] != DBNull.Value)
customer.Phone = (string)reader["Phone"];
return customer;
}
#endregion

#region IDisable members
public void Dispose()
{
if (database != null)
{
database.Dispose();
database = null;
}
}
#endregion
}

public class CustomerInfo
{
#region Fields
private Guid _id = Guid.Empty;
private string _name = string.Empty;
private string _address = string.Empty;
private string _phone = string.Empty;
private Nullable<DateTime> _changeStamp = null;
#endregion

#region Properties
public Guid Id
{
get
{
return _id;
}
set
{
_id = value;
}
}

public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}

public string Address
{
get
{
return _id;
}
set
{
_id = value;
}
}

public string Phone
{
get
{
return _phone;
}
set
{
_phone = value;
}
}

public Nullable<DateTime> ChangeStamp
{
get
{
return _changeStamp;
}
set
{
_changeStamp = value;
}
}
}
}

7. Calling our CustomersDB class. You would typically call this class from your business layer. If we wanted to add a new customer to our Customer table, something like the following would work quite nicely:-

using (CustomersDB customersDb = new CustomersDB())
{
CustomerInfo myCustomer = new CustomerInfo();
myCustomer.Name = "Simon Hart";
myCustomer.Address = "London, England";
myCustomer.Phone = "02012736213";
customersDb.Save(myCustomer);
}

Executing the following code would create the customer and automatically close the database as we implemented the IDisposable interface.

Note the very simple object model. Of course this type of model would be much more complex in the real world but I have kept it simple here for demo purposes.

As you can see, using the Data Access Application block makes the code very simple and clean. We need not worry about messy ADO.NET statements.

The only cumbersome part of the code is declaring the parameters - no doubt this could be improved - I'd be interested in any comments.

I believe LINQ will make this even easier when version 3.5 of the Compact Framework is released.

This article is a first part to my talk on the Data Access Application Block. I will be writing some more which will include writing Transactions and collections. The above code is fine when writing single objects, but how do we write multiple records and wrap these in a Trasaction object to enable us to rollback if a particluar object failed for whatever reason? watch this space!

If you like my blog you can subscribe using a news aggregator, see here for more information.

UPDATE: See part 2 to this series here.

Sunday, June 24, 2007

iPhone Rival

It seems HTC has released a phone that rivals the Apple iPhone. Except this phone runs Windows Mobile 6 and can be used as a business tool.

See here for information on the HTC Touch.

You can bet this will be the first of many devices of its kind.

HTC call it TouchFLOW and it looks great!

VS "Orcas" and CF 3.5 beta 1

Although it has been available for download for sometime and posted on other sites/forums, I thought I'd post the link here.

The download is a Microsoft Virtual PC image, so you'll need Microsoft Virtual PC 2004 + SP1 to use it.

Some of the new interesting features are as follows:

1. System.IO.Compression support (this has got to make things easier)
2. LINQ subset
3. WCF subset

Daniel Moth talked about LINQ at the recent MSDN roadshow in London. The talk wasn't specific to devices but for the desktop but the same syntax and functionality is supported in the coming releases of the CF. All I can say is, watch out for LINQ!

LINQ will be available in the .NET Framework 3.5 and onwards. It is actually a language enhancement, so will be available in C# 3.0 and VB 9.0. But just to be clear, in order to use LINQ, you need version 3.5 of the .NET Framework - which is based on v2.0 of the CLR.

Once I have had a play with the WCF features with regards to the CF, I will blog about my experiences here.

Saturday, June 23, 2007

RSS, Atom - News Aggregators

I have recently downloaded and installed two RSS readers:

SharpReader
Rss Bandit

They are both very good products and both free although if you like either product, you can make a donation.

If you would like to subscribe to my blog, you can simply paste the following link into either aggregator: http://simonrhart.blogspot.com/feeds/posts/default it's an Atom feed but both products support RSS and Atom.

Personally, I much prefer Rss Bandit I think it's a great product and worthy of a donation.

Microsoft Feature Pack Upgrade

If you have a Windows Mobile 5 device that was shipped before MSFP (Microsoft Feature Pack) was released then you'll probebly find your OEM has released a ROM update for your device.

If you want to make use of Push Email like the Blackberry range of devices, then you will want to install this update.

Most major providers were quick to release ROM updates. I recently updated an Orange M3100 with the MSFP ROM update and it worked just fine. I only have another 1199 devices to do! I think I will delegate!

If you too have an Orange M3100 you can get the update from here: http://www.orange.co.uk/business/downloads/SPV_M3100_ROMUpdate_0407.exe
Release notes for the M3100 can be found here: http://www.business.orange.co.uk/servlet/Satellite?pagename=Business&c=OUKPage&cid=1044134915191

Microsoft left it to the OEM's to provide the update, so you will need to check with your provider if they have one.

Note: One major requirement for push email to work via WM5/6 is Microsoft Exchange Server 2003 SP2 or later is required.

Friday, June 22, 2007

.NET Compact Framework FAQ

As I get emailed and see so many repeated questions within various forums, I thought I'd post the MSDN .NET Compact Framework FAQ link here.

If your question still goes unanswered, then you might want to try and post your question to the Compact Framework user group here or the Pocket PC Developer user group here might help answer your question.

If you have a device networking problem, then the following group might be able to help with your problem.

I actively participate in all the above forums most days.

Visual Inheritance is Currently Disabled

Although CF2 has been released now since 07 November 2005, I'd thought I'd blog about this "issue" I received some time ago. As you are probebly aware, CF2 supports the UserControl class so design time support is much easier than it used to be in CF1. However, you might still get an error message after adding your new custom control to a form: "Visual Inheritance is Currently Disabled".

You might typically get this when the control uses P/Invokes or device level funtionality not supported on the desktop - such as the WindowsCE assembly.

There are two ways to get around this problem:-

1. Add an .Xmta (Design Time Attributes) file to your project.
2. Add the DesktopCompatible(true) attribute to the user control.

The prefered method is to add an .xmta (Design Time Attributes) file to your assembly. This is because typically if you have a library of controls you won't need to keep using the DesktopCompatible attribute for every form that uses your control. In addition you can add comments for memebers that will show up in the designer when using an .xmta file. The xml schema should be as follows:

<?xml version="1.0" encoding="utf-16"?>
<Classes xmlns="http://schemas.microsoft.com/VisualStudio/2004/03/SmartDevices/XMTA.xsd">
<Class Name="Company.Mobile.Forms.PropertyHeader">
<DesktopCompatible>true</DesktopCompatible>
<Property Name="Description">
<Description>Displays the description of the parent forms function.</Description>
</Property>
</Class>
</Classes>

The above example would be used in a case where your user control's class was named: Company.Mobile.Forms.PropertyHeader.

Tuesday, June 19, 2007

Provisioning XML with CSP's

A couple of my other posts mentioned using Configuration Service Providers but I didn't mention how to actually provision the XML once created in the correct format.

Doing this is easy in CF2 and WM5 you can use the new assembly which comes with WM5 SDK Microsoft.WindowsMobile.Configuration.dll. ie:

using Microsoft.WindowsMobile.Configuration;

//xmlDoc is of type XmlDocument which contains a valid CSP schema.
ConfigurationManager.ProcessConfiguration(xmlDoc, false);

Of course this is not so easy on pre-WM5 as the *.WindowsMobile.* assembly range of API's only work on WM5 and later.

You can P/Invoke DMProcessConfigXML or you could use InTheHand.WindowsMobile.Configuration. in the hand have written a managed wrapper that will provision your XML on pre Windows Mobile 5 devices and I believe it's free. ie:

using InTheHand.WindowsMobile.Configuration;

//xmlDoc is of type XmlDocument which contains a valid CSP schema.
ConfigurationManager.ProcessConfiguration(xmlDoc, false);

Microsoft Virtual Labs

I was talking to a colleque the other day about Microsoft Virtual Labs and suprisingly, he had never heard of it. So I thought I'd post a link here.

http://msdn2.microsoft.com/en-us/virtuallabs/default.aspx

What is it?
Microsoft Virtual Labs is a way to learn new content without having to install the software on your machine. It includes a guided hands-on walk through which usually lasts around 1 hour. It works a little like Terminal Services but via a web browser.

Tuesday, June 12, 2007

Cracking .NET code

After responding to a post recently regarding strong naming .NET assemblies I replied with the fact that this code can be broken. The mis-conception that strong naming your code will protect it is not true.

Digitally signing your code and writing algorithms in unmanaged code will protect it as much as possible.

See here for removing a strong name: http://www.atrevido.net/blog/PermaLink.aspx?guid=f772c18a-f389-4c28-bd6a-a30f4ccc84f5

See here for cracking an obfucated peice of code: http://www.atrevido.net/blog/PermaLink.aspx?guid=8315fa01-0286-47ce-a20b-fcc15eb297c3

See here for more information of strong naming: http://msdn2.microsoft.com/en-us/library/wd40t7ad(VS.80).aspx (.NET 2.0)

A simple solution is to make your algorithm's more complex and harder to break and to write them in unmanaged C++ then P/Invoke your unmanaged routine from your unmanaged routine.

Preventing the device from turning off

One question a lot of people ask is how to prevent the device from turning itself off?

You can either call the SHIdleTimerReset function in aygshell.dll:

[DllImport("aygshell.dll")]
private static extern void SHIdleTimerReset();

See here for more information on the API: http://msdn2.microsoft.com/en-us/library/ms835757.aspx

Or use the already wrapped method defined in OpenNETCF.WindowsCE.PowerManagement.ResetSystemIdleTimer();

Of course the function needs to be called periodically before the timeout threshold. http://www.opennetcf.org/

Saturday, June 09, 2007

Locking down individual programs in Windows Mobile

There is no API for locking down certain elements (programs, functions) on Windows Mobile and in fact there is not a lot of information out there on how to do such a thing.

There is information about security policies on particular Windows Mobile 5 and 6 and testing tools such as the Device Security Manager. For information about security policies and certificates, see this blog. I am currently in the middle of writing a document about the signing and creating of certificates which I will publish soon…

So back to a simple way of locking down an individual program….

There are three ways for locking down Windows Mobile. They are:

● Kiosk solution (SPB Kiosk)
● Long hand (code “hack”)
● Lock-down product (Trust Digital)

Of course the Kiosk solution or any of the lock down products are the easiest, but if you are fussy about using third party software or trying to keep costs down, then the long hand option might be the way to go. In addition the Kisok solution requires full screen which in some cases is not desirable. Personally I like to stick to using my own code rather than using third party solutions. Based on this, I will talk about the Long hand (code) option.

A silly simple way to stop end users from running an application is to create a zero-byte filename that you wish to block. So for example if you didn’t want a handful of your users using Pocket Internet Explorer, then you would create a zero-byte file named iexplore.exe in the \Windows directory. I know this sounds strange that it might overwrite the existing file, but it merely “hides” it. In order to enable Pocket Internet Explorer again just simply delete the zero-byte file.

This is all fine, but what about the shortcuts? Simply tapping them after locking down the .exe will generate an error message that the application could not be found. A better way to handle this situation is to delete the shortcut file. For example if Pocket Internet Explorer is present in the Start Menu, you would need to delete the shortcut file: \Windows\Start Menu\Internet Explorer.lnk.

There is one thing you should bear in mind when deleting the shortcut file and that is when locking down File Explorer (\Windows\fexplore.exe) – among others the shortcut file is marked as read-only. A simple way of getting around this is to mark the Attribute property for the FileInfo object as Archive. IE:

FileInfo ieShortcutStartMenuFile = new FileInfo(@“\Windows\Start Menu\Internet Explorer.lnk”);
ieShortcutStartMenuFile.Attributes = FileAttributes.Archive;
ieShortcutStartMenuFile.Delete();

Of course when you need to re-enable the application you will not only need to delete the zero-byte file but create the shortcut file as well.

This method of locking down applications can be applied throughout the device for all programs if required including some of the ROM installed apps:

Phone (for Phone Edition devices cprog)
Word Mobile
Word Excel
File Explorer
Camera

It is also possible to limit access to the Settings window. This is as easy as deleting the \Windows\Start Menu\Settings folder. There is nothing contained in this folder, so you won’t lose any shortcuts/data. To re-enable, simply re-create the folder.

Friday, June 01, 2007

Unloading an application Programmatically

In Pocket PC 2003 and earlier you could use unload.exe to unload applications that had been marked with flag NoUninstall as true.

This is not quite possible with later devices - Windows Mobile 5 and onwards. Instead Microsoft encourages the practice of using the Uninstall Configuration Service Provider (this link is for WM5 SDK but works on older devices). The cool thing about using the Uninstall CSP is that, you need not terminate the process if it is already loaded in memory before uninstalling it like you had to do when using unload.exe, instead you can just execute the CSP and it will handle shutting down the process for you. Of course it is recommended to ask the process to terminate rather than letting CSP do it but this shows just how powerful the uninstall CSP is.

Usage of the Uninstall CSP is dependent on the security roles that the OEM sets for individual devices. If unsure, ask your OEM if this CSP has been enabled. See here for a list of default roles for CSP's.

See here on how to provision your XML.