Trying to make a flexible way for documentation in .net core swagger when using shared code between services.
Please have a look at this example:
public abstract class Err
{
/// <summary>
/// The error x
/// </summary>
public abstract string Code { get; }
}
public class Err1 : Err
{
public override string Code { get => "100"; }
}
public class Err2 : Err
{
public override string Code { get => "200"; }
}
public class Err3 : Err
{
public override string Code { get => "300"; }
}
[Route("api/test")]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Err), StatusCodes.Status400BadRequest)]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet]
public ActionResult Get()
{
int i = (new Random().Next(1, 11));
if (1 < 4)
return BadRequest(new Err1());
if (i < 7)
return BadRequest(new Err2());
return Ok("OK");
}
}
This will of course produce a swagger looking something like this:
What I would like to have instead is something like this:
So the important thing here is that the code=300 is NOT included in the summary, because it is not being used in this assembly.
So what I've been thinking of here is;
Is it possible to do some reflection, find all inherited classes being used and write that to the XML (while compiling?) instead of the ordinary summary part?
Override how Swagger read the summary and write your own code.
Can you hook up some Swagger middleware to handle this so it does not read from the XML?
If the reflection part is not possible, of course the next best thing would be in some way to hardcode a list of all the Error-classes being used for this particular services.
Any ideas here guys?
So,
for the first part, what I understand it is not possible to find out if a class is used or not via reflection :(
And for the second part, if you replace the swashbuckle with nswag you are actually able to have this nice "one of" -feature in the swagger UI :) But in my case that is not enough so I guess this is not possible right now.
Related
I have controllers which, for the sake of backwards compatibility, only have one action. The JSON request comes with an attribute "type" which determines what the action should do with it.
My idea for a clean solution was to build a set of action handlers. They all inherit from an abstract class called ActionHandler which has two methods
public abstract bool CanHandle(ClientRequest request);
and
public abstract object Handle(dynamic request)
And it has a property
public abstract string ActionForController { get; }
in which the specific actionhandlers just return the name of the controller they want to handle for. This is not very important, but may help clarify something later.
The controller is inserted with an ActionHandlerRegister which has an IEnumerable and a method "GetActionHandler". It returns the first specific ActionHandler that can handle the request.
public ActionHandler GetActionHandler(ClientRequest request)
{
foreach(var actionHandler in ActionHandlers)
{
if (actionHandler.CanHandle(request))
{
return actionHandler;
}
}
throw new BadRequestException(string.Format(CultureInfo.InvariantCulture, BadRequestExceptionTemplate, request.Type));
}
The controllers look like this:
public class LogController : ControllerBase
{
private readonly IActionHandlerRegister<LogController> logHandlers;
public LogController(IActionHandlerRegister<LogController> logHandlers)
{
this.logHandlers = logHandlers ?? throw new ArgumentNullException(nameof(logHandlers));
}
[HttpPost]
public async Task<IActionResult> Post([FromBody] dynamic rawJson)
{
var jsonBody = ((JsonElement)rawJson).ToString();
if (string.IsNullOrEmpty(jsonBody))
{
return BadRequest(ActionHandler.BadRequestRequestNullOrTypeMissingError);
}
var clientRequest = JsonSerializer.Deserialize<ClientRequest>(jsonBody);
if (clientRequest == null || string.IsNullOrEmpty(clientRequest.Type))
{
return BadRequest(ActionHandler.BadRequestRequestNullOrTypeMissingError);
}
try
{
var handler = logHandlers.GetActionHandler(clientRequest);
var result = handler.Handle(rawJson);
return Ok(result);
}
catch (BadRequestException ex)
{
return BadRequest(ex.Message);
}
}
}
For people paying attention: yes, I'm passing the rawjson to handler.Handle. This is because "ClientRequest" is something generic (from which I can read the type) but the handler needs the specific request, so it's deserializing again to something more specific. Maybe there are better solutions for that. Feel free to tell me.
In startup.cs, the insertion of the ActionHandlerRegister into the controller looks like this:
public void RegisterActionHandlersAsSingleton(IServiceCollection services)
{
IEnumerable<ActionHandler> listOfActionHandlers =
from domainAssembly in AppDomain.CurrentDomain.GetAssemblies()
from actionHandlerType in domainAssembly.GetTypes()
where actionHandlerType.IsAssignableFrom(typeof(ActionHandler))
select (ActionHandler)Activator.CreateInstance(actionHandlerType);
services.AddSingleton<IActionHandlerRegister<LogController>>(new ActionHandlerRegister<LogController>(listOfActionHandlers.Where(a => a.ActionForController == nameof(LogController))));
// other controllers ...
}
You might be able to guess, this last piece of code crashes at runtime telling me it's unable to cast to ActionHandler.
System.InvalidCastException: Unable to cast object of type
'System.Object' to type
'TcServerModules.ActionHandlers.ActionHandler'.
I have been playing around with different solutions, but none of them scratch that itch. What would be a nice, true-to OO-design principle
Im trying to learn .net 3.1 by building a small test webapi and currently my objective is to validate dtos with fluentvalidation and in case it fails, present a custom json to the caller. The problems i have found and cant get over are two;
i cant seem to get the messages i write via fluentvalidation (they always are the - i assume .net core default ones)
i cant seem to modify the object type that is json-ified and then output to the caller.
My code is as follows:
1. The Controller
[ApiController]
[Route("[controller]")]
public class AdminController : ControllerBase
{
[HttpPost]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status202Accepted)]
public async Task<IActionResult> RegisterAccount(NewAccountInput dto)
{
return Ok();
}
}
2. The Dto and the custom validator
public class NewAccountInput
{
public string Username { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public AccountType Type { get; set; }
}
public class NewAccountInputValidator : AbstractValidator<NewAccountInput>
{
public NewAccountInputValidator()
{
RuleFor(o => o.Email).NotNull().NotEmpty().WithMessage("Email vazio");
RuleFor(o => o.Username).NotNull().NotEmpty().WithMessage("Username vazio");
}
}
3. The Filter im using for validation
public class ApiValidationFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
//the only output i want are the error descriptions, nothing else
var data = context.ModelState
.Values
.SelectMany(v => v.Errors.Select(b => b.ErrorMessage))
.ToList();
context.Result = new JsonResult(data) { StatusCode = 400 };
}
//base.OnActionExecuting(context);
}
}
finally, my configureservices
public void ConfigureServices(IServiceCollection services)
{
services
//tried both lines and it doesnt seem to work either way
.AddScoped<ApiValidationFilter>()
.AddControllers(//config=>
//config.Filters.Add(new ApiValidationFilter())
)
.AddFluentValidation(fv => {
fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;//i was hoping this did the trick
fv.RegisterValidatorsFromAssemblyContaining<NewAccountInputValidator>();
});
}
Now, trying this with postman i get the result
which highlights both issues im having atm
This was done with asp.net core 3.15 and visualstudio 16.6.3
The message you are seeing is in fact coming from FluentValidation - see the source.
The reason you aren't seeing the custom message you are providing is that FluentValidation will show the validation message from the first validator that fails in the chain, in this case NotNull.
This question gives some options for specifying a single custom validation message for an entire chain of validators.
In this case the Action Filter you describe is never being hit, as the validation is failing first. To prevent this you can use:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
which will stop the automatic return of BadRequest for an invalid model. This question provides some alternative solutions, including configuring an InvalidModelStateResponseFactory to do what you require.
I have a custom tag defined in my Hook.cs file like
[BeforeScenario("AfterUpgradeTag")]
public void BeforeScenarioAfterUpgrade()
{
// Code execution here
}
What I want to do is I want to change its method definition like
[BeforeScenario("AfterUpgradeTag")]
public void BeforeScenarioAfterUpgrade(bool flag)
{
if(flag)
// Code execution here
else
//Do a clean up
}
And I want to use this in feature file as something like
#AfterUpgradeTag(bool val = false)
I have searched alot for this. I want to know is this possible using Specflow or if there are any alternatives
I am not sure if you can pass parameters like that in feature file but you can utilize tags to achieve your goal
In feature file do this
#upgrade #false
Scenario: testing upgrade
In binding class
public static ScenarioContext _scenarioContext;
and binding class constructor
public BindingClass(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}
and your BeforeScenario method is defined like this in the class BindingClass
[BeforeScenario("upgrade")]
public void BeforeScenarioUpgradeFalseorTrue()
{
if (BindingClass._scenarioContext.ScenarioInfo.Tags.Contains("false"))
{
log.Info("upgrade is false..");
}
if (BindingClass._scenarioContext.ScenarioInfo.Tags.Contains("true"))
{
log.Info("upgrade is true..");
}
}
when you want to pass true in feature file just do
#upgrade #true
Scenario: testing upgrade
You can follow the documentation from specflow to achieve this.
My ASP.NET Core application is using our self-designed pipelines to process requests. Every pipeline contains 1+ blocks, and the number of blocks have no any limit. it can be up to 200+ blocks in real instance, the pipeline will go through all blocks by a sequence from a configuration, like:
Pipeline<DoActionsPipeline>().AddBlock<DoActionAddUserBlock>().AddBlock<DoActionAddUserToRoleBlock>()...
Like above example(just an example), and there are 200+ blocks configured in this pipeline, the blocks could be DoActionAddUserBlock, DoActionAddUserToRoleBlock, DoActionAddAddressToUserBlock, and so on. many actions are mixed in one pipeline. (Please don't ask why mix them, it's just an example, it doesn't matter to my question.)
For this example, in each block, we will check the action name first, if match, then run logics. but this is pretty bad, it has to instance all blocks and go throgh all of them to get a request done.
Here is sample code, not very good, but it shows my pain:
public class DoActionAddUserBlock : BaseBlock<User, User, Context>
{
public override User Execute(User arg, Context context)
{
if (context.ActionName != "AddUser")
{
return arg;
}
return AddUser(arg);
}
protected User AddUser(User user)
{
return user;
}
}
public abstract class BaseBlock<TArg, TResult, TContext>
{
public abstract TResult Execute(TArg arg, TContext context);
}
public class Context
{
public string ActionName { get; set; }
}
public class User
{
}
I want to avoid instancing blocks by conditions, I think it should be in pipeline-configuration level. how can I reach this? Attributes? or something others.
[Condition("Action==AddUser")] // or [Action("AddUser")] // or [TypeOfArg("User")]
public class DoActionAddUserBlock : BaseBlock<User, User, Context>
{
public override User Execute(User arg, Context context)
{
return AddUser(arg);
}
//...
}
Please show us the Pipeline<T>() method (is a method or a class?), because it's essential for an accurate answer.
Anyway i want to try my best with the current infos.
Your goal is "i want to conditionally instance blocks", so you have to move your condition in a out-of-instance context, something you can do with attributes:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ActionNameAttribute : Attribute
{
public ActionNameAttribute(string name)
{
this.Name = name;
}
public string Name { get; }
}
[ActionName(nameof(AddUser))]
public class DoActionAddUserBlock : BaseBlock<User, User, Context>
{
public override User Execute(User arg, Context context)
{
return AddUser(arg);
}
}
Then, do the check into the .AddBlock<T>() method (that, i guess, is something like that):
public YourUnknownType<T> AddBlock<TBlock>()
{
var type = typeof(TBlock);
var attributes = attributes.GetCustomAttributes(typeof(ActionNameAttribute), inherit: true); // or false if you don't need inheritation
var attribute = attributes.FirstOrDefault() as ActionNameAttribute;
if (attribute.Name == this.Context.ActioName)
{
// place here the block init
}
return AnythingYouActuallyReturn();
}
Hope this helps!
IMO
you should define different pipelines for different usage. That's a design pattern that should be used only for some particular cases. Maybe it is not good pattern in your case?
I think that it shouldn't be in pipeline responsibility to check the action name and MAYBE run logic. If you define a pipeline for some logic it should just "go with the flow".
Therefore, pipelines should be build once on project startup and initializing whole pipeline just once is good.
Please think about if using pipelines is good in your scenario.
I've built a simple pipeline with builder and steps you can check it here. It's in polish but all the code is in English so you might get the point.
I'm trying to implement custom paging with the EntitySetController.
public class MyController : EntitySetController<Poco, int>
{
public IQueryable<Poco> Get()
{
var result = _myBusinessLogic.Search(QueryOptions.Top.Value);
return result.AsQueryable()
}
}
I think I'm missing something because it looks like the controller is trying to apply paging to the results of the Search method that already returns just one page. How can I prevent it from doing that and apply paging myself?
It looks like I can just inherit from ODataController instead and implement:
public IEnumerable<Poco> Get(ODataQueryOptions odataQueryOptions)
but I was wondering if I can stay with EntitySetController so that there's less plumbing code to write.
I wanted to stick to the OData format and not return PageResult<>
You can only take full control of the query using ODataQueryOptions or let the framework handle it completely for you using QueryableAttribute. There is no middle ground unfortunately.
So, I think doing an ODataController is the right way to solve this now.
That said, I can suggest a dirty workaround that would work for the time being. Beware that this relies on internal implementations that might/will change and break you.
public class MyController : EntitySetController<Poco, int>
{
public IQueryable<Poco> Get()
{
var result = _myBusinessLogic.Search(QueryOptions.Top.Value);
RemoveQueryString(Request, "$top");
return result.AsQueryable()
}
// This method relies that code that looks for query strings uses the extension
// method Request.GetQueryNameValuePairs that relies on cached implementation to
// not parse request uri again and again.
public static void RemoveQueryString(HttpRequestMessage request, string name)
{
request.Properties[HttpPropertyKeys.RequestQueryNameValuePairsKey] = request.GetQueryNameValuePairs().Where(kvp => kvp.Key != name);
}
}