Features

Features are built-in behaviours that can be customized through their configurers. Small features can be named as Configuration or Option. When they only configure an existing behaviour, such as DatabaseConfiguration, it is a Configuration. When they provide nothing to configure other than enable/disable, such as UtcOption, it is an Option.

Authentication

In this section, you will learn about how authentication is implemented in Gazel.

Authentication is enabled by default in Service Application. All requests from outside of your host project require an token. When you run your App.Service project, you will see an Authorization text box in request headers section which configures an HTTP authorization header for every request.

Below you can see a sequence diagram explaining how gazel interacts with your code through interfaces;

/-diagrams/features/authentication-sequence-diagram.mermaid

Here you can see that for every request;

  • It first calls GetSession() to retrieve session object
  • Then it invokes Validate() to enable validation logic
  • If everything is ok, it sets your session object to request scope.

ISessionManager

When authentication is enabled, Gazel requires an implementation of ISessionManager interface. Once you implement it in any module, it will be registered automatically.

ISessionManager is basically responsible for finding an ISession instance from a given AppToken. An example implementation is as follows;

public class SessionManager : ISessionManager
{
    ...
    public ISession GetSession(AppToken appToken) =>
        context.Query<Sessions>().SingleByAppToken(appToken);
    ...
}

If you don't want GetSession method to be public, you can implement ISessionManager explicitly.

public class SessionManager : ISessionManager
{
    ...
    private ISession GetSession(AppToken appToken) { ... }

    ...

    ISession ISessionManager.GetSession(AppToken appToken) => GetSession(appToken);
    ...
}

ISession interface

Just like a web session, ISession represents a session of a user. Unlike a web session it is not stored in cookies nor in a file. ISessionManager creates it before every request and puts it in the request scope. After every request, session instances are destroyed along with its request.

public interface ISession
{
    AppToken Token { get; } // The unique identifier of the session
    string Host { get; } // A text to represent client's host if needed
    IAccount Account { get; } // The account that this session is attached to

    void Validate(); // The method that is invoked before every request
}

Here is an example implementation of ISession interface;

public class Session : ISession
{
    private readonly IRepository<Session> repository;
    private readonly IModuleContext context;

    protected Session() { }
    public Session(IRepository<Session> repository, IModuleContext context)
    {
        this.repository = repository;
        this.context = context;
    }

    public virtual int Id { get; protected set; }
    public virtual AppToken Token { get; protected set; }
    public virtual Account Account { get; protected set; }
    public virtual string Host { get; protected set; }
    public virtual DateTime ExpireTime { get; protected set; }

    protected internal virtual Session With(Account account)
    {
        Account = account;

        Token = context.System.NewAppToken();
        Host = context.Request.Host.ToString();
        ExpireTime = context.System.Now.AddMinutes(30);

        repository.Insert(this);

        return this;
    }

    protected virtual void Validate()
    {
        if(ExpireTime < context.System.Now)
        {
            throw new AuthenticationRequiredException();
        }
    }

    //implemented explicitly to return IAccount
    IAccount ISession.Account => Account;

    //implemented explicitly to make Validate protected
    void ISession.Validate() => Validate();
}

public class Sessions : Query<Session>
{
    public Sessions(IModuleContext context) : base(context) { }

    internal Session SingleByAppToken(AppToken appToken) =>
        SingleBy(s => s.AppToken == appToken);
}

IAccount interface

IAccount represents an account in your application.

public interface IAccount
{
    int Id { get; } // Id of the account
    string DisplayName { get; } // A text to represent the account

    // The method that is invoked before every request if authorization is enabled
    bool HasAccess(IResource resource);
}

Here is an example implementation of IAccount interface;

public class Account : IAccount
{
    private readonly IRepository<Account> repository;
    private readonly IModuleContext context;

    protected Account() { }
    public Account(IRepository<Account> repository, IModuleContext context)
    {
        this.repository = repository;
        this.context = context;
    }

    public virtual int Id { get; protected set; }
    public virtual string FullName { get; protected set; }

    protected internal virtual Account With(string fullName)
    {
        FullName = fullName;

        repository.Insert(this);

        return this;
    }

    //implemented explicitly to map DisplayName on FullName
    string IAccount.DisplayName => FullName;

    //give access to everything for this sample
    bool IAccount.HasAccess(IResource resource) => true;
}

public class Accounts : Query<Account> { ... }

Accessing User Session

When you finish setting up your authentication mechanism, you can access user session using IModuleContext.Session property.

public class CompanyManager
{
    private readonly IModuleContext context;

    public CompanyManager(IModuleContext context)
    {
        this.context = context;
    }

    public Company CreateCompany(string name)
    {
        return context.New<Company>().With(
           name: name,
           createdBy: context.Session.Account.DisplayName
        );
    }
}

Overriding Current Session

You may need to override the session in some specific cases. You can use IModuleContext.OverrideSession method as shown below.

public class Company
{
    private readonly IRepository<Company> repository;
    private readonly IModuleContext context;

    protected Company() { }
    public Company(IRepository<Company> repository, IModuleContext context)
    {
        this.repository = repository;
        this.context = context;
    }

    protected internal virtual Company With(string name, string address)
    {
        //this level uses current session

        //assume we need an admin session here
        var adminSession = context.New<Session>().WithAdminRights();

        context.OverrideSession(adminSession, () =>
        {
            //here context.Session returns adminSession

            //do some admin stuff
        });

        //back to current session

        Name = name;
        Address = address;

        repository.Insert(this);

        return this;
    }
}

OverrideSession can be used nested. After each OverrideSession block ends, previous session becomes accessible.

...
protected internal virtual Company With(string name, string address)
{
    //this level uses current session
    context.OverrideSession(innerSession, () =>
    {
        //this level uses inner session
        context.OverrideSession(nestedSession, () =>
        {
            //this level uses nested session
        });
    });

    //this level uses current session
    return this;
}
...

Configurations

Application Session

TBD

Business Logic

TBD

Command Line

TBD

Database

TBD

Gateway

TBD

Http Header

TBD

Middleware

TBD

Rest Api

TBD

Service Client

TBD

Service

TBD

Options

Audit

TBD

Decimal Point

TBD

UTC

TBD

Exception Handling

Exception handling mechanism is not documented yet.

Below you can see built-in exceptions and their defined error codes;

Error CodeError Type
20001AuthenticationRequiredException
20002FormatException
20003PermissionDeniedException
20004FormatException<CreditCardNumber>
20005UriFormatException
20006ObjectNotFoundException
20007FormatException<AppToken>
20008FormatException<CardNumber>
20009FormatException<CurrencyCode>
20010FormatException<DateRange>
20011FormatException<Date>
20012FormatException<Email>
20013FormatException<MoneyRange>
20014FormatException<Money>
20015FormatException<Tckn>
20016FormatException<TimeRange>
20017FormatException<Time>
20018FormatException<Vkn>
20019InvalidEnumArgumentException
20020RangeException<DateRange>
20021RangeException<MoneyRange>
20022RangeException<TimeRange>
20023FormatException<TriState>
20024InvalidCurrencyException
20025FormatException<Rate>
20026FormatException<Timestamp>
20027FormatException<DateTime>
20028FormatException<DateTimeRange>
20029RangeException<DateTimeRange>
20030FormatException<Guid>
20031RequestIdRequiredException
20032FormatException<Binary>
20033MaxDailyRequestCountExceededException
20034FormatException<Iban>
20035FormatException<Geoloc>
20036FormatException<TimeSpan>
20037FormatException<VknOrTckn>
20038FormatException<MimeType>
20039FormatException<CountryCode>
20040NotImplementedException
20041FormatException<Password>
20042FormatException<EncryptedString>