In this article, we're going to talk about using a scripting language for developing server-side scripts for web applications. As a scripting language, we'll be using CSCS (Customized Scripting in C#), which is an easy and a lightweight open-source language that has been described in previous CODE Magazine articles: https://www.codemag.com/article/1607081 introduced it, https://www.codemag.com/article/1711081 showed how you can use it on top of Xamarin to create cross-platform native mobile apps, and https://www.codemag.com/article/1903081 showed how you can use it for Unity programming.

CSCS resembles JavaScript with a few differences, such as the variable and function names being case-insensitive. In this article, we'll be talking about developing web APIs with CSCS. As an abbreviation, we're going to call this version of CSCS as CSCS Web.

Working Principles

CSCS Web is very flexible so it can be used for developing SSR (server-side rendering) or CSR (client-side rendering) applications. An important feature is that you can use existing language functions, or you can also define and create new language functions.

In the current version of CSCS Web, most of the basic functions are implemented. Extensions can be made simply by registering new language functions and then implementing these functions. Later in this article, we're going to show you how to do that.

In SSR applications, you can use templating. In other words, you can predefine HTML pages and use them as templates that will be rendered in a browser. Or you can dynamically format an HTML page that can include HTML code, CSS code, and JavaScript code. In the CSR mode, CSCS Web is mainly used as an API server.

You can also mix the SSR and the CSR code. For example, you can first preload an HTML page that is rendered on the server, and then later, you can proceed with hydrating the whole application on the client side using JavaScript or a JavaScript framework. Below, we will show you how CSCS Web can be used for the server-side rendering of the web applications.

CSCS Web is in a development phase, but any developer can continue using and improving this phase on their own. It's 100% open-source and free to use. Check out the GitHub access links in the sidebar.

Web applications often need APIs with server-side logic for applications to communicate with each other. Servers respond to clients' requests sent to the server's API endpoints.

In this article, such endpoints will be written in CSCS. Interpreted CSCS scripts will use ASP.NET's Minimal API for creating endpoints. CSCS APIs are created as functions, so it's in accordance with the CSCS functional paradigm. When endpoints get called, script functions are executed, and responses are returned. Endpoints, defined in the script, will also be able to accept and access the request's headers and body, work with JSON data, and load, fill, and return HTML templates. We'll also use HTMX as a front-end library that needs a server to generate partial HTML code which, once returned, gets swapped into the DOM.

Use ASP.NET Core and CSCS together, because sometimes C# just isn't dynamic enough and sometimes you need an extra scripting language to blame.

– Anonymous

This covers many of the functionalities and gives the opportunity to write APIs inside of the customized scripting language.

Endpoints

In this section, we'll describe how you can create endpoints in CSCS, handle calls to these endpoints, access the request's parameters, and return responses to the client.

A web endpoint is a specific URL or URI that serves as an entry point for interacting with a web application, service, or API.

Creating Endpoints

For opening an endpoint in CSCS, we implemented the CreateEndpoint() CSCS function. Its usage is like that in the following code snippet:

CreateEndpoint(
    "GET",
    "/",
    "getRootHandlerFunction"
);

You can check how this CSCS function is implemented in Listing 1.

Listing 1: Implementation of the CreateEndpoint() CSCS Function

class CreateEndpointFunction : ParserFunction
{
    private async Task<Variable> ExecScriptFunctionAsync(HttpContext context, string scriptFunctionName, string httpMethod)
    {
        var requestData = new Variable(Variable.VarType.ARRAY);
        requestData.SetHashVariable("HttpMethod", new Variable(context.Request.Method));
        requestData.SetHashVariable("RequestPath", new Variable(context.Request.Path));

        var routeParams = new Variable(Variable.VarType.ARRAY);
        foreach (var (key, value) in context.Request.RouteValues)
        {
            routeParams.SetHashVariable(key, new Variable(value?.ToString()));
        }
        requestData.SetHashVariable("RouteValues", routeParams);

        // Add query parameters
        var queryParams = new Variable(Variable.VarType.ARRAY);
        foreach (var (key, value) in context.Request.Query)
        {
            queryParams.SetHashVariable(key, new Variable(value.ToString()));
        }
        requestData.SetHashVariable("QueryParams", queryParams);

        // Add headers
        var headers = new Variable(Variable.VarType.ARRAY);
        foreach (var (key, value) in context.Request.Headers)
        {
            headers.SetHashVariable(key, new Variable(value.ToString()));
        }
        requestData.SetHashVariable("Headers", headers);

        // Add body
        var body = Variable.EmptyInstance;
        if (context.Request.ContentLength > 0)
        {
            try
            {
                using var reader = new StreamReader(context.Request.Body);
                var bodyContent = await reader.ReadToEndAsync();
                requestData.SetHashVariable("Body", new Variable(bodyContent));
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                requestData.SetHashVariable("Body", Variable.EmptyInstance);
            }
        }
        else
        {
            requestData.SetHashVariable("Body", Variable.EmptyInstance);
        }

        try
        {
            return CSCSWebApplication.Interpreter.Run(scriptFunctionName, requestData);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return new Variable("Server error.");
        }
    }
}

Here, the first parameter tells us which HTTP method should be used to invoke this endpoint, the second parameter tells us the route (relative web address) of the endpoint, and the third one tells us the name of the CSCS function that will get called when the endpoint gets called. This CSCS function is defined like this:

function getRootHandlerFunction() {
    responseBody = "<html><body><h1>Hello World from Web API!</h1><a href='testingPage'>Go to testing page</a></body></html>";

    responseHeaders = {"Content-Type": "text/html"};
    statusCode = 200;

    result = {
        "headers": responseHeaders,
        "body": responseBody,
        "statusCode": statusCode
    };

    return result;
}

This function builds a string containing a simple HTML code that will be returned to the client. When returning the HTML code, the response headers must define the Content-Type to be "text/html" but you can return any type of content. You just need to specify it in the response headers and include it in the response body. For example: application/json, text/plain, image/jpg, etc.

We will go over the details on how to run the sample script later in this article, but you can see in Figure 1 how the main page looks.

Figure 1: Main screen when opening http://localhost:5058
Figure 1: Main screen when opening http://localhost:5058

There's a link to the testing page in Figure 1. Here's the CSCS function that implements what happens when the user clicks on the "Go to testing page" link (defined in the previous code snippet):

CreateEndpoint("GET", "/testingPage", "getTestingPageFunction");

function getTestingPageFunction() {
    responseBody = RenderHtml(
        LoadTemplate(
            ReadConfig("TemplatesDirectory") + "testingPage.html"
        )
    );

    responseHeaders = { "Content-Type": "text/html" };
    statusCode = 200;

    return Response(responseHeaders, responseBody, statusCode);
}

You can also access the endpoint created above directly, by typing **http://localhost:5058/testingPage** in your browser's URL entry field.

Returning Responses

In the previous code snippet, a simple HTML was returned to the client. The responseHeaders dictionary defines headers for the response, the responseBody string contains the body of the response, and the statusCode integer shows the status code being sent to the client.

Another way to create such a dictionary is to use the Response() function. All three of the mentioned variables can be passed to the Response() function that generates a dictionary required by the return statement.

Here's an example:

return Response(responseHeaders, responseBody, statusCode)

Accessing the Request's Headers and Body

Different request's parameters can be accessed in a CSCS script function in the following way:

CreateEndpoint("GET", "/testParameters", "testParameters");

function testParameters(request) {
    Method = request["HttpMethod"];
    RequestPath = request["RequestPath"];
    Headers = request["Headers"]; // dictionary
    QueryParams = request["QueryParams"]; // dict
    RouteValues = request["RouteValues"]; // dict
    Body = request["Body"]; // string
    //...
}

As the function parameter, you can type any variable name and use it later to retrieve the request's arguments.

If you need to extract the query parameters, you can do it like in the following CSCS code:

CreateEndpoint("GET", "/listQueryParams", "listQueryParams");

function listQueryParams(request) {
    QueryParams = request["QueryParams"]; // dict

    for (key : QueryParams.Keys) {
        Console.Log(key + " = " + QueryParams[key]);
    }
    //...
}

CreateEndpoint("GET", "/listRouteValues/{value1}/{value2}", "listRouteValues");

function listRouteValues(request) {
    RouteValues = request["RouteValues"]; // dict

    print("value1=" + RouteValues["value1"]);
    print("value2=" + RouteValues["value2"]);
    //...
}

Check out Listing 1 to see how this CSCS functionality has been implemented.

Working with HTML Templates

In the first example, you saw an endpoint returning a string built inside of the function, but you can also read HTML from a template file. That file can include placeholders that can be filled with actual data, and “if” blocks that get included or excluded from the template depending on a condition.

Loading Templates

To simply load and render a template (a static page, without placeholders or if blocks) you can use the following CSCS code of the testingPage endpoint that was defined above:

function getTestingPage() {
    responseBody = RenderHtml(LoadTemplate(
        ReadConfig("TemplatesDirectory") + "testingPage.html"));
    ...
}

Here, you can also see the ReadConfig() CSCS function that's used to retrieve different config entries from the appsettings.json file via a config entry key.

The LoadTemplate() CSCS function loads the HTML file into memory only and returns an integer representing a handle to this template. This integer is used by other templating functions in this chapter, like the RenderHtml() in the code above.

Filling Template Placeholders

There are also a few placeholder replacement functions that should be called between the LoadTemplate() and RenderHtml() calls. They are the following:

* FillTemplateFromDictionary()
* FillTemplatePlaceholder()

  • RenderCondition().

FillTemplateFromDictionary() has as its arguments the template handle mentioned in the previous section and a dictionary with values for placeholders as arguments of the function. It then finds all placeholders that are named as the keys in the supplied dictionary and replaces those placeholders with values from that dictionary. Here's an example of the CSCS script:

htmlHndl = LoadHtmlTemplate("path_to\\template.html");

valuesDictionary["companyName"] = "My Company";
valuesDictionary["companyAddress"] = "123 Main St, Anytown, USA";

FillTemplateFromDictionary(htmlHndl, valuesDictionary);

Let's look at how you can implement any CSCS function and register it with the CSCS interpreter. As an example, let's take a look the implementation of the FillTemplateFromDictionary.

This CSCS function is implemented in C# as follows:

class FillTemplateFromDictionaryFunction
{
    : ParserFunction
    {
        protected override Variable Evaluate(ParsingScript script)
        {
            var args = script.GetFunctionArgs();
            int htmlHndl = Utils.GetSafeInt(args, 0);
            var valuesDict = Utils.GetSafeVariable(args, 1);
            var dictKeys = valuesDict.GetKeys();
            var dictVariables = valuesDict.GetAllKeys();

            foreach (var key in dictKeys)
            {
                var newValue = valuesDict.GetVariable(key);
                Placeholders.ReplaceAll(htmlHndl, key, newValue.AsString());
            }

            return Variable.EmptyInstance;
        }
    }
}

The following code registers this function with the CSCS Interpreter in the initialization phase:

interpreter.RegisterFunction(
    "FillTemplatePlaceholder",
    new FillTemplatePlaceholderFunction()
);

In the same way, you can define any other function in CSCS. The syntax is the following:

interpreter.RegisterFunction(
    "FunctionName",
    new FunctionImplementationClass()
);

Note that the **FunctionImplementationClass **must derive from the **ParserFunction** class.

Take a look at the CSCS repo for details (links in the sidebar).

To add a new CSCS function, follow the following pattern:

  1. Implement the functionality in C# by creating a new class, deriving from the ParserFunction class and overriding its Evaluate() method.
  2. Register the newly created class with the interpreter like this: interpreter.RegisterFunction( FuncName, new FuncImplementationClass());

And here is an example of an HTML template:

<!DOCTYPE html>
<html lang="en">
  <body>
    companyName: {{companyName}}
    <br>
    companyAddress: {{companyAddress}}
  </body>
</html>

Here you can see that the placeholders are defined with the double curly braces.

You can also fill placeholders one at a time with a FillTemplatePlaceholder() CSCS function. Here is an example:

FillTemplatePlaceholder(htmlHndl, "companyName",
    "My Company");

FillTemplatePlaceholder(htmlHndl, "companyAddress",
    "123 Main St, Anytown, USA");

The FillTemplatePlaceholder() CSCS function is implemented similarly and you're encouraged to take a look at its implementation at https://github.com/AurasoftEnzo/cscs_web/blob/main/CSCSWebFunctions.cs.

Take into the account that CSCS Web is a scripting language so the fewer lines in the script there are (i.e., the more you can do with the templates), the more performant it will be.

Rendering Conditions

There is also a feature to remove or retain code blocks from the template. These code blocks should be surrounded by {{IF_conditionName}}, like in the following example:

<!DOCTYPE html>
<html lang="en">
  <body>
    {{IF_displayBlock}}

    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>

    {{IF_displayBlock}}
  </body>
</html>

Then, by using the RenderCondition() function, this “Lorem Ipsum…” block can be removed or retained. Here is how you can do that:

RenderCondition(htmlHndl, "displayBlock", 1 < 2);

Here, the first argument is the handle of the template, the second one is the name of the condition from template, and the third one is the actual condition to be evaluated, which will decide whether to retain or remove the code block from the template.

Rendering HTML

Finally, the RenderHtml() function should be called to render the final HTML into a string variable that can then be returned to the client. You can do it as follows:

finalHtmlString = RenderHtml(htmlHndl);

Here, finalHtmlString holds the whole HTML content.

You can look at the C# implementation of this function at https://github.com/AurasoftEnzo/cscs_web/blob/main/CSCSWebFunctions.cs

Working with JSON

In CSCS Web, there are also a few functions for working with JSON. One function can serialize arrays and dictionaries into JSON strings, another one can deserialize such strings into arrays and dictionaries or even turn the two-dimensional SQL results into JSON strings.

(De)Serializing JSON

When working with CSCS Web, you can turn the CSCS arrays and dictionaries into JSON strings. Suppose your data looks like this:

array1 = {};
array1.Add({"key1": "value1", "key2": "value2"});
array1.Add({"key1": "value3", "key2": "value4"});
JSONString = SerializeJson(array1);

Then the JSONString will look as follows:

[
    {
        "key1": "value1",
        "key2": "value2"
    },
    {
        "key1": "value3",
        "key2": "value4"
    }
]

Another way around, if you have a JSONString like this:

JSONString = '[{"key1": "value1", "key2": "value2"}, {"key1": "value3", "key2": "value4"}]';

You can deserialize it into an array or dictionary like this:

array2 = DeserializeJson(JSONString);
print(array2[1]["key2"]); // prints value4

It probably goes without saying that the CSCS array indices start at 0.

Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought, proper consideration.

– Stan Kelly-Bootle

Sql2Json

In CSCS, you can also use SQL connectivity. You probably will often need the SQL results passed back to the client. In the example that follows in the last section, we'll show you some SQL setup, connectivity, and usage.

In all SQL queries, CSCS always returns a two-dimensional array, which has a simple structure: the first row always contains the table column names, and the rest of the rows contain the query data rows. This two-dimensional array will be converted to a JSON string with the use of the Sql2Json() function. This way, you can very easily return data from a database to the client browser. Here is an example from the sample application below which does that:

sqlString = "SELECT * FROM employees";
sqlResult = sqlQuery(sqlString);
jsonString = Sql2Json(sqlResult);

After executing the above statements, the contents of the jsonString will be the following:

[
    {
        "id": 1,
        "name": "John",
        "surname": "Doe",
        "age": 30,
        "address": "123 Main St",
        "city": "Anytown",
        "email": "john.doe@example.com"
    },
    {
        "id": 2,
        "name": "Jane",
        "surname": "Smith",
        "age": 25,
        "address": "456 Elm St",
        "city": "Othertown",
        "email": "jane.smith@example.com"
    },
    …
]

SSR and CSR Paradigm

With the CSCS Web, you can accomplish both the SSR (server-side rendering) and the CSR (client-side rendering). Usually, it isn't enough to prepare just a pure HTML and CSS to have interactive application, so we have two options.

The first one is to enrich the HTML with the hypermedia-driven code. The second one is to use more JavaScript on the client-side. The case that you will use depends on the type of application.

The SSR offers simplicity of coding, especially when there's a lot of interaction with the database. In case you want the SSR application and also want to avoid a lot of JavaScript code, you can use the techniques offered by hypermedia library like HTMX.

For demonstrating the SSR, we'll use HTMX, a lightweight JavaScript library that supercharges the HTML by letting you use the modern web features like AJAX, CSS transitions, WebSockets, and Server-Sent Events directly in your markup using custom attributes.

The front-end will execute the requests from the API and, after it responds with HTMX, it will modify the DOM with received data from the server.

Template + HTMX (SSR)

We have an HTML template file on the server that will be sent to the client. This HTML file looks as follows:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/htmx.org@1.9.10"></script>
  </head>
  <body>
    <div hx-get="/getPartialHtml" hx-swap="outerHTML" hx-trigger="load"></div>
  </body>
</html>

We can return this HTML in a way described in the Load/Render template section. Once the client gets this HTML and the page loads, the HTMX executes a request to /getPartialHtml, which is described as follows:

CreateEndpoint("GET", "/getPartialHtml", 
               "getPartialHtml");

function getPartialHtml() {
    responseHeaders = {"Content-Type": "text/html"};
    responseBody = "<h1>Hello World!</h1>";
    return Response(responseHeaders, 
                    responseBody, 200);
}

When the response reaches the client, the HTMX will swap outer HTML of the div element with the received HTML code. We will use this technique in the example section below.

Template + JavaScript (CSR)

With the use of JavaScript, we can make the page client-side rendered. The template file is the following one:

<!DOCTYPE html>
<html lang="en">
  <body>
    <script src="/csr.js"></script>
  </body>
</html>

The corresponding JavaScript file is the following:

document.body.innerHTML = "<h1>Hello world!</h1>";

This makes the JavaScript change the inner HTML of the body when the script is started. It will all be clearer in the sample application that we discuss next.

Sample Application (Employees List)

The sample code for this application can be found in the CSCS Web GitHub repository (see links in the sidebar). This sample entry point is the following: https://github.com/AurasoftEnzo/cscs_web/blob/main/wwwroot/scripts/article2/all.cscs

To be able to start the sample project, you will probably have to change the CSCSConfig section settings in the appsettings.json file. The default version is at https://github.com/AurasoftEnzo/cscs_web/blob/main/appsettings.json

And here is an example of the version to be used if your working environment is macOS:

"CSCSConfig": {
  "SQLConnectionString":
 "Data Source=localhost,1433;
  Database=T__DATAX_Y4__BY4;User   
  Id=sa;password=Aura2025;
  TrustServerCertificate=True;",
  "ScriptsDirectory":
  "/Users/vass/GitHub/CSCSweb/wwwroot/scripts/",
  "TemplatesDirectory":
  "/Users/vass/GitHub/CSCSweb/wwwroot/templat/",
  "StaticFilesDirectory":
   "/Users/vass/GitHub/CSCSweb/wwwroot/Static/",
  "StartScript": "article2/all.cscs",
}

The first thing for this sample to work is to set up SQL Server. You can also do it on a Mac—the easiest is to set it up running inside of a Docker container. Here's one of the possibilities for setting it up: https://devblogs.microsoft.com/azure-sql/development-with-sql-in-containers-on-macos/

Next, let's look at a few snippets.

The body of the HTML template without CSS and JavaScript code is the following:

<div class="mb-4 z-50 flex justify-between
 items-center bg-base-100 p-3 rounded-lg
 shadow-lg fixed-header">
 <div class="flex items-center gap-4">
  <h1 class="text-xl font-bold">Employee list</h1>
  <button class='btn btn-accent btn-sm'
   hx-get='/employees/new' 
   hx-target='.datagrid-container-master'
   hx-swap='outerHTML'>
        Add New Employee
  </button>
 </div>
</div>
<div class="master-detail-layout">
  <div class="master-table">
   <div class="datagrid-container-master"
    hx-get="/employees" hx-trigger="load"
    hx-swap="innerHTML"></div>
  </div>
</div>

The CSCS code for the endpoint that returns a list of the employees is shown in Listing 2.

Listing 2: Sample Code of the getEmployees() CSCS function

CreateEndpoint("GET", "/employees", "getEmployees");
function getEmployees(request) {
  // Pagination
  employees_page = 1;
  if (Contains(request["QueryParams"], "page")) {
    employees_page = int(request["QueryParams"]["page"]);
  }
  if (employees_page < 1) {
    getEmployees_responseHeaders = {"Content-Type": "text/html"};
    return Response(getEmployees_responseHeaders,
      "Error: 'page' must be positive integer", 200);
  }
    
  // Sorting
  employees_sort = "id";
  if (Contains(request["QueryParams"], "sort")) {
    employees_sort = request["QueryParams"]["sort"];
  }
  employees_order = "asc";
  if (Contains(request["QueryParams"], "order")) {
    employees_order = request["QueryParams"]["order"];
  }
    
  // Fixed page size
  employees_pageSize = 10;
  employees_skip = (employees_page - 1) * employees_pageSize;
        
  //alert after html
  employees_alert = "";
  if (Contains(request["QueryParams"], "alertText")) {
    employees_alert = request["QueryParams"]["alertText"];
  }
  employees_query = "SELECT " +
     "id, name, surname, age, address, city, email " +
     "FROM employees " +
     "ORDER BY " + employees_sort + " " + employees_order+" "+
     "OFFSET @skip ROWS FETCH NEXT @pageSize ROWS ONLY";
  employees_sqlParams = {};
  employees_sqlParams.Add({"@skip", employees_skip});
  employees_sqlParams.Add({"@pageSize", employees_pageSize});
  employees_records = sqlQuery(employees_query, 
                      employees_sqlParams);
  employees_countQuery = "SELECT COUNT(*) FROM employees";
  employees_countResult = sqlQuery(employees_countQuery);
  employees_totalRecords = employees_countResult[1][0];
  employees_totalPages = Math.Ceil(employees_totalRecords / 
                         employees_pageSize);
  // Build HTML
  employees_html = "<div class='datagrid-container-master'>";
  employees_html += "<table class='datagrid-table'>";
  employees_html += "<thead>";
  employees_html += "<tr>";
  employees_newOrder = "asc";
  if (employees_sort == "id" && employees_order == "asc") {
    employees_newOrder = "desc";
  }
    
  employees_html += "<th><a class='link' hx-get= 
   '/employees?page=1&sort=id&order=" + employees_newOrder +
   "' hx-target='.datagrid-container-master' 
   hx-swap='outerHTML'>Id</a></th>";
    
  for(employees_i = 1; employees_i < Size(employees_columns); 
      employees_i++) {
    employees_column = employees_columns[employees_i];
        
    employees_newOrder = "asc";
    if (employees_sort == employees_column && 
        employees_order == "asc") {
      employees_newOrder = "desc";
    }
    employees_html += "<th><a class='link' 
      hx-get='/employees?page=1&sort=" + employees_column +
      "&order=" + employees_newOrder + 
      "' hx-target='.datagrid-container-master' 
       hx-swap='outerHTML'>" + employees_column +
       "</a></th>";        
  }
    
  employees_column = "Actions";
  employees_html += "<th>" + employees_column + "</th>";
  employees_html += "</tr>";
  employees_html += "</thead>";
  employees_html += "<tbody>";
  if(employees_records != null && Size(employees_records) > 1) {
    for(employees_i = 1; employees_i < Size(employees_records);
        employees_i++) {
      employees_row = employees_records[employees_i];
      if (employees_i % 2 == 0) {
        employees_html += "<tr data-id='" + employees_row[0] +
      "' class='bg-base-200 hover:bg-base-300 cursor-pointer' >";
      } else {
        employees_html += "<tr data-id='" + employees_row[0] +
        "' class='hover:bg-base-300 cursor-pointer' >";
      }
            
      employees_html += "<td data-field='id'>" + employees_row[0]
        + "</td>";
      employees_html += "<td data-field='name' 
       class='text-center'>" + employees_row[1] + "</td>";
      employees_html += "<td data-field='surname' 
        class='text-center'>" + employees_row[2] + "</td>";
      employees_html += "<td data-field='age' 
        class='text-left'>" + employees_row[3] + "</td>";
      employees_html += "<td data-field='address' 
        class='text-center'>" + employees_row[4] + "</td>";
      employees_html += "<td data-field='city' 
        class='text-center'>" + employees_row[5] + "</td>";
      employees_html += "<td data-field='email' 
        class='text-center'>" + employees_row[6] + "</td>";
      employees_html += "<td class='flex gap-1'>";
      employees_html += "<button class='btn btn-info btn-xs
        action-button' hx-get='/employees/" + employees_row[0] +
        "/edit' hx-target='.datagrid-container-master'
         hx-swap='outerHTML'>Edit</button>";
      employees_html += "<button class='btn btn-error btn-xs 
        action-button' onclick='confirmDeleteEmployee(" + 
        employees_row[0] + ")'>Delete</button>";
      employees_html += "</td>";
      employees_html += "</tr>";
    }
  }
  employees_html += "</tbody>";
  employees_html += "</table>";
  // Pagination
  employees_html += "<div class='pagination'>";
  if(employees_page > 1) {
    employees_html += "<a class='btn btn-sm' hx-
     get='/employees?page=" + (employees_page - 1) + "&sort=" +
     employees_sort + "&order=" + employees_order + 
       "' hx-target='.datagrid-container-master' hx-
       swap='outerHTML'>Previous</a>";
  }
  employees_html += "<span class='page-info'>Page " +
    employees_page + " of " + employees_totalPages + "</span>";
  if(employees_page < employees_totalPages) {
     employees_html += "<a class='btn btn-sm' hx-
     get='/employees?page=" + (employees_page + 1) + "&sort=" + 
     employees_sort + "&order=" + employees_order + 
       "' hx-target='.datagrid-container-master'
       hx-swap='outerHTML'>Next</a>";
  }
  employees_html += "</div>";  
  employees_html += "</div>";
  employees_html += employees_alert; 
    
  getEmployees_responseHeaders_2 = {"Content-Type":"text/html"};
  return Response(getEmployees_responseHeaders_2,
                  employees_html, 200);
}

Once you start the application, there are a few different options to test CSCS with Web applications. See Figure 2 for details, which show the contents of the https://github.com/AurasoftEnzo/cscs_web/blob/main/wwwroot/templates/testingPage.html page.

Figure 2: Different options to test CSCS WEB
Figure 2: Different options to test CSCS WEB

The main example, and the subject of this article, is the last one, the Employee List. Once you click on it, you will get a view as in Figure 3.

Figure 3: The browser view of the list of employees
Figure 3: The browser view of the list of employees

If you click on the "Add New Employee" button, you'll get a new page, as in Figure 4. You will also get a similar view if you click on the Edit button for any user.

Figure 4: Adding a new user
Figure 4: Adding a new user

Once you fill out all the details and click on the Save button, you'll get the contents shown in Figure 5. You'll also get a similar view if you click on the Edit button for any user.

Figure 5: A pop-up message when a new user was added
Figure 5: A pop-up message when a new user was added

Pretty cool, right? But the coolest part of this is that everything has been created using the CSCS Web scripting language. Look at Listing 2 on how to get a list of all employees in CSCS and at Listing 3 to check out how adding a new user has been implemented in CSCS.

Listing 3: Sample Code of the createEmployee() CSCS function

CreateEndpoint("POST", "/employees", "createEmployee");

function createEmployee(args) {
    create_fields = ["Name", "Surname", "Age", "Address", "City", "Email"];
    create_values = {};

    for (create_i = 0; create_i < Size(create_fields); create_i++) {
        create_field = create_fields[create_i];
        create_values[create_i] = GetValueFromForm(args["Body"], create_field);
    }

    create_values2 = {};
    create_values2["Name"] = GetValueFromForm(args["Body"], "Name");
    create_values2["Surname"] = GetValueFromForm(args["Body"], "Surname");
    create_values2["Age"] = GetValueFromForm(args["Body"], "Age");
    create_values2["Address"] = GetValueFromForm(args["Body"], "Address");
    create_values2["City"] = GetValueFromForm(args["Body"], "City");
    create_values2["Email"] = GetValueFromForm(args["Body"], "Email");

    if (!IsInt(create_values2["age"])) {
        create_headers = {"Content-Type": "text/html"};
        create_employees_alert = "<script>Swal.fire({title: 'ERROR!', text: 'Age must be an integer.', icon: 'error', confirmButtonText: 'Close'});</script>";
        create_employees_validationError_html = getNewEmployeeFormWithValues(create_values2, create_employees_alert);

        return Response(create_headers, create_employees_validationError_html, 200);
    }

    create_query = "INSERT INTO employees (";
    create_first = true;

    for (create_i = 0; create_i < Size(create_fields); create_i++) {
        if (!create_first) {
            create_query += ", ";
        }
        create_query += create_fields[create_i];
        create_first = false;
    }

    create_query += ") VALUES (";
    create_first = true;

    for (create_i = 0; create_i < Size(create_fields); create_i++) {
        if (!create_first) {
            create_query += ", ";
        }
        create_query += "@" + create_fields[create_i];
        create_first = false;
    }

    create_query += ")";
    create_sqlParams = {};

    for (create_i = 0; create_i < Size(create_fields); create_i++) {
        create_sqlParams.Add({"@" + create_fields[create_i], create_values[create_i]});
    }

    sqlNonQuery(create_query, create_sqlParams);

    create_args2 = {};
    create_args2["QueryParams"] = {"page": 1};

    create_employees_alert = "<script>Swal.fire({title: 'Successfully added!', text: 'Added employee " + create_values2["name"] + " " + create_values2["surname"] + ".', icon: 'success', confirmButtonText: 'Close'});</script>";

    create_args2["QueryParams"] = {"alertText": create_employees_alert};

    return getEmployees(create_args2);
}

Wrapping Up

In this article, you saw how to use the CSCS scripting language in ASP.NET Core. The main advantage is that you can add any functionality to your project without the need for recompilation, because all the CSCS scripts are loaded at runtime. Of course, it could negatively affect your performance, so you should make sure that there are no complex mathematical computations done in CSCS, but rather mostly some GUI-related code.

ASP.NET Core with CSCS? That's a Swiss Army knife—if the knife occasionally compiles itself.

– Anonymous

In this article, we've been talking mostly about just one example, a list of employees. There are many more interesting examples, see Figure 2 for the full list.

All of these examples are implemented in the https://github.com/AurasoftEnzo/cscs_web/blob/main/wwwroot/scripts/article2/all.cscs file.

We're looking forward to your feedback: specifically, your experience in applications where you're using CSCS Web, and what other features in CSCS you would like to see.