ardalis / ApiEndpoints

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

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Multiple binding sources and shared request classes

nhwilly opened this issue · comments

The app is Blazor/WASM with an Asp.Net Core backend.

I am attempting to share the request classes from a shared Blazor project in an effort to ensure that both sides of the solution automatically stay in sync.

Everything was grand until I tried to add query parameters to a get request. And here's how I got boxed in:

  • In order to have multiple binding sources, I need the [From*] attributes on the properties.
  • I don't want to drag the Mvc libraries into my WASM project, so I can't just put them on the request properties and still share them.
  • I've worked out a solution, but it's ugly and I want to know if I've lost my mind, or if there's something really obvious, that I've missed.

In the Blazor.Shared project

public interface IRoute
{
  public string Route();
}

public abstract class AccountRequest : BaseRequest
{
  public virtual int AccountId { get; set; }
}

public abstract class GetAllAccountPermissionsGroupsByAccountIdRequestBase : AccountRequest
{
  public const string RouteTemplate = "api/Accounts/{AccountId}/AccountPermissionsGroups";
  public virtual ArchivedFilter ArchivedFilter { get; init; }
}

public class GetAllAccountPermissionsGroupsByAccountIdRequest : GetAllAccountPermissionsGroupsByAccountIdRequestBase, IRoute
{
  public string Route()
  {
    return RouteTemplate
      .Replace("{AccountId}", AccountId.ToString())
      .Replace("{ArchivedFilter}", ArchivedFilter.ToString());
  }

  public GetAllAccountPermissionsGroupsByAccountIdRequest(int accountId, ArchivedFilter archivedFilter = ArchivedFilter.Active)
  {
    base.AccountId = accountId;
    base.ArchivedFilter = archivedFilter;
  }
}

In the API project

public class GetAllAccountPermissionsGroupsByAccountIdRequestHost : GetAllAccountPermissionsGroupsByAccountIdRequestBase
{

  [FromQuery]
  public override ArchivedFilter ArchivedFilter { get; init; }

  [FromRoute]
  public override int AccountId { get; set; }
}

To me this seems like a lot of work, and maybe I should just suck it up and maintain the multipart binding situations manually.

I guess a little input from your experience or a suggestion for another approach would be great. TIA

I ran into a similar issue a while back with generated code that I wanted to later add attributes to:
https://ardalis.com/adding-attributes-to-generated-classes/

That approach uses partials so probably won't work in your case. The inheritance and override approach is what I would have suggested next.

Some frameworks will check for an attribute by name and it doesn't care if it's the same type as long as the name matches. I don't think MVC is like that but you could try it. Just define your own FromQuery and FromRoute attributes in the shared project, apply them there, and see if the MVC project honors them.

That's my initial thoughts - let me know if any of that is helpful.

Thanks for the quick response. I'll think about the faux [From*] attributes, but inevitably that edge stuff comes back to bite you when some ambitious maintainer decides to clean up some code.

The other option is simply defining a class in the endpoint project with the properties I'm expecting. I'd have to keep that in sync, but at I've just tested that and when they are not in sync, the binding fails and my pipeline gives out a validation error.

It should show up in testing then and I could catch it there.

And I'd only have to do it for those times when the binding sources are from different places. Which would be GET and DELETE as far as my app goes. (So far.)

And, I suppose I could use code generation as an alternative. I've done that before with my own attributes. And people seem to be excited about some new code generation capabilities.

Again, thanks for following up.

For anyone else that ends up here, the fake [From*] attributes I created for the Blazor project (in an attempt to not have Asp.Net Core Mvc classes in the WASM file) did not work.

Since I am sharing the route defined in the Blazor class that is also used in the endpoint, I am pretty much forced to inherit from the Blazor class and override the values I want with the new keyword and the appropriate attributes.

Note that it's only necessary to override the properties you want to get [FromQuery] as {FromRoute] is specified inline in the handler and those will be picked up from the parent object.

HTH