Skip to content
/ Davli Public

Davli is a framework based on ASP.Net Core for web projects with requirements such as high-performance, scalability, reliability, extensibility.

Notifications You must be signed in to change notification settings

Nigje/Davli

Repository files navigation

Davli

Davli is a framework based on ASP.Net Core for web projects with requirements such as high-performance, scalability, reliability, extensibility. Davli uses the update and famous technologies in ASP.Net Core. Davli is the summary of different frameworks. The following is a list of technologies that are used in Davli. I'm going to regularly update the document in the next commits.

Technologies:

  • .Net5
  • EF Core
  • Autofac
  • Swagger
  • MinIO
  • SQL Server

Todo:

  • Standard Repository Implementation example.

  • Minio documentation.

  • Settings documentation.

  • Filter documentation.

  • Add Automapper.

  • Add Docker and cocker-compose.yml.

  • Add Serilog, Elasticsearch, Kibana, Fluentd, Nginx.

Integrate with a project

Davli is designed for ASP.Net Web API applications and there is a sample project that you can debug Davli. To integrate it with your project you should do some changes in Program.cs and Sturtup.cs. At the first step, you should register AutofacServiceProviderFactory by using it as the following in the Program.cs file to integrate Autofac with project.

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
             .UseServiceProviderFactory(new AutofacServiceProviderFactory())
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });

After that, add the following function to Sturtup.cs to define dependency injection roles. In Autofac section we will explain details.

public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterModule(new DIModule());
            builder.RegisterModule(new DavliDIModule());
        }

At the second step, register Davli's services by AddDavliServices in ConfigureServices function at Sturtup.cs file.

services.AddDavliServices(Configuration);

Also, to use SQL SERVER database add the following code to ConfigureServices function after registering Davli's services. SampleApiDbContext is your DbContext and you will define your poco models there. In the EF Core section, I will explain more details.

services.AddDbContext<DavliDBContext, SampleApiDbContext>(opt => opt.UseSqlServer(Configuration.GetConnectionString("Davli")),
                ServiceLifetime.Transient);

Your ConfigureServices will lookl like the following code.

public void ConfigureServices(IServiceCollection services)
        {
            services.AddDavliServices(Configuration);
            services.AddDbContext<DavliDBContext, SampleApiDbContext>(opt => opt.UseSqlServer(Configuration.GetConnectionString("Davli")),
                ServiceLifetime.Transient); 
        }

Finally, add UseDavliApplication to your Configure function in Startup.cs file. your Configure function will look like the following code:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseDavliApplication();

        app.UseRouting();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }

EF Core

Entity Framework (EF) Core is a lightweight, extensible, open source and cross-platform version of the popular Entity Framework data access technology.

I used it in combination with a generic repository pattern and without it (I will explain their details in the generic repository pattern section and the standard repository section). To use it, add the following code to your ConfigureServices function in Startup.cs. SampleApiDbContext is your DbContext and you should define your poco models there.

public void ConfigureServices(IServiceCollection services)
        {
            services.AddDavliServices(Configuration);
            services.AddDbContext<DavliDBContext, SampleApiDbContext>(opt => opt.UseSqlServer(Configuration.GetConnectionString("Davli")),ServiceLifetime.Transient); 
        }

The following code is implementation of SampleApiDbContext that has a User object (User is a poco object).

public class SampleApiDbContext: DavliDBContext
{
    public SampleApiDbContext(DbContextOptions<SampleApiDbContext> options, RequestContext requestContext) : base(options, requestContext)
    {
        RequestContext = requestContext;
    }
    public DbSet<User> Users { get; set; }
}

sAlso, to use the generic repository, all poco models should derive the DavliEntity<TPrimaryKey> class. DavliEntity<TPrimaryKey> gets a type as the primary key and the implementation of it is as follows. All properties in DavliEntity<TPrimaryKey> automatically update by Davli. For more details and soft delete check the DavliDBContext class

public class DavliEntity<TPrimaryKey> : IDavliEntity, ICreationConcept, IModificationConcept
    {
        public TPrimaryKey Id { get; set; } //Primary key
        public DateTime? LastModificationTime { get; set; } //Indicates the last time the change was applied
        public long? LastModifierUserId { get; set; } //Indicates who was the last person to change the data 
        public DateTime CreationTime { get; set; } //Indicates the time the entity was created
        public long? CreatorUserId { get; set; } //Indicates who created the data
    }

The implementation of the User class is as follows. The type of its primary key is long and its name is Id.

public class User : DavliEntity<long>
    {
        [MaxLength(100)]
        public string Name { get; set; }
    }

Generic Repository Pattern

You can debug this section by calling UserController actions. Also, the UserService class implementation is based on the generic repository pattern. After implementation of poco models, to use the generic repository pattern you need to inject IUnitOfWork in your class and manipulate your entities by _unitOfWork.GenericRepository<DavliEntity<TPrimaryKey>>() and its methods. GenericRepository<DavliEntity<TPrimaryKey>>() returns IRepository<DavliEntity<TPrimaryKey>> and it has common Linq .Expression methods. For more information about it check Repository.cs class (to prevent access to data query from business layer replace IQueryable by Enumerable in Where function). To save changes use _unitOfWork.SaveAsync(). A simple example of it is as follows.

public class UserService : IUserService, ITransientLifetime
{
    private readonly IUnitOfWork _unitOfWork;
    
	public UserService(IUnitOfWork unitOfWork)
	{
		_unitOfWork = unitOfWork;
	}

	public async Task<UserModel> AddNewUserAsync(RegisterUserModel registerUserModel)
	{
		User user = new User { Name = registerUserModel.Name };
		_unitOfWork.GenericRepository<User>().Add(user);
		await _unitOfWork.SaveAsync();
		return new UserModel { Id = user.Id, Name = user.Name };
	}
    
    public async Task DeleteUserAsync(long userId)
	{
		User user = await _unitOfWork.GenericRepository<User>().FirstOrDefaultAsync(x => x.Id == userId);
		if (user == null)
			throw new DavliExceptionNotFound("User not found.");
		await _unitOfWork.GenericRepository<User>().RemoveAsync(x => x.Id == userId);
		await _unitOfWork.SaveAsync();
	}
}

Standard Repository Implementation

//Todo: Implement a sample.

Migrations

To add new models use the following code in command line.

dotnet ef migrations add InitialModels --project Davli.Framework.SampleApi

if you got a error like the following message, update your EF tools by dotnet tool update --global dotnet-ef

The Entity Framework tools version 'x.x.x' is older than that of the runtime 'y.y.y'. Update the tools for the latest features and bug fixes. The name 'InitialModels' is used by an existing migration.

dotnet tool update --global dotnet-ef

To update database use the following code.

dotnet ef database update --project Davli.Framework.SampleApi

Swagger

Swagger is an Interface Description Language for describing RESTful APIs expressed using JSON. Swagger UI allows anyone — be it your development team or your end consumers — to visualize and interact with the API’s resources without having any of the implementation logic in place. It’s automatically generated from your OpenAPI (formerly known as Swagger) Specification, with the visual documentation making it easy for back end implementation and client side consumption. Swagger UI is embedded in Davli and just needs to add the following config to appsetting.json. To access Swagger UI use {hostname}/swagger address (you can change default launchUrl at your project Properties/launchSettings.json file).

{
  "SwaggerOption": {
    "JsonRoute": "swagger/{documentName}/swagger.json",
    "Description": "Sample Api",
    "Name": "nameofApi",
    "UIEndpoint": "v1/swagger.json",
    "Title": "Title of Api",
    "Version": "Version of Api"
  }
}

Autofac

Autofac is an addictive Inversion of Control container for .NET Core, ASP.NET Core. It manages the dependencies between classes so that applications stay easy to change as they grow in size and complexity. To integrate it with your project you should do some changes in Program.cs and Sturtup.cs. At the first step, you should register AutofacServiceProviderFactory by using it as the following in the Program.cs file to integrate Autofac with project.

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
             .UseServiceProviderFactory(new AutofacServiceProviderFactory())
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });

After that, add the following function to Startup.cs to define dependency injection roles. To register Davli class you have to register DavliDIModule and for the current project (for example Sample.Api) you have to register DIModule. DIModule and DavliDIModule have the same content that you can change and use just one of them.

public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterModule(new DIModule());
            builder.RegisterModule(new DavliDIModule());
        }

Resolving services has the following life times and for each of them we have defines an specific Interface.

  • Single Instance: Single Instance is based on singleton pattern and create an instance of class and share same instance between all dependencies. We use ISingletonLifetime to sign classes that are needed to be singleton.

    This is also known as ‘singleton.’ Using single instance scope, **one instance is returned from all *request*s in the root and all nested scopes.

  • Instance Per Request: InstancePerRequest resolves dependencies per request. It means during processing a request if a service is injected and within inner dependencies, again the service is injected, both services refer to the same instance. We use IScopedLifetime to sign classes that are needed to be InstancePerRequest.

    Some application types naturally lend themselves to “request” type semantics, for example ASP.NET web forms and MVC applications. In these application types, it’s helpful to have the ability to have a sort of “singleton per request.”

  • Instance Per Dependency: InstancePerDependency create a new instance per injection. We use ITransientLifetime to sign classes that are needed to be InstancePerRequest.

    Also called ‘transient’ or ‘factory’ in other containers. Using per-dependency scope, a unique *instance* will be returned from each *request* for a service.

The following code indicates the UserService is implementation of IUserService and is signed with ITransientLifetime interface. When IUserService is injected to another class, Autofac due to ITransientLifetime creates a new instance of UserService. If instead of ITransientLifetime, UserService had been signed with ISingletonLifetime, the existing instance was injected (just once create a new instance).

public class UserService : IUserService, ITransientLifetime
{
	//Implementation.
}
public class UserController : DavliControllerBase
 {
		private readonly IUserService _userService;
    
        public UserController(IUserService userService)
        {
            _userService = userService;
        }

}

DavliDIModule and DIModule are classes we have used to define ITransientLifetime, IScopedLifetime, and ISingletonLifetime behavior. Both of them are derived from Autofac.Module and override Load function. ThisAssembly says to Autofac to search current assembly and impose your role on classes that are derived from ITransientLifetime, IScopedLifetime, and ISingletonLifetime. DavliDIModule exists in the DavliFramework project and only registers roles for the DavliFramework project's classes. Also, DIModule exists in the current project (Sample.Api) and registers roles there.

public class DavliDIModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterAssemblyTypes(ThisAssembly)
            .Where(x => x.IsAssignableTo<ITransientLifetime>()).AsSelf().AsImplementedInterfaces()
            .InstancePerDependency();

        builder.RegisterAssemblyTypes(ThisAssembly)
            .Where(x => x.IsAssignableTo<IScopedLifetime>()).AsSelf().AsImplementedInterfaces()
            .InstancePerRequest();

        builder.RegisterAssemblyTypes(ThisAssembly)
            .Where(x => x.IsAssignableTo<ISingletonLifetime>()).AsSelf().AsImplementedInterfaces()
            .SingleInstance();
    }
}

Also, instead of registering both DavliDIModule and DIModule, you can ignore DavliDIModule and change DIModule as below. The following code says to Autofac search all *.dll in the build directory and imposes your role on classes that are derived from ITransientLifetime, IScopedLifetime, and ISingletonLifetime (not recommended).

public class DIModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
		var assemblies = Directory.EnumerateFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll", SearchOption.TopDirectoryOnly)
               .Select(Assembly.LoadFrom);

		builder.RegisterAssemblyTypes(assemblies.ToArray())
			.Where(x => x.IsAssignableTo<ITransientLifetime>()).AsSelf().AsImplementedInterfaces().InstancePerDependency();

		builder.RegisterAssemblyTypes(assemblies.ToArray())
			.Where(x => x.IsAssignableTo<IScopedLifetime>()).AsSelf().AsImplementedInterfaces().InstancePerRequest();

		builder.RegisterAssemblyTypes(assemblies.ToArray())
			.Where(x => x.IsAssignableTo<ISingletonLifetime>()).AsSelf().AsImplementedInterfaces().SingleInstance();
    }

}

You can use ContainerBuilder in Startup.ConfigureContainer to register new roles and types. Also, you can use ASP.NET Core Ioc in Startup.ConfigureServices function.

Exceptions

When an exception occurs in the system the action result will be such as the following object and response HttpStatusCode will be 4xx or 5xx. HttopStatusCode in the range of 4xx shows the exception is because of user behavior (client-side) and 5xx shows it has occurred because of system defects (server-side). In the SampleApi project, there is an ExceptionController that you can use to throw an exception and see the result.

{
  "__unauthorizedRequest": true,
  "__wrapped": true,
  "__traceId": "",
  "error": {
    "errorCode": "USER_NOT_FOUND",
    "message": "User not found.",
    "details": "",
    "source": ""
  }
}

The above response had HttpStatusCode 404 that shows the not found exception has occured. Depend on the application is running on production or other environments, the message, details, and source can have more details about exceptions.

  • __unauthorizedRequest: shows the request is authorized or not. In the above example, the request is not authorized.
  • __wrapped: shows the result is wrapped or not. Now, by default, all exceptions are wrapped.
  • __traceId: TraceId will use to track a request over other systems and services. It is not implemented.
  • error.errorCode: ErrorCode shows the exception reason. When a developer throws a DavliException, he/she will fill this field.
  • error.Message: Message shows the exception reason.
  • error.Details: Details shows details of exceptions such as stack trace and inner exception details. It is always empty in production environment.
  • error.Source: The source shows the service that has raised the exception. If you use microservice architecture, It would show the name of the service and its version. The following config is needed in appsetting. json (It is always empty in production).
{
  "ServiceInfo": {
    "Name": "Service Name",
    "Description": "Service Description",
    "Version": "1.0"
  }
}

Davli has its Exception that is derived from Exception. Also, there are other exceptions that are derived from DavliException. We suggest to use DavliException and you can define new exceptions by driving DavliException. If you have defined a new exception that is derived from DavliException, check GlobalExceptionFilter.cs class for more details about exceptions.

public class DavliException : Exception 

{
    /// <summary>
    /// Error code that indicates a summary of error by using some words or numbers.
    /// </summary>public string ErrorCode { get; protected set; }
    
    /// <summary>
    /// Technical-details are not allowed to be shown to the user.
    /// Just log them or use them internally by software-technicians.
    /// </summary>
    public string TechnicalMessage { get; protected set; }
}
  • DavliException: This exception use generally and its HttpStatusCode will be 500.

  • DavliExceptionNotFound: This exception use to show NotFoundException and HttpStatusCode will be 404.

  • DavliExceptionInvalidParameter: This exception use to show InvalidParameter and HttpStatusCode will be 422.

  • DavliBusinessException: This exception use to show BusinessException and HttpStatusCode will be 400 (recommended for exceptions that should be thrown because of a business defect).

  • DavliExceptionBadRequest: This exception use to show BadRequestand HttpStatusCode will be 400.

  • DavliExceptionExternalService: This exception shows during calling other API an exception has occurred and its HttpStatusCode can be set manually.

  • Exception: This exception use generally and its HttpStatusCode will be 500.

About

Davli is a framework based on ASP.Net Core for web projects with requirements such as high-performance, scalability, reliability, extensibility.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages