| name | ewl-server-side-console-app |
| description | Create server-side console apps and call them from EWL web apps or other apps in the system. Use when creating a new console app, background worker, bulk operations worker, batch job, scheduled task, background job, background process, worker process, offline job, offline processor, or Exe project in an EWL system. |
Overview
A server-side console app is a standalone console project that performs
long-running or bulk operations outside the web request lifecycle. EWL calls
these "server-side console projects." The Development Utility generates:
- A
Program class with Main that initializes EWL, deserializes arguments,
and calls a partial void ewlMain method you implement.
- A
ServerSideConsoleAppStatics class in the Library with a typed
Start<Name> method for launching the process.
Configuring the project in Development.xml
Add a serverSideConsoleProjects entry in Library\Configuration\Development.xml:
<serverSideConsoleProjects>
<project>
<Name>Bulk Operations Worker</Name>
<NamespaceAndAssemblyName>MySystem.BulkOperationsWorker</NamespaceAndAssemblyName>
</project>
</serverSideConsoleProjects>
The Name is the folder name for the project inside the solution root. The
NamespaceAndAssemblyName is the root namespace and assembly name.
Creating the project folder and csproj
Create a folder matching the Name (e.g. Bulk Operations Worker\) at the
solution root. Add a .csproj file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Library\Library.csproj" />
</ItemGroup>
</Project>
The Directory.Build.props file (generated by sync) sets
the AssemblyName, RootNamespace, RuntimeIdentifier (win-x64), and
other standard properties. You do not need to set those in the .csproj.
Generated code
After running sync, the DU generates
Generated Code\Main.g.cs in the project folder containing:
namespace MySystem.BulkOperationsWorker;
internal static partial class Program {
private static int Main( string[] args ) {
var dataAccessState = new Lazy<DataAccessState>( () => new DataAccessState() );
GlobalInitializationOps.InitStatics(
new GlobalInitializer(), "Bulk Operations Worker", false,
mainDataAccessStateGetter: () => dataAccessState.Value! );
try {
return GlobalInitializationOps.ExecuteAppWithStandardExceptionHandling( () => {
Console.SetIn( new StreamReader( Console.OpenStandardInput(), Console.InputEncoding, false, 4096 ) );
ewlMain( );
} );
}
finally {
GlobalInitializationOps.CleanUpStatics();
}
}
static partial void ewlMain( IReadOnlyList<string> arguments );
}
This handles EWL initialization, argument deserialization (JSON via stdin when
invoked through the generated starter), and cleanup.
Implementing the worker
Create a Program.cs (or any .cs file) in the project folder with your
partial class Program implementing ewlMain:
namespace MySystem.BulkOperationsWorker;
partial class Program {
static partial void ewlMain( IReadOnlyList<string> arguments ) {
var organizationId = int.Parse( arguments[ 0 ] );
var userId = int.Parse( arguments[ 1 ] );
var filePath = arguments[ 2 ];
DataAccessState.Current.PrimaryDatabaseConnection.ExecuteWithConnectionOpen(
() => DataAccessState.Current.PrimaryDatabaseConnection.ExecuteInTransaction( () => {
} ) );
}
}
The arguments list is strongly typed as IReadOnlyList<string>. Parse each
positional argument as needed. The full EWL data access layer is available,
including table retrievals, modifications, and email.
Generated starter method
The DU also generates a ServerSideConsoleAppStatics class in the Library's
generated code. For a project named "Bulk Operations Worker", it generates:
public static class ServerSideConsoleAppStatics {
public static void StartBulkOperationsWorker(
IEnumerable<string> arguments, string input,
string errorMessageIfAlreadyRunning = "" ) { ... }
}
This method:
- If
errorMessageIfAlreadyRunning is non-empty, checks whether the process
is already running and throws DataModificationException if so.
- If called within an EWL web request (
EwfRequest.Current is not null),
defers execution via AutomaticDatabaseConnectionManager.AddNonTransactionalModificationMethod.
This ensures the process launches after the current database transaction
commits successfully.
- If called outside an EWL web request, runs the process immediately and
synchronously via
ProcessTools.RunProgram.
Calling from an EWL web app
Call the generated starter method from a page's modification method. The
process will start after the current transaction commits:
partial class EventFileImport {
protected override PageContent getContent() =>
FormState.ExecuteWithActions(
PostBack.CreateFull(
modificationMethod: () => {
var filePath = EwlStatics.CombinePaths( folderPath, fileName );
File.WriteAllBytes( filePath, uploadedFile.Contents );
ServerSideConsoleAppStatics.StartBulkOperationsWorker(
organizationId.ToString()
.ToCollection()
.Append( SystemUser.Current.UserId.ToString() )
.Append( filePath ),
"",
errorMessageIfAlreadyRunning:
"Another operation is already in progress." );
AddStatusMessage( StatusMessageType.Info,
"Import started. You will receive an email shortly." );
} ),
() => new UiPageContent( contentFootActions: new ButtonSetup( "Import" ) )
.Add( fileUploadComponents ) );
}
Because AddNonTransactionalModificationMethod is used under the hood, the
worker only launches if the web request's transaction commits. If validation
fails or an exception is thrown, the worker is never started.
Calling from an ASP.NET MVC app
In a non-EWL web app, call ProcessTools.RunProgram directly. There is no
EwfRequest.Current, so the generated starter runs the program synchronously.
You can either call the generated starter or invoke the process yourself:
using EnterpriseWebLibrary.TewlContrib;
ServerSideConsoleAppStatics.StartBulkOperationsWorker(
new[] { organizationId.ToString(), userId.ToString(), filePath },
"" );
var programPath = EwlStatics.CombinePaths(
ConfigurationStatics.InstallationConfiguration.InstallationPath,
"Bulk Operations Worker",
ConfigurationStatics.ServerSideConsoleAppRelativeFolderPath,
"MySystem.BulkOperationsWorker" );
ProcessTools.RunProgram(
programPath,
"ewlUseJsonArguments",
JsonConvert.SerializeObject( arguments ) + Environment.NewLine + input,
false );
The ewlUseJsonArguments first argument tells the generated Main to
deserialize the argument list from JSON on stdin rather than using raw
command-line args.
Passing data to the worker
Arguments are passed as an IEnumerable<string> (serialized as JSON via
stdin). For simple values, use positional string arguments:
ServerSideConsoleAppStatics.StartBulkOperationsWorker(
new[] { organizationId.ToString(), userId.ToString(), filePath },
"" );
The second parameter (input) is additional stdin content appended after the
JSON arguments line. Use it for large data that does not fit in arguments.
Error handling and notifications
The worker runs as a separate process with its own EWL initialization. Use
standard EWL patterns for error handling and email notifications:
static partial void ewlMain( IReadOnlyList<string> arguments ) {
DataAccessState.Current.PrimaryDatabaseConnection.ExecuteWithConnectionOpen(
() => DataAccessState.Current.PrimaryDatabaseConnection.ExecuteInTransaction( () => {
var errors = new List<DataValidationError>();
processRecords( errors );
var message = new EmailMessage();
message.ToAddresses.Add( new EmailAddress( user.EmailAddress, user.FriendlyName ) );
message.Subject = errors.Any() ? "Import failed" : "Import succeeded";
message.BodyHtml = buildResultHtml( errors );
GlobalStatics.SendEmail( message );
if( errors.Any() )
throw new DoNotCommitException();
}, createSavepointIfAlreadyInTransaction: true ) );
}
The DoNotCommitException pattern rolls back the transaction so no partial
data is committed, while still allowing the email to be sent before the
rollback (if using a non-transactional email method) or by sending the email
outside the transaction scope.
Run Sync
Run sync after adding or modifying the
serverSideConsoleProjects configuration in Development.xml. This
regenerates both the worker's Main.g.cs and the Library's
ServerSideConsoleAppStatics class. After running the DU for a new project,
add it to the solution with dotnet sln add.