Giter Site home page Giter Site logo

ardalis / apiendpoints Goto Github PK

View Code? Open in Web Editor NEW
3.0K 58.0 217.0 393 KB

A project for supporting API Endpoints in ASP.NET Core web applications.

License: MIT License

C# 95.12% PowerShell 4.88%
api asp-net-core csharp dotnet dotnet-core endpoints hacktoberfest web-api

apiendpoints's Introduction

Hi there ๐Ÿ‘‹

Want to help? Here are my "Good First Issue" issues.

ardalis

ardalis

Visitors since 11 Nov 2020

apiendpoints's People

Contributors

andrew-hillier avatar ardalis avatar azaferany avatar cmxl avatar davidhenley avatar dependabot-preview[bot] avatar dependabot[bot] avatar hawxy avatar henriksen avatar idormenco avatar ilyanadev avatar jasdefer avatar jeastham1993 avatar joeymckenzie avatar kylemcmaster avatar maxkoshevoi avatar mikesigs avatar paulvanbladel avatar ppittle avatar rafaelbm avatar reapism avatar scottsauber avatar sdepouw avatar shojy avatar smithgeek avatar swizkon avatar szlatkow avatar tbalasavage avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

apiendpoints's Issues

Add Custom Response for File Download Endpoints

Hi Ardalis, please consider adding a custom return type for File types
public abstract class WithResponseCustoms : BaseEndpointAsync { public abstract Task HandleAsync( TRequest request, CancellationToken cancellationToken = default ); }

from #77 (comment)

Add async support

BaseEndpoint should support Task based options because many people use async tasks for IO.

Add security and versioning dependency alerts

  • Dependabot alerts on updates for the project's dated and vulnerable dependencies and automatically creates PRs to easily update them

Pipeline Foundation is a non-profit initiative with the sole purpose of giving back to the IT community by assisting OSS projects with DevOps implementations and best practices.

Built with โค by Pipeline Foundation.

Attribute routes with the same name 'HandleAsync' must have the same template

  • NuGet Package Version: 3.1.0
  • .NET SDK Version: .NET Core 3.1

Steps to Reproduce:

  1. Create a new endpoint List
  2. Create a new endpoint Get

List.cs

/// <summary>
/// Customer List ApiEndpoint
/// </summary>
[Authorize]
[Route("api/v{version:apiVersion}/customers")]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public class List : BaseAsyncEndpoint
    .WithoutRequest
    .WithResponse<IList<CustomerResponse>>
{
    private readonly ICustomerRepository _customerRepository;
    private readonly IMapper _mapper;

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="customerRepository"></param>
    /// <param name="mapper"></param>
    public List(ICustomerRepository customerRepository,
        IMapper mapper)
    {
        _customerRepository = customerRepository ?? throw new ArgumentNullException("ICustomerRepository can't be null.");
        _mapper = mapper ?? throw new ArgumentNullException("IMapper can't be null.");
    }

    /// <summary>
    /// Get a list of customers
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns>A list of customer objects</returns>
    [HttpGet(Name = nameof(List.HandleAsync))]
    [Authorize(Policy = "ApiCustomersRead")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public override async Task<ActionResult<IList<CustomerResponse>>> 
        HandleAsync(CancellationToken cancellationToken = default)
    {
        var result = await _customerRepository.GetAsync();

        return _mapper.Map<List<CustomerResponse>>(result);
    }
}

Get.cs

/// <summary>
/// Customer Get ApiEndpoint
/// </summary>
[Authorize]
[Route("api/v{version:apiVersion}/customers")]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public class Get : BaseAsyncEndpoint
    .WithRequest<Guid>
    .WithResponse<CustomerDetailResponse>
{
    private readonly ICustomerRepository _customerRepository;
    private readonly IMapper _mapper;

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="customerRepository"></param>
    /// <param name="mapper"></param>
    public Get(ICustomerRepository customerRepository,
        IMapper mapper)
    {
        _customerRepository = customerRepository ?? throw new ArgumentNullException("ICustomerRepository can't be null.");
        _mapper = mapper ?? throw new ArgumentNullException("IMapper can't be null.");
    }

    /// <summary>
    /// Get a customer
    /// </summary>
    /// <param name="customerId">The unique identifier of the customer</param>
    /// <param name="cancellationToken"></param>
    /// <returns>A customer object</returns>
    [HttpGet("{customerId:guid}", Name = nameof(Get.HandleAsync))]
    [Authorize(Policy = "ApiCustomersRead")]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public override async Task<ActionResult<CustomerDetailResponse>> 
        HandleAsync(Guid customerId, CancellationToken cancellationToken = default)
    {
        var result = await _customerRepository.GetByKeyAsync(customerId);

        if (result == null)
            throw new SkycountCustomerNotFoundException(customerId);

        return _mapper.Map<CustomerDetailResponse>(result);
    }
}

After running the API I got the following exception:

InvalidOperationException: The following errors occurred with attribute routing information:

Error 1:
Attribute routes with the same name 'HandleAsync' must have the same template:
Action: 'Skycount.Finance.Web.v1_0.Endpoints.Customers.Get.HandleAsync (Skycount.Finance.Web)' - Template: 'api/v{version:apiVersion}/customers/{customerId:guid}'
Action: 'Skycount.Finance.Web.v1_0.Endpoints.Customers.List.HandleAsync (Skycount.Finance.Web)' - Template: 'api/v{version:apiVersion}/customers'

Great! But ...

Hello, this is not really an issue, as much as a set of thoughts...

Thank you for sharing this remarkable piece of open source software! I do feel however that it would be better suited as a PR to Asp.NET core, maybe as an official Microsoft package. It feels like something that inherently fits in Microsoft's mindset around Razor pages.

Secondly I noticed you discuss MediatR. I do think those concepts are related and not related at the same time.
MediatR (IMHO) sits between UI and Application/Domain/... whatever you call it. This package sits between IIS/Kestrel and UI so to speak.

It is perfectly possible (and I would even prefer it) to use this library and still use MediatR to dispatch Commands/Queries.

Thoughts?

Create a Project Template for .NET 6

Modify the ASP.NET Core Web API / webapi project template to use API Endpoints instead of controllers, etc.

Put the code in a folder in this repo.

Publish it to NuGet via GitHub actions when it changes.

Services as plugin

Services in separate dll files through reflection? Just like plugins?
e.g. JWT Login
e.g. Role Manager

How to return FileResult using endpoints?

Dear Steve Smith

In controllers I used to return Files using return File(fileBytes, mime, "archive.zip"); and my controller return type will be IActionResult. How to achieve this with Endpoints? Please assist.

Thanks

Migrating to the controller-less pattern

This sentence: The .NET team already did this exact thing with razor pages. really brought home for me the bigger picture. I'm ready to buy in 100%. If the old way was controllers mainly for sake of routes (opened the door for bloated controllers with too many dependencies) - then this 'razor-pages-for-apis' approach is the basically replacing the conventional controller with a CQRS pattern, isn't it?

And ApiEndpoints gives us linked files which overcomes what used to be very awkward -- navigated between where the event was raised and where the handler's code sat, yes?

How exactly are these files being linked so they show in VS as they do?

Suggestion: Exposing Cancellation Tokens

Current State

Currently, if developers want to control operation cancellation through CancellationToken, they are a little stuck, since the HandleAsync() method doesn't have a CancellationToken parameter.

MVC auto injects these values if they are present.

Motivation

In an app that renders HTML for the browser, this might not be as important, but in client side JavaScript applications, it's not uncommon for for a user to interact with multiple parts of the UI in quick succession (like typing in a search box) that would cause previous XHR requests to be canceled since those responses would be discarded by the client anyway - it's only interested in the latest response.

ASP.NET Core will still handle the cancellation and not send the response, but the developer's pipeline of operations continue to execute on the previous request thread while the newer request is handled.

Many external service libraries (DB, HTTP) accept CancellationTokens to halt operations.

Solution

It would be nice to be able to support cancellation from end-to-end with ApiEndpoints.

I believe we'd need to change the HandleAsync() API to accept a CancellationToken token as the last parameter, which means a breaking change.

Thoughts

I'd be willing to PR this if you are interested in the idea.

I'm also wondering, since it would be a breaking change, if there were any other solutions to problems you'd like to roll in with it, like supporting multiple method params (not sure how... maybe multiple generics like the BCL does it with Func<> and Action<>?)

Enable IActionResult Response

Research For Duplicates

I looked through the issues and docs and didn't see anything discussing this. Apologies if it's there and I missed it.

Describe the feature you'd like

So I love the idea of breaking my controller out like this and started updating my api template to use API Endpoints. Once I started though, I noticed that, as far as I can tell, I have to chose an explicit response for my endpoints that previously use IActionResult as described in the MS docs. This seems to be due to the WithResponse method wrapping whatever is passed to it in a Task<ActionResult<>>. This is obviously by design and I'm sure I can get things to at least working as is, but it seems like we should have the option here to do things, as far as I know, semantically correct.

Example

Here's an example for a POST.

    [Route("/api/patients")]
    public class PostPatientEndpoint : BaseAsyncEndpoint
        .WithRequest<PatientForCreationDto>
        .WithResponse<IActionResult>
    {
        private readonly IMediator _mediator;

        public PostPatientEndpoint(IMediator mediator)
        {
            _mediator = mediator;
        }

        /// <response code="201">Patient created.</response>
        /// <response code="400">Patient has missing/invalid values.</response>
        /// <response code="409">A record already exists with this primary key.</response>
        /// <response code="500">There was an error on the server while creating the Patient.</response>
        [ProducesResponseType(typeof(Response<PatientDto>), 201)]
        [ProducesResponseType(typeof(Response<>), 400)]
        [ProducesResponseType(typeof(Response<>), 409)]
        [ProducesResponseType(500)]
        [Consumes("application/json")]
        [Produces("application/json")]
        [SwaggerOperation(
            Summary = "Creates a new Patient record.",
            OperationId = "PostPatientEndpoint",
            Tags = new[] { "Patients" })
        ]
        [HttpPost, MapToApiVersion("1.0")]
        public override async Task<IActionResult> HandleAsync([FromBody] PatientForCreationDto patientForCreation,
            CancellationToken cancellationToken = new CancellationToken())
        {
            // add error handling
            var command = new AddPatient.AddPatientCommand(patientForCreation);
            var commandResponse = await _mediator.Send(command);
            var response = new Response<PatientDto>(commandResponse);

            return CreatedAtRoute("GetPatient",
                new { commandResponse.PatientId },
                response);
        }
    }

Adding another level of abstraction to keep the returned object consistent

First of all I have to say after making the switch to razor pages last year that this is what I have been looking for.

However, I find the return type interesting as you can return anything from a ActionResult<TResult> and not just TResult.

Have you considered adding another level of abstraction so the HandleAsync returns TResult or even some type of result monad, it would go somewhat to helping #13, but I'm not sure how the FromBody attribute would work.

If this strong typing is something you would entertain for inclusion I might see what i can do over the weekend.

What do you think?

Create an Analyzer to Ensure only One Public Method per Endpoint

Endpoints are actually just controllers, and if they had more public methods beyond the one defined in the class, they could still act as Actions and be routed to by the framework. Create an Analyzer to warn if someone is adding additional public methods to a class that inherits from an Endpoint base class.

Add Related/Similar Projects to README

Add two sections to the README:

  1. Similar projects. A number of folks have pointed me at similar implementations and I'd like to link to them from here.

  2. Projects Using ApiEndpoints. I'd like to link to any projects I know of that are successfully using the ApiEndpoints package.

Change `WithResponce` method names and introduce new ones

Motivation

Current naming is misleading: #105 (comment)
And incomplete: #125 (WithOkResponce it's not ideal name for that method)

Asp.Net Core allows way more flexibility for action return types.

Proposal

Leave WithRequest methods the same, but rename WithResponse methods and create new ones to be mapped to following return types:

  • WithoutResult -> void or Task (not possible with current implementation)
  • WithResult<TResponse> -> TResponse or Task<TResponse> (not possible with current implementation)
  • WithActionResult -> ActionResult or Task<ActionResult> (named WithoutResult in current implementation)
  • WithActionResult<TResponse> -> ActionResult<TResponse> or Task<ActionResult<TResponse>> (named WithResponse<TResponse> in current implementation)

This would allow far more flexibility when creating endpoint (by bringing it's feature set closer to what's possible in regular Asp.Net Core), and resolve confusing name of WithoutResult.

Since this is a breaking change, it would require major version change.

Notes

  • In Asp.Net Core it's also possible to return IActionResult, but that's an artifact from Asp.Net Core 1, and it's functionality is fully covered by ActionResult and ActionResult<T>, so no need to bring support for it here.

Render the absolute url for a given endpoint

NuGet Package Version: 3.1.0
.NET SDK Version: .NET Core 3.1

I am trying to render the absolute url for a given endpoint using IUrlHelper but I can't get this to work.
I am using NamespaceConventions for my endpoints.

Example endpoint:

```
/// <summary>
/// Customer List ApiEndpoint
/// </summary>
[Authorize]
[Route("api/v{version:apiVersion}/customers")]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public class List : BaseAsyncEndpoint
    .WithoutRequest
    .WithResponse<IList<CustomerListResponse>>
{
    private readonly ICustomerService _customerService;
    private readonly IMapper _mapper;

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="customerService"></param>
    /// <param name="mapper"></param>
    public List(
        ICustomerService customerService,
        IMapper mapper)
    {
        _customerService = Guard.Against.Null(customerService, nameof(customerService));
        _mapper = Guard.Against.Null(mapper, nameof(mapper));
    }

    /// <summary>
    /// Get a list of customers
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns>A list of customer objects</returns>
    [HttpGet(Name = "GetCustomers")]
    [Authorize(Policy = "ApiCustomersRead")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public override async Task<ActionResult<IList<CustomerListResponse>>> 
        HandleAsync(CancellationToken cancellationToken = default)
    {
        var customers = await _customerService.GetAsync();

        return _mapper.Map<List<CustomerListResponse>>(customers);
    }
}
```

I need to get the url as:

/api/v1.0/customers

Any idea on how to do this?

When using controllers and without apiVersion I used IUrlHelper and passed controller name and action method:

url.RouteUrl(new { controller = apiControllerName, action = actionName });

Complex Request Model ApiController and QueryString params

I think you will have problems binding to Complex Models if from query params if you don't
SuppressInferBindingSourcesForParameters.

Once you have a test case you can confirm that and then the following should fix it :)

      aServiceCollection.Configure<ApiBehaviorOptions>
      (
        aApiBehaviorOptions =>
        {
          aApiBehaviorOptions.SuppressInferBindingSourcesForParameters = true;
        }
      );

Binding parameters from multiple locations not working in .NET 6?

  • NuGet Package Version: 3.1.0
  • .NET SDK Version: 6.0-RC-1

I am trying to bind parameters from multiple locations as specified in the documentation, but it does not seem to work.

Endpoint:

[HttpPut("api/projects/{projectId}")]
public override async Task<ActionResult<UpdateProjectResponse>> HandleAsync([FromRoute] UpdateProjectRequest request, CancellationToken cancellationToken = default)
{

}

Request:

public class UpdateProjectRequest
{
    [FromRoute(Name = "projectId")] public Guid ProjectId { get; set; } = default!;

    [FromBody] public string Code { get; set; } = default!;

    [FromBody] public string Description { get; set; } = default!;

    [FromBody] public string Number { get; set; } = default!;
}

This is the response I get:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "00-8204c6f7087a1e60fb4c1ab5ac75c778-de42a5cad4fe62d6-00",
  "errors": {
    "$": [
      "The JSON value could not be converted to System.String. Path: $ | LineNumber: 0 | BytePositionInLine: 1.",
      "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0."
    ],
    "Code": [
      "The Code field is required.",
      "'Code' must not be empty."
    ],
    "Number": [
      "The Number field is required.",
      "'Number' must not be empty."
    ],
    "Description": [
      "The Description field is required.",
      "'Description' must not be empty."
    ]
  }
}

I am also using FluentValidation, but that should not matter because FluentValidation validates after model binding.

Could this be because something has changed in .NET 6 or am I just missing something?

Make Ardalis.ApiEndpoints.CodeAnalyzers less coupled with ApiEndpoints project

I would like to use your approach with one single action per controller but don't want to inherit from BaseEndpoint as it will be tricky to update all current methods for Handle usage.

Instead I would like to create my own empty Endpoint base class and reuse your CodeAnalyzer with autofix behaviour to create separate endpoint files per action.

Question - is it possible to bind both FromQuery and Body object using this library

It is possible to do this as I can't workout how based on the TRequest, TResponse definition. The below will obviously not compile.

Thanks

public class MyEndpoint : BaseEndpoint<PayloadBody, string>
...
[HttpPost("resource/{externalTicketId}")]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))]
public override ActionResult Handle([FromQuery] string externalTicketId, PayloadBody payload)
{
....

Use Route and Body Parameters in an Endpoint

For a PUT request that is making an update to a resource, how would an API endpoint allow the ID of the resource to be specified on the ROUTE while the new state of the resource is specified in the request BODY?

Some example code that is not working:

public class Put : Endpoint.WithRequest<InvestmentTypePutRequest>.WithResponse<InvestmentTypePutResponse>
{
    [HttpPut("/investment-types/{Id:Guid}")]
    public override async Task<ActionResult<InvestmentTypePutResponse>> HandleAsync(InvestmentTypePutRequest request, CancellationToken cancellationToken = default)
     {
           // request.Id is null here
      }
}

public class InvestmentTypePutRequest
{
  [FromRoute] public Guid Id { get; set; }
  [FromBody] public byte[] Version { get; set; } = null!;
  [FromBody] public string Name { get; set; } = null!;
  [FromBody] public string? Description { get; set; }
}

Add Screenshots and Walkthrough

Need a more detailed walkthrough with screenshots showing linked files, etc. as well as demonstrating how the endpoints work in Swagger or Postman

Best/Cleanest way of handling different versions?

Greetings and salutations.

Absolutely love the idea of this and I am very keen to start using it in future projects. My only question is around versioning in a longer term project. What would be the cleanest way of doing this? Would it be best to just add the versioned operations on each endpoint file, or would it be better to create a new file per version or is there another way entirely?

I don't mind if the suggested way is opinionated (I would probably prefer it - as long as it makes sense from a long term maintenance perspective).

Hopefully my question isn't too open ended or subjective - I apologize if it is.

Thanks!

How to use CreatedAt* action result helpers

None of the CreatedAt* methods nor the LinkGenerator are able to create urls under pattern (for me at least).

Are there any work arounds? My signature looks like this

[HttpGet( Routes.Files.Download )]
[SwaggerOperation(
	Summary = "Download file",
	Description = "Download file",
	OperationId = "Files." + nameof( Routes.Files.Download ),
	Tags = new[] { "Files" } )]
[SwaggerOrder( 2 )]
[ProducesResponseType( typeof( FileStreamResult ), StatusCodes.Status200OK )]
[ProducesResponseType( StatusCodes.Status304NotModified )]
[ProducesResponseType( typeof( ValidationProblemDetails ), StatusCodes.Status401Unauthorized )]
[ProducesResponseType( typeof( ValidationProblemDetails ), StatusCodes.Status404NotFound )]
public async Task<IActionResult> HandleAsync( [FromQuery] Parameters parameters ) => await DownloadFileAsync( parameters.Id, dbConnectionFactory );

And the class name is Download.

linkGenerator.GetPathByAction( nameof( Download.HandleAsync ), nameof( Download ), new { id = fileId } ); returns null and return CreatedAtAction( nameof( Download.HandleAsync ), nameof( Download ), new { Id = fileId } ); throws an exception.

System.InvalidOperationException: No route matches the supplied values.
at Microsoft.AspNetCore.Mvc.CreatedAtRouteResult.OnFormatting(ActionContext context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsyncCore(ActionContext context, ObjectResult result, Type objectType, Object value)
at Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsync(ActionContext context, ObjectResult result)
at Microsoft.AspNetCore.Mvc.ObjectResult.ExecuteResultAsync(ActionContext context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|21_0(ResourceInvoker invoker, IActionResult result)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Authorization support

Hi,

I'm very interested in this design, but I have not found in documentation how to setup the [Authorize] decorator. Is this possible?
If I create for example a custom Authentication Handler, for execute a policy, i will work with this solution?

Congratulations for that amazing work.

Thanks.
Jose.

Suggestion: using fluent generics

Inspired by this article: https://tyrrrz.me/blog/fluent-generics
I tried it here:
see: https://github.com/paulvanbladel/ApiEndpoints

It's about making the endpoint signature more expressive by using :

 public class Create : Endpoint.WithRequest<CreateAuthorCommand>.WithResponse<CreateAuthorResult>
    {
        private readonly IAsyncRepository<Author> _repository;
        private readonly IMapper _mapper;

        public Create(IAsyncRepository<Author> repository,
            IMapper mapper)
        {
            _repository = repository;
            _mapper = mapper;
        }

        [HttpPost("/authors")]
        [SwaggerOperation(
            Summary = "Creates a new Author",
            Description = "Creates a new Author",
            OperationId = "Author.Create",
            Tags = new[] { "AuthorEndpoint" })
        ]

How to make decorator to work with endpoints?

@ardalis Is it possible to add decorator to Endpoints to enable extra actions in the endpoint execution pipeline? For example, I would like to log my endpoint request data. I don't wanna end up adding logging into each endpoint. Instead a decorator and apply the decorator to the selected endpoints so that it gets executed in the Endpoint Handle Pipeline?

AuditLogDecorator:

public class AuditLoggingDecorator<TRequest, TResponse> : BaseAsyncEndpoint<TRequest, TResponse>
{
    private readonly BaseAsyncEndpoint<TRequest, TResponse> _endpoint;

    public AuditLoggingDecorator(BaseAsyncEndpoint<TRequest, TResponse> endpoint)
    {
        _endpoint = endpoint;
    }

    public override async Task<ActionResult<TResponse>> HandleAsync(TRequest request,
        CancellationToken cancellationToken)
    {
        string requestJson = JsonSerializer.Serialize(request);

        // Use proper logging here
        Console.WriteLine($"Endpoint of type {request.GetType().Name}: {requestJson}");

        return await _endpoint.HandleAsync(request, cancellationToken);
    }
}

AuditLogAttribute:

[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class AuditLogAttribute : Attribute
{
    public AuditLogAttribute()
    {
    }
}

Endpoint:

[AuditLog]
public override async Task<ActionResult<ListCatalogBrandsResponse>> HandleAsync(CancellationToken cancellationToken)
{
...

With the above setup, HandleAsync inside AuditLogDecorator is not getting executed. Am I missing any hooks? Please assist.

Swagger 'Fetch Error' and optional parameters via query string

  1. If you have an endpoint with an [HttpGet( "/calc-engines/{name}" )] attribute but you want to accept optional query string parameters (i.e. bool? allVersions), how do you declare your BaseAsyncEndpoint?

I have public class Get : BaseAsyncEndpoint<Get.Query, Get.CalcEngine[]> and then have a Query object with name and allVersions properties, but when I hit swagger, it shows this:

image

New to creating web apis, is the 'optional' values on the querystring bad practice? Note that I have a [HttpGet( "/calc-engines" )] route that returns all information versus detailed information for one item.

  1. Swagger issue (and I can't seem to find any logging saying why it is failing). When I only had one endpoint, everything worked great. But when I added two endpoints, I received:

Fetch error
undefined /swagger/v1/swagger.json

The following shows my endpoint code. Both are essentially the same and I haven't decided if I'm keeping MediatR or not, but I haven't yanked out yet so just pasting. Note that if I remove one or the other, Swagger works, only fails when both are present.

public class Get : BaseAsyncEndpoint<List.Query, List.CalcEngine[]>
{
	private readonly IMediator mediator;

	public Get( IMediator mediator ) => this.mediator = mediator;

	[HttpGet( "/calc-engines/{name}" )]
	[SwaggerOperation(
		Summary = "Get detailed information about a CalcEngine",
		Description = "Get Live and Test CalcEngine information and all prior version information for each",
		OperationId = "CalcEngines.Get",
		Tags = new[] { "CalcEngines" } )
	]
	public async override Task<ActionResult<CalcEngine[]>> HandleAsync(
		[FromQuery] query,
		CancellationToken cancellationToken = default ) => await mediator.Send( query, cancellationToken );

	public record Query : IRequest<CalcEngine[]>
	{
		public string name { get; init; }
		public bool allVersions { get; init; } = false;
	}

	public record CalcEngine
	{
		public string Name { get; init; }
		...
	}

	class Handler : IRequestHandler<Query, CalcEngine[]>
	{
		public Task<CalcEngine[]> Handle( Query message, CancellationToken token )
		{
			var results = new[]
			{
				new CalcEngine ...
				new CalcEngine ...
			};

			return Task.FromResult( results );
		}
	}
}

Thanks in advance.

Question - Would it make sense to Unify BaseAsyncEndpoint and BaseEndpoint?

I was curious if having a singular root (Base)Endpoint class with the determining of whether the Endpoint was Async or Sync left till the end would make sense.

Not sure if something like the below would make any difference with developer experience or usage.

public static class Endpoint
{
 ... // As is now with the current request classes
    public abstract WithAsyncResponse<TResp> : BaseEndpoint
    {
         // Async method
    }

    public abstract WithResponse<TResp> : BaseEndpoint
    {
         // Sync method
    }
// Without options
...
}

Support routable attributes on records

Support routable attributes on records. Currently the [FromHeader], [FromQuery] attributes only work when the request is a class but not with records.

shared routing conventions not possible with fluent generics API

I might be missing something here but it seems as if it's no longer possible to have any shared routing conventions with the new fluent generics API.

The current version of the docs say to create your own base class that inherits from BaseEndpoint, this is no longer possible since BaseEndpoint (and BaseEndpointAsync) are static classes which cannot be inherited from. Also, even if they could I believe the Route attribute has to be added to a class that inherits from ControllerBase which BaseEndpoint does not.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.