
Delphi Framework (Windows/Linux/Android/MACOSX/IOS) to build high-performance and scalable desktop, mobile and web applications easily. Delphi 10 to 12 Athens supported.
Please "star" this project in GitHub! It costs nothing but helps to reference the code.

If you find this project useful, please consider making a donation.
Abstractions:
Services:
MVC:
Extensions:
Mvc Extensions:
Updates:
QuickCore is a framework to easy build desktop/mobile/web apps.
Entire Framework is based on dependency injection priciples. A container holds all services needed by the application, allowing easy infrastructure changes with a minor enfort.
Services are automatically injected into server and configured from a single unit "startup". Every Core project needs a startup.pas with a class inheriting from TStartupBase (see examples on samples folder).
It's a collection of services where we can register predefined or custom services and control its lifecycle (singleton, transient,..). ServiceCollection is the build-in container included in QuickCore and supports constructor injection by default.
services
.AddLogging(TLoggerBuilder.GetBuilder(TLoggerOptionsFormat.ofYAML,False)
.AddConsole(procedure(aOptions : TConsoleLoggerOptions)
begin
aOptions.LogLevel := LOG_DEBUG;
aOptions.ShowEventColors := True;
aOptions.ShowTimeStamp := True;
aOptions.ShowEventType := False;
aOptions.Enabled := True;
end)
.AddFile(procedure(aOptions : TFileLoggerOptions)
begin
aOptions.FileName := '.WebApiServer.log';
aOptions.MaxFileSizeInMB := 200;
aOptions.Enabled := True;
end)
.Build
)
.AddDebugger
.AddOptions(TOptionsFileFormat.ofYAML,True)
//add entity database
.Extension<TEntityServiceExtension>
.AddDBContext<TShopContext>(TDBContextOptionsBuilder.GetBuilder.UseSQLite.ConnectionStringName('ShopContext').Options)
//add Identity
.Extension<TAuthenticationServiceExtension>()
.AddIdentity<TUser,TRole>(procedure(aOptions : TIdentityOptions)
begin
aOptions.Password.RequiredLength := 6;
aOptions.User.RequireUniqueEmail := True;
end)
.AddEntityStore<TShopContext>();
//add Authentication
services.Extension<TAuthenticationServiceExtension>()
.AddAuthentication(procedure(aOptions : TAuthenticationOptions)
begin
end);
//add ApiKey Authentication
services.Extension<TApiKeyAuthenticationServiceExtension>
.AddApiKey()
.UseIdentityStore<TUser,TRole>('ApiKey');
//add Authorization
services.Extension<TAuthorizationServiceExtension>
.AddAuthorization(procedure(aOptions : TAuthorizationOptions)
begin
aOptions.AddPolicy('ApiKeyValidation',TAuthorizationPolicyBuilder.GetBuilder
.RequireAuthenticatedUser.Build
//.RequireClaim(TClaimTypes.Role,'Admin').Build
);
end);QuickCore works with ILogger interface. We can use build-in Logging extension or define own implementation and inject it.
To use QuickLogger implementation (Needs QuickLogger library. See installation requirements). QuickLogger uses an ILogger builder to easy configuration. Default options can be passed as Options delegate function. When QuickLogger config file exists, no default options will be applied more:
services
.AddLogging(TLoggerBuilder.GetBuilder(TLoggerOptionsFormat.ofYAML,False)
.AddConsole(procedure(aOptions : TConsoleLoggerOptions)
begin
aOptions.LogLevel := LOG_DEBUG;
aOptions.ShowEventColors := True;
aOptions.ShowTimeStamp := True;
aOptions.ShowEventType := False;
aOptions.Enabled := True;
end)
.AddFile(procedure(aOptions : TFileLoggerOptions)
begin
aOptions.FileName := '.WebApiServer.log';
aOptions.MaxFileSizeInMB := 200;
aOptions.Enabled := True;
end)
.Build
);...or add own logger implementation
services.AddLogging(MyLogger);QuickCore logging config file is saved as QuickLogger.yml o json file. Using CORE_ENVIRONMENT environment variable we can define what file use for each implementation. If environment variable is defined, QuickCore will try to load/save "QuickCore.[CORE_ENVIRONMENT].yaml/json" file.
QuickCore works with Options pattern. Every TOptions object will be saved as a section in config file and can be injected into services or controllers constructors. Options service needs to added to ServiceCollection before we can add sections. We can define config filename and Json or Yaml format.
.AddOptions(TOptionsFileFormat.ofYAML,True)Every config section needs to be added, and can be configured with default values.
services.Configure<TAppSettings>(procedure(aOptions : TAppSettings)
begin
aOptions.Smtp := 'mail.domain.com';
aOptions.Email := '[email protected]';
end)
and we can inject it later as simple as...
constructor TMyController.Create(aLogger : ILogger; aAppSettings : IOptions<TAppSettings>);
begin
fOptions := aAppSettings.Value;
fSMTPServer.Host := fOptions.Smtp;
end;Into startup config you can use read options to do some optional actions:
if services.GetConfiguration<TAppSettings>.UseCache then
begin
//do some stuff or define service implementation
end
else
begin
//do some stuff or define alternative service implementation
end;Using CORE_ENVIRONMENT environment variable we can define what file use for every implementation. If environment variable is defined, QuickCore will try to load/save "QuickCore.[CORE_ENVIRONMENT].yaml" file.
If not Options.Name is defined, class name will be used as section name in config file. Every Configured Option will be save and load to config file, but if we want, we can hide some options from been saved. Use Options.HideOptions := True (for internal options not configurable externally).
Debugger is a simple tracer-debugger (See QuickLib documentation). To connect debugger with a logging service only needs to add Debugger service in ServiceCollection (by default uses a console output):
services.AddDebugger;Working with commandline parameters will be easy using commandline extension. Define a class inherited from TParameters or TServiceParameters (if working with QuickAppServices) with your possible arguments:
uses
Quick.Parameters;
type
TArguments = class(TParameters)
private
fPort : Integer;
fSilent : Boolean;
published
[ParamCommand(1)]
[ParamHelp('Define listen port','port')]
property Port : Integer read fPort write fPort;
property Silent : Boolean read fSilent write fSilent;
end;And pass to de commandline extension:
services.AddCommandline<TArguments>;When you call your exe with --help you get documentation. If you need to check for a switch or value, you can do like this:
if services.Commandline<TArguments>.Port = 0 then ...
if services.Commandline<TArguments>.Silent then ...Interfaces and Implementations can be added to ServiceCollection. AddSingleton and AddTransient allow define live cycle.
services.AddSingleton<IMyService,TMyService>;or with delegated creation
services.AddTransient<IMyService,TMyService>(function : TMyService)
begin
Result := TMyService.Create(myparam);
Result.Host := 'localhost';
end);or add an implementation
services.AddSingleton<TMyService>;Extensions are injectable services we can add to our app/server. Extensions are injected into ServiceCollection startup unit. ServiceCollection method Extensions works similar to .net extension methods, extendending ServiceCollection.
To add an extension, we need to add its unit to Startup unit uses clause (See QuickCore predefined extensions above).
uses
Quick.Core.Extensions.AutoMapper;
...
begin
services.Extension<TAutoMapperServiceExtension>
.AddAutoMapper;
end;With QuickCore we can create web applications with controllers and actions.
Create an application server and define binding and security.
ApiServer := TMvcServer.Create('127.0.0.1',8080,False);
ApiServer.UseStartup<TStartup>;
ApiServer.Start;
```delphi
To configure services and middlewares startup must configured
```delphi
class procedure TStartup.Configure(app : TMVCServer);
begin
app
.AddControllers
.AddController(THomeController)
.DefaultRoute(THomeController,'Home/Index')
.UseWebRoot('.wwwroot')
.UseRouting
.UseMVC;
end;AddController(ControllerClass): Allow add a controller to an web app server.
AddControllers: Add all controllers registered during its initialization unit with RegisterController(ControllerClass);
UseWebRoot(path): Define static files/data folder.
UseCustomErrorPages: Enable use of custom error pages. On a 403 error, server will search for a 403.html, 40x.html or 4xx.html files. If dinamic page specified, simple mustache patterns will be replaced with error info (StatusCode, StatusMsg, etc).
UseMustachePages: Simple mustache template engine to replace simple views.
Middlewares are like layers of functionality and runs into a request pipeline. Every request pass for each middlwares (in creation order) or not, depending of middelware requeriments.
UseStaticFiles: To allow serve static content.
UseHsts: Http Strict Transport Security middleware to allow only https connections.
UseHttpsRedirection: Enables redirection middleware to redirect on header location found.
UseRouting: Enables Routing middleware to get matching route from request.
UseMVC: Enable MVC middleware to manage and redirect every request to its correspondent controller action or view.
UseMiddleware: To add custom middleware class to request pipeline.
Use(RequestDelegate): Execute an anonymous method as a middleware.
UseAuthentication: Tries to get authentication info from a request.
UseAuthorization: Allow/Disallow acces to resources based on authorization policies.
Every controller inherites from THttpController and published methods becomes actions. With custom attributes we can define routing, authorization, etc of these methods. As all controllers are injected from dependency injection, we can define constructor with autoinjectable parameters and IOC will try to resolve on constructor creation.
constructor THomeController.Create(aLogger: ILogger);Http routing is custom attributes based. We need to define routing for each controller and method/action.
[HttpGet('home/index')]
function THomeController.Index : IActionResult;
[HttpPost('home/GetAll')]
function THomeController.GetAll : IActionResult;If routing defined on class, then it's global and doesn't need to be replicated on each method/action:
[Route('home/other')]
THomeController = class(THttpController)
published
[HttpPost('GetAll')] // global + local = home/other/GetAll
function THomeController.GetAll : IActionResult;Parameters are defined with attributes and automatically parsed and injected as method parameters.
[HttpGet('Add/{productname}/{price}')]
function Add(const ProductName : string; Price : Integer): IActionResult;Parameters can be typed defined. Int: numeric only alpha: only letters. Float: only floating numbers.
[HttpGet('Add/{productname:alpha}/{price:float}')]
function Add(const ProductName : string; Price : Extended): IActionResult;An ? define a parameter as optional
[HttpGet('Add/{productname:alpha}/{price:float?}')]
function Add(const ProductName : string; Price : Extended): IActionResult;To get a parameter from the request body (with automatic deserialization)
[HttpPost('Add/User')]
function Add([FromBody] User : TUser): IActionResult;Action results are results of a controller. StatusCode(statuscode, statustext): Returns a status code and optional status text to client.
Result := StatusCode(200,'ok');Ok(statustext): Returns a 200 status code and optional statustext.
Accepted(statustext): Returns a 202 status code and optional status text.
BadRequest(statustext): Returns a 400 status code and optional status text.
NotFound(statustext): Returns a 404 status code and optional status text.
Forbid(statustext): Returns a 403 status code and optional status text.
Unauthorized(statustext): Returns a 401 status code and optional status text.
Redirect(url): Returns a temporal redirection to url.
RedirectPermament(url): Returns a permanent redirection to url.
Content(text): Returns a response text.
Json(object,onlypublishedproperties): Returns a serialized json object or list. If OnlyPublishedProperties enabled, only object published properties will be serialized.
Result := Json(User);View(viewname): Returns a view.
Result := View('home');Automapper extension allows map a class type to another class type. To use Automapper we must add service to ServiceCollection in Statup unit:
services.Extension<TAutoMapperServiceExtension>
.AddAutoMapper;Then define profile maps with mapping relationship. If property names are identical, we should not have to manually provide a mapping:
constructor TMyProfile.Create;
begin
//maps properties with same name in both classes
CreateMap<TDBUser,TUser>();
end;
initialization
TAutoMapper.RegisterProfile<TMyProfile>;If some properties have diferent name or type, we must use custom mappings:
constructor TMyProfile.Create;
begin
//maps properties with delegate function and rest maps formember
CreateMap<TDBProduct,TProduct>(procedure(src : TDBProduct; tgt : TProduct)
begin
tgt.Id := src.uid;
tgt.Age := src.Age;
end)
.ForMember('Money','Cash')
.ForMember('Name','FullName')
.IgnoreOtherMembers;
end;
initialization
TAutoMapper.RegisterProfile<TMyProfile>;ForMember(SourceProperty,TargetProperty): Maps a source property name to a target property name. IgnoreAllNonExisting: Ignore all non existing properties on target.
IgnoreOtherMembers: Only properties defined in custom mapping will be resolved.
ResolveUnmapped: Tries to resolve automatically any map without a profilemap defined.
AutoMapper service can be injected into a object/controller defining the abstraction in uses clauses.
uses
Quick.Core.Mapping.Abstractions;
...
TMyController.Create(aMapper : IMapper);..and use it:
product := fMapper.Map(dbproduct).AsType<TProduct>;Do you want to learn delphi or improve your skills? learndelphi.org