tag:blogger.com,1999:blog-49783263676971200612024-03-13T17:13:14.626+01:00The Minimalist Coder"Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away" - Antoine de Saint ExupéryChris Laceyhttp://www.blogger.com/profile/10405697803215160094noreply@blogger.comBlogger8125tag:blogger.com,1999:blog-4978326367697120061.post-4612831763444403842013-03-31T05:31:00.000+02:002013-04-01T01:17:16.484+02:00Data migration to Azure SQL DatabaseI've long been a fan of SQL Azure (under its various names), mainly because it's usually provided the easiest first step for migrating existing .NET apps into the cloud. That said, there have always been hundreds of features it's lacking compared to "real" SQL Server, and I've never been particularly satisfied with any of the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/ee730904.aspx?ppud=4">official means</a> for data migration.<br />
<br />
So imagine (if you can!) my excitement last September with the <a href="http://blogs.msdn.com/b/windowsazure/archive/2012/09/19/announcing-updates-to-windows-azure-sql-database.aspx">announcement </a>that it had became possible to add Azure SQL DB as a linked server. As well as allowing queries which joined tables across multiple servers, I also had high hopes that it would make migration of data from one to another much easier. Between two linked SQL Servers, I've often used the nice and simple SELECT INTO command to copy both the data and schema of a table in one easy step.<br />
<br />
On my veritable roller coaster of emotions that day, imagine now the devastation caused by the following response from Azure:<br />
<br />
<span style="color: red; font-family: Courier New, Courier, monospace;">Msg 40510, Level 16, State 1, Line 1</span><br />
<span style="color: red; font-family: Courier New, Courier, monospace;">Statement 'SELECT INTO' is not supported in this version of SQL Server.</span><br />
<br />
...due, it seems, to something about tables without clustered indices not being supported :-(<br />
<br />
After my initial disappointment, I figured that I could still move data from one place to another using INSERT INTO ... SELECT, providing the schema was already in place on the destination. After lots of playing, I finally came up with the procedure defined below for copying an entire database from a classic SQL Server into a linked Azure database - whilst also maintaining referential integrity. Various other restrictions encountered along the way means that it gets a bit ugly in places (especially due to all the dynamic SQL), but it seems to work pretty well and I've found it especially useful in scenarios where I've needed to re-run the data push several times over during development.<br />
<div>
<br /></div>
<div>
<br /></div>
<div>
<b>1. Build the schema on Azure</b></div>
<div>
<br />
The apps I'm writing at the moment are based on Entity Framework (code-first), so I must admit that building the actual schema of databases is something I get for free these days. If you're in a similar situation to me, just plug into your Web.config the connection string which the Azure portal provides for your server, and fire up your app to have Entity Framework build the empty database.<br />
<br />
Otherwise, you could use Management Studio's <i>"Script Database as" </i>menu item to give you the SQL to create the schema.<br />
<br /></div>
<div>
<b><br /></b>
<b>2. Define the linked server</b><br />
<!-- code formatted by http://manoli.net/csharpformat/ -->
<br />
<pre class="csharpcode"><span class="kwrd">EXEC</span> sp_addlinkedserver <span class="str">'azure'</span>,
N<span class="str">'SQL Server'</span>,
<span class="str">'serverref.database.windows.net'</span>
<span class="kwrd">GO</span>
<span class="kwrd">EXEC</span> sp_addlinkedsrvlogin
@rmtsrvname = <span class="str">'azure'</span>,
@useself = <span class="str">'FALSE'</span>,
@rmtuser = <span class="str">'username@serverref'</span>,
@rmtpassword = <span class="str">'password'</span>
<span class="kwrd">GO</span>
</pre>
</div>
<div>
<b><br /></b>
<b>3. Ready the Azure schema for migration</b><br />
<b><br /></b>
You'll need two new columns on each table to facilitate the data migration: <i>DataImport_OriginalId</i> (to contain the original primary key value of the data that will be transferred - needed to keep foreign keys valid across tables), and <i>DataImport_IsClone</i> for some of the jiggery-pokery that takes place in (5). I'm assuming that each table has one PK which is an <i>identity </i>and an <i>int</i>.<br />
<br />
You'll also need to disable FK constraint checking in readiness for the data to be bulk loaded (because it's quite likely that referenced rows won't yet exist at the time of loading).<br />
<br />
This generates some SQL to add the columns to each table, and disable the constraints. If you're brave, replace the PRINT commands with EXEC, but I prefer to view the SQL it generates and then copy and paste to run it manually.
</div>
<!-- code formatted by http://manoli.net/csharpformat/ -->
<br />
<pre class="csharpcode"><span class="kwrd">DECLARE</span> @tableName nvarchar(<span class="kwrd">max</span>)
<span class="kwrd">DECLARE</span> @<span class="kwrd">sql</span> nvarchar(<span class="kwrd">max</span>)
<span class="kwrd">DECLARE</span> tableCursor <span class="kwrd">CURSOR</span> <span class="kwrd">FOR</span>
<span class="kwrd">SELECT</span> TABLE_NAME
<span class="kwrd">FROM</span> INFORMATION_SCHEMA.TABLES
<span class="kwrd">OPEN</span> tableCursor
<span class="kwrd">FETCH</span> <span class="kwrd">NEXT</span> <span class="kwrd">FROM</span> tableCursor <span class="kwrd">INTO</span> @tableName
<span class="kwrd">WHILE</span> <span class="preproc">@@FETCH_STATUS</span> = 0
<span class="kwrd">BEGIN</span>
<span class="kwrd">SELECT</span> @<span class="kwrd">sql</span> = <span class="str">'ALTER TABLE '</span> + @tablename
+ <span class="str">' ADD DataImport_OriginalId int, DataImport_IsClone bit'</span>
<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span>
<span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'ALTER TABLE '</span> + @tableName
+ <span class="str">' NOCHECK CONSTRAINT ALL'</span>
<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span>
<span class="kwrd">FETCH</span> <span class="kwrd">NEXT</span> <span class="kwrd">FROM</span> tableCursor <span class="kwrd">INTO</span> @tableName
<span class="kwrd">END</span>
<span class="kwrd">CLOSE</span> tableCursor
<span class="kwrd">DEALLOCATE</span> tableCursor
</pre>
<div>
<b><br /></b>
<b>4. Push the data</b><br />
<b><br /></b>
This will generate some SQL to copy over the data from your originating server to Azure, also populating the new columns we added in (3) as appropriate.</div>
<!-- code formatted by http://manoli.net/csharpformat/ -->
<br />
<pre class="csharpcode"><span class="kwrd">DECLARE</span> @tableName nvarchar(<span class="kwrd">max</span>)
<span class="kwrd">DECLARE</span> @identityColumn nvarchar(<span class="kwrd">max</span>)
<span class="kwrd">DECLARE</span> @otherColumns nvarchar(<span class="kwrd">max</span>)
<span class="kwrd">DECLARE</span> @<span class="kwrd">sql</span> nvarchar(<span class="kwrd">max</span>)
<span class="kwrd">DECLARE</span> tableCursor <span class="kwrd">CURSOR</span> <span class="kwrd">FOR</span>
<span class="kwrd">SELECT</span> TABLE_NAME
<span class="kwrd">FROM</span> INFORMATION_SCHEMA.TABLES
<span class="kwrd">WHERE</span> TABLE_NAME <span class="kwrd">NOT</span> <span class="kwrd">IN</span> (<span class="str">'EdmMetadata'</span>)
<span class="kwrd">OPEN</span> tableCursor
<span class="kwrd">FETCH</span> <span class="kwrd">NEXT</span> <span class="kwrd">FROM</span> tableCursor <span class="kwrd">INTO</span> @tableName
<span class="kwrd">WHILE</span> <span class="preproc">@@FETCH_STATUS</span> = 0
<span class="kwrd">BEGIN</span>
<span class="kwrd">SELECT</span> @identityColumn = COLUMN_NAME
<span class="kwrd">FROM</span> INFORMATION_SCHEMA.COLUMNS
<span class="kwrd">WHERE</span> TABLE_NAME = @tableName
<span class="kwrd">AND</span> COLUMNPROPERTY(object_id(TABLE_NAME),
COLUMN_NAME, <span class="str">'IsIdentity'</span>) = 1
<span class="kwrd">SET</span> @otherColumns = <span class="kwrd">null</span>
<span class="kwrd">SELECT</span> @otherColumns = <span class="kwrd">COALESCE</span>(@othercolumns + <span class="str">','</span>,<span class="str">''</span>)
+ (<span class="str">'['</span> + COLUMN_NAME + <span class="str">']'</span>)
<span class="kwrd">FROM</span> INFORMATION_SCHEMA.COLUMNS
<span class="kwrd">WHERE</span> TABLE_NAME = @tableName
<span class="kwrd">AND</span> COLUMN_NAME <> @identityColumn
<span class="kwrd">ORDER</span> <span class="kwrd">BY</span> ORDINAL_POSITION
<span class="kwrd">SET</span> @<span class="kwrd">Sql</span> = <span class="str">'INSERT INTO azure.remotedatabase.dbo.'</span> + @tableName
+ <span class="str">' ([DataImport_OriginalId],[DataImport_IsClone],'</span> + @otherColumns
+ <span class="str">') SELECT ['</span> + @identityColumn + <span class="str">'],0,'</span> + @otherColumns
+ <span class="str">' FROM '</span> + @tableName
<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span>
<span class="kwrd">FETCH</span> <span class="kwrd">NEXT</span> <span class="kwrd">FROM</span> tableCursor <span class="kwrd">INTO</span> @tableName
<span class="kwrd">END</span>
<span class="kwrd">CLOSE</span> tableCursor
<span class="kwrd">DEALLOCATE</span> tableCursor
</pre>
<div>
<b><br /></b>
<b>5. Tidy up!</b><br />
<b><br /></b>
We now have all of the data sitting in our Azure database, but the primary keys will almost certainly be different from their originals, hence any foreign key values will reference non-existent or incorrect rows. That's because we can't SET IDENTITY_INSERT ON across successive connections, and with a linked server you have no control over which connection each SQL statement runs on. Instead, we have the original IDs in that new <i>DataImport_OriginalId</i> column we defined earlier.<br />
<br />
Because we can neither modify the newly assigned PKs, nor even reseed identity columns (DBCC isn't supported in Azure SQL DB), the only way I've found to get the rows back with their original PK values is as follows:
<br />
<ol>
<li>Switch on IDENTITY_INSERT</li>
<li>Find a suitably distant PK value from anything that currently exists or will need to exist... lazily, I've used MAX(PK) + MAX(<i>DataImport_OriginalId</i>)</li>
<li>Insert (and then delete) one row to set the identity seed to this value</li>
<li>Switch off IDENTITY_INSERT</li>
<li>Clone all rows, flagging them as cloned using the <i>DataImport_IsClone</i> column</li>
<li>Delete all original rows (where <i>DataImport_IsClone</i> = 0)</li>
<li>Switch on IDENTITY_INSERT</li>
<li>Clone all cloned rows, with PK now explicitly set to <i>DataImport_OriginalId</i></li>
<li>Switch off IDENTITY_INSERT</li>
<li>Delete all rows from the first clone!</li>
<li>Repeat for all tables; then (and only then) re-enable FK constraint checking.</li>
</ol>
Here comes the humdinger of a query to generate the SQL for all tables:</div>
<!-- code formatted by http://manoli.net/csharpformat/ -->
<br />
<pre class="csharpcode"><span class="kwrd">DECLARE</span> @tableName nvarchar(<span class="kwrd">max</span>)
<span class="kwrd">DECLARE</span> @identityColumn nvarchar(<span class="kwrd">max</span>)
<span class="kwrd">DECLARE</span> @otherColumns nvarchar(<span class="kwrd">max</span>)
<span class="kwrd">DECLARE</span> @maxIdentity <span class="kwrd">int</span>
<span class="kwrd">DECLARE</span> @<span class="kwrd">sql</span> nvarchar(<span class="kwrd">max</span>)
<span class="kwrd">DECLARE</span> tableCursor <span class="kwrd">CURSOR</span> <span class="kwrd">FOR</span>
<span class="kwrd">SELECT</span> TABLE_NAME
<span class="kwrd">FROM</span> Information_Schema.Tables
<span class="kwrd">WHERE</span> TABLE_NAME <span class="kwrd">NOT</span> <span class="kwrd">IN</span> (<span class="str">'EdmMetadata'</span>)
<span class="kwrd">OPEN</span> tableCursor
<span class="kwrd">FETCH</span> <span class="kwrd">NEXT</span> <span class="kwrd">FROM</span> tableCursor <span class="kwrd">INTO</span> @tableName
<span class="kwrd">WHILE</span> <span class="preproc">@@FETCH_STATUS</span> = 0
<span class="kwrd">BEGIN</span>
<span class="kwrd">SELECT</span> @identityColumn = COLUMN_NAME
<span class="kwrd">FROM</span> INFORMATION_SCHEMA.COLUMNS
<span class="kwrd">WHERE</span> TABLE_NAME = @tableName
<span class="kwrd">AND</span> COLUMNPROPERTY(object_id(TABLE_NAME),
COLUMN_NAME, <span class="str">'IsIdentity'</span>) = 1
<span class="kwrd">SET</span> @otherColumns = <span class="kwrd">null</span>
<span class="kwrd">SELECT</span> @otherColumns = <span class="kwrd">COALESCE</span>(@othercolumns + <span class="str">','</span>,<span class="str">''</span>)
+ (<span class="str">'['</span> + COLUMN_NAME + <span class="str">']'</span>)
<span class="kwrd">FROM</span> INFORMATION_SCHEMA.COLUMNS
<span class="kwrd">WHERE</span> TABLE_NAME = @tableName
<span class="kwrd">AND</span> COLUMN_NAME <> @identityColumn
<span class="kwrd">AND</span> COLUMN_NAME <span class="kwrd">NOT</span> <span class="kwrd">LIKE</span> <span class="str">'DataImport_%'</span>
<span class="kwrd">ORDER</span> <span class="kwrd">BY</span> ORDINAL_POSITION
<span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'SET IDENTITY_INSERT '</span> + @tableName + <span class="str">' ON'</span>
<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span>
<span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'INSERT INTO '</span> + @tableName
+ <span class="str">' (['</span> + @identityColumn + <span class="str">'],[DataImport_IsClone],'</span> + @otherColumns
+ <span class="str">') SELECT TOP 1 (SELECT MAX(DataImport_OriginalId) + MAX('</span>
+ @identityColumn
+ <span class="str">') FROM '</span> + @tableName + <span class="str">')+1,1,'</span> + @otherColumns
+ <span class="str">' FROM '</span> + @tableName
<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span>
<span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'SET IDENTITY_INSERT '</span> + @tableName + <span class="str">' OFF'</span>
<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span>
<span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'DELETE FROM '</span> + @tablename + <span class="str">' WHERE DataImport_IsClone = 1'</span>
<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span>
<span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'INSERT INTO '</span> + @tableName
+ <span class="str">' ([DataImport_OriginalId],[DataImport_IsClone],'</span> + @otherColumns
+ <span class="str">') SELECT [DataImport_OriginalId],1,'</span> + @otherColumns
+ <span class="str">' FROM '</span> + @tableName
+ <span class="str">' WHERE DataImport_OriginalId IS NOT NULL'</span>
<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span>
<span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'DELETE FROM '</span> + @tablename
+ <span class="str">' WHERE DataImport_IsClone = 0 OR DataImport_IsClone IS NULL'</span>
<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span>
<span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'SET IDENTITY_INSERT '</span> + @tableName + <span class="str">' ON'</span>
<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span>
<span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'INSERT INTO '</span> + @tableName
+ <span class="str">' (['</span> + @identityColumn + <span class="str">'],[DataImport_IsClone],'</span> + @otherColumns
+ <span class="str">') SELECT DataImport_OriginalId,0,'</span> + @otherColumns
+ <span class="str">' FROM '</span> + @tableName
<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span>
<span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'SET IDENTITY_INSERT '</span> + @tableName + <span class="str">' OFF'</span>
<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span>
<span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'DELETE FROM '</span> + @tablename + <span class="str">' WHERE DataImport_IsClone = 1'</span>
<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span>
<span class="kwrd">FETCH</span> <span class="kwrd">NEXT</span> <span class="kwrd">FROM</span> tableCursor <span class="kwrd">INTO</span> @tableName
<span class="kwrd">END</span>
<span class="kwrd">CLOSE</span> tableCursor
<span class="kwrd">OPEN</span> tableCursor
<span class="kwrd">FETCH</span> <span class="kwrd">NEXT</span> <span class="kwrd">FROM</span> tableCursor <span class="kwrd">INTO</span> @tableName
<span class="kwrd">WHILE</span> <span class="preproc">@@FETCH_STATUS</span> = 0
<span class="kwrd">BEGIN</span>
<span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'ALTER TABLE '</span> + @tableName + <span class="str">' CHECK CONSTRAINT ALL'</span>
<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span>
<span class="kwrd">FETCH</span> <span class="kwrd">NEXT</span> <span class="kwrd">FROM</span> tableCursor <span class="kwrd">INTO</span> @tableName
<span class="kwrd">END</span>
<span class="kwrd">CLOSE</span> tableCursor
<span class="kwrd">DEALLOCATE</span> tableCursor
</pre>
<div>
Note that I've deliberately left behind the two columns I created in (3), because they have no negative impact on my application, and they allow me to repeat this process as many times as I need by emptying the database, and re-running (4) and (5).<br />
<br />
<b><br /></b>
<b>Improvements?</b><br />
<b><br /></b>
I find this technique for migrating data into the cloud useful, but it would be great to find a way to remove the need for all the data shuffling that goes on in (5) just to restore the original identities. Something like the ability to switch on IDENTITY_INSERT for all connections would make things a whole lot nicer.<br />
<br />
If you have any thoughts or suggestions to share for making this cleaner, I'd love to hear from you!<br />
</div>
Chris Laceyhttp://www.blogger.com/profile/10405697803215160094noreply@blogger.com1tag:blogger.com,1999:blog-4978326367697120061.post-12778292810148783392013-03-19T19:29:00.002+01:002013-03-19T19:46:03.572+01:00Comparing SQL Server SchemasIf you're using Entity Framework code-first without database migrations, you'll be only too aware of the pain of updating the live database schema whenever you make changes to the model.<br />
<br />
Assuming you're using a DropCreateDatabaseIfModelChanges initializer on your debug build, your development database will be automatically rebuilt whenever the model changes, but (assuming you don't want to lose data), any changes will need to be manually made on the live server before deployment.<br />
<br />
The following scripts are what I use to identify the differences between development and live schemas.<br />
<br />
<b>1. Create a linked server for Live on your development machine</b>
<br />
<pre class="csharpcode"><span class="kwrd">EXEC</span> sp_addlinkedserver <span class="str">'myLiveServer'</span>, N<span class="str">'SQL Server'</span>, <span class="str">'www.myserver.com'</span>
<span class="kwrd">GO</span>
<span class="kwrd">EXEC</span> sp_addlinkedsrvlogin
@rmtsrvname = <span class="str">'myLiveServer'</span>,
@useself = <span class="str">'FALSE'</span>,
@locallogin = <span class="kwrd">null</span>,
@rmtuser = <span class="str">'sa'</span>,
@rmtpassword = <span class="str">'myPassword'</span>
<span class="kwrd">GO</span></pre>
<br />
<b>2. Compare the two schemas with reference to INFORMATION_SCHEMA</b>
<br />
<pre class="csharpcode"><span class="kwrd">SELECT</span>
<span class="kwrd">Local</span>.TABLE_NAME,
<span class="kwrd">Local</span>.COLUMN_NAME,
<span class="kwrd">Local</span>.DATA_TYPE,
<span class="kwrd">Local</span>.CHARACTER_MAXIMUM_LENGTH,
<span class="kwrd">Local</span>.IS_NULLABLE,
Remote.TABLE_NAME [Remote <span class="kwrd">Table</span>],
Remote.COLUMN_NAME [Remote <span class="kwrd">Column</span>]
<span class="kwrd">FROM</span> myDatabase.INFORMATION_SCHEMA.COLUMNS [<span class="kwrd">Local</span>]
<span class="kwrd">FULL</span> <span class="kwrd">OUTER</span> <span class="kwrd">JOIN</span> myLiveServer.myDatabase.INFORMATION_SCHEMA.COLUMNS [Remote]
<span class="kwrd">ON</span> Remote.TABLE_NAME = <span class="kwrd">Local</span>.TABLE_NAME
<span class="kwrd">AND</span> Remote.COLUMN_NAME = <span class="kwrd">Local</span>.COLUMN_NAME
<span class="kwrd">AND</span> Remote.DATA_TYPE = <span class="kwrd">Local</span>.DATA_TYPE
<span class="kwrd">AND</span> (
Remote.CHARACTER_MAXIMUM_LENGTH = <span class="kwrd">Local</span>.CHARACTER_MAXIMUM_LENGTH
<span class="kwrd">OR</span> (
Remote.CHARACTER_MAXIMUM_LENGTH <span class="kwrd">IS</span> <span class="kwrd">NULL</span>
<span class="kwrd">AND</span> <span class="kwrd">Local</span>.CHARACTER_MAXIMUM_LENGTH <span class="kwrd">IS</span> <span class="kwrd">NULL</span>
)
)
<span class="kwrd">AND</span> Remote.IS_NULLABLE = <span class="kwrd">Local</span>.IS_NULLABLE
<span class="kwrd">WHERE</span>
<span class="kwrd">Local</span>.TABLE_NAME <span class="kwrd">IS</span> <span class="kwrd">NULL</span>
<span class="kwrd">OR</span> <span class="kwrd">Local</span>.COLUMN_NAME <span class="kwrd">IS</span> <span class="kwrd">NULL</span>
<span class="kwrd">OR</span> Remote.TABLE_NAME <span class="kwrd">IS</span> <span class="kwrd">NULL</span>
<span class="kwrd">OR</span> Remote.COLUMN_NAME <span class="kwrd">IS</span> <span class="kwrd">NULL</span>
<span class="kwrd">ORDER</span> <span class="kwrd">BY</span>
Remote.TABLE_NAME,
Remote.COLUMN_NAME,
<span class="kwrd">Local</span>.TABLE_NAME,
<span class="kwrd">Local</span>.COLUMN_NAME</pre>
<br />
<b>3. Make the manual modifications</b><br />
<br />
Running script (2) will return a set of modifications to be made on the live server.<br />
<br />
Items that appear with 'Remote Table' and 'Remote Column' as NULL need to be <b>added </b>to the live server, according to the details given in the other columns. Items that have entries for 'Remote Table' and 'Remote Column' but NULLs elsewhere need to be <b>removed </b>from the live server.<br />
<br />
So, in the example shown below, a new column <i>HomeAddress</i> of type nvarchar(max) needs to be added to the <i>Students</i> table on the live server. And <i>Telephone2</i> needs to be removed from the <i>Students</i> table.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-ST9vx390CLM/UUisO4lJvyI/AAAAAAAAAJQ/oLDi7OHjFZY/s1600/SQLSMgmtStudio.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="35" src="http://3.bp.blogspot.com/-ST9vx390CLM/UUisO4lJvyI/AAAAAAAAAJQ/oLDi7OHjFZY/s320/SQLSMgmtStudio.png" width="320" /></a></div>
<br />
<b>4. Update the hash</b><br />
<br />
Script (2) can be run as many times as needed. When it returns no rows, you know that the schemas on both development and live servers are the same. In order to let Entity Framework work with the new version of the schema, you'll also need to update the hash manually - copying the hash that was generated automatically on the development server.<br />
<pre class="csharpcode"><span class="kwrd">UPDATE</span> myLiveServer.myDatabase.dbo.EdmMetadata
<span class="kwrd">SET</span> ModelHash = (<span class="kwrd">SELECT</span> ModelHash <span class="kwrd">FROM</span> myDatabase.dbo.EdmMetadata)</pre>
<br />
<b>5. Copy over the code</b><br />
<br />
Now you can deploy the new version of the code onto the live server, safe in the knowledge that the required schema is ready and waiting.Chris Laceyhttp://www.blogger.com/profile/10405697803215160094noreply@blogger.com0tag:blogger.com,1999:blog-4978326367697120061.post-57053420692534269762011-06-29T19:43:00.024+02:002011-10-19T05:14:41.584+02:00Google Contacts SynchronisationHaving recently migrated to Google Apps at the organisation in which I work, one of the glaring omissions we discovered was the concept of a <i>Global Address List</i> to which I'd personally become accustomed in MS Exchange. Despite this persistently being at the top of the feature request list with Google, there still seems to be no easy way of sharing contacts between users, and so I decided to write some code to implement it programmatically.<br /><br />The application simply copies all contacts from one account (which might be designated the "master") to another, flagging them as it does so in order that they can be deleted/updated when the program is scheduled to run regularly. As such, the destination account's existing contacts are never modified. This works within Google Apps, and also as a means of syncing contacts between regular Gmail accounts.<br /><br />To obtain the reference assemblies, you'll need to get the GData .NET Client Library from <a href="http://code.google.com/p/google-gdata/">http://code.google.com/p/google-gdata/</a>, or I've included the relevant DLLs in the source download at <a href="http://www.cslacey.co.uk/blog/downloads/CSL.GoogleContactsSync.zip">www.cslacey.co.uk/blog/downloads/CSL.GoogleContactsSync.zip</a>.<br /><br />The latter link also contains the compiled application - to start using it straight away, simply enter the relevant credentials into the .config file within /bin/Release, and run CSL.GoogleContactsSync.ConsoleApp.exe.<br /><br /><i>(Assembly references: System.Configuration, Google.GData.Client, Google.GData.Contacts, Google.GData.Extensions)</i><br /><!-- code formatted by http://manoli.net/csharpformat/ --><pre class="csharpcode"><br /><span class="kwrd">using</span> System;<br /><span class="kwrd">using</span> System.Collections.Generic;<br /><span class="kwrd">using</span> System.Configuration;<br /><span class="kwrd">using</span> System.Linq;<br /><span class="kwrd">using</span> Google.Contacts;<br /><span class="kwrd">using</span> Google.GData.Client;<br /><span class="kwrd">using</span> Google.GData.Contacts;<br /><br /><span class="kwrd">namespace</span> CSL.GoogleContactsSync.ConsoleApp<br />{<br /> <span class="kwrd">class</span> Program<br /> {<br /> <span class="kwrd">const</span> <span class="kwrd">int</span> MAX_BATCH_SIZE = 100;<br /> <span class="kwrd">const</span> <span class="kwrd">string</span> GOOGLE_APP_NAME = <span class="str">"CSL.GoogleContactsSync.ConsoleApp"</span>;<br /> <span class="kwrd">const</span> <span class="kwrd">string</span> GAL_FIELD_KEY = <span class="str">"Origin"</span>;<br /> <span class="kwrd">const</span> <span class="kwrd">string</span> GAL_FIELD_VALUE = <span class="str">"Global Address List"</span>;<br /><br /> <span class="kwrd">static</span> <span class="kwrd">void</span> Main(<span class="kwrd">string</span>[] args)<br /> {<br /> List<Contact> sourceContacts = GetSourceContacts(<br /> ConfigurationManager.AppSettings[<span class="str">"SourceGoogleUsername"</span>],<br /> ConfigurationManager.AppSettings[<span class="str">"SourceGooglePassword"</span>]);<br /> DeleteExistingGlobalContacts(<br /> ConfigurationManager.AppSettings[<span class="str">"DestinationGoogleUsername"</span>],<br /> ConfigurationManager.AppSettings[<span class="str">"DestinationGooglePassword"</span>]);<br /> InsertContacts(sourceContacts,<br /> ConfigurationManager.AppSettings[<span class="str">"DestinationGoogleUsername"</span>],<br /> ConfigurationManager.AppSettings[<span class="str">"DestinationGooglePassword"</span>]);<br /> }<br /><br /> <span class="rem">/// <summary></span><br /> <span class="rem">/// Retrieve all contacts from the specified account in a form</span><br /> <span class="rem">/// suitable for insertion into other destination accounts</span><br /> <span class="rem">/// </summary></span><br /> <span class="kwrd">private</span> <span class="kwrd">static</span> List<Contact> GetSourceContacts(<br /> <span class="kwrd">string</span> username, <span class="kwrd">string</span> password)<br /> {<br /> List<Contact> result = <span class="kwrd">new</span> List<Contact>();<br /><br /> RequestSettings requestSettings = <span class="kwrd">new</span> RequestSettings(<br /> GOOGLE_APP_NAME, username, password);<br /> ContactsRequest contactsRequest =<br /> <span class="kwrd">new</span> ContactsRequest(requestSettings);<br /> Feed<Contact> feed = contactsRequest.GetContacts();<br /> feed.AutoPaging = <span class="kwrd">true</span>;<br /><br /> <span class="kwrd">foreach</span> (Contact contact <span class="kwrd">in</span> feed.Entries)<br /> {<br /> <span class="rem">// Remove all group membership, as groups may not exist in</span><br /> <span class="rem">// destination account</span><br /> <span class="kwrd">while</span> (contact.GroupMembership.Count > 0)<br /> {<br /> contact.GroupMembership.RemoveAt(0);<br /> }<br /><br /> <span class="rem">// Remove all user defined fields, as they may not exist in</span><br /> <span class="rem">// destination account</span><br /> <span class="kwrd">while</span> (contact.ContactEntry.UserDefinedFields.Count > 0)<br /> {<br /> contact.ContactEntry.UserDefinedFields.RemoveAt(0);<br /> }<br /><br /> <span class="rem">// Flag retrieved contact as originating from the GAL</span><br /> contact.ContactEntry.UserDefinedFields.Add(<br /> <span class="kwrd">new</span> UserDefinedField(GAL_FIELD_VALUE, GAL_FIELD_KEY));<br /><br /> Console.WriteLine(String.Format(<span class="str">"RETRIEVING CONTACT: {0}"</span>,<br /> contact.Name.FullName));<br /> result.Add(contact);<br /> }<br /><br /> <span class="kwrd">return</span> result;<br /> }<br /><br /> <span class="rem">/// <summary></span><br /> <span class="rem">/// Delete all contacts which have been flagged</span><br /> <span class="rem">/// as originating from the GAL</span><br /> <span class="rem">/// </summary></span><br /> <span class="kwrd">private</span> <span class="kwrd">static</span> <span class="kwrd">void</span> DeleteExistingGlobalContacts(<br /> <span class="kwrd">string</span> username, <span class="kwrd">string</span> password)<br /> {<br /> RequestSettings requestSettings = <span class="kwrd">new</span> RequestSettings(<br /> GOOGLE_APP_NAME, username, password);<br /> ContactsRequest contactsRequest =<br /> <span class="kwrd">new</span> ContactsRequest(requestSettings);<br /> Feed<Contact> feed = contactsRequest.GetContacts();<br /> feed.AutoPaging = <span class="kwrd">false</span>;<br /><br /> List<Contact> contactsToDelete;<br /><br /> <span class="kwrd">do</span><br /> {<br /> contactsToDelete = <span class="kwrd">new</span> List<Contact>();<br /><br /> <span class="kwrd">foreach</span> (Contact contact <span class="kwrd">in</span> feed.Entries)<br /> {<br /> <span class="kwrd">for</span> (<span class="kwrd">int</span> i = 0;<br /> i < contact.ContactEntry.UserDefinedFields.Count; i++)<br /> {<br /> <span class="kwrd">if</span> (contact.ContactEntry.UserDefinedFields[i].Key<br /> == GAL_FIELD_KEY<br /> && contact.ContactEntry.UserDefinedFields[i].Value<br /> == GAL_FIELD_VALUE)<br /> {<br /> <span class="rem">// Contact originated from GAL, so add it to the</span><br /> <span class="rem">// delete batch</span><br /> Console.WriteLine(String.Format(<span class="str">"PENDING DELETE: {0}"</span>,<br /> contact.Name.FullName));<br /> contactsToDelete.Add(contact);<br /> }<br /> }<br /> }<br /><br /> Console.WriteLine(<span class="str">"BATCH DELETING {0} Contacts"</span>,<br /> contactsToDelete.Count);<br /> contactsRequest.Batch(contactsToDelete, feed,<br /> GDataBatchOperationType.delete);<br /><br /> <span class="rem">// Re-intialise batch and feed for next execution</span><br /> contactsToDelete = <span class="kwrd">new</span> List<Contact>();<br /> feed = contactsRequest.GetContacts();<br /> }<br /> <span class="kwrd">while</span> (feed.Entries.Count() > 0 && contactsToDelete.Count > 0);<br /> }<br /><br /> <span class="rem">/// <summary></span><br /> <span class="rem">/// Insert the specified contacts</span><br /> <span class="rem">/// </summary></span><br /> <span class="kwrd">private</span> <span class="kwrd">static</span> <span class="kwrd">void</span> InsertContacts(<br /> List<Contact> contacts, <span class="kwrd">string</span> username, <span class="kwrd">string</span> password)<br /> {<br /> RequestSettings requestSettings = <span class="kwrd">new</span> RequestSettings(<br /> GOOGLE_APP_NAME, username, password);<br /> ContactsRequest contactsRequest =<br /> <span class="kwrd">new</span> ContactsRequest(requestSettings);<br /> Feed<Contact> feed = contactsRequest.GetContacts();<br /> List<Contact> contactsToInsert = <span class="kwrd">new</span> List<Contact>();<br /><br /> <span class="kwrd">foreach</span> (Contact contact <span class="kwrd">in</span> contacts)<br /> {<br /> Console.WriteLine(String.Format(<span class="str">"PENDING INSERT: {0}"</span>,<br /> contact.Name.FullName));<br /> contactsToInsert.Add(contact);<br /><br /> <span class="kwrd">if</span> (contactsToInsert.Count == MAX_BATCH_SIZE)<br /> {<br /> Console.WriteLine(<span class="str">"BATCH INSERTING {0} Contacts"</span>,<br /> contactsToInsert.Count);<br /> contactsRequest.Batch(contactsToInsert, feed,<br /> GDataBatchOperationType.insert);<br /><br /> <span class="rem">// Re-initialise batch for next execution</span><br /> contactsToInsert = <span class="kwrd">new</span> List<Contact>();<br /> }<br /> }<br /><br /> Console.WriteLine(<span class="str">"BATCH INSERTING {0} Contacts"</span>,<br /> contactsToInsert.Count);<br /> contactsRequest.Batch(contactsToInsert, feed,<br /> GDataBatchOperationType.insert);<br /> }<br /> }<br />}<br /></pre>Chris Laceyhttp://www.blogger.com/profile/10405697803215160094noreply@blogger.com0tag:blogger.com,1999:blog-4978326367697120061.post-56118309909448215402009-11-02T10:23:00.020+01:002009-11-02T12:35:40.467+01:00Migrating Classic WCF to the Service BusThe Service Bus (within Microsoft's new .NET Services product) is a nice bit of technology that allows two-way communication between nodes on the Internet, regardless of the presence of firewalls, NAT gateways or other complicated network topologies. It makes possible all sorts of useful tools, such as Rich's and Rob's <a href="http://socketshifter.codeplex.com/">SocketShifter</a>.<br /><br />What's really nice about Service Bus's implementation, however, (and often overlooked) is that it's nothing more than a new set of WCF bindings which result in the creation of publicly reachable and discoverable endpoints.<br /><br />With that in mind, I wondered whether I could take my <a href="http://minimalistcoder.blogspot.com/2009/01/self-hosting-wcf.html">previous simple WCF example</a>, and change it to use the Service Bus by doing nothing more than modifying the config. In theory, it seems that this should be possible once the Service Bus becomes a standard part of the .NET framework - but for now it proves difficult. This is because installing the .NET Services CTP will modify your machine.config file such that netTcpRelayBinding cannot be added again as an extension within an app.config, but HAS to be added in that way for machines without the CTP installed (i.e. all of your customers' servers, or indeed an Azure VM).<br /><br />The only sensible way to achieve this, therefore, is by explicitly setting options within the code itself - but this proved pretty easy.<br /><br /><br /><strong>Messaging Model</strong><br /><br />Let's start by modifying the code of the previous example slightly so that we emphasise the delivery of a message and a reply. We'll make the <em>GetTimestamp</em> method take a string, which gets printed at the server and copied back within the reply:<br /><br /><em>SharedTypes.IHealthCheck</em><br />...<br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;">[<span style="color: #2b91af;">OperationContract</span>]</pre><pre style="margin: 0px;"><span style="color: blue;">string</span> GetTimestamp(<span style="color: blue;">string</span> serviceName);</pre></div><br /><em>Server.HealthCheck</em><br />...<br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">string</span> GetTimestamp(<span style="color: blue;">string</span> serviceName)</pre><pre style="margin: 0px;">{</pre><pre style="margin: 0px;"> <span style="color: #2b91af;">Console</span>.WriteLine(<span style="color: #a31515;">"Request received from {0}"</span>, serviceName);</pre><pre style="margin: 0px;"> <span style="color: blue;">return</span> <span style="color: #2b91af;">String</span>.Format(<span style="color: #a31515;">"{0}: {1}"</span>, serviceName, <span style="color: #2b91af;">DateTime</span>.Now.ToString());</pre><pre style="margin: 0px;">}</pre></div><br /><em>Client.Program</em><br />...<br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">while</span> (<span style="color: blue;">true</span>)</pre><pre style="margin: 0px;">{</pre><pre style="margin: 0px;"> <span style="color: #2b91af;">Console</span>.WriteLine(healthCheck.GetTimestamp(<span style="color: #a31515;">"My service"</span>));</pre></div><br /><br /><br /><strong>Migrating to Service Bus</strong><br /><br />In both the Server and the Client, we now need to configure the endpoint as follows (If you don't yet have a .NET Services account, you can create one at <a href="http://www.microsoft.com/windowsazure/developers/dotnetservices/">http://www.microsoft.com/windowsazure/developers/dotnetservices/</a>):<br /><ul><br /><li>Specify a Service Bus address (sb://yourAccount.servicebus.windows.net/...)<br /><li>Specify an appropriate Service Bus binding (here, we'll use NetTcpRelayBinding)<br /><li>Specify a new behaviour which contains your Service Bus credentials<br /></ul><br />As mentioned before, the only safe way of doing this at the moment is programatically:<br /><br /><em>Server.Program</em><br /><br />Configure the endpoint of the ServiceHost, just before opening it:<br /><br /><em>(Assembly reference: Microsoft.ServiceBus)</em><br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">using</span> System.ServiceModel.Description;</pre><pre style="margin: 0px;"><span style="color: blue;">using</span> Microsoft.ServiceBus;</pre></div>...<br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">static</span> <span style="color: blue;">void</span> Main(<span style="color: blue;">string</span>[] args)</pre><pre style="margin: 0px;">{</pre><pre style="margin: 0px;"> <span style="color: green;">// Construct EndpointBehaviour for SB username and password</span></pre><pre style="margin: 0px;"> <span style="color: #2b91af;">TransportClientEndpointBehavior</span> serviceBusCredential =</pre><pre style="margin: 0px;"> <span style="color: blue;">new</span> <span style="color: #2b91af;">TransportClientEndpointBehavior</span>();</pre><pre style="margin: 0px;"> serviceBusCredential.CredentialType =</pre><pre style="margin: 0px;"> <span style="color: #2b91af;">TransportClientCredentialType</span>.UserNamePassword;</pre><pre style="margin: 0px;"> serviceBusCredential.Credentials.UserName.UserName = <span style="color: #a31515;">"yourAccount"</span>;</pre><pre style="margin: 0px;"> serviceBusCredential.Credentials.UserName.Password = <span style="color: #a31515;">"yourPassword"</span>;</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: #2b91af;">ServiceHost</span> serviceHost = <span style="color: blue;">new</span> <span style="color: #2b91af;">ServiceHost</span>(<span style="color: blue;">typeof</span>(<span style="color: #2b91af;">HealthCheck</span>));</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: green;">// Set the binding manually (overriding the app.config)</span></pre><pre style="margin: 0px;"> <span style="color: #2b91af;">ServiceEndpoint</span> serviceEndpoint = serviceHost.Description.Endpoints[0];</pre><pre style="margin: 0px;"> serviceEndpoint.Address = <span style="color: blue;">new</span> <span style="color: #2b91af;">EndpointAddress</span>(</pre><pre style="margin: 0px;"> <span style="color: #a31515;">"sb://yourAccount.servicebus.windows.net/test/"</span>);</pre><pre style="margin: 0px;"> serviceEndpoint.Binding = <span style="color: blue;">new</span> <span style="color: #2b91af;">NetTcpRelayBinding</span>();</pre><pre style="margin: 0px;"> serviceEndpoint.Behaviors.Add(serviceBusCredential);</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> serviceHost.Open();</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: green;">// Keep the process running (hence the service open) for an hour</span></pre><pre style="margin: 0px;"> System.Threading.<span style="color: #2b91af;">Thread</span>.Sleep(3600000);</pre><pre style="margin: 0px;">}</pre></div><br /><br /><em>Client.HealthCheck</em><br /><br />Configure the endpoint of the ClientBase class we're inheriting from on construction, to ensure we only do it once:<br /><br /><em>(Assembly reference: Microsoft.ServiceBus)</em><br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">using</span> Microsoft.ServiceBus;</pre></div>...<br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">public</span> HealthCheck()</pre><pre style="margin: 0px;">{</pre><pre style="margin: 0px;"> <span style="color: green;">// Construct EndpointBehaviour for SB username and password</span></pre><pre style="margin: 0px;"> <span style="color: #2b91af;">TransportClientEndpointBehavior</span> serviceBusCredential =</pre><pre style="margin: 0px;"> <span style="color: blue;">new</span> <span style="color: #2b91af;">TransportClientEndpointBehavior</span>();</pre><pre style="margin: 0px;"> serviceBusCredential.CredentialType =</pre><pre style="margin: 0px;"> <span style="color: #2b91af;">TransportClientCredentialType</span>.UserNamePassword;</pre><pre style="margin: 0px;"> serviceBusCredential.Credentials.UserName.UserName = <span style="color: #a31515;">"yourAccount"</span>;</pre><pre style="margin: 0px;"> serviceBusCredential.Credentials.UserName.Password = <span style="color: #a31515;">"yourPassword"</span>;</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: green;">// Set the binding manually (overriding the app.config)</span></pre><pre style="margin: 0px;"> <span style="color: blue;">this</span>.Endpoint.Address = <span style="color: blue;">new</span> <span style="color: #2b91af;">EndpointAddress</span>(</pre><pre style="margin: 0px;"> <span style="color: #a31515;">"sb://yourAccount.servicebus.windows.net/test/"</span>);</pre><pre style="margin: 0px;"> <span style="color: blue;">this</span>.Endpoint.Binding = <span style="color: blue;">new</span> <span style="color: #2b91af;">NetTcpRelayBinding</span>();</pre><pre style="margin: 0px;"> <span style="color: blue;">this</span>.Endpoint.Behaviors.Add(serviceBusCredential);</pre><pre style="margin: 0px;">}</pre></div><br /><br />That's it! If you fire up the server and the client, you should now see them working exactly as before - except you can now have the server running on your home machine behind a NAT gateway, and the client running on your office machine behind a corporate firewall!Chris Laceyhttp://www.blogger.com/profile/10405697803215160094noreply@blogger.com0tag:blogger.com,1999:blog-4978326367697120061.post-64813759449870327982009-06-29T17:52:00.019+02:002009-10-18T22:41:19.928+02:00Triple Boot MacBookHaving bought a shiny new MacBook a few months ago, I got a little bored of OS X and wanted to make most use of its Intel chipset - to let me boot into Windows, OS X and Linux all on the same box.<br /><br />There are quite a few long-winded, complicated explanations out there that describe how to achieve this, but actually it's quite simple once you know what to avoid. The following procedure has been tested using OS X Leopard (I'm assuming it's already installed), Windows XP Professional SP2 and Ubuntu 9.04.<br /><br /><br /><strong>1. Install the rEFIt Bootloader</strong><br /><br />Bootcamp seems to have problems working with more than two parallel OS's. So inside OS X, download and install rEFIt from <a href="http://refit.sourceforge.net" target="_blank">http://refit.sourceforge.net</a>, which is an alternative bootloader providing support for the Extensible Firmware Interface that Macs use.<br /><br />Reboot after installing to check that you get the new rEFIt bootup screen, and choose to boot into OS X again.<br /><br /><br /><strong>2. Partition your drive</strong><br /><br />You'll need at least three partitions (one for OS X, one for XP, and one for Ubuntu). Ideally, though, you might choose to have an additional swap partition for Linux, and perhaps a shared data partition which you can access from all three OS's. You'll also probably already have your first partition defined as an EFI system partition - it's best not to fiddle with that.<br /><br />Limitations to bear in mind are that the Ubuntu GRUB bootloader must be installed on the third partition, and XP on the fourth (due to their dependence on PC BIOS and the MBR, both seemingly have to be within the first four partitions, and XP must be as "low down" as possible). XP also seems to be unable to see any more than the first four partitions (as it doesn't support the GUID partition scheme), so make sure that your shared data is within the first four.<br /><br />I found my ideal partition map to be as follows:<br /><ol><br /><li>EFI - 200MB (untouched, as from the factory)<br /><li>OS X - 50GB (the original OS X partition, shrunk but otherwise not modified)<br /><li>Shared Data (plus GRUB bootloader for Ubuntu) - 20GB<br /><li>XP - 50GB<br /><li>Ubuntu - 25GB<br /><li>Linux Swap - 2.5GB<br /></li></ol><br />In OS X, enter the command:<br /><br /><pre>diskutil list</pre><br />...and you should see that your existing disk structure looks something like this:<br /><pre>/dev/disk0<br />#: type name size identifier<br />0: GUID_partition_scheme *149.1 GB disk0<br />1: EFI 200.0 MB disk0s1<br />2: Apple_HFS Macintosh HD 148.9 GB disk0s2</pre><br />Given that you want to resize the large partition and make lots of other smaller ones, you should then issue the following command (without linebreaks, and assuming my desired partition map):<br /><pre><br />diskutil resizeVolume disk0s2 50G<br /> "HFS+" "Data" 20G<br /> "MS-DOS FAT32" "Windows" 50G<br /> "HFS+" "Linux" 25G<br /> "HFS+" "Linux Swap" 2.5G<br /></pre><br />In actual fact, the formats selected here don't matter all that much, because both Windows and Linux will reformat the relevant drives upon installation. Make sure, however, that for the Data partition you initially pick a format that Windows <strong>can't</strong> understand - this will ensure that the Windows partition ends up as drive C upon installation! You can then reformat Data later (as MS-DOS FAT 32, being the only format all three OSs can read and write) using Terminal or Disk Utility in OSX.<br /><br /><br /><strong>3. Install XP</strong><br /><br />Insert your XP boot CD, and restart the machine while holding down the Option (alt) key. Choose the option to boot from CD.<br /><br />Install Windows as normal, selecting the fourth partition (which should be labelled C:). I also chose to reformat the drive using NTFS, as this provides better performance than FAT32.<br /><br />As Windows needs to restart the machine a couple of times during this process, ensure that each time you select the "Windows" option which should appear in the rEFIt menu, to allow the installation to carry on.<br /><br />Once this is complete, install the relevant Windows drivers for the Mac hardware using the "Boot Camp" installer for Windows (on the Mac OS X Install DVD).<br /><br /><br /><strong>4. Install Ubuntu</strong><br /><br />Insert a bootable Ubuntu CD, and again restart the machine while holding down the Option (alt) key. Choose the option to boot from CD.<br /><br />Install Ubuntu as normal, selecting manual configuration of partitions when prompted. On the subsequent screen, you should select /dev/sda5 to be formatted with ext3 (my preference), and mounted as "/". Assuming my partition map, you should also select /dev/sda6 to be formatted as a swap partition.<br /><br />Important: On the final screen of the installer, select 'Advanced...' and change the location of the GRUB bootloader to be /dev/sda3. This is important, as otherwise Ubuntu's bootloader will attempt to take over the whole disk, which can have some odd results working alongside rEFIt.<br /><br /><br /><strong>5. Celebrate</strong><br /><br />That's it! The only thing that doesn't work for me (bizarrely) is restarting from within Ubuntu... I have to make sure I only ever choose "Shut Down"!Chris Laceyhttp://www.blogger.com/profile/10405697803215160094noreply@blogger.com0tag:blogger.com,1999:blog-4978326367697120061.post-44243850869551322632009-01-22T09:22:00.016+01:002009-07-09T20:13:21.066+02:00Obfuscating QueryString ParametersI need to provide a link to a URL which contains some parameters I'd like to obfuscate. Specifically, within an automatically generated Email, instead of embedding a link /sendMessage.aspx?address=foo@bar.com&expiry=2009-01-22Z13:30 , I'd prefer /sendMessage.aspx?token=s0MEth1ngUnfath0mable!<br /><br />Broken down, my requirements for obfuscation are:<br /><ul><li><strong>Serialize</strong> an object which contains my parameters (address and expiry), so that it can be transmitted to a different server (i.e. the web server)</li><li><strong>Encrypt</strong> the serialized bytes to prevent tampering</li><li><strong>Make Quotable</strong> the encrypted bytes so that they can be included within a URL querystring (albeit after a suitable UrlEncode)</li></ul><br /><br /><strong>Key Generation</strong><br /><br />My security requirements are pretty basic, as I'm not trying to protect anything desperately important here - rather prevent basic tampering by spotting patterns in the quotable string. Therefore, I'll keep things as simple as possible and take some shortcuts, for example using a basic security algorithm (DES), and embedding the encryption key within my source code.<br /><br />Firstly, we'll need to generate a key upon which the encryption and decryption can occur:<br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">using</span> System;</pre><pre style="margin: 0px;"><span style="color: blue;">using</span> System.IO;</pre><pre style="margin: 0px;"><span style="color: blue;">using</span> System.Security.Cryptography;</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"><span style="color: blue;">namespace</span> ObfuscationTesting</pre><pre style="margin: 0px;">{</pre><pre style="margin: 0px;"> <span style="color: blue;">class</span> <span style="color: #2b91af;">Program</span></pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: blue;">static</span> <span style="color: blue;">void</span> Main(<span style="color: blue;">string</span>[] args)</pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: green;">// Generate a suitable key</span></pre><pre style="margin: 0px;"> <span style="color: #2b91af;">DESCryptoServiceProvider</span> desCryptoServiceProvider =</pre><pre style="margin: 0px;"> <span style="color: blue;">new</span> <span style="color: #2b91af;">DESCryptoServiceProvider</span>();</pre><pre style="margin: 0px;"> desCryptoServiceProvider.GenerateKey();</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: green;">// Represent key as a quotable string</span></pre><pre style="margin: 0px;"> <span style="color: blue;">string</span> base64EncodedKey =</pre><pre style="margin: 0px;"> <span style="color: #2b91af;">Convert</span>.ToBase64String(desCryptoServiceProvider.Key);</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: green;">// Save to file</span></pre><pre style="margin: 0px;"> <span style="color: #2b91af;">File</span>.WriteAllText(<span style="color: #a31515;">@"C:\key.txt"</span>, base64EncodedKey);</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;">}</pre></div><br /><br /><strong>Obfuscator Class</strong><br /><br />Now, we'll create a static class that takes any serializable object and returns us a string which represents that object (and, naturally, allows us to re-create the original object from such a string):<br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">using</span> System;</pre><pre style="margin: 0px;"><span style="color: blue;">using</span> System.IO;</pre><pre style="margin: 0px;"><span style="color: blue;">using</span> System.Runtime.Serialization.Formatters.Binary;</pre><pre style="margin: 0px;"><span style="color: blue;">using</span> System.Security.Cryptography;</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"><span style="color: blue;">namespace</span> ObfuscationTesting</pre><pre style="margin: 0px;">{</pre><pre style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">Obfsucator</span></pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: green;">// The key previously generated</span></pre><pre style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: blue;">const</span> <span style="color: blue;">string</span> KEY = <span style="color: #a31515;">"MXvUAmUobjA="</span>;</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">string</span> Obfuscate(<span style="color: blue;">object</span> targetObject)</pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: green;">// Binary serialize</span></pre><pre style="margin: 0px;"> <span style="color: #2b91af;">MemoryStream</span> memoryStream = <span style="color: blue;">new</span> <span style="color: #2b91af;">MemoryStream</span>();</pre><pre style="margin: 0px;"> <span style="color: #2b91af;">BinaryFormatter</span> binaryFormatter = <span style="color: blue;">new</span> <span style="color: #2b91af;">BinaryFormatter</span>();</pre><pre style="margin: 0px;"> binaryFormatter.Serialize(memoryStream, targetObject);</pre><pre style="margin: 0px;"> <span style="color: blue;">byte</span>[] serialized = memoryStream.GetBuffer();</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: green;">// Encrypt</span></pre><pre style="margin: 0px;"> <span style="color: #2b91af;">DESCryptoServiceProvider</span> desCryptoServiceProvider =</pre><pre style="margin: 0px;"> <span style="color: blue;">new</span> <span style="color: #2b91af;">DESCryptoServiceProvider</span>();</pre><pre style="margin: 0px;"> desCryptoServiceProvider.Key = <span style="color: #2b91af;">Convert</span>.FromBase64String(KEY);</pre><pre style="margin: 0px;"> desCryptoServiceProvider.IV = <span style="color: #2b91af;">Convert</span>.FromBase64String(KEY);</pre><pre style="margin: 0px;"> <span style="color: blue;">byte</span>[] encrypted = desCryptoServiceProvider</pre><pre style="margin: 0px;"> .CreateEncryptor()</pre><pre style="margin: 0px;"> .TransformFinalBlock(serialized, 0, serialized.Length);</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: green;">// Render as string</span></pre><pre style="margin: 0px;"> <span style="color: blue;">string</span> quotable = <span style="color: #2b91af;">Convert</span>.ToBase64String(encrypted);</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: blue;">return</span> quotable;</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">object</span> Deobfuscate(<span style="color: blue;">string</span> quotable)</pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: green;">// Retrieve the bytes from the quotable string</span></pre><pre style="margin: 0px;"> <span style="color: blue;">byte</span>[] encrypted = <span style="color: #2b91af;">Convert</span>.FromBase64String(quotable);</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: green;">// Decrypt</span></pre><pre style="margin: 0px;"> <span style="color: #2b91af;">DESCryptoServiceProvider</span> desCryptoServiceProvider =</pre><pre style="margin: 0px;"> <span style="color: blue;">new</span> <span style="color: #2b91af;">DESCryptoServiceProvider</span>();</pre><pre style="margin: 0px;"> desCryptoServiceProvider.Key = <span style="color: #2b91af;">Convert</span>.FromBase64String(KEY);</pre><pre style="margin: 0px;"> desCryptoServiceProvider.IV = <span style="color: #2b91af;">Convert</span>.FromBase64String(KEY);</pre><pre style="margin: 0px;"> <span style="color: blue;">byte</span>[] serialized = desCryptoServiceProvider</pre><pre style="margin: 0px;"> .CreateDecryptor()</pre><pre style="margin: 0px;"> .TransformFinalBlock(encrypted, 0, encrypted.Length);</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: green;">// Deserialize</span></pre><pre style="margin: 0px;"> <span style="color: #2b91af;">MemoryStream</span> memoryStream = <span style="color: blue;">new</span> <span style="color: #2b91af;">MemoryStream</span>(serialized);</pre><pre style="margin: 0px;"> <span style="color: #2b91af;">BinaryFormatter</span> binaryFormatter = <span style="color: blue;">new</span> <span style="color: #2b91af;">BinaryFormatter</span>();</pre><pre style="margin: 0px;"> <span style="color: blue;">object</span> deserialized = binaryFormatter.Deserialize(memoryStream);</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: blue;">return</span> deserialized;</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;">}</pre></div><br /><br /><strong>Usage</strong><br /><br />We'll create a class which contains the parameters I'd like to represent, remembering to mark it as serializable:<br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">using</span> System;</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"><span style="color: blue;">namespace</span> ObfuscationTesting</pre><pre style="margin: 0px;">{</pre><pre style="margin: 0px;"> [<span style="color: #2b91af;">Serializable</span>]</pre><pre style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">Token</span></pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">string</span> EmailAddress { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; }</pre><pre style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: #2b91af;">DateTime</span> Expiry { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; }</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;">}</pre></div><br />To construct the URL with the obfuscated parameters, all we need to have is:<br /><br /><em>(Assembly reference: System.Web)</em><br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">using</span> System;</pre><pre style="margin: 0px;"><span style="color: blue;">using</span> System.Web;</pre></div><br />...<br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"> <span style="color: #2b91af;">Token</span> token = <span style="color: blue;">new</span> <span style="color: #2b91af;">Token</span>()</pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> EmailAddress = <span style="color: #a31515;">"foo@bar.com"</span>,</pre><pre style="margin: 0px;"> Expiry = <span style="color: #2b91af;">DateTime</span>.Now.AddHours(1)</pre><pre style="margin: 0px;"> };</pre><pre style="margin: 0px;"> <span style="color: blue;">string</span> url = <span style="color: #2b91af;">String</span>.Format(<span style="color: #a31515;">@"/sendMessage.aspx?token={0}"</span>,</pre><pre style="margin: 0px;"> <span style="color: #2b91af;">HttpUtility</span>.UrlEncode(<span style="color: #2b91af;">Obfsucator</span>.Obfuscate(token)));</pre></div><br />And, on our web page that receives the URL, we just need to de-obfuscate again:<br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"> <span style="color: blue;">string</span> tokenString = Request.QueryString[<span style="color: #a31515;">"token"</span>];</pre><pre style="margin: 0px;"> <span style="color: #2b91af;">Token</span> token = (<span style="color: #2b91af;">Token</span>)<span style="color: #2b91af;">Obfsucator</span>.Deobfuscate(tokenString);</pre><pre style="margin: 0px;"> <span style="color: blue;">if</span> (token.Expiry < <span style="color: #2b91af;">DateTime</span>.Now)</pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: green;">// Token has expired</span></pre><pre style="margin: 0px;"> Response.End();</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;"> <span style="color: blue;">else</span></pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: blue;">string</span> toAddress = token.EmailAddress;</pre><pre style="margin: 0px;"> <span style="color: green;">// etc</span></pre><pre style="margin: 0px;"> }</pre></div>Chris Laceyhttp://www.blogger.com/profile/10405697803215160094noreply@blogger.com0tag:blogger.com,1999:blog-4978326367697120061.post-15536033104707595042009-01-14T14:14:00.003+01:002009-01-14T17:05:02.741+01:00Self-Hosting WCFI'd like to have two separate processes (both under my control) communicate over WCF. What's the easiest way to achieve this? As an example, I'd like Process 1 (a client) to be able to check the health or presence of Process 2 (the server) by periodically requesting the server's local time.<br /><br />To permit communication, we need to define a <em>contract</em> between the two processes. All I'd like this contract (let's call it <em>HealthCheck</em>) to define is an operation <em>GetTimestamp</em>. Given that my two separate processes will be two separate projects in Visual Studio, and that they both need to access this contract, we'll create a third class library called <em>SharedTypes</em> that will contain nothing but the contract.<br /><br /><br /><strong>SharedTypes (contains the contract)</strong><br /><br /><em>Assembly references: System.ServiceModel</em><br /><br />We'll define the contract as an interface, decorated by System.ServiceModel.ServiceContract. The interface's one method signature (<em>GetTimestamp</em>) should be decorated by System.ServiceModel.OperationContract.<br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">using</span> System;</pre><pre style="margin: 0px;"><span style="color: blue;">using</span> System.ServiceModel;</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"><span style="color: blue;">namespace</span> SharedTypes</pre><pre style="margin: 0px;">{</pre><pre style="margin: 0px;"> [<span style="color: #2b91af;">ServiceContract</span>]</pre><pre style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">interface</span> <span style="color: #2b91af;">IHealthCheck</span></pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> [<span style="color: #2b91af;">OperationContract</span>]</pre><pre style="margin: 0px;"> <span style="color: #2b91af;">DateTime</span> GetTimestamp();</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;">}</pre></div><br /><br /><strong>Server</strong><br /><br /><em>Assembly references: SharedTypes, System.ServiceModel</em><br /><br />The server process, perhaps unsurprisingly, will need to implement this contract interface as follows:<br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">using</span> System;</pre><pre style="margin: 0px;"><span style="color: blue;">using</span> SharedTypes;</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"><span style="color: blue;">namespace</span> Server</pre><pre style="margin: 0px;">{</pre><pre style="margin: 0px;"> <span style="color: blue;">class</span> <span style="color: #2b91af;">HealthCheck</span> : <span style="color: #2b91af;">IHealthCheck</span></pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: #2b91af;">DateTime</span> GetTimestamp()</pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: blue;">return</span> <span style="color: #2b91af;">DateTime</span>.Now;</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;">}</pre></div><br />The program's main method will simply instantiate and open a ServiceHost to expose the service, and hang around in the background:<br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">using</span> System;</pre><pre style="margin: 0px;"><span style="color: blue;">using</span> System.ServiceModel;</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"><span style="color: blue;">namespace</span> Server</pre><pre style="margin: 0px;">{</pre><pre style="margin: 0px;"> <span style="color: blue;">class</span> <span style="color: #2b91af;">Program</span></pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: blue;">static</span> <span style="color: blue;">void</span> Main(<span style="color: blue;">string</span>[] args)</pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: #2b91af;">ServiceHost</span> serviceHost = <span style="color: blue;">new</span> <span style="color: #2b91af;">ServiceHost</span>(<span style="color: blue;">typeof</span>(<span style="color: #2b91af;">HealthCheck</span>));</pre><pre style="margin: 0px;"> serviceHost.Open();</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: green;">// Keep the process running (hence the service open) for an hour</span></pre><pre style="margin: 0px;"> System.Threading.<span style="color: #2b91af;">Thread</span>.Sleep(3600000);</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;">}</pre></div><br />Finally, we'll need to specify the address and protocol by which the service can be called. Part of the joy of WCF is allowing this to be configurable by an end-user or sysadmin, so we'll specify a minimal app.config to contain the core settings we require:<br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;"><?</span><span style="color: #a31515;">xml</span><span style="color: blue;"> </span><span style="color: red;">version</span><span style="color: blue;">=</span>"<span style="color: blue;">1.0</span>"<span style="color: blue;"> </span><span style="color: red;">encoding</span><span style="color: blue;">=</span>"<span style="color: blue;">utf-8</span>"<span style="color: blue;"> ?></span></pre><pre style="margin: 0px;"><span style="color: blue;"><</span><span style="color: #a31515;">configuration</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">system.serviceModel</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">services</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">service</span><span style="color: blue;"> </span><span style="color: red;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">Server.HealthCheck</span>"<span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">endpoint</span><span style="color: blue;"> </span><span style="color: red;">address</span><span style="color: blue;">=</span>"<span style="color: blue;">basic</span>"<span style="color: blue;"> </span><span style="color: red;">binding</span><span style="color: blue;">=</span>"<span style="color: blue;">basicHttpBinding</span>"</pre><pre style="margin: 0px;"><span style="color: blue;"> </span><span style="color: red;">contract</span><span style="color: blue;">=</span>"<span style="color: blue;">SharedTypes.IHealthCheck</span>"<span style="color: blue;"> /></span></pre><pre style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">host</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">baseAddresses</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">add</span><span style="color: blue;"> </span><span style="color: red;">baseAddress</span><span style="color: blue;">=</span>"<span style="color: blue;">http://localhost:8080/HealthCheck</span>"<span style="color: blue;"> /></span></pre><pre style="margin: 0px;"><span style="color: blue;"> </</span><span style="color: #a31515;">baseAddresses</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"> </</span><span style="color: #a31515;">host</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"> </</span><span style="color: #a31515;">service</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"> </</span><span style="color: #a31515;">services</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"> </</span><span style="color: #a31515;">system.serviceModel</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"></</span><span style="color: #a31515;">configuration</span><span style="color: blue;">></span></pre></div><br /><br /><strong>Client</strong><br /><br /><em>Assembly references: SharedTypes, System.ServiceModel</em><br /><br />The client will also need to have a class that implements the service contract, but we'll make it such that calls to its methods actually result in a remote call over WCF:<br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">using</span> System;</pre><pre style="margin: 0px;"><span style="color: blue;">using</span> System.ServiceModel;</pre><pre style="margin: 0px;"><span style="color: blue;">using</span> SharedTypes;</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"><span style="color: blue;">namespace</span> Client</pre><pre style="margin: 0px;">{</pre><pre style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">HealthCheck</span> : <span style="color: #2b91af;">ClientBase</span><<span style="color: #2b91af;">IHealthCheck</span>>, <span style="color: #2b91af;">IHealthCheck</span></pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: #2b91af;">DateTime</span> GetTimestamp()</pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: blue;">return</span> <span style="color: blue;">base</span>.Channel.GetTimestamp();</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;">}</pre></div><br />The program's main method will just sit in a loop calling and echoing the result from <em>GetTimestamp:</em><br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;">using</span> System;</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"><span style="color: blue;">namespace</span> Client</pre><pre style="margin: 0px;">{</pre><pre style="margin: 0px;"> <span style="color: blue;">class</span> <span style="color: #2b91af;">Program</span></pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: blue;">static</span> <span style="color: blue;">void</span> Main(<span style="color: blue;">string</span>[] args)</pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: #2b91af;">HealthCheck</span> healthCheck = <span style="color: blue;">new</span> <span style="color: #2b91af;">HealthCheck</span>();</pre><pre style="margin: 0px;"> <span style="color: blue;">while</span> (<span style="color: blue;">true</span>)</pre><pre style="margin: 0px;"> {</pre><pre style="margin: 0px;"> <span style="color: #2b91af;">Console</span>.WriteLine(healthCheck.GetTimestamp());</pre><pre style="margin: 0px;"> </pre><pre style="margin: 0px;"> <span style="color: green;">// Wait for a second</span></pre><pre style="margin: 0px;"> System.Threading.<span style="color: #2b91af;">Thread</span>.Sleep(1000);</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;"> }</pre><pre style="margin: 0px;">}</pre></div><br />Again, we'll need to specify in app.config the location of the server, and the protocol we want to use:<br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white; line-height: 1em;"><pre style="margin: 0px;"><span style="color: blue;"><?</span><span style="color: #a31515;">xml</span><span style="color: blue;"> </span><span style="color: red;">version</span><span style="color: blue;">=</span>"<span style="color: blue;">1.0</span>"<span style="color: blue;"> </span><span style="color: red;">encoding</span><span style="color: blue;">=</span>"<span style="color: blue;">utf-8</span>"<span style="color: blue;"> ?></span></pre><pre style="margin: 0px;"><span style="color: blue;"><</span><span style="color: #a31515;">configuration</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">system.serviceModel</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">client</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">endpoint</span><span style="color: blue;"> </span><span style="color: red;">address</span><span style="color: blue;">=</span>"<span style="color: blue;">http://localhost:8080/HealthCheck/basic</span>"</pre><pre style="margin: 0px;"><span style="color: blue;"> </span><span style="color: red;">binding</span><span style="color: blue;">=</span>"<span style="color: blue;">basicHttpBinding</span>"</pre><pre style="margin: 0px;"><span style="color: blue;"> </span><span style="color: red;">contract</span><span style="color: blue;">=</span>"<span style="color: blue;">SharedTypes.IHealthCheck</span>"<span style="color: blue;"> /></span></pre><pre style="margin: 0px;"><span style="color: blue;"> </</span><span style="color: #a31515;">client</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"> </</span><span style="color: #a31515;">system.serviceModel</span><span style="color: blue;">></span></pre><pre style="margin: 0px;"><span style="color: blue;"></</span><span style="color: #a31515;">configuration</span><span style="color: blue;">></span></pre></div>Chris Laceyhttp://www.blogger.com/profile/10405697803215160094noreply@blogger.com0tag:blogger.com,1999:blog-4978326367697120061.post-89566115436026042262009-01-14T13:47:00.001+01:002009-01-14T14:13:05.125+01:00IntroductionHistory, or at least working within my company, has shown that the simplest, most basic implementations of code stand the most chance of being correct. As soon as code is littered with endless if-then-else scenarios or numerous configuration options, it becomes increasingly difficult to spot the core logic, and inevitably, the bugs or inconsistencies.<br /><br />Working in the Microsoft space, I have become increasingly frustrated with the typical low signal-to-noise ratio of documentation, samples and articles about Microsoft technologies. Exacerbated by the fact that Microsoft developers (or more specifically Visual Studio developers) have become ever-more dependent on code generators, hidden-away partial classes, and bloated configuration files, there is a tendency for code examples to be little more than screenshots showing which buttons should be pushed within Visual Studio, and which of the configuration options should be tweaked in the sea of automatically generated XML.<br /><br />The code samples I plan to show in this blog will attempt to represent the simplest implementations of particular technologies. Paring back the unnecessary bloat from Visual Studio's code generators and from the multitude of code samples on the web is often a fairly time-consuming task, and the primary purpose of these posts is to record any outcomes as notes to myself. Nonetheless, I hope they might also prove useful to others.Chris Laceyhttp://www.blogger.com/profile/10405697803215160094noreply@blogger.com0