Inline Images with ASP.NET Core

The most common way to show an image in an HTML page is to use the <img> tag to load an external resource. Another option is to use a URL that is a Base64 encoded version of the image. There are some aspects worth considering:

  1. Using this approach the HTML will become larger, because it will also contain the image as a Base64 encoded string
  2. There is no need to load external resources together with the HTML page, which may make the loading faster
  3. The images can be cached together with the page
  4. Of course, if the Base64 string is hardcoded in the page, to change the image, we must change the page

I am a big fan of ASP.NET Core’s tag helpers, and I already wrote about them. This time, I propose a tag helper for rendering a Base64 image from a local file!

Here is the code

[HtmlTargetElement("inline-img")]
public class InlineImage : TagHelper
{
private readonly IWebHostEnvironment _environment
    public InlineImage(IWebHostEnvironment environment)
    {
        this._environment = environment;
}

[HtmlAttributeName("id")]
public string Id { get; set; }

[HtmlAttributeName("src")]
    public string Src { get; set; }

    [HtmlAttributeName("style")]
    public string Style { get; set; } 

   [HtmlAttributeName("class")]
    public string Class { get; set; }

    [HtmlAttributeName("width")]
    public string Width { get; set; } 

   [HtmlAttributeName("height")] 
   public string Height { get; set; } 

    [HtmlAttributeName("title")]
    public string Title { get; set; }

    [HtmlAttributeName("alt")] 
   public string Alt { get; set; } 

    public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) 
   {
     if (!string.IsNullOrWhiteSpace(this.Src)) 
        {
          string absoluteSrc;

            if (this.Src.StartsWith("~/"))
            {
                absoluteSrc = this.Src.Replace("~/", this._environment.WebRootPath);
            }
            else 
            {
                var src = this.Src.StartsWith("/") ? this.Src.Substring(1) : this.Src;
                absoluteSrc = Path.Combine(this._environment.WebRootPath, src);
            }

            var img = File.ReadAllBytes(absoluteSrc); 
            var ext = Path.GetExtension(absoluteSrc).Substring(1);
            var urlEncoded = $"data:image/{ext};base64, {Convert.ToBase64String(img)}";

            output.TagName = "img";      
      output.TagMode = TagMode.SelfClosing;     
        output.Attributes.Add("src", urlEncoded);

if (!string.IsNullOrWhiteSpace(this.Id)) 
            {            
     output.Attributes.Add("id", this.Id);
            }

            if (!string.IsNullOrWhiteSpace(this.Style)) 
            { 
                output.Attributes.Add("style", this.Style); 
            }   

          if (!string.IsNullOrWhiteSpace(this.Class)) 
            {  
               output.Attributes.Add("class", this.Class); 
            } 

            if (!string.IsNullOrWhiteSpace(this.Width)) 
            { 
                output.Attributes.Add("width", this.Width); 
            }

if (!string.IsNullOrWhiteSpace(this.Height)) 
            { 
                output.Attributes.Add("Height", this.Height); 
            } 

            if (!string.IsNullOrWhiteSpace(this.Title)) 
            { 
                output.Attributes.Add("title", this.Title); 
            } 

            if (!string.IsNullOrWhiteSpace(this.Alt)) 
            { 
                output.Attributes.Add("alt", this.Alt); 
            } 
        } 

        return base.ProcessAsync(context, output); 
    }
}

First, this code has absolutely no error checking, you can add it as an exercise!

This class inherits from the TagHelper base class. It is declared as being triggered by an <inline-img> tag, and it requires a property Source to be passed and then checks to see if its relative to the virtual path (starts with ~/), in which case, it replaces it by the actual path. Otherwise, it considers it relative to the root folder.

There are a few properties that sometimes come in handy when dealing with images: Id, Style, Class, Width, Height, Alt and Title, all optional. Feel free to add others.

To use this, you need to register this tag helper, possibly on the _ViewImports.cshtml file:

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, MyWebApplication

Just replace MyWebApplication by the full name of your assembly. After this, it’s just a matter of declaring an <inline-img> element on a Razor view:

<inline-img src="my.logo.png" />

And, presto, you will end up with something like this:

<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEU…………." />

Hope you enjoy this trick! Winking smile

Interfaces and Inversion of Control

The way I see it, there are three reasons for using an Inversion of Control (IoC) / Dependency Injection (DI) container:

  1. To decouple the actual implementation from a base class/interface (the contract), so that we can change the implementation when needed
  2. To allow the implementation itself to have services injected into it when it is built
  3. To control the lifetime of a service, for example, singleton, web request scoped, transient, etc

For #1, one common approach is to create an interface for every service that we are registering in the IoC/DI – this is actually why we call it Inversion of Control: we don’t control it, the container does. Nothing against it, but it may not be necessary: if for the service we are registering there will never, ever, be a different implementation (and we know that this may happen, like, for infrastructure code), there isn’t really a need for registering a service under an umbrella interface (or base class). Of course, this may bite us later, so we need to be really careful about it. The way I sometimes avoid interfaces is because I really don’t need them, and I end up saving some extra code and files.

The second reason is straightforward: we declare, on the implementation’s constructor, the services that we will be taking as dependencies, so that we can keep a reference to them for later usage.

Finally, reason #3, for me, its, for example, what makes Singleton an anti-pattern. Using an IoC container we can control the lifetime and have a service iinjected into our classes, transparently, without caring if there is one or more than one instances of it.

ASP.NET Core Pitfalls – Dependency Injection Lifetime Validation

As you can imagine, it doesn’t make much sense to have a service that is registered as singleton to depend on another service that is registered as scoped, because the singleton instantiation will only happen once. Take this example:

public interface IScopedService { }

public interface ISingletonService { }

public class SingletonService : ISingletonService

{

public SingletonService(IScopedService svc) { }

}

And this registration:

services.AddSingleton<ISingletonService, SingletonService>();

services.AddScoped<IScopedService, ScopedService>();

The actual implementation does not matter here. What is important is that you will only see this problem when you try to instantiate the ISingletonService, normally through constructor injection.

One way to prevent this problem is, of course, to take care with the services registration. Another one is to have ASP.NET Core validate the registrations for us, when building the service provider. This can be achieved on bootstrap, before the Startup class is instantiated, when the application starts:

public static IHostBuilder CreateHostBuilder(string[] args) =>

Host.CreateDefaultBuilder(args)

.UseDefaultServiceProvider((context, options) =>

{

options.ValidateScopes = context.HostingEnvironment.IsDevelopment();

options.ValidateOnBuild = true;

})

.ConfigureWebHostDefaults(webBuilder =>

{

webBuilder.UseStartup<Startup>();

});

Notice the ValidateScopes and ValidateOnBuild properties. The first will prevent a singleton service from taking a scoped service dependency, and the second actually does the validation upon startup. To be clear, one can be used without the other.

When ValidateScopes is enabled, you won’t be able to inject a singleton service that takes as its dependency a scoped one, the application will crash; with ValidateOnBuild you actually specify when it will crash: if set to true, it will crash at startup, otherwise, it will only crash when the injection occurs.

If you really need to instantiate a scoped service from a singleton, you need to use code such as this:

IServiceScopeFactory scopedFactory = … //obtained from dependency injection

using (var scope = scopedFactory.CreateScope())

{

var scopedService = scope.ServiceProvider.GetRequiredService<IScopedService>();

//if IScopedService implements IDisposable, it will be disposed at the end of the scope

}

Note that this will not validate open generic types, as stated in the documentation for ValidateOnBuild.

Finally, using the ApplicationServices collection of IApplicationBuilder, in the Configure method, you can only retrieve transient or singleton services, not scoped: the reason for this is that when Configure executes there is not yet a scope. This will fail:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

{

var scoped = app.ApplicationServices.GetRequiredService<IScopedService>();

//rest goes here

}

Interestingly, you can inject a scoped service as a parameter to Configure, e.g., this will not crash:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IScopedService svs)

{

//rest goes here

}

ASP.NET Core Pitfalls – Returning a Custom Service Provider from ConfigureServices

In pre-3.1 versions of ASP.NET Core, you could return your own service provider (AutoFac, Ninject, etc) by returning some IServiceProvider-implementing class from the ConfigureServices method. This is no longer supporting, and having code like this results in an NotSupportedException being thrown at startup:

public IServiceProvider ConfigureServices(IServiceCollection services)

{

//something

return myCustomServiceProvider;

}

The way to do it now is by registering a service provider factory at bootstrap, something like this:

public static IHostBuilder CreateHostBuilder(string[] args) =>

Host.CreateDefaultBuilder(args)      .UseServiceProviderFactory(new CustomServiceProviderFactory())         .ConfigureWebHostDefaults(webBuilder =>         {          webBuilder.UseStartup<Startup>();         });

A custom service provider factory must implement IServiceProviderFactory<T>, for an example, you can see Autfac’s implementation: https://github.com/autofac/Autofac.Extensions.DependencyInjection/blob/develop/src/Autofac.Extensions.DependencyInjection/AutofacServiceProviderFactory.cs and https://github.com/autofac/Autofac.Extensions.DependencyInjection/blob/develop/src/Autofac.Extensions.DependencyInjection/AutofacChildLifetimeScopeServiceProviderFactory.cs.

ASP.NET Core Pitfalls – Localization with Shared Resources

When localizing a web application using resources, there are two common ways:

  • Using a resource per Razor view or page
  • Using shared resources, e.g., not bound to a specific view or page

By default, ASP.NET Core’s localization expects resource files(.resx) to be located inside a Resources folder on the root of the web application. When using shared resources, we often create a dummy class (say, SharedResources) to which we can associate resources that are not associated with a specific view or page, for example, resources used in the page layout, so, we may be tempted to place this class inside this Resources folder. It turns out, however, that that won’t work: even if you change the namespace of this class to be the root assembly namespace (usually the web application’s name), the satellite resource assembly will not be generated properly. The solution is to place the dummy class at the root of the application (for example) and leave the resource files inside the Resources fimageThe code goes like this, in ConfigureServices:

services.AddMvc()
    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);

And in Configure:

var supportedCultures = new[] { “en”, “pt” };

var localizationOptions = new RequestLocalizationOptions() { ApplyCurrentCultureToResponseHeaders = true } .SetDefaultCulture(supportedCultures[0]) .AddSupportedCultures(supportedCultures) .AddSupportedUICultures(supportedCultures); app.UseRequestLocalization(localizationOptions);

Finally, to use in a Razor view or page:

@inject IHtmlLocalizer<SharedResources> Localizer

<h1>@Localizer[“Hello”]</h1>

Worth saying that this happens in ASP.NET Core 3.1 as well as in ASP.NET Core 5.

 

ASP.NET Core Pitfalls – Areas

There are a few problems with using areas:

  1. The _ViewImport.cshtml and _ViewStart.cshtml files are not loaded by views inside an area, which means that, for example tag helpers registrations are lost and page layouts are not set. The solution is either to copy these files to a view folder inside the area, such as:
    Areas\Admin\Views\Home\_ViewImport.cshtml

    or to copy the files to the root of the application.

  2. You should register the area routes before the other routes:
    endpoints.MapControllerRoute("areas", "{area:exists}/{controller=Home}/{action=Index}/{id?}");