Resolve the issue of request matched multiple endpoints in .NET Core Web API

If there are two endpoint with same route, .NET Core Web API will throw request matched multiple endpoints error. Here is an example;

// api/menus/{menuId}/menuitems
[HttpGet("{menuId}/menuitems")]
public IActionResult GetAllMenuItemsByMenuId(int menuId)
{            
    ....
}

// api/menus/{menuId}/menuitems?userId={userId}
[HttpGet("{menuId}/menuitems")]
public IActionResult GetMenuItemsByMenuAndUser(int menuId, int userId)
{
    ...
}

This is impossible because the actions are dynamically activated. The request data (such as a query string) cannot be bound until the framework knows the action signature. It can’t know the action signature until it follows the route. Therefore, we can’t make routing dependent on things the framework doesn’t even know yet.

Long and short, we need to differentiate the routes in some way: either some other static path or making the userId a route param. However, we don’t actually need separate actions here. All action params are optional by default. Therefore, we can just have:

[HttpGet("{menuId}/menuitems")]
public IActionResult GetMenuItemsByMenu(int menuId, int userId)

And then we can branch on whether userId == 0 (the default). That should be fine here, because there will never be a user with an id of 0, but we may also consider making the param nullable and then branching on userId.HasValue instead, which is a bit more explicit.

We can also continue to keep the logic separate, if we prefer, by utilizing private methods. For example:

[HttpGet("{menuId}/menuitems")]
public IActionResult GetMenuItems(int menuId, int userId) =>
    userId == 0 ? GetMenuItemsByMenuId(menuId) : GetMenuItemsByUserId(menuId, userId);

private IActionResult GetMenuItemsByMenuId(int menuId)
{
    ...
}

private IActionResult GetMenuItemsByUserId(int menuId, int userId)
{
    ...
}

Have fun.

Read more here.

Swagger configuration

Two steps that i needed for this in .NET 6.

Add this to program.cs file;

// Register swagger generator, you can define multiple documents here
services.AddSwaggerGen(s =>
{
	s.SwaggerDoc("v1", new OpenApiInfo
	{
		Title = "FOO API",
		Version = "v1"
	});

	var xmlFile = $"{typeof(Presentation.AssemblyReference).Assembly.GetName().Name}.xml";
	var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
			s.IncludeXmlComments(xmlPath);
		});

Edit project .csproj file and add / change these nodes:

<PropertyGroup>

    <!-- 
    Make sure documentation XML is also included when publishing (not only when testing)
    see https://github.com/Azure/service-fabric-issues/issues/190
    -->
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <DocumentationFile>bin\$(Configuration)\$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>

This also works with .NET Core 3.1. more info can be found here.

Protected web api configuration

Like web apps, the ASP.NET and ASP.NET Core web APIs are protected because their controller actions are prefixed with the [Authorize] attribute. The controller actions can be called only if the API is called with an authorized identity.

Consider the following questions:

  • Only an app can call a web API. How does the API know the identity of the app that calls it?
  • If the app calls the API on behalf of a user, what’s the user’s identity?

Read more here;

https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-app-configuration

Best practices for designing REST API

A reference list of best practices;

https://abdulrwahab.medium.com/api-architecture-best-practices-for-designing-rest-apis-bf907025f5f

https://zonito.medium.com/best-practice-and-cheat-sheet-for-rest-api-design-6a6e12dfa89f

https://medium.com/geekculture/minimal-apis-in-net-6-a-complete-guide-beginners-advanced-fd64f4da07f5

https://levelup.gitconnected.com/5-difficult-skills-that-pay-off-exponentially-in-programming-702a599c636e

Rest API HATEOAS (Hypermedia as the Engine of Application State)

REST architecture allows us to generate hypermedia links in our responses dynamically and thus make navigation much easier. To put this into perspective, think about a website that uses hyperlinks to help you navigate to different parts of it. You can achieve the same effect with HATEOAS in your REST API.

The advantage of the above approach is that hypermedia links returned from the server drive the application’s state and not the other way around.

REST client hits an initial API URI and uses the server-provided links to access the resources it needs and discover available actions dynamically.

The client need not have prior knowledge of the service or the different steps involved in a workflow. Additionally, the clients no longer have to hardcode the URI structures for various resources. HATEOAS allows the server to make URI changes as the API evolves without breaking the clients.

Let’s say we need to define an API that guides a client through a signature process. In this process after getting a Resource, the resource will include a Link indicating the following elements that are necessary. The plan is to add a signature link in this example:

{
    "documentID": 10,
    "documentType": "Contract",
    "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
    "links": [
        {
            "href": "10/signatures",
            "rel": "approval",
            "type" : "POST"
        }
    ]
}

The recommendation is to add a link to xsd/schema document which will define the structure of model attributes. e.g.

"links": [
    {
        "href": "10/signatures",
        "rel": "approval",
        "type" : "POST",
        "schema" : "/schemas/signature.xsd"
    }
]

Now the client can read the XSD document to find out about the model. This approach wouldn’t process the request but return the model XSD/schema document to the client. In this way, when client want to POST on 10/signatures, it may get the request schema by GET 10/signatures.xsd.

Resources

https://thereisnorightway.blogspot.com/2012/05/api-example-using-rest.html

https://medium.com/@andreasreiser94/why-hateoas-is-useless-and-what-that-means-for-rest-a65194471bc8

https://stackoverflow.com/questions/1775782/does-anyone-know-of-an-example-of-a-restful-client-that-follows-the-hateoas-prin

https://netflix.github.io/genie/docs/3.0.0/rest/#_introduction