SoapProxyPocoGenerator 0.4.0

dotnet add package SoapProxyPocoGenerator --version 0.4.0
                    
NuGet\Install-Package SoapProxyPocoGenerator -Version 0.4.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SoapProxyPocoGenerator" Version="0.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SoapProxyPocoGenerator" Version="0.4.0" />
                    
Directory.Packages.props
<PackageReference Include="SoapProxyPocoGenerator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SoapProxyPocoGenerator --version 0.4.0
                    
#r "nuget: SoapProxyPocoGenerator, 0.4.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SoapProxyPocoGenerator@0.4.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SoapProxyPocoGenerator&version=0.4.0
                    
Install as a Cake Addin
#tool nuget:?package=SoapProxyPocoGenerator&version=0.4.0
                    
Install as a Cake Tool

SOAP Proxy POCO Generator

License: MIT .NET Build Status NuGet

A .NET incremental source generator that automatically populates clean POCO (Plain Old CLR Object) domain models from SOAP proxy classes and generates corresponding AutoMapper profiles for seamless bidirectional mapping.

Table of Contents

Features

  • Domain-First Approach: Define your POCO classes in your domain layer and let the generator populate them from proxy classes
  • Auto-Generated Attributes: All marker attributes are generated automatically - no separate attributes assembly required
  • Automatic Property Generation: Extends partial POCO classes with properties from SOAP proxy classes without XML/SOAP attributes
  • Smart Property Filtering: Automatically excludes properties with [XmlIgnore], handles read-only properties, and supports type-safe exclusion configuration
  • Init-Only Property Support: Generates properties with init accessors for immutable domain models (C# 9+)
  • XML Attribute Support: Handles XmlIgnore, XmlChoiceIdentifier, XmlAnyElement, XmlAnyAttribute, and XmlEnum attributes
  • Default Value Preservation: Optionally preserves [DefaultValue] attributes from proxy properties
  • Required Attribute Support: Optionally preserves [Required] attributes for validation frameworks
  • Comprehensive XmlInclude Support: Automatically detects and processes polymorphic type hierarchies from XmlInclude attributes on classes, interfaces, structs, and methods
  • Inheritance Hierarchy Support: Handles complex multi-level inheritance chains with automatic derived type discovery
  • Polymorphic Collections: Preserves polymorphic relationships in collections and properties
  • AutoMapper Profile Generation: Creates bidirectional mappings for entire type hierarchies with proper ordering
  • Incremental Generation: Efficient compilation with Roslyn's incremental generator pipeline
  • Flexible Configuration: Attribute-based and property-level configuration options
  • Comprehensive Diagnostics: Clear error messages and warnings with source location information for XmlInclude and configuration issues
  • Type Safety: Preserves property types, nullability, collections, and generic types across polymorphic hierarchies

Installation

NuGet Package (Coming Soon)

dotnet add package SoapProxyPocoGenerator

Note: All attributes are auto-generated by the source generator. You do not need to reference a separate attributes assembly.

Manual Installation

  1. Clone or download this repository
  2. Add project reference to your solution:
<ItemGroup>
  <ProjectReference Include="..\src\SoapProxyPocoGenerator\SoapProxyPocoGenerator.csproj" 
                    OutputItemType="Analyzer" 
                    ReferenceOutputAssembly="false" />
</ItemGroup>
  1. Add AutoMapper to your project:
dotnet add package AutoMapper

Quick Start

1. Define Your POCO Classes

Create empty partial POCO classes in your domain layer and mark them with [MapFromProxy]:

// Note: The MapFromProxy attribute is auto-generated by the source generator
// No need to reference a separate attributes assembly!

namespace MyApp.Models
{
    // Define your domain model and specify which proxy to map from
    [MapFromProxy(typeof(VehicleProxy))]
    public partial class Vehicle
    {
        // Properties will be automatically generated from VehicleProxy
    }

    [MapFromProxy(typeof(CarProxy))]
    public partial class Car : Vehicle
    {
        // Properties will be automatically generated from CarProxy
    }

    [MapFromProxy(typeof(TruckProxy))]
    public partial class Truck : Vehicle
    {
        // Properties will be automatically generated from TruckProxy
    }
}

Important: POCO classes must be marked as partial to allow the generator to extend them with properties.

2. Your Proxy Classes

Your existing SOAP proxy classes remain unchanged:

using System.Xml.Serialization;

namespace MyApp.ProxyModels
{
    [XmlInclude(typeof(CarProxy))]
    [XmlInclude(typeof(TruckProxy))]
    [XmlRoot("Vehicle")]
    public class VehicleProxy
    {
        [XmlElement("Id")]
        public int Id { get; set; }

        [XmlElement("Make")]
        public string Make { get; set; }

        [XmlElement("Model")]
        public string Model { get; set; }
    }

    [XmlRoot("Car")]
    public class CarProxy : VehicleProxy
    {
        [XmlElement("NumberOfDoors")]
        public int NumberOfDoors { get; set; }
    }

    [XmlRoot("Truck")]
    public class TruckProxy : VehicleProxy
    {
        [XmlElement("PayloadCapacity")]
        public double PayloadCapacity { get; set; }
    }
}

3. Build Your Project

The generator runs during compilation and creates:

  • Partial class extensions with properties from proxy classes
  • AutoMapper profile for bidirectional mapping

Generated partial class example:

// Auto-generated: Vehicle.g.cs
namespace MyApp.Models
{
    public partial class Vehicle
    {
        public int Id { get; set; }
        public string Make { get; set; }
        public string Model { get; set; }
    }
}

4. Use Your POCOs

using AutoMapper;
using MyApp.Models;
using MyApp.ProxyModels;

// Configure AutoMapper with generated profile
var config = new MapperConfiguration(cfg =>
{
    cfg.AddProfile(new VehicleMappingProfile());
});
IMapper mapper = config.CreateMapper();

// Create proxy instance (e.g., from SOAP service)
var carProxy = new CarProxy
{
    Id = 1,
    Make = "Toyota",
    Model = "Camry",
    NumberOfDoors = 4
};

// Map to POCO
var car = mapper.Map<Car>(carProxy);

// Map back to proxy
var mappedProxy = mapper.Map<CarProxy>(car);

Configuration Options

MapFromProxyAttribute

The [MapFromProxy] attribute is auto-generated by the source generator and applied to your POCO classes to specify which proxy class to generate properties from.

Constructor Parameter
[MapFromProxy(typeof(ProxyClassName))]
public partial class MyPoco
{
}

Parameter:

  • proxyType (Type, required): The proxy class type to generate properties from. Must be a valid class type accessible in the compilation.
Optional Properties
Namespace

Specify a custom namespace for generated code. If not specified, uses the POCO class namespace.

// Custom namespace
[MapFromProxy(typeof(VehicleProxy), Namespace = "MyApp.Domain.Models")]
public partial class Vehicle
{
}

// Default: uses POCO class namespace (MyApp.Models)
namespace MyApp.Models
{
    [MapFromProxy(typeof(VehicleProxy))]
    public partial class Vehicle
    {
    }
}
ExcludeProperties

Exclude specific properties from generation using type-safe property exclusion:

[MapFromProxy(
    typeof(PersonProxy),
    ExcludeProperties = new[] { "InternalId", "Metadata" }
)]
public partial class Person
{
    // InternalId and Metadata will not be generated
}

Note: Property exclusions are type-safe - properties are associated with the specific proxy type, preventing accidental exclusions across different types.

IncludeReadOnlyProperties

Control whether read-only properties (properties with only a getter) are included:

[MapFromProxy(
    typeof(PersonProxy),
    IncludeReadOnlyProperties = true  // Default: false
)]
public partial class Person
{
}

Default: false (read-only properties are excluded)

PreserveDefaultValues

Preserve default values from [DefaultValue] attributes on proxy properties:

[MapFromProxy(
    typeof(ConfigProxy),
    PreserveDefaultValues = true  // Default: false
)]
public partial class Config
{
}

// If ConfigProxy has: [DefaultValue(10)] public int MaxRetries { get; set; }
// Generated: public int MaxRetries { get; set; } = 10;

Default: false (default values are not preserved)

PreserveRequiredAttributes

Preserve [Required] attributes from proxy properties for validation:

[MapFromProxy(
    typeof(PersonProxy),
    PreserveRequiredAttributes = true  // Default: false
)]
public partial class Person
{
}

// If PersonProxy has: [Required] public string Name { get; set; }
// Generated: [Required] public string Name { get; set; }

Default: false (Required attributes are not preserved)

Property-Level Configuration

ExcludeMapAttribute

Apply to proxy class properties to exclude them from POCO generation:

// In your proxy class
public class PersonProxy
{
    public string Name { get; set; }
    
    [ExcludeMap]  // Auto-generated attribute
    public string InternalTrackingId { get; set; }  // Will be excluded
    
    public string Email { get; set; }
}

// In your POCO class
[MapFromProxy(typeof(PersonProxy))]
public partial class Person
{
    // Only Name and Email will be generated
}

Note: ExcludeFromPocoAttribute is still supported but obsolete. Use ExcludeMapAttribute instead.

Automatic Property Filtering

The generator automatically excludes certain properties without explicit configuration:

XmlIgnore Support

Properties marked with [XmlIgnore] are automatically excluded:

public class PersonProxy
{
    public string Name { get; set; }
    
    [XmlIgnore]
    public string InternalData { get; set; }  // Automatically excluded
}
Read-Only Properties

Properties with only a getter are excluded by default (unless IncludeReadOnlyProperties = true):

public class PersonProxy
{
    public string Name { get; set; }
    public string FullName { get; }  // Excluded by default
}
Init-Only Properties

Properties with init accessors are preserved:

public class PersonProxy
{
    public string Id { get; init; }  // Generated with init accessor
    public string Name { get; set; }
}

// Generated POCO:
public partial class Person
{
    public string Id { get; init; }  // Immutable after initialization
    public string Name { get; set; }
}

Complete Configuration Example

// Proxy class with various attributes
namespace MyApp.ProxyModels
{
    public class OrderProxy
    {
        public int Id { get; set; }
        public string OrderNumber { get; set; }
        
        [ExcludeMap]
        public string InternalReference { get; set; }
        
        [XmlIgnore]
        public string DebugInfo { get; set; }
        
        [DefaultValue(10)]
        public int MaxRetries { get; set; }
        
        [Required]
        public string CustomerName { get; set; }
        
        public DateTime OrderDate { get; set; }
        public string Status { get; }  // Read-only
    }
}

// POCO class with comprehensive configuration
namespace MyApp.Models
{
    [MapFromProxy(
        typeof(OrderProxy),
        ExcludeProperties = new[] { "OrderDate" },
        PreserveDefaultValues = true,
        PreserveRequiredAttributes = true,
        IncludeReadOnlyProperties = false
    )]
    public partial class Order
    {
        // Generated: Id, OrderNumber, MaxRetries = 10, [Required] CustomerName
        // Excluded: InternalReference (ExcludeMap), DebugInfo (XmlIgnore), 
        //           OrderDate (config), Status (read-only)
    }
}

Generated Code

Partial POCO Classes

The generator creates partial class extensions that add properties from proxy classes without SOAP/XML attributes:

// Your code: Vehicle.cs
namespace MyApp.Models
{
    [MapFromProxy(typeof(VehicleProxy))]
    public partial class Vehicle
    {
        // Empty - properties will be generated
    }
}

// Auto-generated: Vehicle.g.cs
namespace MyApp.Models
{
    public partial class Vehicle
    {
        public int Id { get; set; }
        public string Make { get; set; }
        public string Model { get; set; }
    }
}

// Your code: Car.cs
namespace MyApp.Models
{
    [MapFromProxy(typeof(CarProxy))]
    public partial class Car : Vehicle
    {
    }
}

// Auto-generated: Car.g.cs
namespace MyApp.Models
{
    public partial class Car
    {
        public int NumberOfDoors { get; set; }
    }
}

AutoMapper Profiles

Generated profiles include bidirectional mappings:

// Auto-generated: VehicleMappingProfile.g.cs
using AutoMapper;

namespace MyApp.Models
{
    public class VehicleMappingProfile : Profile
    {
        public VehicleMappingProfile()
        {
            CreateMap<MyApp.ProxyModels.VehicleProxy, MyApp.Models.Vehicle>().ReverseMap();
        }
    }
}

// Auto-generated: CarMappingProfile.g.cs
namespace MyApp.Models
{
    public class CarMappingProfile : Profile
    {
        public CarMappingProfile()
        {
            CreateMap<MyApp.ProxyModels.CarProxy, MyApp.Models.Car>().ReverseMap();
        }
    }
}

Advanced Features

Inheritance Hierarchy Handling

The generator automatically discovers and processes entire inheritance hierarchies:

  1. Base Class Discovery: Traverses up the inheritance chain to find root types
  2. Derived Type Discovery: Extracts derived types from XmlInclude attributes
  3. Recursive Processing: Processes all types in the hierarchy recursively
  4. Circular Reference Detection: Prevents infinite loops with visited type tracking

Complex Type Support

The generator handles various type scenarios:

  • Collections: List<T>, T[], IEnumerable<T>, etc.
  • Nullable Types: int?, string?, Nullable<T>
  • Generic Types: Preserves generic type parameters
  • Nested Proxy Types: Recursively generates POCOs for nested proxy classes

Example with Complex Types

// Proxy class
public class OrderProxy
{
    public int Id { get; set; }
    public List<OrderItemProxy> Items { get; set; }  // Collection
    public DateTime? ShippedDate { get; set; }       // Nullable
    public CustomerProxy Customer { get; set; }      // Nested proxy
}

// Your POCO definition
[MapFromProxy(typeof(OrderProxy))]
public partial class Order
{
}

// Generated partial class
public partial class Order
{
    public int Id { get; set; }
    public List<OrderItem> Items { get; set; }  // Collection preserved, mapped to OrderItem POCO
    public DateTime? ShippedDate { get; set; }  // Nullability preserved
    public Customer Customer { get; set; }      // Nested POCO reference
}

XmlInclude Support

The generator provides comprehensive support for polymorphic type hierarchies using the XmlInclude attribute. This feature automatically detects derived types and generates corresponding POCOs with proper inheritance relationships and AutoMapper mappings.

Overview

XmlInclude attributes can be applied to:

  • Classes: Base classes with derived types
  • Interfaces: Interfaces with multiple implementations
  • Structs: Struct types with derived types
  • Methods: Methods with polymorphic return types

The generator automatically:

  1. Detects XmlInclude attributes on type declarations and methods
  2. Validates that referenced types are actually derived from the base type
  3. Generates POCO classes for all types in the polymorphic hierarchy
  4. Creates AutoMapper mappings ordered from base to derived types
  5. Preserves polymorphic relationships in properties and collections

XmlInclude on Class Declarations

The most common scenario is applying XmlInclude to a base class to specify derived types:

// Proxy classes
using System.Xml.Serialization;

[XmlInclude(typeof(DogProxy))]
[XmlInclude(typeof(CatProxy))]
public class AnimalProxy
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public class DogProxy : AnimalProxy
{
    public string Breed { get; set; }
}

public class CatProxy : AnimalProxy
{
    public bool IsIndoor { get; set; }
}

// POCO definitions
[MapFromProxy(typeof(AnimalProxy))]
public partial class Animal
{
}

[MapFromProxy(typeof(DogProxy))]
public partial class Dog : Animal
{
}

[MapFromProxy(typeof(CatProxy))]
public partial class Cat : Animal
{
}

Generated Output:

  • Animal POCO with Name and Age properties
  • Dog POCO inheriting from Animal with Breed property
  • Cat POCO inheriting from Animal with IsIndoor property
  • AutoMapper profile with mappings for all three types

XmlInclude on Interface Declarations

XmlInclude can be applied to interfaces to specify implementing types:

// Proxy interfaces and classes
[XmlInclude(typeof(CircleProxy))]
[XmlInclude(typeof(RectangleProxy))]
public interface IShapeProxy
{
    double Area { get; }
}

public class CircleProxy : IShapeProxy
{
    public double Radius { get; set; }
    public double Area => Math.PI * Radius * Radius;
}

public class RectangleProxy : IShapeProxy
{
    public double Width { get; set; }
    public double Height { get; set; }
    public double Area => Width * Height;
}

// POCO definitions
[MapFromProxy(typeof(IShapeProxy))]
public partial interface IShape
{
}

[MapFromProxy(typeof(CircleProxy))]
public partial class Circle : IShape
{
}

[MapFromProxy(typeof(RectangleProxy))]
public partial class Rectangle : IShape
{
}

XmlInclude on Method Return Types

XmlInclude can be applied to methods to specify polymorphic return types:

// Service proxy with polymorphic method
public class PaymentServiceProxy
{
    [XmlInclude(typeof(CreditCardPaymentProxy))]
    [XmlInclude(typeof(PayPalPaymentProxy))]
    public PaymentProxy ProcessPayment(decimal amount)
    {
        // Implementation
    }
}

public class PaymentProxy
{
    public decimal Amount { get; set; }
    public DateTime ProcessedDate { get; set; }
}

public class CreditCardPaymentProxy : PaymentProxy
{
    public string CardNumber { get; set; }
}

public class PayPalPaymentProxy : PaymentProxy
{
    public string Email { get; set; }
}

// POCO definitions
[MapFromProxy(typeof(PaymentProxy))]
public partial class Payment
{
}

[MapFromProxy(typeof(CreditCardPaymentProxy))]
public partial class CreditCardPayment : Payment
{
}

[MapFromProxy(typeof(PayPalPaymentProxy))]
public partial class PayPalPayment : Payment
{
}

The generator detects the XmlInclude attributes on the method and generates POCOs for all types in the hierarchy.

Polymorphic Collections

When a property contains a collection of a base type with XmlInclude attributes, the generator preserves the polymorphic relationship:

// Proxy classes
[XmlInclude(typeof(ManagerProxy))]
[XmlInclude(typeof(DeveloperProxy))]
public class EmployeeProxy
{
    public string Name { get; set; }
    public string Department { get; set; }
}

public class ManagerProxy : EmployeeProxy
{
    public int TeamSize { get; set; }
}

public class DeveloperProxy : EmployeeProxy
{
    public string ProgrammingLanguage { get; set; }
}

public class TeamProxy
{
    public string TeamName { get; set; }
    public List<EmployeeProxy> Members { get; set; }  // Polymorphic collection
}

// POCO definitions
[MapFromProxy(typeof(EmployeeProxy))]
public partial class Employee
{
}

[MapFromProxy(typeof(ManagerProxy))]
public partial class Manager : Employee
{
}

[MapFromProxy(typeof(DeveloperProxy))]
public partial class Developer : Employee
{
}

[MapFromProxy(typeof(TeamProxy))]
public partial class Team
{
}

Generated Team POCO:

public partial class Team
{
    public string TeamName { get; set; }
    public List<Employee> Members { get; set; }  // Can contain Manager or Developer instances
}

AutoMapper automatically handles polymorphic collection mapping, so the Members list can contain any Employee-derived type.

Multi-Level Inheritance Hierarchies

The generator supports complex multi-level hierarchies with XmlInclude at each level:

// Three-level hierarchy
[XmlInclude(typeof(TextDocumentProxy))]
public class DocumentProxy
{
    public string Title { get; set; }
}

[XmlInclude(typeof(MarkdownDocumentProxy))]
public class TextDocumentProxy : DocumentProxy
{
    public string Content { get; set; }
}

public class MarkdownDocumentProxy : TextDocumentProxy
{
    public bool EnableTables { get; set; }
}

// POCO definitions
[MapFromProxy(typeof(DocumentProxy))]
public partial class Document
{
}

[MapFromProxy(typeof(TextDocumentProxy))]
public partial class TextDocument : Document
{
}

[MapFromProxy(typeof(MarkdownDocumentProxy))]
public partial class MarkdownDocument : TextDocument
{
}

The generator processes the entire hierarchy recursively, creating POCOs for all three levels with proper inheritance relationships.

AutoMapper Profile Generation

For polymorphic hierarchies, the generator creates AutoMapper profiles with mappings ordered from base to derived types:

// Generated AutoMapper profile
public class AnimalMappingProfile : Profile
{
    public AnimalMappingProfile()
    {
        // Base type mapping first
        CreateMap<AnimalProxy, Animal>().ReverseMap();
        
        // Derived type mappings
        CreateMap<DogProxy, Dog>().ReverseMap();
        CreateMap<CatProxy, Cat>().ReverseMap();
    }
}

This ordering ensures AutoMapper can properly handle polymorphic scenarios where a base type property may contain a derived type instance.

Limitations

While XmlInclude support is comprehensive, there are some limitations to be aware of:

1. XmlInclude on Properties Not Supported

XmlInclude attributes on properties are not part of the official .NET XML serialization API and are not supported by the generator:

// NOT SUPPORTED
public class ContainerProxy
{
    [XmlInclude(typeof(DerivedProxy))]  // This will be ignored
    public BaseProxy Item { get; set; }
}

Workaround: Apply XmlInclude to the type declaration instead:

[XmlInclude(typeof(DerivedProxy))]
public class BaseProxy
{
    // Properties
}

public class ContainerProxy
{
    public BaseProxy Item { get; set; }  // Polymorphism works via type-level XmlInclude
}
2. Generic Type Constraints

XmlInclude with open generic types has limitations:

// Limited support
[XmlInclude(typeof(GenericDerived<>))]  // Open generic type
public class GenericBase<T>
{
    public T Value { get; set; }
}

Workaround: Use closed generic types in XmlInclude:

[XmlInclude(typeof(GenericDerived<string>))]
[XmlInclude(typeof(GenericDerived<int>))]
public class GenericBase<T>
{
    public T Value { get; set; }
}
3. Cross-Assembly References

XmlInclude types must be accessible in the compilation:

// Type must be in the same assembly or a referenced assembly
[XmlInclude(typeof(ExternalLibrary.DerivedType))]  // Must reference ExternalLibrary
public class BaseProxy
{
}

Workaround: Ensure all referenced assemblies are properly included in your project references.

Best Practices

  1. Apply XmlInclude to Base Types: Always apply XmlInclude attributes to the base class or interface, not to properties.

  2. Define All Derived Types: Include all possible derived types in XmlInclude attributes to ensure complete POCO generation.

  3. Use Consistent Naming: Follow consistent naming conventions for proxy and POCO classes to improve code readability.

  4. Validate Hierarchies: Use the generator's diagnostics to identify and fix issues with type relationships.

  5. Test Polymorphic Mappings: Write tests to verify AutoMapper correctly handles polymorphic scenarios in your domain.

  6. Document Polymorphic Properties: Add comments to properties that can contain derived types to help other developers understand the polymorphic behavior.

Example: Complete Polymorphic Scenario

Here's a complete example demonstrating XmlInclude support with collections and multi-level hierarchies:

// Proxy classes
using System.Xml.Serialization;

[XmlInclude(typeof(PremiumAccountProxy))]
[XmlInclude(typeof(EnterpriseAccountProxy))]
public class AccountProxy
{
    public string AccountId { get; set; }
    public string Name { get; set; }
}

public class PremiumAccountProxy : AccountProxy
{
    public decimal MonthlyFee { get; set; }
}

public class EnterpriseAccountProxy : PremiumAccountProxy
{
    public int UserLimit { get; set; }
    public string SupportLevel { get; set; }
}

public class OrganizationProxy
{
    public string OrgName { get; set; }
    public List<AccountProxy> Accounts { get; set; }  // Polymorphic collection
}

// POCO definitions
[MapFromProxy(typeof(AccountProxy))]
public partial class Account
{
}

[MapFromProxy(typeof(PremiumAccountProxy))]
public partial class PremiumAccount : Account
{
}

[MapFromProxy(typeof(EnterpriseAccountProxy))]
public partial class EnterpriseAccount : PremiumAccount
{
}

[MapFromProxy(typeof(OrganizationProxy))]
public partial class Organization
{
}

// Usage
var config = new MapperConfiguration(cfg =>
{
    cfg.AddProfile(new AccountMappingProfile());
    cfg.AddProfile(new OrganizationMappingProfile());
});
IMapper mapper = config.CreateMapper();

var orgProxy = new OrganizationProxy
{
    OrgName = "Acme Corp",
    Accounts = new List<AccountProxy>
    {
        new AccountProxy { AccountId = "1", Name = "Basic" },
        new PremiumAccountProxy { AccountId = "2", Name = "Premium", MonthlyFee = 99.99m },
        new EnterpriseAccountProxy 
        { 
            AccountId = "3", 
            Name = "Enterprise", 
            MonthlyFee = 499.99m,
            UserLimit = 100,
            SupportLevel = "24/7"
        }
    }
};

// Map to POCO - polymorphic types are preserved
var org = mapper.Map<Organization>(orgProxy);

// org.Accounts contains Account, PremiumAccount, and EnterpriseAccount instances
foreach (var account in org.Accounts)
{
    Console.WriteLine($"{account.Name}: {account.GetType().Name}");
    
    if (account is EnterpriseAccount enterprise)
    {
        Console.WriteLine($"  Support: {enterprise.SupportLevel}");
    }
}

XmlChoiceIdentifier Support

The generator handles discriminated union patterns using XmlChoiceIdentifier:

// Proxy with XmlChoice pattern
public class MessageProxy
{
    [XmlChoiceIdentifier("ItemType")]
    [XmlElement("String", typeof(string))]
    [XmlElement("Number", typeof(int))]
    public object Item { get; set; }
    
    [XmlIgnore]
    public ItemChoice ItemType { get; set; }
}

public enum ItemChoice
{
    String,
    Number
}

// Generated POCO preserves both properties
[MapFromProxy(typeof(MessageProxy))]
public partial class Message
{
    public object Item { get; set; }
    public ItemChoice ItemType { get; set; }  // Included even with XmlIgnore
}

The generator emits an informational diagnostic (SPPG017) when it detects XmlChoice patterns.

XmlAnyElement and XmlAnyAttribute Support

Properties with XmlAnyElement or XmlAnyAttribute are included in generated POCOs:

// Proxy with extensibility support
public class ExtensibleProxy
{
    public string Name { get; set; }
    
    [XmlAnyElement]
    public XmlElement[] ExtraElements { get; set; }
    
    [XmlAnyAttribute]
    public XmlAttribute[] ExtraAttributes { get; set; }
}

// Generated POCO includes XmlAny properties
[MapFromProxy(typeof(ExtensibleProxy))]
public partial class Extensible
{
    public string Name { get; set; }
    public XmlElement[] ExtraElements { get; set; }
    public XmlAttribute[] ExtraAttributes { get; set; }
}

The generator emits an informational diagnostic (SPPG018) for XmlAny properties, as manual mapping may be required for XML types.

XmlEnum Support

Enum properties are correctly mapped even when XmlEnum attributes specify different serialization names:

// Proxy with enum
public enum StatusProxy
{
    [XmlEnum("active")]
    Active,
    
    [XmlEnum("inactive")]
    Inactive,
    
    [XmlEnum("pending")]
    Pending
}

public class OrderProxy
{
    public StatusProxy Status { get; set; }
}

// Generated POCO preserves enum type
[MapFromProxy(typeof(OrderProxy))]
public partial class Order
{
    public StatusProxy Status { get; set; }  // Enum type preserved
}

The generator copies enum types as-is without attempting to rename values based on XmlEnum attributes.

Diagnostics

The generator provides comprehensive diagnostics to help identify and resolve issues:

SPPG001: Unsupported Type

Severity: Warning

Description: A type cannot be mapped to a POCO type (e.g., pointer types, ref structs).

Example:

warning SPPG001: Type 'System.IntPtr' is not supported for POCO generation. Pointer types cannot be used in POCOs.

Resolution: Remove or replace unsupported types with compatible alternatives.

SPPG002: Circular Reference Detected

Severity: Warning

Description: A circular reference was detected in the inheritance hierarchy or type relationships.

Example:

warning SPPG002: Circular reference detected in type 'MyApp.ProxyModels.NodeProxy'. The type will be skipped to prevent infinite recursion.

Resolution: Review your type hierarchy and break circular dependencies. Consider using interfaces or restructuring relationships.

SPPG003: Invalid Configuration

Severity: Error

Description: Configuration provided in GeneratePocoAttribute is invalid.

Example:

error SPPG003: Invalid configuration: Namespace cannot be empty or whitespace.

Resolution: Check attribute parameters and ensure they contain valid values.

SPPG004: XmlInclude Type Not Found

Severity: Warning

Description: A type specified in an XmlInclude attribute could not be resolved.

Example:

warning SPPG004: Type 'MyApp.ProxyModels.MissingProxy' specified in XmlInclude attribute on 'VehicleProxy' was not found or could not be resolved.

Resolution: Ensure all types referenced in XmlInclude attributes exist and are accessible in the compilation.

SPPG005: Property Type Mapping Warning

Severity: Info

Description: A property type may not map exactly to the POCO representation.

Example:

info SPPG005: Property 'ComplexData' on type 'MyProxy' has type 'System.Object' which may not map correctly to POCO. Consider using a more specific type.

Resolution: Review the generated code and consider using more specific types for better type safety.

SPPG006: Proxy Type Not Found

Severity: Error

Description: The proxy type specified in MapFromProxy attribute cannot be found.

Example:

error SPPG006: Proxy type 'MyApp.ProxyModels.MissingProxy' specified in MapFromProxy attribute was not found or could not be resolved.

Resolution: Ensure the proxy type exists and is accessible. Check namespace and assembly references.

SPPG007: Invalid Proxy Type

Severity: Error

Description: The proxy type parameter is not a valid class type.

Example:

error SPPG007: Type 'System.Int32' is not a valid proxy class type. The proxy type must be a class.

Resolution: Ensure the MapFromProxy attribute references a class type, not a value type or interface.

SPPG008: POCO Class Not Partial

Severity: Warning

Description: A POCO class with MapFromProxy is not declared as partial.

Example:

warning SPPG008: Class 'Vehicle' should be declared as partial to allow property generation.

Resolution: Add the partial keyword to your POCO class declaration:

[MapFromProxy(typeof(VehicleProxy))]
public partial class Vehicle  // Add 'partial' keyword
{
}

SPPG009: Invalid Namespace

Severity: Error

Description: The namespace specified in MapFromProxy is not valid.

Example:

error SPPG009: Namespace '123Invalid' is not a valid C# namespace identifier.

Resolution: Use a valid C# namespace following naming conventions (letters, numbers, underscores, dots).

SPPG010: Base POCO Class Missing

Severity: Warning

Description: A base POCO class is expected but not found for a proxy base type.

Example:

warning SPPG010: Base POCO class 'Vehicle' is expected but not found for proxy base type 'VehicleProxy'. Ensure the POCO class inherits from the expected base class.

Resolution: Ensure your POCO class hierarchy matches the proxy class hierarchy:

[MapFromProxy(typeof(VehicleProxy))]
public partial class Vehicle { }

[MapFromProxy(typeof(CarProxy))]
public partial class Car : Vehicle { }  // Must inherit from Vehicle

SPPG011: XmlInclude Type Not Derived

Severity: Warning

Description: An XmlInclude attribute references a type that is not derived from the declaring type.

Example:

warning SPPG011: Type 'DogProxy' in XmlInclude attribute is not derived from 'VehicleProxy'

Resolution: Ensure the type in XmlInclude actually inherits from the base type:

[XmlInclude(typeof(CarProxy))]  // CarProxy must inherit from VehicleProxy
public class VehicleProxy
{
}

public class CarProxy : VehicleProxy  // Correct inheritance
{
}

SPPG012: XmlInclude On Void Method

Severity: Warning

Description: XmlInclude attribute is applied to a method with void return type.

Example:

warning SPPG012: XmlInclude attribute on method 'ProcessData' with void return type will be ignored

Resolution: Remove the XmlInclude attribute from void methods, or change the method to return a type:

// Remove XmlInclude from void methods
public void ProcessData() { }

// Or change to return a type
[XmlInclude(typeof(DerivedResult))]
public ResultProxy ProcessData() { }

SPPG013: Duplicate XmlInclude Type

Severity: Info

Description: The same type is referenced in multiple XmlInclude attributes.

Example:

info SPPG013: Type 'DogProxy' is referenced multiple times in XmlInclude attributes on 'AnimalProxy'

Resolution: Remove duplicate XmlInclude attributes:

// Before
[XmlInclude(typeof(DogProxy))]
[XmlInclude(typeof(CatProxy))]
[XmlInclude(typeof(DogProxy))]  // Duplicate
public class AnimalProxy { }

// After
[XmlInclude(typeof(DogProxy))]
[XmlInclude(typeof(CatProxy))]
public class AnimalProxy { }

SPPG014: XmlInclude Type Not Derived From Return Type

Severity: Warning

Description: An XmlInclude attribute on a method references a type that is not derived from the method return type.

Example:

warning SPPG014: Type 'DogProxy' in XmlInclude attribute is not derived from method return type 'VehicleProxy'

Resolution: Ensure the XmlInclude type inherits from the method return type:

[XmlInclude(typeof(CarProxy))]  // CarProxy must inherit from VehicleProxy
public VehicleProxy GetVehicle()
{
    return new CarProxy();
}

public class CarProxy : VehicleProxy  // Correct inheritance
{
}

SPPG015: Property Excluded Due to XmlIgnore

Severity: Info

Description: A property was automatically excluded because it has the [XmlIgnore] attribute.

Example:

info SPPG015: Property 'InternalData' excluded from POCO generation due to [XmlIgnore] attribute

Resolution: This is informational. If you want to include the property, remove the [XmlIgnore] attribute from the proxy class.

SPPG016: Property Excluded Due to Read-Only

Severity: Info

Description: A read-only property was excluded from POCO generation.

Example:

info SPPG016: Property 'FullName' excluded from POCO generation because it is read-only. Set IncludeReadOnlyProperties=true to include it.

Resolution: Set IncludeReadOnlyProperties = true in the MapFromProxy attribute if you want to include read-only properties:

[MapFromProxy(typeof(PersonProxy), IncludeReadOnlyProperties = true)]
public partial class Person { }

SPPG017: XmlChoice Pattern Detected

Severity: Info

Description: A property uses XmlChoiceIdentifier for discriminated union patterns.

Example:

info SPPG017: Property 'Item' uses XmlChoiceIdentifier with discriminator 'ItemType'

Resolution: This is informational. The generator will include both the choice property and discriminator property in the POCO.

SPPG018: XmlAny Property Detected

Severity: Info

Description: A property uses XmlAnyElement or XmlAnyAttribute for extensibility.

Example:

info SPPG018: Property 'ExtraElements' uses XmlAnyElement or XmlAnyAttribute. Manual mapping may be required for XML types.

Resolution: This is informational. The property will be included in the POCO, but you may need to handle XML type mapping manually in your AutoMapper configuration.

SPPG019: Invalid Default Value

Severity: Warning

Description: A default value specified in a [DefaultValue] attribute is not compatible with the property type.

Example:

warning SPPG019: Default value 'abc' for property 'Count' is not compatible with type 'int'

Resolution: Fix the default value in the proxy class to match the property type:

// Wrong
[DefaultValue("abc")]
public int Count { get; set; }

// Correct
[DefaultValue(0)]
public int Count { get; set; }

Troubleshooting

Generated Files Not Appearing

Problem: POCOs and AutoMapper profiles are not being generated.

Solutions:

  1. Ensure the generator project is referenced with OutputItemType="Analyzer"
  2. Clean and rebuild your solution
  3. Check that proxy classes have the [GeneratePoco] attribute
  4. Verify the generator project targets .NET Standard 2.0
  5. Check the build output for diagnostic messages

Compilation Errors in Generated Code

Problem: Generated code causes compilation errors.

Solutions:

  1. Check for SPPG diagnostics in the Error List
  2. Ensure all proxy types are accessible (public visibility)
  3. Verify namespace configuration is valid
  4. Check for naming conflicts with existing types

AutoMapper Configuration Errors

Problem: AutoMapper throws configuration errors at runtime.

Solutions:

  1. Ensure you've added the generated profile to AutoMapper configuration:
    cfg.AddProfile(new YourProxyMappingProfile());
    
  2. Verify AutoMapper package is installed
  3. Check that POCO and proxy types are accessible
  4. Use config.AssertConfigurationIsValid() to identify mapping issues

Missing Properties in Generated POCOs

Problem: Some properties are not appearing in generated POCOs.

Solutions:

  1. Check if properties are marked with [ExcludeFromPoco]
  2. Verify properties are not listed in ExcludeProperties attribute parameter
  3. Ensure properties are public (private/internal properties are excluded)
  4. Check for SPPG005 diagnostics about unsupported property types

Inheritance Not Working Correctly

Problem: Derived types are not being generated or inheritance is broken.

Solutions:

  1. Ensure base classes have [XmlInclude] attributes for all derived types
  2. Verify all types in the hierarchy are accessible
  3. Check for SPPG002 circular reference warnings
  4. Ensure derived types are in the same assembly or referenced assemblies

Performance Issues During Build

Problem: Build times are significantly slower after adding the generator.

Solutions:

  1. The generator uses incremental generation - only changed files are reprocessed
  2. Reduce the number of proxy classes marked with [GeneratePoco]
  3. Check for circular references that may cause repeated processing
  4. Ensure you're using the latest version of the generator

XmlInclude Types Not Being Generated

Problem: POCOs for derived types referenced in XmlInclude are not being generated.

Solutions:

  1. Verify the XmlInclude attribute syntax is correct:
    [XmlInclude(typeof(DerivedProxy))]  // Correct
    public class BaseProxy { }
    
  2. Ensure the derived type is accessible (public visibility)
  3. Check for SPPG004 diagnostics indicating the type cannot be found
  4. Verify the derived type actually inherits from the base type
  5. Ensure you have MapFromProxy attributes on all POCO classes in the hierarchy
  6. Check that the derived type is in the same assembly or a referenced assembly

XmlInclude Diagnostics (SPPG011, SPPG014)

Problem: Getting warnings about XmlInclude types not being derived from base type.

Solutions:

  1. Verify the inheritance relationship:
    [XmlInclude(typeof(DogProxy))]
    public class AnimalProxy { }
    
    public class DogProxy : AnimalProxy { }  // Must inherit
    
  2. For method XmlInclude, ensure the type inherits from the return type:
    [XmlInclude(typeof(CarProxy))]
    public VehicleProxy GetVehicle() { }
    
    public class CarProxy : VehicleProxy { }  // Must inherit from return type
    
  3. Check for typos in type names
  4. Ensure you're not mixing unrelated type hierarchies

Polymorphic Collections Not Mapping Correctly

Problem: AutoMapper fails to map collections with polymorphic elements.

Solutions:

  1. Ensure all types in the hierarchy have generated AutoMapper profiles
  2. Register all profiles with AutoMapper:
    var config = new MapperConfiguration(cfg =>
    {
        cfg.AddProfile(new BaseTypeMappingProfile());
        cfg.AddProfile(new DerivedType1MappingProfile());
        cfg.AddProfile(new DerivedType2MappingProfile());
    });
    
  3. Verify the base type has XmlInclude attributes for all derived types
  4. Check that POCO inheritance matches proxy inheritance
  5. Use config.AssertConfigurationIsValid() to identify mapping issues

XmlInclude on Methods Not Working

Problem: XmlInclude attributes on methods are being ignored.

Solutions:

  1. Ensure the method has a non-void return type:
    // Won't work - void return
    [XmlInclude(typeof(DerivedProxy))]
    public void ProcessData() { }
    
    // Works - returns a type
    [XmlInclude(typeof(DerivedProxy))]
    public BaseProxy ProcessData() { }
    
  2. Check for SPPG012 diagnostics about void methods
  3. Verify the XmlInclude types inherit from the method return type
  4. Ensure the method is public and accessible

Multi-Level Hierarchies Not Fully Generated

Problem: Only some levels of a multi-level hierarchy are being generated.

Solutions:

  1. Ensure XmlInclude attributes are present at each level:
    [XmlInclude(typeof(MiddleProxy))]
    public class BaseProxy { }
    
    [XmlInclude(typeof(DerivedProxy))]  // Don't forget this!
    public class MiddleProxy : BaseProxy { }
    
    public class DerivedProxy : MiddleProxy { }
    
  2. Define MapFromProxy for all levels:
    [MapFromProxy(typeof(BaseProxy))]
    public partial class Base { }
    
    [MapFromProxy(typeof(MiddleProxy))]
    public partial class Middle : Base { }
    
    [MapFromProxy(typeof(DerivedProxy))]
    public partial class Derived : Middle { }
    
  3. Check for circular reference warnings (SPPG002)
  4. Verify all types are accessible and public

XmlInclude on Interfaces Not Working

Problem: XmlInclude on interfaces is not generating POCOs for implementing types.

Solutions:

  1. Ensure the interface is marked with XmlInclude:
    [XmlInclude(typeof(Implementation1Proxy))]
    [XmlInclude(typeof(Implementation2Proxy))]
    public interface IBaseProxy { }
    
  2. Verify implementing types actually implement the interface:
    public class Implementation1Proxy : IBaseProxy { }
    
  3. Define MapFromProxy for the interface and all implementations
  4. Check that the interface and implementations are public

Duplicate Type Warnings (SPPG013)

Problem: Getting informational diagnostics about duplicate XmlInclude references.

Solutions:

  1. Remove duplicate XmlInclude attributes:
    // Before
    [XmlInclude(typeof(DogProxy))]
    [XmlInclude(typeof(CatProxy))]
    [XmlInclude(typeof(DogProxy))]  // Duplicate
    
    // After
    [XmlInclude(typeof(DogProxy))]
    [XmlInclude(typeof(CatProxy))]
    
  2. This is informational only - the generator will process the type once
  3. Clean up duplicates to improve code clarity

Generic Types with XmlInclude

Problem: XmlInclude with generic types is not working as expected.

Solutions:

  1. Use closed generic types in XmlInclude:
    // Won't work well
    [XmlInclude(typeof(GenericDerived<>))]
    
    // Works better
    [XmlInclude(typeof(GenericDerived<string>))]
    [XmlInclude(typeof(GenericDerived<int>))]
    public class GenericBase<T> { }
    
  2. Define separate MapFromProxy for each closed generic type
  3. Consider using non-generic base types for polymorphic scenarios
  4. Check generator diagnostics for specific issues with generic types

Sample Project

The solution includes comprehensive sample projects demonstrating various features:

Main Sample Project

  • Proxy classes with inheritance hierarchies
  • POCO generation configuration
  • AutoMapper profile usage
  • Bidirectional mapping examples

XmlInclude Samples

Located in src/SampleProject/XmlIncludeSamples/, this directory contains detailed examples of XmlInclude support:

  1. Class Declaration Samples: XmlInclude on base classes with multiple derived types
  2. Interface Declaration Samples: XmlInclude on interfaces with multiple implementations
  3. Method Return Type Samples: XmlInclude on methods with polymorphic return types
  4. Polymorphic Collection Samples: Collections containing polymorphic elements
  5. Multi-Level Inheritance Samples: Three-level inheritance hierarchies with XmlInclude at each level
  6. MapFromProxy Approach: Recommended pattern using explicit POCO definitions

Each sample includes:

  • Proxy class definitions with XmlInclude attributes
  • POCO class definitions with MapFromProxy attributes
  • Generated code examples
  • Usage demonstrations

To run the samples:

cd src/SampleProject
dotnet build
dotnet run

To view generated files, check the obj/GeneratedFiles/ directory after building.

For detailed documentation on XmlInclude samples, see src/SampleProject/XmlIncludeSamples/README.md.

Requirements

  • .NET 8.0 or later (for consuming projects)
  • .NET Standard 2.0 (for generator library)
  • AutoMapper 12.0.0 or later (runtime dependency)
  • Microsoft.CodeAnalysis.CSharp 4.8.0 or later (generator dependency)

Contributing

We welcome contributions from the community! Whether you're fixing bugs, adding features, improving documentation, or reporting issues, your help is appreciated.

How to Contribute

  1. Fork the repository and create your branch from main
  2. Make your changes following our coding standards
  3. Add tests for new functionality
  4. Ensure all tests pass by running dotnet test
  5. Update documentation as needed
  6. Submit a pull request with a clear description of your changes

Please read our Contributing Guidelines for detailed information on:

  • Development setup
  • Code style and conventions
  • Testing guidelines
  • Pull request process
  • Reporting issues

Publishing to NuGet

For maintainers publishing new versions:

Code of Conduct

This project adheres to the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code.

Getting Help

License

This project is licensed under the MIT License - see the LICENSE file for details.

Copyright (c) 2025 SOAP Proxy POCO Generator Contributors

Acknowledgments

Built With

  • Roslyn - .NET Compiler Platform for source generation
  • AutoMapper - Object-to-object mapping library

Inspiration

This project was created to solve the common problem of maintaining clean domain models while working with SOAP web services. By reversing the traditional code generation approach, developers can define their domain models explicitly and let the generator handle the tedious property copying.

Contributors

Thank you to all the contributors who have helped make this project better! 🎉

Special Thanks

  • The .NET team for creating the powerful Roslyn compiler platform
  • The AutoMapper team for their excellent mapping library
  • The open-source community for inspiration and support
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.0 179 11/2/2025

Version 0.1.0 - Initial release with domain-first approach, auto-generated attributes, comprehensive XML serialization support, polymorphic type hierarchies, and AutoMapper profile generation. See CHANGELOG.md for full feature list.