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 - HostForLIFE.eu :: Javascript, CSS, HTML in ASP.NET Core

clock April 12, 2019 09:45 by author Scott

This article will teach you how to include and customize the use of static files in ASP .NET Core web applications. It is not a tutorial on front-end web development.

If your ASP .NET Core web app has a front end – whether it’s a collection of MVC Views or a Single-Page Application (SPA) – you will need to include static files in your application. This includes (but is not limited to): JavaScript, CSS, HTML and various image files.

When you create a new web app using one of the built-in templates (MVC or Razor Pages), you should see a “wwwroot” folder in the Solution Explorer. This points to a physical folder in your file system that contains the same files seen from Visual Studio. However, this location can be configured, you can have multiple locations with static files, and you can enable/disable static files in your application if desired. In fact, you have to “opt in” to static files in your middleware pipeline.

Configuring Static Files via Middleware

Let’s start by observing the Startup.cs configuration file. We’ve seen this file several times throughout this blog series. In the Configure() method, you’ll find the familiar method call to enable the use of static files.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   ...
   app.UseStaticFiles();
   ...
   app.UseMvc(...);
}

This call to app.UseStaticFiles() ensures that static files can be served from the designated location, e.g. wwwroot.

It’s useful to note the placement of this line of code. It appears before app.UseMvc(), which is very important. This ensures that static file requests can be processed and sent back to the web browser without having to touch the MVC middleware. This becomes even more important when authentication is used.

In the code below, you can see the familiar call to app.UseStaticFiles() once again. However, there is also a call to app.UseAuthentication(). It’s important for the authentication call to appear after the call to use static files. This ensure that the authentication process isn’t triggered when it isn’t needed.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   ...
   app.UseStaticFiles();
   ...
   app.UseAuthentication();
   ...
   app.UseMvc(...);
}

By using the middleware pipeline in this way, you can “short-circuit” the pipeline when a request has been fulfilled by a specific middleware layer. If a static file has been successfully served using the Static Files middleware, it prevents the next layers of middleware (i.e. authentication, MVC) from processing the request.

Customizing Locations for Static Files

It may be convenient to have the default web templates create a location for your static files and also enable the use of those static files. As you’ve already seen, enabling static files isn’t magic. Removing the call to app.useStaticFiles() will disable static files from being served. In fact, the location for static files isn’t magic either.

public class Program
{
   ...
   public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
      WebHost.CreateDefaultBuilder(args)
         .UseStartup<Startup>();
}

Behind the scenes, this method call sets the “content root” to the current directory, which contains the “wwwroot” folder, your project’s “web root”. These can both be customized.

WebHost.CreateDefaultBuilder(args).UseContentRoot("c:\\<content-root>")

WebHost.CreateDefaultBuilder(args).UseWebRoot("public")

You may also use the call to app.UseStaticFiles() to customize an alternate location to serve static files. This allows you to serve additional static files from a location outside of the designated web root.

...
using Microsoft.Extensions.FileProviders;
using System.IO;
...
public void Configure(IApplicationBuilder app)
{
   ...
   app.UseStaticFiles(new StaticFileOptions
   {
      FileProvider = new PhysicalFileProvider
         Path.Combine(env.ContentRootPath, "AltStaticRoot")),
         RequestPath = "/AltStaticFiles"
   });
}

Wait a minute… why does it look like there are two alternate locations for static files? There is a simple explanation:

  • In the call to Path.Combine(), the “AltStaticRoot” is an actual folder in your current directory. This Path class and its Combine() method are available in the System.IO namespace.
  • The “AltStaticFiles” value for RequestPath is used as a root-level “virtual folder” from which images can be served. The PhysicalFileProvider class is available in the Microsoft.Extensions.FileProviders namespace.

The following markup may be used in a .cshtml file to refer to an image, e.g. MyImage01.png:

<img src="~/AltStaticFiles/MyImages/MyImage01.png" />

The screenshot below shows an example of an image loaded from an alternate location.

The screenshot below shows a web browser displaying such an image.

Preserving CDN Integrity

When you use a CDN (Content Delivery Network) to serve common CSS and JS files, you need to ensure that the integrity of the source code is reliable. You can rest assured that ASP .NET Core has already solved this problem for you in its built-in templates.

<environment include="Development">
 <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</environment>

<environment exclude="Development">
 <link
   rel="stylesheet"
   href=
https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css
   asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
   asp-fallback-test-class="sr-only"
   asp-fallback-test-property="position"
   asp-fallback-test-value="absolute"
   crossorigin="anonymous"
   integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="/>
</environment>

Right away, you’ll notice that there are two conditional <environment> blocks in the above markup. The first block is used only during development, in which the bootstrap CSS file is obtained from your local copy. When not in development (e.g. staging, production, etc), the bootstrap CSS file is obtained from a CDN, e.g. CloudFlare.

You could use an automated hash-generation tool to generate the SRI (Subresource Integrity) hash values, but you would have to manually copy the value into your code. You can try out the relatively-new LibMan (aka Library Manager) for easily adding and updating your client-side libraries.

LibMan (aka Library Manager)

The easiest way to use LibMan is to use the built-in features available in Visual Studio. Using LibMan using the IDE is as easy as launching it from Solution Explorer. Specify the provider from the library you want, and any specific files you want from that library.

In the popup that appears, select/enter the following:

  • Provider: choose from cdnjs, filesystem, unpkg
  • Library search term, e.g. @aspnet/signalr@1… pick latest stable if desired
  • Files: At a minimum, choose specific files, e.g. signalr.js and/or its minified equivalent

 

For more on LibMan (using VS or CLI), check out the official docs:

Use LibMan with ASP.NET Core in Visual Studio: https://docs.microsoft.com/en-us/aspnet/core/client-side/libman/libman-vs

Use the LibMan command-line interface (CLI): https://docs.microsoft.com/en-us/aspnet/core/client-side/libman/libman-cli

Library Manager: Client-side content manager for web apps: https://devblogs.microsoft.com/aspnet/library-manager-client-side-content-manager-for-web-apps/

In any case, using LibMan will auto-populate a “libman.json” manifest file, which you can also inspect and edit manually.

{
  "version": "1.0",
  "defaultProvider": "unpkg",
  "libraries": [
    {
      "library": "@aspnet/signalr@1.1.0",
      "destination": "wwwroot/lib/signalr/",
      "files": [
        "dist/browser/signalr.js",
        "dist/browser/signalr.min.js"
      ]
    }
  ]
}

What About NPM or WebPack?

If you’ve gotten this far, you may be wondering: “hey, what about NPM or WebPack?”

It’s good to be aware that LibMan is a not a replacement for your existing package management systems. In fact, the Single-Page Application templates in Visual Studio (for Angular and React) currently use npm and WebPack. LibMan simply provides a lightweight mechanism to include client-side libraries from external location.

 



European ASP.NET Core Hosting :: How to Use Auth Cookies in ASP.NET Core

clock March 29, 2019 11:49 by author Scott

Cookie-based authentication is the popular choice to secure customer facing web apps. For .NET programmers, ASP.NET Core has a good approach that is worth looking into. In this take, I will delve deep into the auth cookie using ASP.NET Core 2.1. Version 2.1 is the latest LTS version as of the time of this writing. So, feel free to follow along, I’ll assume you’re on Visual Studio or have enough C# chops to use a text editor. I will omit namespaces and using statements to keep code samples focused. If you get stuck, download the sample code found at the end.

With ASP.NET 2.1, you can use cookie-based authentication out of the box. There is no need for additional NuGet packages. New projects include a metapackage that has everything, which is Microsoft.AspNetCore.App. To follow along, type dotnet new mvc in a CLI or do File > New Projectin Visual Studio.

For those of you who come from classic .NET, you may be aware of the OWIN auth cookie. You will be happy to know those same skills transfer over to ASP.NET Core quite well. The two implementations remain somewhat similar. With ASP.NET Core, you still configure the auth cookie, set up middleware, and set identity claims.

Setup

To begin, I’ll assume you know enough about the ASP.NET MVC framework to gut the scaffolding into a skeleton web app. You need a HomeController with an Index, Login, Logout, and Revoke action methods. Login will redirect to Index after it signs you in, so it doesn’t need a view. I’ll omit showing view sample code since views are not the focus here. If you get lost, be sure to download the entire demo to play with it.

I’ll use debug logs to show critical events inside the cookie authentication. Be sure to enable debug logs in appsettings.json and disable Microsoft and system logs.

My log setup looks like this:

"LogLevel": {
  "Default": "Debug",
  "System": "Warning",
  "Microsoft": "Warning"
}

Now you’re ready to build a basic app with cookie authentication. I’ll forgo HTML forms with a user name and password input fields. These front-end concerns only add clutter to what is more important which is the auth cookie. Starting with a skeleton app shows how effective it is to add an auth cookie from scratch. The app will sign you in automatically and land on an Index page with an auth cookie. Then, you can log out or revoke user access. I want you to pay attention to what happens to the auth cookie as I put authentication in place.

Cookie Options

Begin by configuring auth cookie options through middleware inside the Startup class. Cookie options tell the authentication middleware how the cookie behaves in the browser. There are many options, but I will only focus on those that affect cookie security the most.

  • HttpOnly: A flag that says the cookie is only available to servers. The browser only sends the cookie but cannot access it through JavaScript.
  • SecurePolicy: This limits the cookie to HTTPS. I recommend setting this to Always in prod. Leave it set to None in local.
  • SameSite: Indicates whether the browser can use the cookie with cross-site requests. For OAuth authentication, set this to Lax. I am setting this to Strict because the auth cookie is only for a single site. Setting this to None does not set a cookie header value.

There are cookie options for both the auth cookie and a global cookie policy. Stay alert since the cookie policy can override auth cookie options and vice versa. Setting HttpOnly to false in the auth cookie will override cookie policy options. While setting SameSite in the cookie policy overrides auth cookie options. In my demo, I’ll illustrate both scenarios, so it is crystal clear how this works.

In the Startup class, find the ConfigureServices method and type:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
  .AddCookie(options =>
  {
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = _environment.IsDevelopment()
      ? CookieSecurePolicy.None : CookieSecurePolicy.Always;
      options.Cookie.SameSite = SameSiteMode.Lax;
  });

This creates the middleware service with the AddAuthentication and AddCookie methods. AuthenticationScheme is useful when there is more than one auth cookie. Many instances of the cookie authentication let you protect endpoints with many schemes. You supply any string value; the default is set to Cookies. Note that the options object is an instance of the CookieAuthenticationOptions class.

The SecurePolicy is set through a ternary operator that comes from _environment. This is a private property that gets set in the Startup constructor. Add IHostingEnvironment as a parameter and let dependency injection do the rest.

In this same ConfigureServices method, add the global cookie policy through middleware:

services.Configure<CookiePolicyOptions>(options =>
{
  options.MinimumSameSitePolicy = SameSiteMode.Strict;
  options.HttpOnly = HttpOnlyPolicy.None;
  options.Secure = _environment.IsDevelopment()
    ? CookieSecurePolicy.None : CookieSecurePolicy.Always;
});

Take a good look at SameSite and HttpOnly settings for both cookie options. When I set the auth cookie, you will see this set to HttpOnly and Strict. This illustrates how both options override each other.

Invoke this middleware inside the request pipeline in the Configure method:

app.UseCookiePolicy();
app.UseAuthentication();

The cookie policy middleware is order sensitive. This means it only affects components after invocation. By invoking the authentication middleware, you will get a HttpContext.User property. Be sure to call this UseAuthentication method before calling UseMvc.

Login

In the HomeController add an AllowAnonymous filter to the Login and Logout methods. There are only two action methods available without an auth cookie.

Inside the Startup class, look for the AddMvc extension method and add a global auth filter:

services.AddMvc(options => options.Filters.Add(new AuthorizeFilter()))

With the app secure, configure the cookie name and login / logout paths. Find where the rest of the CookieAuthenticationOptions are and do:

options.Cookie.Name = "SimpleTalk.AuthCookieAspNetCore";
options.LoginPath = "/Home/Login";
options.LogoutPath = "/Home/Logout";

This will cause the app to redirect to the login endpoint to sign in. However, before you can take this for a spin, you’ll need to create the auth cookie.

Do this inside the Login action method in the HomeController:

var claims = new List<Claim>
{
  new Claim(ClaimTypes.Name, Guid.NewGuid().ToString())
}; 

var claimsIdentity = new ClaimsIdentity(
  claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties(); 

await HttpContext.SignInAsync(
  CookieAuthenticationDefaults.AuthenticationScheme,
  new ClaimsPrincipal(claimsIdentity),
  authProperties);

AuthenticationProperties drive further auth cookie behavior in the browser. For example, the IsPersistent property persists the cookie across browser sessions. Be sure to get explicit user consent when you enable this property. ExpiresUtc sets an absolute expiration, be sure to enable IsPersistent and set it to true. The default values will give you a session cookie that goes away when you close the tab or browser window. I find the default values in this object enough for most use cases.

To take this for a spin load up the browser by going to the home or Index page. Note it redirects to Login which redirects back to Index with an auth cookie.

Once this loads it looks something like this. Be sure to take a good look at how the auth cookie is set:

JWT Identity Claim

Often, an auth cookie isn’t enough to secure API endpoints or microservices. For the web app to call a service, it can use a JWT bearer token to authenticate. To make the access token accessible, place it inside the identity claims.

In the Login action method within HomeController, expand the list of claims with a JWT:

var userId = Guid.NewGuid().ToString();
var claims = new List<Claim>
{
  new Claim(ClaimTypes.Name, userId),
  new Claim("access_token", GetAccessToken(userId))
}; 

private static string GetAccessToken(string userId)
{
  const string issuer = "localhost";
  const string audience = "localhost"; 

  var identity = new ClaimsIdentity(new List<Claim>
  {
    new Claim("sub", userId)
  }); 

  var bytes = Encoding.UTF8.GetBytes(userId);
  var key = new SymmetricSecurityKey(bytes);
  var signingCredentials = new SigningCredentials(
    key, SecurityAlgorithms.HmacSha256); 

  var now = DateTime.UtcNow;
  var handler = new JwtSecurityTokenHandler(); 

  var token = handler.CreateJwtSecurityToken(
    issuer, audience, identity,
    now, now.Add(TimeSpan.FromHours(1)),
    now, signingCredentials); 

  return handler.WriteToken(token);
}

I must caution, don’t ever do this is in production. Here, I use the user id as the signing key which is symmetric to keep it simple. In a prod environment use an asymmetric signing key with public and private keys. Client apps will then use a well-known configuration endpoint to validate the JWT.

Placing the JWT in ClaimsIdentity makes it accessible through the HttpContex.User property. For this app, say you want to put the JWT in a debug log to show off this fancy access token.

In the Startup class, create this middleware inside the Configure method:

app.Use(async (context, next) =>
{
  var principal = context.User as ClaimsPrincipal;
  var accessToken = principal?.Claims
    .FirstOrDefault(c => c.Type == "access_token"); 

  if (accessToken != null)
  {
    _logger.LogDebug(accessToken.Value);
  } 

  await next();
});

The _logger is another private property you set through the constructor. Add ILogger<Startup> as a parameter and let dependency injection do the rest. Note the ClaimsPrincipal has a list of Claims you can iterate through. What I find useful is to look for a Type of claim like an access_token and get a Value. Because this is middleware always call next() so it doesn’t block the request pipeline.

Logout

To log out of the web app and clear the auth cookie do:

await HttpContext.SignOutAsync(
  CookieAuthenticationDefaults.AuthenticationScheme);

This belongs inside the Logout action method in the HomeController. Note that you specify the authentication scheme. This tells the sign-out method which auth cookie it needs to blot out. Inspecting HTTP response headers reveals Cache-Control and Pragma headers set to no-cache. This shows the auth cookie disables browser caching when it wants to update the cookie. The Login action method responds with the same HTTP headers.

Revocation

There are use cases where the app needs to react to back-end user access changes. The auth cookie will secure the application, but, remains valid for the lifetime of the cookie. With a valid cookie, the end-user will not see any changes until they log out or the cookie expires. In ASP.NET Core 2.1, one way to validate changes is through cookie authentication events. The validation event can do back-end lookups from identity claims in the auth cookie. Create the event by extending CookieAuthenticationEvents. Override the ValidatePrincipal method and set the event in the auth cookie options.

For example:

public class RevokeAuthenticationEvents : CookieAuthenticationEvents
{
  private readonly IMemoryCache _cache;
  private readonly ILogger _logger; 

  public RevokeAuthenticationEvents(
    IMemoryCache cache,
    ILogger<RevokeAuthenticationEvents> logger)
  {
    _cache = cache;
    _logger = logger;
  } 

  public override Task ValidatePrincipal(
    CookieValidatePrincipalContext context)
  {
    var userId = context.Principal.Claims
      .First(c => c.Type == ClaimTypes.Name); 

    if (_cache.Get<bool>("revoke-" + userId.Value))
    {
      context.RejectPrincipal(); 

      _cache.Remove("revoke-" + userId.Value);
      _logger.LogDebug("Access has been revoked for: "
        + userId.Value + ".");
    } 

    return Task.CompletedTask;
  }
}

To have IMemoryCache set by dependency injection, put AddMemoryCache inside the ConfigureSerices method in the Startup class. Calling RejectPrincipal has an immediate effect and kicks you back out to Login to get a new auth cookie. Note this relies on in-memory persistence which gets set in the Revoke action method. Keep in mind that this event runs once per every request, so you want to use an efficient caching strategy. Doing an expensive lookup at every request will affect performance.

Revoke access by setting the cache inside the Revoke method in the HomeController:

var principal = HttpContext.User as ClaimsPrincipal;
var userId = principal?.Claims
  .First(c => c.Type == ClaimTypes.Name); 

_cache.Set("revoke-" + userId.Value, true);
return View();

After visiting the Revoke endpoint, the change does not have an immediate effect. After revocation, navigating home will show the debug log and redirect to Login. Note that landing in the Index page again will have a brand new auth cookie.

To register this event, be sure to set the EventsType in the CookieAuthenticationOptions. You will need to provide a scoped service to register this RevokeAuthenticationEvents. Both are set inside the ConfigureServices method in the Startup class.

For example:

options.EventsType = typeof(RevokeAuthenticationEvents);
services.AddScoped<RevokeAuthenticationEvents>();

The CookieValidatePrincipalContext in ValidatePrincipal can do more than revocation if necessary. This context has ReplacePrincipal to update the principal, then renew the cookie by setting ShouldRenew to true.

Session Store

Setting a JWT in the claims to have a convenient way to access identity data works well. However, every identity claim you put in the principal ends up in the auth cookie. If you inspect the cookie, you will notice it doubles in size with an access token. As you add more claims in the principal the auth cookie gets bigger. You may hit HTTP header limits in a prod environment with many auth cookies. In IIS, the default max limit is set to 8KB-16KB depending on the version. You can increase the limit, but this means bigger payloads per request because of the cookies.

There are many ways to quell this problem, like a user session to keep all JWTs out of the auth cookie. If you have current code that accesses the identity through the principal, then this is not ideal. Moving identity data out of the principal is risky because it may lead to a complete rewrite.

One alternative is to use the SessionStore found in CookieAuthenticationOptions. OWIN, for example, has a similar property. Implement the ITicketStore interface and find a way to persist data in the back-end. Setting the SessionStore property defines a container to store the identity across requests. Only a session identifier gets sent to the browser in the auth cookie.

Say you want to use in-memory persistence instead of the auth cookie:

public class InMemoryTicketStore : ITicketStore
{
  private readonly IMemoryCache _cache; 

  public InMemoryTicketStore(IMemoryCache cache)
  {
    _cache = cache;
  } 

  public Task RemoveAsync(string key)
  {
    _cache.Remove(key); 

    return Task.CompletedTask;
  } 

  public Task<AuthenticationTicket> RetrieveAsync(string key)
  {
    var ticket = _cache.Get<AuthenticationTicket>(key); 

    return Task.FromResult(ticket);
  } 

  public Task RenewAsync(string key, AuthenticationTicket ticket)
  {
    _cache.Set(key, ticket); 

    return Task.CompletedTask;
  } 

  public Task<string> StoreAsync(AuthenticationTicket ticket)
  {
    var key = ticket.Principal.Claims
      .First(c => c.Type == ClaimTypes.Name).Value; 

    _cache.Set(key, ticket); 

    return Task.FromResult(key);
  }
}

Set an instance of this class in SessionStore inside CookieAuthenticationOptions, these options are set in the ConfigureServices method in the Startup class. One caveat is getting an instance since it needs a provider from BuildServiceProvider. A temporary IoC container here feels hacky and pines after a better solution.

A better approach is to use the options pattern in ASP.NET Core. Post-configuration scenarios set or change options at startup. With this solution, you leverage dependency injection without reinventing the wheel.

To put in place this options pattern, implement IPostConfigureOptions<CookieAuthenticationOptions>:

public class ConfigureCookieAuthenticationOptions
  : IPostConfigureOptions<CookieAuthenticationOptions>
{
  private readonly ITicketStore _ticketStore; 

  public ConfigureCookieAuthenticationOptions(ITicketStore ticketStore)
  {
    _ticketStore = ticketStore;
  } 

  public void PostConfigure(string name,
           CookieAuthenticationOptions options)
  {
    options.SessionStore = _ticketStore;
  }
}

To register both InMemoryTicketStore and ConfigureCookieAuthenticationOptions, place this in ConfigureServices:

services.AddTransient<ITicketStore, InMemoryTicketStore>();
services.AddSingleton<IPostConfigureOptions<CookieAuthenticationOptions>,
  ConfigureCookieAuthenticationOptions>();

Make sure you make this change in the Startup class. If you peek inside Configure<CookiePolicyOptions>, for example, and crack open the code. Note the pattern; configuration runs through a singleton object and a lambda expression. ASP.NET Core uses this same options pattern under the hood. Now, firing this up in the browser and inspecting the auth cookie will have a much smaller footprint.

Conclusion

Implementing an auth cookie is seamless in ASP.NET Core 2.1. You configure cookie options, invoke middleware, and set identity claims. Sign in and sign out methods work based on an authentication scheme. Auth cookie options allow the app to react to back-end events and set a session store. The auth cookie is flexible enough to work well with any enterprise solution. 



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. 



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. 



European ASP.NET Core Hosting :: How to Use HTTP-REPL tool to test WEB API in ASP.NET Core 2.2

clock February 26, 2019 07:37 by author Scott

Today there are no tools built into Visual Studio to test WEB API. Using browsers, one can only test http GET requests. You need to use third-party tools like PostmanSoapUIFiddler or Swagger to perform a complete testing of the WEB API. In ASP.NET Core 2.2, a CLI based new dotnet core global tool named “http-repl” is introduced to interact with API endpoints. It’s a CLI based tool which can list down all the routes and execute all HTTP verbs. In this post, let’s find out how to use HTTP-REPL tool to test WEB API in ASP.NET Core 2.2.

HTTP-REPL Tool to test WEB API in ASP.NET Core 2.2

The “http-repl” is a dotnet core global tool and to install this tool, run the following command. At the time of writing this post, the http-repl tool is in preview stage
and available for download at 
dotnet.myget.org

dotnet tool install -g dotnet-httprepl --version 2.2.0-* --add-source https://dotnet.myget.org/F/dotnet-core/api/v3/index.json

Once installed, you can verify the installation using the following command.

dotnet tool list -g



Now the tool is installed, let’s see how we can test the WEB API. For this tool to work properly, the prerequisite here is that your services will have Swagger/OpenAPI available that describes the service.

We need to add this tool to web browser list so that we can browse the API with this tool. To do that, follow the steps given in the below image.



The location of HTTP-REPL tool executable is "C:\Users\<username>\.dotnet\tools". Once added, you can verify it in the browser list.

Run the app (make sure HTTP REPL is selected in browser list) and you should see a command prompt window. As mentioned earlier, it’s a CLI based experience so you can use commands like dir, ls, cdand cls. Below is an example run where I start-up a Web API.

You can use all the HTTP Verbs, and when using the POST verb, you should set a default text editor to supply the JSON. You can set Visual Studio Code as default text editor using the following command.

pref set editor.command.default "C:\Program Files (x86)\Microsoft VS Code\Code.exe"

Once the default editor is set, and you fire POST verb, it will launch the editor with the JSON written for you. See below GIF.

You can also navigate to the Swagger UI from the command prompt via executing ui command. Like,

Similarly, you can also execute the DELETE and PUT. In case of PUT command, you should use following syntax and in the default code editor, supply the updated data.

> delete 2 //This would delete the record with id 2.
>
> put 2010 -h "Content-Type: application/json"

When you fire PUT command, the behavior is same as the POST verb. The text editor will open with the JSON written for you, just supply the updated value to execute PUT command.

Pros and Cons

Pros

  • Helps in debugging WEB API
  • Fast and quickly switch between API endpoints
  • Descriptive error response shown

Cons:

  • Dependency on Swagger/Open API specification
  • Not as informative as UI tools

After playing with this for a while, I strongly feel it’s command line version of the Swagger UI and it would be very handy when there are many API endpoints. You can easily navigate or switch between the APIs and execute it. 



European ASP.NET Hosting :: Overriding ASP.NET Core Framework

clock February 20, 2019 10:57 by author Scott

OVERVIEW

In .NET it’s really easy to create your own interfaces and implementations. Likewise, it’s seemingly effortless to register them for dependency injection. But it is not always obvious how to override existing implementations. Let’s discuss various aspects of “dependency injection” and how you can override the “framework-provided services”.

As an example, let’s take a recent story on our product backlog for building a security audit of login attempts. The story involved the capture of attempted usernames along with their corresponding IP addresses. This would allow system administrators to monitor for potential attackers. This would require our ASP.NET Core application to have custom logging implemented.

LOGGING

Luckily ASP.NET Core Logging is simple to use and is a first-class citizen within ASP.NET Core.

In the Logging repository there is an extension method namely AddLogging, here is what it looks like:

public static IServiceCollection AddLogging(this IServiceCollection services)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }

    services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());
    services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));

    return services;
}

As you can see, it is rather simple. It adds two ServiceDescriptor instances to the IServiceCollection, effectively registering the given service type to the corresponding implementation type.

FOLLOWING THE RABBIT DOWN THE HOLE

When you create a new ASP.NET Core project from Visual Studio, all the templates follow the same pattern. They have the Program.cs file with a Main method that looks very similar to this:

public static void Main(string[] args)
{
    var host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseStartup<Startup>()
        .UseApplicationInsights()
        .Build();

    host.Run();
}

TEMPLATES 

One thing that is concerning about a template like this is that the IWebHost is an IDisposable, so why then is this statement not wrapped in a using you ask? The answer is that the Run extension method internally wraps itself in a using. If you were wondering where the AddLogging occurs, it is a result of invoking the Build function.

[ Microsoft.AspNetCore.Hosting.WebHostBuilder ]
    public IWebHost Build() ...
        private IServiceCollection BuildCommonServices() ...
            creates services then invokes services.AddLogging()

A FEW WORDS ON THE SERVICE DESCRIPTOR

The ServiceDescriptor class is an object that describes a service, and this is used by dependency injection. In other words, instances of the ServiceDescriptor are descriptions of services. The ServiceDescriptor class exposes several static methods that allow its instantiation.

The ILoggerFactory interface is registered as a ServiceLifetime.Singleton and its implementation is mapped to the LoggerFactory. Likewise, the generic type typeof(ILogger<>) is mapped to typeof(Logger<>). This is just one of the several key “Framework-Provided Services” that are registered.

PUTTING IT TOGETHER

Now we know that the framework is providing all implementations of ILogger<T>, and resolving them as their Logger<T>. We also know that we could write our own implementation of the ILogger<T>interface. Being that this is open-source we can look to their implementation for inspiration.

public class RequestDetailLogger<T> : ILogger<T>
{
    private readonly ILogger _logger;

    public RequestDetailLogger(ILoggerFactory factory,
                               IRequestCategoryProvider requestCategoryProvider)
    {
        if (factory == null)
        {
            throw new ArgumentNullException(nameof(factory));
        }
        if (requestCategoryProvider == null)
        {
            throw new ArgumentNullException(nameof(requestCategoryProvider));
        }

        var category = requestDetailCategoryProvider.CreateCategory<T>();
        _logger = factory.CreateLogger(category);
    }

    IDisposable ILogger.BeginScope<TState>(TState state)
        => _logger.BeginScope(state);

    bool ILogger.IsEnabled(LogLevel logLevel)
        => _logger.IsEnabled(logLevel);

    void ILogger.Log<TState>(LogLevel logLevel,
                             EventId eventId,
                             TState state,
                             Exception exception,
                             Func<TState, Exception, string> formatter)
        => _logger.Log(logLevel, eventId, state, exception, formatter);
}

The IRequestCategoryProvider is defined and implemented as follows:

using static Microsoft.Extensions.Logging.Abstractions.Internal.TypeNameHelper;

public interface IRequestCategoryProvider
{
    string CreateCategory<T>();
}

public class RequestCategoryProvider : IRequestCategoryProvider
{
    private readonly IPrincipal _principal;
    private readonly IPAddress _ipAddress;

    public RequestCategoryProvider(IPrincipal principal,
                                   IPAddress ipAddress)
    {
        _principal = principal;
        _ipAddress = ipAddress;
    }

    public string CreateCategory<T>()
    {
        var typeDisplayName = GetTypeDisplayName(typeof(T));

        if (_principal == null || _ipAddress == null)
        {
            return typeDisplayName;
        }

        var username = _principal?.Identity?.Name;
        return $"User: {username}, IP: {_ipAddress} {typeDisplayName}";
    }
}

If you’re curious how to get the IPrincipal and IPAddress into this implementation (with DI). It is pretty straight-forward. In the Startup.ConfigureServices method do the following:

public void ConfigureServices(IServiceCollection services)
{
    // ... omitted for brevity

    services.AddTransient<IRequestCategoryProvider, RequestCategoryProvider>();
    services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
    services.AddTransient<IPrincipal>(
        provider => provider.GetService<IHttpContextAccessor>()
                           ?.HttpContext
                           ?.User);
    services.AddTransient<IPAddress>(
        provider => provider.GetService<IHttpContextAccessor>()
                           ?.HttpContext
                           ?.Connection
                           ?.RemoteIpAddress);
}

Finally, we can Replace the implementations for the ILogger<T> by using the following:

public void ConfigureServices(IServiceCollection services)
{
    // ... omitted for brevity
    services.Replace(ServiceDescriptor.Transient(typeof(ILogger<>),
                                                 typeof(RequestDetailLogger<>)));
}

Notice that we replace the framework-provided service as a ServiceLifetime.Transient. Opposed to the default ServiceLifetime.Singleton. This is more or less an extra precaution. We know that with each request we get the HttpContext from the IHttpContextAccessor, and from this we have the User. This is what is passed to each ILogger<T>.

CONCLUSION

This approach is valid for overriding any of the various framework-provided service implementations. It is simply a matter of knowing the correct ServiceLifetime for your specific needs. Likewise, it is a good idea to leverage the open-source libraries of the framework for inspiration. With this you can take finite control of your web-stack.



European ASP.NET Core Hosting :: How to Use IOptions for ASP.NET Core 2 Configuration

clock February 7, 2019 11:48 by author Scott

Almost every project will have some settings that need to be configured and changed depending on the environment, or secrets that you don't want to hard code into your repository. The classic example is connection strings and passwords etc which in ASP.NET 4 were often stored in the <applicationSettings> section of web.config.

In ASP.NET Core this model of configuration has been significantly extended and enhanced. Application settings can be stored in multiple places - environment variables, appsettings.json, user secrets etc - and easily accessed through the same interface in your application. Further to this, the new configuration system in ASP.NET allows (actually, enforces) strongly typed settings using the IOptions<> pattern.

While working on an RC2 project the other day, I was trying to use this facility to bind a custom Configuration class, but for the life of me I couldn't get it to bind my properties. Partly that was down to the documentation being somewhat out of date since the launch of RC2, and partly down to the way binding works using reflection. In this post I'm going to go into demonstrate the power of the IOptions<> pattern, and describe a few of the problems I ran in to and how to solve them.

Strongly typed configuration

 

In ASP.NET Core, there is now no default AppSettings["MySettingKey"] way to get settings. Instead, the recommended approach is to create a strongly typed configuration class with a structure that matches a section in your configuration file (or wherever your configuration is being loaded from):

public class MySettings
{
    public string StringSetting { get; set; }
    public int IntSetting { get; set; }
}

Would map to the lower section in the appsettings.json below.

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "MySettings": {
    "StringSetting": "My Value",
    "IntSetting": 23
  }
}

Binding the configuration to your classes

 

In order to ensure your appsettings.json file is bound to the MySettings class, you need to do 2 things.

1. Setup the ConfigurationBuilder to load your file

2. Bind your settings class to a configuration section

When you create a new ASP.NET Core application from the default templates, the ConfigurationBuilder is already configured in Startup.cs to load settings from environment variables, appsettings.json, and in development environments, from user secrets:

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

    if (env.IsDevelopment())
    {
        builder.AddUserSecrets();
    }

    builder.AddEnvironmentVariables();
    Configuration = builder.Build();
}

If you need to load your configuration from another source then this is the place to do it, but for most common situations this setup should suffice. There are a number of additional configuration providers that can be used to bind other sources, such as xml files for example.

In order to bind a settings class to your configuration you need to configure this in the ConfigureServices method of Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MySettings>(options => Configuration.GetSection("MySettings").Bind(options));
}

Note: The syntax for model binding has changed from RC1 to RC2 and was one of the issues I was battling with. The previous method, using services.Configure<MySettings>(Configuration.GetSection("MySettings")), is no longer available

You may also need to add the configuration binder package to the dependencies section of your project.json:

"dependencies": {
  ...
  "Microsoft.Extensions.Configuration.Binder": "1.0.0-rc2-final"
  ...
}

Using your configuration class

 

When you need to access the values of MySettings you just need to inject an instance of an IOptions<> class into the constructor of your consuming class, and let dependency injection handle the rest:

public class HomeController : Controller
{
    private MySettings _settings;
    public HomeController(IOptions<MySettings> settings)
    {
        _settings = settings.Value
        // _settings.StringSetting == "My Value";
    }
}

The IOptions<> service exposes a Value property which contains your configured MySettings class.

It's important to note that there doesn't appear to be a way to access the raw IConfigurationRoot through dependency injection, so the strongly typed route is the only way to get to your settings.

You can expose the IConfigurationRoot directly to the DI container using services.AddSingleton(Configuration). (Thanks Saša Ćetković for pointing that out!)

Complex configuration classes

 

The example shown above is all very nice, but what if you have a very complex configuration, nested types, collections, the whole 9 yards?

public class MySettings
{
    public string StringSetting { get; set; }
    public int IntSetting { get; set; }
    public Dictionary<string, InnerClass> Dict { get; set; }
    public List<string> ListOfValues { get; set; }
    public MyEnum AnEnum { get; set; }
}

public class InnerClass
{
    public string Name { get; set; }
    public bool IsEnabled { get; set; } = true;
}

public enum MyEnum
{
    None = 0,
    Lots = 1
}

Amazingly we can bind that using the same configure<MySettings> call to the following, and it all just works:

{
  "MySettings": {
    "StringSetting": "My Value",
    "IntSetting": 23,
    "AnEnum": "Lots",
    "ListOfValues": ["Value1", "Value2"],
    "Dict": {
      "FirstKey": {
        "Name": "First Class",
           "IsEnabled":  false
      },
      "SecondKey": {
        "Name": "Second Class"
      }
    }
  }
}

When values aren't provided, they get their default values, (e.g. MySettings.Dict["SecondKey].IsEnabled == true). Dictionaries, lists and enums are all bound correctly. That is until they aren't...

Models that won't bind

 

So after I'd beaten the RC2 syntax change in to submission, I thought I was home and dry, but I still couldn't get my configuration class to bind correctly. Getting frustrated, I decided to dive in to the source code for the binder and see what's going on (woo, open source!).

It was there I found a number of interesting cases where a model's properties won't be bound even if there are appropriate configuration values. Most of them are fairly obvious, but could feasibly sting you if you're not aware of them. I am only going to go into scenarios that do not throw exceptions, as these seem like the hardest ones to figure out.

Properties must have a public Get method

 

The properties of your configuration class must have a getter, which is public and must not be an indexer, so none of these properties would bind:

private string _noGetter;
private string[] _arr;

public string NoGetter { set { _noGetter = value; } }
public string NonPublicGetter { set { _noGetter = value; } }
public string this[int i]
{
    get { return _arr[i]; }
    set { _arr[i] = value; }
}

Properties must have a public Set method...

 

Similarly, properties must have a public setter, so again, none of these would bind:

public string NoGetter { get; }
public string NonPublicGetter { get; private set; }

...Except when they don't have to

 

The public setter is actually only required if the value being bound is null. If it's a simple type like a string or and int, then the setter is required as there's no way to change the value. You can create readonly properties with default values, but they just won't be bound. For properties which are complex types, you don't need a setter, as long as the value has a value at binding time:

public MyInnerClass ComplexProperty { get; } = new MyInnerClass();
public List<string> ListValues { get; } = new List<string>();
public Dictionary<string, string> DictionaryValue1 { get; } = new Dictionary<string,string>();
private Dictionary<string, string> _dict = new Dictionary<string,string>();
public Dictionary<string, string> DictionaryValue2 { get { return _dict; } }

The sub properties of the MyInnerClass object returned by ComplexProperty would be bound, values would be added to the collection in ListValues, and KeyValuePairs would be added to the dictionaries.

Dictionaries must have string keys

 

This is one of the gotchas that got me! While integers, are obviously perfectly valid keys to dictionaries usually, they are not allowed in this case thanks to this snippet in ConfigurationBinder.BindDictionary:

var typeInfo = dictionaryType.GetTypeInfo();

// IDictionary<K,V> is guaranteed to have exactly two parameters
var keyType = typeInfo.GenericTypeArguments[0];
var valueType = typeInfo.GenericTypeArguments[1];

if (keyType != typeof(string))
{
    // We only support string keys
    return;
}

Don't expose IDictionary

 

This is another one that got me accidentally. While coding to interfaces is nice, the model binder uses reflection and Activator.CreateInstance(type) to create the classes to be bound. If your properties are interfaces or abstract then the binder will throw when trying to create them.

If you are exposing your properties as a readonly getter however, then the binder does not need to create the property and you might think the configuration class would bind correctly. And that is true in almost all cases. Unforunately while the binder can bind any properties which are a type that derives from IDictionary<,>, it will not bind an IDictionary<,> property directly. This leaves you with the following situation:

public interface IMyDictionary<TKey, TValue> : IDictionary<TKey, TValue> { }

public class MyDictionary<TKey, TValue>
    : Dictionary<TKey, TValue>, IMyDictionary<TKey, TValue>
{
}

public class MySettings
{
  public IDictionary<string, string> WontBind { get; } = new Dictionary<string, string>();
  public IMyDictionary<string, string> WillBind { get; } = new MyDictionary<string, string>();
}

Our wrapper type IMyDictionary which is really just an IDictionary will be bound, whereas the directly exposed IMyDictionary will not. This doesn't feel right to me and I've raised an issue with the team.

Make properties Implementing ICollection also expose an Add method

 

Types deriving from ICollection<> are automatically bound in the same way as dictionaries, however the ICollection<> interface exposes no methods to add an object to the collection, only methods for enumerating and counting. It may seem strange then that it is this interface the binder looks for when checking whether a property can be bound.

If a property exposes a type that implements ICollection<> (and is not an ICollection<> itself, as for IDictionary above, though that makes sense in this case), then it is a candidate for binding. In order to add an item to the collection, reflection is used to invoke an Add method on the type:

var addMethod = typeInfo.GetDeclaredMethod("Add");
addMethod.Invoke(collection, new[] { item });

If an add method on the exposed type does not exist (e.g. it could be a ReadOnlyCollection<>), then this property will not be bound, but no error will be thrown, you will just get an empty collection. This one feels a little nasty to me, but I guess the common use case is you will be exposing List<> and IList<> etc. Feels like they should be looking for IList<> if that is what they need though!

Summary

 

The strongly typed configuration is a great addition to ASP.NET Core, providing a clean way to apply the Interface Segregation Principle to your configuration. Currently it seems more convoluted to retrieve your settings than tin ASP.NET 4, but I wouldn't be surprised if they add some convenience methods for quickly accessing values in a forthcoming release.

It's important to consider the gotchas described if you're having trouble binding values (and you're not getting an exceptions thrown). Pay particular attention to your collections, as that's where my issues arose.



European ASP.NET Core Hosting :: ASP.NET Core 2.0 MVC Filters

clock January 28, 2019 09:58 by author Scott

The following is tutorial how to run code before and after MVC request pipeline in ASP.NET Core.

Solution

In an empty project update Startup class to add services and middleware for MVC:

        public void ConfigureServices
            (IServiceCollection services)
        {
            services.AddMvc();
        } 

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

Add the class to implement filter:

    public class ParseParameterActionFilter : Attribute, IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context)
        {
            object param;
            if (context.ActionArguments.TryGetValue("param", out param))
                context.ActionArguments["param"] = param.ToString().ToUpper();
            else
                context.ActionArguments.Add("param", "I come from action filter");
        } 

        public void OnActionExecuted(ActionExecutedContext context)
        {
        }
    }

In the Home controller add an action method that uses Action filter:

        [ParseParameterActionFilter]
        public IActionResult ParseParameter(string param)
        {
            return Content($"Hello ParseParameter. Parameter: {param}");
        }

Browse to /Home/ParseParameter, you’ll see:

 

Discussion

Filter runs after an action method has been selected to execute. MVC provides built-in filters for things like authorisation and caching. Custom filters are very useful to encapsulate reusable code that you want to run before or after action methods.

Filters can short-circuit the result i.e. stops the code in your action from running and return a result to the client. They can also have services injected into them via service container, which makes them very flexible.

Filter Interfaces

Creating a custom filter requires implementing an interface for the type of filter you require. There are two flavours of interfaces for most filter type, synchronous and asynchronous:

    public class HelloActionFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context)
        {
            // runs before action method
        } 

        public void OnActionExecuted(ActionExecutedContext context)
        {
            // runs after action method
        }
    } 

    public class HelloAsyncActionFilter : IAsyncActionFilter
    {
        public async Task OnActionExecutionAsync(
            ActionExecutingContext context,
            ActionExecutionDelegate next)
        {
            // runs before action method
            await next();
            // runs after action method
        }
    }

You can short-circuit the filter pipeline by setting the Result (of type IActionResult) property on context parameter (for Async filters don’t call the next delegate):

    public class SkipActionFilter : Attribute, IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context)
        {
            context.Result = new ContentResult
            {
                Content = "I'll skip the action execution"
            };
        } 

        public void OnActionExecuted(ActionExecutedContext context)
        { }
    } 

    [SkipActionFilter]
    public IActionResult SkipAction()
    {
       return Content("Hello SkipAction");
    }

For Result filters you could short-circuit by setting the Cancel property on context parameter and sending a response:

        public void OnResultExecuting(ResultExecutingContext context)
        {
            context.Cancel = true;
            context.HttpContext.Response.WriteAsync("I'll skip the result execution");
        } 

        [SkipResultFilter]
        public IActionResult SkipResult()
        {
            return Content("Hello SkipResult");
        }

Filter Attributes

MVC provides abstract base classes that you can inherit from to create custom filters. These abstract classes inherit from Attribute class and therefore can be used to decorate controllers and action methods:

  • ActionFilterAttribute
  • ResultFilterAttribute
  • ExceptionFilterAttribute
  • ServiceFilterAttribute
  • TypeFilterAttribute

Filter Types

There are various type of filters that run at different stages of the filter pipeline. Below a figure from official documentation illustrates the sequence:

 

 

Authorization

 

 

This is the first filter to run and short circuits request for unauthorised users. They only have one method (unlike most other filters that have Executing and Executed methods). Normally you won’t write your own Authorization filters, the built-in filter calls into framework’s authorisation mechanism.

Resource

They run before model binding and can be used for changing how it behaves. Also they run after the result has been generated and can be used for caching etc.

Action

They run before and after the action method, thus are useful to manipulate action parameters or its result. The context supplied to these filters let you manipulate the action parameters, controller and result.

Exception

They can be used for unhandled exception before they’re written to the response. Exception handling middleware works for most scenarios however this filter can be used if you want to handle errors differently based on the invoked action.

Result

They run before and after the execution of action method’s result, if the result was successful. They can be used to manipulate the formatting of result.

Filter Scope

Filters can be added at different levels of scope: Action, Controller and Global. Attributes are used for action and controller level scope. For globally scoped filters you need to add them to filter collection of MvcOptions when configuring services in Startup:

            services.AddMvc(options =>
            {
                             // by instance
                options.Filters.Add(new AddDeveloperResultFilter("Tahir Naushad")); 

                // by type
                options.Filters.Add(typeof(GreetDeveloperResultFilter));
            });

Filters are executed in a sequence:

1. The Executing methods are called first for Global > Controller > Action filters.

2. Then Executed methods are called for Action > Controller > Global filters.

Filter Dependency Injection

In order to use filters that require dependencies injected at runtime, you need to add them by Type. You can add them globally (as illustrated above), however, if you want to apply them to action or controller (as attributes) then you have two options:

ServiceFilterAttribute

This attributes retrieves the filter using service container. To use it:

Create a filter that uses dependency injection:

    public class GreetingServiceFilter : IActionFilter
    {
        private readonly IGreetingService greetingService; 

        public GreetingServiceFilter(IGreetingService greetingService)
        {
            this.greetingService = greetingService;
        } 
        public void OnActionExecuting(ActionExecutingContext context)
        {
            context.ActionArguments["param"] =
                this.greetingService.Greet("James Bond");
        } 

        public void OnActionExecuted(ActionExecutedContext context)
        { }
    }

Add filter to service container:

services.AddScoped<GreetingServiceFilter>();

Apply it using ServiceFilterAttribute:

[ServiceFilter(typeof(GreetingServiceFilter))]
public IActionResult GreetService(string param)

TypeFilterAttribute

This attributes doesn’t need registering the filter in service container and initiates the type using ObjectFactory delegate. To use it:

Create a filter that uses dependency injection:

    public class GreetingTypeFilter : IActionFilter
    {
        private readonly IGreetingService greetingService; 

        public GreetingTypeFilter(IGreetingService greetingService)
        {
            this.greetingService = greetingService;
        } 

        public void OnActionExecuting(ActionExecutingContext context)
        {
            context.ActionArguments["param"] = this.greetingService.Greet("Dr. No");
        } 

        public void OnActionExecuted(ActionExecutedContext context)
        { }
    }

Apply it using TypeFilterAttribute:

[TypeFilter(typeof(GreetingTypeFilter))]
public IActionResult GreetType1(string param)

You could also inherit from TypeFilterAttribute and then use without TypeFilter:

public class GreetingTypeFilterWrapper : TypeFilterAttribute
{
   public GreetingTypeFilterWrapper() : base(typeof(GreetingTypeFilter))
   { }


[GreetingTypeFilterWrapper]
public IActionResult GreetType2(string param)
 



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