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...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
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.
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());
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.
Saturday, November 30th, 2019
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
Tweaked exception when FromServiceString fails
Show extra information for repeating jobs (last execution, execution count)
Don't remove request data when disposing UserSession
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
Tuesday, August 28th, 2018
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
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)
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
Wednesday, July 11th, 2018
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
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
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
Monday, March 12th, 2018
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
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
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
Monday, February 5th, 2018
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
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
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/…)
Saturday, February 10th, 2018
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
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
Fixed issue when writing custom filter for Azure table query
Sunday, December 28th, 2017
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
Updated EntityFramework NuGet dependency to 6.2.0
Changed newline endings back to CRLF for PersistentObjectActionsReference.cs
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
Thursday, December 14th, 2017
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
Don’t log outgoing content for GetReport/Api in verbose logs
Fixed multi-threading issue when using IUser.UpdateLastLoginDate
Thursday, November 30th, 2017
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
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)
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
Friday, October 27th, 2017
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()
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
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
Thursday, August 7th, 2017
Added ability to define SwaggerAttributes using Advanced.HandleSwaggerMetadata
Added support for using continuation tokens on custom queries
Updated XML comments to explain which claims are used for UnknownUser
Removed obsolete code to use pages for querying instead of skip/top
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
Wednesday, May 24th, 2017
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
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
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)
Thursday, April 13th, 2017
Added Vidyano.Core.External.AzureStorageConnector helper class for common Azure Storage scenarios.
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
Fixed initialization error for new projects
Tuesday, March 14th, 2017
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)
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
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
Monday, April 3rd, 2017
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)
Changed diagnostics page to also show web2 version information for websites
Ensure that repository always has all the system settings
Fixed issue with correct exception not being shown when RepositorySynchronizer failed on project with NoDatabase enabled
Wednesday, February 1st, 2017
Added ExportToCsv action
Added Manager.GetUsersForGroup method
Use DocumentFormat.OpenXml NuGet 2.7.1
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
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
Wednesday, February 8th, 2017
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:
You will also need to have the Vidyano Extension installed from the Visual Studio Gallery:
Vidyano Extension for Visual Studio
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.
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;
}
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.
Flexibility: Capable of representing both physical and virtual data structures.
Customization: Allows for the definition of fields, relationships, and behaviors tailored to specific needs.
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 in Vidyano are used to retrieve and display data in various ways. There are three primary types of 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 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 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.
public partial class ProductActions
{
public IQueryable<Product> ActiveProducts(CustomQueryArgs args)
{
return Context.Products.Where(p => p.IsActive);
}
}
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.
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 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;
}
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 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.
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
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.
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.
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.
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.
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.
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,
}
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.
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.
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.
See related document Courier Feature, can be used in RavenDB for message based communication.
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.
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.
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.
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.
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.
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.
This documentation will help you build powerful data-driven applications using the Vidyano application platform.
For developers ready to dive in, the Getting Started section provides the necessary steps to install tools and create a new project with Vidyano.
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.
Here are a few badges with the latest versions of various packages used for Vidyano projects
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.
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.
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.
To enable the Courier feature in your Vidyano application:
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>();
}
The Courier feature works as follows:
Message Publishing: Messages are stored in RavenDB as CourierMessage
documents.
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
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
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
Messages can implement the IIdempotentMessage
interface to ensure they are only processed once, identified by a unique identifier.
Failed messages can be automatically retried based on configurable retry strategies.
Processed messages are automatically removed from the system after a configurable expiration period.
// 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}";
}
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));
}
}
// 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));
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;
}
}
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)
Keep Messages Small: Store only essential information in messages.
Make Messages Immutable: Use C# record types or immutable classes.
Design for Idempotency: Ensure that processing a message multiple times does not cause issues.
Handle Exceptions: Implement proper exception handling in your message handlers.
Use Message Chains: Break complex workflows into chains of simple messages.
Consider Message Expiration: Set appropriate expiration times for processed messages.
Monitor the System: Implement logging and monitoring for message processing.
If messages are not being processed as expected:
Check Subscription Status: Ensure the RavenDB subscription is active.
Verify Handler Registration: Confirm that your message handlers are registered correctly.
Review Message Serialization: Check that your message classes can be properly serialized/deserialized by JSON.NET.
Examine Expired Messages: Look for messages that have expired before processing.
Look for Exceptions: Check exception logs for errors in message handlers.
Inspect RetryWaitTime: Adjust RetryWaitTime if messages are not being retried quickly enough.
Disable Specific Message Types: Use the Courier{MessageTypeName}Disabled
setting to temporarily disable processing of specific message types.
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:
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.
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.
The query grid is a powerful data table component with the following features:
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.
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).
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.
Keep columns in view while horizontally scrolling over your data. This view-state is automatically persisted on their profile.
Users are in control over the amount of data they want on the screen. This view-state is automatically persisted on their profile.
Users are in control over which columns should be visible first. This view-state is automatically persisted on their profile.
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.
Users can choose to save the filter for later use:
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.
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.
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.
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.
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.
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:
This option expects the token to be passed as Authorization header using the Bearer scheme when accessing the report.
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).
For testing you can also use as it allows for Pre-request scripts and variables:
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.
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:
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.
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.
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'
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);
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, ...)
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.
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.
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
Actions Generator
BusinessRules Generator
Context Generator
CustomAction Generator
Index Generator
Model Generator
ProjectTargetType Generator
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
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
.
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 GenerateIndex
attribute 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;}
}
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>
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>
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
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.
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.
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:
{
"compilerOptions": {
"lib": [
"dom",
"es2015"
],
...
The most major breaking change is the deprecation of the linq.js base library.
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.
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.
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))
You can now add the inputtype=tags typehint to a MultiString attribute to display the strings as tags.
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.
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.
v6 release notes
Monday, February 10th, 2020
Added Advanced search save-load
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
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
Thursday, February 22nd, 2018
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
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
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
Tuesday, April 17th, 2018
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)
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
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)
Friday, March 30th, 2018
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
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
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
Friday, July 28th, 2017
Added ability to skip data security when using GetEntity/FindByObjectId
Added Advanced.BlacklistedPasswords per Microsoft Password Guidance for improved security
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
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
Thursday, November 8th, 2018
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)
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
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
Wednesday, October 11th, 2017
Added ability to define default/cancel button index for RetryAction
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
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
Thursday, July 26th, 2018
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
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
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
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.
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
Thursday, August 31th, 2017
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)
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
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
Tuesday, September 19th, 2017
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)
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
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
Friday, March 17th, 2017
Added Instant Search feature (example)
Tweaked new website template to use https:// for google fonts url
Tweaked new webcomponent template to remove obsolete content tag
Thursday, March 2nd, 2017
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)
Updated CommonMark.NET NuGet dependency to 0.15.1
Fixed issue with select all not working when adding queries to program unit
Coming soon
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.
Friday, April 21st, 2017
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
Fixed DbSort clause issue when sorting on reference attribute that isn’t visible
Thursday, April 27th, 2017
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
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
Fixed issue when deploying a new website to Azure that the bindings would be lost
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).
This information is still applicable to v6
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.
Vidyano application can be configured to allow multiple authentication sources which all map to users that can be assigned to groups.
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).
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.
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.
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);
}
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.
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:
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).
This is kept for informational purposes as this no longer applies to v6 projects.
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".
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.
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.
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.
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.
Click Go ! to continue.
At this point, Vidyano is setting up your project.
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.
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!
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.
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.
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.
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);
}
This information is still applicable to v6
This page provides some common scenarios to use the PersistentObjectActions class.
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;
}
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);
}
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);
}
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);
}
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");
}
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.
Wednesday, June 7th, 2017
Added ability to define Register PO and handle Forgot password logic (Register / Forgot password)
Changed ToServiceString for enums to use default ToString (allows undefined enum values to be converted to numeric value)
Fixed issue with Verbose logs filtering
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.
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.
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)
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.
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.”
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
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 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
Tuesday, July 18th, 2017
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
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
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
Coming soon
Wednesday, January 18th, 2017
Added PO.RemoveAttribute/AddQuery/RemoveQuery
Added support for Cognitive Services token for translations
Added ITranslationService
Added ?lang=CODE support for reports
Use IDbSet/IObjectSet to discover context property
Increased entropy for new report tokens
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
Friday, November 25th, 2016
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
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