If you want to do something akin to the ROP-style, then you can do the following (Failable name from Jeff Bridgman's suggestion).
First create abstract base classes as follows...
public abstract class Failable<T> {
}
public abstract class Failable {
}
Next create concrete classes from these to represent success and failure. I've added a generic and non-generic version of each, so you can handle service calls that return a value as well as void ones...
public class Success<T> : Failable<T> {
public Success(T value) {
Value = value;
}
public T Value { get; set; }
}
public class Success : Failable {
}
public class Failure<T> : Failable<T> {
public Failure(Exception value) {
Value = value;
}
public Exception Value { get; set; }
}
public class Failure : Failable {
public Failure(Exception value) {
Value = value;
}
public Exception Value { get; set; }
}
Then a couple of helper methods will give you what you want...
public Failable<T> DoFailableAction<T>(Func<T> f) {
Failable<T> result;
try {
T fResult = f();
result = new Success<T>(fResult);
}
catch (Exception ex) {
result = new Failure<T>(ex);
// Do logging, etc here...
}
return result;
}
public Failable DoFailableAction(Action f) {
Failable result;
try {
f();
result = new Success();
}
catch (Exception ex) {
result = new Failure(ex);
// Do logging, etc here...
}
return result;
}
To use these, suppose you have your WCF service. You wrap the service call in the helper method...
Failable<Person> p = failableHelpers.DoFailableAction(() => service.GetPerson(1));
...where failableHelpers is an instance of the class that contains the two helper methods shown above.
Then, you can check if the call succeeded or failed, and act appropriately...
if (p is Success<Person>) {
Person p = ((Success<Person>)p).Value;
Debug.WriteLine("Value is Success, and the person is " + p.FirstName + " " + p.Surname);
} else {
Exception ex = ((Failure<Person>)p).Value;
Debug.WriteLine("Value is Failure, and the message is " + ex.Message);
}
If your service method is void, you just use the non-generic variation...
Person jim = new Person(1, "Jim", "Spriggs");
Failable saveResult = failableHelpers.DoFailableAction(() => service.Update(jim));
You haven't quite achieved what you wanted, as you still have some boilerplate code in each service method, but it's very little. Not as little as an attribute on the method, but then this approach is much simpler to implement, and works for any kind of level, whether a repository, business logic, WCF service, client, etc. The attibute approach only works for WCF.
The benefit of this approach is that it puts the onus on the consumer of the WCF service (or whatever) to check if the service call was successful, and act appropriately. This is a functional style, and leads to much more robust coding, as you are pretty much forced not to ignore exceptions, unlike non-functional programming, where you would likely just grab the result of the service under the assumption that nothing went wrong. That's fine until something does go wrong!
Hope that helps.