Simple Knockout sample

Knockout is a JavaScript library that implements MVVM pattern. It has a very small footprint and can be easily integrated in ASP.NET Core projects. It helps to solve complex logical UI challenges.

MVVM architecture is based on Martin Fowler’s Presentation Model. MVVM consists of three main components: Model, View, and ViewModel.

ViewModel references the Model where the View is completely unaware of the model. This helps front-end developer to stay focused on front-end development..

Here is a simple example using ASP.NET Core 6 MVC template.

<div>
    The name is <span data-bind="text: personName"></span> and the age is <span data-bind="text: personAge"></span> 
</div>
<div>
    <span>PersonName: </span><input data-bind="value: personName" />
    <span>PersonAge: </span><input data-bind="value: personAge" />
</div>

@section scripts {
    <script type="text/javascript">

        var myViewModel = {
            personName: ko.observable('Shahzad'),
            personAge: ko.observable(55)
        };

        //jQuery ready function
        $(function () {

            ko.applyBindings(myViewModel);

        });

    </script>
}

Some key functions from Knockout;

arrayFilter() -> Where()
arrayFirst() -> First()
arrayForEach() -> (no direct equivalent)
arrayGetDistictValues() -> Distinct()
arrayIndexOf() -> IndexOf()
arrayMap() -> Select()
arrayPushAll() -> (no direct equivalent)
arrayRemoveItem() -> (no direct equivalent)
compareArrays() -> (no direct equivalent)

For more info, refer here;

Knockout Home Page

ASP.NET MVC with Knockout.js

Utility function in Knockout

Filter, Search and Sort

jQuery UI and Knockout

https://learn.microsoft.com/en-us/archive/msdn-magazine/2012/march/client-insight-knockout-s-built-in-bindings-for-html-and-javascript

https://visualstudiomagazine.com/articles/2013/08/12/javascript-data-binding-with-knockout.aspx

https://blog.craigtp.co.uk/Post/2017/01/10/KnockoutJS_binding_on_SELECT_elements_in_a_ForEach_loop

Change event on select with knockout binding, how can I know if it is a real change?

Refresh Knockout bindings when viewModel changes

Interesting idea for dynamic queries

A real world example

Use LibMan with ASP.NET Core in Visual Studio

To install/uninstall client side libraries (Bootstrap, jQuery, Popper, Knockout etc), Visual studio has a built-in support known as LibMan in ASP.NET Core projects, including:

  • Support for configuring and running LibMan restore operations on build.
  • Menu items for triggering LibMan restore and clean operations.
  • Search dialog for finding libraries and adding the files to a project.
  • Editing support for libman.json—the LibMan manifest file.

This file can be added in existing project by going through Project->Manage Client Side Libraries. A file Libman.json will be added to the root folder automatically.

LibMan file can be manually edited. This file is located in project root folder, Libman.json.

Cleaning & Restoring Client-Side Libraries can be done quite easily by right clicking on the libman.json file, and it will show up these 2 options. Simply select the needed option to perform the task.

Here is the screenshot:

Update or Uninstall Client Side Libraries

The steps to update or uninstall a Client-Side Library are similar. You need to follow the below steps.

  • 1. Open the libman.json file and then click on the client-side library which you want to uninstall or update.
  • 2. You will see a light yellow bulb icon appearing on the left side. Click on this icon to get the options for – whether to update or uninstall that specific client-side library. Here is the screenshot.

You can also uninstall and update the a client-side library by ‘removing or changing it’s version nunber’ given on it’s entry in the libman.json file, and then saving the file.

For popovers to work, either install Popover.js client library. If you prefer, try bootstrap bundle and you will get popover in it;

<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
Prerequisites

Reference

Use LibMan in Visual Studio

How to install Bootstrap and other libraries

Bootstrap 4 Get started

Bootstrap 4 has solved .NET CORE 6 popover problem. It does support jQuery version @3.56.1 and jQuery UI version @1.13.2. Bootstrap 5 doesn’t support jQuery. It supports vanilla JavaScript.

Visual Studio Task List Feature

Visual Studio has a handy feature, Task List. Click on View->Task List.

Task list has tokens that are used to track code comments. This also acts as a shortcut to navigate to relevant code section. This list comes with 3 default tokens; TODO, HACK and !UnresolvedMergeConflict.

We can add custom tokens, if we want. Here is how;

Open Tools -> options -> Environment, you will see Task List.

Add a new custom token;

To make this feature working, All you need to do is to add comments in your code;

    /*
        HACK: I don't need serviceProviderId parameter here. The only reason I am adding this becuase this comes
        from a link in razor page list and I need to keep serviceProviderId and projectId in session. 
        Session starts here for now.
     */
    [HttpGet]
    [Route("ProjectTask/ProjectTaskList/{projectId:Guid}")]
    public async Task<IActionResult> ProjectTaskList(Guid projectId)
    {
        var result = await _httpFactoryService.GetProjectActionAsync(projectId);
        return View(result);
    }

Reference

Using the Task List

JSON Circular Reference Loop Error running Raw SQL in Web API using EF Core

I had a complex stored procedure that I needed to run using EF core in a Web API project. I cannot use LINQ to represent this query. I have followed all best practices outlined here;

Everything worked great except raw sql endpoint. It started giving me “Circular Reference loop error”. To keep thing simple, I am returning DTO from endpoint not the entity.

Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'task' with type 'System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[System.Collections.Generic.IEnumerable`1[FM.Service.Shared.DTO.TranMasterViewDto],FM.Service.LedgerTranViewService+<GetTransactionForProjectActionAsync>d__4]'. Path 'stateMachine.<>t__builder'
......

Here is my configuration before and after the fix;

Newtonsoft Configuration in program.cs file before the fix;
}).AddNewtonsoftJson()
Newtonsoft Configuration in program.cs file after the fix;
}).AddNewtonsoftJson(
    options => 
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore)

The error is gone and I am getting this results.

{
    "stateMachine": {
        "<>1__state": 0,
        "<>t__builder": {},
        "projectActionID": "82ee5154-971a-ed11-90e6-00155d717606",
        "trackChanges": false,
        "<>4__this": {}
    },
    "context": {},
    "moveNextAction": {
        "method": {
            "name": "MoveNext",

Wait…This is not what I expected. Why I am getting Tasks result where I need to see my Dto results.

It turns out that I am not returning results from my Async method but the task;

public async Task<IActionResult> GetTransactionForProjectActionAsync(Guid projectActionId)
{
   return Ok(_service.LedgerTranViewService.GetTransactionForProjectActionAsync(projectActionId, trackChanges: false));
        }

Fixing the return type has solved all of my problems;

return Ok(_service.LedgerTranViewService.GetTransactionForProjectActionAsync(projectActionId, trackChanges: false).Result);
        }
[
    {
        "id": 20221,
        "projectActionID": "82ee5154-971a-ed11-90e6-00155d717606",        
        "tranMasterDetail": [
            {
                "tranMasterId": "9358b0c5-6d30-ed11-90e8-00155d717606",
                "ledgerID": "63302216-0946-ea11-ac82-f81654967f7a",
                "tranDescription": "Rent paid",
                "tranDate": "2022-01-03T00:00:00",
                "tranFlag": 0,
                "tranAmount": 1300.00
            }
        ]
    }
]

Remove Newtonsoft ReferenceLoopHandler from program.cs file.

}).AddNewtonsoftJson()

Hope this will help someone.

Reference

https://stackoverflow.com/questions/62985907/avoid-or-control-circular-references-in-entity-framework-core

Resolve the issue of request matched multiple endpoints in .NET Core Web API

If there are two endpoint with same route, .NET Core Web API will throw request matched multiple endpoints error. Here is an example;

// api/menus/{menuId}/menuitems
[HttpGet("{menuId}/menuitems")]
public IActionResult GetAllMenuItemsByMenuId(int menuId)
{            
    ....
}

// api/menus/{menuId}/menuitems?userId={userId}
[HttpGet("{menuId}/menuitems")]
public IActionResult GetMenuItemsByMenuAndUser(int menuId, int userId)
{
    ...
}

This is impossible because the actions are dynamically activated. The request data (such as a query string) cannot be bound until the framework knows the action signature. It can’t know the action signature until it follows the route. Therefore, we can’t make routing dependent on things the framework doesn’t even know yet.

Long and short, we need to differentiate the routes in some way: either some other static path or making the userId a route param. However, we don’t actually need separate actions here. All action params are optional by default. Therefore, we can just have:

[HttpGet("{menuId}/menuitems")]
public IActionResult GetMenuItemsByMenu(int menuId, int userId)

And then we can branch on whether userId == 0 (the default). That should be fine here, because there will never be a user with an id of 0, but we may also consider making the param nullable and then branching on userId.HasValue instead, which is a bit more explicit.

We can also continue to keep the logic separate, if we prefer, by utilizing private methods. For example:

[HttpGet("{menuId}/menuitems")]
public IActionResult GetMenuItems(int menuId, int userId) =>
    userId == 0 ? GetMenuItemsByMenuId(menuId) : GetMenuItemsByUserId(menuId, userId);

private IActionResult GetMenuItemsByMenuId(int menuId)
{
    ...
}

private IActionResult GetMenuItemsByUserId(int menuId, int userId)
{
    ...
}

Have fun.

Read more here.