I have a number of Get operations in my Api. Currently the swagger document includes the following parameters:
"parameters": [
{
"in": "body",
"name": "body",
"schema": { }
}
],
Is there any way to remove these using an OperationFilter? I've tried the following but the operations do not have any parameters
public class RemoveBodyParametersFilter : IOpenApiOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var parametersToRemove = operation.Parameters.Where(p => p.Name == "body").ToList();
foreach (var parameter in parametersToRemove)
{
operation.Parameters.Remove(parameter);
}
}
}
The endpoint looks like this:
[HttpGet(Name = "GetOperationStatus")]
[SwaggerConsumes("application/json")]
[ProducesResponseType(typeof(DtoBaseMgmtResponse), 200)]
[ProducesResponseType(typeof(DtoBaseMgmtResponse), 202)]
[ProducesResponseType(typeof(DtoBaseMgmtResponse), 400)]
[Metadata("Get an Operation Status", "Get the operation status for the operation ID provided.", VisibilityType.Internal)]
public async Task<IActionResult> GetOperationStatus(string operationId)
Related
I want to return my custom message when take invalid values my model.Therefore I use the Fluentvalidation.Create validators and asyncActionfilter. But Method returned default fluent message instead of my custom message. Why?
My AsyncActionFilter Class
public class ModelValidatorFilterAttribute : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
var errorMessages =
context.ModelState.Values
.Select(
x =>
new ExceptionResponse.ExceptionMessage { Message = x.Errors.First().ErrorMessage })
.ToList();
throw new ValidationException(errorMessages);
}
await next();
}
}
My Validator
public class MyValidator : AbstractValidator<MyRequestDto>
{
public MyValidator()
{
RuleFor(s => s.ProductId).NotEmpty().NotNull()
.WithMessage(x => "my custom message");
}
}
My Startup config
services.AddFluentValidation();
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
Response
{
"exceptionMessages": [
{
"message": "'Product Id' must not be empty.",
"duration": 0,
"type": "Type"
}
]
}
So for my job I need to funnel logs from all our services to a logging api. I decided to use the Serilog.Sinks.Http v8.0.0 to send logs to a asp api service. The configuration of the the services that send to the api are as follows:
LoggerConfiguration config = new LoggerConfiguration()
.MinimumLevel.ControlledBy(loggingLevelSwitch)
.Enrich.FromLogContext()
.Enrich.WithExceptionDetails()
.Enrich.WithMachineName()
.Enrich.WithEnvironmentName()
.WithPdxProperties()
.WriteTo.Http(logAddress, period: TimeSpan.FromMilliseconds(500), textFormatter: new CompactTextFormatter(), queueLimitBytes: null)
.WriteTo.Console();
The controller endpoint is as show below:
[HttpPost]
public void Post([FromBody] LogEvents body)
{
_logger.LogInformation($"Received batch of {LogFields.COUNT_NUM} log events.", body.Events.Length);
foreach (JObject jsonEvent in body.Events)
{
LogEvent logEvent = LogEventReader.ReadFromJObject(jsonEvent);
Serilog.Log.Write(logEvent);
}
}
When I get a log event with an exception in it the jsonEvent will look as follows when converted to json:
{
"#t":"2022-09-01T13:42:41.467405Z",
"#mt":"Unhandled exception.",
"#l":"Error",
"#x":"MassTransit.SendException: Exception of type 'MassTransit.SendException' was thrown.\r\n at Program.<Main>$(String[] args) in C:\\Users\\hostname\\Documents\\Github\\Solution\\Dotnet.Project\\Program.cs:line 73",
"ExceptionDetail":{
"HResult":-2146233088,
"Message":"Exception of type 'MassTransit.SendException' was thrown.",
"Source":"Project.Source",
"TargetSite":"Void MoveNext()",
"MessageType":null,
"Uri":null,
"Type":"MassTransit.SendException"
},
"MachineName":"MACHINE",
"EnvironmentName":"Development",
"PdxAppName":"job-woopwoop",
"PdxClientGroup":"default",
"PdxAppType":"woop"
}
Now this is where I run into difficulty. When the reader gets the json object and turns it into a LogEvent I lose valuable information and it turns into this:
{
"Timestamp":"2022-09-01T13:42:41.467405+00:00",
"Level":4,
"MessageTemplate":{
"Text":"Unhandled exception.",
"Tokens":[
{
"Length":20,
"Text":"Unhandled exception.",
"StartIndex":0
}
]
},
"Properties":{
"ExceptionDetail":{
"TypeTag":null,
"Properties":[
{
"Name":"HResult",
"Value":{
"Value":-2146233088
}
},
{
"Name":"Message",
"Value":{
"Value":"Exception of type 'MassTransit.SendException' was thrown."
}
},
{
"Name":"Source",
"Value":{
"Value":"Project.Source"
}
},
{
"Name":"TargetSite",
"Value":{
"Value":"Void MoveNext()"
}
},
{
"Name":"MessageType",
"Value":{
"Value":null
}
},
{
"Name":"Uri",
"Value":{
"Value":null
}
},
{
"Name":"Type",
"Value":{
"Value":"MassTransit.SendException"
}
}
]
},
"MachineName":{
"Value":"MACHINE"
},
"EnvironmentName":{
"Value":"Development"
},
"PdxAppName":{
"Value":"job-woopwoop"
},
"PdxClientGroup":{
"Value":"default"
},
"PdxAppType":{
"Value":"woop"
}
},
"Exception":{
"Message":"This exception type provides ToString() access to details only.",
"Data":{
},
"InnerException":null,
"HelpLink":null,
"Source":null,
"HResult":-2146233088,
"StackTrace":null
}
}
How can I make it so that the exception object doesnt have the "This exception type provides ToString() access to details only." message and has the correct properties populated like the stack trace?
How to get list of users from UserManager from Microsoft.AspNetCore.Identity using [ScopedService]
Below is what I already tried:
using System.Linq;
using hostapp.Data;
using hostapp.Models;
using HotChocolate;
using HotChocolate.Data;
using Microsoft.AspNetCore.Identity;
namespace hostapp.GraphQL
{
public class Query
{
// 1.
[UseDbContext(typeof(DataContext))]
public IQueryable<AppUser> Users([ScopedService] UserManager<AppUser> userManager)
{
return (IQueryable<AppUser>)userManager.Users;
}
// 2.
[UseDbContext(typeof(DataContext))]
public async Task<List<AppUser>> Users([ScopedService] UserManager<AppUser> userManager)
{
return await userManager.Users.ToListAsync();
}
}
}
Input:
query {
users {
emailConfirmed
}
}
Output:
{
"errors": [
{
"message": "Unexpected Execution Error",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"users"
]
}
],
"data": {
"users": null
}
}
You do not need to use [ScopedService] but rather [Service]
You really only even need to use [ScopedService] in case of a DBContext in combination with UseDbContext.
We will fix this confusion in the next version
I have the following controller:
[Route("api/[controller]")]
public class ReleaseController : BaseController
{
[HttpPut]
public Task<ReleaseModel> UpdateAsync([FromBody] UpdateReleaseForm form, CancellationToken cancellationToken = default)
=> _releaseService.UpdateAsync(form, cancellationToken);
}
The form among other properties has enum property ReleaseStatus:
public class UpdateReleaseForm
{
// other props omitted for brevity
public ReleaseStatus Status { get; set; }
}
I use FluentValidation to create a rule for the Status:
internal sealed class UpdateReleaseFormValidator : AbstractValidator<UpdateReleaseForm>
{
public UpdateReleaseFormValidator()
{
// other rules omitted for brevity
RuleFor(u => u.Status)
.ReleaseStatusValidation();
}
}
public static class RuleBuildersExtensions
{
public static IRuleBuilderOptions<T, ReleaseStatus> ReleaseStatusValidation<T>(
this IRuleBuilder<T, ReleaseStatus> rule)
{
return rule.
IsInEnum()
.WithMessage(
string.Format("Status should be one of the following values: `{0}`",
string.Join(", ", Enum.GetNames(typeof(ReleaseStatus)))));
}
}
The problem is that when a wrong Status passed in to the action method, I get the error:
{
"errors": {
"status": [
"Error converting value \"WrongStatus\" to type '...ReleaseStatus'. Path 'Status', line 4, position 28."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|3405bc61-4f67bebc131b8dc8."
}
I want to get my custom message defined in validation rule instead.
I tried to use NullObjectModelValidator from disable-validation.
Also I tried this:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
But get the following:
System.ArgumentNullException: Cannot pass null model to Validate.
(Parameter 'instanceToValidate')
Just an idea, but you could try to make the UpdateReleaseForm form parameter in the UpdateAsync method nullable:
[Route("api/[controller]")]
public class ReleaseController : BaseController
{
[HttpPut]
public Task<ReleaseModel> UpdateAsync([FromBody] UpdateReleaseForm? form, CancellationToken cancellationToken = default)
=> _releaseService.UpdateAsync(form, cancellationToken);
}
Maybe that will allow the parameter to be null.
This is basically the same question as How do I set or remove the Default Response Content Type Using SwashBuckle, but for .NET Core 3.0
By default in .NET Core 3.0, you configure a web api with services.AddControllers() and then you configure swagger with swashbuckle with services.AddSwaggerGen() + app.UseSwagger()
That is working fine, however the swagger.json contains multiple response content types for every operation (text/plain + application/json + text/json)
I know I can restrict these response content types by adding [Produces] and [Consumes] to my operations, but I'd like to avoid that for each and every operation (i.e I want to do that globally)
Please note that I preferably want to use System.Text.Json but if you have a solution that works only with Newtonsoft.JSON then it's better than nothing ;)
Swashbuckle.AspNetCore.SwaggerGen 5.0 uses the OpenApiOperation to describe API operations.
using System.Collections.Generic;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
public class AssignContentTypeFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Responses.ContainsKey("200"))
{
operation.Responses.Clear();
}
var data = new OpenApiResponse
{
Description = "Ok",
Content = new Dictionary<string, OpenApiMediaType>
{
["application/json"] = new OpenApiMediaType(),
["application/xml"] = new OpenApiMediaType(),
}
};
operation.Responses.Add("200", data);
}
}
In Startup.cs
services.AddSwaggerGen(q =>
{
q.SwaggerDoc("v1", new OpenApiInfo
{
Title = "mytitle",
Version = "v1",
});
q.OperationFilter<AssignContentTypeFilter>();
});
You can create custom filter for swagger
internal class AssignContentTypeFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
operation.Consumes.Clear();
operation.Consumes.Add("application/json");
operation.Produces.Clear();
operation.Produces.Add("application/json");
}
}
then
services.AddSwaggerGen(cfg => cfg.OperationFilter<AssignContentTypeFilter>());
This is what worked for me in Swashbuckle.AspNetCore.SwaggerGen 5.0:
using System.Collections.Generic;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
internal class ContentTypeOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.RequestBody == null)
{
return;
}
operation.RequestBody.Content = new Dictionary<string, OpenApiMediaType>
{
{ "application/json", new OpenApiMediaType() }
};
foreach (var response in operation.Responses)
{
response.Value.Content = new Dictionary<string, OpenApiMediaType>
{
{ "application/json", new OpenApiMediaType() }
};
}
}
}
Startup.cs (modified from sjokkogutten's answer):
services.AddSwaggerGen(q =>
{
q.SwaggerDoc("v1", new OpenApiInfo
{
Title = "mytitle",
Version = "v1",
});
q.OperationFilter<ContentTypeOperationFilter>();
});
The text/plain ends up in the generated swagger/openapi spec because by default API controllers have a StringOutputFormatter (from the Microsoft.AspNetCore.Mvc.Formatters namespace) available.
This MSDN goes into detail, but the crux of it is that by doing:
services.AddControllers(options =>
{
options.OutputFormatters.RemoveType<StringOutputFormatter>();
});
..(probably in your Startup/ConfigureServices) you remove the relevant formatter and text/plain no longer appears in the generated swagger doc
Note: remember to install/import Microsoft.AspNetCore.Mvc.Formatters
Version for Swashbuckle.AspNetCore.SwaggerGen 6+. Removes only text/plain content type. Works for string result in my case.
internal class RemoveTextContentOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
foreach (var (_, response) in operation.Responses)
{
if (response.Content.ContainsKey("text/plain"))
response.Content.Remove("text/plain");
}
}
}
Generated swagger for string operation:
"/api/news/{newsArticleId}/file-link": {
"get": {
"tags": [
"NewsApi"
],
"operationId": "NewsApi_GetUploadFileLink",
"parameters": [
{
"name": "newsArticleId",
"in": "path",
"required": true,
"schema": {
"type": "integer",
"format": "int32"
}
}
],
"responses": {
"500": {
"description": "Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
},
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "string"
}
},
"text/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
},