Developer

Forum

 

Dapper

Refit

Serilog

Dotnet cli

Serilog

Flexible, structured events — log file convenience.

Serilog api

Clean and modern

Code-as-configuration

Simple 'levelled' logging

Thread-safe

Highly concurrent

async aware

No default ambient state

What the difference

  • Event capturing with message templates
  • Correlation through Event enrichment

Message templates

Text Logging

Structured logging


Log($"Commit took {0} ms", elapsed);
Log(new {
    Event = "commit",
    Elapsed = elapsed
});
  • Easy to write
  • Easy to read logs
  • Loss of information
  • Hard to parse by machine
  • Cumbersome to write
  • Hard to read logs
  • Easy to parse

Demo

Message templates


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

correlation

Syntetic correlation

Natural correlation

CorrelationId

RequestId

ThreadId

MachineName

ProcessName

Application

EmployeeId

RequestPath

Url

...

correlation

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 

...

Enrichment api

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

Dapper - a simple object mapper for .Net

 

And it is really fast!

What does dapper give us?

  • Sql injection safe parameters
  • Handles parameter list out of the box
  • Maps SqlDataReader to C# types
  • And did I say that it is fast
  • Visible sql to easily test in SqlManagementStudio
  • Use Sql Table-Valued parameters

Parameters

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

Parameters

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

Parameters

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

Parameters

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

REsult

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

REsult

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

Multiple Resultsets

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

REsult

Multi mapping

Come talk to me

Multi type

Come talk to me

Buffering

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)

Refit

The automatic type-safe REST library for .NET Core, Xamarin and .NET

What is refit

Strongly typed rest client 

Uses HttpClient

Declare rest api contract via c# interface

Defining a rest 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);
    }

Getting a Client

// 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")
);

USING A CLIENT

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));
        }
}

Refit Gotchas

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)

Refit Gotchas 2

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!

Dotnet cli

AcademicWork Templates

Best Practices

Repository CI/CD setup

Environment setup (not yet)

Developer

By fhelje

Developer

  • 474