Episode 005 - Dependency Injection - ASP.NET Core: From 0 to overkill

In this post/video we continue our look into base concepts of ASP.NET Core, in preparation to what’s in our way for building the application. This time, dependency injection, which was built right into the core of ASP.NET Core (see what I did there? 😆).

In this post/video we continue our look into base concepts of ASP.NET Core, in preparation to what’s in our way for building the application. This time, dependency injection, which was built right into the core of ASP.NET Core (see what I did there? 😆).

For the walk-through you can check the next video, but if you prefer a quick read, skip to the written synthesis.

The playlist for the whole series is here.

Intro

In the last episode, we started looking into some important core concepts when developing ASP.NET Core applications, namely the Program and Startup classes, taking also a quick look into dependency injection and middlewares. In this episode, we go a little further with dependency injection.

Extracting logic from the controller

Why

Normally we don’t want our controllers to have too much logic, particularly business logic. Ideally the controllers focus on MVC/HTTP specific logic - receiving client inputs, pass them along to something responsible for the business logic, return the results back (feeding views or in other forms, such as a JSON payload), maybe including different HTTP status codes depending on the output of the business logic.

This, of course, isn’t mandatory, and I’ve seen articles and talks arguing that for instance, if we’re developing really small microservices, we might as well bypass splitting the code this way, as if adjustments are needed we could just rewrite the service. Anyway, I’ll go with the more classic approach of splitting things, just wanted to make the note that this isn’t set in stone (as nothing is really, there are always multiple ways of doing things).

Some benefits of having the business logic separated from the web framework are the ability to test it without the complexity added by the web framework specificities, as well as allowing the use of the same logic components with different “gateways” (an alternative web framework, a desktop application, etc).

New class libraries

To extract the business logic from the controller, we’ll go along with the old school 3-tier architecture, where our MVC application will be the presentation layer and the extracted logic will make up the business layer. No data layer yet, we’ll get to it in the future.

N-tier architectures may not be what’s hot right now, but for what we’re doing right now, it fits our needs and is simple enough.

Starting with the creation of couple of class libraries CodingMilitia.PlayBall.GroupManagement.Business and CodingMilitia.PlayBall.GroupManagement.Business.Impl. The first library will contain the contracts/API that the clients of the business logic need to know, while the latter will contain the implementation of said logic.

In the contracts library, we need the models (Group class only in this case) and the IGroupService interface.

public class Group
{
    public long Id { get; set; }
    public string Name { get; set; }
}
public interface IGroupsService
{
    IReadOnlyCollection<Group> GetAll();
Group GetById(long id);

Group Update(Group group);

Group Add(Group group);

}

Going to the service implementation library, we create a InMemoryGroupsService class that implements the IGroupsService interface - there isn’t much business logic right now, it’s mostly CRUD, but let’s pretend there is 😛

public class InMemoryGroupsService : IGroupsService
{
private readonly List<Group> _groups = new List<Group>();
private long _currentId = 0;

public IReadOnlyCollection&lt;Group&gt; GetAll()
{
    return _groups.AsReadOnly();
}

public Group GetById(long id)
{
    return _groups.SingleOrDefault(g =&gt; g.Id == id);
}

public Group Update(Group group)
{
    var toUpdate = _groups.SingleOrDefault(g =&gt; g.Id == group.Id);

    if (toUpdate == null)
    {
        return null;
    }

    toUpdate.Name = group.Name;
    return toUpdate;
}

public Group Add(Group group)
{
    group.Id = ++_currentId;
    _groups.Add(group);
    return group;
}

}

As the name implies, the data is stored in memory (it’s not even thread safe) so to say it’s far from production ready is an understatement. It’s exactly the same logic we had in the controller, just pulled into a class of its own.

Heading back to the web application project, we add the references to the newly created libraries and rework the GroupsController to use the IGroupsService instead of having the logic implemented in it.

[Route("groups")]
public class GroupsController : Controller
{
private readonly IGroupsService _groupsService;

public GroupsController(IGroupsService groupsService)
{
    _groupsService = groupsService;
}

[HttpGet]
[Route("")]
public IActionResult Index()
{
    return View(_groupsService.GetAll().ToViewModel());
}

[HttpGet]
[Route("{id}")]
public IActionResult Details(long id)
{
    var group = _groupsService.GetById(id);

    if (group == null)
    {
        return NotFound();
    }

    return View(group.ToViewModel());
}

[HttpPost]
[Route("{id}")]
[ValidateAntiForgeryToken]
public IActionResult Edit(long id, GroupViewModel model)
{
    var group = _groupsService.Update(model.ToServiceModel());

    if (group == null)
    {
        return NotFound();
    }

    return RedirectToAction("Index");
}

[HttpGet]
[Route("create")]
public IActionResult Create()
{
    return View();
}

[HttpPost]
[Route("")]
[ValidateAntiForgeryToken]
public IActionResult CreateReally(GroupViewModel model)
{
    _groupsService.Add(model.ToServiceModel());

    return RedirectToAction("Index");
}

}

A couple of things to notice:

  • We’re now receiving the IGroupsService in the constructor, so we can use it in the actions
  • ToViewModel and ToServiceModel extension methods - as the presentation layer and the business layer use a different set of models, we need to convert them when passing them along
public static class GroupMappings
{
public static GroupViewModel ToViewModel(this Group model)
{
return model != null ? new GroupViewModel { Id = model.Id, Name = model.Name } : null;
}

public static Group ToServiceModel(this GroupViewModel model)
{
    return model != null ? new Group { Id = model.Id, Name = model.Name } : null;
}

public static IReadOnlyCollection&lt;GroupViewModel&gt; ToViewModel(this IReadOnlyCollection&lt;Group&gt; models)
{
    if (models.Count == 0)
    {
        return Array.Empty&lt;GroupViewModel&gt;();
    }

    var groups = new GroupViewModel[models.Count];
    var i = 0;
    foreach (var model in models)
    {
        groups[i] = model.ToViewModel();
        ++i;
    }

    return new ReadOnlyCollection&lt;GroupViewModel&gt;(groups);
}

}

We could do this mapping directly in the controller, but it would end up being polluted by this boilerplate code. We could also use AutoMapper to do this auto-magically for us, but it can sometimes hide problems in our code (for instance, we can’t be sure of the existence of references to properties when using it). All in all, I like this extension method idea by a colleague of mine, as it provides nice readability in the controller, so I’ll go with it.

If we try to run this now we’ll get an error, because we’re now expecting to get an IGroupsService in the controller, but we haven’t told the framework how to get it. To do this we need to register the service implementation in the dependency injection container.

Using the builtin container

Registering the service

In the previous episode we saw how to register services in the builtin dependency injection container, and this will be no different. We want to register the InMemoryGroupsService and like we also saw previously, we want it to have lifestyle of type singleton, so the data it keeps in memory sticks around for the lifetime of the application.

To do this, we need a single new line added to the Startup class: services.AddSingleton<IGroupsService, InMemoryGroupsService>();.

Now we can run the application and it behaves has before, just with a different internal organization.

Improve organization

For now, we don’t have too much to worry, as we only have a couple of lines in the ConfigureServices method of the Startup class, but what about when we add more and more?

One good way of keeping the ConfigureServices tidy is to create extension methods on IServiceCollection to register groups of services that go along together. With this in mind, in a newly created IoC folder, we add a new class ServiceCollectionExtensions.

namespace Microsoft.Extensions.DependencyInjection
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddBusiness(this IServiceCollection services)
{
services.AddSingleton<IGroupsService, InMemoryGroupsService>();

        //more business services...

        return services;
    }
}

}

Notice the namespace “trick” in there, using the same namespace as IServiceCollection. This is something usually done to ease discoverability of extension methods, as it allows for us to go to the ConfigureServicesmethod, type services. and get immediately the methods in intellisense without the need to add another using.

Now in the ConfigureServices we can replace the line we added previously with services.AddBusiness();.

Using third party containers

It’s good that ASP.NET Core comes with dependency injection built right into it, but we need to keep in mind that the out of the box container was built to answer ASP.NET Core internal needs, not every need of the applications built on top of it.

For more advanced scenarios we might have, we can use third party DI containers that can provide those features.

Just to check out this possibility, we’ll add Autofac to the project and play a little with it. The objective is not to go into much detail on Autofac itself, but see how to use it with ASP.NET Core and an example of extra features it adds.

Replacing the container

First thing is to add a couple of NuGet packages - Autofac and Autofac.Extensions.DependencyInjection.

To register dependencies with Autofac, we create a new class AutofacModule(in the IoC folder) that inherits from Module and we override the Loadmethod.

public class AutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder
.RegisterType<InMemoryGroupsService>()
.As<IGroupsService>()
.SingleInstance();
}
}

The code seen above is the equivalent of what we had in AddBusiness, but adapted to Autofac’s API. Now we need to configure Autofac as the container.

//...
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();

//if using default DI container, uncomment
//services.AddBusiness();

// Add Autofac
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule&lt;AutofacModule&gt;();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);

}
//...

The first change to notice is the ConfigureServices method is no longer void, it now returns an IServiceProvider. This is needed so ASP.NET Core uses the returned service provider instead of building its own out of the IServiceCollection that’s received as an argument.

The Autofac configuration code by the end of the method is a plain copy paste from Microsoft’s dependency injection docs 😇

Basically, what we’re doing here is:

  • Creating an Autofac container builder
  • Registering the module we created with our dependency configurations
  • Populating Autofac’s container with the registrations present in the IServiceCollection, put there by ASP.NET Core’s components
  • Building the container
  • Return a new service provider, implemented using the Autofac container

Again, rerunning the application, we maintain the same functionality.

Using other container features

So far we haven’t used any specific feature of Autofac that justifies the change, unless it was a matter of performance (which wouldn’t be the case, at least at the time of writing, the builtin container seems faster, see here).

Just for arguments sake, let’s use an extra feature Autofac provides: decorators.

public class AutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder
.RegisterType<InMemoryGroupsService>()
.Named<IGroupsService>("groupsService")
.SingleInstance();
builder
.RegisterDecorator<IGroupsService>(
(context, service) => new GroupsServiceDecorator(service),
"groupsService");
}

private class GroupsServiceDecorator : IGroupsService
{
    private readonly IGroupsService _inner;

    public GroupsServiceDecorator(IGroupsService inner)
    {
        _inner = inner;
    }

    public IReadOnlyCollection&lt;Group&gt; GetAll()
    {
        Console.WriteLine($"######### Helloooooo from {nameof(GetAll)} #########");
        return _inner.GetAll();
    }

    public Group GetById(long id)
    {
        Console.WriteLine($"######### Helloooooo from {nameof(GetById)} #########");
        return _inner.GetById(id);
    }

    public Group Update(Group group)
    {
        Console.WriteLine($"######### Helloooooo from {nameof(Update)} #########");
        return _inner.Update(group);
    }

    public Group Add(Group group)
    {
        Console.WriteLine($"######### Helloooooo from {nameof(Add)} #########");
        return _inner.Add(group);
    }
}

}

The decorator implements the IGroupsService interface, writing to the console on every method call, then delegating the real work to another IGroupsService implementation it gets in the constructor.

To register the decorator, we changed the InMemoryGroupsServiceregistration to a named one, and then call RegisterDecorator with a lambda to build the decorator and the name we added to the real implementation’s registration. Can’t say I’m the biggest fan of this API, but that’s what we have right now.

Now if we rerun the application, we keep the same functionality as before, but additionally if we look at the console, intermixed with the logs we see the decorators messages.

Outro

This is it for a quick look at dependency injection in ASP.NET Core. Even though not going very deep it’s good enough to prepare for what’s coming, and we’ll introduce more related concepts as needed along the way.

Using Autofac was good for an introduction on replacing the builtin container, but I’m not entirely sold on its API, I’ll probably replace it in the future.

The source code for this post is here.

Please send any feedback you have, so the next posts/videos can be better and even adjusted to more interesting topics.


By : JoĂŁo Antunes


Create Login and Registration in Your ASP.NET Core MVC App

Create Login and Registration in Your ASP.NET Core MVC App

This tutorial walks you through setting up login and registration with ASP.NET Core MVC. In this tutorial, you learned how to add authentication to your ASP.NET Core MVC app and allow users to register for a new account.

This tutorial walks you through setting up login and registration with ASP.NET Core MVC. In this tutorial, you learned how to add authentication to your ASP.NET Core MVC app and allow users to register for a new account.

User authentication and authorization are common features in web applications, but building these mechanics has the potential to take a lot of time. Doing so requires setting up persistent storage for user information (in some type of database) and paying keen attention to potential security issues around sensitive operations like hashing passwords, password reset workflows, etc. - weeks of development time begin to add up before we ever get to the functionality that delivers value to your users.

In this post, we’ll walk through how Okta simplifies this process for us and set up a simple integration for an ASP.NET Core MVC app using the Okta NuGet package. We’ll build functionality for users to register for new accounts and login with their Okta credentials.

Scaffold Your ASP.NET Project

To follow along with this tutorial start by creating a new app in the console:

mkdir MyOktaProject
cd MyOktaProject
dotnet new mvc


Configure User Registration

If you don’t already have one, you’ll need to create an Okta developer account. Doing so will give you a URL for your organization called an “Okta domain”. It will also allow you to create your login credentials for accessing the Okta dashboard.

Upon submission of this form, you will receive an email Okta with instructions to obtain your credentials and complete your registration.

Execute the following steps to configure Okta so that users can register themselves for an account.

  1. From the Administrative Dashboard, hover over Users and click Registration
  2. Click Enable Registration
  3. Save the changes

Configure Basic User Authentication

Once you have access to your account you can proceed to the Dashboard using a link like the one below:

<span

class="okta-preview-domain">https://{yourOktaDomain}/admin/dashboard

On the Dashboard, click Applications in the main menu and on the Application screen, click Add Application.

Select Web then click Next.

On the Create New Application screen, set the following values:

Click Done, then click Edit next to General Settings on your newly created Okta app. Edit the following values:

Logout redirect URIs: https://localhost:5001/signout-callback-oidc

Initiate login URI: https://localhost:5001/authorization-code/callback

Add .NET Authentication Dependencies

Once your account is set up you need to add the Okta.Sdk library to your project. This post will take the approach of using the NuGet package, but the Github repository for Okta.AspNetCore can be found here.

To proceed simply search for the latest version of the Okta.Sdk NuGet package in your IDE of choice (version 1.2.0 at the time of this publication) and install it. If you’re using Visual Studio you can do this by right-clicking on the project in the solution explorer and selecting Manage NuGet Packages. For those of you not using Visual Studio, add the package via console window using the following command:

dotnet add package Okta.Sdk --version 1.2.0


Configure Your ASP.NET App for Login

Authentication works by redirecting users to the Okta website, where they will log in with their credentials, and then be returned to your site via the URL you configured above.

Add the following code to your appsettings.json file:

  "Okta": {
    "Issuer": "https://{yourOktaDomain}/oauth2/default",
    "ClientId": "{yourClientId}",
    "ClientSecret": "{yourClientSecret}"
  }


You can find each of the actual values needed to replace the settings in the config above in the following places:

ClientId refers to the client ID of the Okta application ClientSecret refers to the client secret of the Okta application Issuer will need the text {yourOktaDomain} replaced with your Okta domain, found at the top-right of the Dashboard page

Add some using statements to your Startup.cs file:

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;


Add the following code to the top of the ConfigureServices method in your Startup.cs file:

services.AddAuthentication(sharedOptions =>
{
    sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.ClientId = Configuration["okta:ClientId"];
        options.ClientSecret = Configuration["okta:ClientSecret"];
        options.Authority = Configuration["okta:Issuer"];
        options.CallbackPath = "/authorization-code/callback";
        options.ResponseType = "code";
        options.SaveTokens = true;
        options.UseTokenLifetime = false;
        options.GetClaimsFromUserInfoEndpoint = true;
        options.Scope.Add("openid");
        options.Scope.Add("profile");
        options.TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = "name"
        };
    });


In the Configure() method of your Startup.cs file add this line just before the app.UseMvc() method:

app.UseAuthentication();


Add the following MeViewModel to the Models directory:

using System.Collections.Generic;

namespace OktaAspNetCoreMvc.Models
{
    public class MeViewModel
    {
        public string Username { get; set; }

        public bool SdkAvailable { get; set; }

        public dynamic UserInfo { get; set; }

        public IEnumerable<string> Groups { get; set; }
    }
}


Add Login to Your ASP.NET App

Now that all the configuration and plumbing is done, you’re ready to add the code that will actually log users into your application.

Add the following AccountController to the Controller directory.

The controller exposes the Login() action. If the user has already been authenticated, the Login() action will redirect them to the home page. Otherwise, it will redirect them to the Okta login screen.

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc;
using Okta.Sdk;

namespace OktaAspNetCoreMvc.Controllers
{
    public class AccountController : Controller
    {
        private readonly IOktaClient _oktaClient;

        public AccountController(IOktaClient oktaClient = null)
        {
            _oktaClient = oktaClient;
        }

        public IActionResult Login()
        {
            if (!HttpContext.User.Identity.IsAuthenticated)
            {
                return Challenge(OpenIdConnectDefaults.AuthenticationScheme);
            }

            return RedirectToAction("Index", "Home");
        }
    }
}


Add the following code to your _Layout.cshtml file, just below the main menu to add the login button, or a welcome message, based on the current user’s status.:

   @if (User.Identity.IsAuthenticated)
    {
        <ul class="nav navbar-nav navbar-right">
            <li><p class="navbar-text">Hello, @User.Identity.Name</p></li>
        </ul>
    }
    else
    {
        <ul class="nav navbar-nav navbar-right">
            <li><a asp-controller="Account" asp-action="Login">Log in</a></li>
        </ul>
    }


For information on user authorization using Okta groups check out Lee Brandt’s article on user authorization in ASP.NET Core with Okta.

Register Users

If you following the instructions above to enable self-service registration the “Don’t have an account? Sign Up” message will appear at the bottom of the login form. In the next step, you’ll run the application.

Log In Using ASP.NET

That’s it! To run your solution open up a terminal and enter the following command:

dotnet run


Then navigate to http://localhost:5001 in your browser and enjoy!

The source code for this tutorial is available on GitHub.

Now you have a website with a working login and user registration form. Your website also allows users to recover lost passwords. By repeating these steps you can create a network of tools that your users can access all with the same login.

Learn More

☞ The Complete ASP.NET MVC 5 Course

☞ Build a Real-world App with ASP.NET Core and Angular 2 (4+)

☞ ASP NET Core (ASP.NET 5),MVC 6,C#,Angular2 & EF Crash Course

☞ Rest Api’s in Asp.Net and C#

☞ Hands on ASP .Net Core 2

DevOps For ASP.NET Developers

DevOps is the union of people, process, and products to enable continuous delivery of value to our end users. Azure DevOps is everything you need to turn an idea into a working piece of software.


Abel and Jeremy introduce us the benefits of DevOps. They give us a high level overview of how to implement some DevOps best practices using Azure DevOps.

Abel and Jeremy explain the difference between these two options and show how we can get started with Azure Repos. They will walk us through creating branches, adding policies, and also integrating with GitHub.

Thanks for reading ❤

If you liked this post, share it with all of your programming buddies!

Follow me on Facebook | Twitter

Learn More

☞ The Complete ASP.NET MVC 5 Course

☞ Build a Real-world App with ASP.NET Core and Angular 2 (4+)

☞ Build an app with ASPNET Core and Angular from scratch

☞ Introduction to ASP.NET Core 2.2

☞ Build a REST API with ASP.NET Core 2.2

☞ Build amazing web apps with ASP.NET Core 3.0

☞ Angular and .NET Core

☞ Building Web App using ASP.NET Web API Angular 7 and SQL Server

Using Parcel.js in an ASP.NET Core Application

Using Parcel.js in an ASP.NET Core Application

In this post, we’re going to take an ASP.NET website template that uses Bootstrap 4 and set it up to use Parcel-generated bundles instead.

In this post, we’re going to take an ASP.NET website template that uses Bootstrap 4 and set it up to use Parcel-generated bundles instead.

Parcel.js is a “Blazing fast, zero configuration web application bundler.” In this post, we’re going to take an ASP.NET Core website template that uses Bootstrap 4 and set it up to use Parcel-generated bundles instead.

ASP.NET Core supports bundling and minifying static assets at design-time using the community supported BuildBundlerMinifier package that can be configured in a bundleconfig.json file. However it’s not well suited for scenarios that would benefit from a deploy-time bundling strategy, i.e. assets are built during deployment and output files are not checked in.

This is where Parcel.js comes in. Parcel is a “Blazing fast, zero configuration web application bundler.” The zero-configuration bit is its major selling point because it allows you to get started with minimal effort.

In this post, we’re going to take an ASP.NET website template that uses Bootstrap 4 and set it up to use Parcel-generated bundles instead.

Create & Set Up a New ASP.NET Project

  1. Create a web project that uses Razor Pages. To do this on the command line, run:
  2. dotnet new webapp --name AspNetParcelExp cd AspNetParcelExp
  3. Delete the folders under wwwroot. (You may delete this later on, if you want it for reference — our goal is to generate these files using Parcel and use those instead.)
  4. Install npm Dependencies
  5. Add a package.json file to the project root like the following:
 	{
  "name": "aspnet-parcel-exp",
  "private": true,
  "version": "0.1.0"
}


  1. Create a web project that uses Razor Pages. To do this on the command line, run:
  2. dotnet new webapp --name AspNetParcelExp cd AspNetParcelExp
  3. Delete the folders under wwwroot. (You may delete this later on, if you want it for reference — our goal is to generate these files using Parcel and use those instead.)
  4. Install npm Dependencies
  5. Add a package.json file to the project root like the following:
javascriptnpm install --save-dev [email protected]

  1. Create a web project that uses Razor Pages. To do this on the command line, run:
  2. dotnet new webapp --name AspNetParcelExp cd AspNetParcelExp
  3. Delete the folders under wwwroot. (You may delete this later on, if you want it for reference — our goal is to generate these files using Parcel and use those instead.)
  4. Install npm Dependencies
  5. Add a package.json file to the project root like the following:
npm install [email protected]
npm install [email protected]
npm install [email protected]
npm install [email protected]
npm install [email protected]

If everything went right, your package.json should look something like this:

{
  "name": "aspnet-parcel-exp",
  "private": true,
  "version": "0.1.0",
  "devDependencies": {
    "parcel-bundler": "^1.11.0"
  },
  "dependencies": {
    "bootstrap": "^4.2.1",
    "jquery": "^3.3.1",
    "jquery-validation": "^1.19.0",
    "jquery-validation-unobtrusive": "^3.2.11",
    "popper.js": "^1.14.7"
  }
}


Set Up an Asset Bundle Using Parcel.js

  1. Create a web project that uses Razor Pages. To do this on the command line, run:
  2. dotnet new webapp --name AspNetParcelExp cd AspNetParcelExp
  3. Delete the folders under wwwroot. (You may delete this later on, if you want it for reference — our goal is to generate these files using Parcel and use those instead.)
  4. Install npm Dependencies
  5. Add a package.json file to the project root like the following:
 	/AspNetParcelExp/ # project root
  - .sassrc       # sass configuration
  - assets/       # front end assets root
    - scss/       # Place for all styles
      - site.scss
    - js/         # Place for all scripts
      - site.js
    - bundle.js   # Entry point for our output bundle


  1. Create a web project that uses Razor Pages. To do this on the command line, run:
  2. dotnet new webapp --name AspNetParcelExp cd AspNetParcelExp
  3. Delete the folders under wwwroot. (You may delete this later on, if you want it for reference — our goal is to generate these files using Parcel and use those instead.)
  4. Install npm Dependencies
  5. Add a package.json file to the project root like the following:
// Import styles  
import './scss/site.scss'  

// Setup jquery
import $ from 'jquery'
window.$ = window.jQuery = $

// Import other scripts
import 'bootstrap'  
import 'jquery-validation'  
import 'jquery-validation-unobtrusive'  

import './js/site'


We import everything we depend on. ‘bootstrap’ for example refers to the …/node_modules/bootstrap/ folder. If you want to import a specific file from a package only, you may do that too. The above code should be straightforward, except for maybe jQuery, which I’ll explain in a bit.

  1. Create a web project that uses Razor Pages. To do this on the command line, run:
  2. dotnet new webapp --name AspNetParcelExp cd AspNetParcelExp
  3. Delete the folders under wwwroot. (You may delete this later on, if you want it for reference — our goal is to generate these files using Parcel and use those instead.)
  4. Install npm Dependencies
  5. Add a package.json file to the project root like the following:
 	{
  "includePaths": [
    "./node_modules/"
  ]
}


This will allow referencing package folders without a full path to it. See parcel-bundler/parcel#39 for more information.

  1. Create a web project that uses Razor Pages. To do this on the command line, run:
  2. dotnet new webapp --name AspNetParcelExp cd AspNetParcelExp
  3. Delete the folders under wwwroot. (You may delete this later on, if you want it for reference — our goal is to generate these files using Parcel and use those instead.)
  4. Install npm Dependencies
  5. Add a package.json file to the project root like the following:
 	@import "~bootstrap/scss/bootstrap";


You may also just include the bootstrap SCSS files that you actually need to keep the output size down. Since we’re trying to replicate the template, we could also paste the code in the original template’s site.css here after the line.

  1. Create a web project that uses Razor Pages. To do this on the command line, run:
  2. dotnet new webapp --name AspNetParcelExp cd AspNetParcelExp
  3. Delete the folders under wwwroot. (You may delete this later on, if you want it for reference — our goal is to generate these files using Parcel and use those instead.)
  4. Install npm Dependencies
  5. Add a package.json file to the project root like the following:
 	"scripts": {
  "build": "parcel build assets/bundle.js --out-dir wwwroot/dist/",
  "watch": "parcel watch assets/bundle.js --out-dir wwwroot/dist/"
},

This adds scripts that can be invoked as npm run build to build, for example. It passes the bundle.js entry point to Parcel, and instructs it to generate output files in the wwwroot/dist/ using the --out-dir option.

  1. Create a web project that uses Razor Pages. To do this on the command line, run:
  2. dotnet new webapp --name AspNetParcelExp cd AspNetParcelExp
  3. Delete the folders under wwwroot. (You may delete this later on, if you want it for reference — our goal is to generate these files using Parcel and use those instead.)
  4. Install npm Dependencies
  5. Add a package.json file to the project root like the following:
npm run build

You should now see a bundle.css, bundle.js and a bundle.map file in the wwwroot/dist directory (the directory we specified for the build script above). It’s a good idea to ignore the wwwroot/dist from version control.

  1. Create a web project that uses Razor Pages. To do this on the command line, run:
  2. dotnet new webapp --name AspNetParcelExp cd AspNetParcelExp
  3. Delete the folders under wwwroot. (You may delete this later on, if you want it for reference — our goal is to generate these files using Parcel and use those instead.)
  4. Install npm Dependencies
  5. Add a package.json file to the project root like the following:
 	<script src="~/dist/bundle.js" 
        asp-append-version="true"></script>
 	And replace the stylesheet <link> tags with:
 	<link rel="stylesheet" 
      href="~/dist/bundle.css" 
      asp-append-version="true" />


That’s it. If you did everything right, running the program should display the same output as with the old files.

If it feels like a lot of work, it’s probably because you aren’t familiar with the npm, SCSS, etc., so take your time.

Watching Changes

Rather than running npm run build each time you make changes, you can use HMR (Hot Module Replacement), which will detect pages and reload for you, so that you don’t have to do it.

Open a new terminal instance and run npm run watch. Keep this running while performing any dev changes — it’ll speed you up.

Add a Pre-Publish Task

Add the following to the AspNetParcelExp.csproj file right before the closing

</Project> tag:
<Target Name="ParcelBeforePublish" 
        BeforeTargets="PrepareForPublish">
  <Exec Command="npm run build" />
</Target>


Now, every time you create a publish package, it will run the npm build script. This is particularly important in Continuous Delivery scenarios, because the wwwroot/dist is (usually) not under version control, and the build environment needs to build the files before deploying. You may test this step using dotnet publish: you’ll see output from parcel-bundler.

If you want the task to be run every time is the project is built, change PrepareForPublish to BeforeBuild.

A Note on CommonJS Modules

The parcel-bundler generates a CommonJS module, which means it doesn’t pollute the global window object. Now this can be a problem sometimes, because some libraries — particularly the old ones — have always been polluting window.

Take jQuery for instance. Libraries that require jQuery perform a test on the window object to check if it’s got a jQuery or a $ property. Since CommonJS libraries don’t pollute window, these checks will fail. So we’ll need to manually pollute the window object ourselves. We did that for jquery in bundle.js using:

import $ from 'jquery'
window.$ = window.jQuery = $

This is one thing you need to remember when using Parcel.js or other similar bundlers.

A few pointers and ideas

  • You do not have to use SCSS. LESS or even plain CSS is completely fine.
  • Parcel.js doesn’t have a config file of its own, unlike Grunt or webpack. You may, however, have config files for each tool, and parcel-bundler will honor them. E.g. tsconfig.json for typescript, .sassrc for SCSS, etc.
  • Parce.js has built-in support for PostCSS. For example, to automatically add CSS prefixes to the generated output using the autoprefixer-postcss plugin, add the following to .postcssrc at the project root:
 	{ 
  "plugins": { 
    "autoprefixer": true 
  } 
}


  • You do not have to use SCSS. LESS or even plain CSS is completely fine.
  • Parcel.js doesn’t have a config file of its own, unlike Grunt or webpack. You may, however, have config files for each tool, and parcel-bundler will honor them. E.g. tsconfig.json for typescript, .sassrc for SCSS, etc.
  • Parce.js has built-in support for PostCSS. For example, to automatically add CSS prefixes to the generated output using the autoprefixer-postcss plugin, add the following to .postcssrc at the project root: