Building a Basic WCF Book Information Service with Console Host and WinForms Client

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <ImplicitUsings>disable</ImplicitUsings>
    <Nullable>disable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System.ServiceModel" />
  </ItemGroup>
</Project>

Create a modular project layout to separate concerns:

  1. SharedLibrary: Contains cross-application utilities and base contracts.
  2. DataEntities: Defines serializable data structures.
  3. CoreService: Implements WCF service logic and contracts.
  4. ServiceHostConsole: Runs the WCF service in a managed console process.
  5. WinFormsClient: Displays service-retrieved data.

First, define serializable data models in DataEntities. Mark classes and properties with WCF data serialization attributes:

using System.Runtime.Serialization;

namespace DataEntities
{
    [DataContract]
    public class BookRecord
    {
        [DataMember]
        public string Title { get; set; }
        
        [DataMember]
        public decimal Cost { get; set; }
        
        [DataMember]
        public int WriterId { get; set; }
    }
}

Next, implement a lightweight XML serialization helper in SharedLibrary. Remove redundant methods and wrap streams with using for proper disposal:

using System; using System.IO; using System.Text; using System.Xml.Serialization;

namespace SharedLibrary
{
    public static class XmlSerializationTool
    {
        public static string ToXmlString<T>(T entity)
        {
            if (entity == null) throw new ArgumentNullException(nameof(entity));
            var serializer = new XmlSerializer(typeof(T));
            using (var sw = new StringWriter(new StringBuilder(), System.Globalization.CultureInfo.InvariantCulture))
            {
                serializer.Serialize(sw, entity);
                return sw.ToString();
            }
        }

        public static T FromXmlString<T>(string xmlContent)
        {
            if (string.IsNullOrWhiteSpace(xmlContent)) throw new ArgumentException("XML content cannot be empty.", nameof(xmlContent));
            var serializer = new XmlSerializer(typeof(T));
            using (var sr = new StringReader(xmlContent))
            {
                return (T)serializer.Deserialize(sr);
            }
        }
    }
}

Now build the CoreService project. Add references to SharedLibrary and DataEntities, plus System.ServiceModel. Contracts are defined first with WCF service/operation attributes:

using System.ServiceModel; using DataEntities;

namespace CoreService
{
    [ServiceContract]
    public interface IBookRetrievalService
    {
        [OperationContract]
        string FetchBookByRecordId(string recordId);
    }

    public class BookRetrievalService : IBookRetrievalService
    {
        public string FetchBookByRecordId(string recordId)
        {
            var sample = FetchHardcodedBook(recordId);
            return XmlSerializationTool.ToXmlString(sample);
        }

        private BookRecord FetchHardcodedBook(string recordId)
        {
            return new BookRecord
            {
                Title = "WCF Practical Guide",
                Cost = 62.99m,
                WriterId = 42
            };
        }
    }
}

Configure the ServiceHostConsole project. Add references to CoreService, DataEntities, and System.ServiceModel. Replace App.config with the following system.serviceModel section to define a WSHttpBinding endpoint and enable HTTP metadata exchange:

<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
  </startup>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="HttpMetadataEnabled">
          <serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:9000/BookRetrieval/metadata" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="CoreService.BookRetrievalService" behaviorConfiguration="HttpMetadataEnabled">
        <endpoint address="http://localhost:9000/BookRetrieval" binding="wsHttpBinding" contract="CoreService.IBookRetrievalService" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

Implement the console host entry point, ensuring admin privilege are noted and the service stays running:

using System; using System.ServiceModel; using CoreService;

namespace ServiceHostConsole
{
    internal class Program
    {n        static void Main(string[] args)
        {
            using (var host = new ServiceHost(typeof(BookRetrievalService)))
            {
                host.Opened += (s, e) => Console.WriteLine("WCF BookRetrievalService active. Press any key to terminate.");
                try
                {
                    host.Open();
                    Console.ReadKey(true);
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Service failed to start: {ex.Message}");
                    Console.ReadKey(true);
                }
            }
        }
    }
}

For client access, use ChannelFactory<T> instead of auto-generated service references to avoid tight coupling. Move the IBookRetrievalService interface to SharedLibrary first, udpate CoreService and WinFormsClient to reference SharedLibrary, then configure the WinFormsClient App.config:

<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
  </startup>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="WSHttpBinding_IBookRetrieval" />
      </wsHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:9000/BookRetrieval" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IBookRetrieval" contract="SharedLibrary.IBookRetrievalService" name="BookRetrievalEndpoint" />
    </client>
  </system.serviceModel>
</configuration>

Add a WinForms form with four controls: a Buton, a multiline TextBox (for raw XML), and three single-line TextBoxes (for Title, Cost, WriterId). Wire the button click event as follows:

using System; using System.Windows.Forms; using System.ServiceModel; using SharedLibrary; using DataEntities;

namespace WinFormsClient
{
    public partial class BookDisplayForm : Form
    {
        public BookDisplayForm()
        {
            InitializeComponent();
        }

        private void btnFetch_Click(object sender, EventArgs e)
        {
            var factory = new ChannelFactory<IBookRetrievalService>("BookRetrievalEndpoint");
            var proxy = factory.CreateChannel();
            try
            {
                string rawXml = proxy.FetchBookByRecordId("REC-001");
                txtRawXml.Text = rawXml;
                BookRecord record = XmlSerializationTool.FromXmlString<BookRecord>(rawXml);
                txtTitle.Text = record.Title;
                txtCost.Text = record.Cost.ToString("F2");
                txtWriterId.Text = record.WriterId.ToString();
                ((ICommunicationObject)proxy).Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Error fetching book: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                ((ICommunicationObject)proxy).Abort();
            }
            finally
            {
                factory.Close();
            }
        }
    }
}

Tags: WCF C# .NET Framework Service-Oriented Architecture WinForms

Posted on Thu, 11 Jun 2026 17:30:52 +0000 by abie