LiteApi - controllers, actions and parameters mappings

LiteApi is using old-school root/controller/action mapping methods. By default every action will respond to /api/{controller}/{action} URL.

Update: this post has been updated to reflect changes in version >= 0.4.0
Update 2: LiteApi since v0.6 supports restful links, see here.

Controllers matching

If we want to change controller route we can use LiteApi.Attributes.ControllerRouteAttribute. With this attribute we can set different root of the controller or even remove the root. Check following examples:

using LiteApi;  
using LiteApi.Attributes;

namespace Demo  
{
    // DEFAULT ROOT Controller
    public class AdditionController : LiteController
    {
        // this action will respond to: /api/addition/doit?a=4&b=9
        // when no [ControllerRoute] is present than default route is "/api/{controllerName}"
        // Notice that "Controller" from the name is removed when controller is mapped to URL
        public int DoIt(int a, int b) => a + b;
    }

    // NO ROOT Controller
    [ControllerRoute("Values")]
    public class ValuesController : LiteController 
    {
        // this action will respond to: /values/add?a=1&b=4
        public int Add(int a, int b) => a + b;
    }

    // MULTIPART ROOT controller
    [ControllerRoute("/api/v2/Operations")] // we can add as many root parts as we want
    // "Controller" is not necessary in the name, it's just convenient to follow convention  
    public class Operations : LiteController 
    {
        // this action will respond to: /api/v2/operations/subtract?a=5&b=4
        public int Subtract(int a, int b) => a - b;
    }
}

From the examples above, we can conclude following:

  • each controller needs to inherit LiteApi.LiteController class
  • by default if ControllerRoute is not present, URL route will be /api/{controllerName}
  • with ControllerRoute we can change controller route (it can be single-part or multi-part URL)
  • "Controller" is not necessary part of controller class name, if it's set it will be removed from URL matching
  • controller and action name matching is case insensitive

Actions matching

Action will by default match to URL by method name. If you want to change action route you can use LiteApi.Attributes.ActionRouteAttribute, see example:

[ControllerRoute("/api/v2/ops")]
public class OperationsController : LiteController  
{
    // action will respond to path: /api/v2/ops/4/plus/9?c=1
    // and return 14, note that c is optional parameter
    [ActionRoute("{a}/Plus/{b}")]
    public int Sum([FromRoute]int a, [FromRoute]int b, int c = 0)
        => return a + b + c
}

Parameters matching

Parameter matching is done by model binders. Any JSON can be passed by HTTP request body and deserialized as parameter by LiteApi. For parameters from request body there is one condition to be met: only one parameter can be passed in the HTTP request body (it can be complex or simple model, anything that can be deserialized by Json.NET).

For parameters from query string and route segments following types are supported:

  • bool
  • string
  • char
  • Int16
  • Int32
  • Int64
  • UInt16
  • UInt32
  • UInt64
  • Byte
  • SByte
  • decimal
  • float
  • double
  • DateTime
  • Guid
  • Nullable<T> (where T is one of the types above) are supported for query parameters and not for route segment parameters
  • List<T>, T[], IEnumerable<T> (where T is one of the previous types, including nullable variants) are supported for query parameters and not for route segment parameters
  • Dictionary<TKey, TValue> and IDictionary<TKey, TValues> (where TKey and TValue are one of the types above, simple types and their nullable variants) are supported for query parameters and not for route segment parameters

For matching collection (List, Array or IEnumerable) parameter of name collection, here is a sample URL:

/api/values/sum?collection=6&collection&=98&collection=45
// This URL will match action with parameter named collection which can be
// one of the following types: List<int>, int[], IEnumerable<int>

Matching IDictionary or Dictionary parameter is done in query by following format:

/api/values/doSomething?dict.a=4&dict.n&=7&dict.f=9
// This URL will match action with parameter named dict which is of type
// IDictionary<int, string> or Dictionary<int, string> where key value pairs will be:
// { "a" = 4 }, { "n" = 7 }, { "f" = 9 }

LiteApi is making guess from where parameter is coming from (URL, route segment or body). If parameter type is supported by model binders (one of the parameter types from the list above) it will try to find it in the URL or route segment, if it's not supported by model binders it will look into the body (if HTTP request method is supporting body). You can override this behavior with parameter attributes FromUrl, FromRoute and FromBody (those are in LiteApi.Attributes namespace).

Here is a sample controller with overridden parameter sources:

using LiteApi;  
using LiteApi.Attributes;  
using System.Linq;

namespace Demo  
{
    public class ValuesController : LiteController
    {
        [HttpPost]
        public int Sum([FromBody] int[] array, [FromUrl] int? additionalValue) 
            => array.Sum() + (additionalValue ?? 0);
    }

    public class OpsController: LiteController
    {
        [ActionRoute("/{a}/plus/{b}")]
        public int Add(int a, int b) => a + b;
        // [FromRoute] parameter is optional, LiteApi will check if 
        // parameter name exists in the route
        // this action will respond to URL:
        // /api/ops/4/plus/5
    }
}

For parameters from route segments you can also check sample for actions matching above.

If you need additional support for parameter types from URL you can implement LiteApi.Contracts.Abstractions.IQueryModelBinder or inherit LiteApi.Services.ModelBinders.BasicQueryModelBinder and override appropriate methods. After you implement your custom query model binder (let's call it MyModelBinder) you can register it when registering middleware:

public void Configure(IApplicationBuilder app)  
{
    app.UseLiteApi(
        LiteApiOptions.Default
        .AddAdditionalQueryModelBinder(new MyModelBinder())
      //.AddAdditionalQueryModelBinder(new MyAnotherModelBinder())
    );
}

You can add as many custom model binder implementations as you want.