Backend Questions
Soft delete
By default, generated methods executes hard delete.
Hard delete method from a repository:
public bool Delete(SampleClassName entity)
{
using (var context = GetDbContext<Your_DBContext>())
{
context.Set<SampleClassName>().Remove(entity);
context.SaveChanges();
return true;
}
}
You can change it to a sample soft delete method from CoreCompanyRepository.cs
which manages deletion by only status change:
public bool Delete(CoreCompany entity)
{
using (var context = Helper.GetGenesisContext())
{
entity = context.Set<CoreCompany>().Find(entity.CompanyId);
if (entity == null)
return false;
entity.Status = (int) Status.Deleted;
context.Set<CoreCompany>().Update(entity);
context.SaveChanges();
return true;
}
}
Or instead, elegantly you can manage it by overriding
DBContextsEx
'sSaveChanges
method.
Sending email and SMS
Instead of lots of coding, we recommend using communication middleware via Management / Communication
Resource code and Action for authorization
Please check all details in Authentication & Authorization
Logging
There exists a comprehensive logging middleware. Please check all details in Logging Middleware
Exclude a model/class property from logging
By adding [IgnoreLogging]
, [MaskedLogging]
and [HashedLogging]
attributes to model classes, you can manage logging for specific cases.
public class User
{
[IgnoreLogging] // Do not log "password" ever
[Column("password")]
public string Password { get; set; }
[MaskedLogging(@"\w\w(.*)\w")] // Log the value of "ibanNumber" as masked
[Column("ibanNumber")]
public string IbanNumber { get; set; }
[HashedLogging] // Log the value of "email" as hashed
[Column("email")]
public string Email { get; set; }
}
Please check all details in Logging Middleware
Base classes
Through base classes you can manage main classes in a centralized manner.
- Controllers implement
BaseController
- Models implement
BaseContract
- Repositories implement
BaseRepository
- DBContexts implement
ContextBase
- Validators implement
AbstractValidator
Generic response object
There is a standard and generic reponse object returned for any method call.
Property | Type |
---|---|
success | boolean |
message | string |
errors | JSON Object Array |
data | JSON Object, JSON Object Array |
Example:
{
"success": false,
"message": "Something went wrong",
"errors": [
{
"errorCode": "1020",
"errorMessage": "fullName cannot be null",
"propertyName": "fullName"
}
],
"data": {}
}
EF Core performance
One of the most common pieces of advice is to use method called .AsNoTracking()
. Supposedly this method greatly improves performance on EF queries.
Dapper sample
You can find a sample use in Admin/Admin.Data/Repositories/AuthRepository.cs
public LoggedInUser Login(LoggedInUser parameters)
{
using (IDbConnection dbConnection = Connection)
{
dbConnection.Open();
DynamicParameter p = new DynamicParameter();
p.Add("p_email", parameters.Email);
p.Add("p_password", CoreHelper.GetHashedString(parameters.Password));
return dbConnection.Query<LoggedInUser>("adm_validateuser", p, commandType: CommandType.StoredProcedure).FirstOrDefault();
}
}
Exception Handling
You don't need to handle exceptions in most of the time. Solution adds exception handling middleware, so you get nicely formatted exceptions.
Caching
We use Redis caching but you can use in-memory interchangeably with a slight effort.
Enable Elastic Stack
Check Elastic Stack
Extension methods for EF Core in repository
AddFilters, AddSortings, AddPagination, AddFiltersAndPagination, SelectExclusively, ToPaginatedList and so on
TODO: will be elaborated.
Bulk/Batch transaction
If you create your project via AutoCode Solution Generator, you'll get bulkSave
method for all of your controllers. If you upload/import an excel file from UI, it'll be handled automatically.
Thus, you won't need to struggle with tasks, threads, performance issues and so on.
If you go through boilerplate, find a sample in
Admin/AdminSvc/Controllers/UserController.cs
[HttpPost("bulkSave")]
[ClaimRequirement(ActionType.Import)]
public ResponseWrapper BulkSave([FromBody] RequestWithExcelData<SampleModelClass> request)
{
ResponseWrapper genericResponse = new ResponseWrapper();
if (request != null)
{
Task.Run(() => BulkSaveAsync(request, _mainRepository.BulkSave, HttpContext.Request));
genericResponse.Message = DistributedCache.Get(Messages.PROCESS_SUCCESSFUL);
genericResponse.Success = true;
}
else
genericResponse.Message = DistributedCache.Get(Messages.PROCESS_FAILED);
return genericResponse;
}
Override some columns/properties' values before writing to DB
All Admin.Data
models has 4 properties namely CreatedUserId, CreatedDate, UpdatedUserId, UpdatedDate. They are handled automatically by overriding DBContexts
's SaveChanges
method.
So you can use the same methodology by overriding DBContextsEx
's SaveChanges
method. A sample code block is below:
public override int SaveChanges()
{
if (entity.State == EntityState.Added)
{
entity.CurrentValues["CreatedUserId"] = Session.CurrentUser.UserId;
entity.CurrentValues["CreatedDate"] = DateTime.UtcNow;
}
else if (entity.State == EntityState.Modified)
{
entity.Property("UpdatedUserId").CurrentValue = Session.CurrentUser.UserId;
entity.Property("UpdatedDate").CurrentValue = DateTime.UtcNow;
}
return base.SaveChanges();
}
How model validation works?
We use FluentValidation which is a strong library to adapt and use easily.
In each insert
and update
method, the request model is validated.
[HttpPost("insert")]
[ClaimRequirement(ActionType.Insert)]
public ResponseWrapper Insert([FromBody] SampleModelClass request)
{
_sampleValidator.ValidateAndThrow(request);
return Save(request);
}
Please visit FluentValidation official web site for details.
Set custom multi-language message for a specific control in Validator
...
RuleFor(x => x.TenantType) // Only SystemOwner can assign a new SystemOwner
.NotEqual(x => (int) TenantType.SystemOwner)
.When(x => SessionAccessor.GetSession().CurrentUser.TenantType != (int)TenantType.SystemOwner)
.WithMessage(session => DistributedCache.Get("YOU_CANNOT_ASSIGN_SYSTEM_OWNER_TENANTTYPE_MESSAGE")); // This line provides multi-language message
...
CORS-Origin settings
AllowedCorsOrigins setting is used to allow access for cross-origin requests. This is also be needed if you host your React UI or Microservices in separate servers/domains/ports.
Find appsettings.Development.json
and appsettings.Production.json
{
"ConnectionStrings": {
"GenesisDB": "User ID=postgres;Password=123456;Host=localhost;Port=5432;Database=GENESIS_DB;",
"PostgreSQL": "User ID=postgres;Password=123456;Host=localhost;Port=5432;Database=YOUR_DB;"
},
"DefaultDatabase": "PostgreSQL",
"GenesisDBType": "PostgreSQL",
"ApplicationUrl": "http://0.0.0.0:5051",
"AllowedCorsOrigins": [
"http://localhost:3000",
"http://localhost:5050",
"http://localhost:5000"
]
}
For more app settings, check Configuration.
How to manage and save FILE_UPLOADER's JSON data
By default, posted data for upload components is a json as:
{
"file": "iVBORw0KGgoAAAANSUhEUgAAAvQAAAISCAYAAACjwVuJAAAgAElEQVR4AeydB5wURfbHf70zG4jiAgossCxZghIERVhJBlDgwHAG1EPgCHreX4mCIlFFghhJCnIIKueJqCgiSthFgpJUctolr2Qkbpr....",
"fileName":"someImageName.png",
"mimeType":"image/png"
}
In that case, you should add a serializer to public partial class YourDBContextEx
which is in YourMicroserviceName.DataLib
using CoreType.Types;
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<YourModelClass>(entity =>
{
entity.Property(e => e.YourFilesColumnName) // Don't forget to change YourFilesColumnName's type to FileContent in DataLib/DBModels/YourModelClass.cs
.HasColumnName("YourFilesColumnName")
.HasColumnType("json")
.HasConversion(
v => JsonConvert.SerializeObject(v),
v => JsonConvert.DeserializeObject<FileContent>(v));
});
}
In DB, you don't need to use BLOB types. If your preferred DB supports JSON data-type (as for PostgreSQL), use it. Otherwise, we recommend
nvarchar(max)
ortext
.
Can we choose different DBs for each microservice throughout AutoCode Solution Generation?
Yes, you can specify MSSQL Server for first microservice and PostgreSQL for the other one. It is flexible to choose amongst DB servers, schemas or tables for different microservices.
Throw a custom multi-language error message
throw new GenesisException("SOME_PARAMETER_CODE_FROM_COREPARAMETERS");
If you want to set dynamic variable into the message
// "Message includes variables as {0} and {1}"
throw new GenesisException("SOME_PARAMETER_CODE_FROM_COREPARAMETERS", SomeVariable1, SomeVariable2);
What happens if I use Where condition and AddFilters, AddFiltersAndPagination extension methods together?
No problem. They will be "AND"ed. Find an example in TenantRepository.cs
public PaginationWrapper<Tenant> List(RequestWithPagination<Tenant> entity)
{
using (var context = Helper.GetGenesisContext())
{
var query = context.Set<Tenant>().AsNoTracking();
if (Session.CurrentUser.TenantType == (int) TenantType.SystemOwner)
query = query.IgnoreQueryFilters();
if (entity.Criteria.StatusFilter != null)
query = query.Where(x => x.Status == entity.Criteria.StatusFilter);
var res = query.AddFilters(entity)
.OrderBy(x => x.TenantName)
.SelectExclusively(x => new { x.TempPassword })
.ToPaginatedList(entity);
return res;
}
}
How should I proceed to use Model-First/Code-First approach?
You can benefit AutoCode Solution Generator's update
method.
Check on Genesis Migration
Encrypt-Decrypt method
Find sample in CommunicationManager.cs
string encryptedPassword = EncryptionManager.Encrypt(somePassword);
string decryptedPassword = EncryptionManager.Decrypt(encryptedPassword);
Can I use ToPaginatedList and AddPagination extension methods together?
No need and you should NOT use them together.
Store/persist data as encrypted but decrypt it when fetched
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Microservice.DataLib.DBModels
{
public partial class SampleModelClass
{
[Column("userId")]
public int UserId { get; set; }
[Required]
[Column("userName")]
[StringLength(50)]
public string UserName { get; set; }
[Required]
[Column("email")]
[StringLength(80)]
public string Email { get; set; }
[Column("password")]
[StringLength(64)]
[HashedLogging] // Log the password as hashed
public string Password { get; set; }
[EncryptedPersistence] // Store the gender as encrypted since it is subject to GDPR
[IgnoreLogging] // Don't log the gender
[Column("gender")]
public short? Gender { get; set; }
[HashedPersistence] // Stora data as hashed
[HashedLogging] // Log as hashed
[Column("someHashValue")]
public string SomeHashValue { get; set; }
....
}
}
TODOs
Scheduling Monitoring
Add new controller
Change IdentityServer
Integrate to Active Directory or LDAP
Extended partial classes
DBContextEx and ModelEx
Swagger as documentation
Dependency injection
DTOs
Object AutoMapper
Unit testing
Extending Existing Entities
SignalR integration
Notification sending
Login process
hashed password, call sp, validate and get claims