Skip to content

About Multiple Route Attributes

fonlow edited this page Feb 12, 2021 · 1 revision

(Based on #105)

It is an interesting subject that I had considered in the very early days of the development around 5 years ago, and this is down to 2 major areas:

  1. The business intent of this project. Client developers do not need to care about the technical details of HTTP, like route and HTTP method, and focus on the API prototype like in-process API, as further described in Points of Interests at https://www.codeproject.com/Articles/1074039/Generate-Csharp-Client-API-for-ASP-NET-Web-API
  2. Versioning of API

In your OP, you might have hinted a technical solution of "improved" webapiclientgen: generate multiple client API functions for one Web API with multiple routes. For example, client functions with suffix named after each route.

Another approach to deal with versioning is:

  1. Keep the practice of one route+parameters one Web API function.
  2. Keep one version of API in one controller.

I would prefer this approach since the whole design of WebApiClientGen is optimized for RPC, not REST, and I consider that RPC is more suitable for complex business applications with complex business models, while REST is too technical, verbose and even wasteful in such contexts.

And generally I would group functions in a controller based on respective business model, not REST resources.

[Route("api/[controller]")]
public class HotLinkV3Controller : ControllerBase
{
[HttpGet, Route("status")]]
public string StatusCheck ()
{
return "OK";
}
... other Web API functions....
}

[Route("api/[controller]")]
public class HotLinkV3Controller : ControllerBase
{
[HttpGet, Route("status")]
public string StatusCheck ()
{
return "OK";
}
... other Web API functions....
}

[Route("api/[controller]")]
public class HotLinkV4Controller : ControllerBase
{
[HttpGet]
public string StatusCheck ()
{
return "OK";
}
... other Web API functions....
}

[Route("api/[controller]")]
public class HotLinkV4Controller : ControllerBase
{
[HttpGet]
public string StatusCheck ()
{
return "OK";
}
... other Web API functions....
}

[Route("api/[controller]")]
public class ContentServerV1Controller : ControllerBase
{
[HttpGet]
public string StatusCheck ()
{
return "OK";
}
... other Web API functions....
}

And in the client codes, you will have

HotLinkV3Client ...
HotLinkV4Client ...
ContentServerV1Client

I believe that in some contexts, especially in complex business applications, such approach minimize the maintenance cost on server development and client development, with versioning.

And presumably you generally have just 1 to 3 statements in each Web API function. For example:

	[Route("api/[controller]")]
	public class PeopleController : ControllerBase
	{
		[HttpPost]
		[Route("Add")]
		public Guid Add([FromBody] Person entity)
		{
			return PersonOperations.Add(entity);
		}

		[HttpPost]
		[Route("Undelete")]
		public void Undelete([FromQuery] Guid id)
		{
			EntityOperations.Reactivate(id);
		}

		[HttpDelete]
		[Route("ForceDelete")]
		public bool ForceDelete([FromQuery] Guid id)
		{
			return PersonOperations.DeleteInTransaction(id);
		}

		[HttpPut]
		[Route("Update")]
		public int Update([FromBody] Person entity)
		{
			return PersonOperations.Update(entity);
		}

		[HttpGet]
		[Route("Get")]
		public Person Get([FromQuery] Guid id)
		{
			return PersonOperations.Get(id);
		}

		[HttpGet]
		[Route("GetName")]
		public string GetName([FromQuery] Guid id)
		{
			return PersonOperations.GetName(id);
		}

So the cost of versioning with the preferred approach could be minimum. And also, if you would provide SOAP/WCF endpoints, the development is obviously easy. WCF or WebAPI controller is just something of transportation, so the implementation of Web API functions should be thin, and the implementation details should be hidden behind something like PersonOperations. And such approach is good for unit testing too.

And in complex business applications, generally I don't explicitly return HTTP (error) status in the controller, and I throw exceptions inside something like PersonOperations, and then the filter or error handling middleware will catch the exception and return HTTP status code.

Clone this wiki locally