<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:
- SharedLibrary: Contains cross-application utilities and base contracts.
- DataEntities: Defines serializable data structures.
- CoreService: Implements WCF service logic and contracts.
- ServiceHostConsole: Runs the WCF service in a managed console process.
- 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();
}
}
}
}