//******************************************************************************************************
// DataContext.cs - Gbtc
//
// Copyright © 2016, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
// file except in compliance with the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 02/01/2016 - J. Ritchie Carroll
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Web.Routing;
using GSF.Collections;
using GSF.ComponentModel.DataAnnotations;
using GSF.Data;
using GSF.Data.Model;
using GSF.Reflection;
using GSF.Security;
using GSF.Web.Hubs;
using RazorEngine.Templating;
// ReSharper disable once StaticMemberInGenericType
namespace GSF.Web.Model
{
///
/// Defines a data context for Razor views.
///
///
/// This class is used by views to render HTML form input templates, paged view model configurations,
/// provide access to modeled table operations and general database access.
///
public class DataContext : IDisposable
{
#region [ Members ]
// Constants
///
/// Defines the regular expression used to validate URLs.
///
public const string UrlValidation = UrlValidationAttribute.ValidationPattern;
// Fields
private AdoDataConnection m_connection;
private readonly IRazorEngine m_razorEngine;
private readonly Dictionary m_tableOperationsCache;
private ConcurrentDictionary m_primaryKeySessionCache;
private readonly Action m_exceptionHandler;
private Dictionary>> m_customTableOperationTokens;
private readonly Dictionary> m_fieldValidationParameters;
private readonly List> m_fieldValueInitializers;
private readonly List> m_readonlyHotLinkFields;
private readonly List m_definedDateFields;
private readonly string m_settingsCategory;
private readonly bool m_disposeConnection;
private string m_initialFocusField;
private string m_addDateFieldTemplate;
private string m_addInputFieldTemplate;
private string m_addTextAreaFieldTemplate;
private string m_addSelectFieldTemplate;
private string m_addCheckBoxFieldTemplate;
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Creates a new .
///
/// to use; defaults to a new connection.
/// Set to true to dispose the provided .
/// Razor engine instance to use for data context; set to null to use default embedded resources instance.
/// Delegate to handle exceptions.
public DataContext(AdoDataConnection connection = null, bool disposeConnection = false, IRazorEngine razorEngine = null, Action exceptionHandler = null)
{
m_connection = connection;
m_razorEngine = razorEngine ?? RazorEngine.Default;
m_exceptionHandler = exceptionHandler;
m_tableOperationsCache = new Dictionary();
m_fieldValidationParameters = new Dictionary>();
m_fieldValueInitializers = new List>();
m_readonlyHotLinkFields = new List>();
m_definedDateFields = new List();
m_settingsCategory = "systemSettings";
m_disposeConnection = disposeConnection || connection == null;
}
///
/// Creates a new using the specified .
///
/// Setting category that contains the connection settings.
/// Razor engine instance to use for data context; set to null to use default embedded resources instance.
/// Delegate to handle exceptions.
public DataContext(string settingsCategory, IRazorEngine razorEngine = null, Action exceptionHandler = null)
{
m_razorEngine = razorEngine ?? RazorEngine.Default;
m_exceptionHandler = exceptionHandler;
m_tableOperationsCache = new Dictionary();
m_fieldValidationParameters = new Dictionary>();
m_fieldValueInitializers = new List>();
m_readonlyHotLinkFields = new List>();
m_definedDateFields = new List();
m_settingsCategory = settingsCategory;
m_disposeConnection = true;
}
#endregion
#region [ Properties ]
///
/// Gets reference to used by this .
///
public IRazorEngine RazorEngine => m_razorEngine;
///
/// Gets the for this , creating a new one if needed.
///
public AdoDataConnection Connection => m_connection ?? (m_connection = new AdoDataConnection(m_settingsCategory));
///
/// Gets validation pattern and error message for rendered fields, if any.
///
public Dictionary> FieldValidationParameters => m_fieldValidationParameters;
///
/// Gets field value initializers, if any.
///
public List> FieldValueInitializers => m_fieldValueInitializers;
///
/// Gets any text input fields that should render clickable URLs and e-mail addresses when in view mode.
///
public List> ReadonlyHotLinkFields => m_readonlyHotLinkFields;
///
/// Gets defined date fields, if any.
///
public List DefinedDateFields => m_definedDateFields;
///
/// Gets field name designated for initial focus.
///
public string InitialFocusField => m_initialFocusField;
///
/// Gets or sets the date field razor template file name.
///
public string AddDateFieldTemplate
{
get
{
return m_addDateFieldTemplate ?? (m_addDateFieldTemplate = $"{m_razorEngine.TemplatePath}AddDateField.cshtml");
}
set
{
m_addDateFieldTemplate = value;
}
}
///
/// Gets or sets the input field razor template file name.
///
public string AddInputFieldTemplate
{
get
{
return m_addInputFieldTemplate ?? (m_addInputFieldTemplate = $"{m_razorEngine.TemplatePath}AddInputField.cshtml");
}
set
{
m_addInputFieldTemplate = value;
}
}
///
/// Gets or sets the text area field razor template file name.
///
public string AddTextAreaFieldTemplate
{
get
{
return m_addTextAreaFieldTemplate ?? (m_addTextAreaFieldTemplate = $"{m_razorEngine.TemplatePath}AddTextAreaField.cshtml");
}
set
{
m_addTextAreaFieldTemplate = value;
}
}
///
/// Gets or sets the select field razor template file name.
///
public string AddSelectFieldTemplate
{
get
{
return m_addSelectFieldTemplate ?? (m_addSelectFieldTemplate = $"{m_razorEngine.TemplatePath}AddSelectField.cshtml");
}
set
{
m_addSelectFieldTemplate = value;
}
}
///
/// Gets or sets the check box field razor template file name.
///
public string AddCheckBoxFieldTemplate
{
get
{
return m_addCheckBoxFieldTemplate ?? (m_addCheckBoxFieldTemplate = $"{m_razorEngine.TemplatePath}AddCheckBoxField.cshtml");
}
set
{
m_addCheckBoxFieldTemplate = value;
}
}
///
/// Gets dictionary of per-model custom tokens to use with values for in this .
///
///
/// The returned dictionary can be used to apply run-time tokens to any defined values,
/// for example, given the following amendment expression applied to a modeled class:
///
/// [AmendExpression("TOP {count}",
/// TargetExpression = TargetExpression.FieldList,
/// AffixPosition = AffixPosition.Prefix,
/// StatementTypes = StatementTypes.SelectSet)]]
/// public class MyTable
/// {
/// string MyField;
/// }
///
/// The per-model key/value pairs could be set as follows at run-time:
///
/// int count = 200;
/// dataContext.CustomTableOperationTokens[typeof(MyTable)] = new[] { new KeyValuePair<string, string>("{count}", $"{count}") };
///
///
public Dictionary>> CustomTableOperationTokens => m_customTableOperationTokens ?? (m_customTableOperationTokens = new Dictionary>>());
///
/// Gets table operations cache for data context.
///
internal Dictionary TableOperationsCache => m_tableOperationsCache;
///
/// Gets or sets session based primary key cache of table operations, creating it if needed.
///
internal ConcurrentDictionary PrimaryKeySessionCache
{
get
{
return m_primaryKeySessionCache ?? (m_primaryKeySessionCache = new ConcurrentDictionary());
}
set
{
m_primaryKeySessionCache = value;
}
}
#endregion
#region [ Methods ]
///
/// Releases all the resources used by the object.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
if (disposing)
{
if (m_disposeConnection)
m_connection?.Dispose();
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
}
}
}
///
/// Gets the table operations for the specified modeled table .
///
/// Modeled database table (or view).
/// Table operations for the specified modeled table .
public TableOperations Table() where TModel : class, new()
{
IEnumerable> customTokens = null;
if ((object)m_customTableOperationTokens != null)
m_customTableOperationTokens.TryGetValue(typeof(TModel), out customTokens);
return m_tableOperationsCache.GetOrAdd(typeof(TModel), type =>
{
DataTable primaryKeyCache;
TableOperations tableOperations = new TableOperations(Connection, m_exceptionHandler, customTokens);
if (PrimaryKeySessionCache.TryGetValue(type, out primaryKeyCache))
tableOperations.PrimaryKeyCache = primaryKeyCache;
return tableOperations;
})
as TableOperations;
}
///
/// Gets the table operations for the specified modeled table type .
///
/// Modeled database table (or view) type.
/// Table operations for the specified modeled table type .
public ITableOperations Table(Type model)
{
IEnumerable> customTokens = null;
if ((object)m_customTableOperationTokens != null)
m_customTableOperationTokens.TryGetValue(model, out customTokens);
return m_tableOperationsCache.GetOrAdd(model, type =>
{
DataTable primaryKeyCache;
ITableOperations tableOperations = Activator.CreateInstance(typeof(TableOperations<>).MakeGenericType(model), Connection, m_exceptionHandler, customTokens) as ITableOperations;
// ReSharper disable once PossibleNullReferenceException
if (PrimaryKeySessionCache.TryGetValue(type, out primaryKeyCache))
tableOperations.PrimaryKeyCache = primaryKeyCache;
return tableOperations;
});
}
///
/// Gets the field name targeted as the primary label for the modeled table.
///
/// Modeled database table (or view).
/// Field name targeted as the primary label for the modeled table.
public string GetPrimaryLabelField() where TModel : class, new()
{
return GetPrimaryLabelField(typeof(TModel));
}
///
/// Gets the field name targeted as the primary label for the modeled table.
///
/// Modeled database table (or view) type.
/// Field name targeted as the primary label for the modeled table.
public string GetPrimaryLabelField(Type model)
{
PrimaryLabelAttribute primaryLabelAttribute;
if (model.TryGetAttribute(out primaryLabelAttribute) && !string.IsNullOrWhiteSpace(primaryLabelAttribute.FieldName))
return primaryLabelAttribute.FieldName;
string[] fieldNames = Table(model).GetFieldNames(false);
return fieldNames.Length > 0 ? fieldNames[0] : "";
}
///
/// Gets the field name targeted to mark a record as deleted.
///
/// Modeled database table (or view).
/// Field name targeted to mark a record as deleted.
public string GetIsDeletedFlag() where TModel : class, new()
{
return GetIsDeletedFlag(typeof(TModel));
}
///
/// Gets the field name targeted to mark a record as deleted.
///
/// Modeled database table (or view) type.
/// Field name targeted to mark a record as deleted.
public string GetIsDeletedFlag(Type model)
{
IsDeletedFlagAttribute isDeletedFlagAttribute;
if (model.TryGetAttribute(out isDeletedFlagAttribute) && !string.IsNullOrWhiteSpace(isDeletedFlagAttribute.FieldName))
return isDeletedFlagAttribute.FieldName;
return null;
}
///
/// Determines if user is in a specific role or list of roles (comma separated).
///
/// Principal that provides role-based security.
/// Role or comma separated list of roles.
/// true if user is in (s); otherwise, false.
///
/// Set to * for any role.
///
public bool UserIsInRole(SecurityPrincipal securityPrincipal, string role)
{
if ((object)securityPrincipal == null)
return false;
if (string.IsNullOrWhiteSpace(role))
return false;
role = role.Trim();
string[] roles = role.Split(',').Where(value => !string.IsNullOrWhiteSpace(value)).ToArray();
if (roles.Length > 1)
return UserIsInRole(securityPrincipal, roles);
ISecurityProvider securityProvider = securityPrincipal.Identity.Provider;
List userRoles = securityProvider.UserData.Roles ?? new List();
if (role.Equals("*") && userRoles.Count > 0)
return true;
foreach (string userRole in userRoles)
if (userRole.Equals(role, StringComparison.OrdinalIgnoreCase))
return true;
return false;
}
///
/// Determines if user is in one of the provided of roles.
///
/// Security principal that provides role-based security.
/// List of role names.
/// true if user is in one of the ; otherwise, false.
public bool UserIsInRole(SecurityPrincipal securityPrincipal, string[] roles)
{
return roles.Any(role => UserIsInRole(securityPrincipal, role));
}
///
/// Determines if user is in a specific group or list of groups (comma separated).
///
/// Security principal that provides role-based security.
/// Group or comma separated list of groups.
/// true if user is in (s); otherwise, false.
///
/// Set to * for any group.
///
public bool UserIsInGroup(SecurityPrincipal securityPrincipal, string group)
{
if ((object)securityPrincipal == null)
return false;
if (string.IsNullOrWhiteSpace(group))
return false;
group = group.Trim();
string[] groups = group.Split(',').Where(value => !string.IsNullOrWhiteSpace(value)).ToArray();
if (groups.Length > 1)
return UserIsInGroup(securityPrincipal, groups);
ISecurityProvider securityProvider = securityPrincipal.Identity.Provider;
List userGroups = securityProvider?.UserData?.Groups ?? new List();
if (group.Equals("*") && userGroups.Count > 0)
return true;
foreach (string userGroup in userGroups)
if (userGroup.Equals(group, StringComparison.OrdinalIgnoreCase))
return true;
return false;
}
///
/// Determines if user is in one of the provided of groups.
///
/// Security principal that provides role-based security.
/// List of group names.
/// true if user is in one of the ; otherwise, false.
public bool UserIsInGroup(SecurityPrincipal securityPrincipal, string[] groups)
{
return groups.Any(group => UserIsInGroup(securityPrincipal, group));
}
///
/// Configures a simple view with common view bag parameters.
///
/// HTTP request message for view used to derive route ID, if any.
/// Current view bag.
///
/// This is normally called from controller before returning view action result.
/// For self-hosted web servers, route ID is passed as a command line parameter, e.g.: myview.cshtml?RouteID=ShowDeleted
///
public void ConfigureView(HttpRequestMessage request, dynamic viewBag)
{
string routeID;
request.QueryParameters().TryGetValue("RouteID", out routeID);
ConfigureView(routeID, viewBag);
}
///
/// Configures a view establishing user roles based on modeled table and SignalR .
///
/// HTTP request message for view used to derive route ID, if any.
/// Current view bag.
/// Modeled database table (or view).
/// SignalR hub that implements .
///
/// This is normally called from controller before returning view action result.
/// For self-hosted web servers, route ID is passed as a command line parameter, e.g.: myview.cshtml?RouteID=ShowDeleted
///
public void ConfigureView(HttpRequestMessage request, dynamic viewBag) where TModel : class, new() where THub : IRecordOperationsHub, new()
{
ConfigureView(typeof(TModel), typeof(THub), request, viewBag);
}
///
/// Configures a view establishing user roles based on modeled table and SignalR .
///
/// Modeled database table (or view) type.
/// Type of SignalR hub that implements .
/// HTTP request message for view used to derive route ID, if any.
/// Current view bag.
///
/// This is normally called from controller before returning view action result.
/// For self-hosted web servers, route ID is passed as a command line parameter, e.g.: myview.cshtml?RouteID=ShowDeleted
///
public void ConfigureView(Type model, Type hub, HttpRequestMessage request, dynamic viewBag)
{
string routeID;
request.QueryParameters().TryGetValue("RouteID", out routeID);
ConfigureView(model, hub, routeID, viewBag);
}
///
/// Configures a simple view with common view bag parameters.
///
/// Url.RequestContext for view used to derive route ID, if any.
/// Current view bag.
///
/// This is normally called from controller before returning view action result.
/// For normal MVC views the common route is "{controller}/{action}/{id}", the {id} of
/// the route is the route ID parameter derived from the route data. In many use
/// cases this is a primary key or action value for the page, e.g., "ShowDeleted".
///
public void ConfigureView(RequestContext requestContext, dynamic viewBag)
{
viewBag.SecurityPrincipal = requestContext.HttpContext.User;
ConfigureView(requestContext.RouteData.Values["id"] as string, viewBag);
}
///
/// Configures a view establishing user roles based on modeled table and SignalR .
///
/// Url.RequestContext for view used to derive route ID, if any.
/// Current view bag.
/// Modeled database table (or view).
/// SignalR hub that implements .
///
/// This is normally called from controller before returning view action result.
/// For normal MVC views the common route is "{controller}/{action}/{id}", the {id} of
/// the route is the route ID parameter derived from the route data. In many use
/// cases this is a primary key or action value for the page, e.g., "ShowDeleted".
///
public void ConfigureView(RequestContext requestContext, dynamic viewBag) where TModel : class, new() where THub : IRecordOperationsHub, new()
{
viewBag.SecurityPrincipal = requestContext.HttpContext.User;
ConfigureView(typeof(TModel), typeof(THub), requestContext, viewBag);
}
///
/// Configures a view establishing user roles based on modeled table and SignalR .
///
/// Modeled database table (or view) type.
/// Type of SignalR hub that implements .
/// Url.RequestContext for view used to derive route ID, if any.
/// Current view bag.
///
/// This is normally called from controller before returning view action result.
/// For normal MVC views the common route is "{controller}/{action}/{id}", the {id} of
/// the route is the route ID parameter derived from the route data. In many use
/// cases this is a primary key or action value for the page, e.g., "ShowDeleted".
///
public void ConfigureView(Type model, Type hub, RequestContext requestContext, dynamic viewBag)
{
viewBag.SecurityPrincipal = requestContext.HttpContext.User;
ConfigureView(model, hub, requestContext.RouteData.Values["id"] as string, viewBag);
}
///
/// Configures a simple view with common view bag parameters.
///
/// Route ID for view, if any (e.g., AddNew or ShowDeleted).
/// Current view bag.
///
/// This is normally called from controller before returning view action result.
/// For normal MVC views the common route is "{controller}/{action}/{id}", the {id} of
/// the route is what is requested as the parameter. In many
/// use cases this is a primary key or action value for the page, e.g., "ShowDeleted".
///
public void ConfigureView(string routeID, dynamic viewBag)
{
viewBag.RouteID = routeID;
viewBag.PageControlScripts = new StringBuilder();
// Setup default roles if none are defined. Important to only check for null here as empty string will
// be treated as none-allowed, e.g., read-only for modeled views
if (viewBag.EditRoles == null)
viewBag.EditRoles = "*";
if (viewBag.AddNewRoles == null)
viewBag.AddNewRoles = viewBag.EditRoles;
if (viewBag.DeleteRoles == null)
viewBag.DeleteRoles = viewBag.EditRoles;
// Check current allowed roles for user
viewBag.CanEdit = UserIsInRole((SecurityPrincipal)viewBag.SecurityPrincipal, viewBag.EditRoles);
viewBag.CanAddNew = UserIsInRole((SecurityPrincipal)viewBag.SecurityPrincipal, viewBag.AddNewRoles);
viewBag.CanDelete = UserIsInRole((SecurityPrincipal)viewBag.SecurityPrincipal, viewBag.DeleteRoles);
}
///
/// Configures a view establishing user roles based on modeled table and SignalR .
///
/// Route ID for view, if any (e.g., AddNew or ShowDeleted).
/// Current view bag.
/// Modeled database table (or view).
/// SignalR hub that implements .
///
/// This is normally called from controller before returning view action result.
/// For normal MVC views the common route is "{controller}/{action}/{id}", the {id} of
/// the route is what is requested as the parameter. In many
/// use cases this is a primary key or action value for the page, e.g., "ShowDeleted".
///
public void ConfigureView(string routeID, dynamic viewBag) where TModel : class, new() where THub : IRecordOperationsHub, new()
{
ConfigureView(typeof(TModel), typeof(THub), routeID, viewBag);
}
///
/// Configures a view establishing user roles based on modeled table and SignalR .
///
/// Modeled database table (or view) type.
/// Type of SignalR hub that implements .
/// Route ID for view, if any (e.g., AddNew or ShowDeleted).
/// Current view bag.
///
/// This is normally called from controller before returning view action result.
/// For normal MVC views the common route is "{controller}/{action}/{id}", the {id} of
/// the route is what is requested as the parameter. In many
/// use cases this is a primary key or action value for the page, e.g., "ShowDeleted".
///
public void ConfigureView(Type model, Type hub, string routeID, dynamic viewBag)
{
// Attempt to establish roles based on hub defined security for specified modeled table
EstablishUserRolesForPage(model, hub, viewBag);
ConfigureView(routeID, viewBag);
// See if modeled table has a flag field that represents a deleted row
string isDeletedField = GetIsDeletedFlag(model);
if (!string.IsNullOrWhiteSpace(isDeletedField))
{
// See if user has requested to show deleted records
viewBag.ShowDeleted = viewBag.RouteID?.Equals("ShowDeleted", StringComparison.OrdinalIgnoreCase) ?? false;
viewBag.IsDeletedField = isDeletedField;
}
}
///
/// Looks up proper user roles for paged based on modeled security in .
///
/// Modeled database table (or view).
/// SignalR hub that implements .
/// ViewBag for the current view.
///
/// Typically used in paged view model scenarios and invoked by controller prior to view rendering.
/// Security is controlled at hub level, so failure to call will not impact security but may result
/// in screen enabling and/or showing controls that the user does not actually have access to and
/// upon attempted use will result in a security error.
///
public void EstablishUserRolesForPage(dynamic viewBag) where TModel : class, new() where THub : IRecordOperationsHub, new()
{
EstablishUserRolesForPage(typeof(TModel), typeof(THub), viewBag);
}
///
/// Looks up proper user roles for paged based on modeled security in .
///
/// Modeled database table (or view) type.
/// Type of SignalR hub that implements .
/// ViewBag for the current view.
///
/// Typically used in paged view model scenarios and invoked by controller prior to view rendering.
/// Security is controlled at hub level, so failure to call will not impact security but may result
/// in screen enabling and/or showing controls that the user does not actually have access to and
/// upon attempted use will result in a security error.
///
public void EstablishUserRolesForPage(Type model, Type hub, dynamic viewBag)
{
RecordOperationsCache cache = s_recordOperationCaches.GetOrAdd(hub, type =>
{
using (IRecordOperationsHub operationsHub = Activator.CreateInstance(hub) as IRecordOperationsHub)
return operationsHub?.RecordOperationsCache;
});
EstablishUserRolesForPage(model, cache, viewBag);
}
///
/// Looks up proper user roles for paged based on modeled security in .
///
/// Modeled database table (or view).
/// Data hub record operations cache.
/// ViewBag for the current view.
///
/// Typically used in paged view model scenarios and invoked by controller prior to view rendering.
/// Security is controlled at hub level, so failure to call will not impact security but may result
/// in screen enabling and/or showing controls that the user does not actually have access to and
/// upon attempted use will result in a security error.
///
public void EstablishUserRolesForPage(RecordOperationsCache cache, dynamic viewBag) where TModel : class, new()
{
EstablishUserRolesForPage(typeof(TModel), cache, viewBag);
}
///
/// Looks up proper user roles for paged based on modeled security in .
///
/// Modeled database table (or view) type.
/// Data hub record operations cache.
/// ViewBag for the current view.
///
/// Typically used in paged view model scenarios and invoked by controller prior to view rendering.
/// Security is controlled at hub level, so failure to call will not impact security but may result
/// in screen enabling and/or showing controls that the user does not actually have access to and
/// upon attempted use will result in a security error.
///
public void EstablishUserRolesForPage(Type model, RecordOperationsCache cache, dynamic viewBag)
{
// Get any authorized roles as defined in hub for key records operations of modeled table
Tuple[] recordOperations = cache.GetRecordOperations(model);
// Create a function to check if a method exists for operation - if none is defined, read-only access will be assumed (e.g. for a view)
Func getRoles = operationType =>
{
Tuple recordOperation = recordOperations[(int)operationType];
return string.IsNullOrEmpty(recordOperation?.Item1) ? "" : recordOperation.Item2 ?? "";
};
viewBag.EditRoles = getRoles(RecordOperation.UpdateRecord);
viewBag.AddNewRoles = getRoles(RecordOperation.AddNewRecord);
viewBag.DeleteRoles = getRoles(RecordOperation.DeleteRecord);
}
///
/// Renders client-side configuration script for paged view model.
///
/// Modeled database table (or view).
/// SignalR hub that implements .
/// ViewBag for the view.
/// Default sort field name, defaults to first primary key field. Prefix field name with a minus, i.e., '-', to default to descending sort.
/// Javascript hub name, defaults to camel-cased type name.
/// Primary keys values of the parent record to load.
/// Rendered paged view model configuration script.
public string RenderViewModelConfiguration(dynamic viewBag, string defaultSortField = null, string hubScriptName = null, params object[] parentKeys) where TModel : class, new() where THub : IRecordOperationsHub, new()
{
Type hubType = typeof(THub);
RecordOperationsCache cache = s_recordOperationCaches.GetOrAdd(hubType, type =>
{
using (THub hub = new THub())
return hub.RecordOperationsCache;
});
return RenderViewModelConfiguration(cache, viewBag, defaultSortField, hubType.FullName, hubScriptName ?? hubType.Name.ToCamelCase(), parentKeys);
}
///
/// Renders client-side configuration script for paged view model.
///
/// Modeled database table (or view).
/// Data hub record operations cache.
/// ViewBag for the view.
/// Default sort field name, defaults to first primary key field. Prefix field name with a minus, i.e., '-', to default to descending sort.
/// Full class name of hub instance, defaults to "GSF.Web.Security.SecurityHub".
/// Javascript hub name, defaults to "securityHub".
/// Primary keys values of the parent record to load.
/// Rendered paged view model configuration script.
public string RenderViewModelConfiguration(RecordOperationsCache cache, dynamic viewBag, string defaultSortField = null, string hubClassName = "GSF.Web.Security.SecurityHub", string hubScriptName = "securityHub", params object[] parentKeys) where TModel : class, new()
{
StringBuilder javascript = new StringBuilder();
string[] primaryKeyFields = Table().GetPrimaryKeyFieldNames(false);
string defaultSortAscending = "true";
defaultSortField = defaultSortField ?? primaryKeyFields[0];
// Handle case for desired default descending sort order
if (defaultSortField[0] == '-')
{
defaultSortField = defaultSortField.Substring(1);
defaultSortAscending = "false";
}
javascript.Append($@"// Configure view model
viewModel.defaultSortField = ""{defaultSortField}"";
viewModel.defaultSortAscending = {defaultSortAscending};
viewModel.labelField = ""{GetPrimaryLabelField()}"";
viewModel.modelName = ""{typeof(TModel).FullName}"";
viewModel.hubName = ""{hubClassName}"";
viewModel.primaryKeyFields = [{primaryKeyFields.Select(fieldName => $"\"{fieldName}\"").ToDelimitedString(", ")}];
".FixForwardSpacing());
string showDeletedValue = null;
if (viewBag.IsDeletedField != null)
showDeletedValue = (viewBag.ShowDeleted ?? false).ToString().ToLower();
// Get method names for records operations of modeled table
Tuple[] recordOperations = cache.GetRecordOperations();
string queryRecordCountMethod = recordOperations[(int)RecordOperation.QueryRecordCount]?.Item1.ToCamelCase();
string queryRecordsMethod = recordOperations[(int)RecordOperation.QueryRecords]?.Item1.ToCamelCase();
string deleteRecordMethod = recordOperations[(int)RecordOperation.DeleteRecord]?.Item1.ToCamelCase();
string createNewRecordMethod = recordOperations[(int)RecordOperation.CreateNewRecord]?.Item1.ToCamelCase();
string addNewRecordMethod = recordOperations[(int)RecordOperation.AddNewRecord]?.Item1.ToCamelCase();
string updateMethod = recordOperations[(int)RecordOperation.UpdateRecord]?.Item1.ToCamelCase();
string parentKeyParams = null;
if (parentKeys.Length > 0)
{
viewBag.ParentKeys = string.Join(",", parentKeys.Select(parentKey => parentKey.ToString().Replace(",", ','.RegexEncode())));
parentKeyParams = string.Join(", ", parentKeys.Select(parentKey => Common.IsNumericType(parentKey) ? parentKey.ToString().ToLower() : $"\"{parentKey.ToString().JavaScriptEncode()}\""));
}
// If modeled table has IsDeletedField marker, the showDeleted parameter should come first in DataHub operations
if (showDeletedValue != null)
parentKeyParams = parentKeyParams != null ? $"{showDeletedValue}, {parentKeyParams}" : showDeletedValue;
if (!string.IsNullOrWhiteSpace(queryRecordCountMethod))
javascript.Append($@"
viewModel.setQueryRecordCount(function(filterText) {{
return {hubScriptName}.{queryRecordCountMethod}({(parentKeyParams == null ? "" : $"{parentKeyParams}, ")}filterText);
}});
".FixForwardSpacing());
if (!string.IsNullOrWhiteSpace(queryRecordsMethod))
javascript.Append($@"
viewModel.setQueryRecords(function(sortField, ascending, page, pageSize, filterText) {{
return {hubScriptName}.{queryRecordsMethod}({(parentKeyParams == null ? "" : $"{parentKeyParams}, ")}sortField, ascending, page, pageSize, filterText);
}});
".FixForwardSpacing());
if (!string.IsNullOrWhiteSpace(deleteRecordMethod))
javascript.Append($@"
viewModel.setDeleteRecord(function(keyValues) {{
return {hubScriptName}.{deleteRecordMethod}({Enumerable.Range(0, Table().GetPrimaryKeyFieldNames().Length).Select(index => $"keyValues[{index}]").ToDelimitedString(", ")});
}});
".FixForwardSpacing());
if (!string.IsNullOrWhiteSpace(createNewRecordMethod))
javascript.Append($@"
viewModel.setNewRecord(function() {{
return {hubScriptName}.{createNewRecordMethod}();
}});
".FixForwardSpacing());
if (!string.IsNullOrWhiteSpace(addNewRecordMethod))
javascript.Append($@"
viewModel.setAddNewRecord(function(record) {{
return {hubScriptName}.{addNewRecordMethod}(record);
}});
".FixForwardSpacing());
if (!string.IsNullOrWhiteSpace(updateMethod))
javascript.Append($@"
viewModel.setUpdateRecord(function(record) {{
return {hubScriptName}.{updateMethod}(record);
}});
".FixForwardSpacing());
return javascript.ToString();
}
///
/// Renders client-side Javascript function for looking up single values from a table.
///
/// Modeled database table (or view).
/// Table field name as defined in the table.
/// Name of primary key field, defaults to "ID".
/// Name of lookup function, defaults to lookup + name + + Value.
/// Name of value array, defaults to camel cased name + + Values.
/// Client-side Javascript lookup function.
public string RenderLookupFunction(string valueFieldName, string keyFieldName = "ID", string lookupFunctionName = null, string arrayName = null) where TModel : class, new()
{
StringBuilder javascript = new StringBuilder();
if (string.IsNullOrWhiteSpace(lookupFunctionName))
lookupFunctionName = $"lookup{typeof(TModel).Name}{valueFieldName}Value";
if (string.IsNullOrWhiteSpace(arrayName))
arrayName = $"{typeof(TModel).Name.ToCamelCase()}{valueFieldName}Values";
TableOperations operations = Table();
bool keyIsString = operations.GetFieldType(keyFieldName) == typeof(string);
bool valueIsString = operations.GetFieldType(valueFieldName) == typeof(string);
javascript.AppendLine($"var {arrayName} = [];");
javascript.AppendLine();
foreach (TModel record in operations.QueryRecords())
{
string key = operations.GetFieldValue(record, keyFieldName)?.ToString();
if (string.IsNullOrEmpty(key))
continue;
key = key.JavaScriptEncode();
if (keyIsString)
key = $"\"{key}\"";
string value = operations.GetFieldValue(record, valueFieldName)?.ToString() ?? "";
value = value.JavaScriptEncode();
if (valueIsString)
value = $"\"{value}\"";
javascript.AppendLine($" {arrayName}[{key}] = {value};");
}
javascript.AppendLine();
javascript.AppendLine($" function {lookupFunctionName}(value) {{");
javascript.AppendLine($" return {arrayName}[value];");
javascript.AppendLine(" }");
return javascript.ToString();
}
///
/// Adds a new pattern based validation and option error message to a field.
///
/// Observable field reference (from JS view model).
/// Reg-ex based validation pattern.
/// Optional error message to display when pattern fails.
public void AddFieldValidation(string observableFieldReference, string validationPattern, string errorMessage = null)
{
m_fieldValidationParameters[observableFieldReference] = new Tuple(validationPattern, errorMessage);
}
///
/// Adds a new field value initializer.
///
/// Field name (as defined in model).
/// Javascript based initial value for field.
public void AddFieldValueInitializer(string fieldName, string initialValueScript = null)
{
m_fieldValueInitializers.Add(new Tuple(fieldName, initialValueScript ?? "\"\""));
}
///
/// Adds a new field value initializer based on modeled table field attributes.
///
/// Field name (as defined in model).
public void AddFieldValueInitializer(string fieldName) where TModel : class, new()
{
InitialValueScriptAttribute initialValueScriptAttribute;
if (Table().TryGetFieldAttribute(fieldName, out initialValueScriptAttribute))
AddFieldValueInitializer(fieldName, initialValueScriptAttribute.InitialValueScript);
}
///
/// Adds field initialization, and optional validation, from a page-defined (e.g., loaded from database) parameter definition.
///
/// Target field name.
/// Javascript based initial value for field.
/// Reg-ex based validation pattern, if any.
/// Optional error message to display when pattern fails.
public void AddPageDefinedFieldInitialization(string fieldName, string initialValue, string validationPattern = null, string errorMessage = null)
{
AddFieldValueInitializer(fieldName, initialValue);
if (!string.IsNullOrEmpty(validationPattern))
AddFieldValidation($"viewModel.currentRecord().{fieldName}", validationPattern, errorMessage);
}
///
/// Renders a read-only field with enabled hot links for a given text input field when in view mode.
///
/// ID of input field.
/// ID of read-only div.
/// Field name.
/// true if field is a textarea; otherwise, false for input[type="text"].
///
/// These fields will be rendered with clickable URL and e-mail addresses when viewing a record and normal text
/// when adding or updating a record.
///
public void AddReadOnlyHotLinkField(string fieldID, string readOnlyDivID, string fieldName, bool isTextArea)
{
m_readonlyHotLinkFields.Add(new Tuple(fieldID, readOnlyDivID, fieldName, isTextArea));
}
///
/// Generates template based input date field based on reflected modeled table field attributes.
///
/// Modeled table.
/// Field name for input date field.
/// Input field type, defaults to text.
/// Label name for input date field, pulls from if defined, otherwise defaults to .
/// ID to use for input field; defaults to date + .
/// Data-bind operations to apply to outer form-group div, if any.
/// Data-bind operations to apply to label, if any.
/// Boolean data-bind operation to apply to required state, if any.
/// Extra custom data-binding operations to apply to field, if any.
/// Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.
/// Tool tip text to apply to field, if any.
/// Use field for initial focus.
/// Generated HTML for new date based input field based on modeled table field attributes.
public string AddDateField(string fieldName, string inputType = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false) where TModel : class, new()
{
TableOperations tableOperations = Table();
StringLengthAttribute stringLengthAttribute;
RegularExpressionAttribute regularExpressionAttribute;
if (string.IsNullOrEmpty(fieldLabel))
{
LabelAttribute labelAttribute;
if (tableOperations.TryGetFieldAttribute(fieldName, out labelAttribute))
fieldLabel = labelAttribute.Label;
}
tableOperations.TryGetFieldAttribute(fieldName, out stringLengthAttribute);
tableOperations.TryGetFieldAttribute(fieldName, out regularExpressionAttribute);
if ((object)regularExpressionAttribute != null)
{
string observableReference;
if (string.IsNullOrEmpty(groupDataBinding))
observableReference = $"viewModel.currentRecord().{fieldName}";
else // "with: $root.connectionString"
observableReference = $"viewModel.{groupDataBinding.Substring(groupDataBinding.IndexOf('.') + 1)}";
AddFieldValidation(observableReference, regularExpressionAttribute.Pattern, regularExpressionAttribute.ErrorMessage ?? "Invalid format.");
}
AddFieldValueInitializer(fieldName);
return AddDateField(fieldName, tableOperations.FieldHasAttribute(fieldName),
stringLengthAttribute?.MaximumLength ?? 0, inputType, fieldLabel, fieldID, groupDataBinding, labelDataBinding, requiredDataBinding, customDataBinding, dependencyFieldName, toolTip, initialFocus);
}
///
/// Generates template based input date field based on specified parameters.
///
/// Field name for input date field.
/// Determines if field name is required.
/// Defines maximum input field length.
/// Input field type, defaults to text.
/// Label name for input date field, defaults to .
/// ID to use for input field; defaults to date + .
/// Data-bind operations to apply to outer form-group div, if any.
/// Data-bind operations to apply to label, if any.
/// Boolean data-bind operation to apply to required state, if any.
/// Extra custom data-binding operations to apply to field, if any.
/// Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.
/// Tool tip text to apply to field, if any.
/// Use field for initial focus.
/// Generated HTML for new date based input field based on specified parameters.
public string AddDateField(string fieldName, bool required, int maxLength = 0, string inputType = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false)
{
RazorView addDateFieldTemplate = new RazorView(m_razorEngine, AddDateFieldTemplate, m_exceptionHandler);
DynamicViewBag viewBag = addDateFieldTemplate.ViewBag;
if (string.IsNullOrEmpty(fieldID))
fieldID = $"date{fieldName}";
if (initialFocus)
m_initialFocusField = fieldID;
viewBag.AddValue("FieldName", fieldName);
viewBag.AddValue("Required", required);
viewBag.AddValue("MaxLength", maxLength);
viewBag.AddValue("InputType", inputType ?? "text");
viewBag.AddValue("FieldLabel", fieldLabel ?? fieldName);
viewBag.AddValue("FieldID", fieldID);
viewBag.AddValue("GroupDataBinding", groupDataBinding);
viewBag.AddValue("LabelDataBinding", labelDataBinding);
viewBag.AddValue("RequiredDataBinding", requiredDataBinding);
viewBag.AddValue("CustomDataBinding", customDataBinding);
viewBag.AddValue("DependencyFieldName", dependencyFieldName);
viewBag.AddValue("ToolTip", toolTip);
m_definedDateFields.Add(fieldName);
return addDateFieldTemplate.Execute();
}
///
/// Generates template based input text field based on reflected modeled table field attributes.
///
/// Modeled table.
/// Field name for input text field.
/// Input field type, defaults to appropriate model field type.
/// Label name for input text field, pulls from if defined, otherwise defaults to .
/// ID to use for input field; defaults to input + .
/// Data-bind operations to apply to outer form-group div, if any.
/// Data-bind operations to apply to label, if any.
/// Boolean data-bind operation to apply to required state, if any.
/// Extra custom data-binding operations to apply to field, if any.
/// Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.
/// Tool tip text to apply to field, if any.
/// Use field for initial focus.
/// Enable clickable URLs and e-mail addresses in view mode.
/// Generated HTML for new input field based on modeled table field attributes.
public string AddInputField(string fieldName, string inputType = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false, bool enableHotLinks = true) where TModel : class, new()
{
TableOperations tableOperations = Table();
StringLengthAttribute stringLengthAttribute;
RegularExpressionAttribute regularExpressionAttribute;
bool targetedFieldType = false;
if (string.IsNullOrEmpty(inputType))
{
Type fieldType = tableOperations.GetFieldType(fieldName);
if (IsIntegerType(fieldType))
{
inputType = "number";
customDataBinding = string.IsNullOrEmpty(customDataBinding) ? "integer" : $"integer, {customDataBinding}";
targetedFieldType = true;
}
else if (IsNumericType(fieldType))
{
inputType = "number";
customDataBinding = string.IsNullOrEmpty(customDataBinding) ? "numeric" : $"numeric, {customDataBinding}";
targetedFieldType = true;
}
else if (fieldType == typeof(DateTime))
{
inputType = "date";
targetedFieldType = true;
}
}
if (string.IsNullOrEmpty(fieldLabel))
{
LabelAttribute labelAttribute;
if (tableOperations.TryGetFieldAttribute(fieldName, out labelAttribute))
fieldLabel = labelAttribute.Label;
}
tableOperations.TryGetFieldAttribute(fieldName, out stringLengthAttribute);
tableOperations.TryGetFieldAttribute(fieldName, out regularExpressionAttribute);
if ((object)regularExpressionAttribute != null)
{
string observableReference;
if (string.IsNullOrEmpty(groupDataBinding))
observableReference = $"viewModel.currentRecord().{fieldName}";
else // "with: $root.connectionString"
observableReference = $"viewModel.{groupDataBinding.Substring(groupDataBinding.IndexOf('.') + 1)}";
AddFieldValidation(observableReference, regularExpressionAttribute.Pattern, regularExpressionAttribute.ErrorMessage ?? "Invalid format.");
if (!targetedFieldType && regularExpressionAttribute is AcronymValidationAttribute)
customDataBinding = string.IsNullOrEmpty(customDataBinding) ? $"acronym: {fieldName}" : $"acronym: {fieldName}, {customDataBinding}";
}
AddFieldValueInitializer(fieldName);
return AddInputField(fieldName, tableOperations.FieldHasAttribute(fieldName),
stringLengthAttribute?.MaximumLength ?? 0, inputType, fieldLabel, fieldID, groupDataBinding, labelDataBinding, requiredDataBinding, customDataBinding, dependencyFieldName, toolTip, initialFocus, enableHotLinks);
}
///
/// Generates template based input text field based on specified parameters.
///
/// Field name for input text field.
/// Determines if field name is required.
/// Defines maximum input field length.
/// Input field type, defaults to text.
/// Label name for input text field, defaults to .
/// ID to use for input field; defaults to input + .
/// Data-bind operations to apply to outer form-group div, if any.
/// Data-bind operations to apply to label, if any.
/// Boolean data-bind operation to apply to required state, if any.
/// Extra custom data-binding operations to apply to field, if any.
/// Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.
/// Tool tip text to apply to field, if any.
/// Use field for initial focus.
/// Enable clickable URLs and e-mail addresses in view mode.
/// Generated HTML for new input field based on specified parameters.
public string AddInputField(string fieldName, bool required, int maxLength = 0, string inputType = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false, bool enableHotLinks = true)
{
RazorView addInputFieldTemplate = new RazorView(m_razorEngine, AddInputFieldTemplate, m_exceptionHandler);
DynamicViewBag viewBag = addInputFieldTemplate.ViewBag;
if (string.IsNullOrEmpty(fieldID))
fieldID = $"input{fieldName}";
if (initialFocus)
m_initialFocusField = fieldID;
if (enableHotLinks)
{
AddReadOnlyHotLinkField(fieldID, $"readonly{fieldID}", fieldName, false);
customDataBinding = $"{(string.IsNullOrWhiteSpace(customDataBinding) ? "" : $"{customDataBinding}, ")}visible: $root.recordMode()!==RecordMode.View";
}
viewBag.AddValue("FieldName", fieldName);
viewBag.AddValue("Required", required);
viewBag.AddValue("MaxLength", maxLength);
viewBag.AddValue("InputType", inputType ?? "text");
viewBag.AddValue("FieldLabel", fieldLabel ?? fieldName);
viewBag.AddValue("FieldID", fieldID);
viewBag.AddValue("GroupDataBinding", groupDataBinding);
viewBag.AddValue("LabelDataBinding", labelDataBinding);
viewBag.AddValue("RequiredDataBinding", requiredDataBinding);
viewBag.AddValue("CustomDataBinding", customDataBinding);
viewBag.AddValue("DependencyFieldName", dependencyFieldName);
viewBag.AddValue("ToolTip", toolTip);
return addInputFieldTemplate.Execute();
}
///
/// Generates template based text area field based on reflected modeled table field attributes.
///
/// Modeled table.
/// Field name for text area field.
/// Number of rows for text area.
/// Label name for text area field, pulls from if defined, otherwise defaults to .
/// ID to use for text area field; defaults to text + .
/// Data-bind operations to apply to outer form-group div, if any.
/// Data-bind operations to apply to label, if any.
/// Boolean data-bind operation to apply to required state, if any.
/// Extra custom data-binding operations to apply to field, if any.
/// Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.
/// Tool tip text to apply to field, if any.
/// Use field for initial focus.
/// Enable clickable URLs and e-mail addresses in view mode.
/// Generated HTML for new text area field based on modeled table field attributes.
public string AddTextAreaField(string fieldName, int rows = 2, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false, bool enableHotLinks = true) where TModel : class, new()
{
TableOperations tableOperations = Table();
StringLengthAttribute stringLengthAttribute;
RegularExpressionAttribute regularExpressionAttribute;
if (string.IsNullOrEmpty(fieldLabel))
{
LabelAttribute labelAttribute;
if (tableOperations.TryGetFieldAttribute(fieldName, out labelAttribute))
fieldLabel = labelAttribute.Label;
}
tableOperations.TryGetFieldAttribute(fieldName, out stringLengthAttribute);
tableOperations.TryGetFieldAttribute(fieldName, out regularExpressionAttribute);
if ((object)regularExpressionAttribute != null)
{
string observableReference;
if (string.IsNullOrEmpty(groupDataBinding))
observableReference = $"viewModel.currentRecord().{fieldName}";
else // "with: $root.connectionString"
observableReference = $"viewModel.{groupDataBinding.Substring(groupDataBinding.IndexOf('.') + 1)}";
AddFieldValidation(observableReference, regularExpressionAttribute.Pattern, regularExpressionAttribute.ErrorMessage ?? "Invalid format.");
}
AddFieldValueInitializer(fieldName);
return AddTextAreaField(fieldName, tableOperations.FieldHasAttribute(fieldName),
stringLengthAttribute?.MaximumLength ?? 0, rows, fieldLabel, fieldID, groupDataBinding, labelDataBinding, requiredDataBinding, customDataBinding, dependencyFieldName, toolTip, initialFocus, enableHotLinks);
}
///
/// Generates template based text area field based on specified parameters.
///
/// Field name for text area field.
/// Determines if field name is required.
/// Defines maximum text area field length.
/// Number of rows for text area.
/// Label name for text area field, defaults to .
/// ID to use for text area field; defaults to text + .
/// Data-bind operations to apply to outer form-group div, if any.
/// Data-bind operations to apply to label, if any.
/// Boolean data-bind operation to apply to required state, if any.
/// Extra custom data-binding operations to apply to field, if any.
/// Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.
/// Tool tip text to apply to field, if any.
/// Use field for initial focus.
/// Enable clickable URLs and e-mail addresses in view mode.
/// Generated HTML for new text area field based on specified parameters.
public string AddTextAreaField(string fieldName, bool required, int maxLength = 0, int rows = 2, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false, bool enableHotLinks = true)
{
RazorView addTextAreaTemplate = new RazorView(m_razorEngine, AddTextAreaFieldTemplate, m_exceptionHandler);
DynamicViewBag viewBag = addTextAreaTemplate.ViewBag;
if (string.IsNullOrEmpty(fieldID))
fieldID = $"text{fieldName}";
if (initialFocus)
m_initialFocusField = fieldID;
if (enableHotLinks)
{
AddReadOnlyHotLinkField(fieldID, $"readonly{fieldID}", fieldName, true);
customDataBinding = $"{(string.IsNullOrWhiteSpace(customDataBinding) ? "" : $"{customDataBinding}, ")}visible: $root.recordMode()!==RecordMode.View";
}
viewBag.AddValue("FieldName", fieldName);
viewBag.AddValue("Required", required);
viewBag.AddValue("MaxLength", maxLength);
viewBag.AddValue("Rows", rows);
viewBag.AddValue("FieldLabel", fieldLabel ?? fieldName);
viewBag.AddValue("FieldID", fieldID);
viewBag.AddValue("GroupDataBinding", groupDataBinding);
viewBag.AddValue("LabelDataBinding", labelDataBinding);
viewBag.AddValue("RequiredDataBinding", requiredDataBinding);
viewBag.AddValue("CustomDataBinding", customDataBinding);
viewBag.AddValue("DependencyFieldName", dependencyFieldName);
viewBag.AddValue("ToolTip", toolTip);
return addTextAreaTemplate.Execute();
}
///
/// Generates template based select field based on reflected modeled table field attributes.
///
/// Modeled table for select field.
/// Modeled table for option data.
/// Field name for value of select field.
/// Field name for ID of option data.
/// Field name for label of option data, defaults to
/// Field name for sort order of option data, defaults to
/// Label name for select field, pulls from if defined, otherwise defaults to .
/// ID to use for select field; defaults to select + .
/// Data-bind operations to apply to outer form-group div, if any.
/// Data-bind operations to apply to label, if any.
/// Boolean data-bind operation to apply to required state, if any.
/// Extra custom data-binding operations to apply to field, if any.
/// Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.
/// Data-bind operations to apply to each option value, if any.
/// Tool tip text to apply to field, if any.
/// Use field for initial focus.
/// Record restriction to apply, if any.
/// Flag that determines if select can have no selected value.
/// Label to show when no value is selected; defaults to "Select value...".
/// Flag that determines if an empty row should be added to options list.
/// Value to use for empty row; defaults to empty string.
/// Flag that determines if an option representing no records should be shown if select query returns no values.
/// Value for no records option when select query returns no values; defaults to "-1".
/// Text for no records option when select query returns no values; defaults to "No records".
/// Generated HTML for new text field based on modeled table field attributes.
public string AddSelectField(string fieldName, string optionValueFieldName, string optionLabelFieldName = null, string optionSortFieldName = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string optionDataBinding = null, string toolTip = null, bool initialFocus = false, RecordRestriction restriction = null, bool allowUnset = false, string unsetCaption = "Select value...", bool addEmptyRow = false, string emptyRowValue = "", bool showNoRecordOption = false, string noRecordValue = "-1", string noRecordText = "No records") where TModel : class, new() where TOption : class, new()
{
if (string.IsNullOrEmpty(fieldLabel))
{
LabelAttribute labelAttribute;
if (Table().TryGetFieldAttribute(fieldName, out labelAttribute))
fieldLabel = labelAttribute.Label;
}
AddFieldValueInitializer(fieldName);
return AddSelectField(fieldName, Table().FieldHasAttribute(fieldName),
optionValueFieldName, optionLabelFieldName, optionSortFieldName, fieldLabel, fieldID, groupDataBinding, labelDataBinding, requiredDataBinding, customDataBinding, dependencyFieldName, optionDataBinding, toolTip, initialFocus, restriction, allowUnset, unsetCaption, addEmptyRow, emptyRowValue, showNoRecordOption, noRecordValue, noRecordText);
}
///
/// Generates template based select field based on reflected modeled enum field attributes.
///
/// Modeled table for select field.
/// Modeled Enum for option data.
/// Field name for value of select field.
/// Label name for select field, pulls from if defined, otherwise defaults to .
/// ID to use for select field; defaults to select + .
/// Data-bind operations to apply to outer form-group div, if any.
/// Data-bind operations to apply to label, if any.
/// Boolean data-bind operation to apply to required state, if any.
/// Extra custom data-binding operations to apply to field, if any.
/// Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.
/// Data-bind operations to apply to each option value, if any.
/// Tool tip text to apply to field, if any.
/// Use field for initial focus.
/// Flag that determines if select can have no selected value.
/// Label to show when no value is selected; defaults to "Select value...".
/// Flag that determines if an empty row should be added to options list.
/// Value to use for empty row; defaults to empty string.
/// Flag that determines if an option representing no records should be shown if select query returns no values.
/// Value for no records option when select query returns no values; defaults to "-1".
/// Text for no records option when select query returns no values; defaults to "No records".
/// Generated HTML for new text field based on modeled table field attributes.
public string AddSelectFieldFromEnum(string fieldName, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string optionDataBinding = null, string toolTip = null, bool initialFocus = false, bool allowUnset = false, string unsetCaption = "Select value...", bool addEmptyRow = false, string emptyRowValue = "", bool showNoRecordOption = false, string noRecordValue = "-1", string noRecordText = "No records") where TModel : class, new() where TOption : struct, IConvertible
{
if (string.IsNullOrEmpty(fieldLabel))
{
LabelAttribute labelAttribute;
if (Table().TryGetFieldAttribute(fieldName, out labelAttribute))
fieldLabel = labelAttribute.Label;
}
AddFieldValueInitializer(fieldName);
return AddSelectFieldFromEnum(fieldName, Table().FieldHasAttribute(fieldName),
fieldLabel, fieldID, groupDataBinding, labelDataBinding, requiredDataBinding, customDataBinding, dependencyFieldName, optionDataBinding, toolTip, initialFocus, allowUnset, unsetCaption, addEmptyRow, emptyRowValue, showNoRecordOption, noRecordValue, noRecordText);
}
///
/// Generates template based select field based on specified parameters.
///
/// Modeled table for option data.
/// Field name for value of select field.
/// Determines if field name is required.
/// Field name for ID of option data.
/// Field name for label of option data, defaults to
/// Field name for sort order of option data, defaults to
/// Label name for select field, defaults to .
/// ID to use for select field; defaults to select + .
/// Data-bind operations to apply to outer form-group div, if any.
/// Data-bind operations to apply to label, if any.
/// Boolean data-bind operation to apply to required state, if any.
/// Extra custom data-binding operations to apply to field, if any.
/// Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.
/// Data-bind operations to apply to each option value, if any.
/// Tool tip text to apply to field, if any.
/// Use field for initial focus.
/// Record restriction to apply, if any.
/// Flag that determines if select can have no selected value.
/// Label to show when no value is selected; defaults to "Select value...".
/// Flag that determines if an empty row should be added to options list.
/// Value to use for empty row; defaults to empty string.
/// Flag that determines if an option representing no records should be shown if select query returns no values.
/// Value for no records option when select query returns no values; defaults to "-1".
/// Text for no records option when select query returns no values; defaults to "No records".
/// Generated HTML for new text field based on specified parameters.
public string AddSelectField(string fieldName, bool required, string optionValueFieldName, string optionLabelFieldName = null, string optionSortFieldName = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string optionDataBinding = null, string toolTip = null, bool initialFocus = false, RecordRestriction restriction = null, bool allowUnset = false, string unsetCaption = "Select value...", bool addEmptyRow = false, string emptyRowValue = "", bool showNoRecordOption = false, string noRecordValue = "-1", string noRecordText = "No records") where TOption : class, new()
{
RazorView addSelectFieldTemplate = new RazorView(m_razorEngine, AddSelectFieldTemplate, m_exceptionHandler);
DynamicViewBag viewBag = addSelectFieldTemplate.ViewBag;
TableOperations optionTableOperations = Table();
Dictionary options = new Dictionary();
string optionTableName = typeof(TOption).Name;
optionLabelFieldName = optionLabelFieldName ?? optionValueFieldName;
optionSortFieldName = optionSortFieldName ?? optionLabelFieldName;
fieldLabel = fieldLabel ?? optionTableName;
if (string.IsNullOrEmpty(fieldID))
fieldID = $"select{fieldName}";
if (initialFocus)
m_initialFocusField = fieldID;
viewBag.AddValue("FieldName", fieldName);
viewBag.AddValue("Required", required);
viewBag.AddValue("FieldLabel", fieldLabel);
viewBag.AddValue("FieldID", fieldID);
viewBag.AddValue("GroupDataBinding", groupDataBinding);
viewBag.AddValue("LabelDataBinding", labelDataBinding);
viewBag.AddValue("RequiredDataBinding", requiredDataBinding);
viewBag.AddValue("CustomDataBinding", customDataBinding);
viewBag.AddValue("DependencyFieldName", dependencyFieldName);
viewBag.AddValue("ToolTip", toolTip);
viewBag.AddValue("OptionDataBinding", optionDataBinding);
viewBag.AddValue("AllowUnset", allowUnset);
viewBag.AddValue("UnsetCaption", unsetCaption);
if (addEmptyRow)
options.Add("", emptyRowValue);
foreach (TOption record in Table().QueryRecords(optionSortFieldName, restriction))
{
if (record != null)
options.Add(optionTableOperations.GetFieldValue(record, optionValueFieldName).ToString(), optionTableOperations.GetFieldValue(record, optionLabelFieldName).ToNonNullString(fieldLabel));
}
if (options.Count == 0 && showNoRecordOption)
options.Add(noRecordValue, noRecordText);
viewBag.AddValue("Options", options);
return addSelectFieldTemplate.Execute();
}
///
/// Generates template based select field based on specified parameters.
///
/// Modeled enum for option data.
/// Field name for value of select field.
/// Determines if field name is required.
/// Label name for select field, defaults to .
/// ID to use for select field; defaults to select + .
/// Data-bind operations to apply to outer form-group div, if any.
/// Data-bind operations to apply to label, if any.
/// Boolean data-bind operation to apply to required state, if any.
/// Extra custom data-binding operations to apply to field, if any.
/// Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.
/// Data-bind operations to apply to each option value, if any.
/// Tool tip text to apply to field, if any.
/// Use field for initial focus.
/// Flag that determines if select can have no selected value.
/// Label to show when no value is selected; defaults to "Select value...".
/// Flag that determines if an empty row should be added to options list.
/// Value to use for empty row; defaults to empty string.
/// Flag that determines if an option representing no records should be shown if select query returns no values.
/// Value for no records option when select query returns no values; defaults to "-1".
/// Text for no records option when select query returns no values; defaults to "No records".
/// Generated HTML for new text field based on specified parameters.
public string AddSelectFieldFromEnum(string fieldName, bool required, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string optionDataBinding = null, string toolTip = null, bool initialFocus = false, bool allowUnset = false, string unsetCaption = "Select value...", bool addEmptyRow = false, string emptyRowValue = "", bool showNoRecordOption = false, string noRecordValue = "-1", string noRecordText = "No records") where TOption : struct, IConvertible
{
RazorView addSelectFieldTemplate = new RazorView(m_razorEngine, AddSelectFieldTemplate, m_exceptionHandler);
DynamicViewBag viewBag = addSelectFieldTemplate.ViewBag;
Dictionary options = new Dictionary();
if (string.IsNullOrEmpty(fieldID))
fieldID = $"select{fieldName}";
if (initialFocus)
m_initialFocusField = fieldID;
viewBag.AddValue("FieldName", fieldName);
viewBag.AddValue("Required", required);
viewBag.AddValue("FieldLabel", fieldLabel);
viewBag.AddValue("FieldID", fieldID);
viewBag.AddValue("GroupDataBinding", groupDataBinding);
viewBag.AddValue("LabelDataBinding", labelDataBinding);
viewBag.AddValue("RequiredDataBinding", requiredDataBinding);
viewBag.AddValue("CustomDataBinding", customDataBinding);
viewBag.AddValue("DependencyFieldName", dependencyFieldName);
viewBag.AddValue("ToolTip", toolTip);
viewBag.AddValue("OptionDataBinding", optionDataBinding);
viewBag.AddValue("AllowUnset", allowUnset);
viewBag.AddValue("UnsetCaption", unsetCaption);
if (addEmptyRow)
options.Add("", emptyRowValue);
foreach (Enum toption in Enum.GetValues(typeof(TOption)))
{
string description = toption.GetDescription();
if (description == toption.ToString())
description = toption.GetFormattedName();
options.Add(Convert.ChangeType(toption, Enum.GetUnderlyingType(typeof(TOption))).ToString(), description);
}
if (options.Count == 0 && showNoRecordOption)
options.Add(noRecordValue, noRecordText);
viewBag.AddValue("Options", options);
return addSelectFieldTemplate.Execute();
}
///
/// Generates template based select field based on a dictionary of field attributes.
///
/// Modeled table for select field.
/// Dictionary generated to be used for the options.
/// Field name for value of select field.
/// Determines if field name is required.
/// Label name for select field, defaults to .
/// ID to use for select field; defaults to select + .
/// Data-bind operations to apply to outer form-group div, if any.
/// Data-bind operations to apply to label, if any.
/// Boolean data-bind operation to apply to required state, if any.
/// Extra custom data-binding operations to apply to field, if any.
/// Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.
/// Data-bind operations to apply to each option value, if any.
/// Tool tip text to apply to field, if any.
/// Use field for initial focus.
/// Flag that determines if select can have no selected value.
/// Label to show when no value is selected; defaults to "Select value...".
/// Flag that determines if an empty row should be added to options list.
/// Value to use for empty row; defaults to empty string.
/// Flag that determines if an option representing no records should be shown if select query returns no values.
/// Value for no records option when select query returns no values; defaults to "-1".
/// Text for no records option when select query returns no values; defaults to "No records".
/// Generated HTML for new text field based on specified parameters.
public string AddSelectField(Dictionary options, string fieldName, bool required, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string optionDataBinding = null, string toolTip = null, bool initialFocus = false, bool allowUnset = false, string unsetCaption = "Select value...", bool addEmptyRow = false, string emptyRowValue = "", bool showNoRecordOption = false, string noRecordValue = "-1", string noRecordText = "No records") where TModel : class, new()
{
RazorView addSelectFieldTemplate = new RazorView(m_razorEngine, AddSelectFieldTemplate, m_exceptionHandler);
DynamicViewBag viewBag = addSelectFieldTemplate.ViewBag;
if (string.IsNullOrEmpty(fieldLabel))
{
LabelAttribute labelAttribute;
if (Table().TryGetFieldAttribute(fieldName, out labelAttribute))
fieldLabel = labelAttribute.Label;
}
AddFieldValueInitializer(fieldName);
if (string.IsNullOrEmpty(fieldID))
fieldID = $"select{fieldName}";
if (initialFocus)
m_initialFocusField = fieldID;
viewBag.AddValue("FieldName", fieldName);
viewBag.AddValue("Required", required);
viewBag.AddValue("FieldLabel", fieldLabel);
viewBag.AddValue("FieldID", fieldID);
viewBag.AddValue("GroupDataBinding", groupDataBinding);
viewBag.AddValue("LabelDataBinding", labelDataBinding);
viewBag.AddValue("RequiredDataBinding", requiredDataBinding);
viewBag.AddValue("CustomDataBinding", customDataBinding);
viewBag.AddValue("DependencyFieldName", dependencyFieldName);
viewBag.AddValue("ToolTip", toolTip);
viewBag.AddValue("OptionDataBinding", optionDataBinding);
viewBag.AddValue("AllowUnset", allowUnset);
viewBag.AddValue("UnsetCaption", unsetCaption);
if (addEmptyRow)
options.Add("", emptyRowValue);
if (options.Count == 0 && showNoRecordOption)
options.Add(noRecordValue, noRecordText);
viewBag.AddValue("Options", options);
return addSelectFieldTemplate.Execute();
}
///
/// Generates template based check box field based on reflected modeled table field attributes.
///
/// Modeled table.
/// Field name for value of check box field.
/// Label name for check box field, defaults to .
/// ID to use for check box field; defaults to check + .
/// Data-bind operations to apply to outer form-group div, if any.
/// Data-bind operations to apply to label, if any.
/// Extra custom data-binding operations to apply to field, if any.
/// Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.
/// Tool tip text to apply to field, if any.
/// Use field for initial focus.
/// Generated HTML for new check box field based on modeled table field attributes.
public string AddCheckBoxField(string fieldName, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false) where TModel : class, new()
{
if (string.IsNullOrEmpty(fieldLabel))
{
LabelAttribute labelAttribute;
if (Table().TryGetFieldAttribute(fieldName, out labelAttribute))
fieldLabel = labelAttribute.Label;
}
AddFieldValueInitializer(fieldName);
return AddCheckBoxField(fieldName, fieldLabel, fieldID, groupDataBinding, labelDataBinding, customDataBinding, dependencyFieldName, toolTip, initialFocus);
}
///
/// Generates template based check box field based on specified parameters.
///
/// Field name for value of check box field.
/// Label name for check box field, defaults to .
/// ID to use for check box field; defaults to check + .
/// Data-bind operations to apply to outer form-group div, if any.
/// Data-bind operations to apply to label, if any.
/// Extra custom data-binding operations to apply to field, if any.
/// Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.
/// Tool tip text to apply to field, if any.
/// Use field for initial focus.
/// Generated HTML for new check box field based on specified parameters.
public string AddCheckBoxField(string fieldName, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false)
{
RazorView addCheckBoxFieldTemplate = new RazorView(m_razorEngine, AddCheckBoxFieldTemplate, m_exceptionHandler);
DynamicViewBag viewBag = addCheckBoxFieldTemplate.ViewBag;
if (string.IsNullOrEmpty(fieldID))
fieldID = $"check{fieldName}";
if (initialFocus)
m_initialFocusField = fieldID;
viewBag.AddValue("FieldName", fieldName);
viewBag.AddValue("FieldLabel", fieldLabel ?? fieldName);
viewBag.AddValue("FieldID", fieldID);
viewBag.AddValue("GroupDataBinding", groupDataBinding);
viewBag.AddValue("LabelDataBinding", labelDataBinding);
viewBag.AddValue("CustomDataBinding", customDataBinding);
viewBag.AddValue("DependencyFieldName", dependencyFieldName);
viewBag.AddValue("ToolTip", toolTip);
return addCheckBoxFieldTemplate.Execute();
}
#endregion
#region [ Static ]
// Static Fields
private static readonly ConcurrentDictionary s_recordOperationCaches;
// Static Constructor
static DataContext()
{
s_recordOperationCaches = new ConcurrentDictionary();
}
// Static Methods
private static bool IsIntegerType(Type type)
{
return
type == typeof(byte) ||
type == typeof(sbyte) ||
type == typeof(short) ||
type == typeof(ushort) ||
type == typeof(Int24) ||
type == typeof(UInt24) ||
type == typeof(int) ||
type == typeof(uint) ||
type == typeof(long) ||
type == typeof(ulong);
}
private static bool IsNumericType(Type type)
{
return
type == typeof(float) ||
type == typeof(double) ||
type == typeof(decimal);
}
#endregion
}
}