Only this pageAll pages
Powered by GitBook
1 of 79

Vidyano Documentation

Loading...

Vidyano v6 (.NET 8.0 based)

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Release Notes

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Verbose Logs

Vidyano has the ability to enable verbose logging. This will log all requests including details about the user (user name, ip address, user agent, client version, …), the incoming data and outgoing data. This can help for troubleshooting request or for auditing purposes.

Custom Vars Example

As a developer you can also set 5 custom variables per request to make it easier to search for specific request or easily see something without having to open the incoming or outgoing data.

// Check the user's language at the moment of the request
Manager.Current.SetLogCustomVar(1, Manager.Current.UserLanguage);
// Check the user's culture info at the moment of the request
Manager.Current.SetLogCustomVar(2, Manager.Current.UserCulture);
// Check the weekday at the moment of the request
Manager.Current.SetLogCustomVar(5, Manager.Current.Today.DayOfWeek.ToString());

RequestId Example

On the client side we can also set a request id that can be used as correlation id to follow different request in a single brower session or on a specific computer. You can use the AppServiceHook.createData method to set a requestId on the data.

Previous

5.44.0+6e65421

Saturday, November 30th, 2019

Features

  • Added option to explicitly order distinct values

  • Added detection of .NET 4.8 in diagnostics page

  • Added JwtPayload class

  • Added advanced hook to determine friendly filter

  • Added PersistentObject.KeepValueChanged

  • Added advanced hook for customizing Advanced search

  • Added advanced hook when a user opens its User settings page

Changes

  • Tweaked exception when FromServiceString fails

  • Show extra information for repeating jobs (last execution, execution count)

  • Don't remove request data when disposing UserSession

Fixes

  • Don't throw when user agent is not found

  • Fixed issue when creating custom actions from Management

  • Fixed NullReferenceException when cdncache.tmp is empty

  • Pass targetContext correctly when filtering for load entity

  • Fixed issue with Hasherid not working for EF POCO entity

  • Fixed issue when ContentProperty points to DbQuery<T>

  • Fixed issue with empty ProgramUnit staying

5.39.1+f04e696

Tuesday, August 28th, 2018

Features

  • Added ProjectionStringLengthAttribute to reduce the length of string columns in the projected type (i.e. to only get the first 101 characters of the Message column from the Vidyano.Logs)

  • Updated repository version to V60 to introduce optimized cache updates, load-balanced deployment should get a big performance increase

  • Added custom user schema support for SCIM

Changes

  • Don’t show stack traces to users that aren’t in the Administrators group (a Log-id is shown instead)

  • Exposed AuthenticationService.HandleUnknownAuthenticate to handle custom /Authenticate/X requests (only SAML2 is handled by default)

  • Exposed the IScimHandler interface for SCIM customizations

  • Changed required rights for a custom AddReference query from Edit to Query

  • Changed logic for creating new context in PersistentObjectActions/AsyncCustomAction

  • Also show non-database SqlErrors to end-users (e.g. timeout)

Fixes

  • Fixed issue when corrupt model.json is fixed on read-only deployment

  • Fixed threading issue which caused Options for a SelectInPlace attribute to be empty

  • Fixed issue that caused fallback to default sort options not working if attribute no longer exists

  • Fixed issue that the current groups weren’t returned correctly for the current user

5.37.1+3fd7ebea

Wednesday, July 11th, 2018

Features

  • Added explicit UnabletToLoadEntityException (requires the System.ServiceModel namespace, latest Visual Studio extension will add it for new projects)

  • New Feature - Improved initial cold-start verbose model loading by reading them as parallel (can be disabled by setting the Vidyano.ModelLoaderMaxDegreeOfParallelism appSetting to 1)

  • New Feature - Added in-preview UserNotifications (repository version has been increased to 59)

  • New Feature - Added extra security layer for entities using an int/long PK by using hashids using per entity salt

  • New Feature - Added implementation for a custom IStorageProvider named FolderStorageProvider

  • New Feature - Added support for Azure Application Insights by adding extra information to the operation names (can be disabled using the Vidyano.DisableApplicationInsights appSetting)

  • New Feature - Added support for providing extra search parameter when getting column distincts

  • New Feature - Added helper method on Manager.LogEntry to hide sensitive information from urls (i.e. token passed to api)

  • New Feature - Made repository schema forward compatible allowing versions starting from this version to connect to repository that are using a newer version

  • New Feature - Added support for using “master” as Web2 version which will use the latest commit as web2 client (for testing purposes, not supported for production environments)

  • New Feature - Added throttling when too many UnableToLoadEntityExceptions are thrown by a single user

Changes

  • Expose Manager.UserCultureInfo

  • Expose PersistentObjectAttribute.DisplaySum getter

  • Include original stacktrace when logging EF exceptions

  • Always show DisableTwoFactor action in management for users with Two-factor enabled (can be used to reset two-factor token)

  • Expose Query.GroupedBy

  • Expose PersistentObjectActions.CheckDetailRules in reference implementation

  • Return false when ValidateAuthToken is called with empty userName (instead of exception)

  • Added ServiceWorker flag as possible ClientEnvironment

  • Expose hook for custom paths during scim patch user operation

  • Show New feedback as dialog

  • Try to use browser acceptlanguage for Sign-in screen (during GetClientData)

  • Support AccountSAS for AzureStorageConnecto

  • Export ExecuteAction’s parameters on ChartArgs

  • Renamed AzureAD authentication provider to SAML2

  • Skip “EntityFramework: “ prefix for generic log-id message

  • Skip total/count/chart for Report/Excel/Word

  • Extended scim support to provide more correct information ($ref/location)

  • Updated Azure Blob Service REST api version to 2017-11-09

Fixes

  • Fixed AmbigiousException on viSearch

  • Fixed issue with web2 cdn proxy on Owin hosting

  • Fixed issue when multiple instance tried to update the same repository (load-balancer first run for new version)

  • Fixed issue when invalid Web2 version was used in settings by validating version before saving

  • Fixed issue when getting SAS for a blobName with special characters

  • Fixed issue with invalid custom query methods being suggested

  • Fixed entropy issue when using Hashids

5.34.3+d278982

Monday, March 12th, 2018

Features

  • Added Manager.LogEntry to get client version, user agent and requestId

  • Added CustomApiController.OnPreApi hook and ApiArgs.CustomMethod property

  • Added Advanced.HandlePasswordOrTwoFactorChanged

  • Added support for Swagger definitions in arguments/responses

  • Support UserName/AuthToken in Authorization header for Vidyano default web calls

  • Added support for $select on reports to only return a limited set of columns

  • Added parameter user on Manager.Log to log for that specified user instead of current user

Changes

  • Validate actual password change for ResetPasswordNextLogin

  • Sort swagger tags/path alphabetically

  • Also handle signin/signout as reserved words

  • Increased pwnedpasswords api timeout to 10sec

  • Log AzureStorageConnector auto-resume logs as Warning

  • Log failed sign in attempt for existing users using user in log instead of username in message

  • Minimize timing attack against unknown users

Fixes

  • Fixed issue when saving existing filter

  • Fixed issue when custom Api method resumes null instead of HttpResponseMessage

  • Fixed issue with nested repeated sections in Word.Generate

  • Fixed issue with exception handling in AzureStorageConnector

5.31.2+c8aabb2

Monday, February 5th, 2018

Features

  • Added column to show which users have Two-factor enabled and “Disable two-factor” action for admins to unlink Two-factor on a user

  • Added Group.TwoFactorRequired to indicate that all users in that group require Two-factor authentication

  • Added “Remove password” action on users with a password so that they can only login using external authentication providers (or impersonate)

  • Added optional Setting.Description to describe the usage of the custom setting

  • Added Word.ConvertToPdf helper (requires key from 2sky)

  • Added ScimCredentials.AllowedOperations to disable specific scim operations if needed (i.e. allow a token to only read user/group data but not modify it)

  • Added new /diagnostics/types helper page to determine what .NET types/methods are used for custom queries/custom actions/POActions

  • Switched to an ip based throttling for checking password during authentication (1 try per second) (app setting Vidyano.MaxLoginTries, e.g. 1/5 for 1 try every 5 seconds or 10/1 for 10 tries every second, use 0/0 to disable throttling)

  • Added ApiArgs.IsValidSignature method to check for same AuthorizationSignature based authentication as reports

  • Added notification when creating new User to inform to which group it will be added

  • Added string.GetSHA512() extension method

Changes

  • Expose WordProcessingDocument on WordArgs

  • Setting PO.IsReadOnly in Management part will now also check this for New/BulkEdit/Delete action calls

  • Removed override on ServicePointManager.SecurityProtocol in favor of default .NET configuration

  • Changed User settings screen to require current two-factor code when disabling two-factor

  • Changed Default QueryLayoutMode setting to MasterDetail for new projects

  • Reverted EntityFramework NuGet dependency back to 6.1.3

Fixes

  • Fixed issue when removing an attribute that would also remove unrelated columns with the same name

  • Fixed issue with new sync option for hidden attributes and detail queries

  • Ensure Vidyano is initialized when it receives authentication requests (OAuth/ACS/…)

5.32.1+0c41761

Saturday, February 10th, 2018

Features

  • Added Manager.OriginalUser to determine which user impersonated the current user

  • Give Feedback/Profiler rights if OriginalUser is member of Administrators

  • Log Feedback as OriginalUser

Changes

  • Don’t show DisableTwoFactor for users that require Two-factor

  • Changed new Vidyano project template with more entropy for application salt and updated the default generated files

Fixes

  • Fixed issue when writing custom filter for Azure table query

5.30.0+530afaf

Sunday, December 28th, 2017

Features

  • Added Advanced.StorageProvider to handle reading/writing of verbose log content (example)

  • Added extra logging in Verbose logs (User agent, Client version, Request id and 5 custom variables) (example)

  • Added basic support for being a SCIM 2 service provider (example)

  • Added authentication support for Azure AD Single Sign-On using SAML (example)

  • Added sync option for hidden attributes and detail queries

Changes

  • Updated EntityFramework NuGet dependency to 6.2.0

  • Changed newline endings back to CRLF for PersistentObjectActionsReference.cs

Fixes

  • Fixed issue with AuthorizationSignature for reports not being checked correctly

  • Fixed issue with missing groups for users when enabling NoDatabase

  • Fixed issue when enabling or disabling verbose model

5.29.3+30608c3

Thursday, December 14th, 2017

Features

  • Updated reports to define minimum required access level, new signature based option using HMAC256 and logging/disabling of compromised report for maximum security (Reports help)

  • Added InstantSearchArgs.AddRepository method for easy instant search on repository

Changes

  • Don’t log outgoing content for GetReport/Api in verbose logs

Fixes

  • Fixed multi-threading issue when using IUser.UpdateLastLoginDate

5.28.2+bc49431

Thursday, November 30th, 2017

Features

  • Added Advanced ACS hook to passthrough a custom domain

  • Added Manager.StartTime property to get AppDomain start time (also available in diagnostics)

  • Added support for byte/sbyte/short/ushort/uint properties for Display on total

  • Added CustomApiController.GetWebsiteContent hook to customize html for a website index.html

Changes

  • Allow authenticated API calls and Instant Search using default user

  • Made PersistentObjectAttribute.GroupName setter public

  • Replaced Advanced.BlacklistedPasswords property with Advanced.IsPasswordBlacklisted method

  • Changed Report XSD generation to include more details about model (extra types, required, max length, …)

  • Allow User/NullableUser for string properties

  • Changed Report to also allow Accept header for content-type (application/json, text/xml or text/csv)

  • Prefer DbContext.SaveChanges directly for PersistChanges

  • Skip removing rights when deleting user on a read-only repository (user is still deleted but we keep orphaned rights)

Fixes

  • Fixed issue when throwing ValidationException using a single message

  • Fixed issue when using Builder to create/update messages

  • Fixed issue when changing custom settings not showing new value until application restart

  • Fixed issue when importing patch

5.27.0+6b9495e

Friday, October 27th, 2017

Features

  • Added Advanced.TryReadDataFile/WriteDataFile hook to customize loading json files from App_Data

  • Added QueryAction enum to define built-in actions on query that can be passed to Query.DisableActions

  • Added IExcelWriter.SetCell method that can also handle data type (numbers, dates, boolean, …)

  • Added IUser.IsEnabled/Enable()/Disable()

Changes

  • Changed serialization property order to make debugging of http requests easier

  • Added overload on CustomApiController.Json to also pass JsonSerializerSettings

  • Added Tag property on service objects to pass any extra data to the client

Fixes

  • Fixed issue with unique constraint violation not using attribute’s label

  • Fixed issue with PersistentObject.SaveDetails causing PersistentObject.PopulateAttributeValues to fail

  • Fixed issue with Query UserRight not having Filter action

5.23.0+9b8b99

Thursday, August 7th, 2017

Features

  • Added ability to define SwaggerAttributes using Advanced.HandleSwaggerMetadata

  • Added support for using continuation tokens on custom queries

Changes

  • Updated XML comments to explain which claims are used for UnknownUser

  • Removed obsolete code to use pages for querying instead of skip/top

Fixes

  • Made sure that filters are included correctly for queries that are added during OnLoad

  • Fixed issue with Security page in NoDatabase mode trying to use database

  • Fixed issue with not being able to create new feedback

5.19.0+0964f9

Wednesday, May 24th, 2017

Features

  • Added ability to request PO on backend using IsNew directly

  • Added Advanced.HandleUnobservedTaskException hook

  • Use TypeConverter when converting to and from a service string

  • Allow changing of IUser.Profile

Changes

  • Don’t block application start when cleanup of CacheUpdates/Logs fails

  • Respect developer CanSort=false options for existing user sort options

  • Allow Async suffix for custom api methods

  • Don’t show outdated model message when model hash is IGNORED

Fixes

  • Fixed issue with Word.Generate when passing IsNew PO trying to add queries

  • Fixed issue with AzureTableQuery in Release builds

  • Fixed issue with IUser.Profile not returning correct data

  • Fixed issue with unknown user id on report throwing NullReferenceException

  • Fixed issue when AutoQuery is enabled during OnConstruct(query, parent)

5.15.2+5ed89a

Thursday, April 13th, 2017

Features

  • Added Vidyano.Core.External.AzureStorageConnector helper class for common Azure Storage scenarios.

Changes

  • Removed Windows Azure NuGet packages in favor of custom implemented REST calls for Verbose Logs. Existing projects will keep the NuGet packages installed but can be removed if they aren’t used.

  • Removed fixed dependency on Jurassic library, Vidyano application can now remove this dependency unless they want to use the server side javascript functionality.

  • Don’t show custom exception message for foreign key SQL error 547

Fixes

  • Fixed initialization error for new projects

5.11.1+d7647c

Tuesday, March 14th, 2017

Features

  • Added ApiMethodAttribute.NeedsAuthentication to require Authorization header

  • Added ability to manage filters for users using code

  • Added Advanced.Poll method to add extra logic to /poll request

  • Added ability to override Vidyano Settings using AppSettings (example)

Changes

  • Don’t serve index html for static resources that are not found

  • Use all application languages for date operations (today, …)

  • Improved performance when saving model changes in Verbose mode

Fixes

  • Fixed issue with empty language value not being set when it has the same translated value as English

  • Fixed issue with some translations being incorrectly reused

5.14.1+ec0dbd

Monday, April 3rd, 2017

Features

  • Added reference source file for commonly used methods inside the PersistentObjectActions class. Installing the latest NuGet package will add an PersistentObjectActionsReference.cs file

  • Added extra methods on PersistentObjectActions base class to handle common scenarios without having to overwrite the complete methods (SaveNew/SaveExisting/OnDelete/…) (example)

Changes

  • Changed diagnostics page to also show web2 version information for websites

  • Ensure that repository always has all the system settings

Fixes

  • Fixed issue with correct exception not being shown when RepositorySynchronizer failed on project with NoDatabase enabled

5.9.0+68a51e

Friday, February 24th, 2017

Features

  • Added option to keep selection on query after refresh for custom action (requires Web2 1.10 or higher)

  • Added ApiMethodAttribute to control if manager is required or not

Fixes

  • Fixed issue with invalid csv being generated

5.7.1+554316

Wednesday, February 1st, 2017

Features

  • Added ExportToCsv action

  • Added Manager.GetUsersForGroup method

  • Use DocumentFormat.OpenXml NuGet 2.7.1

Changes

  • Updated CommonMark.NET NuGet dependency to 0.15.0

  • Updated WindowsAzure.Storage NuGet dependency to 8.0.1

  • Show better exception when sorting fails on custom IEnumerable query

  • Removed DB call when filtering on reference attribute

Fixes

  • Fixed issue when migrating repository to new semantic version

  • Fixed issue when using bulk-edit on PersistentObjectAttributes

  • Fixed issue when using bulk-edit on UserRights

5.8.0+aab7d8

Wednesday, February 8th, 2017

Features

  • Added hook Advanced.PostNewFeedback which is called after a new feedback has been logged

Changes

  • Removed QueryResultItem.Breadcrumb

Fixes

  • Fixed issue when saving Settings on SingleInstance

  • Fixed issue when using Manager.GetPersistentObject not passing correct ObjectId when changed

5.8.1+67bcab

Monday, February 13th, 2017

Changes

  • Changed cors handler to use * for allowed origin

Fixes

  • Fixed issue with exception in custom Web class constructor not being logged correctly

Installation (Legacy)

Installation and software requirements

For Vidyano v6 check Getting started, you no longer need the extension as everything is managed with NuGet packages

In order to get started you will need to have the following software installed:

  • Visual Studio 2017 Community or higher

  • SQL Server 2017 Express Edition or higher

You will also need to have the Vidyano Extension installed from the Visual Studio Gallery:

  • Vidyano Extension for Visual Studio

Actions

Labels

This information is still applicable to v6, for the code snippet, the Manager.Current.GetTranslatedMessage("Disable") can also be replaced with Messages.Disabled

For custom actions a developer can choose the label that should be used, but for the system actions like Save or Delete the labels have always been fixed for an application. The ActionLabels feature allows you to change the Label for the system actions using code.

Example

Changing the Delete label on a Persistent object that has soft-delete enabled.

public override void OnConstruct(Query query, PersistentObject parent)
{
	base.OnConstruct(query, parent);

	query.ActionLabels["Delete"] = Manager.Current.GetTranslatedMessage("Disable"); // Or "Revoke"/"Deactivate"/"Remove"
}

Another use case for changing the Save label is when a virtual persistent object is shown for a custom action.

public override void OnNew(PersistentObject obj, PersistentObject parent, Query query, Dictionary<string, string> parameters)
{
    base.OnNew(obj, parent, query, parameters);

    obj.ActionLabels["Save"] = obj.Label;
}

Security

Documentation

Persistent Objects

Persistent Objects in Vidyano are utilized to represent various data structures, including:

  • Tables: Directly mapping to database tables.

  • Collections: Representing groups of related items.

  • Entities: Defining individual records or objects.

  • Virtual Forms: Describing forms with a set of fields without a direct database mapping.

Key Features

  • Flexibility: Capable of representing both physical and virtual data structures.

  • Customization: Allows for the definition of fields, relationships, and behaviors tailored to specific needs.

Use Cases

  • Data Representation: Managing complex data structures within the application.

  • Form Design: Creating virtual forms to capture and process user input efficiently.

Persistent Objects are a fundamental component of Vidyano, providing a versatile mechanism for data management and interaction.


Queries

Queries in Vidyano are used to retrieve and display data in various ways. There are three primary types of queries:

Context Property Queries

These queries map directly to a property on the context, typically representing entire database tables. They are straightforward and provide a direct view of the underlying data.

Detail Queries

Detail queries are used to fetch data related to a specific entity. They display records based on their parent entity, allowing for a focused view of related data.

Custom Queries

Custom queries offer the most flexibility, allowing developers to implement complex business logic in code. These queries can filter data, retrieve information from external services, or provide customized views tailored to specific needs.

Each type of query serves distinct purposes and provides a powerful mechanism for data retrieval in Vidyano.

Examples

public partial class ProductActions
{
    public IQueryable<Product> ActiveProducts(CustomQueryArgs args)
    {
        return Context.Products.Where(p => p.IsActive);
    }
}

Persistent Object Attributes

Attributes in Vidyano represent individual fields within a Persistent Object. They can be:

  • Linked Attributes: Directly connected to a class property.

  • Virtual Attributes: Used in code but not mapped to a database field, ideal for scenarios like contact forms or additional processing options.

Attributes contain detailed metadata such as:

  • Name: The identifier for the attribute.

  • Data Type: Specifies the type of data (refer to the Data Types section for more details).

  • Position: Determines its placement in the interface.

  • Group/Tab: Indicates which group or tab the attribute belongs to.

  • Label: A translatable label for user-friendly display.

  • Business Rules: Associated validation rules.

Attributes also have customizable hints, such as displaying a dropdown as a select box or radio buttons. This flexibility allows developers to create intuitive and user-friendly interfaces.


Data Types

Vidyano supports a range of built-in data types to represent different kinds of information:

  • Primitive Types: Includes basic types like string, integer, datetime, and datetimeoffset.

  • Multiline String: Provides a text area for users to input large amounts of text.

  • Multivalue String: Allows users to enter multiple lines of text that can be reordered.

  • Combobox and Dropdowns: Enables selection from a predefined list, with options to add new values.

Developers can also create custom data types to represent specific business needs:

  • Custom Data Types: For example, an IBAN bank account number or a phone number. These can be reused across the application and have specific logic for display and input.

Data types dictate how information is presented, whether in a persistent object or within a query. For instance, an Image Data Type might display the image directly in a query, while in edit mode, it allows users to upload or paste a new image.

This flexibility enables developers to tailor the application to specific requirements.


Business Rules

Business Rules in Vidyano allow you to enforce predefined validation rules across your application. Some of the built-in rules include:

  • NotEmpty: Ensures that a field is not left empty.

  • Required: Mandates that a field must be filled.

  • Min/MaxValue: Sets minimum and maximum values for numeric fields.

  • IsEmail: Validates if the input is a properly formatted email address.

In addition to these, developers can write custom business rules in C# to handle more complex validation logic. This involves:

  • Custom Validation: Writing code to evaluate the data input by the user and determine if it meets the required criteria.

  • Error Messaging: Displaying validation error messages when the input does not comply with the rules.

Business Rules provide a robust mechanism for maintaining data integrity and ensuring consistent validation throughout the application.

// No arguments, rule can be used as "NotEmpty"
public static string? NotEmpty(BusinessRulesArgs args, string value)
{
    if (string.IsNullOrWhiteSpace(value))
        return "{0} cannot be empty.";

    return null;
}

// With arguments, rule can be used as "Between(0, 100)"
public static string? Between(BusinessRulesArgs args, decimal value, decimal min, decimal max)
{
    if (value < min || value > max)
        return "{0} must be between {1} and {2}.";
    return null;
}

PersistentObjectActions

The Actions class in Vidyano is linked to specific Persistent Objects and is crucial for implementing custom logic, named following the [PersistentObject]Actions convention. Key functionalities include:

  • Custom Logic: Execute code when an entity is loaded, created, saved, or deleted.

  • Query Execution: Add logic when executing queries, enhancing data processing.

  • Versatile Methods: Provides various methods to handle different stages of an entity's lifecycle.

This class is widely used for customizing application behavior, providing developers with powerful tools to manage

The Legacy v5.x also contains relevant information about Actions classes.


Custom Actions

Custom Actions in Vidyano are user-defined actions, typically represented as buttons, that can be placed on Persistent Objects or Queries. They provide a flexible way to extend functionality.

Key Features

  • Role-Based Visibility: Custom Actions can be controlled via user roles, allowing developers to specify when and where these actions are visible.

  • Contextual Behavior: On a Query, actions can be configured to require conditions, such as

Examples of Using Custom Actions

Custom Actions can be used for a variety of tasks within Vidyano. Here are a few practical examples:

  • Send Email Action: This action can be used to send an email to a selected customer. When triggered, it opens a predefined email template and sends it to the customer's email address.

  • Import Data Action: This action can be placed on a customer query, allowing users to import data from a file. When the import button is clicked, a dialog appears for selecting a file, and the data is imported accordingly.

    public partial class Import : CustomAction<ProjectTargetContextType>
    {
        public override PersistentObject Execute(CustomActionArgs e)
        {
            return Manager.Current.RegisterImport(e.Query?.PersistentObject ?? e.Parent!, accept: ".xlsx");
        }
    }
    
    public partial class EmployeeActions
    {
        public override void OnImport(ImportArgs e)
        {
            var sheet = e.ReadAsExcelSheet();
        }
    }
  • Download Action: This action allows users to export download data for a specific Employee. When the user clicks the Download button, the data is fetched and will show a download dialog in the browser to save the result.

    [Description("""
               Allows to download a file from the server.
               """)]
    public partial class Download : CustomAction<ProjectTargetContextType>
    {
        /// <inheritdoc />
        public override PersistentObject Execute(CustomActionArgs e)
        {
            e.EnsureParent(Types.Employee);
    
            // This will inform Vidyano to call the OnGetStream method on EmployeeActions.
            return Manager.Current.RegisterStream(e.Parent);
        }
    }
    
    public partial class EmployeeActions
    {
        public override Stream OnGetStream(GetStreamArgs e)
        {
            return e.GetText(e.Key, "Employee.txt");
        }
    }

These examples illustrate the versatility of Custom Actions, enabling developers to extend Vidyano's capabilities to meet specific business needs.


Menu

The menu in Vidyano provides a structured way for end users to navigate through the application. It consists of:

  • Program Units: Higher-level items in the menu that can be easily shown or hidden based on user rights. Each application can have multiple program units.

  • Program Unit Items: These are links within a program unit and can point to a query, a persistent object (e.g., an account page), or even an external URL.

The entire menu system is driven by user rights, ensuring users only see the options they have access to. Additionally, developers can implement hooks to further control the visibility of program units or dynamically add new values.

This system offers a flexible way to organize and manage the application's navigation, enhancing user experience.


Web API Hooks

Vidyano allows you to extend your application by adding custom API methods. Each application comes with a predefined web file, named using a specific naming convention ([schema]Web), which acts as a built-in API controller.

Key Features

  • Custom Methods: You can add methods that are accessible via the /api/[methodName] endpoint.

  • Flexible Authentication: API methods can be configured to require authentication, support anonymous access, or use custom authentication mechanisms.

  • ApiArgs: Each method receives an ApiArgs argument, providing various request details.

  • Standard ASP.NET Core: Use the standard IResult interface for returning results, compatible with ASP.NET Core helper methods.

public IResult Ping(ApiArgs args)
{
    return Results.NoContent();
}

This system offers a flexible way to extend your application's functionality, allowing you to handle various scenarios and integrate external services seamlessly.


Settings

Vidyano provides a variety of settings that can be configured to tailor the application to specific needs. These include:

  • Built-in Settings: Accessible from the management interface, these settings cover a range of configurations, such as:

    • SMTP Configuration: For managing feedback or email sending settings.

    • Default Language and Culture: Setting the application's default language or cultural preferences.

  • View Preferences: Configuring how persistent objects are displayed, such as master-detail or full-page views.

  • Custom Settings: Developers can create and manage custom settings, which can be fetched and set via code. These settings are environment-specific, allowing different configurations for production, staging, and development environments.

These settings provide a flexible mechanism to customize the application behavior, from basic configurations to more complex feature toggles and external service settings.

// Get a setting
var enabled = Manager.Current.GetSetting("FlagEnabled", false);
var retries = Manager.Current.GetSetting("NumberOfRetries", 5);
var url = Manager.Current.GetSetting("ExternalUrl", "https://service/api/");

// Set a setting
Manager.Current.SetSetting("LastSync", Manager.Current.NowOffset);

The Legacy v5.x documentation also contains relevant information about Overriding Vidyano Settings.


Logging

Vidyano includes a built-in logging system that records events and errors, storing them in the database (SQL or RavenDB). Key features include:

  • Automatic Logging: The application logs important events automatically, such as metadata synchronization, incorrect sign in attempts or faults.

  • Error Logging: Records exceptions and faults, providing developers with detailed information during development and troubleshooting.

  • Custom Logging: Developers can add custom logs via code, useful for tracking specific events or processes.

  • Updateable Logs: Supports appending to logs during long-running processes, allowing continuous tracking and insight into process execution.

This robust system ensures that developers have comprehensive insights into application behavior and can quickly identify and resolve issues.

// Log an exception with detail information
ServiceLocator.GetService<IExceptionService>().Log(ex);

// Or as a warning
ServiceLocator.GetService<IExceptionService>().LogWarning(ex);

// Logged exceptions will contain:
// - Message/Type/Source
// - Data
// - All inner exceptions recursively
// Log
Manager.Current.Log("Message", LogType.Information);
// Will also log the User that was currently logged in
// For background jobs we can pass any user
Manager.Current.Log("Job failed", LogType.Error, adminUser);
// Updateable log
using var log = Manager.Current.UpdateableLog("Message", LogType.Custom);
// do some processing
log.AppendLine("Extra information");
log.ChangeType(LogType.Warning);
log.Prepend("Finished at ..."); // Makes it easy in the Logs view to see this part
log.Delete(); // if the log isn't relevant you could clean it up
/// <summary>Describes the type of logging.</summary>
public enum LogType : byte
{
  /// <summary>An error has occurred.</summary>
  Error,
  /// <summary>The synchronization process has logged something.</summary>
  Synchronization,
  /// <summary>A security related entry has been logged.</summary>
  Security,
  /// <summary>A custom entry has been logged.</summary>
  Custom,
  /// <summary>A warning entry has been logged.</summary>
  Warning,
  /// <summary>An information entry has been logged.</summary>
  Information,
  /// <summary>A licensing entry has been logged (Legacy).</summary>
  Licensing,
}

Verbose Logging

Vidyano also supports a verbose logging concept, where all requests are logged automatically with detailed information. This feature is invaluable for advanced troubleshooting and collecting metrics, allowing developers to gain deeper insights into application performance and user interactions.


Advanced Topics

Authenticator Service

The Authenticator Service in Vidyano is based on a naming convention ([schema]AuthenticatorService) and extends the base Authenticator Service class. This class is crucial for mapping users and verifying credentials. Key functionalities include:

  • Default Authentication: Uses BCrypt-encrypted passwords stored in the database.

  • External Providers: Supports integration with external authentication providers like Azure AD, Google, and Microsoft.

  • Custom Authentication: Allows implementation of custom password logic.

  • Group Membership: Determines user group memberships and supports various authentication customizations, including header-based authentication.

The Authenticator Service provides extensive flexibility, enabling developers to tailor authentication processes to specific requirements.

Check the legacy v5.x documentation about configuring Azure AD SAML based authentication.

Advanced Base Class

Vidyano includes an advanced class, named using a specific naming convention ([schema]Advanced), that provides numerous hooks to extend or modify built-in features. While these hooks are less frequently used, they are invaluable for specific scenarios.

The Advanced class is ideal for developers needing fine-grained control over the application's behavior.

Courier Messaging

See related document Courier Feature, can be used in RavenDB for message based communication.


Context

The Context class in Vidyano is a core component used for database interactions. Named following the [schema]Context convention, each application has at least one Context class. Key features include:

  • Database Interaction: For SQL Server, it's based on Entity Framework's DbContext. For RavenDB, it wraps the IDocumentSession.

  • Scoped Service: A single instance per request, used across the application.

  • Data Management: Provides properties for tables or collections and includes helper methods to add, remove, and save changes.

  • Custom Contexts: Developers can implement their own Context classes if needed.

The Context class is essential for managing data access and interactions, providing a flexible and powerful way to handle database communication.


Custom UI

Vidyano offers a default user interface out-of-the-box, allowing you to run applications without writing HTML or JavaScript. However, you can create custom components for specific needs, such as new data types or unique UI elements.

Custom UI is particularly useful for:

  • New Data Types: When adding new data types, you can design custom UI components.

  • Unique Requirements: For cases where the built-in UI doesn't meet your needs, custom components allow full control over the interface.

This flexibility ensures you can tailor the user experience to your application's unique needs.

Source code is available on https://github.com/Vidyano/vidyano together with a sample Vidyano application to showcase the different data types.


Samples

This section provides a collection of sample projects and code snippets to help you understand various use cases. These samples are designed to be downloaded and run, offering practical examples of how to implement different features in Vidyano.

While the full details are found in the code, the documentation highlights:

  • Common Use Cases: Real-world scenarios demonstrating key Vidyano features.

  • Code Examples: Sample code illustrating how to achieve specific tasks.

  • Downloadable Projects: Ready-to-run projects you can explore and adapt.

These samples serve as a practical reference, helping you to quickly grasp how to leverage Vidyano's capabilities in your own projects.

Sample Projects

  1. Web3: https://github.com/Vidyano/vidyano Web3 is a sample Vidyano application that showcases various data types and custom UI components. It provides a comprehensive overview of Vidyano's capabilities and how to implement them in your projects.

  2. VidyanoRavenSample: https://github.com/2sky/VidyanoRavenSample A sample project demonstrating how to use RavenDB with Vidyano. It includes a basic setup and configuration to get you started with RavenDB in Vidyano.


Tips and Tricks

Vidyano offers several developer-centric features to enhance productivity:

  • Profiler: Open the profiler while the application runs in the browser to view details of requests, including methods called, parameters, database calls, and performance metrics.

  • Control + Right-Click: Use this shortcut to open a pop-up menu for quick navigation within the management interface.

  • Impersonation: Test various user roles by impersonating users without needing to log in separately.

These features streamline development and testing, making it easier to manage and optimize your Vidyano applications.


This document provides a structured guide to working with Vidyano. For further details, explore each section and refer to the provided code snippets.

Vidyano Documentation

This documentation will help you build powerful data-driven applications using the Vidyano application platform.

Getting started Section

For developers ready to dive in, the Getting Started section provides the necessary steps to install tools and create a new project with Vidyano.

Documentation Sections

The documentation provides insights into the various concepts within a Vidyano project. It details their functionalities and guides on how to effectively utilize them in your applications. Developers can leverage this knowledge to enhance their projects and maximize the platform's potential.

Current versions

Here are a few badges with the latest versions of various packages used for Vidyano projects

Getting started

Getting the correct tools

Make sure .NET 8.0 SDK is installed (https://dotnet.microsoft.com/download/dotnet/8.0) (Vidyano v6 can also be used with .NET 9.0 but the project template uses .NET 8.0 by default)

Install the Vidyano dotnet new templates

dotnet new install Vidyano.Templates::1.0.20241119.5814

Trust the development certificate (needs to be done once on a development machine)

dotnet dev-certs https --trust

You only need to do this once.

Create your first Vidyano application

mkdir VidyanoSample
cd VidyanoSample
dotnet new vidyano

The latest version currently is 6.0.20250305.5880 (you can check the .csproj file for the version you are referencing), in most cases you can immediately update your NuGet packages even before running the application.

Courier Feature

1. Overview

The Courier feature in Vidyano provides a reliable, message-based processing system built on top of RavenDB. It enables asynchronous communication between different parts of your application through a publish-subscribe pattern, supporting immediate and delayed message delivery with at-least-once guarantees.

2. Enabling the Courier Feature

To enable the Courier feature in your Vidyano application:

  1. Register the feature in your Startup.cs file:

public void ConfigureServices(IServiceCollection services)
{
    // Add other services...
    
    // Register your Courier message handlers
    services.AddRecipient<YourMessage, YourMessageHandler>();
}

3. How It Works

The Courier feature works as follows:

  1. Message Publishing: Messages are stored in RavenDB as CourierMessage documents.

  2. Message Processing:

    • A RavenDB subscription monitors the database for new messages

    • When a message is detected, the system finds the appropriate recipient(s)

    • Each recipient processes the message through its ReceiveAsync method

    • After processing, messages are marked as processed with an expiration time

  3. Message Lifecycle:

    • Created → Waiting for Processing → Processed → Expired

    • Or: Created → Delayed → Waiting for Processing → Processed → Expired

    • Or: Created → Waiting for Processing → Failed → Retry → Processed → Expired

4. Key Features

4.1 Message Delivery Patterns

  • Immediate Delivery: Messages are sent and processed as soon as possible

  • Delayed Delivery: Messages are processed after a specified time delay

  • At-Least-Once Delivery: Messages are guaranteed to be delivered at least once

4.2 Idempotent Messages

Messages can implement the IIdempotentMessage interface to ensure they are only processed once, identified by a unique identifier.

4.3 Message Retry

Failed messages can be automatically retried based on configurable retry strategies.

4.4 Message Expiration

Processed messages are automatically removed from the system after a configurable expiration period.

5. Usage Examples

5.1 Defining a Message

// Regular message
public record NotificationMessage(string Subject, string Body, string RecipientEmail);

// Idempotent message
public record OrderProcessedMessage(string OrderId, DateTimeOffset ProcessedOn) : IIdempotentMessage
{
    // Implementation of IIdempotentMessage.Identifier
    public string Identifier => $"order-processed-{OrderId}";
}

5.2 Creating a Message Handler

public class NotificationMessageHandler : IRecipient<NotificationMessage>
{
    private readonly IEmailService _emailService;
    
    public NotificationMessageHandler(IEmailService emailService)
    {
        _emailService = emailService;
    }
    
    public async Task ReceiveAsync(ReceiveArgs<NotificationMessage> args, CancellationToken cancellationToken)
    {
        var message = args.Message;
        
        // Process the message
        await _emailService.SendEmailAsync(
            message.RecipientEmail, 
            message.Subject, 
            message.Body, 
            cancellationToken);
        
        // Configure message expiration (optional, defaults to 1 day)
        args.ExpireAfter = TimeSpan.FromDays(7);
        
        // Example of sending a follow-up message
        args.Courier.Send(new NotificationLoggedMessage(message.RecipientEmail));
    }
}

5.3 Sending Messages

// Get the courier service
using var _ = ServiceLocator.GetScopedRequiredService<ICourier>(out var courier);

// Send immediate message
courier.Send(new NotificationMessage(
    "Welcome!", 
    "Thank you for registering.", 
    "user@example.com"));

// Send delayed message (relative time)
courier.DelaySend(
    new ReminderMessage("Don't forget to complete your profile."), 
    TimeSpan.FromDays(3));

// Send delayed message (absolute time)
var nextMonday = GetNextMonday();
courier.DelaySend(
    new WeeklyReportMessage(), 
    nextMonday);

// Send idempotent message
// This will only be processed once, even if sent multiple times with the same ID
courier.Send(new OrderProcessedMessage("ORD-12345", DateTimeOffset.UtcNow));

5.4 Chain of Messages

public class OrderCreatedMessageHandler : IRecipient<OrderCreatedMessage>
{
    public Task ReceiveAsync(ReceiveArgs<OrderCreatedMessage> args, CancellationToken cancellationToken)
    {
        // Process the order
        ProcessOrder(args.Message);
        
        // Start order fulfillment process by sending another message
        args.Courier.Send(new InitiateFulfillmentMessage(
            args.Message.OrderId, 
            args.Message.Items));
            
        // Schedule a follow-up message after 3 days
        args.Courier.DelaySend(
            new OrderFollowUpMessage(args.Message.OrderId, args.Message.CustomerEmail),
            TimeSpan.FromDays(3));
            
        return Task.CompletedTask;
    }
}

6. Configuration Options

The Courier system can be configured in your appsettings.json file:

{
  "Vidyano": {
    "Courier": {
      "RetryWaitTime": "00:00:30",
      "MaxDownTime": "00:05:00",
      "MaxDocsPerBatch": 25
    }
  }
}

Configuration options:

  • RetryWaitTime: Time to wait before retrying subscription connection (default: 30 seconds)

  • MaxDownTime: Maximum period the system will be down before giving up (default: 5 minutes)

  • MaxDocsPerBatch: Maximum number of messages processed in a batch (default: 25)

7. Best Practices

  1. Keep Messages Small: Store only essential information in messages.

  2. Make Messages Immutable: Use C# record types or immutable classes.

  3. Design for Idempotency: Ensure that processing a message multiple times does not cause issues.

  4. Handle Exceptions: Implement proper exception handling in your message handlers.

  5. Use Message Chains: Break complex workflows into chains of simple messages.

  6. Consider Message Expiration: Set appropriate expiration times for processed messages.

  7. Monitor the System: Implement logging and monitoring for message processing.

8. Troubleshooting

If messages are not being processed as expected:

  1. Check Subscription Status: Ensure the RavenDB subscription is active.

  2. Verify Handler Registration: Confirm that your message handlers are registered correctly.

  3. Review Message Serialization: Check that your message classes can be properly serialized/deserialized by JSON.NET.

  4. Examine Expired Messages: Look for messages that have expired before processing.

  5. Look for Exceptions: Check exception logs for errors in message handlers.

  6. Inspect RetryWaitTime: Adjust RetryWaitTime if messages are not being retried quickly enough.

  7. Disable Specific Message Types: Use the Courier{MessageTypeName}Disabled setting to temporarily disable processing of specific message types.

Managing User Secrets

During development it is recommended to store connection strings, salts, or other secrets in a secure manner. It is best to avoid storing these secrets in the code or local configuration files.

In .NET Core, you will want to take advantage of the secrets manager. You can find more information here:

In Visual Studio 2019, you can access these via right-click on your project and select Manage User Secrets.

In Visual Studio Code, you can install the following extension to get the same behavior:

https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-3.1&tabs=windows
https://marketplace.visualstudio.com/items?itemName=Reptarsrage.vscode-manage-user-secrets

Storage Provider

Vidyano has the ability to store incoming and outgoing requests when the verbose logging feature is enabled. This can help for troubleshooting requests or for auditing purposes.

By default it will use the Vidyano.Logging connection string to create a blob container to store the data as block blobs.

Using the Advanced.StorageProvider property you can set your own provider that implements the IStorageProvider interface.

Example

This example will store the data inside the App_Data/Logs folder.

using System.IO;
using System.Web.Hosting;
using Vidyano.Core.External;

public sealed class FolderStorageProvider : IStorageProvider
{
    private readonly string path = HostingEnvironment.MapPath("~/App_Data/Logs");
    public void Delete(AzureStorageAccount account, string containerName, string name)
    {
        File.Delete(Path.Combine(path, containerName, name));
    }

    public void EnsureExists(AzureStorageAccount account, string containerName)
    {
        Directory.CreateDirectory(Path.Combine(path, containerName));
    }

    public string DownloadText(AzureStorageAccount account, string containerName, string name)
    {
        return File.ReadAllText(Path.Combine(path, containerName, name));
    }

    public string UploadText(AzureStorageAccount account, string containerName, string name, string data)
    {
        File.WriteAllText(Path.Combine(path, containerName, name), data);
        return string.Empty;
    }
}

Vidyano contains a Vidyano.Core.External.FolderStorageProvider implementation that has gzip and encryption support.

Grids

These screenshots are from older versions, some minor UI changes can be expected

The query grid is a powerful data table component with the following features:

Big data

Users can quickly scroll over a million lines of data. While in practice this would make for a time consuming job, other features such as filtering will make it easy to find relevant data. Developers however do not need to worry about the size of the data set as data is loaded and rendered in a performance and memory efficient way.

Lazy loading

Data set chunks are loaded whenever they should be visible within the visible boundaries of the screen. This means that even if your entire data set would potentially return over a million records but your screen can only show 30 or so at the time, the grid will load a chunk of data (by default in pages of 100 records).

Data sorting

Out of the box sorting of data on any column or multiple columns by holding the CTRL-key and clicking each column in order of sorting priority.

Pin columns

Keep columns in view while horizontally scrolling over your data. This view-state is automatically persisted on their profile.

Hide columns

Users are in control over the amount of data they want on the screen. This view-state is automatically persisted on their profile.

Reorder columns

Users are in control over which columns should be visible first. This view-state is automatically persisted on their profile.

Manage column order, visibility and pinning

Filtering

Easily filter data by clicking on the column filter icon and enter custom filter text or select one or more items from the list with the distinct values for that column in the data set.

Enter search text or select a value from the list

Users can choose to save the filter for later use:

Saved filter that only shows products with color red

Built for performance

The grid component uses cutting edge techniques to ensure that the user experience feels lightning fast. For example, the grid reuses DOM elements in order to keep the memory footprint as small as possible while taking advantage of hardware accelerated rendering.

Icons

Ensure you are using version 3.19.0 or later of Web3 to take advantage of the new feature that allows setting any Iconify icon as an icon for a custom action. Access the full list of icons at Iconify, and use the set:name format for specifying icons in your custom actions.

In the Management you can go to the Custom Action and enter the set:name of the icon you want to use. For example material-symbols:verified-user-outline for an Check user action.

Reports

The reports feature offers developers the capability to securely expose data queries in various formats such as XML, JSON, and CSV. This ensures that data can be accessed externally while maintaining security through authentication and tokenization.

Token

Reports make use of a secure random (384bit) token to access the specified query. This token should be kept private and not shared with anyone as they will be able to access the report with it.

Access

Depending on the sensitivity of the data you can use 3 options to access a report.

  • IdWithToken: You can pass the token as a query parameter (e.g. )

  • (Recommended): This method uses the Authorization header to pass the token. The main benefit is that the token won’t be accidentally exposed in places where the request url is logged (i.e. diagnostics).

  • (Best security): This method needs custom code to generate a HMAC256 signature for the requested resource. The benefits are that the key is never send across the wire and that the request can’t be replayed at a later time.

You can configure the minimum access level that should be used on a report. When it is accessed using a lesser secure option it will be flagged as and it will not be available unless a new token has been generated.

Format

Reports return XML by default. You can use the format query parameter or Accept header to request the data as json (application/json), csv (text/csv) or tsv (text/tab-separated-values).

Examples:

  • curl:

AuthorizationHeader

This option expects the token to be passed as Authorization header using the Bearer scheme when accessing the report.

curl

JavaScript

PowerShell

AuthorizationSignature

The signature is generated by including a Date header (containing a RFC1123 formatted UTC date and time), an Accept header (containing application/json, text/xml, text/csv or text/tab-separated-values) and using HMAC256 to sign the combination of the request url, accept and timestamp using a newline separator with the token as key. The Date header will be validated that it is no more than 5 minutes in the past or future (to allow for some clock skewing).

.NET

PowerShell

Postman

For testing you can also use as it allows for Pre-request scripts and variables:

Compromised

When a report is accessed using a less secure option than configured or using http instead of https it will be flagged as compromised. A log entry will be written with information about the requested url, ip and user-agent.

You can re-enable the report by giving it a new token. On a production deployment the patching feature can be used to regenerate the token.

Excel

Reports can be used to import data in an Excel file. The data will be linked to the Excel table so that it can be refreshed manually or automatically when the file is opened.

This can be done by starting the From Web wizard in Excel:

In the wizard you can switch to Advanced mode where you can add the Authorization header:

Paging / Sorting

Using top and/or skip also requires sorting to provide consistent results.

Examples:

For the AuthorizationSignature option you’ll need to include the whole url as requested (which shouldn’t contain the token and format in that case, the format should be in the Accept header) for the signature calculation.

Filtering

You can use the $filter query parameter to include the text-search (as used inside a Vidyano application in the search box on a query) that should be used to filter.

Examples:

For the AuthorizationSignature option you’ll need to include the whole url as requested (which shouldn’t contain the token and format in that case, the format should be in the Accept header) for the signature calculation.

Migration scripts for v5 Repository database

Make sure you have a backup before running the scripts

-- Logs
if COL_LENGTH('Vidyano.Logs', 'ExternalId') is null
begin
	exec sp_rename 'Vidyano.Logs.Id', 'ExternalId', 'COLUMN';
	alter table Vidyano.Logs add Id bigint identity
	alter table Vidyano.Logs alter column Type tinyint not null
	alter table Vidyano.Logs alter column CreatedOn datetimeoffset(3) not null

	alter table Vidyano.Logs drop constraint [PK_Vidyano_Logs]
	alter table Vidyano.Logs add constraint PK_Logs primary key(Id)

	CREATE UNIQUE NONCLUSTERED INDEX [AK_Logs] ON [Vidyano].[Logs] ([ExternalId])

	--CREATE NONCLUSTERED INDEX [IX_Logs_PERF1] ON [Vidyano].[Logs] ([CreatedOn] DESC)
end

-- Settings
if COL_LENGTH('Vidyano.Settings', 'Id') is not null
begin
	declare @keyLength int = COL_LENGTH('Vidyano.Settings', 'Key')
	if @keyLength = -1 or @keyLength > 255
	begin
		alter table Vidyano.Settings alter column [Key] nvarchar(255) not null
	end

	alter table Vidyano.Settings drop constraint [PK_Settings]
	alter table Vidyano.Settings drop column [Id]
	alter table Vidyano.Settings add constraint [PK_Settings] primary key ([Key])
end

-- Users/UserGroup/Users_Group
SET ANSI_PADDING ON

IF (OBJECT_ID('[Vidyano].[Groups]', N'U') is null)
BEGIN 

-- Move Groups to separate table
select u.[Id], u.[Name], u.IsSystem, u.CreationDate, g.TwoFactorRequired
	into [Vidyano].[Groups]
	from [Vidyano].Users u
	inner join [Vidyano].[Users_Group] g on g.[Id] = u.[Id]

alter table [Vidyano].[Groups] add constraint [PK_Groups] primary key ([Id])
alter table [Vidyano].[Groups] add constraint [UQ_Groups_Name] UNIQUE NONCLUSTERED ([Name])

-- Switch to Groups table
ALTER TABLE [Vidyano].[UserGroup] DROP CONSTRAINT [FK_UserGroup_Group]
ALTER TABLE [Vidyano].[UserGroup]  WITH CHECK ADD  CONSTRAINT [FK_UserGroup_Group] FOREIGN KEY([Groups_Id]) REFERENCES [Vidyano].[Groups] ([Id])
ALTER TABLE [Vidyano].[UserGroup] CHECK CONSTRAINT [FK_UserGroup_Group]

END
go

if COL_LENGTH('Vidyano.Users', 'IsSystem') is not null
begin

alter table Vidyano.Users drop column IsSystem

end
go

if COL_LENGTH('Vidyano.Users', 'IsEnabled') is null
begin

alter table Vidyano.Users add
	IsEnabled bit constraint DF_Users_IsEnabled default 1 not null
	,TwoFactorToken varchar(100) null
	,ResetPasswordNextLogin bit constraint DF_Users_ResetPasswordNextLogin default 0 not null

declare @constraint nvarchar(256)

select @constraint=d.name from  sys.tables t
inner join sys.schemas s ON s.schema_id = t.schema_id
inner join sys.default_constraints d on d.parent_object_id = t.object_id
inner join sys.columns c on c.object_id = t.object_id
and c.column_id = d.parent_column_id
where 
t.name = N'Users'
and s.name = N'Vidyano' 
and c.name = N'Version'

IF LEN(ISNULL(@constraint, '')) <> 0
BEGIN
    DECLARE @sqlcmd VARCHAR(MAX)
    SET @sqlcmd = 'ALTER TABLE [Vidyano].[Users] DROP CONSTRAINT' +  QUOTENAME(@constraint);
    EXEC (@sqlcmd);
END

alter table Vidyano.Users alter column Version varchar(100) not null

exec ('update [Vidyano].[Users] set
	IsEnabled = case when JSON_VALUE(Profile, ''$.Disabled'') = ''true'' then 0 else 1 end
	, TwoFactorToken = JSON_VALUE(Profile, ''$.TwoFactorToken'')
	, ResetPasswordNextLogin = case when JSON_VALUE(Profile, ''$.ResetPasswordNextLogin'') = ''true'' then 1 else 0 end
	, Profile = JSON_MODIFY(JSON_MODIFY(JSON_MODIFY(Profile, ''$.Disabled'', NULL), ''$.TwoFactorToken'', NULL), ''$.ResetPasswordNextLogin'', NULL)
	from Vidyano.Users 
	where Profile is not null')

end
go

declare @keyLength int = COL_LENGTH('Vidyano.Users', 'Language')
if @keyLength = -1 or @keyLength > 10
begin
	update [Vidyano].[Users] set [Language] = '' where [Language] is null
	alter table [Vidyano].[Users] alter column [Language] varchar(10) not null
end

set @keyLength = COL_LENGTH('Vidyano.Users', 'CultureInfo')
if @keyLength = -1 or @keyLength > 10
begin
	update [Vidyano].[Users] set [CultureInfo] = '' where [CultureInfo] is null
	alter table [Vidyano].[Users] alter column [CultureInfo] varchar(10) not null
end
go

IF (OBJECT_ID('[Vidyano].[UserSettings]', N'U') is null)
BEGIN 

exec('select Id, [Settings]
	into [Vidyano].[UserSettings]
	from [Vidyano].Users
	where len([Settings]) > 2')

alter table [Vidyano].[UserSettings] add constraint PK_UserSettings primary key (Id)
alter table [Vidyano].[UserSettings] alter column [Settings] nvarchar(max) not null
alter table [Vidyano].[UserSettings] add constraint [FK_UserSettings_Users]
    foreign key ([Id])
    references [Vidyano].[Users] ([Id])
    on delete cascade on update no action;

alter table [Vidyano].Users drop column [Settings]

end
go

IF (OBJECT_ID('[Vidyano].[UserProfiles]', N'U') is null)
BEGIN 

exec('select Id, [Profile]
	into [Vidyano].[UserProfiles]
	from [Vidyano].Users
	where len([Profile]) > 2')

alter table [Vidyano].[UserProfiles] add constraint PK_UserProfiles primary key (Id)
alter table [Vidyano].[UserProfiles] alter column [Profile] nvarchar(max) not null
alter table [Vidyano].[UserProfiles] add constraint [FK_UserProfiles_Users]
    foreign key ([Id])
    references [Vidyano].[Users] ([Id])
    on delete cascade on update no action;

alter table [Vidyano].Users drop column [Profile]

end

-- ClientCodeSnippets (was used in v1 web client)
drop table if exists Vidyano.ClientCodeSnippets

-- Group flattening (groups are no longer member of another group)
IF (OBJECT_ID('[Vidyano].[TmpGroupGroups]', N'U') is null)
BEGIN
exec('select Users_Id, Groups_Id
	into Vidyano.TmpGroupGroups
	from Vidyano.UserGroup where Users_Id in (select Id from Vidyano.Groups)');
END

-- Drop old inherited Users_Group table
drop table if exists [Vidyano].[Users_Group]

-- Delete groups from Vidyano.Users
delete from Vidyano.UserGroup where Users_Id in (select Id from Vidyano.Groups)
delete from Vidyano.Users where Id in (select Id from Vidyano.Groups)

-- Flatten groups
;WITH cte AS (
SELECT tmp.[Users_Id]
	,ug.Name GroupName
      ,tmp.[Groups_Id]
	  ,gg.Name MemberOf
  FROM [Vidyano].[TmpGroupGroups] tmp
	inner join Vidyano.Groups ug on ug.Id = tmp.Users_Id
	inner join Vidyano.Groups gg on gg.Id = tmp.Groups_Id

union all

SELECT cte.[Users_Id]
	,cte.GroupName
      ,tmp.Groups_Id
	  ,gg.Name MemberOf
  FROM [Vidyano].[TmpGroupGroups] tmp
  inner join cte on cte.Groups_Id = tmp.[Users_Id]
	inner join Vidyano.Groups gg on gg.Id = tmp.Groups_Id
)
insert into Vidyano.UserGroup (Users_Id, Groups_Id)
select distinct ug.Users_Id, cte.Groups_Id
	from Vidyano.UserGroup ug
	inner join cte on cte.Users_Id = ug.Groups_Id
except
select ug.Users_Id, ug.Groups_Id from Vidyano.UserGroup ug

-- Update repository version
update Vidyano.Settings set Value='61' where [Key]='RepositoryVersion'

Client

Web3 release notes

curl -H 'Authorization: Bearer TOKEN' 'https://localhost:44332/GetReport/4cb4854ca87a4e4aaf66c8f2469d58ea'
curl -H 'Accept: application/json' 'https://localhost:44332/GetReport/4cb4854ca87a4e4aaf66c8f2469d58ea'
curl -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_TOKEN_HERE' 'https://localhost:44332/GetReport/4cb4854ca87a4e4aaf66c8f2469d58ea'
var token = "YOUR_TOKEN_HERE";
var reportUrl = "https://localhost:44332/GetReport/4cb4854ca87a4e4aaf66c8f2469d58ea";
var headers = {
  "Accept": "application/json",
  "Authorization":"Bearer " + token
};
fetch(reportUrl, { headers: headers })
  .then(function (response) {
    return response.json();
  })
  .then(function (json) {
    var data = json.d; // Array with the objects
    console.log(data)
  });
$token = 'get token from secure setting/vault'
$reportUrl = 'https://localhost:44332/GetReport/4cb4854ca87a4e4aaf66c8f2469d58ea'
$headers = @{"Accept"='application/json';"Authorization"='Bearer '+$token}
$data = (Invoke-WebRequest $reportUrl -Headers $headers | ConvertFrom-Json).d
var token = "YOUR_TOKEN_HERE"; // It's recommended to get this from a setting/vault/configuration/...
var reportUrl = "https://localhost:44332/GetReport/4cb4854ca87a4e4aaf66c8f2469d58ea";
var accept = "application/json";
var date = System.DateTime.UtcNow;
var timestamp = date.ToString("R", System.Globalization.CultureInfo.InvariantCulture);
var messageSignature = string.Join("\n", reportUrl, accept, timestamp);

string signature;
using (var hmac = new System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(token)))
    signature = Convert.ToBase64String(hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(messageSignature)));

var request = System.Net.WebRequest.CreateHttp(reportUrl);
request.Accept = accept;
request.Date = date;
request.Headers.Add("Authorization", "Bearer " + signature);
$token = 'get token from secure setting/vault'
$reportUrl = 'https://localhost:44332/GetReport/4cb4854ca87a4e4aaf66c8f2469d58ea'
$accept = 'application/json'
$timestamp = [System.DateTime]::UtcNow.ToString('R')
$messageSignature = "$reportUrl`n$accept`n$timestamp"

$hmac = New-Object System.Security.Cryptography.HMACSHA256
$hmac.Key = [Text.Encoding]::UTF8.GetBytes($token)
$signature = [Convert]::ToBase64String($hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($messageSignature)))

$headers = @{"Accept"=$accept;"Date"=$timestamp;"Authorization"='Bearer '+$signature}
$data = (Invoke-WebRequest $reportUrl -Headers $headers | ConvertFrom-Json).d
// Pre-request Script
var token = pm.environment.get("token");
var reportUrl = request.url;
var accept = "application/json";
var timestamp = (new Date()).toGMTString();
var messageSignature = [reportUrl, accept, timestamp].join("\n");

var signature = CryptoJS.HmacSHA256(messageSignature, token).toString(CryptoJS.enc.Base64);

pm.globals.set("accept", accept);
pm.globals.set("timestamp", timestamp);
pm.globals.set("encrypted_signature", "Bearer " + signature);
https://localhost:44332/GetReport/4cb4854ca87a4e4aaf66c8f2469d58ea?token=TOKEN
AuthorizationHeader
AuthorizationSignature
compromised
https://localhost:44332/GetReport/4cb4854ca87a4e4aaf66c8f2469d58ea?token=TOKEN&format=json
https://localhost:44332/GetReport/4cb4854ca87a4e4aaf66c8f2469d58ea?token=TOKEN&format=csv
Postman
https://localhost:44332/GetReport/4cb4854ca87a4e4aaf66c8f2469d58ea?token=TOKEN&format=json&$orderBy=FirstName&$top=100&$skip=100
https://localhost:44332/GetReport/4cb4854ca87a4e4aaf66c8f2469d58ea?token=TOKEN&format=csv&$orderBy=LastName+DESC&$top=100
https://localhost:44332/GetReport/4cb4854ca87a4e4aaf66c8f2469d58ea?token=TOKEN&format=json&$filter=FirstName:Steve
https://localhost:44332/GetReport/4cb4854ca87a4e4aaf66c8f2469d58ea?token=TOKEN&format=csv&$filter=FirstName:Steve+LastName:Hansen

Migrating an existing Vidyano v5 project

Because Vidyano v6 uses ASP.NET Core and EntityFrameworkCore most migrating steps are related to this (and any online tutorial for upgrading to ASP.NET Core will help in this step). Below are the general steps needed to get you up and running.

  • Create a new project using the above steps (this will create a correct project for .NET core and the correct startup logic)

  • As with most tutorials, it is now time to copy over the existing code you had (.NET core no longer has the path of different files inside your .csproj file so you can just copy them over folder wise)

  • Service goes to Service folder, static webrelated files for to wwwroot, App_Data goes to wwwroot/App_Data

  • A few exceptions are needed, for the *Advanced/*AuthenticatorService/*BusinessRules/*Queries/*Web it is best to copy the methods/properties/fields in the existing classes where possible

  • Any existing NuGet references that the application had will need to be added again (as PackageReference, the packages.json is no longer valid)

  • Any appSettings/connectionStrings from web.config need to be copied over in the appsettings.json file, Vidyano.X settings go inside the Vidyano object (<add key="Vidyano.X" value="True" /> becomes {"Vidyano":{"X": true}})

  • Vidyano v6 (and .NET Core/ASP .NET Core) makes great use of DI, that's why all PersistentObjectActions implementation now require a default constructor that at least excepts the TContext as a parameter, other services can now be constructor injected there as well.

  • Your existing EntityFramework DbContext needs to be upgraded to EntityFrameworkCore. Some notable/recommended changes

    • Make sure properties are virtual (the Proxy package is included but needs to be enabled manually)

    • Mark InverseProperties where needed

    • Mark ForeignKey where needed

    • Mark classes with Table attribute

    • Remove Index attribute (needs to be configured in OnModelCreating)

  • You existing *Web also needs some updates, ASP.NET Core no longer uses HttpResponseMessage so all custom api methods needs to be updated to use IActionResult as return type (that exact type).

  • *Advanced also has most breaking changes (deprecated code has been dropped, ...)

Migrating from v5.x

If you still have a Vidyano project running on .NET 4.8 and ASP.NET you can find steps here to migrate to the latest version running on ASP.NET Core.

SourceGenerators

Vidyano makes use of the Vidyano.SourceGenerators NuGet package to offer various helpers.

The latest Vidyano project template will enable this by default, otherwise there are a few things that need to be added to the .csproj to provide enough context for it to work.

Disclaimer

  • Use of SourceGenerators depend on available content.

  • Analyzers and CodeFixes are available in any project.

Some Patterns are not supported by Analyzers at the moment:

  • When Clause on Switch Expression / Statements

Generators

  • Actions Generator

  • BusinessRules Generator

  • Context Generator

  • CustomAction Generator

  • Index Generator

  • Model Generator

  • ProjectTargetType Generator

Generated Constant classes

All generated Constant classes are available under {RootNamespace}.Service.Generated namespace.

  • BusinessRuleNames

  • PersistentObjectTypes

  • PersistentObjectAttributeNames

  • ProgramUnitNames

  • ProgramUnitItemsNames

  • QueryNames

  • QuerySources

  • ActionNames

  • Languages

  • MessageKeys and Messages

  • AppRoles

  • WebsiteNames

  • Obsolete.PersistentObjectTypes

Dependency Injection

By using the InitializeByCtor attribute on a field, the source generator will inject this field via the generated constructor.

The Attribute can only be applied when no constructor is provided.

public partial class CompanyActions
{
    [InitializeByCtor]
    private readonly IMyService myService;
}

Attribute is available via reference Vidyano.Abstractions.

Index Generator

Generate an overview index by adding the GenerateIndex attribute to an entity.

This will result in the following being generated:

  • A partial {Entity} class with a QueryType attribute.

  • A partial {VEntity} QueryType class.

  • A partial {Entities}_Overview Index class.

  • A V{Entities} property on the Context if it does not already exist.

Creating an Index for an Entity

Adding this GenerateIndexattribute creates an overview index.

  • To change the name of the Index, pass the desired type as an argument. Note: Include a namespace if you want the index to be generated under a different namespace.

  • To change the name of the QueryType, use the QueryType attribute.

Important Notes:

  • Index will not be generated if QueryType or Index is already added manually, except if they are partial.

  • If the QueryType contains audit fields, the appropriate interface will be applied. This can differ from the Entity if you add IgnoreForIndex to an audit field.

  • Only the IId interface will be copied to the QueryType.

Additional Attributes

You can add several attributes to the entity's properties to control the index:

  • Search: Adding this attribute creates a second {Property}_Sort property to allow full search on this property.

    Note: This is only needed when values can contain spaces.

  • IgnoreForIndex: This property will not be included in the index.

  • IndexReference: You can include a property from the Reference in the index by adding one attribute per property and defining the path on the reference. This can be more than one level deep.

    Note:

    • If the Reference is Nullable, then every generated IndexReference property is also Nullable.

    • Path cannot contains a Collection property.

[GenerateIndex]
public partial class Customer
{
    public string Id {get; set;}

    [Search]
    public string Name {get; set;}

    [IgnoreForIndex]
    public string Private {get; set;}

    [IndexReference(nameof(Person.Name), Search = true)]
    [IndexReference("Address.CityName")]
    [Reference(typeof(Person)]
    public string Person {get; set;}
}

Projects

Main project

Additional Files & Global Usings

Add following ItemGroup to the .csproj project file.

<ItemGroup>
  <AdditionalFiles Include="App_Data\**\*.json" />
  <Using Include="$(MSBuildProjectName).Service.Generated" />
  <Using Alias="Types" Include="$(MSBuildProjectName).Service.Generated.PersistentObjectTypes.$(MSBuildProjectName)" />
  <Using Alias="AttributeNames" Include="$(MSBuildProjectName).Service.Generated.PersistentObjectAttributeNames.$(MSBuildProjectName)" />
  <Using Alias="QueryNames" Include="$(MSBuildProjectName).Service.Generated.QueryNames.$(MSBuildProjectName)" />
  <Using Alias="QuerySources" Include="$(MSBuildProjectName).Service.Generated.QuerySources.$(MSBuildProjectName)" />
</ItemGroup>   

External Context

If the context file does not exist in the main project, you can use the appsettings.json file as an alternative, as the context will be read from there.

<ItemGroup>
  <AdditionalFiles Include="appsettings.json" />    
</ItemGroup>

Library project

Missing App_Data json files warning

When incorporating SourceGenerators into a library project, you may encounter a VIDYANO0010 warning indicating missing App_Data JSON files. This warning is not applicable to library projects, as they do not utilize App_Data.

To bypass this warning, two methods are available.

  • By disabling the Model SourceGenerator via local .editorconfig file

is_global = true

# Source Generators
disable_model_source_generator = true
  • By ignore the warning via csproj file

<NoWarn>VIDYANO0010</NoWarn>

Exceptions

When using EmitCompilerGeneratedFiles in .csproj project file you can get exceptions when compiling in Windows because of use of long filenames.

<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>

To fix this issue you need to add the following registration key (Reboot needed to take effect)

reg ADD HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem /v LongPathsEnabled /t REG_DWORD /d 1

Backwards Compatibility

For backwards compatibility we created an Obsolete PersitentObjectTypes class (old version). The only thing you need todo when upgrading to the new source generator is changing the using in de .csproj project file by adding the Generated.Obsolete namespace.

<Using Alias="Types" Include="Fleet.Service.Generated.Obsolete.PersistentObjectTypes.Fleet" />

This way the project wil run as before.

Note: If you add the new usings directly you can use de CodeFix to update to the correct code.

2.0.0

Breaking Changes

Removed shim libraries and thereby out of the box support for Internet Explorer

This version drops out of the box support for Internet Explorer. This means a lot of the shim libraries have been removed in favor of adding the following code to your index.html:

<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6"></script>

Note that this will also be the last major version of the Vidyano web client that will support Internet Explorer at all.

Upgraded Typescript to version 3

This update together with the removal of the shim libraries (see below) will require you to add a lib section to the tsconfig.json file:

tsconfig.json
{
  "compilerOptions": {
    "lib": [
      "dom",
      "es2015"
    ],
    ...

Deprecated LINQ for JavaScript and removed all references from base library

The most major breaking change is the deprecation of the linq.js base library.

The bad news

  • all code using LINQ for JavaScript will write a deprecation warning to the console. The next major version of the client will no longer include the library by default.

The good news

  • we added replacements for the most used functions directly on the native JavaScript Array prototype.

  • you can easily include the library if you choose not to migrate your code. Simply add the following line of code to the ```<head>``` element in your index.html file:

<script src="web2/Libs/linqjs/linq.min.js"></script>
  • adding this code below the Vidyano import will also get rid of the deprecation warning in version 2.

Migrating away from linq.js

Enumerable.from

Can be omitted in most cases as the argument will most likely be an array already. In case the argument is for example a DOM NodeList, you can use the native Array.from() function instead:

// Array.from for DOM NodeList
Array.from(this.querySelectorAll(".item")).forEach(item => ...

Array extensions

// distinct
arr.distinct<T, U>( selector?: (element: T) => T | U): T[];

// groupBy
arr.groupBy<T>(selector: (element: T) => string): KeyValuePair<string, T[]>[];
arr.groupBy<T>(selector: (element: T) => number): KeyValuePair<number, T[]>[];

// orderBy
arr.orderBy<T>(selector: (element: T) => number | string): T[];
arr.orderBy<T>(property: string): T[];

// orderByDescending
arr.orderByDescending<T>(selector: (element: T) => number): T[];
arr.orderByDescending<T>(property: string): T[];

// min
arr.min<T>(selector: (element: T) => number): number;

// max
arr.max<T>(selector: (element: T) => number): number;

// sum
arr.sum<T>(selector: (element: T) => number): number;

Enumerable.SelectMany

SelectMany can be easily written with native JavaScript:

// E.g. select all detailLines from lines
[].concat(...this.lines.map(d => d.detailLines))

What's New

Tags

You can now add the inputtype=tags typehint to a MultiString attribute to display the strings as tags.

is-ie attribute on vi-app

This attribute is now added automatically to the vi-app element so the child elements can more easily add custom styles via :host-context specifically for Internet Explorer.

Grouping

These screenshots are from older versions, some minor UI changes can be expected

An opt-in feature that should be enabled per attribute that an user can group on. Will allow them to see the query grouped by the attribute that they selected instantly showing the count per group and the ability to collapse those groups.

You can use this feature by setting the “Can group by” to Yes on the attribute.

Users can then use the context menu on a query grid to enable grouping for that attribute.

Service

v6 release notes

5.45.0+26864bd

Monday, February 10th, 2020

Features

  • Added Advanced search save-load

Changes

  • Always include name in ProgramUnitItemGroup based on DefaultLanguage

  • Trim user name that is passed to GetCredentialType

  • Expose friendly information when a FaultException is thrown in GetCredentialType/HandleUnknownAuthenticate

Fixes

  • Fixed issue with ExportToExcel not respecting custom column order from user

  • Fixed NullReferenceException when starting a legacy Vidyano application

  • Fixed NullReferenceException when searching on a reference attribute that uses breadcrumb attributes that aren't available (i.e. because they are hidden or disabled through security)

  • Fixed issue with ExportToExcel on Advanced search not returning any rows

  • Fixed issue with verbose logging not working if repository cache failed to load

5.33.1+12ad63a

Thursday, February 22nd, 2018

Features

  • Added support for Pwned Password list (https://haveibeenpwned.com/Passwords) to automatically block breached passwords

  • Added IsSensitive flag on attributes and IsBreadcrumbSensitive on Persistent object

  • Added support for tsv format and $where filtering on reports

  • Added IExceptionService.LogWarning to log catched exceptions as Warning

  • Added IUser.HasPassword/RemovePassword/ResetPasswordNextLogin

Changes

  • Made SwaggerPathAttribute Path and Operation property public

  • Prefer alwaysStrict tsconfig

  • Skip new PO in synchronize if type already exists in another schema

  • Also log api method name in verbose logs

  • Made PersistentObjectAttribute.ToolTip setter public

  • Manager.GetUser returns null for null or empty name

  • Allow setting of response headers when serving index.html

Fixes

  • Fixed issue with rights for query not being removed when query was deleted

  • Fixed issue with enum options not showing for nullable enum

  • Fixed issue with IsNew PO not resetting attributes when an AsDetail attribute failed

  • Fixed issue with PersistentObjectAttributeWithReferenceBuilder not saving

  • Fixed issue with Security page when Register PO is deleted

  • Fixed ArgumentNullException when saving new User without setting username

5.36.0+5338103

Tuesday, April 17th, 2018

Features

  • Added GetTextSearchColumnsForValue method to limit the columns to use depending on the searched value (i.e. 2018 should only search on dates and not numbers)

  • Added ApiMethodAttribute.TryAuthentication property to allow Basic authentication on the api method if supplied, otherwise the api method can still use custom authentication logic

  • Require two-factor code (if configured) for changing password or removing two-factor

  • Added scim hook for filtering users/groups

  • Added AuthenticatorService.GetCredentialType for new sign-in experience (includes new messages)

Changes

  • Changed default expiration for authtoken from 2/28 days to 12 hours/2 days

  • Changed throttle behavior for invalid passwords (defaults to 5 tries per window of 30secs and a maximum of 5 failed attempts per 10 minutes per user/ip combination)

  • Added basic retry logic when logging verbose logs fails (connection closed, keep-alive issues)

  • Expose WebsiteArgs.ResponseHeaders/HttpStatusCode

  • Allow custom SqlErrors (error number >= 5000) to be shown to the end-user

  • Verify two-factor code after validating password

  • Improved cleaning up vidyano logs (allows usage of index on DB)

  • Don’t get computed attribute value for IsNew PO when attribute doesn’t have New visibility

  • Expose IBuilder.GetPersistentObject/GetQuery (returns null if it doesn’t exists yet)

  • Allow CustomActions classes to be discoved in a different namespace

Fixes

  • Fixed issue with authenticate not updating internal caches first

  • Fixed issue when double-saving new feedback (following saves are skipped when ObjectId is not empty)

5.35.5+2316022

Friday, March 30th, 2018

Features

  • Vidyano will now throw a SaveFailedException inside SaveExisting/SaveNew/SaveDetails when it has an error

  • Added computed attributes that can be translated to database calls

  • Added grouping on attributes

  • Added IncludeCount on ProgramUnitItem to send query count to client during GetApplication

Changes

  • Still send profiler information (if allowed) when an exception occures

  • Don’t send SqlException information to client for non-admins, instead a generic database error message is shown with a log id

  • Use ObjectEx.GetSecureRandomString for SecurityToken salt

  • Allow HEAD method on /Poll endpoint

  • Added localNameSelector parameter for PieChart.WithValues

  • Expose Manager.WebsiteRoot property

Fixes

  • Fixed issue with verbose logging to sql server not trimming data correctly

  • Fixed issue when incoming user name not trimming correctly

  • Fixed issue when using ResetPasswordNextLogin for a user without a password (e.g. for a new user)

  • Fixed issue with instant search input not trimming correctly

  • Fixed security issue that might leak profiler information on invalid requests

  • Fixed issue with verbose logging only showing 101 records when db is used

  • Fixed issue when VPD Where handler assumed IEnumerable

  • Fixed issue with Edge displaying MultiLineString/CommonMark as “null” on New entity

  • Fixed issue with ComputeTotalItems not working for PageSize=0

5.22.1+557c11

Friday, July 28th, 2017

Features

  • Added ability to skip data security when using GetEntity/FindByObjectId

  • Added Advanced.BlacklistedPasswords per Microsoft Password Guidance for improved security

Changes

  • Show more information when repository metadata upgrade failed

  • Updated XML comments to indicate that GetEntity/FindByObjectId will use data security

  • Don’t use data security when loading entity for read-only reference attribute

  • Increased default BCrypt complexity to 13 (will rehash passwords on first use)

  • Changed default Password Complexity setting for new projects to None as per Microsoft Password Guidance

Fixes

  • Fixed issue with distincts on lookup query showing wrong values

  • Fixed issue with new CheckDetailRules failing for New entity

  • Fixed issue with Random not being thread safe

  • Fixed issue with circular group memberships

5.40.2+2a48896

Thursday, November 8th, 2018

Features

  • Added Advanced.IsSettingValueValid method to allow checking of value for custom settings

  • Added PersistentObjectAttribute.SetValueWithRefresh method that will also call the OnRefresh code if needed

  • Added (Async)CustomAction.IsAllowed method

  • Added Manager.HasRepositoryConnection helper method to see if the repository database is available

  • Added PersistentObject.LoadingStrategy to determine how the entity should be loaded from the database

  • Added IQueryable.FilterByObjectId method to apply the correct Where filtering including data security

  • Added PersistentObjectAttribute.OptionalColumn to indicate that the column should not be shown by default

  • Added extra hooks for SAML 2.0 processing (DetermineSaml20Information and GenerateSaml20RedirectUrl)

  • Added viAudit feature that is avaiable for Administrators when verbose logging is enabled

  • Added Advanced.IsTrustedProxyIpAddress to allow X-Forwarded-For header for trusted proxy/load balancer/forwarded

  • Added WebsiteArgs.Redirect method to redirect the page load to another location (e.g. SSO portal)

Changes

  • Swallow “Cannot access a disposed object” exception for System.Web.WebSockets.AspNetWebSocket

  • Persist changed attributes after unchanged to entity

  • Also profile CreateUserSession

  • Replace authToken in verbose logs with just the expiration info

  • Use Setting.DataType as configured in repository (i.e. for Password the content will be hidden)

  • Allow custom queries from base PersistentObjectActions class

Fixes

  • Fixed issue on Diagnostics page when GenerateMessage would throw an exception (e.g. validation error)

  • Fixed issue with NullReferenceException when calling Manager.WebsiteRoot, will return null now instead

  • Fixed issue with new RepeatingJob not triggering

  • Fixed issue in ReplaceSensitiveUrlPart when Url is empty

  • Fixed issue where data from removed columns in QueryExecuted were still being sent to the client

  • Fixed issue when saving a new DataType when the project doesn’t have templates in the model.json

  • Fixed issue where unique index violation would also list included columns from index

5.26.2+bccf416

Wednesday, October 11th, 2017

Features

  • Added ability to define default/cancel button index for RetryAction

Changes

  • Strip comments from vulcanized html for Web2

  • Made ImageUtils class public (can validate image, generate QR code, …)

  • Don’t show New Query UserRight on Custom Actions’ Rights query

  • Show more information from DbUpdateException and UpdateException in GenerateMessage

  • Changed logic for converting decimal to service string to match client

  • Updated signature of RefreshOperation.Query to reflect that it can be either a name or an id

  • Refresh queries with user rights when saving or deleting user right

  • Changed WebController.GenerateLocalAuthToken to use same long expiration as staysignedin

Fixes

  • Fixed issue when ACS was used through proxy

  • Fixed issue with user created through CreateNewUser not loading groups

  • Fixed issue with AutoCreateUsersInGroup having invalid options after a refresh

  • Fixed a caching issue when a group is renamed that an user can’t be added to that group

  • Fixed NullReferenceException when using HttpHandler directly with WebController with null HttpContent in request

  • Fixed issue with Manager.DeleteUser

  • Fixed issue with ExecuteAction through form using 100% cpu for very large requests in chrome

5.38.3+a697611

Thursday, July 26th, 2018

Features

  • Redirect to Web2 CDN by default instead of proxying requests, will allow browser cache and even between different Vidyano applications

  • Cache vulcanized requests for better performance

  • Made all requests handling async

  • Make use of the System.Buffers package if it is added to a Vidyano application

  • Added advanced hook for handling cache updates manually (Advanced.OnCacheUpdateSaved)

  • Added PersistentObject.DialogSaveAction to use a different action for the default Save button on dialogs

  • Added AsyncCustomAction class to have a complete async flow in custom actions

Changes

  • Calling Manager.DeleteUser with the name of a group will no longer delete the group

  • Keep working with in-memory cache when CDN is not available and CDN is being proxied

  • Expose more information on SCIM /Groups

  • Parent PersistentObject’ ObjectId/IsNew is now set before the lookup quueries are executed on reference attributes

  • Removed unused version override support on web2 endpoint

  • Added more logging information when an exception is thrown during ExecuteQuery

  • Implemented SCIM PUT /Users/ID

  • Allow SCIM PUT/PATCH /Users/ID to change user.Name

  • Added Vidyano.AuthTokenExpirationDevMultiplier appSetting for longer expiration during development

  • Keep ObjectId from newly saved entity in verbose logs

  • Added Reload/SessionLost client messages

  • Copy all attributes values to entity when saving new entity

  • Added detection for .NET 4.7.2 in diagnostics page (and fixed detection of 4.7.1)

  • Missed the notificationDuration parameter in some methods

  • Append random data to verbose log rowKey to reduce duplicates

  • Allow patch import to be used on already patched data

Fixes

  • Fixed issue when deleting empty group

  • Fixed issue with TLS 1.1/1.2 not being used for external requests

  • Fixed issue when invalid model.json was loaded

Instant Search

Vidyano has the ability to set any Query as Global Search which allows the end users to use a simple text search in the menu that will search through all the queries and shows those queries that returned results (or errors). This has given us an easy way to provide this kind of functionality to users.

The only problem has always been performance, the application will launch a full text search for all columns on all the queries that are defined as global search so the total time to give results has always been in the 1-10 seconds range.

Instant search has been written from the ground up with performance as the number one priority.

Example

Instant search is implemented as an advanced feature, it is up to the developer to keep the total processing time as low as possible (recommended <100ms).

public sealed class MyCRMAdvanced : Advanced
{
    public MyCRMAdvanced()
    {
        InstantSearchDelay = TimeSpan.FromMilliseconds(200d);
    }

    public override TimeSpan? GetInstantSearchDelayForUser(IUser user)
    {
        if (user.IsMemberOf("CantUseInstantSearch"))
            return null;

        // Fallback to default
        return base.GetInstantSearchDelayForUser(user);
    }

    public override void HandleInstantSearch(InstantSearchArgs args)
    {
        args.MaxResults = 12; // Defaults to 15 results

        var user = Manager.Current.User;
        if (user.IsMemberOf("Administrators"))
        {
            // Some built-in searches specific for application admins
            args.AddPersistentObjects();
            args.AddUsers();
        }

        using (var context = new MyCRMEntityModelContainer())
        {
            var search = args.Search; // The search query from the end user

            // The first parameter specifies the PO type name
            args.AddRange("Customer", context.Customers
                .Where(it => it.FirstName.Contains(search) || it.LastName.Contains(search))
                .Select(it => new
                {
                    ID = it.CustomerID,
                    it.FirstName,
                    it.LastName
                })
                .Take(4)
                .AsEnumerable()
                .Select(it => new InstantResult(it.ID.ToServiceString(), it.FirstName + " " + it.LastName)));
            // The instant result determines the ObjectId and Breadcrumb to display the result in the menu

            // For single property lookups we have an easy helper method
            args.AddRange("Product", context.Products, it => it.ProductID, it => it.Name, 4); // 4 max results instead of the default 5
            args.AddRange("ProductCategory", context.ProductCategories, it => it.ProductCategoryID, it => it.Name, 4);

            int number;
            if (int.TryParse(search, out number)) // We can skip certain tables if the search input doesn't match the expected input
            {
                args.AddRange("SalesOrderHeader", context.SalesOrderHeaders
                    .Where(it => it.SalesOrderNumber.Contains(search))
                    .Select(it => new
                    {
                        ID = it.SalesOrderID,
                        it.SalesOrderNumber
                    })
                    .Take(4)
                    .AsEnumerable()
                    .Select(it => new InstantResult(it.ID.ToServiceString(), it.SalesOrderNumber, it.SalesOrderNumber.IndexOf(search, StringComparison.OrdinalIgnoreCase) - 2)));
                // The SalesOrderNumber column is a computed column ("SO" + ID) so we decrement the score by 2 for better ranking results
            }
        }
    }
}

Which will give the following experience to the end user, as seen on demo.vidyano.com

5.24.0+a20f7c

Thursday, August 31th, 2017

Features

  • Added the PreviewLoadArgs.HandleAsNew method to convert a load to a new

  • Made the auth token expiration time in days configurable (app settings Vidyano.AuthTokenExpiration and Vidyano.AuthTokenExpirationStaySignedIn)

Changes

  • Never use Web2 CDN if Vidyano.Web2 NuGet package is installed

  • Made PersistentObjectAttributeWithReference.SelectInPlace property public

  • Always detect classes with ComplexTypeAttribute as being mapped to the database

  • Changed the Security page to be more compact using a single Authentication Providers attribute

  • Disabled sliding expiration on auth tokens by default, old behavior can be enabled using the Vidyano.SlidingAuthToken app setting

Fixes

  • Fixed issue with ?id=X not working for api methods

  • Fixed issue with distinct values for translated string attributes

  • Fixed issue with DefaultPersistentObjectActions being used for internal Vidyano objects

5.25.3+8224b3b

Tuesday, September 19th, 2017

Features

  • Added extra per PO unique salt for securing the SecurityToken

  • Added support for using anonymous types in custom queries

  • Added default MinLength business rule

  • Added support for per query user rights to give user/group access to a single query instead of all queries of a persistent object

  • Added Manager.UpdatableLog method to get a Vidyano log entry that can be changed

  • Added Advanced.PreNewFeedback hook for adding extra attributes on New Feedback

  • Added PO/Query ActionLabels property to override label for specific action

  • Added Advanced.OnLogin hook to show extra PO to end-user before the application can be used

  • Added Report.Description for optional description

  • Added a Profile detail query on User to see the current profile values

  • Added caching of all Web2 CDN resources when a version is requested (can be disabled using the Vidyano.DisableWeb2CdnPreload appSetting)

Changes

  • Made ObjectEx.GetSecureRandomString public

  • Made some changes to action based user right (new NewDelete combined right, grouped rights, …)

  • Made Report.Token optional, a report without token is disabled

  • Made email sending part of Feedback asynchronous

  • Don’t allow Impersonate for Groups

Fixes

  • Fixed issue with non-sliding auth token not working on ADFS via ACS

  • Fixed incompatibility issue with .NET standard System.Net.Http package when receiving large request

  • Better detection for mapped code first entities

5.18.0+de3495

Features

  • Moved legacy old Web code to separate Vidyano.Service.Legacy NuGet package

Fixes

  • Fixed issue with web2 vulcanize resource when it only has read access

  • Fixed issue with Regenerate token action being shown on read-only report

  • Fixed issue with Read right being required for OnPreviewAction

5.12.0+66cbb5

Friday, March 17th, 2017

Features

  • Added Instant Search feature (example)

Changes

  • Tweaked new website template to use https:// for google fonts url

  • Tweaked new webcomponent template to remove obsolete content tag

5.10.2+a3acd1

Thursday, March 2nd, 2017

Features

  • Introduced local cache for Web2 CDN requests and informing of client that the version has changed (can be disabled using the Vidyano.DisableClientVersionCheck app setting)

Changes

  • Updated CommonMark.NET NuGet dependency to 0.15.1

Fixes

  • Fixed issue with select all not working when adding queries to program unit

3.0

Coming soon

Vidyano.Core

The Vidyano.Core NuGet package is a powerful tool designed for developers who want to connect to their Vidyano applications using C#.

Available on GitHub, it offers seamless access for creating custom integrations or performing unit tests, ensuring enhanced functionality and reliability in your applications.

5.13.1+c8fdb1

Wednesday, March 22nd, 2017

Features

  • Added per website setting for Web2 version

Fixes

  • Fixed issue with NoDatabase mode not synchronizing repository changes

5.16.0+aae2a8

Friday, April 21st, 2017

Changes

  • Improved performance when searching verbose logs on Created on

  • Handle searching on User/NullableUser attributes

  • Marked ConvertFromCommonMark as obsolete

  • Allow sorting of attribute with unsortable data type when using sort expression

Fixes

  • Fixed DbSort clause issue when sorting on reference attribute that isn’t visible

5.17.0+aaa255

Thursday, April 27th, 2017

Features

  • Added ability to search for last X days on date(time) attributes (using Last 2 days would return all records for today and yesterday)

  • Added action on reports to regenerate the token to a new random secure token

  • Added ability to return distinct values in descending order (by setting the args.Ascending to false in the GetDistinctValues method)

  • Added action to restart the Web App on the Client -> Websites query in the Management part

Changes

  • Added extra features to AzureStorageConnector (working with Content-Type, Content-Encoding and generating Shared Access Signature)

  • For pure Web2 applications it is now possible to remove the Templates array inside the model.json

  • Removed developer only actions from Management part from model.json (eg Synchronize, RefreshFromTranslations, StartStop, …) to reduce merge conflicts

  • For security reasons we no longer send or require an auth token for the default user of an application

  • Show the property name when we couldn’t sort on an unmapped property

Fixes

  • Fixed issue when deploying a new website to Azure that the bindings would be lost

Legacy v5.x

Old documentation for Vidyano v5 which was based on .NET full framework (4.8).

Some content will still be relevant but all parts related to ASP.NET (i.e. HttpResponseMessage or HttpContext) will need to be rewritten with ASP.NET Core alternatives (i.e. IResult, IActionResult, HttpRequest, ...). All parts related to EntityFramework6 syntax will need to be rewritten using EFCore syntax (mostly namespace changed).

Architecture

This information is still applicable to v6

Groups / Rights

Functionality is driven by giving groups (application roles) specific rights to actions (e.g. Save, New, Delete, Print, Export, …). Without an explicit right for an action the user won’t be able to execute the action and the backend will throw an exception.

Query, Read, Edit and New rights can be defined at the attribute (property/column) level, all other actions can be defined at the persistent object (class/table) level.

Authentication

Vidyano application can be configured to allow multiple authentication sources which all map to users that can be assigned to groups.

Vidyano Authentication

Enabled by default, allows the usage of any custom name and password to log in. Passwords are stored in the database using a BCrypt hash. The BCrypt complexity can be configured and is increased by the framework on regular intervals (currently at 13). Will require a password with at least a length of 8 and it should not be in the blacklist (currently a list of the top 25 worst passwords and Pwned Passwords using the V2 range API), can be configured to use a different length, complexity (no longer recommended) or a different blacklist.

Password requirements are based on Microsoft’s Password Guidance and NIST’s Digital Identity Guidelines

The https://haveibeenpwned.com/Passwords service is used to validate that passwords don’t appear on any leaked password list. The new V2 api is used with the Range API to provide k-anonymity to check for breached password without disclosing the actual password (or even a full hash of it).

ADFS Authentication

Allows the use of an Active Directory Federation Service to authenticate the user in the application. The application can be configured to automatically put unknown authenticated users in specific group or this logic can be handled using code (to put the user in a specific group based on the returned claims). Will use the http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname claim as user name.

OAuth Authentication Providers

Allows the use of an OAuth provider (Microsoft, Google, Facebook, Twitter or Yammer) to authenticate the user in the application. Can also be configured to automatically create unknown users. Will use the email as user name.

Auth Token

Once the user has been authenticated the server will return an auth token to the client that can be used for the next requests.

The token is a SHA256 hash composed of the following information:

  • Application salt

  • User name

  • User version (incremental number in the database that is increased when the password is changed or the user is disabled)

  • Expiry date and time

  • IP address

  • Optionally the original user when an user is impersonated

The actual IP address to check can be configured to allow switching between addresses within a specific cidr range in case the users have multiple external IP address or it can be changed using code.

public override string GetClientIpAddress(string userName, string ipAddress)
{
    if (userName == "salesguy") // NOTE: Always on the road
        return Manager.Current.GetUser(userName).Profile["AuthTokenSalt"];

    return base.GetClientIpAddress(userName, ipAddress);
}

Security Token

To ensure that the client works with the correct model (based on the rights) the server will generate a security token that can be checked when the persistent object is send back.

The token is a SHA256 hash composed using the following code:

private string GetTamperingDetectionToken(string securityToken)
{
    string salt;
    if (securityToken == null)
        salt = ObjectEx.GetSecureRandomString(6);
    else
        salt = securityToken.Substring(1, 8);

    var allData = new StringBuilder();
    allData.AppendLine(salt);
    var poSalt = VidyanoDbCache.Default.GetPersistentObject(Id, false)?.Salt;
    if (poSalt != null)
        allData.AppendLine(poSalt);
    allData.AppendLine(Id.ToString());
    allData.AppendLine(ObjectId);
    if (BulkObjectIds != null)
        BulkObjectIds.Run(id => allData.AppendLine(id));

    Attributes.OrderBy(a => a.Id).Run(attr =>
    {
        allData.AppendLine(attr.Id.ToString());
        if (attr.IsReadOnly && !attr.Name.Contains("."))
        {
            var attrWithReference = attr as PersistentObjectAttributeWithReference;
            allData.AppendLine(attrWithReference != null ? attrWithReference.ObjectId : attr.Value);
            allData.Append(attr.IsValueChanged);
            allData.AppendLine();
        }
    });

    allData.AppendLine(SecurityScope.ApplicationSalt);
    var result = allData.ToString().GetSHA256();
    return "$" + salt + result;
}

The token uses a random salt to prevent any oracle attacks. All information is included so that the client can only modify the entity as it was sent by the server with only the attributes that were available. For attributes that the user has no edit rights the actual value is also used in the token so that the application can securely set these attributes on the server-side. Trying to modify any of the data will result in an exception being thrown by the backend.

Force Https / TLS 1.2

Vidyano will automatically redirect to https:// for certain subdomains (azurewebsites.net, apphb.com, …) and can be configured with a simple appSetting for custom domains. Enabling this flag will also enable HSTS (Strict Transport Security) which tells the browser to always go the https:// site directly even if the user tries to go to http:// to block MITM attacks.

Depending on the deployment it is recommended to only allow TLS 1.2 if possible. This can be enabled for Microsoft Azure App Services on the SSL settings tab:

Two-factor authentication

Each user can set its own two-factor code on the user settings page (available using the gear in the lower left of the application). The application can also be configured to require (force) two-factor authentication for users that are in a specific group (e.g. Administrators).

Tutorial 1: Your first application (Legacy)

This is kept for informational purposes as this no longer applies to v6 projects.

Prerequisites

Downloads

Vidyano Sample database

Installation

Extract and run the Sample database installer to deploy the database to your local SQL Server. This tutorial assumes that you keep the default name for the database "MyCRM".

Tutorial

Step 1: Creating a new Vidyano Project

Launch Visual Studio 2017 or higher. From the File menu, select New ▶ Project.

From the templates on the left, select Visual C# ▶ Vidyano.

Choose Vidyano Application,give your project a Name and click OK.

Step 2: Name and protect your new application

Give your App a Title and provide an administrative Password to secure the backend. Make sure to pick a secure password.

Click Next to continue.

Step 3: Select the target database

On this wizard step you need to select the database that holds your application data.

Click on the Browse (...) button next to the Connection String input field.

Select the MyCRM database that you installed in the prerequisites step of this tutorial.

Click OK.

Click Next to continue.

Step 4: Select the repository database

Vidyano will store some metadata in repository tables. Select the database where these tables will be created.

By default the connection string above will be the same as your target database, in this case MyCRM.

Optionally you may also select a separate database to store the repository data as to not make any changes to the target database that may be used by other applications.

Click Go ! to continue.

Step 5: Let Vidyano set up your application

At this point, Vidyano is setting up your project.

During set up you might get a message dialog asking if you want to search for TypeScript typings, you can select No.

Step 6: Launch your newly created application and sign in

Press ctrl+f5 or click the ► button on the toolbar to start your application.

Use admin as your user name and the password you entered in step 2.

Step 7: Add a menu item

Now that you are in, let's add a menu item to see the customers on the database.

On the menu, click Add menu item.

Select Customers and click OK.

Wait for the application to reload and click Customers.

Congratulations, you just created your first Vidyano application!

Allow user registration

This information is still applicable to v6

On the Security page you can configure the PO that should be shown and the application group for the user rights. For most applications the Register PO will be a new virtual Persistent Object with at least an email address (username) and password attribute. The following code shows an example POActions class for the Register PO.

Computed attributes

This information is still applicable to v6, for the code snippet, the CustomerActions class should be partial and the base type is not needed.

Computed attributes can be used as an alternative to views but still keep the same performance. By defining the expression for a computed attribute Vidyano can make sure that the expression is translated by Entity Framework to the correct sql code.

Usage

Computed attributes should be defined in the static Initialize method of the specific Persistent Objects’ Actions class and added manually as an attribute with the correct mapping. All computed attributes will automatically be flagged as read-only.

Once configured they can be used to sort, filter (with distincts) or group on. They can be shown in a query and when loading existing entities.

Remarks

Only valid EntityFramework expression can be used if the code needs to be translated to sql (e.g. no custom extension methods)

public class RegisterActions : PersistentObjectActionsReference<AdventureWorksEntityContainer, object>
{
    public override void OnSave(PersistentObject obj)
    {
        if (!CheckRules(obj))
            return;

        var userName = (string)obj["Email"];
        if (Manager.Current.GetUser(userName) != null)
            throw new FaultException("That user already exists. Use the forgot password functionality to reset your password.");

        Manager.Current.CreateNewUser(userName, (string)obj["Password"], profile: new Dictionary<string, string> { { "Registered", "self" } });

        obj.AddNotification("You can now login using your email and password.", NotificationType.OK);
    }
}
public class CustomerActions : PersistentObjectActionsReference<AdventureWorksEntityContainer, Customer>
{
    public static void Initialize()
    {
        SetComputedAttribute("OrdersCount", entity => entity.SalesOrderHeaders.Count);
        SetComputedAttribute("OrdersTotal", entity => entity.SalesOrderHeaders.Select(h => h.TotalDue).DefaultIfEmpty().Sum());
        SetComputedAttribute("FullName", entity => entity.FirstName + " " + entity.LastName);
    }

Actions classes

This information is still applicable to v6

This page provides some common scenarios to use the PersistentObjectActions class.

Soft Delete

By default, Vidyano will tell Entity Framework to delete an entity from the target context when the end user uses the Delete action. We can override this behavior so that the entity is not actually deleted but only marked as deleted (e.g. disabled, not active, obsolete, …).

protected override void MarkForDeletion(Customer entity)
{
    //Context.DeleteObject(entity);
    entity.IsActive = false;
}

Cascade Delete

Another common scenario is to delete related entities when a master entity is deleted (in most cases this can also be done on the database using cascade delete on the foreign key).

protected override void MarkForDeletion(Customer entity)
{
    Context.DeleteObject(entity);
    foreach (var address in entity.Addresses)
        Context.DeleteObject(address);
}

Auditing

Setting audit attributes can be easily implemented by using the UpdateEntity method to set the attributes.

protected override void UpdateEntity(PersistentObject obj, Customer entity)
{
    if (obj.IsNew)
    {
        obj[nameof(Customer.CreatedOn)] = Manager.Current.Now;
        obj[nameof(Customer.CreatedBy)] = Manager.Current.User.Id;
    }
    obj[nameof(Customer.ModifiedOn)] = Manager.Current.Now;
    obj[nameof(Customer.ModifiedBy)] = Manager.Current.User.Id;

    base.UpdateEntity(obj, entity);
}

External services

If we need to inform another external service about new or updated entities we can use the following code.

protected override void PopulateAfterPersist(PersistentObject obj, Customer entity)
{
    base.PopulateAfterPersist(obj, entity);

    var wasNew = obj.IsNew; // The entity will have the correct DB primary key at this moment
    ExternalService.PostCustomer(wasNew, entity);
}

Forgot password

This information is still applicable to v6 but the code snippets on [Schema]Web need to be updated to return an IResult instead of HttpResponseMessage and Results.XX methods should be used instead of args.Request.CreateXX, i.e. Results.Problem and Results.Redirect

When working with email addresses as user names we can use this information to provide a forgot password functionality. By implementing the HandleForgotPassword method on the Advanced class you’ll get the “Forgot password?” button on the Sign in screen.

Best practice recommends for generating a random reset password token (ObjectEx.GetSecureRandomPassword can be used), storing the token in the user profile, emailing the user a link that triggers an api method that will verify the token and user combination. At that moment you can use the IUser.ResetPasswordNextLogin() method to trigger a password change on next login and redirecting the user to an existing logged in session.

// In [Schema]Advanced.cs
public override void HandleForgotPassword(ForgotPasswordArgs args)
{
    // NOTE: Always inform for success so that we don't leak information about users
    args.Notification = "An email has been sent with all the information to reset your password.";

    var user = Manager.Current.GetUser(args.UserName);
    if (user == null)
        return;

    var userHostAddress = Manager.Current.RequestMessage.GetClientIpAddress();
    var token = user.Profile["ResetPasswordToken"];
    if (string.IsNullOrEmpty(token))
    {
        token = ObjectEx.GetSecureRandomString(16).Replace("-", null).Replace("_", null);
        user.Profile.SetValue("ResetPasswordToken", token);
    }

    var location = Manager.Current.WebsiteRoot + $"api/ResetPassword?UserName={user.Name}&Token={token}";
    // TODO: Send email to user with information (password change requested, from ip, location to click, ...)
}
// In [Schema]Web.cs
public HttpResponseMessage ResetPassword(ApiArgs args)
{
    var userName = args.Query["UserName"];
    var token = args.Query["Token"];
    if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(token))
        return args.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid request");

    var user = Manager.Current.GetUser(userName);
    if (user != null && token == user.Profile["ResetPasswordToken"])
    {
        user.Profile.SetValue("ResetPasswordToken", null); // NOTE: Resetting the token can be a problem if the user doesn't complete the change password prompt
        user.ResetPasswordNextLogin();

        var encodedUser = Convert.ToBase64String(Encoding.UTF8.GetBytes(user.Name)).Replace('/', '_');
        var authToken = WebController.GenerateAuthToken(user.Name, DateTimeOffset.Now.AddDays(28), Manager.Current.RequestMessage.GetClientIpAddress()).Replace('/', '_');
        var response = args.Request.CreateResponse(HttpStatusCode.Redirect);
        response.Headers.Location = new Uri(Manager.Current.WebsiteRoot + "#!/SignInWithToken/" + encodedUser + "/" + authToken);
        return response;
    }

    return args.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Invalid request");
}

Overriding Vidyano Settings

This information is still applicable to v6 but you should use the .NET core related configuration providers, so any of the configured providers can be used (appsettings.json, environment variables, user secrets, ...), the key to set will be Vidyano:Setting e.g. Vidyano:MicrosoftTranslatorAppId

Vidyano has always used 2 places to store settings, the web.config appSettings section for settings that required the application to restart, and the Settings repository table for settings that can be changed at runtime.

This new feature allows you to override the settings stored in the Settings repository table using the web.config appSettings section. This can be used for system settings and custom settings.

<appSettings>
  <add key="Vidyano.MicrosoftTranslatorAppId" value="xxx" />
</appSettings>

Overridden settings will be shown as read-only in the Management part.

5.20.0+95f4d1

Wednesday, June 7th, 2017

Features

  • Added ability to define Register PO and handle Forgot password logic (Register / Forgot password)

Changes

  • Changed ToServiceString for enums to use default ToString (allows undefined enum values to be converted to numeric value)

Fixes

  • Fixed issue with Verbose logs filtering

Azure AD SAML based Sign-on

This information is still applicable to v6

Vidyano can be configured to use Azure AD as an authentication provider. In combination with the SCIM feature to provision the users and groups you could use the Azure AD to completely control your users outside the application.

Configuration

You’ll need to set the Single Sign-on Mode to SAML-based Sign-on.

For the URLs you should use the url of the application for Identifier and followed by Authenticate/SAML2 as Reply URL:

The Configure button at the bottom will give your the correct settings (SSO Service Url and Base64 Certificate) that you need to use on the Security page.

Best Practices

This information is still applicable to v6

If your deployment allows for configuring settings outside of the source code (e.g. the Application Settings tab on Microsoft Azure App Services) then this can be used to configure a set of application settings that even the developers won’t have access to.

  • Configure another ApplicationSalt (via the Vidyano.ApplicationSalt app setting)

  • Set the DiagnosticsToken to a secure random string (Vidyano.Diagnostics token)

  • Use a different set of credentials/connection string for the database

  • If configured, use another connection string for verbose logging

  • Enable the Vidyano.ForceHttps app setting to enable HSTS

  • Configure TLS 1.2 to be the minimum TLS version

  • Disable the admin user and use another named user that is an administrator or make sure that the password is strong (and different from development)

Automation protection

If the web app is available on the public internet and can be accessed without logging in (e.g. contact form) you should provide extra protection against abuse. This can easily be done by using the Google reCaptcha library to validate the request.

External Audit

Vidyano has been externally audited by The Security Factory, you can read the report here.

“Overall Security Posture

Based on our experience we would rate the security posture of the application in the higher regions of good security.”

SCIM 2.0 Service Provider

This information is still applicable to v6

The SCIM standard was created to simplify user management in the cloud by defining a schema for representing users and groups and a REST API for all the necessary CRUD operations. See spec

Example

To enable the SCIM end-point Vidyano will require an authenticated request, the passed bearer token will need to be checked in the AuthenticatorService class. The easiest way would be to provide a single custom setting that contains the token so that it can be checked.

public override bool CheckScimCredentials(ScimCredentials credentials)
{
    var setting = Manager.Current.GetSetting("ScimBearerToken");
    return !string.IsNullOrEmpty(setting) && setting == credentials.Token;
}

Other scenarios could be created by storing multiple tokens in the database.

Azure AD

Azure can be configured to sync the AD users with your Vidyano application: Using System for Cross-Domain Identity Management to automatically provision users and groups from Azure Active Directory to applications

Tenant URL is the url of the application (e.g. for the demo application the tenant url is https://demo.vidyano.com/ )

For the mapping we recommended the following settings: Groups

Users

5.21.1+923828

Tuesday, July 18th, 2017

Features

  • Added ability to disable computing of total items for queries, e.g. skipping the extra count call to the database

  • Added ability to disable implicit rights for Administrators group allowing for easier testing of rights (e.g. <add key="Vidyano.NoImplicitAdminRights" value="True" />)

  • Added ability to define cidr ranges for less strict ip check by setting the Vidyano.CidrRanges app setting (e.g. <add key="Vidyano.CidrRanges" value="192.168.0.0/16;127.0.0.0/24" />)

  • Added Duplicate dev action on User right and Report

  • Always log exceptions on Api methods

  • Added ability to create new users with settings directly and flag to throw exception if user already exists

  • Updated available type hints on attributes for Web2 client

  • Added .NET 4.7 detection on diagnostics page

  • Added some reserved words for program unit names to prevent conflicts with routes

  • Added new CheckDetailRules method on PersistentObjectActions that will called before trying to save a detail PO

Changes

  • Throw exception during OnLoad if entity is not found instead of setting notification

  • Don’t store new group in patches, added notifications to describe recommended behavior

  • Updated DocumentFormat.OpenXml NuGet dependency to 2.7.2

  • Updated Newtonsoft.Json NuGet dependency to 10.0.3

  • Made CommonMark dependency optional, if you are not using this you can remove the NuGet dependency

Fixes

  • Fixed issue when deleting PO that had detail queries on another PO

  • Fixed issue when using <= on date time

  • Fixed issue when using space to separate hour part

  • Fixed issue with invalid sort options for reports

  • Fixed issue when saving query filters with NoDatabase enabled

  • Fixed issue when a native client connects to a production environment

  • Fixed issue with custom query that has another entity type not using DefaultPersistentObjectActions class

  • Fixed issue when adding a new language and no microsoft translator key was defined

  • Fixed issue when renaming Persistent Object on an application with verbode model enabled

6.0

Coming soon

5.6.4+151e2e

Wednesday, January 18th, 2017

Features

  • Added PO.RemoveAttribute/AddQuery/RemoveQuery

  • Added support for Cognitive Services token for translations

  • Added ITranslationService

  • Added ?lang=CODE support for reports

Changes

  • Use IDbSet/IObjectSet to discover context property

  • Increased entropy for new report tokens

Fixes

  • Fixed issue with new language being kept on error

  • Fixed allow bulk-edit detection when working with base POActions class

  • Fixed issue with new Data Type not saving correctly

  • Fixed issue when using bulk-edit on Users

  • Fixed issue when using bulk-edit on UserRights

  • Fixed issue when using bulk-edit on PersistentObjects

  • Don’t send HSTS header when running on localhost

5.1.60401.4035

Friday, November 25th, 2016

Features

  • Allow new ProgramUnit in Advanced.OnBuildProgramUnits

  • Show validation errors from hidden attributes in PO Notification

  • Added Vidyano.TransactionIsolationLevel app setting to determine IsolationLevel

  • Added POActions.OnBulkSave

  • Added IUser.ChangeName

Changes

  • Updated CommonMark.NET NuGet dependency to 0.14.0

  • Updated Microsoft.Data.* NuGet dependencies to 5.8.1

  • Updated WindowsAzure.Storage NuGet dependency to 7.2.1

  • Removed default logic from POActions.OnRefresh