I've marked a property as obsolete in my (input)model
public class MyModel
{
[Obsolete("Use 'OtherProperty'")]
public string SomeProperty {get;set;}
public List<string> OtherProperty {get;set;}
}
However, swagger shows no distinction between the two properties, neither does it show the message.
Is there any way I can get swagger to honor the Obsolete attribute? Or will I need to put this in the xml-comments above the property myself?
Unfortunately there is no support for obsolete properties on Swashbuckle yet...
We are limited by the OpenAPI-Specification, and Swashbuckle still using 2.0
The closest thing is deprecated but that is available only for the methods not for properties:
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object
One option will be to hack something using an IDocumentFilter to completely hide those properties tagged with Obsolete but that will be a bumpy road.
Another option is to create two methods and two models, that way you can tag the method and that will transition to the method within, everything will be deprecated (I think this is a bit messy) but I have seen this pattern used in many web-api
I think your best/easiest solution is what you suggested add some xml comments noting that the property should not be used.
[Obsolete]
public string Property {get; set;}
services.AddSwaggerGen(x =>
// your other settings...
x.IgnoreObsoleteProperties();
)
This works for me
It works for me by simply decorating property with [Obsolete] attribute (from System namespace) and setting the Swagger flag IgnoreObsoleteProperties to true. I added also property SomePropertySpecified, which is automatically set to true by serializer in case SomeProperty exists in request (null value does not mean that property did not exist). I have a custom logic to return appropriate error message if SomePropertySpecified is true.
public class Item
{
[Obsolete]
public string SomeProperty { get; set; }
[JsonIgnore]
public bool SomePropertySpecified { get; set; }
public List<string> OtherProperty { get; set; }
}
Class SwaggerConfig:
public class SwaggerConfig
{
public static void Register()
{
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "Demo");
c.IgnoreObsoleteProperties();
})
.EnableSwaggerUi(c =>
{
c.DocExpansion(DocExpansion.Full);
});
}
}
Swagger UI:
Related
I am using Swashbuckle and Swagger UI to automatically generate API documentation.
My person endpoint uses the following PersonViewmodel:
public int? ID { get; set; }
public string Name { get; set; }
My endpoint method takes in a PersonViewmodel as the body and returns a PersonViewmodel using an IActionResult. However, I don't want the user defining an ID as that is generated by the business logic. In the code if an ID is set it is ignored.
How can I change the Swagger UI to not show the ID in the Example value for the body input but still show the ID for the Example value for the responses?
I have found many ways to remove properties completely from Swagger UI such as [JsonIgnore] or setting the property to internal or private. But how can I remove a property from the input example in swagger but keep it in the output/responses example?
There is a better way now with now. There are two things you need to do
Annotate with SwaggerSchema found in Swashbuckle.AspNetCore.Annotations;
[SwaggerSchema(ReadOnly = true)]
public int Id { get; set; }
EnableAnnotations in AddSwaggerGen
builder.Services.AddSwaggerGen(options =>
{
options.EnableAnnotations();
});
See more about it in the Documentation
Please use the attribute [BindNever] above the property like
[BindNever]
public int? Id {get;set;}
Also, please Check if you're using Newtonsoft.Json to serialize, that could be the reason your System.Text's JsonIgnore attribute didn't work.
My solution has a WebAPI project (.net core 3.1, Microsoft.AspNetCore.Mvc) and a (.Net Standard 2.1) class library that defines the data structures.
My Controller takes a post with a single parameter that deserializes mostly correctly
public class apiRequest
{
public RequestData TheData { get; set; }
public Options Options { get; set; }
public apiRequest() { }
}
The RequestData and child objects are defined i a .Net Standard 2.1 class library and added via a nuget package
public class RequestData : IRequestData
{
public int Datum{ get; set; }
...
public List<ComplexItem> ComplexItems { get; set; }
...
}
public class ComplexItem: ItemBase, IComplexItem
{
public ComplexItem() : base() { }
public ComplexItem(Pricing defaultPricing) : base(defaultPricing) { }
[JsonConstructor]
public ComplexItem(Pricing defaultPricing, Pricing selectedPricing) : base(defaultPricing, selectedPricing) { }
}
The problem I am running into is with the defaultPricing is always null when it gets to the controller
public class ItemBase : IItemBase
{
public ItemBase () { }
public ItemBase (Pricing defaultPricing)
{
DefaultPricing = defaultPricing;
}
[JsonConstructor]
public ItemBase (Pricing defaultPricing, Pricing selectedPricing)
{
DefaultPricing = defaultPricing;
SelectedPricing = selectedPricing;
}
#region Pricing
[JsonProperty]
protected Pricing DefaultPricing { get; set; }
public Pricing SelectedPricing { get; set; }
[JsonIgnore]
protected Pricing CurrentPricing
{
get { return SelectedPricing ?? DefaultPricing; }
set { SelectedPricing = value; }
}
[JsonIgnore]
public decimal Cost { get => CurrentPricing?.Cost ?? 0; }
[JsonIgnore]
public decimal Price { get => CurrentPricing?.Price ?? 0; }
#endregion
}
I've tried using [DataContract] and [DataMember] attributes, JsonObject, JsonConstructor, JsonProperty attributes and [Serializable] attribute. (Is there a current best practice on what to use?)
If I read the Json from a file and use Newtonsoft.Json.JsonConvert.DeserializeObject it deserializes correctly with the Json attributes added, but still null in the controller.
It also deserializes in the API properly if I make it public, so it doesn't seem like a problem in the Pricing class itself
After posting I found this Question about making Newtonsoft the default and using MikeBeaton's accepted solution there with Microsoft.AspNetCore.Mvc.NewtonsoftJson package worked so I'll put this as one potential answer for anyone else with this issue. Would still like to know if there is a more correct solution available.
System.Text.Json Serializes Public Properties
As the documentation implies (emphasis mine):
By default, all (read: only) public properties are serialized. You can specify properties to exclude.
I would guess that this was the design chosen because serializing an object is allowing that object to cross barriers of scope and the public scope is the only one that can reliably be assumed.
If you think about it, it makes sense. Lets say, you define a protected property and serialize the object. Then a client picks it up and deserializates that text representation into a public property. What you have designed to be an implementation detail of/to derived types is now accessible outside the scope defined by the modifier.
Apart from simply pointing you to your own answer where Newtonsoft allows this protected property to be serialized, I would suggest you look more intently at your design and why those properties are protected in the first place. It makes sense within the context of your API implementation, but the client can't (shouldn't) be assumed to follow your same inheritance structure (or support inheritance at all). It seems like you might want to define a true DTO to act as the "shape" of your API response and find the right place to transition from your internal types using protected scope to control access and the DTO that can cross the border of the API.
I have several classes in our code that are automatically generated by the XSD generator tool from XSD file definitions. The classes look very similar with similar names, but based on the XSD schema (which we've received from an external vendor), the classes that are generated are all of different types. And these are pretty complex classes, with lots of deep nested properties and enum values. Hence, we used to work with the classes directly, because it was hard to do a generalized approach to work with the classes.
But I undertook the challenge, and I've (kind of) succeeded. To avoid code duplication when working with these classes, I have added properties to the classes using interface definitions outside the XSD generated file, so as to prevent them from being overwritten when generating the classes again, taking advantage of the partial class declaration like so:
Simplified example of XSD generated classes
public partial class xsdGeneratedClass1
{
public xsdGeneratedClass1Header header { get; set }
public xsdGeneratedClass1Body body { get; set; }
}
public partial class xsdGeneratedClass2
{
public xsdGeneratedClass2Header header { get; set }
public xsdGeneratedClass2Body body { get; set; }
}
Simplified example of an interface, where the properties also consists of type interfaces, which we've written to match the properties in the XSD generated classes
public interface IXsdGeneratedClass
{
IXsdGeneratedClassHeader header { get; set; }
IXsdGeneratedClassBody body { get; set; }
}
Simplified example of us implementing the interface, outside of the XSD generated file
public partial class xsdGeneratedClass1 : IXsdGeneratedClass
{
public IXsdGeneratedClassHeader header { get; set; }
public IXsdGeneratedClassBody body { get; set; }
}
public partial class xsdGeneratedClass2 : IXsdGeneratedClass
{
public IXsdGeneratedClassHeader header { get; set; }
public IXsdGeneratedClassBody body { get; set; }
}
In this simplified example, this construct allows me to work with the header and body properties using interfaces instead of the concrete implementations, for the dozens of classes that we have with the same structure, but with different class types, without editing in the autogenerated code of the XSD tool. This all works fine and dandy.
The problem comes when trying to compare the objects in our unit tests, using Fluent Assertions. It seems that Fluent Assertions has trouble knowing which properties of the instantiated objects to compare. In this simple example, an instantiated object of xsdGeneratedClass1 will have four properties:
public xsdGeneratedClass1Header header { get; set }
public xsdGeneratedClass1Body body { get; set; }
public IXsdGeneratedClassHeader header { get; set; }
public IXsdGeneratedClassBody body { get; set; }
The objects that I want to compare are the header and body properties with the interface types, since these will be the only ones that have actual data in them. The concrete class properties are always all null. So I've made tests like these:
class1.Should().BeEquivalentTo(expectedClass);
But it seems like Fluent Assertions constantly compares class1's IXsdGeneratedClassHeader header with the expectedClass' xsdGeneratedClass1Header header, which is null.
I have tried using the RespectingRuntimeTypes option, which makes the test pass, but then it seems like it doesn't compare the objects as it should. If I change a property value in the expectedClass' header property for example that I know won't match the one in class1, the test still passes.
I've tried scouring the interwebs for answers, and I've come to an end in my search, and basically am pondering on whether I should just write my own tool or make a gazillion manual assertions. Obi-Wan Assertions, please help!
Thank you very much Jonas Nyrup! Seems to be a defect that is now amended in the master branch of Fluent Assertions. In the meantime, someone posted an answer in the github thread for a work-around, that also worked for me. Very happy. Thanks!
Declare this class somewhere, available for use in your unit tests
public class ReflectionMemberMatchingRule : IMemberMatchingRule
{
public SelectedMemberInfo Match(SelectedMemberInfo expectedMember, object subject, string memberPath, IEquivalencyAssertionOptions config) => expectedMember;
}
Use the extension class in your unit tests, by adding the configuration to Fluent Assertions.
AssertionOptions.AssertEquivalencyUsing(x => x.Using(new ReflectionMemberMatchingRule()));
It now works as expected!
I am having trouble getting FluentValidation to work with a collection of objects. My controller POST action takes in an IEnumerable of objects like below. When I post to an action that takes a single EventInputDto, with an incorrectly formatted Url property, my validation occurs successfully. When I post to a collection of EventInputDto, it does not work and does no validation.
If I use regular MVC Attributes (i.e. required / email), they work with collections as well as single objects. How do I get this to work with FluentValidation? I am not working with inner collections so I'm not sure why it does not work as intended.
public async Task<IActionResult> CreateEventCollection([FromBody] IEnumerable<EventInputDto> events)
{
if (!ModelState.IsValid)
{
return UnprocessableEntity(ModelState); //does not work
}
}
My validators are setup using generics because I am using separate models for inputs and updates.
public class EventManipulationValidator<T> : AbstractValidator<T> where T : EventManipulationDto
{
public EventManipulationValidator()
{
RuleFor(manipulationDto => manipulationDto.Title).NotNull().WithMessage("Title cannot be blank")
.Length(1, 50);
RuleFor(manipulationDto => manipulationDto.Message).NotNull().WithMessage("Message cannot be blank")
.Length(1, 1000);
RuleFor(manipulationDto => manipulationDto.ScheduledTime).NotNull().WithMessage("Scheduled Time cannot be blank");
RuleFor(inputDto => inputDto.Url).Matches(#"https://.*windows\.net.*").WithMessage("The url must be valid and stored on Azure");
}
}
As my CreateEventCollection action takes in an IEnumerable of EventInputDto, my validator for EventInputDto is setup as below:
public class EventInputValidator : EventManipulationValidator<EventInputDto>
{
public EventInputValidator()
{
//all property validators are inherited from EventManipulationValidator
}
}
public class EventInputCollectionValidator : AbstractValidator<IEnumerable<EventInputDto>>
{
public EventInputCollectionValidator()
{
RuleForEach(p => p).SetValidator(new EventManipulationValidator<EventInputDto>());
}
}
Below are my models for reference:
EventManipulationDto
public abstract class EventManipulationDto
{
public string Title { get; set; }
public string Message { get; set; }
public string Url { get; set; }
public DateTime? ScheduledTime { get; set; }
}
EventInputDto
public class EventInputDto : EventManipulationDto
{
//all properties inherited from base class
}
After going through the list of open/closed issues on the project GitHub, it seems that not all of my approach is required. There is no need for my `EventInputCollectionValidator. FluentValidation no longer requires explicitly defining an IEnumerable validator like I defined above.
It's enough to define a base AbstractValidator or as in my case an inherited validator from a parent class.
The only change needed to get it to work was in my startup.cs when registering fluentvalidation. I needed to explicitly add ImplicitlyValidateChildProperties = true. Didn't realize this was required as I thought this was for validating child property collections and not the parent collection objects. Works perfectly now.
.AddFluentValidation(fv => {
fv.RunDefaultMvcValidationAfterFluentValidationExecutes = true;
fv.RegisterValidatorsFromAssemblyContaining<Startup>();
fv.ImplicitlyValidateChildProperties = true;
});
On MVC3, is there a way to decorate a ViewModel property in order to get the DefaultModelBinder to use a different name for it in the request?
For example, suppose you have the following view model:
public class SomeModel
{
public string Direction {get;set;}
}
But the parameter coming in is Dir from an external source (such as some third-party component, for example).
I know a custom model binder could handle that, but I assume there must be a way to decorate the property, similar to the way action parameters can use Bind(Prefix="...") in order to define that mapping.
You could always create another Property:
public class SomeModel
{
public string Direction {get;set;}
public string Dir
{
get { return this.Direction; }
set { this.Direction = value; }
}
}
I'd also mention that the ViewModel used in a view (cshtml/vbhtml) does not have to be the same ViewModel used on the Post Method.
OK, so after more research looking at similar questions and seeing the feedback here as well, it seems that the answer to my question is basically "NO".
There is no out-of-the-box way, so either custom binders must be used or or the properties should be renamed.
A similar question with a more detailed answer can be found here: How to bind URL parameters to model properties with different names
I was able to accomplish this in ASP.NET MVC Core using the FromForm attribute.
public class DataTableOrder
{
public int Column { get; set; }
[FromForm(Name = "Dir")]
public string Direction { get; set; }
}
Documentation: https://docs.asp.net/en/latest/mvc/models/model-binding.html#customize-model-binding-behavior-with-attributes
However, depending if you do a GET or a POST, you might want to use [FromQuery] instead of [FromForm] I suppose.