how can i use Description attribute work with swashbuckle - asp.net-core

I am using the latest version of swashbuckle for .net core and I want swashbuckle to repspect the property Description so that for all parameters the description will be used for the swagger file
public class dtoClass
{
[System.ComponentModel.Description("desciption swagger Name")]
public string name {get; set; }
}
Is there an extention point/ filter in swashbuckle to be used?
my current soltion is to add the SwaggerSchema attribute.
public class dtoClass
{
[System.ComponentModel.Description("desciption swagger Name")]
[Swashbuckle.AspNetCore.Annotations("desciption swagger Name")]
public string name {get; set; }
}

You can use schema filter
builder.Services.AddSwaggerGen(c =>
{
c.SchemaFilter<DescriptionSchemaFilter>();
});
Where DescriptionSchemaFilter is
internal class DescriptionSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.ParameterInfo != null)
{
var descriptionAttributes = context.ParameterInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (descriptionAttributes.Length > 0)
{
var descriptionAttribute = (DescriptionAttribute)descriptionAttributes[0];
schema.Description = descriptionAttribute.Description;
}
}
if (context.MemberInfo != null)
{
var descriptionAttributes = context.MemberInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (descriptionAttributes.Length > 0)
{
var descriptionAttribute = (DescriptionAttribute)descriptionAttributes[0];
schema.Description = descriptionAttribute.Description;
}
}
if (context.Type != null)
{
var descriptionAttributes = context.Type.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (descriptionAttributes.Length > 0)
{
var descriptionAttribute = (DescriptionAttribute)descriptionAttributes[0];
schema.Description = descriptionAttribute.Description;
}
}
}
}
And code:
in swagger look's like:

Related

XML Serialization - Required attribute

I have a class that should represent a controller's action parameter and I'd like its properties to be "required" (meaning, you get a status code 400 or something in case it's passed as null). I managed to get it done using System.ComponentModel.DataAnnotations, but the ErrorMessage that I pass to the constructor of the Required attribute is never shown.
[XmlRoot(ElementName = "root")]
public class Request
{
[XmlElement(ElementName = "prop")]
[Required(ErrorMessage = "The property is required.")]
public string Property { get; set; }
[XmlElement(ElementName = "another")]
[Required(ErrorMessage = "The property is required.")]
public string Another { get; set; }
}
Action:
[HttpPost]
public IActionResult Post([FromBody] Request value)
{
return Ok(value); //ignore this, it's just for testing purposes...
}
However, if I don't pass the Property value, I get a 400 that doesn't contain the ErrorMessage I passed earlier. Am I missing something here?
<ValidationProblemDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Title>One or more validation errors occurred.</Title>
<Status>400</Status>
</ValidationProblemDetails>
My Startup has Xml formatters added to it:
services.AddMvc(options =>
{
options.RespectBrowserAcceptHeader = true;
options.InputFormatters.Insert(0, new XmlSerializerInputFormatter(options));
options.OutputFormatters.Insert(0, new XmlSerializerOutputFormatter());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
The body of the request looks like this, and it doesn't have "Property":
<root>
<another>Test</another>
<!-- Property "Property" is missing here -->
</root>
Kudos to Code Rethinked for the huge help - Customizing automatic HTTP 400 error response in ASP.NET Core Web APIs.
An approach that I managed to figure out eventually includes the use of services.Configure in my Startup.ConfigureServices method.
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
return new OkObjectResult(new CustomResponse(someStatusCode, context))
{
ContentTypes = { "application/xml" }
};
};
});
So, I made a class named CustomResponse that holds the status code I want to retrieve and all the validation errors (including the ones where my Required property was not passed to the API).
[XmlRoot(ElementName = "rcemsTrxSubReqAck")]
public class CustomResponse
{
[XmlElement(ElementName = "Status")]
public string Status { get; set; }
[XmlArray(ElementName = "Errors"), XmlArrayItem(ElementName = "Error")]
public string[] Errors { get; set; }
public CustomResponse(int status, ActionContext context)
{
Status = status;
Errors = ConstructErrorMessages(context);
}
private string[] ConstructErrorMessages(ActionContext context)
{
if (context == null)
{
return null;
}
string[] arr = new string[context.ModelState.ErrorCount];
int i = 0;
foreach (var keyModelStatePair in context.ModelState)
{
var key = keyModelStatePair.Key;
var errors = keyModelStatePair.Value.Errors;
if (errors != null && errors.Count > 0)
{
if (errors.Count == 1)
{
var errorMessage = GetErrorMessage(errors[0]);
arr[i] = $"{key}: {errorMessage}";
}
else
{
var errorMessages = new string[errors.Count];
for (var j = 0; j < errors.Count; j++)
{
errorMessages[j] = GetErrorMessage(errors[j]);
}
arr[i] = $"{key}: {errorMessages.ToString()}";
}
i++;
}
}
return arr;
}
private string GetErrorMessage(ModelError error)
{
return string.IsNullOrEmpty(error.ErrorMessage) ? "The input was not valid." : error.ErrorMessage;
}
}

Swashbuckle.AspNetCore 5.0.0-rc4 UploadFileFilter doesn't work

I need to add upload file for Swashbuckle.AspNetCore 5.0.0-rc4. In earlier version it works like:
public class SwaggerUploadFileParametersFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters != null)
{
var attribute =
apiDescription.ActionDescriptor.GetCustomAttributes<UploadFileParametersAttribute>()
.FirstOrDefault();
if (attribute != null)
{
operation.consumes.Add("multipart/form-data");
operation.parameters.Add(new Parameter
{
name = "file",
required = true,
type = "file",
#in = "formData"
}
);
}
}
}
}
[UploadFileParameters]
public async Task<IHttpActionResult> MyMethod([FromUri]MyMethodParams parameters)
I try to implement it using Microsoft.OpenApi.Models objects:
public class SwaggerUploadFileParametersFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var actionAttributes = context.MethodInfo.GetCustomAttributes<UploadFileParametersAttribute>().FirstOrDefault();
if (actionAttributes != null)
{
operation.RequestBody = new OpenApiRequestBody()
{
Content =
{
["multipart/form-data"] = new OpenApiMediaType()
{
Schema = new OpenApiSchema()
{
Properties =
{
["file"] = new OpenApiSchema()
{
Description = "Select file",
Type = "file"
}
}
}
}
}
};
}
}
}
But it doesn't work. I don't see file component in swagger
I took your code and some documentation from Swagger File Upload
I modified your code and added small fix
public class SwaggerUploadFileParametersFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var parameters = operation.Parameters;
if (parameters == null || parameters.Count == 0)
{
return;
}
var isUploadFile = context.ApiDescription.ActionDescriptor.Parameters.Any(x => x.ParameterType == typeof(IFormFile));
if (isUploadFile)
{
operation.RequestBody = new OpenApiRequestBody()
{
Content =
{
["multipart/form-data"] = new OpenApiMediaType()
{
Schema = new OpenApiSchema()
{
Type = "object",
Properties =
{
["file"] = new OpenApiSchema()
{
Description = "Select file", Type = "string", Format = "binary"
}
}
}
}
}
};
}
}
}
And controller:
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<IActionResult> UploadFileAsync([FromForm] IFormFile file)

How to keep user logged in after browser is closed

Every time I close the browser I need to log in again into this app. It is developed in .NET Core 2.0. I'm trying to let it logged in, like every other regular site.
I checked this post that may be useful, but since the code is quite different from this application I decided to create this post.
This is my security code:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
namespace Petito.Common
{
public interface IActivityContext
{
string ActivityID { get; }
IPrincipal User { get; }
}
[JsonObject(MemberSerialization = MemberSerialization.Fields)]
public class ActivityIdentity : IIdentity
{
private string _name = null;
[JsonIgnore()]
private bool _isAuthenticated = false;
[JsonIgnore()]
private string _authenticationType = "";
public ActivityIdentity()
{
}
public ActivityIdentity(string name) : this(name, false, "")
{
}
internal ActivityIdentity(string name, bool isAuthenticated, string authenticationType)
{
this._name = name;
this._isAuthenticated = isAuthenticated;
this._authenticationType = authenticationType;
}
public string Name { get => _name; }
public bool IsAuthenticated { get => _isAuthenticated; }
public string AuthenticationType { get => _authenticationType; }
public static ActivityIdentity Unathenticated => new ActivityIdentity();
}
[JsonObject(MemberSerialization = MemberSerialization.Fields)]
public class ActivityPrincipal : IPrincipal
{
private ActivityIdentity _activityIdentity = null;
private string[] _roles = null;
public ActivityPrincipal() : this(ActivityIdentity.Unathenticated, null)
{
}
public ActivityPrincipal(ActivityIdentity activityIdentity, params string[] roles)
{
_activityIdentity = activityIdentity;
_roles = roles;
}
public ActivityIdentity Identity => _activityIdentity;
IIdentity IPrincipal.Identity => _activityIdentity;
public bool IsInRole(string role)
{
if (_roles != null && _roles.Length > 0)
{
return _roles.Contains(role);
}
return false;
}
}
[JsonObject(MemberSerialization = MemberSerialization.Fields)]
public class ActivityContext : IDisposable, IActivityContext
{
private string _activityID = Guid.NewGuid().ToString();
private DateTime _startDate = DateTime.UtcNow;
private DateTime? _endDate = null;
private ActivityPrincipal _activityPrincipal = null;
public ActivityContext() : this(null)
{
}
public ActivityContext(IPrincipal principal)
{
_activityPrincipal = Convert(principal);
}
private ActivityPrincipal Convert(IPrincipal principal)
{
if (principal == null)
{
return new ActivityPrincipal();
}
var activityPrincipal = principal as ActivityPrincipal;
if (activityPrincipal != null)
{
return activityPrincipal;
}
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal != null)
{
var roles = claimsPrincipal.Claims.Select(x => x.Value);
var p = new ActivityPrincipal(
new ActivityIdentity(claimsPrincipal.Identity.Name, claimsPrincipal.Identity.IsAuthenticated, claimsPrincipal.Identity.AuthenticationType)
, roles.ToArray()
);
return p;
}
throw new NotSupportedException($"Converting {principal.GetType()} not supported");
}
public void Dispose()
{
if (!_endDate.HasValue)
{
_endDate = DateTime.UtcNow;
}
}
public string ActivityID { get => _activityID; }
public DateTime StartDate { get => _startDate; }
public DateTime? EndDate { get => _endDate; }
public IPrincipal User
{
get
{
return _activityPrincipal;
}
}
}
}
Of course, I'll still try to figure out what's wrong with the code. Please if you need another part of the code other from that I posted let me know.
Thanks!

Setting up examples in Swagger

I am using Swashbuckle.AspNetCore.Swagger (1.0.0) and Swashbuckle.AspNetCore.SwaggerGen (1.0.0). I am trying to add default examples to my API following Default model example in Swashbuckle (Swagger). I created a new class file and added,
public class SwaggerDefaultValue : Attribute
{
public string ParameterName { get; set; }
public string Value { get; set; }
public SwaggerDefaultValue(string parameterName, string value)
{
this.ParameterName = parameterName;
this.Value = value;
}
}
public class AddDefaultValues : IOperationFilter
{
public void Apply(Operation operation, DataTypeRegistry dataTypeRegistry, ApiDescription apiDescription)
{
foreach (var param in operation.Parameters)
{
var actionParam = apiDescription.ActionDescriptor.GetParameters().First(p => p.ParameterName == param.Name);
if (actionParam != null)
{
var customAttribute = actionParam.ActionDescriptor.GetCustomAttributes<SwaggerDefaultValue>().FirstOrDefault();
if (customAttribute != null)
{
param.DefaultValue = customAttribute.Value;
}
}
}
}
}
but I get this error - AddDefaultValues does not implement interface member IOperationFilter.Apply(Operation, OperationFilterContext)
That link you are following is not for the Swashbuckle.AspNetCore version
Look in the correct project for the proper examples:
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/search?q=IOperationFilter&unscoped_q=IOperationFilter

Custom model binder with inheritance using Web API and RavenDB

I'm developing a simple web app where I need to bind all types implementing and interface of a specific type. My interface has one single property like this
public interface IContent {
string Id { get;set; }
}
a common class using this interface would look like this
public class Article : IContent {
public string Id { get;set; }
public string Heading { get;set; }
}
to be clean here the article class is just one of many different classes implementing IContent so therefor I need a generic way of storing and updating these types.
So in my controller I have the put method like this
public void Put(string id, [System.Web.Http.ModelBinding.ModelBinder(typeof(ContentModelBinder))] IContent value)
{
// Store the updated object in ravendb
}
and the ContentBinder
public class ContentModelBinder : System.Web.Http.ModelBinding.IModelBinder {
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) {
actionContext.ControllerContext.Request.Content.ReadAsAsync<Article>().ContinueWith(task =>
{
Article model = task.Result;
bindingContext.Model = model;
});
return true;
}
}
The code above does not work because it does not seem to get hold of the Heading property even though if I use the default model binder it binds the Heading correctly.
So, in the BindModel method I guess I need to load the correct object from ravendb based on the Id and then update the complex object using some kind of default model binder or so? This is where I need some help.
Marcus, following is an example which would work fine for both Json and Xml formatter.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using System.Web.Http;
using System.Web.Http.SelfHost;
namespace Service
{
class Service
{
private static HttpSelfHostServer server = null;
private static string baseAddress = string.Format("http://{0}:9095/", Environment.MachineName);
static void Main(string[] args)
{
HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(baseAddress);
config.Routes.MapHttpRoute("Default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
try
{
server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
Console.WriteLine("Service listenting at: {0} ...", baseAddress);
TestWithHttpClient("application/xml");
TestWithHttpClient("application/json");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine("Exception Details:\n{0}", ex.ToString());
}
finally
{
if (server != null)
{
server.CloseAsync().Wait();
}
}
}
private static void TestWithHttpClient(string mediaType)
{
HttpClient client = new HttpClient();
MediaTypeFormatter formatter = null;
// NOTE: following any settings on the following formatters should match
// to the settings that the service's formatters have.
if (mediaType == "application/xml")
{
formatter = new XmlMediaTypeFormatter();
}
else if (mediaType == "application/json")
{
JsonMediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
jsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
formatter = jsonFormatter;
}
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Get;
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
HttpResponseMessage response = client.SendAsync(request).Result;
Student std = response.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("GET data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std))
{
Console.WriteLine("both are equal");
}
client = new HttpClient();
request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Post;
request.Content = new ObjectContent<Person>(StudentsController.CONSTANT_STUDENT, formatter);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
Student std1 = client.SendAsync(request).Result.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("POST and receive data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std1))
{
Console.WriteLine("both are equal");
}
}
}
public class StudentsController : ApiController
{
public static readonly Student CONSTANT_STUDENT = new Student() { Id = 1, Name = "John", EnrolledCourses = new List<string>() { "maths", "physics" } };
public Person Get()
{
return CONSTANT_STUDENT;
}
// NOTE: specifying FromBody here is not required. By default complextypes are bound
// by formatters which read the body
public Person Post([FromBody] Person person)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState));
}
return person;
}
}
[DataContract]
[KnownType(typeof(Student))]
public abstract class Person : IEquatable<Person>
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
// this is ignored
public DateTime DateOfBirth { get; set; }
public bool Equals(Person other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (this.Id != other.Id)
return false;
if (this.Name != other.Name)
return false;
return true;
}
}
[DataContract]
public class Student : Person, IEquatable<Student>
{
[DataMember]
public List<string> EnrolledCourses { get; set; }
public bool Equals(Student other)
{
if (!base.Equals(other))
{
return false;
}
if (this.EnrolledCourses == null && other.EnrolledCourses == null)
{
return true;
}
if ((this.EnrolledCourses == null && other.EnrolledCourses != null) ||
(this.EnrolledCourses != null && other.EnrolledCourses == null))
return false;
if (this.EnrolledCourses.Count != other.EnrolledCourses.Count)
return false;
for (int i = 0; i < this.EnrolledCourses.Count; i++)
{
if (this.EnrolledCourses[i] != other.EnrolledCourses[i])
return false;
}
return true;
}
}
}
I used #kiran-challa solution and added TypeNameHandling on Json media type formatter's SerializerSettings.