How to require a property using Json.NET.Schema? - c#

I am trying to create a schema to ensure that outside-supplied JSON is of the following form:
{ Username: "Aaron" }
Right now, I'm creating a Newtonsoft JSchema object in C# by doing:
var sch = new JSchema()
{
Type = JSchemaType.Object,
AllowAdditionalProperties = false,
Properties =
{
{
"Username",
new JSchema() { Type = JSchemaType.String }
}
}
};
This is close, but does not require the presence of the Username property. I've tried the following:
var sch = new JSchema()
{
Type = JSchemaType.Object,
AllowAdditionalProperties = false,
Properties =
{
{
"Username",
new JSchema() { Type = JSchemaType.String }
}
},
Required = new List<string> { "Username" }
};
But I get:
Error CS0200 Property or indexer 'JSchema.Required' cannot be assigned to -- it is read only
And indeed, the documentation notes that the Required property is read-only:
https://www.newtonsoft.com/jsonschema/help/html/P_Newtonsoft_Json_Schema_JSchema_Required.htm
Am I missing something? Why would the Required property be read-only? How can I require the presence of Username?

You can't set Required (is only a get) use instead this:
var sch = new JSchema()
{
Type = JSchemaType.Object,
AllowAdditionalProperties = false,
Properties =
{
{
"Username",
new JSchema() { Type = JSchemaType.String }
}
},
};
sch.Required.Add("Username");

The answer from #PinBack from 2017 is incorrect: you can use C# Collection Initialization syntax with read-only list properties, like so:
var sch = new JSchema()
{
Type = JSchemaType.Object,
AllowAdditionalProperties = false,
Properties =
{
{
"Username",
new JSchema() { Type = JSchemaType.String }
}
},
Required = // <-- here!
{
"Username"
}
};

Related

How to mock EF with NSubstitute

I have been tasked at work to create a test script that will (using entity framework) look-up a value in a table if existing.
The code I have to work with has this constructor:
public PostProductHelper(
Func<IMachineDBContext> contextFactory )
{
_contextFactory = contextFactory;
}
My method to unit test could be something like this:
public string CheckAndRemoveProductNameFileExtIfExists(
string productName )
{
using ( var ctx = CreateContext() )
{
return ctx.Products.FirstOrDefault( d => d.Name == productName);
}
}
so, going by the examples when Googling I am supposed to do this:
MockProductRepository = Substitute.For<IProductRepository>();
MockMessagePublicationService = Substitute.For<IMessagePublicationService>();
MockMachineDBContext = Substitute.For<IMachineDBContext>(););
var Products = new List<Product>
{
new Product { Name = "BBB" },
new Product { Name = "ZZZ" },
new Product { Name = "AAA" },
}.AsQueryable();
MockMachineDBContext.Products.AddRange( Products );
But in order to pass to my constructor I have to modify this to:
MockProductRepository = Substitute.For<IProductRepository>();
MockMessagePublicationService = Substitute.For<IMessagePublicationService>();
MockMachineDBContext = Substitute.For<Func<IMachineDBContext>>();
var Products = new List<Product>
{
new Product { Name = "BBB" },
new Product { Name = "ZZZ" },
new Product { Name = "AAA" },
}.AsQueryable();
MockMachineDBContext.Products.AddRange( Products );
which errors on the last line saying 'cannot resolve symbol 'Products'.
I am not allowed to change this constructor and I appreciate I may be making some mistakes.
You are missing () after MockMachineDBContext in MockMachineDBContext().Products.AddRange( Products );
MockMachineDBContext is delegate.
For usage also see Substituting for delegates in NSubstitute.

How do I include subclasses in Swagger API documentation/ OpenAPI specification using Swashbuckle?

I have an Asp.Net web API 5.2 project in c# and generating documentation with Swashbuckle.
I have model that contain inheritance something like having an Animal property from an Animal abstract class and Dog and Cat classes that derive from it.
Swashbuckle only shows the schema for the Animal class so I tried to play with ISchemaFilter (that what they suggest too) but I couldn't make it work and also I cannot find a proper example.
Anybody can help?
It seems Swashbuckle doesn't implement polymorphism correctly and I understand the point of view of the author about subclasses as parameters (if an action expects an Animal class and behaves differently if you call it with a dog object or a cat object, then you should have 2 different actions...) but as return types I believe that it is correct to return Animal and the objects could be Dog or Cat types.
So to describe my API and produce a proper JSON schema in line with correct guidelines (be aware of the way I describe the disciminator, if you have your own discriminator you may need to change that part in particular), I use document and schema filters as follows:
SwaggerDocsConfig configuration;
.....
configuration.DocumentFilter<PolymorphismDocumentFilter<YourBaseClass>>();
configuration.SchemaFilter<PolymorphismSchemaFilter<YourBaseClass>>();
.....
public class PolymorphismSchemaFilter<T> : ISchemaFilter
{
private readonly Lazy<HashSet<Type>> derivedTypes = new Lazy<HashSet<Type>>(Init);
private static HashSet<Type> Init()
{
var abstractType = typeof(T);
var dTypes = abstractType.Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
var result = new HashSet<Type>();
foreach (var item in dTypes)
result.Add(item);
return result;
}
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
if (!derivedTypes.Value.Contains(type)) return;
var clonedSchema = new Schema
{
properties = schema.properties,
type = schema.type,
required = schema.required
};
//schemaRegistry.Definitions[typeof(T).Name]; does not work correctly in SwashBuckle
var parentSchema = new Schema { #ref = "#/definitions/" + typeof(T).Name };
schema.allOf = new List<Schema> { parentSchema, clonedSchema };
//reset properties for they are included in allOf, should be null but code does not handle it
schema.properties = new Dictionary<string, Schema>();
}
}
public class PolymorphismDocumentFilter<T> : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, System.Web.Http.Description.IApiExplorer apiExplorer)
{
RegisterSubClasses(schemaRegistry, typeof(T));
}
private static void RegisterSubClasses(SchemaRegistry schemaRegistry, Type abstractType)
{
const string discriminatorName = "discriminator";
var parentSchema = schemaRegistry.Definitions[SchemaIdProvider.GetSchemaId(abstractType)];
//set up a discriminator property (it must be required)
parentSchema.discriminator = discriminatorName;
parentSchema.required = new List<string> { discriminatorName };
if (!parentSchema.properties.ContainsKey(discriminatorName))
parentSchema.properties.Add(discriminatorName, new Schema { type = "string" });
//register all subclasses
var derivedTypes = abstractType.Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
foreach (var item in derivedTypes)
schemaRegistry.GetOrRegister(item);
}
}
What the previous code implements is specified here, in the section "Models with Polymorphism Support. It basically produces something like the following:
{
"definitions": {
"Pet": {
"type": "object",
"discriminator": "petType",
"properties": {
"name": {
"type": "string"
},
"petType": {
"type": "string"
}
},
"required": [
"name",
"petType"
]
},
"Cat": {
"description": "A representation of a cat",
"allOf": [
{
"$ref": "#/definitions/Pet"
},
{
"type": "object",
"properties": {
"huntingSkill": {
"type": "string",
"description": "The measured skill for hunting",
"default": "lazy",
"enum": [
"clueless",
"lazy",
"adventurous",
"aggressive"
]
}
},
"required": [
"huntingSkill"
]
}
]
},
"Dog": {
"description": "A representation of a dog",
"allOf": [
{
"$ref": "#/definitions/Pet"
},
{
"type": "object",
"properties": {
"packSize": {
"type": "integer",
"format": "int32",
"description": "the size of the pack the dog is from",
"default": 0,
"minimum": 0
}
},
"required": [
"packSize"
]
}
]
}
}
}
To follow on from Paulo's great answer, if you're using Swagger 2.0, you'll need to modify the classes as shown:
public class PolymorphismSchemaFilter<T> : ISchemaFilter
{
private readonly Lazy<HashSet<Type>> derivedTypes = new Lazy<HashSet<Type>>(Init);
private static HashSet<Type> Init()
{
var abstractType = typeof(T);
var dTypes = abstractType.Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
var result = new HashSet<Type>();
foreach (var item in dTypes)
result.Add(item);
return result;
}
public void Apply(Schema model, SchemaFilterContext context)
{
if (!derivedTypes.Value.Contains(context.SystemType)) return;
var clonedSchema = new Schema
{
Properties = model.Properties,
Type = model.Type,
Required = model.Required
};
//schemaRegistry.Definitions[typeof(T).Name]; does not work correctly in SwashBuckle
var parentSchema = new Schema { Ref = "#/definitions/" + typeof(T).Name };
model.AllOf = new List<Schema> { parentSchema, clonedSchema };
//reset properties for they are included in allOf, should be null but code does not handle it
model.Properties = new Dictionary<string, Schema>();
}
}
public class PolymorphismDocumentFilter<T> : IDocumentFilter
{
private static void RegisterSubClasses(ISchemaRegistry schemaRegistry, Type abstractType)
{
const string discriminatorName = "discriminator";
var parentSchema = schemaRegistry.Definitions[abstractType.Name];
//set up a discriminator property (it must be required)
parentSchema.Discriminator = discriminatorName;
parentSchema.Required = new List<string> { discriminatorName };
if (!parentSchema.Properties.ContainsKey(discriminatorName))
parentSchema.Properties.Add(discriminatorName, new Schema { Type = "string" });
//register all subclasses
var derivedTypes = abstractType.Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
foreach (var item in derivedTypes)
schemaRegistry.GetOrRegister(item);
}
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
RegisterSubClasses(context.SchemaRegistry, typeof(T));
}
}
As of this merge into Swashbuckle.AspNetCore, you can get basic support for polymorphic schemas by using:
services.AddSwaggerGen(c =>
{
c.GeneratePolymorphicSchemas();
}
You can also express your derived types via attributes present in the Annotations library:
[SwaggerSubTypes(typeof(SubClass), Discriminator = "value")]
This article goes into further detail as to how you can deserialize derived types using Newtonsoft.
I'd like to follow up on Craig's answer.
If you use NSwag to generate TypeScript definitions from the Swagger API documentation generated with Swashbuckle (3.x at the time of writing) using the method explained in Paulo's answer and further enhanced in Craig's answer you will probably face the following problems:
Generated TypeScript definitions will have duplicate properties even though the generated classes will extend the base classes. Consider the following C# classes:
public abstract class BaseClass
{
public string BaseProperty { get; set; }
}
public class ChildClass : BaseClass
{
public string ChildProperty { get; set; }
}
When using the aforementioned answers, the resulting TypeScript definition of IBaseClass and IChildClass interfaces would look like this:
export interface IBaseClass {
baseProperty : string | undefined;
}
export interface IChildClass extends IBaseClass {
baseProperty : string | undefined;
childProperty: string | undefined;
}
As you can see, the baseProperty is incorrectly defined in both base and child classes. To solve this, we can modify the Apply method of the PolymorphismSchemaFilter<T> class to include only owned properties to the schema, i.e. to exclude the inherited properties from the current types schema. Here is an example:
public void Apply(Schema model, SchemaFilterContext context)
{
...
// Prepare a dictionary of inherited properties
var inheritedProperties = context.SystemType.GetProperties()
.Where(x => x.DeclaringType != context.SystemType)
.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
var clonedSchema = new Schema
{
// Exclude inherited properties. If not excluded,
// they would have appeared twice in nswag-generated typescript definition
Properties =
model.Properties.Where(x => !inheritedProperties.ContainsKey(x.Key))
.ToDictionary(x => x.Key, x => x.Value),
Type = model.Type,
Required = model.Required
};
...
}
Generated TypeScript definitions will not reference properties from any existing intermediate abstract classes. Consider the following C# classes:
public abstract class SuperClass
{
public string SuperProperty { get; set; }
}
public abstract class IntermediateClass : SuperClass
{
public string IntermediateProperty { get; set; }
}
public class ChildClass : BaseClass
{
public string ChildProperty { get; set; }
}
In this case, the generated TypeScript definitions would look like this:
export interface ISuperClass {
superProperty: string | undefined;
}
export interface IIntermediateClass extends ISuperClass {
intermediateProperty : string | undefined;
}
export interface IChildClass extends ISuperClass {
childProperty: string | undefined;
}
Notice how the generated IChildClass interface extends ISuperClass directly, ignoring the IIntermediateClass interface, effectively leaving any instance of IChildClass without the intermediateProperty property.
We can use the following code to solve this problem:
public void Apply(Schema model, SchemaFilterContext context)
{
...
// Use the BaseType name for parentSchema instead of typeof(T),
// because we could have more classes in the hierarchy
var parentSchema = new Schema
{
Ref = "#/definitions/" + (context.SystemType.BaseType?.Name ?? typeof(T).Name)
};
...
}
This will ensure that the child class correctly references the intermediate class.
In conclusion, the final code would then look like this:
public void Apply(Schema model, SchemaFilterContext context)
{
if (!derivedTypes.Value.Contains(context.SystemType))
{
return;
}
// Prepare a dictionary of inherited properties
var inheritedProperties = context.SystemType.GetProperties()
.Where(x => x.DeclaringType != context.SystemType)
.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
var clonedSchema = new Schema
{
// Exclude inherited properties. If not excluded,
// they would have appeared twice in nswag-generated typescript definition
Properties =
model.Properties.Where(x => !inheritedProperties.ContainsKey(x.Key))
.ToDictionary(x => x.Key, x => x.Value),
Type = model.Type,
Required = model.Required
};
// Use the BaseType name for parentSchema instead of typeof(T),
// because we could have more abstract classes in the hierarchy
var parentSchema = new Schema
{
Ref = "#/definitions/" + (context.SystemType.BaseType?.Name ?? typeof(T).Name)
};
model.AllOf = new List<Schema> { parentSchema, clonedSchema };
// reset properties for they are included in allOf, should be null but code does not handle it
model.Properties = new Dictionary<string, Schema>();
}
We recently upgraded to .NET Core 3.1 and Swashbuckle.AspNetCore 5.0
And the API is somewhat changed.
In case somebody needs this filter here is the code with minimal changes to get similar behavior:
public class PolymorphismDocumentFilter<T> : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
RegisterSubClasses(context.SchemaRepository, context.SchemaGenerator, typeof(T));
}
private static void RegisterSubClasses(SchemaRepository schemaRegistry, ISchemaGenerator schemaGenerator, Type abstractType)
{
const string discriminatorName = "$type";
OpenApiSchema parentSchema = null;
if (schemaRegistry.TryGetIdFor(abstractType, out string parentSchemaId))
parentSchema = schemaRegistry.Schemas[parentSchemaId];
else
parentSchema = schemaRegistry.GetOrAdd(abstractType, parentSchemaId, () => new OpenApiSchema());
// set up a discriminator property (it must be required)
parentSchema.Discriminator = new OpenApiDiscriminator() { PropertyName = discriminatorName };
parentSchema.Required = new HashSet<string> { discriminatorName };
if (parentSchema.Properties == null)
parentSchema.Properties = new Dictionary<string, OpenApiSchema>();
if (!parentSchema.Properties.ContainsKey(discriminatorName))
parentSchema.Properties.Add(discriminatorName, new OpenApiSchema() { Type = "string", Default = new OpenApiString(abstractType.FullName) });
// register all subclasses
var derivedTypes = abstractType.GetTypeInfo().Assembly.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
foreach (var item in derivedTypes)
schemaGenerator.GenerateSchema(item, schemaRegistry);
}
}
public class PolymorphismSchemaFilter<T> : ISchemaFilter
{
private readonly Lazy<HashSet<Type>> derivedTypes = new Lazy<HashSet<Type>>(Init);
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (!derivedTypes.Value.Contains(context.Type)) return;
Type type = context.Type;
var clonedSchema = new OpenApiSchema
{
Properties = schema.Properties,
Type = schema.Type,
Required = schema.Required
};
// schemaRegistry.Definitions[typeof(T).Name]; does not work correctly in Swashbuckle.AspNetCore
var parentSchema = new OpenApiSchema
{
Reference = new OpenApiReference() { ExternalResource = "#/definitions/" + typeof(T).Name }
};
var assemblyName = Assembly.GetAssembly(type).GetName();
schema.Discriminator = new OpenApiDiscriminator() { PropertyName = "$type" };
// This is required if you use Microsoft's AutoRest client to generate the JavaScript/TypeScript models
schema.Extensions.Add("x-ms-discriminator-value", new OpenApiObject() { ["name"] = new OpenApiString($"{type.FullName}, {assemblyName.Name}") });
schema.AllOf = new List<OpenApiSchema> { parentSchema, clonedSchema };
// reset properties for they are included in allOf, should be null but code does not handle it
schema.Properties = new Dictionary<string, OpenApiSchema>();
}
private static HashSet<Type> Init()
{
var abstractType = typeof(T);
var dTypes = abstractType.GetTypeInfo().Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
var result = new HashSet<Type>();
foreach (var item in dTypes)
result.Add(item);
return result;
}
}
I did not inspect the result fully, but it seems that it gives the same behavior.
Also note that you need to import these namespaces:
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Any;
using System.Reflection;
using Swashbuckle.AspNetCore.SwaggerGen;
This works as of version 5.6.3:
services.AddSwaggerGen(options =>
{
options.UseOneOfForPolymorphism();
options.SelectDiscriminatorNameUsing(_ => "type");
});

.net generic method VB.Net vs C#, since when vb is more flexible?

Have a generic function, it doesn't really mater what it is doing (FYI coping one list of object to another), main idea is that it has two types Ts and Tp
public static List<Tp> CreateAndFillList<Ts, Tp>(this IEnumerable<Ts> sItems) where Tp : class, new()
{
Type myType = default(Type);
PropertyInfo[] pSourceAllInfos = null;
if (pSourceAllInfos == null)
{
myType = typeof(Ts);
pSourceAllInfos = myType.GetProperties(BindingFlags.Public | BindingFlags.Instance).ToArray();
}
PropertyInfo[] pTargetAllInfos = null;
if (pTargetAllInfos == null)
{
myType = typeof(Tp);
pTargetAllInfos = myType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(pi => pi.CanWrite).ToArray();
}
var joinedPI = (from spi in pSourceAllInfos
join tpi in pTargetAllInfos on spi.Name.ToLower() equals tpi.Name.ToLower()
select new { spi, tpi }).ToList();
List<Tp> retList = new List<Tp>();
foreach (var sItem in sItems)
{
Tp tpNewItem = new Tp();
foreach (var jpi in joinedPI)
{
jpi.tpi.SetValue(tpNewItem, jpi.spi.GetValue(sItem, null), null);
}
retList.Add(tpNewItem);
}
return retList;
}
Have two simple classes
public class SourceInfo
{
public int Id { get; set; }
public string Name { get; set; }
public string SourceData { get; set; }
}
public class TargetInfo
{
public int Id { get; set; }
public string Name { get; set; }
public string TargetData { get; set; }
}
My problem is that following code throw compilation error
private void button1_Click(object sender, EventArgs e)
{
List<SourceInfo> srcLst = new List<SourceInfo>();
srcLst.Add(new SourceInfo() { Id = 1, Name = "First", SourceData = "data1" });
srcLst.Add(new SourceInfo() { Id = 2, Name = "Second", SourceData = "data2" });
var q = from li in srcLst
select new { li.Id, li.Name };
dynamic qD = from li in srcLst
select new { li.Id, li.Name };
var resultLst = srcLst.CreateAndFillList<TargetInfo>();
//Using the generic method 'ExtensionTest.Extensions.CreateAndFillList<Ts,Tp>(System.Collections.Generic.IEnumerable<Ts>)' requires 2 type arguments
var resultLst1 = q.CreateAndFillList<TargetInfo>();
//Using the generic method 'ExtensionTest.Extensions.CreateAndFillList<Ts,Tp>(System.Collections.Generic.IEnumerable<Ts>)' requires 2 type arguments
var resultLst2 = qD.CreateAndFillList<TargetInfo>();
//works but will have to use dynamic...
}
And at the same time in VB.Net everything is ok!!!!
Dim lst As List(Of SourceInfo) = New List(Of SourceInfo)()
lst.Add(New SourceInfo() With {.Id = 1, .Name = "First"})
lst.Add(New SourceInfo() With {.Id = 2, .Name = "Second"})
Dim q = From li In lst
Select New With {li.Id, li.Name}
Dim retLst = lst.CreateAndFillList(Of TargetInfo)()
Dim retLst1 = q.CreateAndFillList(Of TargetInfo)()
My problem is I don't want to use dynamic everywhere because it will require extra coding plus it is run-time compilation.
What I am doing wrong in C#? please help.
The compiler message is quite clear about the problem: You need to specify both type arguments. If you specify only one, it is unclear which of both parameters it should be.
var resultLst = srcLst.CreateAndFillList<SourceInfo, TargetInfo>();
var resultLst1 = q.CreateAndFillList<SourceInfo, TargetInfo>();
And this:
dynamic qD = from li in srcLst
select new { li.Id, li.Name };
does not need to be dynamic. var is more appropriate here and will give you compile-time errors. If you do this, you get the same error for qD:
var resultLst2 = qD.CreateAndFillList<SourceInfo, TargetInfo>();
Otherwise, you will get the error only at runtime.
What I am doing wrong in C#?
The compiler won't partially infer generic parameters. Using dynamic defers that check to runtime, where it will likely fail. You need to supply both the input and output generic parameters in your call to CreateAndFillList.
var resultLst = srcLst.CreateAndFillList<SourceInfo, TargetInfo>();

C# anonymous object (javascript like) for adhoc usage. No class definition

I wanted to create an anonymous C# object with no class definition. Similar to javascript object. Sample code not working:
var data = new
{
groups = new object[] {
new {
header = "Facebook",
buttons = new object[] {
new {
label = "Create new post"
}
}
}
}
};
The problem was the new object[]. It is just new []. Working code:
var data = new
{
groups = new [] {
new {
header = "Facebook",
buttons = new [] {
new {
label = "Create new post"
}
}
}
}
};

MVC error working with a List getting Object not set to an instance of an object

I am getting an error I can't figure out how to fix? It claims to be happening on:
var respondents = RespondentRepository.GetRespondents(UserSession, fieldsToInclude).Where(r=>r.Email.Contains(query)).ToList();
When I remove all the .Where stuff it appears to work fine. I am not really use to this error so I am not sure where the best start would be?
Error:
System.NullReferenceException: Object reference not set to an instance of an object.
Code:
MakeSafeRequest(() =>
{
var respondents = RespondentRepository.GetRespondents(UserSession, fieldsToInclude).Where(r=>r.Email.Contains(query)).ToList();
model = new RespondentSearchViewModel
{
Respondents = respondents,
TableData = respondents.SelectToListOrEmpty(r =>
{
return new TableRowViewModel
{
Id = r.Id,
Data = new Dictionary<string, string>
{
{ "FirstName", r.FirstName },
{ "LastName", r.LastName },
{ "EmailAddress", r.Email },
{ "Project", r.ProjectId.ToString() },
{ "Status", r.RecruitingStatus.ToIconHtml() },
{ "ViewHistory", "<div class=\"btnBlue btSml\"><a class=\"closeMe\" href=\"/RespondentSearch/RespondentDetails/?respondentId="+r.Id +"\" >view detail</a></div>"}
//{ "StatusDate", r.LastActionDate.Value.ToLocalTime().ToString() },
}
};
})
};
});
MODEL:
public class RespondentSearchViewModel:ListPageViewModel
{
public List<Respondent> Respondents { get; set; }
}
r.Email might be null. Try changing your lambda to:
r => (r.Email == null ? false : r.Email.Contains(query))

Categories