Filtering schema out of NSwag swagger - asp.net-web-api

I have an ASP.NET full framework application with API endpoints. I am using NSwag to generate a swagger document. This all works.
I need to generate a document for only a small subset of the endpoints. The paths are filtered, but the schema is not. How do I filter the schema objects to match the path filtering?
Example:
I have this filter
public class IncludeControllersInSwagger : IOperationProcessor
{
public Task<bool> ProcessAsync(OperationProcessorContext context)
{
return Task.FromResult(
context.ControllerType == typeof(ControllerA));
}
}
And this at startup:
settings.GeneratorSettings.OperationProcessors.Add(new IncludeControllersInSwagger());
The controllers are:
public class AResponse
{
public string Message { get; set; }
public bool Flag { get; set; }
}
public class BResponse
{
public string Message { get; set; }
public int Count { get; set; }
}
[Route("a")]
public class ControllerA : ApiController
{
[HttpGet]
public AResponse Get()
{
return new AResponse
{
Message = "Hello from A",
Flag = true
};
}
}
[Route("b")]
public class ControllerB : ApiController
{
[HttpGet]
public BResponse Get()
{
return new BResponse
{
Message = "Hello from B",
Count = 42
};
}
}
Then the generated swagger contains just one path:
"paths": {
"/a": {
"get": { .. etc
}
}
And that's all, this is correct.
But the schemas contains both:
"schemas": {
"AResponse": {
"type": "object",
"additionalProperties": false,
etc
},
"BResponse": {
"type": "object",
"additionalProperties": false,
etc
}
}
}
The BResponse type should not be there. How do you remove it?
This extra data makes the Schemas section extremely verbose and unsuitable for public documentation in the case where there are over 10 endpoints, and only 2 are exposed via a gateway and therefor documented in swagger.
There is a ISchemaProcessor but it does not return a Boolean like the IOperationProcessor.

Have you tried to add the operation filter as first element?
i.e. OperationProcessors.Insert(0, new IncludeControllersInSwagger())
I think this is important as it will filter out the operation before the dto schemas are generated and added to the document.

This is not an answer to your problem, as you already got an answer that seems to work. I do have a suggestion however. Instead of checking the type of controller in your processor, I would suggest to create a custom attribute:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class IncludeInSwaggerAttribute : Attribute
{
}
Then change your processor to look for this attribute:
public class IncludeInSwaggerOperationProcessor : IOperationProcessor
{
public async Task<bool> ProcessAsync(OperationProcessorContext context)
{
return context.ControllerType.GetCustomAttributes<IncludeInSwaggerAttribute>().Any() ||
context.MethodInfo.GetCustomAttributes<IncludeInSwaggerAttribute>().Any();
}
}
This way, you can add the attribute to any new controller you want to include in swagger without having to change your processor. You can also add the attribute on a single action to only include that action, leaving the rest of the actions in the controller out of swagger.

Related

GraphQL Generics Unexpected Value Type Error

I can't figure out how to make the GraphQL types happy with my Generics plan. I am trying to have a response wrapper for all my query and mutation definitions.
Everything was working fine with my query definition using PaginationType and my service returning the Pagination model and all was good. I added the ResponseType wrapper and now I get the conflict.
Expected value of type "ABC.GraphQL.Framework.DTO.ResponseType1[ABC.GraphQL.Framework.Types.Object.PaginationType]\" for \"ResponseType\" but got: ABC.GraphQL.Framework.DTO.Response1[ABC.GraphQL.Framework.DTO.Pagination].",
FieldAsync<ResponseType<PaginationType>>(
"PaginationSearch",
"Returns paginated groups for specified search terms",
arguments: new QueryArguments(...),
resolve: async context => {
return await Service.GetPagination(...);
}
);
public class PaginationType : ObjectGraphType<Pagination>
{
public PaginationType()
{
Field(x => x.totalRecords, nullable: true).Description("Total Records");
....
}
}
public class ResponseType<T> : ObjectGraphType<Response<T>>
{
public ResponseType()
{
Name = "ResponseType";
Field(x => x.success, nullable: true).Description("Operation Success");
Field(x => x.message, nullable: true).Description("Operation Message");
Field(x => x.response, nullable: true, typeof(T)).Description("Operation Response");
}
}
Of course the plain backing models
public class Pagination
{
public int totalRecords { get; set; }
.....
}
public class Response<T>
{
public bool success { get; set; }
public string message { get; set; }
public T response { get; set; }
}
Now my service class returns the plain objects and this has been working to this point so not sure why adding the Response wrapper is now breaking it.
public async Task<Response<Pagination>> GetPagination(...)
{...}
I debugged into the GraphQL library and found it is by design. So I begun digging into to why and found this:
https://github.com/graphql-dotnet/graphql-dotnet/issues/2279

swagger swashbuckle does not support nested class as action method parameter

I am using asp.net 5
I have two model class, which are nested, both of the inner class are named Command
public class EditModel
{
public class Command
{
public int Id { get; set; }
public string Info { get; set; }
}
}
and
public class CreateModel
{
public class Command
{
public int Id { get; set; }
public string Info { get; set; }
}
}
In my Controller class has two methods
[HttpPost]
public IActionResult PutData(CreateModel.Command model)
{
return Ok();
}
[HttpPut]
public IActionResult PostData(EditModel.Command model)
{
return Ok();
}
Since for both Put and Post's query I am using nested class both name Command, Swagger will return the following error
An unhandled exception has occurred while executing the request.
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Conflicting method/path combination "PUT Test" for actions -
TestSwagger.Controllers.TestController.PutData
(TestSwagger),TestSwagger.Controllers.TestController.PostData
(TestSwagger). Actions require a unique method/path combination for
Swagger/OpenAPI 3.0. Use ConflictingActionsResolver as a workaround
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable1 apiDescriptions, SchemaRepository schemaRepository) at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable1
apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String
documentName, String host, String basePath)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext
httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext
context)
Swagger will work, if I change one of the Command model name to something different.
Yet, I believe this nested class model name is legit and should work with swagger also. If there a way to work around this. Thanks
By adding c.CustomSchemaIds(x => x.FullName);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "TestSwagger", Version = "v1" });
c.CustomSchemaIds(x => x.FullName);
});
solved the schemaId conflict. Thanks to this question

Conditional validation based on request route with asp.net core 2.2 and FluentValidation

So Basically i wrote a validator for my class with FluentValidation and also a filter to do the validation task for me in my webAPI project, so far it's OK but assume that my User class has firstname,lastname,email,password properties
and i have two routes (one for register and the other one for login)
and as you might have noticed required properties are different on these route.
Thus,should I really need to write individual validation for each and every action i have?because this makes a lot of code code duplication and it's hard to change.is there any way to just add required condition based on the request coming with single validation class?
Any suggestion???
A better practice would be to use a factory pattern for your validations and use a an action filter to short circuit bad requests. You could validate any action argument(Headers, Request Bodies, etc..) with something like this.
public class TestValidationAttribute : Attribute, IActionFilter
{
private string _requestModelName;
public TestValidationAttribute(string requestModelName)
{
_requestModelName = requestModelName;
}
public void OnActionExecuting(ActionExecutingContext context)
{
// using Microsoft.Extensions.DependencyInjection;
var services = context.HttpContext.RequestServices;
var accessor = services.GetService<IHttpContextAccessor>();
var factory = services.GetService<ITestValidatorFactory>();
var tokens = accessor.HttpContext.GetRouteData().DataTokens;
if (!tokens.TryGetValue("RouteName", out var routeNameObj))
{
throw new Exception($"Action doesn't have a named route.");
}
var routeName = routeNameObj.ToString();
var validator = factory.Create(routeName);
if (!context.ActionArguments.TryGetValue(_requestModelName, out var model))
{
throw new Exception($"Action doesn't have argument named {_requestModelName}.");
}
TestModel test;
try
{
test = (TestModel) model;
}
catch (InvalidCastException)
{
throw new Exception($"Action argument can't be casted to {nameof(TestModel)}.");
}
var validation = validator.Validate(test);
if (!validation.Successful)
{
context.Result = new BadRequestObjectResult(validation.ResponseModel);
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
public class TestController : Controller
{
[HttpPost]
[Route("Test/{id}", Name = "TestGet")]
[TestValidation("model")]
public IActionResult Test(TestModel model)
{
return Ok();
}
}
public class ValidationResult
{
public bool Successful { get; }
public ResponseModel ResponseModel { get; }
}
public class TestModel
{
}
public interface ITestValidator
{
ValidationResult Validate(TestModel model);
}
public interface ITestValidatorFactory
{
ITestValidator Create(string routeName);
}

NHibernate HasManyToMany automapping not populating list on load

I've got a many-to-many relationship set up via AutoMapping. Now, the save and updates work fine as expected, however the DiscountGroups are not being loaded in DiscountDay when I get the entities afterwards. I cannot for the life of me work out why the _discountGroups list is always empty, even though it's all correct in the database.
I've seen suggestions about using ISet rather than IList however it doesn't seem to make any difference in my case, neiter does using
.Not.LazyLoad()
in the mapping. Removing AsBag() and AsSet() also makes no difference.
The Entities
public class DiscountDay
{
public virtual DayOfWeek DayOfWeek { get; set; }
public virtual Discount Discount { get; set; }
private readonly IList<DiscountGroup> _discountGroups = new List<DiscountGroup>();
public virtual IEnumerable<DiscountGroup> DiscountGroups
{
get { return _discountGroups; }
set { }
}
}
public class DiscountGroup
{
public virtual string Name { get; set; }
private readonly IList<DiscountDay> _discountDay = new List<DiscountDay>();
public virtual IEnumerable<DiscountDay> DiscountDay
{
get { return _discountDay; }
}
}
The Mappings
public class DiscountDayOverride : IAutoMappingOverride<DiscountDay>
{
public void Override(AutoMapping<DiscountDay> mapping)
{
mapping.HasManyToMany( x => x.DiscountGroups )
.AsSet()
.Cascade
.SaveUpdate();
mapping.Cache.ReadWrite();
}
}
public class DiscountGroupOverride : IAutoMappingOverride<DiscountGroup>
{
public void Override(AutoMapping<DiscountGroup> mapping)
{
mapping.HasManyToMany( x => x.DiscountDay )
.AsBag()
.Inverse();
mapping.Cache.ReadWrite();
}
}
Well, I'm a complete and utter numpty. The empty set on DiscountGroups on the DiscountDay entity was causing the issue. For some reason I'd glossed over it and just didn't think that an empty set would do anything.
Updated code:
public virtual IEnumerable<DiscountGroup> DiscountGroups
{
get { return _discountGroups; }
}

Repository Pattern and Azure Table Storage(???)

While doing the following simple example, I found the following difficulties
As the title says, I am intending to use the Repository pattern while I am storing data in the Azure table storage.now I have couple of classes, Repository.cs, IRepository.cs, DataContext.cs and the Controller.
During my reading I found some info and been doing as follows.
IRepository.cs
public interface IRepository<T> where T: TableServiceEntity
{
T GetById(int Id);
IQueryable<T> GetAll();
}
and the DataContext.cs
public class DataContext<T>:TableServiceContext where T:TableServiceEntity
{
public DataContext(CloudStorageAccount storageaccount, StorageCredentials credentials)
: base(storageaccount.TableEndpoint.AbsoluteUri, credentials)
{
// _storageAccount = storageaccount;
var storageAccount = CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue(KEY_STORAGE));
storageAccount.CreateCloudTableClient().CreateTableIfNotExist(tableName);
}
public IQueryable<T> DeviceTable
{
get { return CreateQuery<T>(tableName); }
}
}
plus some part of the controller(I have already data in the table which I created before)
public class DeviceMeController : Controller
{
private IRepository<Entity>_repository;
public Controller() : this(new Repository<Entity>())
{
}
public Controller(IRepository<Entity> repository)
{
_repository = repository;
}
public ActionResult Index()
{
var List = _repository.GetAll();
return View(deviceList);
}
and the the Implementation of the interface Reposistory.cs, here is where I have an error and got lost somewhere
public class Repository<T>:IRepository<T> where T:TableServiceEntity
{
private DataContext<T> _serviceContext;
// here get tablename as pararameter;
// so the Enities call this function
public Repository()
{
// the same context for multiple tables ?
}
// perhaps it should take the table Name
public void Add(T item)
{
_serviceContext.AddObject(TableName,item);
_serviceContext.SaveChangesWithRetries();
}
public IQueryable<T> GetAll()
{
var results = from c in _serviceContext.Table
select c;
return results;
Error is about the null reference, the debugger shows the variable results is null?
In the end I need to know few things.
what should I do in the Repository.cs constructor? I believe the Datacontext.cs class has to be in a separate class ...
any Hint here
Hy,
first of all I presume you left out some code, because I don't see how you get your context in your repository. But supposing you do set it correctly, (injection?) taking into account the way you desinged your datacontext the repository doesn't need to know the table name because it is set in the following lines of code:
public IQueryable<T> DeviceTable
{
get { return CreateQuery<T>(Constants.DeviceTableName); }
}
So when you create a query based on the IQueryable DeviceTable, the table name is already set.
The thing is I don't see the need for your context class, especially as it can only bring over a single entity type (it is generic and based on an entity).
A basic layout of my Repository for Azure Table Storage is:
public abstract class CloudRepository<TEntity> : ICloudRepository<TEntity>
{
private TableServiceContext _tableServiceContext;
private string _tableName;
public string TableName
{
get { return _tableName ?? ( _tableName = typeof(TEntity).Name.Replace("Entity", string.Empty).ToLower()); }
}
public CloudStorageAccount StorageAccount
{
get
{
return CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue("StorageConnectionString"));
}
}
public CloudTableClient TableClient
{
get
{
CloudTableClient cloudTableClient = StorageAccount.CreateCloudTableClient();
cloudTableClient.CreateTableIfNotExist(TableName);
return cloudTableClient;
}
}
public TableServiceContext ServiceContext
{
get
{
return _tableServiceContext ?? (_tableServiceContext = TableClient.GetDataServiceContext());
}
}
public IEnumerable<TEntity> FindAll()
{
return ServiceContext.CreateQuery<TEntity>(TableName).ToList();
}
}
Hope this helps you.

Resources