{"id":26445617,"url":"https://github.com/alekshura/sourcemapper","last_synced_at":"2026-02-23T22:15:20.877Z","repository":{"id":39053396,"uuid":"402455724","full_name":"alekshura/SourceMapper","owner":"alekshura","description":"Mappings code generator based on attributes","archived":false,"fork":false,"pushed_at":"2022-06-01T19:04:17.000Z","size":2519,"stargazers_count":8,"open_issues_count":5,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-08T10:05:01.179Z","etag":null,"topics":["alekshura","codegenerator","csharp","dotnet-core","dotnet-standard","mapper"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alekshura.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-09-02T14:41:53.000Z","updated_at":"2024-09-10T08:20:50.000Z","dependencies_parsed_at":"2022-08-31T16:22:22.179Z","dependency_job_id":null,"html_url":"https://github.com/alekshura/SourceMapper","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alekshura%2FSourceMapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alekshura%2FSourceMapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alekshura%2FSourceMapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alekshura%2FSourceMapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alekshura","download_url":"https://codeload.github.com/alekshura/SourceMapper/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244208596,"owners_count":20416110,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alekshura","codegenerator","csharp","dotnet-core","dotnet-standard","mapper"],"created_at":"2025-03-18T11:19:52.909Z","updated_at":"2026-02-23T22:15:15.857Z","avatar_url":"https://github.com/alekshura.png","language":"C#","readme":"# \u003cimg src=\"/Compentio.Assets/Logo.png\" align=\"left\" width=\"50\"\u003e SourceMapper\n\n[![NuGet](http://img.shields.io/nuget/v/Compentio.SourceMapper.svg)](https://www.nuget.org/packages/Compentio.SourceMapper)\n![Sonar Quality Gate](https://img.shields.io/sonar/quality_gate/alekshura_SourceMapper?server=https%3A%2F%2Fsonarcloud.io)\n[![Test](https://github.com/alekshura/SourceMapper/actions/workflows/pr-tests.yml/badge.svg)](https://github.com/alekshura/SourceMapper/actions/workflows/pr-tests.yml)\n[![Build](https://github.com/alekshura/SourceMapper/actions/workflows/main.yml/badge.svg)](https://github.com/alekshura/SourceMapper/actions/workflows/main.yml)\n![Nuget](https://img.shields.io/nuget/dt/Compentio.SourceMapper)\n![GitHub](https://img.shields.io/github/license/alekshura/SourceMapper)\n![GitHub top language](https://img.shields.io/github/languages/top/alekshura/SourceMapper)\n\n# Introduction\n`SourceMapper` is a code generator that uses attributes placed in interfaces or abstract classes: \nduring build time it generates mapping classes and methods for mappings based on \"rules\" defined in these attributes. \n\nIt is based on [Source Generators](https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.md) feature\nthat has been intoduced with `C# 9.0` and brings a possibility to  generate code during build time.\n\n:point_right:\nAfter configuring you mappers you can see, control and override the generated code for the mappings.\n:point_left:\n\n# Installation\nInstall using nuget package manager:\n\n```console\nInstall-Package Compentio.SourceMapper\n```\n\nor `.NET CLI`:\n\n```console\ndotnet add package Compentio.SourceMapper\n```\n\n# How to use\nTo define mapping we have to mark mapping abstract class or interface with `MapperAttribute`:\n\n```csharp\n[Mapper]\npublic interface INotesMapper\n{\n    NoteDto MapToDto(NoteDao source);\n}\n```\nThis will generate mapping class with default class name `NotesMapper` for properties that names are the same for `NoteDto` and `NoteDao` classes.\nThe generated class is in the same namespace as its base abstract class of interface. It can be found in project in Visual Studio: \n\u003e Dependencies -\u003e Analyzers -\u003e Compentio.SourceMapper.Generators.MainSourceGenerator.\n\nWhen the names are different than we can use `Source` and `Target` names of the properties:\n\n```csharp\n[Mapper(ClassName = \"InterfaceUserMapper\")]\npublic interface IUserMapper\n{\n    [Mapping(Source = nameof(UserDao.FirstName), Target = nameof(UserInfo.Name))]\n    UserInfo MapToDomainMoodel(UserDao userDao);       \n}\n```\n\nThe `ClassName` property in `MapperAttribute` is responsible for name of the generated mapping class. \nFor default `MapperAttribute` interface prefix `I` is removed or `Impl` suffix added to the generated class name if there is no `I` prefix\nin the mapping interface name.\n\n## Interface mapping\nUse interfaces to prepare basic mapping. \nIn a case when mapped object contains another objects, e.g.:\n\n```csharp\npublic class NoteDto\n{\n    public long Id { get; set; }\n    public string Title { get; set; }\n    public string Description { get; set; }\n    public NoteDocumentDto Document { get; set; }\n}\n```\n\nand\n\n```csharp\npublic class NoteDao\n{\n    public long Id { get; set; }\n    public string PageTitle { get; set; }\n    public string Description { get; set; }\n    public NoteDocumentDao Document { get; set; }\n}\n```\nit is enough to add mapping method to the interface for these types and the code generation processor will match and generate mappings for \n`NoteDto MapToDto(NoteDao source)` method:\n\n```csharp\n[Mapper]\npublic interface INotesMapper\n{\n    [Mapping(Source = nameof(NoteDao.PageTitle), Target = nameof(NoteDto.Title))]\n    NoteDto MapToDto(NoteDao source);\n\n    [Mapping(Source = nameof(NoteDocumentDao.Metadata.CreatorFirstName), Target = nameof(NoteDocumentDto.Autor))]\n    NoteDocumentDto MapToDto(NoteDocumentDao source);\n}\n```\n\nthe output will be:\n\n```csharp\npublic class NotesMapper : INotesMapper\n{\n    public static NotesMapper Create() =\u003e new();\n    public virtual Compentio.Example.App.Entities.NoteDto MapToDto(Compentio.Example.App.Entities.NoteDao source)\n    {\n        if (source == null)\n            return source;\n        var target = new Compentio.Example.App.Entities.NoteDto();\n        target.Id = source.Id;\n        target.Title = source.PageTitle;\n        target.Description = source.Description;\n        target.Document = MapDocumentToDto(source.Document);\n        return target;\n    }\n\n    public virtual Compentio.Example.App.Entities.NoteDocumentDto MapDocumentToDto(Compentio.Example.App.Entities.NoteDocumentDao source)\n    {\n        if (source == null)\n            return source;\n        var target = new Compentio.Example.App.Entities.NoteDocumentDto();\n        target.Id = source.Id;\n        target.Title = source.Title;\n        return target;\n    }\n}\n```\n\u003e All methds are marked as `virtual`, so there is a possibility to override them in own mappers code. \n\n\n## Class mapping\nFor more complicated mapings use abstract class to define mappers. The main difference between abstract class mapper and interface, that `Expression`\nproperty can be used in `MappingAttribute`:\n\n```csharp\n[Mapper(ClassName = \"NotesMappings\")]\npublic abstract class NotesClassMapper\n{\n    [Mapping(Target = nameof(NoteDocumentDto.Autor), Expression = nameof(ConvertAuthor))]\n    public abstract NoteDocumentDto MapToDto(NoteDocumentDao source);\n\n    protected readonly Func\u003cNoteDocumentDao, string\u003e ConvertAuthor = s =\u003e s.Metadata.CreatorFirstName + s.Metadata.CreatorLastName;\n}\n\n```\n\n`Expression` - it is a name of mapping function, that can be used for additional properties mapping. \n\u003e It must be `public` or `protected`, since it is used in generated mapper class that implements abstract mapping class.\n\n## Ignore mapping\n\nIf for any reason part of the class/interface properties should not be mapped, `IgnoreMapping` attribute should be used for that.\nAdded `IgnoreMapping` causes that both source and target property during mapping generation will be omitted, not generating any linked map and not reporting any warning in diagnostics.\nIf we have two classes `NoteDao` and `NoteDto` \n\n```csharp\npublic class NoteDao\n{\n\tpublic long Id { get; set; }\n\tpublic string PageTitle { get; set; }\n\tpublic string Description { get; set; }\n\n\t[IgnoreMapping]\n\tpublic DateTime ValidFrom { get; set; }\n\n\t[IgnoreMapping]\n\tpublic DateTime ValidTo { get; set; }\n\n\t[IgnoreMapping]\n\tpublic string CreatedBy { get; set; }\n\n\t[IgnoreMapping]\n\tpublic DateTime Created { get; set; }\n\n\t[IgnoreMapping]\n\tpublic DateTime Modified { get; set; }\n}\n```\n\n```csharp\npublic class NoteDto\n{\n\tpublic long Id { get; set; }\n\tpublic string Title { get; set; }\n\tpublic string Description { get; set; }\n}\n```\n\nand we need to map only matched fields, the mapper class `NotesClassMapper` lead to creating proper mapping result class `NotesMappings` without any warning:\n\n```csharp\n[Mapper(ClassName = \"NotesMappings\")]\npublic abstract partial class NotesClassMapper\n{\n\t[Mapping(Source = nameof(NoteDao.PageTitle), Target = nameof(NoteDto.Title))]\n\tpublic abstract NoteDto MapToDto(NoteDao source);\n}\n```\n\n```csharp\npublic class NotesMappings : NotesClassMapper\n{\n\tpublic override Compentio.Example.DotNetCore.App.Entities.NoteDto MapToDto(Compentio.Example.DotNetCore.App.Entities.NoteDao source)\n\t{\n\t\tif (source == null)\n\t\t\treturn null;\n\t\tvar target = new Compentio.Example.DotNetCore.App.Entities.NoteDto();\n\t\ttarget.Id = source.Id;\n\t\ttarget.Title = source.PageTitle;\n\t\ttarget.Description = source.Description;\n\t\treturn target;\n\t}\n}\n```\n\n### Mapping collections\nLets assume we need to map two entities:\n\n```csharp\npublic class UserDao\n{\n    public long UserId { get; set; }\n    public AddressDao[] UserAddresses { get; set; }\n}\n```\nto \n\n```csharp\npublic class UserInfo\n{\n    public int Id { get; set; }\n    public Address[] Addresses { get; set; }\n}\n```\nIt can be achieved using abstract class mapper:\n\n```csharp\n[Mapper(ClassName = \"UserDataMapper\")]\npublic abstract class UserMapper\n{\n    [Mapping(Source = nameof(UserDao.UserAddresses), Target = nameof(UserInfo.Addresses), Expression = nameof(ConvertAddresses))]\n    [Mapping(Source = nameof(UserDao.UserId), Target = nameof(UserInfo.Id), Expression = nameof(ConvertUserId))]\n    public abstract UserInfoWithArray MapToDomainModel(UserWithArrayDao userWithArrayDao);\n\n    protected Address[] ConvertAddresses(AddressDao[] addresses)\n    {\n        return addresses.Select(a =\u003e MapAddress(a)).ToArray();\n    }\n    \n    protected static int ConvertUserId(long id)\n    {\n        return Convert.ToInt32(id);\n    }\n\n    public abstract Address MapAddress(AddressDao addressDao);\n}\n```\n\nFor more examples see [Wiki examples](https://github.com/alekshura/SourceMapper/wiki/Examples#mapping-collections).\n\n\n## Dependency injection\nThe `Compentio.SourceMapper` searches for 3 main dependency container packages (`Microsoft.Extensions.DependencyInjection`, `Autofac.Extensions.DependencyInjection`, and `StructureMap.Microsoft.DependencyInjection`) and generates extension code. If there no any container packages found, Dependency Injection extension class is not generated.\n\nTo simplify adding dependency injection for mappers `MappersDependencyInjectionExtensions` class is generated, that can be used by adding `AddMappers()` that adds all mappers defined in the project.\n\nFor `Microsoft.Extensions.DependencyInjection`, in service configuration:\n\n```csharp\n Host.CreateDefaultBuilder(args)\n                .ConfigureServices((_, services) =\u003e\n                    services\n                    //.here you services\n                    //\n                    .AddMappers());\n```\n\nIn case of `Autofac.Extensions.DependencyInjection`, container configuration can be separated in module file `AutofacModules`, where we place registrations directly with Autofac: \n\n```csharp\npublic class AutofacModule : Module\n{\n\tprotected override void Load(ContainerBuilder builder)\n\t{\n\t\t// other services\n\t\tbuilder.AddMappers();\n\t}\n}\n```\n\nThat module need to be register in container configure section of `Startup` file:\n\n```csharp\npublic void ConfigureContainer(ContainerBuilder builder)\n{\n\tbuilder.RegisterModule(new AutofacModule());\n}\n```\n\nTo run Autofac mechanism, we need to call Autofac factory with attached `Startup` file by using:\n\n```csharp\nHost.CreateDefaultBuilder(args)\n\t.UseServiceProviderFactory(new AutofacServiceProviderFactory())\n\t.ConfigureWebHostDefaults(webBuilder =\u003e\n\t{\n\t\twebBuilder.UseStartup\u003cStartup\u003e();\n\t});\n```\n\nFor StructureMap, we add `AddMappers` in container configuration in `Startup` file:\n\n```csharp\npublic void ConfigureContainer(Container builder)\n{\n\tbuilder.Configure(config =\u003e\n\t{\n\t\t// other services\n\t\tconfig.AddMappers();\n\t});\n}\n```\n\nIn example project StructureMap container is builded by running `StructureMapContainerBuilderFactory` from program host\n\n```csharp\nHost.CreateDefaultBuilder(args)\n\t.UseServiceProviderFactory(new StructureMapContainerBuilderFactory())\n\t.ConfigureWebHostDefaults(webBuilder =\u003e\n\t{\n\t\twebBuilder.UseStartup\u003cStartup\u003e();\n\t});\n```\n\nwhere provider is a service provider factory class constructed for StructureMap mechanism (working with `Container`, not with `Registry`):\n\n```csharp\npublic class StructureMapContainerBuilderFactory : IServiceProviderFactory\u003cContainer\u003e\n{\n\tprivate IServiceCollection _services;\n\n\tpublic Container CreateBuilder(IServiceCollection services)\n\t{\n\t\t_services = services;\n\t\treturn new Container();\n\t}\n\n\tpublic IServiceProvider CreateServiceProvider(Container builder)\n\t{\n\t\tbuilder.Configure(config =\u003e\n\t\t{\n\t\t\tconfig.Populate(_services);\n\t\t});\n\n\t\treturn builder.GetInstance\u003cIServiceProvider\u003e();\n\t}\n}\n```\n\n### Ninject Dependency Injection\n\nSourceMapper allow also, in limited way, to use Ninject dependency injection. Solution is compatible with Ninject in version \u003c 4 and work with Asp.Net Core projects.\nIntegration with Ninject dependency injection is allowed by adding to project `Ninject.Web.AspNetCore` package. Ninject based on `IKernel`, so the SourceMapper generate mappers, which can be added by using `AddMappers()` method in `Kernel` declaration place in project. In our example, we created 'NinjectKernel' class with `CreateKernel` methods - it is place, where SourceMapper inject `MapperDependencyInjectionExtension` class, with all mappings generated from declared mappers.\n\n```csharp\n/// Ninject Dependency Injection mechanism : kernel\npublic class NinjectKernel\n{\n\t/// Method create kernel with all user defined dependency injections\n\tpublic static IKernel CreateKernel()\n\t{\n\t\tvar settings = new NinjectSettings();\n\t\tsettings.LoadExtensions = false;\n\n\t\tvar kernel = new AspNetCoreKernel(settings);\n\n\t\tkernel.Load(typeof(AspNetCoreHostConfiguration).Assembly);\n\t\t// User Dependency Injections\n\t\tkernel.Bind\u003cIInvoiceService\u003e().To\u003cInvoiceService\u003e().InSingletonScope();\n\t\tkernel.Bind\u003cIInvoiceRepository\u003e().To\u003cInvoiceRepository\u003e().InSingletonScope();\n\t\t// Using mappers from SourceMapper\n\t\tkernel.AddMappers();\n\n\t\treturn kernel;\n\t}\n}\n```\n\nNext, `CreateKernel` is used in `Program` starting class, in host generating section:\n\n```csharp\nvar host = new NinjectSelfHostBootstrapper(NinjectKernel.CreateKernel, hostConfiguration);\nhost.Start();\n```\n\n\n## Inverse Mapping Mechanism\n\nInverse mapping allow to create about half of the mapper code by automate. The main goal is to define only one way mapping methods in mapper class, the second one - inverse mapping - can be created by internal mechanism.\nDue to using dependency injections, SourceMapper generate additional part of mapping class (or interface), so the source mapping class (interface) must be declared as `partial`.\n\n### Interface Inverse Mapping\nInverse mapping is provided by using inverse mapping attributes, where the name of the inverse method is required, like in example (Compentio.Example.Autofac.App):\n\n```csharp\n[Mapper]\npublic partial interface IBooksMapper\n{\n\t[InverseMapping(InverseMethodName = \"MapBookToDao\")]\n\tBookDto MapBookToDto(BookDao source);\n}\n```\n\nthat lead to two methods implementations in mapper result class:\n\n```csharp\npublic class BooksMapper : IBooksMapper\n{\n        public static BooksMapper Create() =\u003e new();\n        public virtual Compentio.Example.Autofac.App.Entities.BookDto MapBookToDto(Compentio.Example.Autofac.App.Entities.BookDao source)\n        {\n\t...\n        }\n\n        public virtual Compentio.Example.Autofac.App.Entities.BookDao MapBookToDao(Compentio.Example.Autofac.App.Entities.BookDto source)\n        {\n\t...\n        }\n}\n```\n\nIf mapped object contain collections or other complex fields/expressions, mappings cant be created by automate. In this case we can create class that inherit mapper result class and override complex objects, for example:\n\n```csharp\npublic class CustomBooksMapper : BooksMapper\n{\n\tpublic override BookDao MapBookToDao(BookDto source)\n\t{\n\t    var result = base.MapBookToDao(source);\n\t    result.LibraryAddressesDao = source.LibraryAddressesDto.Select(a =\u003e MapAddressToDao(a)).ToList();\n\t    return result;\n\t}\n\t...\n}\n```\n\nand register it in dependency injection section\n\n```csharp\npublic class AutofacModule : Module\n{\n\tprotected override void Load(ContainerBuilder builder)\n\t{\n\t    ...\n\t    builder.AddMappers();\n\t    // Override mapper class by custom implementation\n\t    builder.RegisterType\u003cCustomBooksMapper\u003e().As\u003cIBooksMapper\u003e().SingleInstance();\n\t}\n}\n```\n\n### Class Inverse Mapping\n\nCase of class inverse mapping is mainly similar to interfaces inverse mechanism. First of all, we need to mark class as partial and add inverse attribute with the name for inverse method.\nIf mapping between classes need expresions, they both - primary and invers expression methods - should be implemented by developer. Inverse mechanism is not able to generate it by automate. On the Compentio.Example.AtructureMap.App example:\n\n```csharp\n[Mapper(ClassName = \"ClassInvoiceMapper\")]\npublic abstract partial class InvoiceMapper\n{\n\t...\n\t[InverseMapping(InverseMethodName = \"MapInvoiceToDao\")]\n\tpublic abstract InvoiceDto MapInvoiceToDto(InvoiceDao source);\n\t...\n\t[InverseMapping(InverseMethodName = \"MapInvoiceItemToDao\")]\n        public abstract InvoiceItemDto MapInvoiceItemToDto(InvoiceItemDao source);\n\t\n\tprotected IEnumerable\u003cInvoiceItemDto\u003e ConvertToItemsDto(IEnumerable\u003cInvoiceItemDao\u003e items)\n\t{\n\t    return items.Select(i =\u003e MapInvoiceItemToDto(i)).AsEnumerable();\n\t}\n}\n```\n\nThat prepared mapping class lead to obtained proper result mapping class:\n\n```csharp\npublic class ClassInvoiceMapper : InvoiceMapper\n{\n\tpublic override Compentio.Example.StructureMap.App.Entities.InvoiceDto MapInvoiceToDto(Compentio.Example.StructureMap.App.Entities.InvoiceDao source)\n\t{\n\t    ...\n\t    target.Items = ConvertToItemsDto(source.Items);\n\t}\n\n\tpublic override Compentio.Example.StructureMap.App.Entities.InvoiceDao MapInvoiceToDao(Compentio.Example.StructureMap.App.Entities.InvoiceDto source)\n\t{\n\t    ...\n\t}\n\n\tpublic override Compentio.Example.StructureMap.App.Entities.InvoiceItemDto MapInvoiceItemToDto(Compentio.Example.StructureMap.App.Entities.InvoiceItemDao source)\n\t{\n\t...\n\t}\n\n\tpublic override Compentio.Example.StructureMap.App.Entities.InvoiceItemDao MapInvoiceItemToDao(Compentio.Example.StructureMap.App.Entities.InvoiceItemDto source)\n\t{\n\t...\n\t}\n}\n\npublic abstract partial class InvoiceMapper\n{\n\tpublic abstract Compentio.Example.StructureMap.App.Entities.InvoiceDao MapInvoiceToDao(Compentio.Example.StructureMap.App.Entities.InvoiceDto source);\n\tpublic abstract Compentio.Example.StructureMap.App.Entities.InvoiceItemDao MapInvoiceItemToDao(Compentio.Example.StructureMap.App.Entities.InvoiceItemDto source);\n}\n```\n\nNow `ClassInvoiceMapper` can be inherited by, for example, `CustomClassInvoiceMapper` and complex methods can be overrided, similarly to interface case.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falekshura%2Fsourcemapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falekshura%2Fsourcemapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falekshura%2Fsourcemapper/lists"}