Core
It is the library where Milvasoft libraries share common components. In this part of the document, we will describe the components found in the core library one by one.
Entity Bases
Entities are one of the core concepts of DDD (Domain Driven Design). Eric Evans describes it as "An object that is not fundamentally defined by its attributes, but rather by a thread of continuity and identity". An entity is generally mapped to a table in a relational database.
All methods and entities in database related libraries and components are inherited from this class.
You can use BaseEntity
to use Id which inherited from IEquatable
interface and whose type is struct
public abstract class BaseEntity<TKey> : IBaseEntity<TKey> where TKey : struct, IEquatable<TKey>
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual TKey Id { get; set; }
public override string ToString() => $"[{GetType().Name} {Id}]";
}
If you want to use a different type of Id, you can use EntityBase
public abstract class EntityBase<TKey> : IEntityBase<TKey>
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual TKey Id { get; set; }
public override string ToString() => $"[{GetType().Name} {Id}]";
}
If you do not want to derive your entity from the base BaseEntity<TKey>
or EntityBase<TKey>
class, you can directly implement IBaseEntity<TKey>
or IEntityBase<TKey>
interface.
Base Classes & Interfaces for Audit Properties
There are some properties like CreationDate
, CreatorUserId
, LastModificationDate
and much more, which are very common in all applications. Milvasoft provides some interfaces and base classes to standardize these properties and also sets their values automatically.
There are a lot of auditing interfaces, so you can implement the one that you need. While you can manually implement these interfaces, you can use the base classes defined in the next section to simplify it. You can see all these interfaces here.
IHasCreator
defines the following properties;
CreatorUserId
ICreationAuditable
has multiple versions and inherits from the IHasCreator
, so it defines the following properties;
CreationDate
CreatorUserId
CreatorUser
IHasModifier
defines the following properties;
LastModifierUserId
IAuditable
has multiple versions and inherits from the IHasModifier
and ICreationAuditable
, so it defines the following properties;
LastModificationDate
LastModifierUserId
LastModifierUser
CreationDate
CreatorUserId
CreatorUser
ISoftDeletable
defines the following properties;
IsDeleted
DeletionDate
IHasDeleter
defines the following properties;
DeleterUserId
IFullAuditable
has multiple versions and inherits from the ISoftDeletable
,IHasDeleter
,ICreationAuditable
and IAuditable
, so it defines the following properties;
IsDeleted
DeletionDate
DeleterUserId
DeleterUser
LastModificationDate
LastModifierUserId
LastModifierUser
CreationDate
CreatorUserId
CreatorUser
Auditing Base Classes
While you can manually implement any of the interfaces defined above, it is suggested to inherit from the base classes defined here;
CreationAuditableEntity
implement the ICreationAuditable
interface.
AuditableEntity
implement the IAuditable
interface.
FullAuditableEntity
implement the IFullAuditable
interface.
All these base classes also have multiple versions. You can check here thesee versions.
All these base classes also have ...WithUser
pairs, like FullAuditableWithUser
and FullAuditableWithCustomUser
. This makes possible to add a navigation property to your user entity. It is also available in a version that you can use with the Microsoft.AspNetCore.Identity library.
Models
It contains models that you can use in the library or in your projects.
Response Models
BaseResponse
Base response model for API responses.
Properties | Description | Default Value |
---|---|---|
Success | Information of whether the request was successful. | true |
Message | Message of response. | "Process Succesful!" |
StatusCode | Http status code of response. | MilvaStatusCodes.Status200OK |
ExceptionResponse
Response model that can be returned in error situations.
Properties | Description |
---|---|
Result | Result of request. |
ErrorCodes | Error codes of request, if exists. |
ObjectResponse
Response model for requests that return a object.
Properties | Description |
---|---|
Result | Result object. |
File Models
IFileDTO
Abstraction for file upload process.
Properties | Description |
---|---|
FileName | Name of file. |
File | File object. |
IFileEntity
Abstraction for file upload process.
Properties | Description |
---|---|
FileName | Name of file. |
FilePath | Path of file. |
InvalidString
The model used to control the blacklist with dependency injection of the attributes that perform string validation.
Properties | Description |
---|---|
Name | Name of invalid string. |
Values | Invalid values. |
OrderByProp
Model used to forward multiple sort requests from the client to the database.
Properties | Description |
---|---|
PropName | Determines order by Property name of entity. |
Ascending | Determines order by ascending or descending. |
Priority | Priority of order operation. Ex. first order by creation date then order by updated date. |
PaginationDTO
Model for use in retrieving paging parameters from the client.
Properties | Description |
---|---|
DTOList | Items in page. |
PageCount | Page count information. |
TotalDataCount | Total data count of all pages. |
Exceptions
With the server-side checks, you can throw custom exceptions and catch them with a custom middleware. In this way, you can both check for errors without breaking your application and forward your user-friendly messages to the user by rewriting the response. For exception types and overloads check here.
All exception classes in below inherit from the MilvaBaseException class.
Example middleware for usage example;
public class ExceptionHandlerMiddleware
{
private readonly RequestDelegate _next;
public ExceptionHandlerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
// Get localizer instance from service collection
var sharedLocalizer = context.RequestServices.GetLocalizerInstanceWithMilvaResource();
string message = sharedLocalizer[nameof(ResourceKey.MiddlewareGeneralErrorMessage)];
List<int> errorCodes = new();
try
{
context.Request.EnableBuffering();
//Continue processing
if (_next != null)
await _next.Invoke(context);
}
catch (Exception ex)
{
int errorCode = MilvaStatusCodes.Status600Exception;
if (ex is MilvaUserFriendlyException userFriendlyEx)
{
if (userFriendlyEx.ExceptionObject == null)
message = sharedLocalizer[userFriendlyEx.Message];
else
message = sharedLocalizer[userFriendlyEx.Message, userFriendlyEx.ExceptionObject];
errorCodes.Add(userFriendlyEx.ExceptionCode);
}
else
{
if (ex is MilvaDeveloperException devEx)
{
// Send mail this error to developers
}
else
{
message = sharedLocalizer[nameof(ResourceKey.AnErrorOccured)];
// Get logger instance from service collection
var logger = context.RequestServices.GetRequiredService<IMilvaLogger>();
// Log exception
logger.Write(SeriLogEventLevel.Fatal, ex, ex.Message);
}
}
// Rewrite response
if (!context.Response.HasStarted)
{
var response = new ExceptionResponse
{
Message = message,
StatusCode = errorCode,
Success = false,
Result = new object(),
ErrorCodes = errorCodes
};
// Serialize with Newtonsoft.Json library
var json = JsonConvert.SerializeObject(response);
context.Response.ContentType = MimeTypeNames.ApplicationJson;
// We prefer to set the HTTP status codes of all responses to 200.
context.Response.StatusCode = MilvaStatusCodes.Status200OK;
await context.Response.WriteAsync(json);
}
}
}
}
MilvaDeveloperException
It can be used to distinguish developer-related errors.
MilvaUserFriendlyException
It can be used to distinguish errors made by the users your application serves.
Usage
...
List<Product> products = await _productRepository.GetProductsFromDatabaseAsync();
if(products.IsNullOrEmpty())
throw new MilvaUserFriendlyException("CannotFindProductsLocalizerKey");
...
The above middleware catches the error and rewrites the response.
Response;
{
"result": {},
"errorCodes": [
600
],
"success": false,
"message": "No products found. Please add new products!",
"statusCode": 600
}
MilvaValidationException
Error class that can be thrown on validation errors.
MilvaTestException
An error class that we usually use when testing the application, the text of which we can throw in any situation we specify ourselves.
Extensions
Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. There are many extension methods in the library. We will explain a few of them, for more you can check here.
Expression Extensions
Append()
Combines two expressions.
public static Expression<Func<T, bool>> Append<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right, ExpressionType expressionAppendType)
Usage
Expression<Func<Product, bool>> priceCondition = i => i.Price > 10;
Expression<Func<Product, bool>> isDeletedCondition = i => i.IsDeleted;
// Output : i => i.Price > 10 && i.IsDeleted
var combinedCondition = priceCondition.Append(isDeletedCondition , ExpressionType.AndAlso);
Generic Collection Extensions
IsNullOrEmpty()
Checks whether or not collection is null or empty. Assumes collection can be safely enumerated multiple times.
Usage
var list = new List<string>();
// Output : true
var isNullOrEmpty = list.IsNullOrEmpty();
String Extensions
ToUpperFirst()
Uppercases the first letter of the word.
Usage
var value = "test";
// Output : Test
var upperValue = value.ToUpperFirst();
GeoLocation
Helper for geographic calculations.
Models
GeoPoint
Properties | Description |
---|---|
Latitude | Latitude of point. |
Longitude | Longitude of point. |
Extensions
Extension methods for geo calculation.
CalculateDistance
Calculates the distance in km from point1
to point2
.
public static double CalculateDistance(this GeoPoint point1, GeoPoint point2)
Usage
var firstPoint = new GeoPoint(11, 11);
var secondPoint = new GeoPoint(12, 12);
// Output = 155.68179481457878
var distance = firstPoint.CalculateDistance(secondPoint);
You can confirm this values from here.
Common Components
Request Sender
What is Http Request ?
An HTTP request is made by a client, to a named host, which is located on a server. The aim of the request is to access a resource on the server. To make the request, the client uses components of a URL (Uniform Resource Locator), which includes the information needed to access the resource. The components of a URL explains URLs. For more information.
Url schema;
https://www.milvasoft.com:444/api/v1.0/products/product?id=1
_____ ________________ ___ _______________________ ____
| | | | |
protocol | | pathName query
hostName |
port
This class simplifies http request sending.
Usage
// Creates proper request url
var url = Request.CreateRequestUrlFromAddress("https://www.milvasoft.com", "api/v1.0/products/product");
var headers = new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("Accept-Language", "en-US")
};
// Request Body
var product = new Product
{
Id = 1,
Name = "Water"
};
// Creates HttpRequestMessage object according to request parameters
using var requestMessage Request.CreateRequestMessage(HttpMethod.Post, Encoding.UTF8, "json", url, product, headers);
// Don't use HttpClient like this in your projects. Use instead AddHttpClient() service collection extension.
using var httpClient = new HttpClient();
// Sends the request and deserializes the response to the requested type.
var response = await httpClient.SendRequestDeserialize<ObjectResponse>(requestMessage);
For more overloads check here.
Response Modifier
In cases where the api returns a response in a specific structure, you must create the response object at each endpoint. This class was created to reduce code duplication, increase readability, and standardize error messages to be returned. There are extension methods according to the response models in the Models section. You can check here and here to understand what the methods do.
Usage
Sample from Response.cs
[HttpPost("Login")]
public async Task<IActionResult> LoginAsync([FromBody] LoginDTO loginDTO)
{
var loginResult = await _accountService.LoginAsync(loginDTO);
return loginResult.GetObjectResponse(_sharedLocalizer["SuccessfullyLoginMessage"]);
}
{
"result": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
},
"success": true,
"message": "Successfully logged in.",
"statusCode": 200
}
Sample from ResponseV2.cs;
[HttpGet("Products")]
public async Task<IActionResult> GetProductsAsync()
{
var products = await _productService.GetProductsAsync();
return products.GetObjectResponseByEntities(HttpContext, "Product", isFiltering : false);
}
{
"result": [
{
"id": 121,
"name": "Water",
"description": "Water is life!"
},
{
"id": 122,
"name": "Tea",
"description": "Warm yourself!"
}
],
"success": true,
"message": "The operation was completed successfully.",
"statusCode": 200
}
Regex Matcher
What Is Regex ?
Regular expressions provide a powerful, flexible, and efficient method for processing text. The extensive pattern-matching notation of regular expressions enables you to quickly parse large amounts of text to:
- Find specific character patterns.
- Validate text to ensure that it matches a predefined pattern (such as an email address).
- Extract, edit, replace, or delete text substrings.
- Add extracted strings to a collection in order to generate a report.
For many applications that deal with strings or that parse large blocks of text, regular expressions are an indispensable tool. For more information.
MatchRegex
Checks whether the entered value conforms to the requested regex or not.
Usage
var regex = "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"
var value = "[email protected]";
// Output : true
var result = value.MatchRegex(regex);
Common Helper Methods
This class contains helper methods. You can check here to understand what the methods do.
PropertyExists Methods
Checks whether the specified type has the named property.
Usage
bool propExists = CommonHelper.PropertyExists<TEntity>("propertyName");
Poco poco = new();
bool propExists = poco.PropertyExists("propertyName");
Type pocoType = new Poco().GetType();
bool propExists = pocoType.PropertyExists("propertyName");
CreateOrderByKeySelector Methods
Creates order by key selector expression according to property name. If property is not exists thw
Usage
// Output expression : i => i.Roles
Expression<Func<User, object>> selector = CommonHelper.CreateOrderByKeySelector<User>("Roles");
// Output expression : i => i.Username
Expression<Func<User, string>> selector = CommonHelper.CreateOrderByKeySelector<User, string>("Username");
EnsureLegalLengths
When checking the maximum and minimum length, it controls the maximum and minimum length values.
Usage
// Throws MilvaUserFriendlyException.
CommonHelper.EnsureLegalLengths(maxLength : 5, minLength : 10);
GetEnumDesciption
Returns the value of the DescriptionAttribute of the enum value.
Usage
public enum PocoType
{
[Description("This is a type!")]
SomeType,
}
var enumValue = PocoType.SomeType;
// Output : This is a type!
var description = enumValue.GetEnumDesciption();
ToGuid
Converts int value to Guid.
Usage
int intValue = 1;
// Output : "00000001-0000-0000-0000-000000000000"
var guidValue = intValue.ToGuid();
ToObjectId
Converts int value to ObjectId.
Usage
int intValue = 1;
// Output : "000000000000000000000001"
var objectIdValue = intValue.ToObjectId();
ToJson
Converts value to json.
Usage
var poco = new Product
{
Id = 1,
Name = "T-Shirt"
};
var value = poco.ToJson();
Output;
{
Id : 1,
Name : "T-Shirt"
}
ToObject
Converts json string value to specified T
type.
CustomErrorResponse
Prepares custom validation model for response.
IsBetween Methods
Performs a "between comparision" for dates or times.
Usage
var date = new DateTime(2022, 5, 5, 5, 5, 5);
bool between = date.IsBetween(time1, time2, false);
Localizer Methods
Creates localizer instance if IStringLocalizerFactory registered to service collection.
Usage
...
var localizer = httpContext.RequestServices.GetRequiredLocalizerInstance<SharedResource>();
...
Networking
Provides access host machine's network statistics.
GetIPAddressesAsync
Gets all the IP addresses of the server machine hosting the application.
public static async Task<IPAddress[]> GetIPAddressesAsync()
GetIPAddressAsync
Returns the (first available or chosen) IP address of the server machine.
public static async Task<IPAddress> GetIPAddressAsync(int num = 0)
HasIPAddressAsync
Checks if the given IP address is one of the IP addresses registered to the server machine hosting the application.
public static async Task<bool> HasIPAddressAsync(IPAddress ipAddress)
This method returns TRUE if the IP address is registered, FALSE otherwise.
Other overload;
public static async Task<bool> HasIPAddressAsync(string ipAddress)
This method returns TRUE if the IP address is registered, FALSE otherwise.
This class have sync
versions of this methods. For more information.
Filter Helpers
It contains helper methods for filtering operations. For more.