//****************************************************************************************************** // 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.Linq; using System.Net.Http; using System.Text; using System.Web.Routing; using GSF.Collections; 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 = @"^(?:(?:[a-zA-Z][a-zA-Z0-9.+-]*:\/\/)?[a-zA-Z0-9][a-zA-Z0-9.-]*(?::[0-9]+)?(?:\/[^ ""]*)?|mailto:[a-zA-Z0-9!#$%&'*+-\/=?^_`{|}~][a-zA-Z0-9!#$%&'*+-\/=?^_`{|}~.]*@[a-zA-Z0-9][a-zA-Z0-9.-]*)$"; // Fields private AdoDataConnection m_connection; private readonly IRazorEngine m_razorEngine; private readonly Dictionary m_tableOperations; private readonly Action m_exceptionHandler; 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_tableOperations = new Dictionary(); m_exceptionHandler = exceptionHandler; 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_tableOperations = new Dictionary(); m_exceptionHandler = exceptionHandler; 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 reference to user specific security provider instance. /// public AdoSecurityProvider SecurityProvider => SecurityProviderCache.CurrentProvider as AdoSecurityProvider; /// /// 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; } } #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() { return m_tableOperations.GetOrAdd(typeof(TModel), type => new TableOperations(Connection, m_exceptionHandler)) 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) { return m_tableOperations.GetOrAdd(model, type => Activator.CreateInstance(typeof(TableOperations<>).MakeGenericType(model), Connection, m_exceptionHandler)) as ITableOperations; } /// /// 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). /// /// Role or comma separated list of roles. /// true if user is in (s); otherwise, false. /// /// Set to * for any role. /// public bool UserIsInRole(string role) { 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(roles); 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. /// /// List of role names. /// true if user is in one of the ; otherwise, false. public bool UserIsInRole(string[] roles) { return roles.Any(UserIsInRole); } /// /// Determines if user is in a specific group or list of groups (comma separated). /// /// Group or comma separated list of groups. /// true if user is in (s); otherwise, false. /// /// Set to * for any group. /// public bool UserIsInGroup(string group) { 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(groups); 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. /// /// List of group names. /// true if user is in one of the ; otherwise, false. public bool UserIsInGroup(string[] groups) { return groups.Any(UserIsInGroup); } /// /// 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) { 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() { 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) { 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; // Ensure that the user's roles have been properly initialized SecurityProviderCache.ValidateCurrentProvider(); // Check current allowed roles for user viewBag.CanEdit = UserIsInRole(viewBag.EditRoles); viewBag.CanAddNew = UserIsInRole(viewBag.AddNewRoles); viewBag.CanDelete = UserIsInRole(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 keyValues = null; if (parentKeys.Length > 0) { keyValues = parentKeys.ToDelimitedString(", "); viewBag.ParentKeys = keyValues; } // If modeled table has IsDeletedField marker, the showDeleted parameter should come first in DataHub operations if (showDeletedValue != null) keyValues = keyValues != null ? $"{showDeletedValue}, {keyValues}" : showDeletedValue; if (!string.IsNullOrWhiteSpace(queryRecordCountMethod)) javascript.Append($@" viewModel.setQueryRecordCount(function (filterText) {{ return {hubScriptName}.{queryRecordCountMethod}({(keyValues == null ? "" : $"{keyValues}, ")} filterText); }}); ".FixForwardSpacing()); if (!string.IsNullOrWhiteSpace(queryRecordsMethod)) javascript.Append($@" viewModel.setQueryRecords(function (sortField, ascending, page, pageSize, filterText) {{ return {hubScriptName}.{queryRecordsMethod}({(keyValues == null ? "" : $"{keyValues}, ")}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 initialValue = null) { m_fieldValueInitializers.Add(new Tuple(fieldName, initialValue ?? "\"\"")); } /// /// 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() { InitialValueAttribute initialValueAttribute; if (Table().TryGetFieldAttribute(fieldName, out initialValueAttribute)) AddFieldValueInitializer(fieldName, initialValueAttribute.InitialValue); } /// /// 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; if (string.IsNullOrEmpty(inputType)) { Type fieldType = tableOperations.GetFieldType(fieldName); if (IsIntegerType(fieldType)) { inputType = "number"; customDataBinding = string.IsNullOrEmpty(customDataBinding) ? "integer" : $"integer, {customDataBinding}"; } else if (IsNumericType(fieldType)) { inputType = "number"; customDataBinding = string.IsNullOrEmpty(customDataBinding) ? "numeric" : $"numeric, {customDataBinding}"; } else if (fieldType == typeof(DateTime)) { inputType = "date"; } } 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 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 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 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 } }