What's new in LiteApi (v0.7.0)

There has been some time since my last post. There are a lot of new features implemented in LiteApi, and we are getting close to feature complete release.

Here is what's new:

Action overloading rules

There was an issue with overloading action in cases we have a controller like this:

public class MyController: LiteController  
{
    public int Action1(int i)
    {
        return i;
    }

    public int Action1(int[] i)
    {
        return i.Sum();
    }
}

Issue has been fixed and in this case action with int[] i will be called, regardless if there is one or more values provided for i. This way it's easier for middleware to decide which of the methods to call. Similar issue can be encountered with overloading methods with parameters int, int?, int[] and int?[]. In order to decide which method to use, LiteApi will choose the method that can handle most of the edge cases. Here is the priority where top choice has the most priority:

  • int?[]
  • int?
  • int[]
  • int

Same can be said for float?, Guid?, DateTime?, and so on.

Another example would be to have two actions with same routes and parameter names where one action accepts int? i and the other int[] i. Action with int? i will be chosen and if there is more than one value for i it can be expected the last value to be used (but it's not guaranteed). It would be best to avoid this type of overloading by naming the parameters differently, it would also lower the time it takes to choose the right action since middleware would have less work to perform.

Restful links are must have for any web API middleware. Although I personally prefer to have action name in URL, most people (I presume) don't. Here is a sample controller that should be self-explanatory.

[RestfulLinks]
public class PersonsController: LiteController  
{
    private readonly IPersonDataAccess _dataAccess;

    public PersonsController(IPersonDataAccess dataAccess)
    {
        _dataAccess = dataAccess;
    }

    [HttpGet] // will respond to /api/persons?id={someGuid}
    public PersonModel ById(Guid id) => _dataAccess.Get(id);

    [HttpGet, ActionRoute("/{id}")] // will respond to /api/persons/{someGuid}
    public PersonModel ByIdFromRoute([FromRoute]Guid id) => _dataAccess.Get(id);

    [HttpGet] // will respond to /api/persons
    public IEnumerable<PersonModel> All() => _dataAccess.GetAll();

    [HttpPost] // will respond to /api/persons
    public PersonModel Save(PersonModel model) => _dataAccess.Save(model);

    [HttpPost, ActionRoute("/{id}")] // will respond to /api/persons/{someGuid}
    public PersonModel Update(Guid id, PersonModel model) => _dataAccess.Update(id, model);
}

From the sample controller above we can conclude that action name does not affect route on which action is responding to. Critical difference from /controller/action-type of controller is use of LiteApi.Attributes.RestfulLinksAttribute on class level.

File download

This feature is not a critical one but it's a nice to have. There is a workaround for downloading files. You could simply put file in wwwroot of your app and have static files middleware handle it, or you could use a separate middleware for downloading files, or even return files in base64 format. In case of LiteApi there is an interface called ILiteActionResult and a method in LiteController called FileDownload which can be used for sending files to client. There is nothing special about FileDownload method, except that it returns object that implements ILiteActionResult. You can write your own class that implements ILiteActionResult and it would be handled differently than other action call results (it won't be serialized, but it will need to handle response on it's own).

Here is an action that returns file for download.

[HttpGet]
public ILiteActionResult Download()  
{
    byte[] data = Encoding.UTF8.GetBytes("hello from LiteApi"); // can be Stream or byte[]
    string contentType = "text/plain";
    string fileName = "hello.txt";

    return FileDownload(data, contentType, fileName);
}

One note on providing files this way for download: behind the scenes LiteApi is using byte[] to hold file content, so it would be best to avoid sending large files this way to the client. Depending on the hardware and load FileDownlaod method should probably be able to handle files up to few 100s of megabytes.

File upload

Same as for file download feature, this one has many workarounds, but somehow I felt the middleware would be incomplete without file upload feature. Let's face it, most SPA apps needs to have file upload support, and in order to avoid workarounds LiteApi supports HTML form data only in case of file upload. Here is an action that supports reading files uploaded by HTML form:

[HttpPost]
public async Task<long> Upload(FormFileCollection fileCollection)  
{
    long bytesUploaded = 0;
    foreach (var file in fileCollection.Files)
    {
        using (Stream fileStream = new FileStream(file.FileName, FileMode.Create))
        {
            bytesUploaded += file.Length;
            await file.CopyToAsync(fileStream);
        }
    }

    return bytesUploaded;
}

In case of file upload, only one parameter from body is needed of type FormFileCollection, multiple parameters from route or query are supported in the same call. As it's name says it FormFileCollection supports multiple files, there is no parameter type that supports just one file, in case when client uploads just one file FormFileCollection will have just that one uploaded file.

Dependency injection on action level

Sometimes there is just one action in controller that depends on a service. In order to avoid injecting the service for each action call in that controller we can inject the service only when appropriate action method is called. This can be done using FromServiceAttribute (located in LiteApi.Attributes namespace) which should be set on parameter level. Here is an example:

public int Add(int a, int b, [FromServices]IMathOps theService)  
{
    return theService.Add(a, b);
}

It should be noted that you can have regular parameters from body/URL/route next to your dependency-injected parameter, order of parameters is not important. Dependency-injected parameter is resolved from IServiceProvider, so any service that is registered on app level can be retrieved this way. It is possible to injected more than one parameter this way.


That's it I think. For roadmap and upcoming features (and stable release) please GitHub repo.