Category Archives: C#

A reference architecture (part 12)

..continued from part 11

Conclusion

In this series I explained an approach on how to set up a solution. Of course, as this is only a simple case, various aspects can be different in your case.

You probably will end up using other frameworks for dependency injection, logging and validation. You might choose to create a separate query service that uses its own data access component to query its dedicated read-only database with specialized views, or maybe use WCF data services for that matter. Or decide that commands should never return anything, and simplify the request dispatcher and expose a validation operation next to the execute operation to allow consumers to first validate commands before executing them. Or change the fault contract so that it contains technical as well as business and validation errors. You might extend the domain with domain events and use a service bus, or in real complex cases apply CQRS techniques and implement event sourcing.

In any case I think this is a nice basic design to start with!

Where is the source code?

You can download the source code for free here. The only thing I ask is: let me know what you think: suggestions, questions, fixes or any other feedback is most welcome!

Advertisements

A reference architecture (part 11)

…continued from part 10

Logging

If we say logging, we automatically think about log4net, the de facto standard logging framework. So download it, and put the log4net.dll assembly in the \Trunk\Libraries\Log4Net folder. From the ArchitectureExample.Service.WebHost project, add a reference to it.

First, add the needed configuration in the web.config file of the ArchitectureExample.Service.WebHost project:

<?xml version="1.0"?>
<configuration>

<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>

<appSettings>
<!-- If you want log4net to add its own diagnostics messages -->
<add key="log4net.Internal.Debug" value="true" />
</appSettings>


<log4net debug="true">
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="C:\\log\\architectureexample.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%-5p %d %5rms %-22.22c{1} %-18.18M - %m%n" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="RollingLogFileAppender" />
</root>
</log4net>

</configuration>

Next, we will start using log4net. The default usage says that you should put a static variable on top of each class where you want to log, but I don’t like that approach, so we’ll do it a bit different. As explained in a previous post we have to add a logger interface. So add the following interface to the ArchitectureExample.Infrastructure.Shared project:

public interface ILogger
{
void Debug(string message);
void Debug(string message, Exception ex);
void Info(string message);
void Info(string message, Exception ex);
void Warn(string message);
void Warn(string message, Exception ex);
void Error(string message);
void Error(string message, Exception ex);
void Fatal(string message);
void Fatal(string message, Exception ex);
}

Also add the concrete implementation Log4NetLogger, that uses log4net to do the actual logging:

public class Log4NetLogger : ILogger
{
private readonly ILog _log;

/// <summary>
/// Constructor.
/// </summary>
/// <param name="type"></param>
public Log4NetLogger(Type type)
{
_log = LogManager.GetLogger(type);
}

public void Debug(string message)
{
_log.Debug(message);
}

public void Debug(string message, Exception ex)
{
_log.Debug(message, ex);
}

public void Info(string message)
{
_log.Info(message);
}

public void Info(string message, Exception ex)
{
_log.Info(message, ex);
}

public void Warn(string message)
{
_log.Warn(message);
}

public void Warn(string message, Exception ex)
{
_log.Warn(message, ex);
}

public void Error(string message)
{
_log.Error(message);
}

public void Error(string message, Exception ex)
{
_log.Error(message, ex);
}

public void Fatal(string message)
{
_log.Fatal(message);
}

public void Fatal(string message, Exception ex)
{
_log.Fatal(message, ex);
}
}

In the ArchitectureExample.Service.WebHost project, make sure the log4net is correctly registered by adding this to the StructureMapRegistry:

// Logging
For<ILogger>().AlwaysUnique().Use(s => s.ParentType == null ? new Log4NetLogger(s.BuildStack.Current.ConcreteType) : new Log4NetLogger(s.ParentType));
var applicationPath = Path.GetDirectoryName(Assembly.GetAssembly(GetType()).Location);
var configFile = new FileInfo(Path.Combine(applicationPath, "web.config"));
XmlConfigurator.ConfigureAndWatch(configFile);

Finally, to actually log, it’s just a matter of injection the ILogger into the class where you want to use it, for example, in the AddBlogEntryRequestHandler of the ArchitectureExample.Application.RequestHandlers project:

public class AddBlogEntryRequestHandler : IRequestHandler<AddBlogEntryRequest, AddBlogEntryResponse>
{
private readonly ILogger _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly IBlogEntryRepository _blogEntryRepository;

public AddBlogEntryRequestHandler(ILogger logger, IUnitOfWork unitOfWork, IBlogEntryRepository blogEntryRepository)
{
_logger = logger;
_unitOfWork = unitOfWork;
_blogEntryRepository = blogEntryRepository;
}

public AddBlogEntryResponse Execute(AddBlogEntryRequest request)
{
var blogEntry = new BlogEntry(request.Title, request.Body);
_blogEntryRepository.Add(blogEntry);
_unitOfWork.Commit();
_logger.Debug("AddBlogEntryRequestHandler completed");
return new AddBlogEntryResponse(blogEntry.Id);
}
}

If you now run the AddBlogEntryTest again, you will see that the log file C:\log\architectureexample.txt contains:

DEBUG 2013-03-07 10:54:42,543  2921ms logEntryRequestHandler Debug              - AddBlogEntryRequestHandler completed

You can also easily add logging in the GlobalExceptionHandler like this:

public class GlobalExceptionHandler : IErrorHandler
{
private readonly ILogger _logger;

public GlobalExceptionHandler(ILogger logger)
{
_logger = logger;
}

public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error is BusinessException)
{
var businessError = error as BusinessException;
var businessFault = new BusinessFault(businessError.ErrorCode, businessError.Message);
var faultException = new FaultException<BusinessFault>(businessFault, "Oops", new FaultCode("BusinessFault"));
var msgFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, error.TargetSite.Name);
_logger.Error("BusinessException", businessError);
}
else if (error is ValidationException)
{
var validationError = error as ValidationException;
var businessFault = new BusinessFault("VALIDATIONERROR", validationError.Message);
var faultException = new FaultException<BusinessFault>(businessFault, "Oops", new FaultCode("ValidationFault"));
var msgFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, error.TargetSite.Name);
_logger.Error("ValidationException", validationError);
}
else
{
var faultException = new FaultException(string.Format("{0}", error.TargetSite.Name));
var msgFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, faultException.Action);
_logger.Error("Unknown exception", faultException);
}
}

public bool HandleError(Exception error)
{
return true;
}
}

Now run the AddBlogEntryWithoutTitleTest and check whether the ValidationException appears in the log file:

DEBUG 2013-03-07 10:54:42,543  2921ms logEntryRequestHandler Debug              - AddBlogEntryRequestHandler completed
DEBUG 2013-03-07 10:59:54,336 1919ms logEntryRequestHandler Debug - AddBlogEntryRequestHandler completed
DEBUG 2013-03-07 11:00:34,408 41992ms logEntryRequestHandler Debug - AddBlogEntryRequestHandler completed
ERROR 2013-03-07 11:01:08,511 76094ms GlobalExceptionHandler Error - ValidationException
FluentValidation.ValidationException: Validation failed:
-- 'Title' must not be empty.
-- 'Title' should not be empty.
-- 'Title' must be between 1 and 200 characters. You entered 0 characters.
at ArchitectureExample.Service.Shared.HandlerDispatcher.Execute[TRequest,TResponse](TRequest request, Boolean asynchronously) in C:\Projects\R&D\ArchitectureExample\Trunk\Sources\ArchitectureExample.Service.Shared\HandlerDispatcher.cs:line 32
at ArchitectureExample.Service.Adapter.ArchitectureExampleService.AddBlogEntry(AddBlogEntryRequest request) in C:\Projects\R&D\ArchitectureExample\Trunk\Sources\ArchitectureExample.Service.Adapter\ArchitectureExampleService.cs:line 27
at SyncInvokeAddBlogEntry(Object , Object[] , Object[] )
at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)


Almost time for the conclusion

A reference architecture (part 10)

…continued from part 9

Validation

In the previous part we made it possible to throw business exceptions when something goes wrong in the business logic. However, next to that we also need to do validation, which basically means checking whether properties have valid values.

In the case of validation in request handlers, we can use first check whether the request itself is correct before executing the actual logic. If there is something wrong with the request, like a required property not set, then it makes no sense to continue.

In the case of a domain entity, we can put validation at creation phase, to avoid that we can never create invalid domain entities. As such the best place is in its constructor.

The validation logic itself could be placed in the domain entities and request handlers themselves, but I find it cleaner to separate them out and move that responsibility to dedicated validators. The main concern of entities should be keeping track of business state. Another advantage is that you could create validators that are shared and so reusable inside other validators, like for example an EmailAddressValidator.

My validation framework of choice is FluentValidation. It offers everything we need, so download it, and put the FluentValidation.dll assembly in \Trunk\Libraries\FluentValidation.

Add domain entity validation

In the ArchitectureExample.Domain.Entities project, add the following validators:

public class BlogEntryValidator : AbstractValidator<BlogEntry>
{
public BlogEntryValidator()
{
RuleFor(blogEntry => blogEntry.Title).NotNull().NotEmpty().Length(1, 200);
RuleFor(blogEntry => blogEntry.Body).NotNull().NotEmpty().Length(1, 2000);
RuleFor(blogEntry => blogEntry.LastModifiedDate).NotEqual(DateTime.MinValue);
}
}

public class CommentValidator : AbstractValidator<Comment>
{
public CommentValidator()
{
RuleFor(comment => comment.Name).NotNull().NotEmpty().Length(1, 50);
RuleFor(comment => comment.EmailAddress).NotNull().NotEmpty().Length(1, 200).EmailAddress();
RuleFor(comment => comment.CommentText).NotNull().NotEmpty().Length(1, 1000);
RuleFor(comment => comment.DateWritten).NotEqual(DateTime.MinValue);
}
}

And in the entities, call validation in the constructor:

public class BlogEntry
{
private BlogEntry() {}
public BlogEntry(string title, string body)
{
LastModifiedDate = DateTime.Now;
Title = title;
Body = body;

var blogEntryValidator = new BlogEntryValidator();
blogEntryValidator.ValidateAndThrow(this);
}
}

public class Comment
{
private Comment() {}
public Comment(BlogEntry blogEntry, string name, string emailAddress, string commentText)
{
BlogEntry = blogEntry;
Name = name;
EmailAddress = emailAddress;
CommentText = commentText;
DateWritten = DateTime.Now;

var commentValidator = new CommentValidator();
commentValidator.ValidateAndThrow(this);
}
}

Now, because the ValidateAndThrow operation throws a ValidationException, we have to extend the GlobalExceptionHandler so that it can handle it too and convert it to a business fault:

public class GlobalExceptionHandler : IErrorHandler
{
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error is BusinessException)
{
var businessError = error as BusinessException;
var businessFault = new BusinessFault(businessError.ErrorCode, businessError.Message);
var faultException = new FaultException<BusinessFault>(businessFault, "Oops", new FaultCode("BusinessFault"));
var msgFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, error.TargetSite.Name);
}
else if (error is ValidationException)
{
var validationError = error as ValidationException;
var businessFault = new BusinessFault("VALIDATIONERROR", validationError.Message);
var faultException = new FaultException<BusinessFault>(businessFault, "Oops", new FaultCode("ValidationFault"));
var msgFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, error.TargetSite.Name);
}
else
{
var faultException = new FaultException(string.Format("{0}", error.TargetSite.Name));
var msgFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, faultException.Action);
}
}

public bool HandleError(Exception error)
{
return true;
}
}

Of course, this is just an example; you could create a separate ValidationFault that contains a list of errors, and throw that one in the case of a validation error; but you get the idea on how to catch exceptions in the global exception handler and throw faults based on them.

To test this:

[TestMethod]
public void AddBlogEntryWithoutTitleTest()
{
string errorCode = null, faultMessage = null;
using (var architectureExampleServiceAgent = new ArchitectureExampleServiceAgent("BasicHttpBinding_IArchitectureExampleService"))
{
try
{
var response = architectureExampleServiceAgent.AddBlogEntry(new AddBlogEntryRequest()
{
Title = null,
Body = "Blog entry body"
});
}
catch (FaultException<BusinessFault> fex)
{
errorCode = fex.Detail.ErrorCode;
faultMessage = fex.Detail.Message;
}
}
Assert.IsNotNull(errorCode);
Assert.IsNotNull(faultMessage);
}

Add request validation

Adding validation to the request handlers is about the same: you just add request validators and call them in the beginning of the execute operation of the request handlers.

Next time I’ll add logging!

A reference architecture (part 9)

…continued from part 8

Throwing business faults

In the previous blog posts I set up a working solution, but I didn’t take into account that sometimes things can go wrong. And it is reality that things most definitely will go wrong! That means that during our business logic, we need a way to throw business exceptions.

In the ArchitectureExample.Service.Shared project, add the following classes:

[AttributeUsage(AttributeTargets.Class)]
public class GlobalExceptionHandlerBehaviourAttribute : Attribute, IServiceBehavior
{
private readonly IErrorHandler _errorHandler;

public GlobalExceptionHandlerBehaviourAttribute()
{
// Although we should always use constructor injection, in this case we have no choice to use ObjectFactory directly
// because we cannot change the default constructor as it is used as an attribute.
_errorHandler = ObjectFactory.GetInstance<IErrorHandler>();
}

public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase) { }
public void AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters) { }

public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase dispatcherBase in serviceHostBase.ChannelDispatchers)
{
var channelDispatcher = dispatcherBase as ChannelDispatcher;
if (channelDispatcher != null) channelDispatcher.ErrorHandlers.Add(_errorHandler);
}
}
}

public class BusinessException : Exception
{
public string ErrorCode { get; set; }

public BusinessException(string errorCode, string message)
: base(message)
{
ErrorCode = errorCode;
}
}

[DataContract]
public class BusinessFault
{
[DataMember]
public string ErrorCode { get; set; }
[DataMember]
public string Message { get; set; }

public BusinessFault(string errorCode, string message)
{
ErrorCode = errorCode;
Message = message;
}
}

public class GlobalExceptionHandler : IErrorHandler
{
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
MessageFault msgFault;
if (error is BusinessException)
{
var businessError = error as BusinessException;
var businessFault = new BusinessFault(businessError.ErrorCode, businessError.Message);
var faultException = new FaultException<BusinessFault>(businessFault, "Oops", new FaultCode("BusinessFault"));
msgFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, error.TargetSite.Name);
}
else
{
FaultException faultException = new FaultException(string.Format("{0}", error.TargetSite.Name));
msgFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, faultException.Action);
}
}

public bool HandleError(Exception error)
{
return true;
}
}

Then decorate the service implementation in the ArchitectureExample.Service.Adapter project with the GlobalExceptionHandlerBehaviour attribute, for example:

[GlobalExceptionHandlerBehaviour]
public class ArchitectureExampleService : IArchitectureExampleService
{
}

Also decorate each service operation with the [FaultContract] attribute in the IArchitectureExampleService interface in the ArchitectureExample.Service.Contracts project:

[ServiceContract]
public interface IArchitectureExampleService
{
[OperationContract]
[FaultContract(typeof(BusinessFault))]
AddBlogEntryResponse AddBlogEntry(AddBlogEntryRequest request);

[OperationContract]
[FaultContract(typeof(BusinessFault))]
AddCommentToBlogEntryResponse AddCommentToBlogEntry(AddCommentToBlogEntryRequest request);

[OperationContract]
[FaultContract(typeof(BusinessFault))]
void ModifyBlogEntry(ModifyBlogEntryRequest request);

[OperationContract]
[FaultContract(typeof(BusinessFault))]
void PublishBlogEntry(PublishBlogEntryRequest request);

[OperationContract]
[FaultContract(typeof(BusinessFault))]
void UnpublishBlogEntry(UnpublishBlogEntryRequest request);

[OperationContract]
[FaultContract(typeof(BusinessFault))]
FindAllBlogEntriesResponse FindAllBlogEntries();

[OperationContract]
[FaultContract(typeof(BusinessFault))]
FindBlogEntryDetailsResponse FindBlogEntryDetails(FindBlogEntryDetailsRequest request);
}

Then add the following registration in the StructureMapRegistry in the ArchitectureExample.Service.WebHost project:

For<IErrorHandler>().Use<GlobalExceptionHandler>();

If you now throw a BusinessException somewhere in the service code, the global exception handler will automatically convert the business exception to a BusinessFault and throw it so that service consumers can catch it.

To quickly test this, throw a business exception in the AddBlogEntryRequestHandler:

public class AddBlogEntryRequestHandler : IRequestHandler<AddBlogEntryRequest, AddBlogEntryResponse>
{
private readonly IUnitOfWork _unitOfWork;
private readonly IBlogEntryRepository _blogEntryRepository;

public AddBlogEntryRequestHandler(IUnitOfWork unitOfWork, IBlogEntryRepository blogEntryRepository)
{
_unitOfWork = unitOfWork;
_blogEntryRepository = blogEntryRepository;
}

public AddBlogEntryResponse Execute(AddBlogEntryRequest request)
{
throw new BusinessException("FISH", "Something fishy happened!");
var blogEntry = new BlogEntry(request.Title, request.Body);
_blogEntryRepository.Add(blogEntry);
_unitOfWork.Commit();
return new AddBlogEntryResponse(blogEntry.Id);
}
}

And add a new test:

[TestMethod]
public void AddBlogEntryWithFaultTest()
{
string errorCode = null, faultMessage = null;
using (var architectureExampleServiceAgent = new ArchitectureExampleServiceAgent("BasicHttpBinding_IArchitectureExampleService"))
{
try
{
var response = architectureExampleServiceAgent.AddBlogEntry(new AddBlogEntryRequest()
{
Title = "Blog entry title",
Body = "Blog entry body"
});
}
catch (FaultException<BusinessFault> fex)
{
errorCode = fex.Detail.ErrorCode;
faultMessage = fex.Detail.Message;
}
}
Assert.IsNotNull(errorCode);
Assert.IsNotNull(faultMessage);
}

Next time we’ll handle validation of requests and domain entities.

A reference architecture (part 8)

…continued from part 7

The query side

So now the command side works, but what about the query side? As you know, queries bypass the application and domain logic; they just query a data source for information directly and expose it in the desired format. This means that those service operations don’t use the request handler dispatcher to dispatch requests to the correct handler, but instead use the repositories directly.

To clearly separate commands from queries, it’s probably cleaner to split commands and queries in two separate services: a command service and a query service; especialliy if both sides use their own database; but in our case – for simplicity – we will put them all in the same service.

As mentioned earlier we need two queries: FindAllBlogEntries and FindBlogEntryById. As these query operations don’t use the application layer, we have to define their dto’s in the service layer; so add a class library project called ArchitectureExample.Service.Dtos to the Service Layer solution folder, and put it physically in the \Trunk\Sources\ folder:

p8_1

Then add the following classes to this project:

[DataContract]
public class BlogEntryDetails
{
    public BlogEntryDetails() { }
    public BlogEntryDetails(int blogEntryId, string title, string body)
    {
        BlogEntryId = blogEntryId;
        Title = title;
        Body = body;
    }

    [DataMember]
    public int BlogEntryId { get; set; }

    [DataMember]
    public string Title { get; set; }

    [DataMember]
    public string Body { get; set; }

    [DataMember]
    public List<CommentDetails> Comments { get; set; }
}


[DataContract]
public class BlogEntrySummary
{
    public BlogEntrySummary() {}
    public BlogEntrySummary(int blogEntryId, string title, string body)
    {
        BlogEntryId = blogEntryId;
        Title = title;
        Body = body;
    }

    [DataMember]
    public int BlogEntryId { get; set; }

    [DataMember]
    public string Title { get; set; }

    [DataMember]
    public string Body { get; set; }
}


[DataContract]
public class CommentDetails
{
    public CommentDetails() {}
    public CommentDetails(int commentId, string name, string emailAddress, string commentText)
    {
        CommentId = commentId;
        Name = name;
        EmailAddress = emailAddress;
        CommentText = commentText;
    }

    [DataMember]
    public int CommentId { get; set; }

    [DataMember]
    public string Name { get; set; }

    [DataMember]
    public string EmailAddress { get; set; }

    [DataMember]
    public string CommentText { get; set; }
}


[DataContract]
public class FindAllBlogEntriesResponse
{
    public FindAllBlogEntriesResponse() {}
    public FindAllBlogEntriesResponse(List<BlogEntrySummary> blogEntrySummaries)
    {
        BlogEntrySummaries = blogEntrySummaries;
    }

    [DataMember]
    public List<BlogEntrySummary> BlogEntrySummaries { get; set; }
}

[DataContract]
public class FindBlogEntryDetailsRequest
{
    public FindBlogEntryDetailsRequest() {}
    public FindBlogEntryDetailsRequest(int blogEntryId)
    {
        BlogEntryId = blogEntryId;
    }

    [DataMember]
    public int BlogEntryId { get; set; }
}


[DataContract]
public class FindBlogEntryDetailsResponse
{
    public FindBlogEntryDetailsResponse() {}
    public FindBlogEntryDetailsResponse(BlogEntryDetails blogEntryDetails)
    {
        BlogEntryDetails = blogEntryDetails;
    }

    [DataMember]
    public BlogEntryDetails BlogEntryDetails { get; set; }
}

 

Then add the service operations to the IArchitectureExampleService interface of the ArchitectureExample.Service.Contracts project:

[OperationContract]
FindAllBlogEntriesResponse FindAllBlogEntries();

[OperationContract]
FindBlogEntryDetailsResponse FindBlogEntryDetails(FindBlogEntryDetailsRequest request);

 

And implement them in the ArchitectureExampleService of the ArchitectureExample.Service.Adapter project:

public class ArchitectureExampleService : IArchitectureExampleService
{
    private readonly IHandlerDispatcher _handlerDispatcher;
    private readonly IBlogEntryRepository _blogEntryRepository;

    public ArchitectureExampleService(IHandlerDispatcher handlerDispatcher, IBlogEntryRepository blogEntryRepository)
    {
        _handlerDispatcher = handlerDispatcher;
        _blogEntryRepository = blogEntryRepository;
    }

    public FindAllBlogEntriesResponse FindAllBlogEntries()
    {
        return new FindAllBlogEntriesResponse(_blogEntryRepository.BlogEntries.ToList().Select(be => new BlogEntrySummary(be.Id, be.Title, be.Body)).ToList());
    }

    public FindBlogEntryDetailsResponse FindBlogEntryDetails(FindBlogEntryDetailsRequest request)
    {
        var blogEntryDetails = _blogEntryRepository.BlogEntries.SingleOrDefault(be => be.Id == request.BlogEntryId);
        return new FindBlogEntryDetailsResponse(blogEntryDetails!=null ? new BlogEntryDetails(blogEntryDetails.Id, blogEntryDetails.Title, blogEntryDetails.Body) : null);
    }
}

 

And also the service agent:

public class ArchitectureExampleServiceAgent : ServiceProxyBase<IArchitectureExampleService>, IArchitectureExampleService
{    
    public FindAllBlogEntriesResponse FindAllBlogEntries()
    {
        return Channel.FindAllBlogEntries();
    }

    public FindBlogEntryDetailsResponse FindBlogEntryDetails(FindBlogEntryDetailsRequest request)
    {
        return Channel.FindBlogEntryDetails(request);
    }
}

 

And finally add a test:

[TestMethod]
public void FindAllBlogEntriesTest()
{
    using (var architectureExampleServiceAgent = new ArchitectureExampleServiceAgent("BasicHttpBinding_IArchitectureExampleService"))
    {
        var response = architectureExampleServiceAgent.FindAllBlogEntries();

        Assert.IsTrue(response.BlogEntrySummaries.Count > 0);
    }
}

 

Next time we will implement a way to automatically throw business faults by the service if an exception occurs.