using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace NotFoundWcfService1
{
// Setting the IsReference parameter of this
// attribute to true eliminates NotFound error
[DataContract]
public class Item
{
[DataMember]
public List<Category> Categories { get; set; }
}
[DataContract]
public class Category
{
[DataMember]
public List<Item> Items { get; set; }
}
[ServiceContract]
public class Service1
{
[OperationContract]
public Item GetItem()
{
Item c = new Item()
{
Categories = new List<Category>()
{
new Category()
{
Items = new List<Item>()
}
}
};
// Commenting this out makes the operation
// contract appear to work. Tricky!
c.Categories[0].Items.Add(c);
return c;
}
}
}
Wednesday, March 31, 2010
The remote server returned an error: NotFound. - Yet Another Cause And Solution
Having encountered this error while working with Silverlight and WCF the number of potential fixes I found while searching was overwhelming. To get an idea of just how many causes there are for this error take a look at this thread. None of the solutions appeared relevant to my application, so I decided to dig further by setting includeExceptionDetailInFault in the service's web.config to true. This yielded no new information so I moved on to observing the HTTP traffic with Fiddler. To my surprise the ASP.NET development server was closing the connection with no response. Even stranger was the fact that this behavior was only exhibited by one of my operation contracts when certain parameters were passed to it--the rest of my operation contracts worked fine! Naturally I stepped through the problematic method and everything appeared to be working. Seeing this I knew that the problem had to be in the data contract returned, so I resorted to trial and error: one by one I tested the service with different data members nulled. Finally I discovered the data member causing the issue and the actual problem: circular references. After setting the IsReference property of my data contract to true the CommunicationException disappeared. Below is a sample WCF service that displays the aforementioned issue along with comments noting the fix.
Wednesday, March 17, 2010
The Easy Way To Synchronously Call WCF Services In Silverlight
If you're reading this you probably know why Microsoft tries to force the use of asynchronous WCF service calls in Silverlight. If not and you cannot infer based on your knowledge of threading I suggest you read up on the matter.
While Microsoft may have a strong case for their decision, it can lead to bloated, messy code when several service calls are chained together. The following extension method, through heavy use of reflection, allows for synchronous service calls. Remember, do not block the UI thread.
ICommunicationObjectExtension.cs
Asynchronous WCF Service Call
Synchronous WCF Service Call
While Microsoft may have a strong case for their decision, it can lead to bloated, messy code when several service calls are chained together. The following extension method, through heavy use of reflection, allows for synchronous service calls. Remember, do not block the UI thread.
ICommunicationObjectExtension.cs
using System;
using System.Net;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.ServiceModel;
namespace SyncWCFCalls
{
public static class ICommunicationObjectExtension
{
public static TEventArgs SynchronousCall<TEventArgs>(this ICommunicationObject Client,
string Method, params object[] Parameters)
where TEventArgs : EventArgs
{
Type clientType = Client.GetType();
string methodName = Method + "Async";
string completedName = Method + "Completed";
MethodInfo[] methods = null;
try
{
methods = clientType.GetMethods().Where(x =>
x.Name == methodName).ToArray();
}
catch { }
if (methods == null || methods.Length == 0)
{
throw new MissingMethodException(methodName + " not found");
}
MethodInfo method = null;
try
{
method = methods.Single(x =>
x.GetParameters().Length == Parameters.Length);
}
catch { }
if (method == null)
{
throw new MissingMethodException(methodName +
" parameter count mismatch");
}
EventInfo completedEvent = null;
try
{
completedEvent = clientType.GetEvent(completedName);
}
catch { }
if (completedEvent == null)
throw new MissingMemberException(completedName + " not found");
ManualResetEvent reset = new ManualResetEvent(false);
EventHandler<TEventArgs> completedHandler = null;
TEventArgs args = null;
completedHandler = (s, e) =>
{
completedEvent.RemoveEventHandler(Client, completedHandler);
args = e;
reset.Set();
};
completedEvent.AddEventHandler(Client, completedHandler);
method.Invoke(Client, Parameters);
reset.WaitOne();
return args;
}
}
}
Asynchronous WCF Service Call
void AsyncTest()
{
CalculatorServiceClient client = new CalculatorServiceClient();
Guid addState1 = Guid.NewGuid(), addState2 = Guid.NewGuid();
client.AddCompleted += (s, e) =>
{
if ((Guid)e.UserState == addState1)
{
// Do something with AddCompletedEventArgs
client.AddAsync(e.Result, 10, addState2);
}
else if ((Guid)e.UserState == addState2)
{
// Do something with AddCompletedEventArgs
client.SubtractAsync(e.Result, 5);
}
};
client.SubtractCompleted += (s, e) =>
{
// Do something with SubtractCompletedEventArgs
};
client.AddAsync(1, 1, addState1);
}
Synchronous WCF Service Call
void SyncTest()
{
CalculatorServiceClient client = new CalculatorServiceClient();
ThreadPool.QueueUserWorkItem(x =>
{
AddCompletedEventArgs addArgs1 =
client.SynchronousCall<AddCompletedEventArgs>("Add", 1, 1);
// Do something with AddCompletedEventArgs
AddCompletedEventArgs addArgs2 =
client.SynchronousCall<AddCompletedEventArgs>("Add",
addArgs1.Result, 10);
// Do something with AddCompletedEventArgs
SubtractCompletedEventArgs subtractArgs =
client.SynchronousCall<SubtractCompletedEventArgs>("Subtract",
addArgs2.Result, 5);
// Do something with SubtractCompletedEventArgs
});
}
Updated 3/21/2010
Wednesday, March 3, 2010
.NET Assembly Tamperproofing
Development of an application that verifies its own integrity presents unique challenges. Using a cryptographic hash function to detect potentially malicious modifications to an assembly is a common approach. Embedding the the digest of an assembly within it and then, at runtime, comparing the embedded digest to a newly computed hash will achieve this. However, attempting to embed the assembly digest by adding it to the source of the application and recompiling will alter the hash value of the resulting file. Because of this, the value must be patched into the assembly after compilation; also, during the integrity check the embedded hash within the message needs to somehow be ignored before the hash is computed, otherwise it too will affect the result.
The following C# solution shows how to create an application that detects modifications through the use of the RIPEMD-160 cryptographic hash function. PatchDigest (configured to run as a post-build event of Calculator) searches Calculator.exe for the _embeddedDigest value, zeroes it, hashes the result, then overwrites the array with the actual digest. When Calculator is run it reads itself, zeroes _embeddedDigest, then hashes the result just as PatchDigest does before comparing the computed digest to the embedded one. If they don't match it is assumed the application has been modified in a malicious manner and, in an attempt to throw the attacker off, the output of the application is corrupted. This behavior can be observed by removing Calculator's post-build event and recompiling.
Download
Calculator
PatchDigest
The following C# solution shows how to create an application that detects modifications through the use of the RIPEMD-160 cryptographic hash function. PatchDigest (configured to run as a post-build event of Calculator) searches Calculator.exe for the _embeddedDigest value, zeroes it, hashes the result, then overwrites the array with the actual digest. When Calculator is run it reads itself, zeroes _embeddedDigest, then hashes the result just as PatchDigest does before comparing the computed digest to the embedded one. If they don't match it is assumed the application has been modified in a malicious manner and, in an attempt to throw the attacker off, the output of the application is corrupted. This behavior can be observed by removing Calculator's post-build event and recompiling.
Download
Calculator
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Security.Cryptography;
namespace Calculator
{
class Program
{
private static byte[] _embeddedDigest = new byte[20]
{
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
};
private static Random _rnd = new Random();
static bool CheckIntegrity()
{
string asmLocation = Assembly.GetExecutingAssembly().Location;
byte[] buffer = File.ReadAllBytes(asmLocation);
bool match;
for (int x = 0; x < buffer.Length; x++)
{
match = true;
for (int y = 0; y < _embeddedDigest.Length; y++)
{
if (_embeddedDigest[y] != buffer[x + y])
{
match = false;
break;
}
}
if (match)
{
for (int y = 0; y < _embeddedDigest.Length; y++)
buffer[x + y] = 0;
break;
}
}
RIPEMD160Managed ripemd = new RIPEMD160Managed();
byte[] digest = ripemd.ComputeHash(buffer);
match = true;
for (int x = 0; x < digest.Length; x++)
{
if (_embeddedDigest[x] != digest[x])
{
match = false;
break;
}
}
return match;
}
static void Main(string[] args)
{
Console.Write("X: ");
int x = int.Parse(Console.ReadLine());
Console.Write("Y: ");
int y = int.Parse(Console.ReadLine());
// if the integrity check fails replace y with a random value
y = CheckIntegrity() ? y : _rnd.Next();
Console.WriteLine("X + Y = {0}\r\n" +
"Press any key to continue...", x + y);
Console.ReadKey();
}
}
}
PatchDigest
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Security.Cryptography;
namespace PatchDigest
{
/// <summary>
/// Usage: PatchDigest [Target Application]
/// </summary>
class Program
{
private static byte[] _embeddedDigest = new byte[20]
{
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
};
static void Main(string[] args)
{
string file = args[0];
byte[] buffer = File.ReadAllBytes(file);
int index = -1;
bool patched = false;
for (int x = 0; x < buffer.Length; x++)
{
bool match = true;
for (int y = 0; y < _embeddedDigest.Length; y++)
{
if (_embeddedDigest[y] != buffer[x + y])
{
match = false;
break;
}
}
if (match)
{
index = x;
for (int y = 0; y < _embeddedDigest.Length; y++)
buffer[x + y] = 0;
RIPEMD160Managed ripemd = new RIPEMD160Managed();
byte[] digest = ripemd.ComputeHash(buffer);
for (int y = 0; y < _embeddedDigest.Length; y++)
buffer[x + y] = digest[y];
File.WriteAllBytes(file, buffer);
patched = true;
break;
}
}
Console.WriteLine((patched ? "Patched" : "Patch Failed") +
"Press any key to continue...");
}
}
}
Subscribe to:
Posts (Atom)