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 :: How to Fix “An error occurred while starting the application” in ASP.NET Core on IIS

clock July 5, 2019 07:33 by author Scott

.NET Core is the latest Microsoft product and Microsoft always keep update their technologies. We have written tutorial about how to publish ASP.NET Core on IIS server, but we know that some of you sometimes receive error when deploying your ASP.NET Core. Feel frustrated to fix the issue? Yeah, not only you have headache, but some of our clients also experience same problem like you. That’s why we write this tutorial and hope it can help to fix your issue!

Anyone see this error? Have problem to solve it? We want to help you here!

The Problem

 

It basically means something bad happened with your application. Things you need to check

  • You might not have the correct .NET Core version installed on the server.
  • You might be missing DLL’s
  • Something went wrong in your Program.cs or Startup.cs before any exception handling kicked in

If you use Windows Server, then I believe that you can’t find anything on your Event Viewer too. You’ll notice that there is no error on your Event Viewer log. Why? This is because Event Logging must be wired up explicitly and you’ll need to use the Microsoft.Extensions.Logging.EventLog package, and depending on the error, you might not have a chance to even catch it to log to the Event Viewer.

How to Fix it?

So, how to fix error above? The followings are the steps to fix the error:

1. Open your web.config

2. Change stdoutLogEnabled=true

3. Create a logs folder

  • Unfortunately, the AspNetCoreModule doesn’t create the folder for you by default
  • If you forget to create the logs folder, an error will be logged to the Event Viewer that says: Warning: Could not create stdoutLogFile \\?\YourPath\logs\stdout_timestamp.log, ErrorCode = -2147024893.
  • The “stdout” part of  the value “.\logs\stdout” actually references the filename not the folder.  Which is a bit confusing.

4. Run your request again, then open the \logs\stdout_*.log file

Note – you will want to turn this off after you’re done troubleshooting, as it is a performance hit.

So your web.config’s aspNetCore element should look something like this

 <aspNetCore processPath=”.\YourProjectName.exe” stdoutLogEnabled=”true” stdoutLogFile=”.\logs\stdout” />

Doing this will log all the requests out to this file and when the exception occurs, it will give you the full stack trace of what happened in the \logs\stdout_*.log file

  

 

 

Hope this helps!

Looking for ASP.NET Core hosting with affordable cost?

We know it is quite hard to find reliable hosting that support ASP.NET Core. We believe that some of you have signed up for 1 hosting, but they are below your expectation. Maybe they don’t support latest ASP.NET Core or they might support .NET Core but their support are not competent. We are here to help you. If you are looking for cheap, reliable with best ASP.NET Core hosting support, then you can visit our site at https://www.hostforlife.eu.



European ASP.NET Core Hosting :: Integration Test ASP.NET Core

clock June 25, 2019 09:19 by author Scott

Writing integration tests for ASP.NET Core controller actions used for file uploads is not a rare need. It is fully supported by ASP.NET Core integration tests system. This post shows how to write integration tests for single and multiple file uploads.

Getting started

Suppose we have controller action for file upload that supports multiple files. It uses complex composite command for image file analysis and saving. Command is injected to action by framework-level dependency injection using controller action injection.

[HttpPost]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> Upload(IList<IFormFile> files, int? parentFolderId,
                                        [FromServices]SavePhotoCommand savePhotoCommand)
{
    foreach(var file in files)
    {
        var model = new PhotoEditModel();
        model.FileName = Path.GetFileName(file.FileName);
        model.Thumbnail = Path.GetFileName(file.FileName);
        model.ParentFolderId = parentFolderId;
        model.File = file;
 
        list.AddRange(savePhotoCommand.Validate(model));
 
        await savePhotoCommand.Execute(model);
    }
 
    ViewBag.Messages = savePhotoCommand.Messages;
 
    return View();
}

We want to write integration tests for this action but we need to upload at least one file to make sure that command doesn’t fail.

Making files available for integration tests

It’s good practice to have files for testing available no matter where tests are run. It’s specially true when writing code in team or using continuous integration server to run integration tests. If we don’t have many files and the files are not large then we can include those files in project.

Important thing is to specify in Visual Studio that these files are copied to output folder.

Same way it’s possible to use also other types of files and nobody stops us creating multiple folders or folder trees if we want to organize files better.

Uploading files in integration tests

Here is integration tests class for controller mentioned above. Right now there’s only one test and it is testing Upload action. Notice how image files are loaded from TestPhotos folder to file streams and how form data object is built using the file streams.

public class PhotosControllerTests : IClassFixture<WebApplicationFactory<Startup>>
{
    private readonly WebApplicationFactory<Startup> _factory;
 
    public PhotosControllerTests(WebApplicationFactory<Startup> factory)
    {
        _factory = factory;
    }
 
    [Fact]
    public async Task Upload_SavesPhotoAndReturnSuccess()
    {
        // Arrange
        var expectedContentType = "text/html; charset=utf-8";
        var url = "Photos/Upload";
        var options = new WebApplicationFactoryClientOptions { AllowAutoRedirect = false };
        var client = _factory.CreateClient(options);
 
        // Act
        HttpResponseMessage response;
 
        using (var file1 = File.OpenRead(@"TestPhotos\rt-n66u.jpg.webp"))
        using (var content1 = new StreamContent(file1))
        using (var file2 = File.OpenRead(@"TestPhotos\speedtest.png.webp"))
        using (var content2 = new StreamContent(file2))
        using (var formData = new MultipartFormDataContent())
        {
            // Add file (file, field name, file name)
            formData.Add(content1, "files", "rt-n66u.jpg.webp");
            formData.Add(content2, "files", "speedtest.png.webp");
 
            response = await client.PostAsync(url, formData);
        }
 
        // Assert
        response.EnsureSuccessStatusCode();
        var responseString = await response.Content.ReadAsStringAsync();
 
        Assert.NotEmpty(responseString);
        Assert.Equal(expectedContentType, response.Content.Headers.ContentType.ToString());
 
        response.Dispose();
        client.Dispose();
    }
}

For actions that accept only one file we need only one call to Add() method of formData.

Wrapping up

Integration tests mechanism in ASP.NET Core is flexible enough to support also more advanced scenarios like file uploads in tests. It’s not very straightforward and we can’t just call few methods of HTTP client to do it but it’s still easy enough once we know the tricks. If we keep test files in integration tests project then we don’t have to worry about getting files to machine where integration tests are running.



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 :: 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.



European ASP.NET Core Hosting - HostForLIFE.eu :: Customising model-binding conventions in ASP.NET Core

clock February 21, 2017 08:04 by author Scott

A pattern I use when building Web APIs is to create commands to represent an API operation and models to represent resources or results. We share these "common" objects with our .NET client so we can be sure we're using the same parameters names/types.

Here's an excerpt from Fabrik's API for creating a project:

public HttpResponseMessage Post(int siteId, AddProjectCommand command)
{
    var project = new CMS.Domain.Project(
        session.GetSiteId(siteId),
        command.Title,
        command.Slug,
        command.Summary,
        command.ContentType,
        command.Content,
        command.Template,
        command.Tags,
        command.Published,
        command.Private);

    session.Store(project);

    var model = CreateProjectModel(project);
    var link = Url.Link(RouteNames.DefaultRoute, new { controller = "projects", siteId = siteId, id = project.Id.ToIntId() });

    return Created(model, new Uri(link));
}

We also use commands for GET operations that have multiple parameters such as search endpoints. So instead of:

public IActionResult GetProjects(string searchTerm = null, int page = 1, int pageSize = 10)
{

}

We have a GetProjectsCommand:

public class GetProjectsCommand
{
    public string SearchTerm { get; set; }
    [MinValue(1, ErrorMessage = "Page must be greater than 0.")]
    public int Page { get; set; } = 1;
    public int PageSize { get; set; } = 20;
}

This provides a single place to encapsulate our default values and validation rules, keeping our controllers nice and lean.

Model-binding in ASP.NET Core MVC

To bind complex types to query strings in ASP.NET Web API we had to change the parameter binding rules. This is because the default was to bind complex types from the HTTP Request body.

When implementing the above pattern in ASP.NET Core I was pleasantly surprised to see that the following worked out of the box:

// GET: api/values
[HttpGet]
public IEnumerable<string> Get(GetValuesCommand command)
{

}

I thought that perhaps the framework detected that this was a HTTP GET request and therefore bound the parameter values from the query string instead.

Actually this is not the case - in ASP.NET Core, complex types are not bound from the request body by default. Instead you have to opt-in to body-based binding with the FromBodyAttribute:

// POST api/values
[HttpPost]
public void Post([FromBody]AddValueCommand command)
{
}

This seems an odd default given that (in my experience) binding complex types from the request body is far more common.

In any case, we can customise the default model-binding behaviour by providing a convention:

public class CommandParameterBindingConvention : IActionModelConvention
{
    public void Apply(ActionModel action)
    {
        if (action == null)
        {
            throw new ArgumentNullException(nameof(action));
        }

        foreach (var parameter in action.Parameters)
        {
            if (typeof(ICommand).IsAssignableFrom((parameter.ParameterInfo.ParameterType)))
            {
                parameter.BindingInfo = parameter.BindingInfo ?? new BindingInfo();
                parameter.BindingInfo.BindingSource = BindingSource.Body;
            }
        }
    }
}

Which is registered like so:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Conventions.Add(new CommandParameterBindingConvention());
    });
}

This convention checks to see if the parameter type implements ICommand (a marker interface I created) and if so, instructs the framework to bind the values for this parameter from the request body.

All I have to do then is update my command with this interface:

public class AddValueCommand : ICommand
{
    public string Value { get; set; }
}

Then I can drop the unnecessary [FromBody] attribute:

// POST api/values
[HttpPost]
public void Post(AddValueCommand command)
{
}

 



European ASP.NET Core Hosting - HostForLIFE.eu :: How to Setup Webpack in ASP.NET Core

clock February 10, 2017 11:11 by author Scott

Webpack is a great tool for bundling the client side assets in your web application. In this post we'll briefly discuss why you should create bundles and see how Webpack can automate that for us in ASP.NET Core.

Why should I bundle

When building web applications, regardless of the server side framework, you'll need to get your client side resources over to the browser. You may have dozens of JavaScript and CSS files in your project but having to reference each of them individually in your HTML markup is just not ideal for production deployments.

Each browser only allows so many concurrent requests per hostname. BrowserScope has some data on this that you can view on their site. If your web application makes more than the allowed number of simultaneous requests then the additional requests will end up being queued. This leads to longer load times and a not so smooth experience for your users; especially on mobile devices.

It would be much better to group resources into bundles so that the browser would have fewer files to download and thus fewer requests to make. This will help in keeping bandwidth usage low and even with battery life on your users' devices.

What is Webpack?

Webpack is a module bundler for the static assets in your web application. Essentially, you point Webpack at the main entry point(s) of your code then it will determine the dependencies, run transformations and create bundles that you can provide to your browser. What's even better is that in addition to JavaScript Webpack can also handle CSS, LESS, TypeScript, CoffeeScript, images, web fonts and more.

Setting up

We're going to start by setting up a new ASP.NET Core project using the dotnet cli tooling. If you don't have tooling installed, you can find can setup files and instructions here. If you're not a Windows user, the tooling works on Windows, OSX and Linux so no need to worry.

Let's get started by opening a terminal and creating an empty directory. Now, we'll generate a new ASP.NET Core project by running the following command:

dotnet new -t web 

Currently, the generated project includes .bowerrc and bower.json files. You can delete these since we'll be using NPM to install packages. If you don't have NodeJS installed on your system, make sure you do so before continuing.

The next thing we'll do is create a folder called Scripts in the root of your project. You'll find out why later on. Also add an empty webpack.config.js to the root of your project. As you might have guessed, this is the file we'll use to configure Webpack. Your project layout should look something like this.

Configuring Webpack

Before we start configuring Webpack, let's check out where our assets actually are. ASP.NET Core places all the files destined for the browser inside of the wwwroot folder by default. If you take a peep inside that folder, you'll see sub folders for your your JavaScript, CSS and image files. Note, the names of these sub folders aren't important. Feel free to rename them if you wish.

Personally, I prefer to reserve the wwwroot folder for the bundles that I want to provide to the browser. What we'll do is use the Scripts directory that was created earlier for the working files that will get included in the bundles.

Let's add two pretty trivial JavaScript files to our Scripts folder.

//other.js
function func() {
    alert('loaded!');
}
module.exports = func;

//main.js
var other = require('./other');

other();

Our scripts have been written using the CommonJS module syntax. The main.js file imports other.jsand calls the exported function. Ok, simple enough. Let's take a look at webpack.config.js.

var path = require('path');

module.exports = {
    entry: {
        main: './Scripts/main'
    },
    output: {
       publicPath: "/js/",
       path: path.join(__dirname, '/wwwroot/js/'),
       filename: 'main.build.js'
    }
};

The webpack.config.js file is a CommonJS module that we'll use to setup Webpack. The sample above shows a fairly bare bones Webpack configuration. Inside of entry, we define a main module and point it to the location of main.js file since that's where our app starts. The name of the bundle can be changed to something else if you like. There's no requirement for it to be called main. Inside of output, we let Webpack know what to name the bundle file and where to place it. The publicPath property configures the relative URL that the browser will use to reference our bundles.

Alright, that's good for now. Before we generate our bundle, make sure you have Webpack installed globally on your machine. Type the following command in your terminal. You'll only have to do this once.

npm i -g webpack 

Now we're ready to create our bundle. Make sure your terminal path is at the root of your project directory. Now run

webpack 

Your terminal output should look similar to below.

In your code editor, open Views/_Layout.cshtml. Near the bottom of the file, add a script reference to our bundle.

<script src="~/js/main.build.js"></script>

The generated ASP.NET Core template adds a few scripts tags wrapped in environment tag helpers. Go ahead and remove these for now.

<environment names="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
        <script src="~/js/site.js" asp-append-version="true"></script>
</environment>

Finally, we can run our application and see if our bundle works. Execute the following commands in the command terminal.

dotnet restore
dotnet run

Navigate to http://localhost:5000 in your browser. If everything works as expected, you should see an alert with loaded! in the browser window.

Tying the builds together

We can reduce the number of commands we have to type by leveraging the build events in project.json. Update the scripts section to include the precompile event. To see the other available events, head over to the .NET Core Tools docs.

"scripts": {
    "precompile": ["webpack"],
  },

Now running dotnet run or dotnet build will also run Webpack to generate the bundle.

Conclusion

In this post, we got a short introduction to setting up Webpack in ASP.NET Core. Webpack often gets labeled as overly complex and difficult to setup. Hopefully, this post showed you how easy it is to get started and made you a little more curious about what else it can do.



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