In today’s post, we’re going to discuss an increasingly common requirement of tasks running within a web application. Depending upon your specialism you can think of these as service works, or simply background tasks. For this demo we’re going to focus on our web application continuously running a task which emits I/O data for the duration of the app’s life time. This data can then be consumed by another service, since our task needs to continuously run, it’s suited for more of a observable pattern on the client side, so we can subscribe and handle new data accordingly. In this scenario, we’re going to use SignalR to trigger invocation to any clients connected.

First, you’ll need to do a small amount of configuration, I won’t elaborate on this since it’s fairly straight forward
Continue reading

In this post, we’re going to explore the relatively new entity data seeding method which was introduced with EF Core 2.1.

It’s fantastic.

Unlike it’s predecessor EF6, seed data can be associated with migrations and it’s relative entity type as part of your migration strategy. What this means is adding or editing any seed data for any entity will compute the correct insert(s) or updates to push those data changes into your DB, this is incredibly powerful and leverages the benefits of tracking that migrations provide.

Lets see some code

First, you’ll need to override the OnModelCreating method in your DbContext, chances are you’re already doing this in an existing project.

1
2
3
4
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder)
}

Next, we’ll call the HasData method and pass in a populated instance of an entity.

1
2
3
4
5
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
modelBuilder.Entity().HasData(new Person { PersonId = 1, Name = "Ash" });
}

Good so far? Before running our add-migration command, I want to touch on a few key points:

  • Primary keys for all entities must be specified, this is how the seed data change detection works via comparing if the particular record exist, or if any fields have changed
  • Any entities with foreign keys must be explicitly stated
  • Previously seeded data that has a primary key change will be removed (this makes sense, since the record can no longer be referred to)
  • You may want custom initialization logic depending on your scenario such as temporary data for testing

Now, running add-migration firstseed will produce a migration for entering our new records (since their primary keys don’t exist in the table).

1
2
3
4
migrationBuilder.InsertData(
table: "Persons",
columns: new[] { "PersonId", "Name" },
values: new object[] { 1, "Ash" });

Any changes to this data will result in a new migration with correct behaviour (insert/update/delete) making seed data much easier to progress as your applications develops, rather than a monolithic script of inserts.

Further Points

Structuring Seed Data

Adding seed data with the OnModelCreating can become difficult to manage, especially with dozens of entities, personally I would opt to create extension methods to keep this method clean.

1
2
3
4
5
6
7
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.AddContractHoursData();
builder.AddLocationData();
builder.AddJobTitleData();
}

Additionally, creating a single contained class for each data entity was my approach, adhering to the Single Responsibility Principle and violating DRY.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void AddRewardCategoryData(this ModelBuilder model)
{
model.Entity().HasData(new List()
{
new RewardCategory()
{
RewardCategoryId = 1,
Name = "Vouchers",
Enabled = true
},
new RewardCategory()
{
RewardCategoryId = 2,
Name = "Charity",
Enabled = true
}
});
}
Creating Identity Users

One question I’ve seen people ask is seeding users from Identity (since UserManager takes care of things like Password Hash).

We can use Identities PasswordHasher.

However there’s a problem with this approach, this will programmatically generate a different hash per run, and therefore bloat every migration with updates. Additionally you’d rather avoid hard coding dozens or hundreds of type User models.

My strategy for this approach was the following:

  • Use a library such as Faker to generate a list of user details
  • Iterate through the list, assign a hard-coded GUID from a list you’ve created
  • Use the PasswordHasher to generate the hashed password
  • Pass the completed list to the HasData method
  • Copy the data within the AspNetUser table in your database as JSON (I used SQL Operations Studio for this)
  • Use some Find and replace with regular expressions to fix formatting errors and form into a list of Users
  • You should a list of populated entities, all with hard coded data without tedious data entry

Comment and share

I recently needed to auto generate boilerplate classes for a project based upon reflection meta data. Having been a few years since I’d leveraged T4 for SQL Stored Proc generation and the awesome T4MVC template, surely there’s a shiny new method?

Scripty comes up as an alternative scripting approach for code generation, unfortunately it’s having major issues with VS 2017 and the Roslyn compiler.

So, it looks like T4 Templating is still the way to go. However whilst writing the template, it appears that splitting code into multiple files and moving directories is quite a problem as templating standard behaviour is to nest files under the parent template.

Prerequisites

First, we’re going to leverage the T4 Toolbox extension. Yes, this works in VS 2017 at the time of writing (I have update 15.8.9), you can find this here.

Getting Started

So our strategy here is to have a Runner template and code generation template.

  • Runner template – This will be responsible for initialisation of the code generate template, our method is to enumerate through a collection and pass data to our template
  • Code generation template – This will contain our templating code

Please note – The below represents a basic example, and is not production code.

TemplateRunner.tt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<#@ template language="C#" debug="false" #>			 //#A
<#@ output extension="cs" #> //#A
<#@ import namespace="System.Linq" #> //#A
<#@ import namespace="System.Text" #> //#A
<#@ import namespace="System.IO" #> //#A
<#@ import namespace="System.Collections.Generic" #> //#A
<#@ include file="BaseGeneratorApi.tt" #> //#B
<#
BaseGeneratorApi baseTemplate = new BaseGeneratorApi(); //#B
var currentFolder = new FileInfo(Host.ResolvePath(Host.TemplateFile)).DirectoryName; //#C
var webProject = Path.GetFullPath(Path.Combine(currentFolder, @"../", "src")); //#C


var apiControllerNames = File.ReadAllLines(Path.Combine(currentFolder, "bin", "Debug", "netcoreapp2.1", "webapinames.txt")); //#D

if(apiControllerNames != null && apiControllerNames.Any()) {

foreach (string className in apiControllerNames)
{
baseTemplate.controllerName = className; //#E
baseTemplate.controllerNameTitled = char.ToLower(className[0]) + className.Substring(1); //#E

var controllerFileName = $"{className}Controller.cs";

baseTemplate.RenderToFile(Path.Combine("Autogenerated", controllerFileName)); #//F
}
}
#>

Lets go through the code above:

  • #A – We want to ouput .cs files, also we’ll need those namespaces to import the required classes for enumeration/file reading
  • #B – Here we instantiate our code generation template, in this example it’s a template which generates a blank Web API controller
  • #C – Since the templates base directory at runtime is within a sub directory of your VS folder, we need to do a little configuration, for this example we want to read a text file in our bin folder
  • #D – Reading of a text file
  • #E – We pass a few strings to own code generation template, since these will be the subject of said class
  • #F – We call the RenderToFileMethod, passing a file path for our class
BaseGeneratorApi.tt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<#@ output extension="cs" #>
<#@ include file="T4Toolbox.tt" #> //#A
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Collections.Generic" #>
<#+
public class BaseGeneratorRepository : CSharpTemplate //#B
{
public string repositoryName;
public string repositoryNameTitled;

public override string TransformText() //#B
{
base.TransformText();

#>

//All your code goes here!
[Route("api/[controller]")]
[Authorize]
public class <#= $"{controllerName}Controller" #> : ControllerBase
{

}


<#+
return this.GenerationEnvironment.ToString();
}
}
#>

As above:

  • #A – Here we’ll leverage the helpers which T4 Toolbox providses, therefore you’ll in to include the file
  • #B – We’ll want to inherit from the abstract class CSharpTemplate which is part on the T4 toolbox. In our derived class we’ll access the TransformText method in the base class to append our additional code

You can have multiple code generation templates which are used within a single template runner, however it increases the scope of a problem occurring in the transformations, and therefore a 1:1 ratio is better.

Comment and share

With NopCommerce I’ve noticed high waits on sql querys resolving when hitting a database in a test environment for local development. This problem becomes more apparent when your seed / test dataset is quite large.

When logging into the Administration area, I would frequently get the following, despite setting a high connection timeout period:

1
2
3
4
5
6
Exception Details: System.ComponentModel.Win32Exception: The wait operation timed out
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[Win32Exception (0x80004005): The wait operation timed out]
The stack trace pointed towards PageList.cs class, the method hit returns an IQueryable DbSet. The initial Administrator dashboard performs a lot of I/O reads which
has a large contribution towards the timeout issue.

Solution

All the dashboard statistic calls are within a single View which can be found at:

Nop.Admin/Views/Home/Index.cshtml

To avoid running these on your machine, use the HttpContext.Current.IsDebuggingEnabled and wrap in an If, like below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<div class="content">
<div class="row">
<div class="col-md-12">

@if (!HttpContext.Current.IsDebuggingEnabled)
{
@Html.Widget("admin_dashboard_top")
if (!Model.IsLoggedInAsVendor)
{
<div class="row">
<div class="col-md-12">
@Html.Action("NopCommerceNews", "Home")
</div>
</div>
}
@Html.Widget("admin_dashboard_news_after")
if (!Model.IsLoggedInAsVendor && canManageOrders && canManageCustomers && canManageProducts && canManageReturnRequests)
{
<div class="row">
<div class="col-md-12">
@Html.Action("CommonStatistics", "Home")
</div>
</div>
}
@Html.Widget("admin_dashboard_commonstatistics_after")
if (!Model.IsLoggedInAsVendor && (canManageOrders || canManageCustomers))
{
<div class="row">
@if (!Model.IsLoggedInAsVendor && canManageOrders)
{
<div class="col-md-6">
@Html.Action("OrderStatistics", "Order")
</div>
}
@if (!Model.IsLoggedInAsVendor && canManageCustomers)
{
<div class="col-md-6">
@Html.Action("CustomerStatistics", "Customer")
</div>
}
</div>
}
@Html.Widget("admin_dashboard_customerordercharts_after")
if (!Model.IsLoggedInAsVendor && canManageOrders)
{
<div class="row">
<div class="col-md-8">
@Html.Action("OrderAverageReport", "Order")
</div>
<div class="col-md-4">
@Html.Action("OrderIncompleteReport", "Order")
</div>
</div>
}
@Html.Widget("admin_dashboard_orderreports_after")
if (!Model.IsLoggedInAsVendor && (canManageOrders || canManageProducts))
{
<div class="row">
@if (canManageOrders)
{
<div class="col-md-8">
@Html.Action("LatestOrders", "Order")
</div>
}
<div class="col-md-4">
@if (canManageProducts)
{
@Html.Action("PopularSearchTermsReport", "Common")
}
</div>
</div>
}
@Html.Widget("admin_dashboard_latestorders_searchterms_after")
if (canManageOrders)
{
<div class="row">
<div class="col-md-6">
@Html.Action("BestsellersBriefReportByQuantity", "Order")
</div>
<div class="col-md-6">
@Html.Action("BestsellersBriefReportByAmount", "Order")
</div>
</div>
}
@Html.Widget("admin_dashboard_bottom")
}


</div>
</div>
</div>

Depending on the context and environment of the project, you may want to write a more robust solution, however for local debugging this is perfectly fine.

Comment and share

Recently I needed to use MSBuild to compile a solution for a build server.

The MSBuild was location at:

C:\Program Files (x86)\MSBuild\14.0\Bin

The following errors were occurring despite this working in the past:

error CS1525: Invalid expression term ‘decimal’ Syntax error, ‘,’ expected error CS1003: Syntax error, ‘,’ expected [Web.csproj]

This was due to C#6/C#7 features which aren’t supported by this MSBuild version.

The Fix

Using this MSBuild resolved the issue:

C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin

Comment and share

Author's picture

Ash Grennan

Software engineer, mostly working with .NET, Identity, React, AWS and many other technologies.

AO.com

UK