Giter Club home page Giter Club logo

autointerface's Introduction

AutoInterface

AutoInterface is a source generator that generates an interface based on your class/struct.
Basically, you write your class and get the corresponding interface for free.

using AutoInterfaceAttributes;

[AutoInterface]
public class Example : IExample {
    public int Number { get; private set; }

    public Example() {
        ResetNumber();
    }

    /// <summary>
    /// some method description
    /// </summary>
    public int AddToNumber(int increase) {
        Number += increase;
        return Number;
    }

    private void ResetNumber() => Number = 0;
}
// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

public partial interface IExample {
    int Number { get; }

    /// <summary>
    /// some method description
    /// </summary>
    int AddToNumber(int increase);
}

AutoInterface supports:



Examples

AutoInterfaceAttribute on struct

using AutoInterfaceAttributes;

[AutoInterface]
public struct Point {
    public int X { get; private set; }
    public int Y { get; private set; }

    public Point(int x, int y) => (X, Y) = (x, y);
}
// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

public partial interface IPoint {
    int X { get; }

    int Y { get; }
}



AutoInterfaceAttribute with all kinds of members

using AutoInterfaceAttributes;

[AutoInterface]
public sealed class FullExample {
    public void SomeMethod() { }

    public int SomeProperty { get; init; }

    public int this[int i] => i;

    public event Action? someEvent;

    public event Action SomeEvent { add { } remove { } }
}
// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

public partial interface IFullExample {
    void SomeMethod();

    int SomeProperty { get; init; }

    int this[int i] { get; }

    event Action? someEvent;

    event Action SomeEvent;
}



AutoInterfaceAttribute with explicit interface specifier

using AutoInterfaceAttributes;

[AutoInterface]
public sealed class ExplicitExample : IExplicitExample {
    void IExplicitExample.SomeMethod() { }

    int IExplicitExample.SomeProperty { get; init; }

    int IExplicitExample.this[int i] => i;

    event Action IExplicitExample.SomeEvent { add { } remove { } }
}
// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

public partial interface IExplicitExample {
    void SomeMethod();

    int SomeProperty { get; init; }

    int this[int i] { get; }

    event Action SomeEvent;
}



multiple AutoInterfaceAttributes on same class

using AutoInterfaceAttributes;

[AutoInterface(Name = "IMultipleExample1")]
[AutoInterface(Name = "IMultipleExample2")]
public sealed class MultipleExample : IMultipleExample1, IMultipleExample2 {
    public void SomeMethod() { }

    int IMultipleExample1.PropertyFirst { get; set; }

    string IMultipleExample2.PropertySecond { get; set; }
}
// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

public partial interface IMultipleExample1 {
    void SomeMethod();

    int PropertyFirst { get; set; }
}

...

// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

public partial interface IMultipleExample2 {
    void SomeMethod();

    string PropertySecond { get; set; }
}



AutoInterfaceAttribute with summary documentation

using AutoInterfaceAttributes;

/// <summary>
/// my class description
/// </summary>
[AutoInterface]
public sealed class SummaryExample {
    /// <summary>
    /// some method description
    /// </summary>
    public void SomeMethod() { }
}
// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

/// <summary>
/// my class description
/// </summary>
public partial interface ISummaryExample {
    /// <summary>
    /// some method description
    /// </summary>
    void SomeMethod();
}



AutoInterfaceAttribute with generic class

using AutoInterfaceAttributes;

[AutoInterface]
public sealed class GenericExample<T> {
    public T Identity(T parameter) => parameter;
}
// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

public partial interface IGenericExample<T> {
    T Identity(T parameter);
}



Parameter

  • Name

Type: string
Default: $"I{ClassName}"

If you want another name for your interface, put it here.

using AutoInterfaceAttributes;

[AutoInterface(Name = "NewName")]
public sealed class Example;
// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

public partial interface NewName {}



  • Modifier

Type: string
Default: "public partial"

If you want another visible modifier or make the interface non-partial or unsafe, you can do this here.

using AutoInterfaceAttributes;

[AutoInterface(Modifier = "internal")]
public sealed class Example;
// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

internal interface IExample {}



  • Namespace

Type: string
Default: $"{ClassNamespace}"

When the generated interface should live in a specific namespace, you can specify it here.
For global namespace, use an empty string.

using AutoInterfaceAttributes;

namespace MyApp.Core;

[AutoInterface(Namespace = "MyApp.Utils")]
public sealed class Example;
// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

namespace MyApp.Utils;

public partial interface IExample {}



  • Inheritance

Type: Type[]
Default: []

If the generated interface should inherit from one or more other interfaces, you can list them here.

using AutoInterfaceAttributes;

[AutoInterface(Inheritance = [typeof(ICore)])]
public sealed class Example;

public partial interface ICore { ... }
// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

public partial interface IExample : ICore {}



  • Nested

Type: string[]
Default: []

When the interface should be nested inside other classes, structs or interfaces, declare them here.

using AutoInterfaceAttributes;

[AutoInterface(Nested = ["public partial class MyWrapper", "public partial interface OuterInterface"])]
public sealed class Example {
    public void SomeMethod() { }
}
// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

public partial class MyWrapper {
    public partial interface OuterInterface {
        public partial interface IExample {
            void SomeMethod();
        }
    }
}



  • StaticMembers

Type: bool
Default: false

Normally, static members are just ignored. However, an interface can contain static members as a "static abstract" member.
To accept static members to generate "static abstract" members, set this flag to true.

using AutoInterfaceAttributes;

[AutoInterface(StaticMembers = true)]
public sealed class Example {
    public static void SomeMethod() { }
}
// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

public partial interface IExample {
    static abstract void SomeMethod();
}



  • IgnoreAutoInterfaceAttribute

When you want a specific member to be ignored by the generator, you can decorate it with [IgnoreAutoInterface].

using AutoInterfaceAttributes;

[AutoInterface]
public sealed class Example {
    [IgnoreAutoInterface]
    public void SomeMethod() { }
}
// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using AutoInterfaceAttributes;

public partial interface IExample {}



Disable Attribute Generation

You can disable the generation of the attributes by defining a constant for your compilation:

  <PropertyGroup>
    <DefineConstants>AUTOINTERFACE_EXCLUDE_ATTRIBUTES</DefineConstants>
  </PropertyGroup>

This functionality is specific for the use case when you have a project referencing another project, both projects using this generator and you have InternalsVisibleTo enabled. In that case you have the attributes defined twice in your referencing project and you get a warning about that. By defining this constant in your referencing project, you prevent one generation, so the attributes are only defined once in the referenced project.



Remarks

Using-statements will always be placed on the top, so using not fully-qualified using-statements might cause compile errors.

using AutoInterfaceAttributes;

namespace System.Collections {
    using Generic; // <-- refers to "System.Collections.Generic"

    [AutoInterface]
    public sealed class Example;
}


// ...


// <auto-generated/>
#pragma warning disable
#nullable enable annotations


using Generic; // <-- refers to "Generic"
using AutoInterfaceAttributes;

public partial interface IExample {}

You also should not use not fully-qualified using-statements in the first place, because they can be ambiguous. By introducing an additional namespace, the referring of a not fully-qualified using-statement might change and your application breaks at unrelated places. Just put your using statements at the top.

autointerface's People

Stargazers

Piotr Michalewicz avatar  avatar  avatar  avatar  avatar BlackWhiteYoshi avatar

Watchers

 avatar BlackWhiteYoshi avatar

autointerface's Issues

Overwriting Generated Files with Same Name

When integrating AutoInterface into a project where classes share the same name but reside in different namespaces, the tool generates conflicting files with identical names in the same directory. Consequently, this leads to file overwriting, and the interfaces not being available for use in the code.

Steps to Reproduce:

  • Define two classes with the same name but residing in different namespaces
    • (e.g., Sample.Api.V1.MyService and Sample.Api.V2.MyService).
  • Annotate with the AutoInterface attribute.

Expected Behavior:

AutoInterface should generate separate files for classes with the same name but residing in different namespaces, preserving each class's implementation without overwriting existing files, e.g.
Sample_Api_V1_IMyService_MyService_MyService.cs.g.cs
Sample_Api_V2_IMyService_MyService_MyService.cs.g.cs

Actual Behavior:

AutoInterface generates files with the same name, leading to the overwriting of existing files.
IMyService_MyService_MyService.cs.g.cs

Potential Solution:

Implement a mechanism in AutoInterface to generate unique filenames for classes with the same name but residing in different namespaces. This could involve incorporating namespace information into the generated filenames to avoid conflicts.

Nested Classes can't produce top-level interfaces

Referring to this previous feature request, it was determined that nested interfaces were most likely off the table. However it was stated (as I understood) that interfaces applied to nested classes should work regardless, but would be generated as top-level interfaces.

I attempted the suggested simple work around to achieve the desired result, however, I cannot get the inner classes' interface to generate as a top-level interface.

Here's an example class (and nested class) that isn't working for me:

[AutoInterface]
public sealed class MonitorsClient(IClientFactory clientFactory) : ApiClientBase,
                                                                   IMonitorsClient
{
   #region properties

   public IMonitorsClientResponse AsResponse { get; } = new Response(clientFactory);

   #endregion

   #region methods

   public Task<string> GetAsync(CancellationToken cancellationToken = default) => GetAsync(WithSettingsDefault, cancellationToken);

   public Task<string> GetAsync(Action<FlurlHttpSettings> withSettings, CancellationToken cancellationToken = default) =>
      AsResponse.GetAsync(withSettings, cancellationToken)
                .ReceiveString();

   #endregion

   #region nested types

   [AutoInterface]
   sealed class Response : IMonitorsClientResponse
   {
      #region fields

      readonly IClientFactory _clientFactory;
      IFlurlClient?           _client;

      #endregion

      #region constructors

      internal Response(IClientFactory clientFactory) => _clientFactory = clientFactory;

      #endregion

      #region properties

      IFlurlClient Client => _client ??= _clientFactory.Get();

      #endregion

      #region methods

      [MustDisposeResource] public Task<IFlurlResponse> GetAsync(CancellationToken cancellationToken = default) => GetAsync(WithSettingsDefault, cancellationToken);

      [MustDisposeResource]
      public Task<IFlurlResponse> GetAsync(Action<FlurlHttpSettings> withSettings, CancellationToken cancellationToken = default) =>
         Client.Request("monitor")
               .WithSettings(withSettings)
               .GetAsync(cancellationToken: cancellationToken);

      #endregion
   }

   #endregion
}

Declared elements from existing interfaces being duplicated in generated interface

I have this existing interface:

public interface IBaseEffectProcessor
{
   #region methods

   void DisposeBitmapAndDoc();
   [MustDisposeResource] BitmapOrDocOrString Process(BaseEffect baseEffect, BitmapOrDocOrString imageSource);

   #endregion
}

Previously, we had an interface implemented as such (for DI registrations in the ServiceCollection):

public interface ICropEffectProcessor : IBaseEffectProcessor;

However, when I discard the ICropEffectProcessor interface in favor of implementing via AutoInterface like so:

[AutoInterface(Inheritance = [typeof(IBaseEffectProcessor)])]
public sealed class CropEffectProcessor(IBitmapOrDocDisposeServices bitmapOrDocDisposeServices, 
                                        IConvertServices convertServices, 
                                        ILoggerService<CropEffectProcessor> loggerService) : ICropEffectProcessor
{
   #region fields

   readonly IBitmapOrDocDisposeServices         _bitmapOrDocDisposeServices = bitmapOrDocDisposeServices.ThrowIfNull();
   readonly IConvertServices                    _convertServices            = convertServices.ThrowIfNull();
   readonly ILoggerService<CropEffectProcessor> _loggerService              = loggerService.ThrowIfNull();

   #endregion

   #region methods

   public void DisposeBitmapAndDoc() => _bitmapOrDocDisposeServices.DisposeBitmapAndDoc();

   [MustDisposeResource]
   public BitmapOrDocOrString Process(BaseEffect baseEffect, BitmapOrDocOrString imageSource)
   {
      // removed for brevity
   }

   #endregion
}

What seems to be generated is like so:

// <auto-generated/>
#pragma warning disable
#nullable enable annotations

public interface ICropEffectProcessor : IBaseEffectProcessor {
    void DisposeBitmapAndDoc();

    [MustDisposeResource]
    BitmapOrDocOrString Process(BaseEffect baseEffect, BitmapOrDocOrString imageSource);
}

Under inspection, code analysis tools indicate a warning like so:

The keyword 'new' is required on 'Process' because it hides method 'BitmapOrDocOrString IBaseEffectProcessor. Process (BaseEffect, BitmapOrDocOrString) '

Support for attributes

I have methods/properties with attributes decorated on them. So too should the interface have these same attributes, but they are not carrying over into the interface.

Example Implementation:

   [AutoInterface]
   sealed class Response : IResponse
   {
      #region fields

      readonly IClientFactory _clientFactory;
      IFlurlClient?           _client;

      #endregion

      #region constructors

      internal Response(IClientFactory clientFactory) => _clientFactory = clientFactory;

      #endregion

      #region properties

      IFlurlClient Client => _client ??= _clientFactory.Get();

      #endregion

      #region methods

      [MustDisposeResource] public Task<IFlurlResponse> GetAsync(CancellationToken cancellationToken = default) => GetAsync(WithSettingsDefault, cancellationToken);

      [MustDisposeResource]
      public Task<IFlurlResponse> GetAsync(Action<FlurlHttpSettings> withSettings, CancellationToken cancellationToken = default) =>
         Client.Request("monitor")
               .WithSettings(withSettings)
               .GetAsync(cancellationToken: cancellationToken);

      #endregion
   }

Generated Interface with missing attributes (JetBrains.Annotations.MustDisposeResourceAttribute)

// <auto-generated/>
#pragma warning disable
#nullable enable annotations

namespace Taylor.UFP.Document.Web.Api;

public interface IResponse {
    Task<IFlurlResponse> GetAsync(CancellationToken cancellationToken = default);

    Task<IFlurlResponse> GetAsync(Action<FlurlHttpSettings> withSettings, CancellationToken cancellationToken = default);
}

Support Nested Interfaces

I have a pattern of nested interfaces repeated throughout many client implementations. However, I cannot seem to get AutoInterface to work with nested interfaces, despite my best efforts.

Here was the original interface and implementation:
Interface:

public interface IMonitorsClient
{
   #region properties

   IResponse AsResponse { get; }

   #endregion

   #region methods

   Task<string> GetAsync(CancellationToken cancellationToken = default);
   Task<string> GetAsync(Action<FlurlHttpSettings> withSettings, CancellationToken cancellationToken = default);

   #endregion

   #region nested types

   public interface IResponse
   {
      #region methods

      [MustDisposeResource] Task<IFlurlResponse> GetAsync(CancellationToken cancellationToken = default);
      [MustDisposeResource] Task<IFlurlResponse> GetAsync(Action<FlurlHttpSettings> withSettings, CancellationToken cancellationToken = default);

      #endregion
   }

   #endregion
}

Implementation:

public sealed class MonitorsClient(IClientFactory clientFactory) : ApiClientBase,
                                                                   IMonitorsClient
{
   #region properties

   public IMonitorsClient.IResponse AsResponse { get; } = new Response(clientFactory);

   #endregion

   #region methods

   public Task<string> GetAsync(CancellationToken cancellationToken = default) => GetAsync(WithSettingsDefault, cancellationToken);

   public Task<string> GetAsync(Action<FlurlHttpSettings> withSettings, CancellationToken cancellationToken = default) =>
      AsResponse.GetAsync(withSettings, cancellationToken)
                .ReceiveString();

   #endregion

   #region nested types

   sealed class Response : IMonitorsClient.IResponse
   {
      #region fields

      readonly IClientFactory _clientFactory;
      IFlurlClient?           _client;

      #endregion

      #region constructors

      internal Response(IClientFactory clientFactory) => _clientFactory = clientFactory;

      #endregion

      #region properties

      IFlurlClient Client => _client ??= _clientFactory.Get();

      #endregion

      #region methods

      [MustDisposeResource] public Task<IFlurlResponse> GetAsync(CancellationToken cancellationToken = default) => GetAsync(WithSettingsDefault, cancellationToken);

      [MustDisposeResource]
      public Task<IFlurlResponse> GetAsync(Action<FlurlHttpSettings> withSettings, CancellationToken cancellationToken = default) =>
         Client.Request("monitor")
               .WithSettings(withSettings)
               .GetAsync(cancellationToken: cancellationToken);

      #endregion
   }

   #endregion
}

Here is my attempt to recreate using AutoInterface:

[AutoInterface]
public sealed class MonitorsClient(IClientFactory clientFactory) : ApiClientBase,
                                                                   IMonitorsClient
{
   #region properties

   public IMonitorsClient.IResponse AsResponse { get; } = new Response(clientFactory);

   #endregion

   #region methods

   public Task<string> GetAsync(CancellationToken cancellationToken = default) => GetAsync(WithSettingsDefault, cancellationToken);

   public Task<string> GetAsync(Action<FlurlHttpSettings> withSettings, CancellationToken cancellationToken = default) =>
      AsResponse.GetAsync(withSettings, cancellationToken)
                .ReceiveString();

   #endregion

   #region nested types

   [AutoInterface]
   sealed class Response : IMonitorsClient.IResponse
   {
      #region fields

      readonly IClientFactory _clientFactory;
      IFlurlClient?           _client;

      #endregion

      #region constructors

      internal Response(IClientFactory clientFactory) => _clientFactory = clientFactory;

      #endregion

      #region properties

      IFlurlClient Client => _client ??= _clientFactory.Get();

      #endregion

      #region methods

      [MustDisposeResource] public Task<IFlurlResponse> GetAsync(CancellationToken cancellationToken = default) => GetAsync(WithSettingsDefault, cancellationToken);

      [MustDisposeResource]
      public Task<IFlurlResponse> GetAsync(Action<FlurlHttpSettings> withSettings, CancellationToken cancellationToken = default) =>
         Client.Request("monitor")
               .WithSettings(withSettings)
               .GetAsync(cancellationToken: cancellationToken);

      #endregion
   }

   #endregion
}

Support for Records

I can't seem to generate an interface for a record like this:

[AutoInterface]
public sealed record LogService<TCategoryName>(Box Box, ILogger<TCategoryName> Logger) : ILogService<TCategoryName>;

Methods with Generics not generating into interface

I have this class:

[AutoInterface]
public class LoggerServiceFactory(Box box, ILoggerFactory loggerFactory) : ILoggerServiceFactory
{
   #region fields

   readonly Box                                               _box                   = box.ThrowIfNull();
   readonly Lazy<ConcurrentDictionary<string, LoggerService>> _loggerServicesFactory = new ();

   #endregion

   #region properties

   ConcurrentDictionary<string, LoggerService> LoggerServices => _loggerServicesFactory.Value;

   #endregion

   #region methods

   public ILoggerService CreateLoggerService<TCategoryName>() => CreateLoggerService(typeof(TCategoryName));

   public ILoggerService CreateLoggerService(Type category)
   {
      var categoryName = GetCategoryName(category);

      return LoggerServices.GetOrAdd(categoryName, name => new (_box, loggerFactory.CreateLogger(name), category));
   }

   public ILoggerService CreateLoggerService(string categoryName) => LoggerServices.GetOrAdd(categoryName, name => new (_box, name, loggerFactory.CreateLogger(name)));

   #endregion
}

It is generating this interface:

// <auto-generated/>
#pragma warning disable
#nullable enable annotations

public interface ILoggerServiceFactory {
    ILoggerService CreateLoggerService();

    ILoggerService CreateLoggerService(Type category);

    ILoggerService CreateLoggerService(string categoryName);
}

However, I would expect it to be like this:

public interface ILoggerServiceFactory
{
   ILoggerService CreateLoggerService<TCategoryName>();
   ILoggerService CreateLoggerService(Type category);
   ILoggerService CreateLoggerService(string categoryName);
}

The TCategoryName generic is missing on the interface's method declaration.

Interfaces from Records don't generate Deconstruct method

I have a record like this:

[AutoInterface]
public sealed record LogService<TCategoryName>(Box Box, ILogger<TCategoryName> Logger) : ILogService<TCategoryName>;

Which is equivalent to this (from an exposed API context):

[AutoInterface]
public sealed class LogService<TCategoryName>(Box Box, ILogger<TCategoryName> Logger) : ILogService<TCategoryName>
{
   #region properties

   public Box                    Box    { get; init; } = Box;
   public ILogger<TCategoryName> Logger { get; init; } = Logger;

   #endregion

   #region methods

   public void Deconstruct(out Box Box, out ILogger<TCategoryName> Logger)
   {
      Box    = this.Box;
      Logger = this.Logger;
   }

   #endregion
}

If I inspect the generated interface from the record, it appears like so:

public interface ILogService<TCategoryName> {
    Box Box { get; init; }

    ILogger<TCategoryName> Logger { get; init; }
}

However the interface from the equivalent class is:

public interface ILogService<TCategoryName> {
    Box Box { get; init; }

    ILogger<TCategoryName> Logger { get; init; }

    void Deconstruct(out Box Box, out ILogger<TCategoryName> Logger);
}

Our code makes use of the Deconstruct method to set values in tuple (appearing) syntax despite only seeing the interface. So if I use AutoInterface with the record, I am forced to rewrite it as a class to prevent breaking downstream code.

Inherited class members are not included in generated interface

If my class inherits from another class, the class that is being inherited from does not have it's members included. I realize that if I own both the inherited class as well as the inheriting class, I can generate interfaces for both and all members will be included, like so:

This:

[AutoInterface]
public sealed BaseClass : IBaseClass
{
   public int SomeValue { get; set; }
}

[AutoInterface(Inheritance = [typeof(IBaseClass)])]
public sealed MyClass : BaseClass,
                        IMyClass
{
   public int MyValue { get; set; }
}

Should generate:

public partial interface IBaseClass
{
   int SomeValue { get; set; }
}   
public partial interface IMyClass : IBaseClass
{
   int MyValue { get; set; }
} 

However, if I must inherit from a 3rd party distributed class that I do not own and that does not implement an interface to which I can refer to, the members of that 3rd party class will not be included in the generated interface.

For example, if I inherit from Microsoft.EntityFrameworkCore.DbContext, none of the members from that class will appear in the generated interface.

This:

[AutoInterface(Inheritance = [typeof(Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>),
                              typeof(Microsoft.EntityFrameworkCore.Internal.IDbContextDependencies),
                              typeof(Microsoft.EntityFrameworkCore.Internal.IDbSetCache),
                              typeof(Microsoft.EntityFrameworkCore.Internal.IDbContextPoolable)])]
public sealed class MyDbContext(DbContextOptions dbContextOptions) : Microsoft.EntityFrameworkCore.DbContext(dbContextOptions),
                                                                     IMyDbContext
{
   public int SaveChanges(Options options)
   {
      SetOptions(options);

      return base.SaveChanges();
   }

   void SetOptions(Options? options = default)
   {
      // removed for brevity
   }
}

Generates:

public partial interface IMyDbContext: Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>, 
                                       Microsoft.EntityFrameworkCore.Internal.IDbContextDependencies, 
                                       Microsoft.EntityFrameworkCore.Internal.IDbSetCache, 
                                       Microsoft.EntityFrameworkCore.Internal.IDbContextPoolable {
    int SaveChanges(Options options);
}

I would expect it to generate this:

public partial interface IMyDbContext : Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>, 
                                        Microsoft.EntityFrameworkCore.Internal.IDbContextDependencies, 
                                        Microsoft.EntityFrameworkCore.Internal.IDbSetCache, 
                                        Microsoft.EntityFrameworkCore.Internal.IDbContextPoolable {
   
   // from MyDbContext
   int SaveChanges(Options options);
   
   // from Microsoft.EntityFrameworkCore.DbContext
   event EventHandler<SaveChangesFailedEventArgs>? SaveChangesFailed;
   
   event EventHandler<SavedChangesEventArgs>? SavedChanges;
   
   event EventHandler<SavingChangesEventArgs>? SavingChanges;
   
   ChangeTracker ChangeTracker { get; }
   
   DbContextId ContextId { get; }
   
   DatabaseFacade Database { get; }
   
   EntityEntry<TEntity> Add<TEntity>(TEntity entity) where TEntity : class;
   
   EntityEntry Add(object entity);
   
   ValueTask<EntityEntry<TEntity>> AddAsync<TEntity>(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class;
   
   ValueTask<EntityEntry> AddAsync(object entity, CancellationToken cancellationToken = default);
   
   void AddRange(params object[] entities);
   
   void AddRange(IEnumerable<object> entities);
   
   Task AddRangeAsync(params object[] entities);
   
   Task AddRangeAsync(IEnumerable<object> entities, CancellationToken cancellationToken = default);
   
   EntityEntry<TEntity> Attach<TEntity>(TEntity entity) where TEntity : class;
   
   EntityEntry Attach(object entity);
   
   void AttachRange(params object[] entities);
   
   void AttachRange(IEnumerable<object> entities);
   
   EntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
   
   EntityEntry Entry(object entity);
   
   object? Find([DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type entityType, params object?[]? keyValues);
   
   TEntity? Find<TEntity>(params object?[]? keyValues) where TEntity : class;
   
   ValueTask<object?> FindAsync([DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type entityType, params object?[]? keyValues);
   
   ValueTask<object?> FindAsync([DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type entityType, object?[]? keyValues, CancellationToken cancellationToken);
   
   ValueTask<TEntity?> FindAsync<[DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] TEntity>(params object?[]? keyValues) where TEntity : class;
   
   ValueTask<TEntity?> FindAsync<[DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] TEntity>(object?[]? keyValues, CancellationToken cancellationToken) where TEntity : class;
   
   IQueryable<TResult> FromExpression<TResult>(Expression<Func<IQueryable<TResult>>> expression);
                                                                                                                    
   EntityEntry<TEntity> Remove<TEntity>(TEntity entity) where TEntity : class;
   
   EntityEntry Remove(object entity);
   
   void RemoveRange(params object[] entities);
   
   void RemoveRange(IEnumerable<object> entities);
   
   int SaveChanges();
   
   int SaveChanges(bool acceptAllChangesOnSuccess);   
   
   Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
   
   Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default);
   
   DbSet<TEntity> Set<[DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] TEntity>() where TEntity : class;
   
   DbSet<TEntity> Set<[DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] TEntity>(string name) where TEntity : class;
   
   EntityEntry<TEntity> Update<TEntity>(TEntity entity) where TEntity : class;
   
   EntityEntry Update(object entity);
   
   void UpdateRange(params object[] entities);
   
   void UpdateRange(IEnumerable<object> entities);
}

Feature Request: Attribute Filtering

Recently attributes found on the implementation class were persisted to the generated interface as covered in issue #3 .

This change was a welcomed change and received well as the default use-case, and I appreciate you fulfilling this request.

However, a coworker pointed out a scenario when used with another code generating package, where the attributes there exist to drive behavior within the generated mapper.

namespace MyApp;

[AutoInterface]
[Mapper(EnumMappingStrategy = ByName, UseReferenceHandling = true)]
public sealed partial class Mapper : IMapper
{
   [MapperIgnoreSource(nameof(Data.FilterCategory.CreatedTs))]
   [MapperIgnoreSource(nameof(Data.FilterCategory.LastUpdatedTs))]
   [MapperIgnoreTarget(nameof(Services.FilterCategory.ChildFilterCategory))]
   [MapProperty([nameof(Data.FilterCategory.ColorFilterCategory), nameof(Data.FilterCategory.ColorFilterCategory.Hex)], [nameof(Services.FilterCategory.Hex)])]
   public partial Services.FilterCategory MapFrom(Data.FilterCategory source);
}

Generates:

// <auto-generated/>
#pragma warning disable
#nullable enable annotations

namespace MyApp;

public interface IMapper {
   [MapperIgnoreSource(nameof(Data.FilterCategory.CreatedTs))]
   [MapperIgnoreSource(nameof(Data.FilterCategory.LastUpdatedTs))]
   [MapperIgnoreTarget(nameof(Services.FilterCategory.ChildFilterCategory))]
   [MapProperty([nameof(Data.FilterCategory.ColorFilterCategory), nameof(Data.FilterCategory.ColorFilterCategory.Hex)], [nameof(Services.FilterCategory.Hex)])]
   Services.FilterCategory MapFrom(Data.FilterCategory source);
}

I am not opinionated on whether those attributes are persisted to the interface as there doesn't appear to be any obvious concerns. However, we have observed odd behavior with the mapper logic itself for some generated maps with the addition of AutoInterface. Although we haven't pin-pointed the exact cause, it does appear that when we declare the interface ourselves, omitting the corresponding attributes, we observe the expected behavior of the mapper.

The actual mapper in question is vast and we had hoped to leverage AutoInterface for code maintainability purposes, keeping our implementation and contracts in sync.

Ideally, for some distinct scenarios, it might be beneficial to control which attributes are filtered out of the interface.

A suggested approach:

namespace MyApp;

[AutoInterface(ExcludeAttributes = [typeof(MapperIgnoreSource), typeof(MapperIgnoreTarget), typeof(MapProperty)])]
[Mapper(EnumMappingStrategy = ByName, UseReferenceHandling = true)]
public sealed partial class Mapper : IMapper
{
   [MapperIgnoreSource(nameof(Data.FilterCategory.CreatedTs))]
   [MapperIgnoreSource(nameof(Data.FilterCategory.LastUpdatedTs))]
   [MapperIgnoreTarget(nameof(Services.FilterCategory.ChildFilterCategory))]
   [MapProperty([nameof(Data.FilterCategory.ColorFilterCategory), nameof(Data.FilterCategory.ColorFilterCategory.Hex)], [nameof(Services.FilterCategory.Hex)])]
   public partial Services.FilterCategory MapFrom(Data.FilterCategory source);
}

Perhaps there are use-case scenarios where more granular control could be needed, but I thought a simple start might suffice.

Thanks for your consideration.

Generator does not detect inside-namespace imports

When using using declarations within a namespace block, the generator does not generate imports in its output.

I would expect the following code:

namespace Thaum.Gw2Api.Clients
{
    using System.Threading;
    using System.Threading.Tasks;
    using AutoInterfaceAttributes;
    using Thaum.Gw2Api.Models;
    
    [AutoInterface]
    public sealed class Gw2AccountApi : IGw2AccountApi
    {

To identify and maintain the imports within the namespace block, like so:

// <auto-generated/>
#pragma warning disable
#nullable enable annotations

namespace Thaum.Gw2Api.Clients
{
    using System.Threading;
    using System.Threading.Tasks;
    using AutoInterfaceAttributes;
    using Thaum.Gw2Api.Models;

    public interface IGw2AccountApi
    {
        Task<Account?> GetAccountInfo(string apiKey, CancellationToken ct);
    }
}

But it in fact generates the following, causing errors in my code (that the interface is not implemented):

// <auto-generated/>
#pragma warning disable
#nullable enable annotations


namespace Thaum.Gw2Api.Clients;

public interface IGw2AccountApi {
    Task<Account?> GetAccountInfo(string apiKey, CancellationToken ct);
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.