European ASP.NET 4.5 Hosting BLOG

BLOG about ASP.NET 4, ASP.NET 4.5 Hosting and Its Technology - Dedicated to European Windows Hosting Customer

European ASP.NET Core Hosting :: Using Docker to Store ASP.NET Core Kestrel Certificates

clock March 22, 2019 08:48 by author Scott

When working with ASP.Net Core in Docker containers, it can be cumbersome to deal with certificates. While there is a documentation about setting certificate for dev environment, there’s no real guidance on how to make it work when deploying containers in a Swarm for example.
In this article we are going to see how to take advantage of Docker secrets to store ASP.Net Core Kestrel certificates in the context of Docker Swarm.

Hosting the service

First of all, we are going to create à Swarm service on our machine that use the sample Asp.Net Core app. The purpose of this article is to make SSL work in the container withoutchanging anything to an existing image.

docker service create --name mywebsite --publish published=8080,target=80,mode=host microsoft/dotnet-samples:aspnetapp

We are creating service mywebsite, publishing only one port 8080 bound to the port 80 in the container using the host mode and using the image microsoft/dotnet-samples:aspnetapp. Please note that you can use others configuration (for example expose port in routing mesh mode).

Preparing the certificate

We need a certificate. It can be created via an external certificate authority but here for the sake of the article, we are going to create a self signed certificate (of course, don’t use this in production). We are using Powershell for this task (you can skip this if you already have a pfx certificate signed by a real CA).

$cert = New-SelfSignedCertificate -DnsName "mywebsite" -CertStoreLocation "cert:\LocalMachine\My"
$password = ConvertTo-SecureString -String "mylittlesecret" -Force -AsPlainText
$cert | Export-PfxCertificate -FilePath c:\temp\mywebsite.pfx -Password $password

Once you have your pfx, we are goind to unprotect it from the password. It might be seem unsecure but when it will be added to the Docker secret store, it will be stored securedly. For this task I will use OpenSSL (not possible with Powerhsell as far as I know). OpenSSL is provided with Git for example.

& 'C:\Program Files\Git\mingw64\bin\openssl.exe' pkcs12 -in c:\temp\mywebsite.pfx -nodes -out c:\temp\mywebsite.pem -passin pass:mylittlesecret
& 'C:\Program Files\Git\mingw64\bin\openssl.exe' pkcs12 -export -in c:\temp\mywebsite.pem -out c:\temp\mywebsite.unprotected.pfx -passout pass:

Now that the pfx is un protected, we can add it to the docker store certificate and display it.

docker secret create kestrelcertificate c:\temp\mywebsite.unprotected.pfx

docker secret ls

ID                          NAME                 DRIVER              CREATED             UPDATED

iapy6rolt7po1mwm9aw6z0qc5   kestrelcertificate                       13 minutes ago      13 minutes ago

Our secret being in the store, you can delete (or store securely somewhere else your pfx).

Making it work

We can now update our service to take in account this secret. When adding a secret to a service, Docker will create a file in a specific directory containing the value of the secret. On Windows it’s c:\programdata\docker\secrets.

Let’s update our service and see what happened inside the container.

docker service update --secret-add kestrelcertificate mywebsite

docker exec 4b51e736ce65 cmd.exe /c dir c:\programdata\docker\secrets

 Volume in drive C has no label.
 Volume Serial Number is 3CBB-E577

 Directory of c:\programdata\docker\secrets

11/15/2018  10:38 PM    <DIR>          .
11/15/2018  10:38 PM    <DIR>          ..
11/15/2018  10:38 PM    <SYMLINK>      kestrelcertificate [C:\ProgramData\Docker\internal\secrets\iapy6rolt7po1mwm9aw6z0qc5]
               1 File(s)              0 bytes
               2 Dir(s)  21,245,009,920 bytes free

We can see that our secret exists and is named kestrelcertificate, as we named it in the command line.

We can therefore update our service to remove the old binding on port 80, replace it with a binding on port 443, tell Kestrel to use this port and finally give Kestrel the path of our secret.
This can be done with only one command:

docker service update --publish-rm published=8080,target=80,mode=host --publish-add published=8080,target=443,mode=host --env-add ASPNETCORE_URLS=https://+:443 --env-add Kestrel__Certificates__Default__Path=c:\programdata\docker\secrets\kestrelcertificate mywebsite

Wait a while that your service update, try to browse and it should work ! Well, actually it should only works on Linux.

Making it work on Windows

If you try to have a look a the logs generated by your service, you should end with something like this.

docker service logs mywebsite

mywebsite.1.uy3vm8txwxec@nmarchand-lt    | crit: Microsoft.AspNetCore.Server.Kestrel[0]
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |       Unable to start Kestrel.
mywebsite.1.uy3vm8txwxec@nmarchand-lt    | Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Unspecified error
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Internal.Cryptography.Pal.CertificatePal.FromBlobOrFile(Byte[] rawData, String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(String fileName, String password, X509KeyStorageFlags keyStorageFlags)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(String fileName, String password)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.LoadCertificate(CertificateConfig certInfo, String endpointName)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.LoadDefaultCert(ConfigurationReader configReader)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.Load()
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.ValidateOptions()
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |
mywebsite.1.uy3vm8txwxec@nmarchand-lt    | Unhandled Exception: Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Unspecified error
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Internal.Cryptography.Pal.CertificatePal.FromBlobOrFile(Byte[] rawData, String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(String fileName, String password, X509KeyStorageFlags keyStorageFlags)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(String fileName, String password)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.LoadCertificate(CertificateConfig certInfo, String endpointName)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.LoadDefaultCert(ConfigurationReader configReader)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.Load()
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.ValidateOptions()
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Hosting.Internal.WebHost.StartAsync(CancellationToken cancellationToken)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Hosting.WebHostExtensions.RunAsync(IWebHost host, CancellationToken token, String shutdownMessage)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Hosting.WebHostExtensions.RunAsync(IWebHost host, CancellationToken token)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Hosting.WebHostExtensions.Run(IWebHost host)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at aspnetapp.Program.Main(String[] args) in C:\app\aspnetapp\Program.cs:line 18

We can see a nasty bug of Windows here (Github issue).
What did happen ? If you look closely at the dir command we made in the container, you’ll see that the secret is not really a file but instead a symbolic link to an other file. Unfortunately, Windows is unable to use a certificate that is a symlink. One solution could be to manually read the certificate with File.ReadAllBytes() and pass it to the constructor of X509Certificate. However, it would be against the purpose of this article which is to not modify the Docker image.

We can find a workaround by browsing the Docker documentation which states that the real file containing the secret (which in fact is the target of the symlink) can be found in the path c:\programdata\docker\internal\secrets\<secretid> where secretid is the id of the secret (as shown by docker secret ls).

We can update our service to change the path by updating the environment variable. It now works also on windows!

docker service update --env-rm
Kestrel__Certificates__Default__Path=c:\programdata\docker\secrets\kestrelcertificate --env-add Kestrel__Certificates__Default__Path=c:\programdata\docker\internal\secrets\iapy6rolt7po1mwm9aw6z0qc5 mywebsite

docker logs 7b54cdc42a86

Hosting environment: Production
Content root path: C:\app
Now listening on: https://[::]:443
Application started. Press Ctrl+C to shut down.

Final word

We have seen in this article how to use Docker secrets to store ASP.Net Core Kestrel certificates in our Docker Swarm. However, please keep in mind that the Windows workaround should be used with care as written in the Docker documentation.

Another word also about SSL Offloading : I know that usually the reverse proxy (Nginx, Traefik, etc.) is used to be the SSL termination but sometimes you still want SSL end to end. 



ASP.NET Core 2.2.3 Hosting - HostForLIFE.eu :: Show Alert Message Box using JavaScript jQuery in ASP.NET

clock March 20, 2019 12:03 by author Peter

Today, I will explain you about Showing Alert Message Box from client Side on ASP.NET Core 2.2.3 using JavaScript and JQuery. In the Following, this is jQuery Code Snippet to show/display alert message:

//Function for jQuery
$(function () {
$('#btnUsingjQuery').click(function () {
alert('Alert using jQuery Function!');
});
});

And, this is the JavaScript code to show or display alert message:
//Function for JavaScript
function alertUsingJavaScript() {
alert('Alert using JavaScript Function!');
}

Show Alert Message – (.aspx)
Here is the code snippet for your .aspx webpage:

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>How to show alert message using jQuery/JavaScript in ASP.NET
</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.js">
</script>
<script type="text/javascript">
//Function for JavaScript
function alertUsingJavaScript() {
alert('Alert using JavaScript Function!');
}
//Function for jQuery
$(function () {
$('#btnUsingjQuery').click(function () {
alert('Alert using jQuery Function!');
});
});
</script>
</head>
<body>
<form id="form1" runat="server">
<h4>
Show alert message using JavaScript/jQuery in Asp.net</h4>
<div>
<input id="btnUsingJavaScript" type="button" onclick="alertUsingJavaScript()"
value="Using JavaScript" />
<input id="btnUsingjQuery" type="button" value="Using jQuery" />
</div>
</form>
</body>
</html>

You can see i’d added onclick=”alertUsingJavaScript()” when calling function using JavaScript. Just in case of jQuery, we are able to bind click event directly on window load as $(‘#btnUsingjQuery’).click(function () ); });. And here is the result
:



European ASP.NET Core Hosting :: ASP.NET Core with MySQL and Entity Framework Core

clock March 14, 2019 07:58 by author Scott

This article shows how to use MySQL with ASP.NET Core 2.1 using Entity Framework Core.

The Entity Framework MySQL package can be downloaded using the NuGet package Pomelo.EntityFrameworkCore.MySql. At present no official provider from MySQL exists for Entity Framework Core which can be used in an ASP.NET Core application.

The Pomelo.EntityFrameworkCore.MySql package can be added to the csproj file.

<Project Sdk="Microsoft.NET.Sdk"> 

  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AssemblyName>DataAccessMySqlProvider</AssemblyName>
    <PackageId>DataAccessMySqlProvider</PackageId>
  </PropertyGroup> 

  <ItemGroup>
    <ProjectReference Include="..\DomainModel\DomainModel.csproj" />
  </ItemGroup> 

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App"  />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.2.0" PrivateAssets="All" />
    <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.0.1" />
  </ItemGroup> 

  <ItemGroup>
    <Folder Include="Properties\" />
  </ItemGroup> 

</Project>

The web project which loads the project with EF Core needs to support migrations if you wish to create a database this way.

<Project Sdk="Microsoft.NET.Sdk.Web"> 

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <AssemblyName>AspNetCoreMultipleProject</AssemblyName>
    <PackageId>AspNet5MultipleProject</PackageId>
  </PropertyGroup> 

  <ItemGroup>
    <Content Update="wwwroot\**\*;Views;Areas\**\Views;appsettings.json;config.json;web.config">
      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
    </Content>
  </ItemGroup> 

  <ItemGroup>
    <ProjectReference Include="..\DataAccessMsSqlServerProvider\DataAccessMsSqlServerProvider.csproj" />
    <ProjectReference Include="..\DataAccessMySqlProvider\DataAccessMySqlProvider.csproj" />
    <ProjectReference Include="..\DataAccessPostgreSqlProvider\DataAccessPostgreSqlProvider.csproj"
/>
    <ProjectReference Include="..\DataAccessSqliteProvider\DataAccessSqliteProvider.csproj" />
    <ProjectReference Include="..\DomainModel\DomainModel.csproj" />
  </ItemGroup>   

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App"  />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.2.0" PrivateAssets="All" />
  </ItemGroup>
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
  </ItemGroup>
    <ItemGroup>
      <Folder Include="Migrations\" />
    </ItemGroup>
</Project>

An EfCore DbContext can be added like any other context supported by Entity Framework Core.

using System;
using System.Linq;
using DomainModel.Model;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; 

namespace DataAccessMySqlProvider
{
    // >dotnet ef migration add testMigration
    public class DomainModelMySqlContext : DbContext
    {
        public DomainModelMySqlContext(DbContextOptions<DomainModelMySqlContext> options) :base(options)
        { }         

        public DbSet<DataEventRecord> DataEventRecords { get; set; } 

        public DbSet<SourceInfo> SourceInfos { get; set; } 

        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<DataEventRecord>().HasKey(m => m.DataEventRecordId);
            builder.Entity<SourceInfo>().HasKey(m => m.SourceInfoId); 

            // shadow properties
            builder.Entity<DataEventRecord>().Property<DateTime>("UpdatedTimestamp");
            builder.Entity<SourceInfo>().Property<DateTime>("UpdatedTimestamp"); 

            base.OnModelCreating(builder);
        } 

        public override int SaveChanges()
        {
            ChangeTracker.DetectChanges(); 

            updateUpdatedProperty<SourceInfo>();
            updateUpdatedProperty<DataEventRecord>(); 

            return base.SaveChanges();
        } 

        private void updateUpdatedProperty<T>() where T : class
        {
            var modifiedSourceInfo =
                ChangeTracker.Entries<T>()
                    .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified); 

            foreach (var entry in modifiedSourceInfo)
            {
                entry.Property("UpdatedTimestamp").CurrentValue = DateTime.UtcNow;
            }
        }
    }
}

In an ASP.NET Core web application, the DbContext is added to the application in the startup class. In this example, the DbContext is defined in a different class library. The MigrationsAssembly needs to be defined, so that the migrations will work. If the context and the migrations are defined in the same assembly, this is not required.

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile("config.json", optional: true, reloadOnChange: true); 

    Configuration = builder.Build();
}         
public void ConfigureServices(IServiceCollection services)
{  
    var sqlConnectionString = Configuration.GetConnectionString("DataAccessMySqlProvider"); 

    services.AddDbContext<DomainModelMySqlContext>(options =>
        options.UseMySQL(
            sqlConnectionString,
            b => b.MigrationsAssembly("AspNetCoreMultipleProject")
        )
    );
}

The application uses the configuration from the config.json. This file is used to get the MySQL connection string, which is used in the Startup class.

{
    "ConnectionStrings": { 
        "DataAccessMySqlProvider": "server=localhost;userid=store;password=3333;database=store;"
        }
    }
}

MySQL workbench can be used to add the schema ‘store to the MySQL database. The user ‘store is also required, which must match the defined user in the connection string. If you configure the MySQL database differently, then you need to change the connection string in the config.json file.

Now the database migrations can be created and the database can be updated.

>
> dotnet ef migrations add mySqlMigration --context DomainModelMySqlContext
>
> dotnet ef database update --context DomainModelMySqlContext
>

If successful, the tables are created.

The MySQL provider can be used in a MVC controller using construction injection.

using System.Collections.Generic;
using DomainModel;
using DomainModel.Model;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json; 

namespace AspNet5MultipleProject.Controllers
{
    [Route("api/[controller]")]
    public class DataEventRecordsController : Controller
    {
        private readonly IDataAccessProvider _dataAccessProvider; 

        public DataEventRecordsController(IDataAccessProvider dataAccessProvider)
        {
            _dataAccessProvider = dataAccessProvider;
        } 

        [HttpGet]
        public IEnumerable<DataEventRecord> Get()
        {
            return _dataAccessProvider.GetDataEventRecords();
        } 

        [HttpGet]
        [Route("SourceInfos")]
        public IEnumerable<SourceInfo> GetSourceInfos(bool withChildren)
        {
            return _dataAccessProvider.GetSourceInfos(withChildren);
        } 

        [HttpGet("{id}")]
        public DataEventRecord Get(long id)
        {
            return _dataAccessProvider.GetDataEventRecord(id);
        } 

        [HttpPost]
        public void Post([FromBody]DataEventRecord value)
        {
            _dataAccessProvider.AddDataEventRecord(value);
        } 

        [HttpPut("{id}")]
        public void Put(long id, [FromBody]DataEventRecord value)
        {
            _dataAccessProvider.UpdateDataEventRecord(id, value);
        } 

        [HttpDelete("{id}")]
        public void Delete(long id)
        {
            _dataAccessProvider.DeleteDataEventRecord(id);
        }
    }
}

The controller api can be called using Fiddler:

POST http://localhost:5000/api/dataeventrecords HTTP/1.1
User-Agent: Fiddler
Host: localhost:5000
Content-Length: 135
Content-Type: application/json;  

{
  "DataEventRecordId":3,
  "Name":"Funny data",
  "Description":"yes",
  "Timestamp":"2015-12-27T08:31:35Z",
   "SourceInfo":
  {
    "SourceInfoId":0,
    "Name":"Beauty",
    "Description":"second Source",
    "Timestamp":"2015-12-23T08:31:35+01:00",
    "DataEventRecords":[]
  },
 "SourceInfoId":0
}

The data is added to the database as required.



European ASP.NET Core Hosting :: Smart Logging Middleware for ASP.NET Core

clock March 13, 2019 09:44 by author Scott

ASP.NET Core comes with request logging built-in. This is great for getting an app up-and-running, but the events are not as descriptive or efficient as hand-crafted request logging can be. Here is a typical trace from a single GET request to /about:



If the request fails because of an error, you'll see instead:



While these fine-grained events are sometimes useful, they take up storage space and bandwidth, and add noise to the log.

Spreading the information across many events also makes it harder to perform some analyses, for example, the HTTP method and elapsed time are on different events, making it hard to separate the timings of GET and POST requests to the same RequestPath.

In production we want:

One "infrastructure" event per normal request, with basic HTTP information attached

Extra information to help with debugging if a request fails due to a server-side (5xx) error

The same format and event type for all requests, so that logs from both successful and failed requests can be easily grouped, sorted and analyzed together

Here's the format we're aiming for, expanded so that you can see all of the attached properties:

Instead of five infrastructure events + one application event, just a single infrastructure event is logged. This makes the application's own "Hello" event much easier to spot.

This post includes the full source code for the middleware, so you can take it and modify it to include the information you find most useful.

Step 1: Install and Configure Serilog

ASP.NET Core includes some basic logging providers, but to get the most out of it you'll need to plug in a full logging framework like Serilog. If you haven't done that already, these instructions should have you up and running quickly.

Step 2: Turn off Information events from Microsoft and System

Where Serilog is configured, add level overrides for the Microsoft and System namespaces:

Log.Logger = new LoggerConfiguration() 
    .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
    .MinimumLevel.Override("System", LogEventLevel.Warning)
    // Other logger configuration

These can be turned back on for debugging when they're needed.

If you're using appsettings.json configuration, check out the level overrides example in the README.

Step 3: Add the SerilogMiddleware class

The SerilogMiddleware class hooks into the request processing pipeline to collect information about the requests:

using Microsoft.AspNetCore.Http; 
using Serilog; 
using Serilog.Events; 
using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Threading.Tasks;

namespace Datalust.SerilogMiddlewareExample.Diagnostics 
{
    class SerilogMiddleware
    {
        const string MessageTemplate =
            "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";

        static readonly ILogger Log = Serilog.Log.ForContext<SerilogMiddleware>();

        readonly RequestDelegate _next;

        public SerilogMiddleware(RequestDelegate next)
        {
            if (next == null) throw new ArgumentNullException(nameof(next));
            _next = next;
        }

        public async Task Invoke(HttpContext httpContext)
        {
            if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));

            var sw = Stopwatch.StartNew();
            try
            {
                await _next(httpContext);
                sw.Stop();

                var statusCode = httpContext.Response?.StatusCode;
                var level = statusCode > 499 ? LogEventLevel.Error : LogEventLevel.Information;

                var log = level == LogEventLevel.Error ? LogForErrorContext(httpContext) : Log;
                log.Write(level, MessageTemplate, httpContext.Request.Method, httpContext.Request.Path, statusCode, sw.Elapsed.TotalMilliseconds);
            }
            // Never caught, because `LogException()` returns false.
            catch (Exception ex) when (LogException(httpContext, sw, ex)) { }
        }

        static bool LogException(HttpContext httpContext, Stopwatch sw, Exception ex)
        {
            sw.Stop();

            LogForErrorContext(httpContext)
                .Error(ex, MessageTemplate, httpContext.Request.Method, httpContext.Request.Path, 500, sw.Elapsed.TotalMilliseconds);

            return false;
        }

        static ILogger LogForErrorContext(HttpContext httpContext)
        {
            var request = httpContext.Request;

            var result = Log
                .ForContext("RequestHeaders", request.Headers.ToDictionary(h => h.Key, h => h.Value.ToString()), destructureObjects: true)
                .ForContext("RequestHost", request.Host)
                .ForContext("RequestProtocol", request.Protocol);

            if (request.HasFormContentType)
                result = result.ForContext("RequestForm", request.Form.ToDictionary(v => v.Key, v => v.Value.ToString()));

            return result;
        }
    }
}

I've deliberately kept this to a single (somewhat ugly!) source file so that it's easy to copy, paste and modify.

There are a lot of design details and trade-offs involved. You can see that when a request is deemed to have failed, some additional information is attached, including the headers sent by the client, and the form data if any.

Step 4: Add the middleware to the pipeline

In your application's Startup.cs file, you can add the middleware either before or after the outermost exception handling components. If you add it before this, you won't get the full Exception information in any error events, but you will always be able to record the exact status code returned to the client. Adding the middleware into the pipeline inside the exception handling components (i.e., after them in the source code) will provide all exception detail but has to assume that the status code is 500 in this case.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
                      ILoggerFactory loggerFactory)
{
    loggerFactory.AddSerilog();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseMiddleware<SerilogMiddleware>();

    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

Step 5: Profit!

Now you have a stream of request log events each with paths, status codes, timings, and exceptions:

We've seen how a simple customized logging strategy can not only produce cleaner events, but also reduce the volume of infrastructure log events.



ASP.NET Core Hosting :: How to Upload File using C# | SFTP Server

clock March 4, 2019 07:56 by author Scott

Although there are many Graphical Tools available for sending files to a server using SFTP. But as a developer, we may have a scenario where we need to upload a file to SFTP Serverfrom our Code.

A few days ago a job assigned to me was to develop a Task Scheduler for generating XML files daily on a specific time of the day & send these files on a Remote Server using File Transfer Protocol in a secure way.

In .Net Framework there are many Libraries available for uploading files to another machine using File Transfer Protocol but most of the libraries don’t work with .Net Core. In this Tutorial, we will develop a very simple SFTP client using C# for .Net Core.

Before start let’s have a quick look at SFTP.

What is SFTP?

SFTP stands for SSH File Transfer Protocol or Secure File Transfer Protocol. It is a protocol used to transfer files between remote machines over a secure shell.

 

In almost all cases, SFTP is preferable over FTP because of security features. FTP is not a secure protocol & it should only be used on a trusted network.

Choosing Library for C#

A lot of search & after testing many libraries I finally met with SSH.NET which was working perfectly with .Net Core 2.2 project & the good thing was that It does its job in a very few lines of Code.

So we’ll use SSH.NET

What is SSH.NET?

SSH.NET is an open-source library available at Nuget for .NET to work over SFTP. It is also optimized for parallelism to achieve the best possible performance. It was inspired by Sharp.SSH library which was ported from Java. This library is a complete rewrite using .Net, without any third party dependencies.

Here are the features of SSH.NET: 

Creating Project

I’m in love with VS Code right after its first release so I’m going to use VS Code for creating project to upload/transfer a file to a remote server using SFTP.

Create a console application using this command

dotnet new console

Installing SSH.NET

I won’t recommend you to install the latest version of SSH.NET. It has a bug, it can be stuck on transferring the file to the remote location.

version 2016.0.0 is perfect. 

run this command to install the library from NuGet

using package manager

Install-Package SSH.NET -Version 2016.0.0

or using .Net CLI

dotnet add package SSH.NET --version 2016.0.0

Code

Finally, It’s time to create a class for SFTP Client Code.

Create a file with the name as “SendFileToServer” & add the below code

using Renci.SshNet

public static class SendFileToServer
{
// Enter your host name or IP here
private static string host = "127.0.0.1";

// Enter your sftp username here
private static string username = "sftp";

// Enter your sftp password here
private static string password = "12345";
public static int Send(string fileName)
{
var connectionInfo = new ConnectionInfo(host, "sftp", new PasswordAuthenticationMethod(username, password));

// Upload File
using (var sftp = new SftpClient(connectionInfo)){

sftp.Connect();
//sftp.ChangeDirectory("/MyFolder");
using (var uplfileStream = System.IO.File.OpenRead(fileName)){
sftp.UploadFile(uplfileStream, fileName, true);
}
sftp.Disconnect();
}
return 0;
}
}

Now you can call this Method to transfer a file to SFTP Server like this

SendFileToServer.Send("myFile.txt");

“myFile.txt” is the name of the file which should be located in your project root directory. 



About HostForLIFE.eu

HostForLIFE.eu is European Windows Hosting Provider which focuses on Windows Platform only. We deliver on-demand hosting solutions including Shared hosting, Reseller Hosting, Cloud Hosting, Dedicated Servers, and IT as a Service for companies of all sizes.

We have offered the latest Windows 2016 Hosting, ASP.NET Core 2.2.1 Hosting, ASP.NET MVC 6 Hosting and SQL 2017 Hosting.


Tag cloud

Sign in