Flexible, structured events — log file convenience.
Clean and modern
Code-as-configuration
Simple 'levelled' logging
Thread-safe
Highly concurrent
async aware
No default ambient state
Text Logging
Structured logging
Log($"Commit took {0} ms", elapsed);Log(new {
Event = "commit",
Elapsed = elapsed
});
log.information("Processed {@sensorInput} in {Elapsed} ms", sensorInput, elapsed);MessageTemplate
Timestamp
SensorInput
Elapsed
SensorIProcessed {@sensorInput} in {Elapsed} msnput
2018-03-02 12:00:00.123
24.7
Lat
Long
132.2
153
[2018 ...] Processed at Lat: 24.7, Long 132.2 in 153 ms
json.log
ELASTIC
CorrelationId
RequestId
ThreadId
MachineName
ProcessName
Application
EmployeeId
RequestPath
Url
...
When searching through logs we use different correlation to find the IMPORTANT logs.
Origination request id to search over multiple service boundaries
Logs grouped by request
...
| Examples | Api | |
|---|---|---|
| Fixed values | App Name Environment Version |
Enrich.WithProperty("App", "Demo") |
| Ambient state | ThreadId ProcessId Http State |
Enrich.WithThreadId() |
| Scoped state | MessageId OrderId |
Enrich.FromLogContext() LogContext.PushProperty("App", "Demo") |
| Local values | SourceContext | Log.ForContext<Homecontroller>() |
Dapper - a simple object mapper for .Net
And it is really fast!
string sql = @"
INSERT INTO Customers (CustomerName)
Values (@CustomerName);";
using (var connection = new SqlConnection(cs))
{
connection.Open();
var affectedRows = connection.Execute(
sql,
new {CustomerName = "Mark"}
);
Console.WriteLine(affectedRows);
}Anonymous parameter class
var sql = "Invoice_Insert";
using (var connection = My.ConnectionFactory())
{
connection.Open();
DynamicParameters parameter = new DynamicParameters();
parameter.Add("@Code", "Many_Insert_0", DbType.String, ParameterDirection.Input);
parameter.Add("@RowCount", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);
connection.Execute(sql,
parameter,
commandType: CommandType.StoredProcedure);
int rowCount = parameter.Get<int>("@RowCount");
}DynamicParameters
var sql = "SELECT * FROM Invoice WHERE Id IN @Ids;";
using (var connection = My.ConnectionFactory())
{
connection.Open();
var invoices = connection.Query<Invoice>(
sql,
new {Ids = new[] { 1, 2 }}
).ToList();
}List
public class JobCollectiveBargainRepository : IJobCollectiveBargainRepository
{
private const string typeName = "COLLECTIVEBARGAIN_ARTICLE";
private static readonly SqlMetaData[] ArticleRow = {
new SqlMetaData("BisUniqueId", SqlDbType.BigInt),
new SqlMetaData("PayAmount", SqlDbType.Decimal, 10,2),
new SqlMetaData("PayPercent", SqlDbType.Decimal, 5,2),
};
public async Task UpdateArticles(List<Article> artList) {
var parameters = new { articles = artList.Select(CreateRow)
.AsTableValuedParameter(typeName) };
using (var conn = new SqlConnection(cs)) {
await context.ExecuteAsync(
storeProcName,
parameters,
commandType: CommandType.StoredProcedure);
}
}
private static SqlDataRecord CreateRow(Article x)
{
var row = new SqlDataRecord(ArticleRow);
row.SetInt64(0, x.BisUniqueId);
row.SetDecimal(1, x.PayAmount);
row.SetDecimal(2, x.PayPercent);
return row;
}
}TABLE-VALUED
public class Article {
public int Id { get; set; }
public string Name { get; set; }
}
public async Task<IEnumerable<Article>> GetArticles() {
using (var conn = new SqlConnection(cs)) {
await context.QueryAsync<Article>(
"SELECT ArticleId as Id, Title as Name FROM Article"
);
}
}Strongly typed
public static string SqlGetRecordByUrlUniqueId = @"
SELECT r.[Id]
...
FROM [dbo].[GflRecord] as r
WHERE r.UrlUniqueId = @UrlUniqueId
SELECT si.[Id] AS Id
...
FROM [dbo].[GflRecord] as r
INNER JOIN [dbo].[SalaryInformation] as si on r.fkSalaryInformationId = si.Id
WHERE r.UrlUniqueId = @UrlUniqueId
SELECT ci.[Id] AS Id
...
FROM [dbo].[GflRecord] as r
INNER JOIN [dbo].[ClientInformation] as ci on r.fkClientInformationId = ci.Id
WHERE r.UrlUniqueId = @UrlUniqueId
";
using (var conn = new SqlConnection(_config.ConnectionString)) {
var grid = await conn.QueryMultipleAsync(
SqlBuilder.SqlGetRecordByUrlUniqueId,
new { UrlUniqueId = urlUniqueId });
var record = (await grid.ReadAsync<GflRecordSql>()).SingleOrDefault();
var salary = (await grid.ReadAsync<GflSalarySql>()).SingleOrDefault();
var client = (await grid.ReadAsync<GflClientSql>()).SingleOrDefault();
return record?.ToModel(salary, client);
}
Multi result
string sql = @"
SELECT * FROM Invoice WHERE InvoiceID = @InvoiceID;
SELECT * FROM InvoiceItem WHERE InvoiceID = @InvoiceID;
";
using (var connection = My.ConnectionFactory())
{
connection.Open();
using (var multi = connection.QueryMultiple(sql, new {InvoiceID = 1}))
{
var invoice = multi.Read<Invoice>().First();
var invoiceItems = multi.Read<InvoiceItem>().ToList();
}
}List
Multi mapping
Come talk to me
Multi type
Come talk to me
Dapper buffers result by default
(speed for small resultsets)
For large resultset 10000 - Gb of data
turning off Buffering can help with memory issues
connection.Query<OrderDetail>(sql, buffered: false)The automatic type-safe REST library for .NET Core, Xamarin and .NET
Strongly typed rest client
Uses HttpClient
Declare rest api contract via c# interface
public interface IAdvertApiV3
{
[Get("/api/v3/adverts/jobref/{jobref}")]
Task<IEnumerable<Models.Response.Advert>> GetByJobRef(long jobref);
[Get("/api/v3/adverts/{advertRef}")]
Task<Models.Response.Advert> GetByAdvertRef(long advertRef);
[Get("/api/v3/adverts/latest/{count}")]
Task<List<Models.Response.Advert>> GetLatest(int count);
[Post("/api/v3/adverts/jobrefs")]
Task<IEnumerable<Models.Response.Advert>> PostByJobRef([Body]long[] jobRef);
[Post("/api/v3/adverts/advertrefs")]
Task<IEnumerable<Models.Response.Advert>> PostByAdvertRef([Body]long[] advertRefs);
[Get("/api/v3/adverts")]
Task<IEnumerable<long>> GetChangedAdvertsSince([Query] DateTime date);
[Post("/api/v3/adverts")]
Task<long> Post([Body]CreateAdvertRequest advert);
}// DEPENDENCY INJECTION
var tokenCache = new TokenCache();
var apiConf = new ApiConfigurationService(config);
var refitFactory = new RefitFactory.RefitFactory(
apiConf,
tokenCache,
new HttpContextAccessor()
);
// Api s
services.AddTransient(x =>
refitFactory.GetRestService<IMailApi>("MailApi")
);namespace AcademicWork.Gfl.Api.Controllers.Api
{
[Produces("application/json")]
[Route("api/gfl")]
public class GflController : Controller
{
private readonly IMailService _mailService;
public GflController( IMailService mailService)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_mailService = mailService;
}
[HttpPost()]
public async Task<IActionResult> Post([FromBody] CreateGflRequest input)
{
var body = await _mailService.RenderBodyAsync(
input.MailType,
input.Sender,
input.UrlUniqueId.ToUrl(),
input.LoginKey
);
return Ok(data.ToResponse(body));
}
}http://localhost/api/values?id=1&id=2
Very hard to get to work
"Work around" or better api designs
http://localhost/api/values?ids=1,2
(Requires pluming in MVC, come see me)
Or just use POST (Or Rickards user will bite U)
YOU MUST HAVE A DIRECT
REFERENCE TO REFIT IN THE
SAME PROJECT AS YOU ARE GOING TO USE IT!
Refit generate a class when you compile that is used as the concrete HttpClient!
AcademicWork Templates
Best Practices
Repository CI/CD setup
Environment setup (not yet)