question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Abstract Base Class and Inheritance

See original GitHub issue

Hey,

I have a special situation in which one of the methods of my controller returns an array of an abstract base class which contains children of that class. However, the code generated with the NSwag Studio is unusable and also there is no way to access properties exclusive to the child classes even if the code generated by the NSwag is fixed manually. Let’s take a look at the following examples:

What Happens Now:

Api Server:

public abstract class BaseSettings {
    public string SharedProperty { get; set; } = "Shared";
}
public class BarSettings : BaseSettings {
    public string BarProperty { get; set; } = "Bar";
}
public class FooSettings : BaseSettings {
    public string FooProperty { get; set; } = "Foo";
}

public class SampleController : ApiController {
    [SwaggerResponse(HttpStatusCode.OK, typeof(BaseSettings[]))]
    public IEnumerable<BaseSettings> GetSettings() {
        return new BaseSettings[] { new BarSettings(), new FooSettings()  };
    }
}

Generated Definition:

.......
"BaseSettings": {
      "type": "object",
      "x-abstract": true,
      "additionalProperties": false,
      "properties": {
        "SharedProperty": {
          "type": "string"
         }
        }
      },
.......
    "/api/Settings/GetSettings": {
      "get": {
        "tags": [
          "Settings"
        ],
        "operationId": "Settings_GetSettings",
        "responses": {
          "200": {
            "x-nullable": true,
            "description": "",
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/BaseSettings"
              }
            }
          }
        },
.......

Actual Response;

[
   {
      "SharedProperty": "Shared",
      "BarProperty": "Bar"
    },
    {
      "SharedProperty": "Shared",
      "FooProperty": "Foo"
    }
]

Generated client code:

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.10.67.0 (Newtonsoft.Json v9.0.0.0)")]
    public partial abstract class BaseSettings : System.ComponentModel.INotifyPropertyChanged
    {
        private string _sharedProperty;
        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
    
        [Newtonsoft.Json.JsonProperty("SharedProperty", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string SharedProperty
        {
            get { return _sharedProperty; }
            set 
            {
                if (_sharedProperty != value)
                {
                    _sharedProperty = value; 
                    RaisePropertyChanged();
                }
            }
        }
    
        public string ToJson() 
        {
            return Newtonsoft.Json.JsonConvert.SerializeObject(this);
        }
        
        public static BaseSettings FromJson(string data)
        {
            return Newtonsoft.Json.JsonConvert.DeserializeObject<BaseSettings>(data);
        }

        protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) 
                handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }    
    }

Now as you can clearly see from the above example,

  1. This method by itself is not executable as the generated BaseSettings class is, in fact, an abstract class and can’t instantiate from. This can be solved by manually removing abstract modifier from the generated C# client code tho.

  2. However, the problem with not being able to access child properties remains. This is especially unfortunate because the child class’s property values are in fact available in the response generated by WebAPI, but we can’t decode it right.

Is there any way to solve this or at least a way to work around this problem?

Proposed solution:

  • A new property for the SwaggerResponseAttribute class allowing us to provide a list of additional types for the WebApi Server:
public class SampleController : ApiController {
    [SwaggerResponse(HttpStatusCode.OK, typeof(BaseSettings[]), AdditionalTypes = new [] {typeof(BarSettings[]), typeof(FooSettings[])})]
    public IEnumerable<BaseSettings> GetSettings() {
        return new BaseSettings[] { new BarSettings(), new FooSettings()  };
    }
}
  • Generating type definition for the child classes based on this property; also containing an x-inherits property (x-implements for interfaces?):
.......
"BaseSettings": {
      "type": "object",
      "x-abstract": true,
      "additionalProperties": false,
      "properties": {
        "SharedProperty": {
          "type": "string"
         }
        }
      },
"BarSettings": {
      "type": "object",
      "x-abstract": false,
      "x-inherits": "#/definitions/BaseSettings",
      "additionalProperties": false,
      "properties": {
        "SharedProperty": {
          "type": "string"
         },
        "BarProperty": {
          "type": "string"
         }
        }
      },
"FooSettings": {
      "type": "object",
      "x-abstract": false,
      "x-inherits": "#/definitions/BaseSettings",
      "additionalProperties": false,
      "properties": {
        "SharedProperty": {
          "type": "string"
         },
        "FooProperty": {
          "type": "string"
         }
        }
      },
.......
  • Specifying the target type in the method response:
[
   {
      "$type": "#/definitions/BarSettings",
      "SharedProperty": "Shared",
      "BarProperty": "Bar"
    },
    {
      "$type": "#/definitions/FooSettings",
      "SharedProperty": "Shared",
      "FooProperty": "Foo"
    }
]

I suspect this problem to arise also when an interface is used instead of an abstract class.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
RicoSutercommented, Oct 4, 2018
0reactions
falahaticommented, Dec 2, 2018

I am going to close this and let the discussion continue in here:

https://github.com/RSuter/NJsonSchema/pull/829

Read more comments on GitHub >

github_iconTop Results From Across the Web

python - Using abstract base class VS plain inheritance
This primarily shows up through the lens of the register() method. Any class that has ABCMeta as its metaclass (or simply inherits from...
Read more >
Inheritance — Abstract Base Classes (ABCs), C++ FAQ
At the design level, an abstract base class (ABC) corresponds to an abstract concept. If you asked a mechanic if he repaired vehicles,...
Read more >
Inheritance in Python with Abstract Base Class(ABC) | by E.Y.
Abstract base classes are a form of interface checking more strict than protocols. They are classes that contain abstract methods, which are ...
Read more >
C# | Abstract Classes
An abstract class cannot be inherited by structures. It can contain constructors or destructors. It can implement functions with non-Abstract ...
Read more >
Inheritance, Abstract Class and Interface in Java
Inheritance allows a class to inherit properties and behaviors from another class, the abstract class provides a common base class for a group ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found