From 29bfcdbbda596731358c7c2e44ec81fe41ebf866 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 04:19:38 +0000 Subject: [PATCH 01/11] docs: add MIGRATION_NOTES.md with ASP.NET Core/.NET 10 migration gotchas (feature: Phase 0 research) Co-Authored-By: Parker Duff --- MIGRATION_NOTES.md | 351 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 MIGRATION_NOTES.md diff --git a/MIGRATION_NOTES.md b/MIGRATION_NOTES.md new file mode 100644 index 0000000..b10f9c3 --- /dev/null +++ b/MIGRATION_NOTES.md @@ -0,0 +1,351 @@ +# Migration Notes: ASP.NET MVC 5 (.NET Framework 4.5.1) → ASP.NET Core MVC (.NET 10) + +This is a **re-platform**, not a version bump. The whole solution moves off `System.Web`, +Entity Framework 6, OWIN, and the classic MSBuild project format onto ASP.NET Core MVC, +EF Core, the built-in generic host, and SDK-style `net10.0` projects. + +Projects (dependency order): `DataLayer` → `BizLayer` → `ServiceLayer` → `SampleWebApp`; `Tests` sits on top. + +> ⚠️ **HIGHEST-RISK ITEM: `GenericServices` 1.0.9 → `EfCore.GenericServices`.** The public API +> shape has changed **substantially** (see §3). Every controller action and every DTO in +> `ServiceLayer` is affected. Coordinate the `ICrudServices` / DTO surface between the +> data/service subsession and the web subsession before finalizing. + +--- + +## Legacy dependency → chosen replacement (summary) + +| Legacy (packages.config, `net451`) | Replacement (`net10.0` `PackageReference`) | +|---|---| +| `EntityFramework` 6.1.3 (`System.Data.Entity`) | `Microsoft.EntityFrameworkCore` + `Microsoft.EntityFrameworkCore.SqlServer` + `Microsoft.EntityFrameworkCore.Design` (10.x) | +| `GenericServices` 1.0.9 (EF6-based) | `EfCore.GenericServices` (5.x) + `EfCore.GenericServices.AspNetCore` (for `CopyErrorsToModelState`) | +| `GenericLibsBase` 1.0.1 (`IGenericLogger`, `ISuccessOrErrors`) | `Microsoft.Extensions.Logging` (`ILogger`) + EfCore.GenericServices' `IStatusGeneric` | +| `Autofac` 3.5 + `Autofac.Mvc5` 3.3.1 | Built-in `IServiceCollection` DI (simplest), or `Autofac` + `Autofac.Extensions.DependencyInjection` | +| `AutoMapper` 4.2.1 / 3.2.1 (BizLayer) | `AutoMapper` current (13.x) — but EfCore.GenericServices bundles/registers its own mapper; explicit AutoMapper only needed for the BizLayer if it still maps directly | +| `Microsoft.AspNet.Mvc` 5.2.3 + `System.Web.Mvc` | `Microsoft.AspNetCore.Mvc` (framework reference `Microsoft.AspNetCore.App`) | +| `System.Web.Optimization` + `WebGrease` + `Modernizr` + `Respond` (bundling) | `wwwroot/` static files + ``/`."); - } - }; - - _pageWindow.load(function () { _pageLoaded = true; }); - - function validateTransport(requestedTransport, connection) { - /// Validates the requested transport by cross checking it with the pre-defined signalR.transports - /// The designated transports that the user has specified. - /// The connection that will be using the requested transports. Used for logging purposes. - /// - - if ($.isArray(requestedTransport)) { - // Go through transport array and remove an "invalid" tranports - for (var i = requestedTransport.length - 1; i >= 0; i--) { - var transport = requestedTransport[i]; - if ($.type(transport) !== "string" || !signalR.transports[transport]) { - connection.log("Invalid transport: " + transport + ", removing it from the transports list."); - requestedTransport.splice(i, 1); - } - } - - // Verify we still have transports left, if we dont then we have invalid transports - if (requestedTransport.length === 0) { - connection.log("No transports remain within the specified transport array."); - requestedTransport = null; - } - } else if (!signalR.transports[requestedTransport] && requestedTransport !== "auto") { - connection.log("Invalid transport: " + requestedTransport.toString() + "."); - requestedTransport = null; - } else if (requestedTransport === "auto" && signalR._.ieVersion <= 8) { - // If we're doing an auto transport and we're IE8 then force longPolling, #1764 - return ["longPolling"]; - - } - - return requestedTransport; - } - - function getDefaultPort(protocol) { - if (protocol === "http:") { - return 80; - } else if (protocol === "https:") { - return 443; - } - } - - function addDefaultPort(protocol, url) { - // Remove ports from url. We have to check if there's a / or end of line - // following the port in order to avoid removing ports such as 8080. - if (url.match(/:\d+$/)) { - return url; - } else { - return url + ":" + getDefaultPort(protocol); - } - } - - function ConnectingMessageBuffer(connection, drainCallback) { - var that = this, - buffer = []; - - that.tryBuffer = function (message) { - if (connection.state === $.signalR.connectionState.connecting) { - buffer.push(message); - - return true; - } - - return false; - }; - - that.drain = function () { - // Ensure that the connection is connected when we drain (do not want to drain while a connection is not active) - if (connection.state === $.signalR.connectionState.connected) { - while (buffer.length > 0) { - drainCallback(buffer.shift()); - } - } - }; - - that.clear = function () { - buffer = []; - }; - } - - signalR.fn = signalR.prototype = { - init: function (url, qs, logging) { - var $connection = $(this); - - this.url = url; - this.qs = qs; - this._ = { - keepAliveData: {}, - connectingMessageBuffer: new ConnectingMessageBuffer(this, function (message) { - $connection.triggerHandler(events.onReceived, [message]); - }), - onFailedTimeoutHandle: null, - lastMessageAt: new Date().getTime(), - lastActiveAt: new Date().getTime(), - beatInterval: 5000, // Default value, will only be overridden if keep alive is enabled, - beatHandle: null, - totalTransportConnectTimeout: 0 // This will be the sum of the TransportConnectTimeout sent in response to negotiate and connection.transportConnectTimeout - }; - if (typeof (logging) === "boolean") { - this.logging = logging; - } - }, - - _parseResponse: function (response) { - var that = this; - - if (!response) { - return response; - } else if (typeof response === "string") { - return that.json.parse(response); - } else { - return response; - } - }, - - json: window.JSON, - - isCrossDomain: function (url, against) { - /// Checks if url is cross domain - /// The base URL - /// - /// An optional argument to compare the URL against, if not specified it will be set to window.location. - /// If specified it must contain a protocol and a host property. - /// - var link; - - url = $.trim(url); - - against = against || window.location; - - if (url.indexOf("http") !== 0) { - return false; - } - - // Create an anchor tag. - link = window.document.createElement("a"); - link.href = url; - - // When checking for cross domain we have to special case port 80 because the window.location will remove the - return link.protocol + addDefaultPort(link.protocol, link.host) !== against.protocol + addDefaultPort(against.protocol, against.host); - }, - - ajaxDataType: "text", - - contentType: "application/json; charset=UTF-8", - - logging: false, - - state: signalR.connectionState.disconnected, - - clientProtocol: "1.3", - - reconnectDelay: 2000, - - transportConnectTimeout: 0, - - disconnectTimeout: 30000, // This should be set by the server in response to the negotiate request (30s default) - - reconnectWindow: 30000, // This should be set by the server in response to the negotiate request - - keepAliveWarnAt: 2 / 3, // Warn user of slow connection if we breach the X% mark of the keep alive timeout - - start: function (options, callback) { - /// Starts the connection - /// Options map - /// A callback function to execute when the connection has started - var connection = this, - config = { - pingInterval: 300000, - waitForPageLoad: true, - transport: "auto", - jsonp: false - }, - initialize, - deferred = connection._deferral || $.Deferred(), // Check to see if there is a pre-existing deferral that's being built on, if so we want to keep using it - parser = window.document.createElement("a"); - - // Persist the deferral so that if start is called multiple times the same deferral is used. - connection._deferral = deferred; - - if (!connection.json) { - // no JSON! - throw new Error("SignalR: No JSON parser found. Please ensure json2.js is referenced before the SignalR.js file if you need to support clients without native JSON parsing support, e.g. IE<8."); - } - - if ($.type(options) === "function") { - // Support calling with single callback parameter - callback = options; - } else if ($.type(options) === "object") { - $.extend(config, options); - if ($.type(config.callback) === "function") { - callback = config.callback; - } - } - - config.transport = validateTransport(config.transport, connection); - - // If the transport is invalid throw an error and abort start - if (!config.transport) { - throw new Error("SignalR: Invalid transport(s) specified, aborting start."); - } - - connection._.config = config; - - // Check to see if start is being called prior to page load - // If waitForPageLoad is true we then want to re-direct function call to the window load event - if (!_pageLoaded && config.waitForPageLoad === true) { - connection._.deferredStartHandler = function () { - connection.start(options, callback); - }; - _pageWindow.bind("load", connection._.deferredStartHandler); - - return deferred.promise(); - } - - // If we're already connecting just return the same deferral as the original connection start - if (connection.state === signalR.connectionState.connecting) { - return deferred.promise(); - } else if (changeState(connection, - signalR.connectionState.disconnected, - signalR.connectionState.connecting) === false) { - // We're not connecting so try and transition into connecting. - // If we fail to transition then we're either in connected or reconnecting. - - deferred.resolve(connection); - return deferred.promise(); - } - - configureStopReconnectingTimeout(connection); - - // Resolve the full url - parser.href = connection.url; - if (!parser.protocol || parser.protocol === ":") { - connection.protocol = window.document.location.protocol; - connection.host = window.document.location.host; - connection.baseUrl = connection.protocol + "//" + connection.host; - } else { - connection.protocol = parser.protocol; - connection.host = parser.host; - connection.baseUrl = parser.protocol + "//" + parser.host; - } - - // Set the websocket protocol - connection.wsProtocol = connection.protocol === "https:" ? "wss://" : "ws://"; - - // If jsonp with no/auto transport is specified, then set the transport to long polling - // since that is the only transport for which jsonp really makes sense. - // Some developers might actually choose to specify jsonp for same origin requests - // as demonstrated by Issue #623. - if (config.transport === "auto" && config.jsonp === true) { - config.transport = "longPolling"; - } - - // If the url is protocol relative, prepend the current windows protocol to the url. - if (connection.url.indexOf("//") === 0) { - connection.url = window.location.protocol + connection.url; - connection.log("Protocol relative URL detected, normalizing it to '" + connection.url + "'."); - } - - if (this.isCrossDomain(connection.url)) { - connection.log("Auto detected cross domain url."); - - if (config.transport === "auto") { - // TODO: Support XDM with foreverFrame - config.transport = ["webSockets", "serverSentEvents", "longPolling"]; - } - - if (typeof (config.withCredentials) === "undefined") { - config.withCredentials = true; - } - - // Determine if jsonp is the only choice for negotiation, ajaxSend and ajaxAbort. - // i.e. if the browser doesn't supports CORS - // If it is, ignore any preference to the contrary, and switch to jsonp. - if (!config.jsonp) { - config.jsonp = !$.support.cors; - - if (config.jsonp) { - connection.log("Using jsonp because this browser doesn't support CORS."); - } - } - - connection.contentType = signalR._.defaultContentType; - } - - connection.withCredentials = config.withCredentials; - - connection.ajaxDataType = config.jsonp ? "jsonp" : "text"; - - $(connection).bind(events.onStart, function (e, data) { - if ($.type(callback) === "function") { - callback.call(connection); - } - deferred.resolve(connection); - }); - - initialize = function (transports, index) { - var noTransportError = signalR._.error(resources.noTransportOnInit); - - index = index || 0; - if (index >= transports.length) { - // No transport initialized successfully - $(connection).triggerHandler(events.onError, [noTransportError]); - deferred.reject(noTransportError); - // Stop the connection if it has connected and move it into the disconnected state - connection.stop(); - return; - } - - // The connection was aborted - if (connection.state === signalR.connectionState.disconnected) { - return; - } - - var transportName = transports[index], - transport = signalR.transports[transportName], - initializationComplete = false, - onFailed = function () { - // Check if we've already triggered onFailed, onStart - if (!initializationComplete) { - initializationComplete = true; - window.clearTimeout(connection._.onFailedTimeoutHandle); - transport.stop(connection); - initialize(transports, index + 1); - } - }; - - connection.transport = transport; - - try { - connection._.onFailedTimeoutHandle = window.setTimeout(function () { - connection.log(transport.name + " timed out when trying to connect."); - onFailed(); - }, connection._.totalTransportConnectTimeout); - - transport.start(connection, function () { // success - // Firefox 11+ doesn't allow sync XHR withCredentials: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#withCredentials - var isFirefox11OrGreater = signalR._.firefoxMajorVersion(window.navigator.userAgent) >= 11, - asyncAbort = !!connection.withCredentials && isFirefox11OrGreater; - - // The connection was aborted while initializing transports - if (connection.state === signalR.connectionState.disconnected) { - return; - } - - if (!initializationComplete) { - initializationComplete = true; - - window.clearTimeout(connection._.onFailedTimeoutHandle); - - if (transport.supportsKeepAlive && connection._.keepAliveData.activated) { - signalR.transports._logic.monitorKeepAlive(connection); - } - - signalR.transports._logic.startHeartbeat(connection); - - // Used to ensure low activity clients maintain their authentication. - // Must be configured once a transport has been decided to perform valid ping requests. - signalR._.configurePingInterval(connection); - - changeState(connection, - signalR.connectionState.connecting, - signalR.connectionState.connected); - - // Drain any incoming buffered messages (messages that came in prior to connect) - connection._.connectingMessageBuffer.drain(); - - $(connection).triggerHandler(events.onStart); - - // wire the stop handler for when the user leaves the page - _pageWindow.bind("unload", function () { - connection.log("Window unloading, stopping the connection."); - - connection.stop(asyncAbort); - }); - - if (isFirefox11OrGreater) { - // Firefox does not fire cross-domain XHRs in the normal unload handler on tab close. - // #2400 - _pageWindow.bind("beforeunload", function () { - // If connection.stop() runs runs in beforeunload and fails, it will also fail - // in unload unless connection.stop() runs after a timeout. - window.setTimeout(function () { - connection.stop(asyncAbort); - }, 0); - }); - } - } - }, onFailed); - } - catch (error) { - connection.log(transport.name + " transport threw '" + error.message + "' when attempting to start."); - onFailed(); - } - }; - - var url = connection.url + "/negotiate", - onFailed = function (error, connection) { - var err = signalR._.error(resources.errorOnNegotiate, error, connection._.negotiateRequest); - - $(connection).triggerHandler(events.onError, err); - deferred.reject(err); - // Stop the connection if negotiate failed - connection.stop(); - }; - - $(connection).triggerHandler(events.onStarting); - - url = signalR.transports._logic.prepareQueryString(connection, url); - - // Add the client version to the negotiate request. We utilize the same addQs method here - // so that it can append the clientVersion appropriately to the URL - url = signalR.transports._logic.addQs(url, { - clientProtocol: connection.clientProtocol - }); - - connection.log("Negotiating with '" + url + "'."); - - // Save the ajax negotiate request object so we can abort it if stop is called while the request is in flight. - connection._.negotiateRequest = $.ajax( - $.extend({}, $.signalR.ajaxDefaults, { - xhrFields: { withCredentials: connection.withCredentials }, - url: url, - type: "GET", - contentType: connection.contentType, - data: {}, - dataType: connection.ajaxDataType, - error: function (error, statusText) { - // We don't want to cause any errors if we're aborting our own negotiate request. - if (statusText !== _negotiateAbortText) { - onFailed(error, connection); - } else { - // This rejection will noop if the deferred has already been resolved or rejected. - deferred.reject(signalR._.error(resources.stoppedWhileNegotiating, null /* error */, connection._.negotiateRequest)); - } - }, - success: function (result) { - var res, - keepAliveData, - protocolError, - transports = [], - supportedTransports = []; - - try { - res = connection._parseResponse(result); - } catch (error) { - onFailed(signalR._.error(resources.errorParsingNegotiateResponse, error), connection); - return; - } - - keepAliveData = connection._.keepAliveData; - connection.appRelativeUrl = res.Url; - connection.id = res.ConnectionId; - connection.token = res.ConnectionToken; - connection.webSocketServerUrl = res.WebSocketServerUrl; - - // Once the server has labeled the PersistentConnection as Disconnected, we should stop attempting to reconnect - // after res.DisconnectTimeout seconds. - connection.disconnectTimeout = res.DisconnectTimeout * 1000; // in ms - - // Add the TransportConnectTimeout from the response to the transportConnectTimeout from the client to calculate the total timeout - connection._.totalTransportConnectTimeout = connection.transportConnectTimeout + res.TransportConnectTimeout * 1000; - - // If we have a keep alive - if (res.KeepAliveTimeout) { - // Register the keep alive data as activated - keepAliveData.activated = true; - - // Timeout to designate when to force the connection into reconnecting converted to milliseconds - keepAliveData.timeout = res.KeepAliveTimeout * 1000; - - // Timeout to designate when to warn the developer that the connection may be dead or is not responding. - keepAliveData.timeoutWarning = keepAliveData.timeout * connection.keepAliveWarnAt; - - // Instantiate the frequency in which we check the keep alive. It must be short in order to not miss/pick up any changes - connection._.beatInterval = (keepAliveData.timeout - keepAliveData.timeoutWarning) / 3; - } else { - keepAliveData.activated = false; - } - - connection.reconnectWindow = connection.disconnectTimeout + (keepAliveData.timeout || 0); - - if (!res.ProtocolVersion || res.ProtocolVersion !== connection.clientProtocol) { - protocolError = signalR._.error(signalR._.format(resources.protocolIncompatible, connection.clientProtocol, res.ProtocolVersion)); - $(connection).triggerHandler(events.onError, [protocolError]); - deferred.reject(protocolError); - - return; - } - - $.each(signalR.transports, function (key) { - if ((key.indexOf("_") === 0) || (key === "webSockets" && !res.TryWebSockets)) { - return true; - } - supportedTransports.push(key); - }); - - if ($.isArray(config.transport)) { - $.each(config.transport, function (_, transport) { - if ($.inArray(transport, supportedTransports) >= 0) { - transports.push(transport); - } - }); - } else if (config.transport === "auto") { - transports = supportedTransports; - } else if ($.inArray(config.transport, supportedTransports) >= 0) { - transports.push(config.transport); - } - - initialize(transports); - } - } - )); - - return deferred.promise(); - }, - - starting: function (callback) { - /// Adds a callback that will be invoked before anything is sent over the connection - /// A callback function to execute before the connection is fully instantiated. - /// - var connection = this; - $(connection).bind(events.onStarting, function (e, data) { - callback.call(connection); - }); - return connection; - }, - - send: function (data) { - /// Sends data over the connection - /// The data to send over the connection - /// - var connection = this; - - if (connection.state === signalR.connectionState.disconnected) { - // Connection hasn't been started yet - throw new Error("SignalR: Connection must be started before data can be sent. Call .start() before .send()"); - } - - if (connection.state === signalR.connectionState.connecting) { - // Connection hasn't been started yet - throw new Error("SignalR: Connection has not been fully initialized. Use .start().done() or .start().fail() to run logic after the connection has started."); - } - - connection.transport.send(connection, data); - // REVIEW: Should we return deferred here? - return connection; - }, - - received: function (callback) { - /// Adds a callback that will be invoked after anything is received over the connection - /// A callback function to execute when any data is received on the connection - /// - var connection = this; - $(connection).bind(events.onReceived, function (e, data) { - callback.call(connection, data); - }); - return connection; - }, - - stateChanged: function (callback) { - /// Adds a callback that will be invoked when the connection state changes - /// A callback function to execute when the connection state changes - /// - var connection = this; - $(connection).bind(events.onStateChanged, function (e, data) { - callback.call(connection, data); - }); - return connection; - }, - - error: function (callback) { - /// Adds a callback that will be invoked after an error occurs with the connection - /// A callback function to execute when an error occurs on the connection - /// - var connection = this; - $(connection).bind(events.onError, function (e, errorData, sendData) { - // In practice 'errorData' is the SignalR built error object. - // In practice 'sendData' is undefined for all error events except those triggered by - // 'ajaxSend' and 'webSockets.send'.'sendData' is the original send payload. - callback.call(connection, errorData, sendData); - }); - return connection; - }, - - disconnected: function (callback) { - /// Adds a callback that will be invoked when the client disconnects - /// A callback function to execute when the connection is broken - /// - var connection = this; - $(connection).bind(events.onDisconnect, function (e, data) { - callback.call(connection); - }); - return connection; - }, - - connectionSlow: function (callback) { - /// Adds a callback that will be invoked when the client detects a slow connection - /// A callback function to execute when the connection is slow - /// - var connection = this; - $(connection).bind(events.onConnectionSlow, function (e, data) { - callback.call(connection); - }); - - return connection; - }, - - reconnecting: function (callback) { - /// Adds a callback that will be invoked when the underlying transport begins reconnecting - /// A callback function to execute when the connection enters a reconnecting state - /// - var connection = this; - $(connection).bind(events.onReconnecting, function (e, data) { - callback.call(connection); - }); - return connection; - }, - - reconnected: function (callback) { - /// Adds a callback that will be invoked when the underlying transport reconnects - /// A callback function to execute when the connection is restored - /// - var connection = this; - $(connection).bind(events.onReconnect, function (e, data) { - callback.call(connection); - }); - return connection; - }, - - stop: function (async, notifyServer) { - /// Stops listening - /// Whether or not to asynchronously abort the connection - /// Whether we want to notify the server that we are aborting the connection - /// - var connection = this, - // Save deferral because this is always cleaned up - deferral = connection._deferral; - - // Verify that we've bound a load event. - if (connection._.deferredStartHandler) { - // Unbind the event. - _pageWindow.unbind("load", connection._.deferredStartHandler); - } - - // Always clean up private non-timeout based state. - delete connection._deferral; - delete connection._.config; - delete connection._.deferredStartHandler; - - // This needs to be checked despite the connection state because a connection start can be deferred until page load. - // If we've deferred the start due to a page load we need to unbind the "onLoad" -> start event. - if (!_pageLoaded && (!connection._.config || connection._.config.waitForPageLoad === true)) { - connection.log("Stopping connection prior to negotiate."); - - // If we have a deferral we should reject it - if (deferral) { - deferral.reject(signalR._.error(resources.stoppedWhileLoading)); - } - - // Short-circuit because the start has not been fully started. - return; - } - - if (connection.state === signalR.connectionState.disconnected) { - return; - } - - connection.log("Stopping connection."); - - changeState(connection, connection.state, signalR.connectionState.disconnected); - - // Clear this no matter what - window.clearTimeout(connection._.beatHandle); - window.clearTimeout(connection._.onFailedTimeoutHandle); - window.clearInterval(connection._.pingIntervalId); - - if (connection.transport) { - connection.transport.stop(connection); - - if (notifyServer !== false) { - connection.transport.abort(connection, async); - } - - if (connection.transport.supportsKeepAlive && connection._.keepAliveData.activated) { - signalR.transports._logic.stopMonitoringKeepAlive(connection); - } - - connection.transport = null; - } - - if (connection._.negotiateRequest) { - // If the negotiation request has already completed this will noop. - connection._.negotiateRequest.abort(_negotiateAbortText); - delete connection._.negotiateRequest; - } - - // Trigger the disconnect event - $(connection).triggerHandler(events.onDisconnect); - - delete connection.messageId; - delete connection.groupsToken; - delete connection.id; - delete connection._.pingIntervalId; - delete connection._.lastMessageAt; - delete connection._.lastActiveAt; - - // Clear out our message buffer - connection._.connectingMessageBuffer.clear(); - - return connection; - }, - - log: function (msg) { - log(msg, this.logging); - } - }; - - signalR.fn.init.prototype = signalR.fn; - - signalR.noConflict = function () { - /// Reinstates the original value of $.connection and returns the signalR object for manual assignment - /// - if ($.connection === signalR) { - $.connection = _connection; - } - return signalR; - }; - - if ($.connection) { - _connection = $.connection; - } - - $.connection = $.signalR = signalR; - -}(window.jQuery, window)); -/* jquery.signalR.transports.common.js */ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information. - -/*global window:false */ -/// - -(function ($, window, undefined) { - "use strict"; - - var signalR = $.signalR, - events = $.signalR.events, - changeState = $.signalR.changeState, - transportLogic; - - signalR.transports = {}; - - function beat(connection) { - if (connection._.keepAliveData.monitoring) { - checkIfAlive(connection); - } - - // Ensure that we successfully marked active before continuing the heartbeat. - if (transportLogic.markActive(connection)) { - connection._.beatHandle = window.setTimeout(function () { - beat(connection); - }, connection._.beatInterval); - } - } - - function checkIfAlive(connection) { - var keepAliveData = connection._.keepAliveData, - timeElapsed; - - // Only check if we're connected - if (connection.state === signalR.connectionState.connected) { - timeElapsed = new Date().getTime() - connection._.lastMessageAt; - - // Check if the keep alive has completely timed out - if (timeElapsed >= keepAliveData.timeout) { - connection.log("Keep alive timed out. Notifying transport that connection has been lost."); - - // Notify transport that the connection has been lost - connection.transport.lostConnection(connection); - } else if (timeElapsed >= keepAliveData.timeoutWarning) { - // This is to assure that the user only gets a single warning - if (!keepAliveData.userNotified) { - connection.log("Keep alive has been missed, connection may be dead/slow."); - $(connection).triggerHandler(events.onConnectionSlow); - keepAliveData.userNotified = true; - } - } else { - keepAliveData.userNotified = false; - } - } - } - - function addConnectionData(url, connectionData) { - var appender = url.indexOf("?") !== -1 ? "&" : "?"; - - if (connectionData) { - url += appender + "connectionData=" + window.encodeURIComponent(connectionData); - } - - return url; - } - - transportLogic = signalR.transports._logic = { - pingServer: function (connection) { - /// Pings the server - /// Connection associated with the server ping - /// - var url, deferral = $.Deferred(), xhr; - - if (connection.transport) { - url = connection.url + "/ping"; - - url = transportLogic.addQs(url, connection.qs); - - xhr = $.ajax( - $.extend({}, $.signalR.ajaxDefaults, { - xhrFields: { withCredentials: connection.withCredentials }, - url: url, - type: "GET", - contentType: connection.contentType, - data: {}, - dataType: connection.ajaxDataType, - success: function (result) { - var data; - - try { - data = connection._parseResponse(result); - } - catch (error) { - deferral.reject( - signalR._.transportError( - signalR.resources.pingServerFailedParse, - connection.transport, - error, - xhr - ) - ); - connection.stop(); - return; - } - - if (data.Response === "pong") { - deferral.resolve(); - } - else { - deferral.reject( - signalR._.transportError( - signalR._.format(signalR.resources.pingServerFailedInvalidResponse, result.responseText), - connection.transport, - null /* error */, - xhr - ) - ); - } - }, - error: function (error) { - if (error.status === 401 || error.status === 403) { - deferral.reject( - signalR._.transportError( - signalR._.format(signalR.resources.pingServerFailedStatusCode, error.status), - connection.transport, - error, - xhr - ) - ); - connection.stop(); - } - else { - deferral.reject( - signalR._.transportError( - signalR.resources.pingServerFailed, - connection.transport, - error, - xhr - ) - ); - } - } - } - )); - - } - else { - deferral.reject( - signalR._.transportError( - signalR.resources.noConnectionTransport, - connection.transport - ) - ); - } - - return deferral.promise(); - }, - - prepareQueryString: function (connection, url) { - url = transportLogic.addQs(url, connection.qs); - - return addConnectionData(url, connection.data); - }, - - addQs: function (url, qs) { - var appender = url.indexOf("?") !== -1 ? "&" : "?", - firstChar; - - if (!qs) { - return url; - } - - if (typeof (qs) === "object") { - return url + appender + $.param(qs); - } - - if (typeof (qs) === "string") { - firstChar = qs.charAt(0); - - if (firstChar === "?" || firstChar === "&") { - appender = ""; - } - - return url + appender + qs; - } - - throw new Error("Query string property must be either a string or object."); - }, - - getUrl: function (connection, transport, reconnecting, poll) { - /// Gets the url for making a GET based connect request - var baseUrl = transport === "webSockets" ? "" : connection.baseUrl, - url = baseUrl + connection.appRelativeUrl, - qs = "transport=" + transport + "&connectionToken=" + window.encodeURIComponent(connection.token); - - if (connection.groupsToken) { - qs += "&groupsToken=" + window.encodeURIComponent(connection.groupsToken); - } - - if (!reconnecting) { - url += "/connect"; - } else { - if (poll) { - // longPolling transport specific - url += "/poll"; - } else { - url += "/reconnect"; - } - - if (connection.messageId) { - qs += "&messageId=" + window.encodeURIComponent(connection.messageId); - } - } - url += "?" + qs; - url = transportLogic.prepareQueryString(connection, url); - url += "&tid=" + Math.floor(Math.random() * 11); - return url; - }, - - maximizePersistentResponse: function (minPersistentResponse) { - return { - MessageId: minPersistentResponse.C, - Messages: minPersistentResponse.M, - Initialized: typeof (minPersistentResponse.S) !== "undefined" ? true : false, - Disconnect: typeof (minPersistentResponse.D) !== "undefined" ? true : false, - ShouldReconnect: typeof (minPersistentResponse.T) !== "undefined" ? true : false, - LongPollDelay: minPersistentResponse.L, - GroupsToken: minPersistentResponse.G - }; - }, - - updateGroups: function (connection, groupsToken) { - if (groupsToken) { - connection.groupsToken = groupsToken; - } - }, - - stringifySend: function (connection, message) { - if (typeof (message) === "string" || typeof (message) === "undefined" || message === null) { - return message; - } - return connection.json.stringify(message); - }, - - ajaxSend: function (connection, data) { - var payload = transportLogic.stringifySend(connection, data), - url = connection.url + "/send" + "?transport=" + connection.transport.name + "&connectionToken=" + window.encodeURIComponent(connection.token), - xhr, - onFail = function (error, connection) { - $(connection).triggerHandler(events.onError, [signalR._.transportError(signalR.resources.sendFailed, connection.transport, error, xhr), data]); - }; - - url = transportLogic.prepareQueryString(connection, url); - - xhr = $.ajax( - $.extend({}, $.signalR.ajaxDefaults, { - xhrFields: { withCredentials: connection.withCredentials }, - url: url, - type: connection.ajaxDataType === "jsonp" ? "GET" : "POST", - contentType: signalR._.defaultContentType, - dataType: connection.ajaxDataType, - data: { - data: payload - }, - success: function (result) { - var res; - - if (result) { - try { - res = connection._parseResponse(result); - } - catch (error) { - onFail(error, connection); - connection.stop(); - return; - } - - transportLogic.triggerReceived(connection, res); - } - }, - error: function (error, textStatus) { - if (textStatus === "abort" || textStatus === "parsererror") { - // The parsererror happens for sends that don't return any data, and hence - // don't write the jsonp callback to the response. This is harder to fix on the server - // so just hack around it on the client for now. - return; - } - - onFail(error, connection); - } - } - )); - - return xhr; - }, - - ajaxAbort: function (connection, async) { - if (typeof (connection.transport) === "undefined") { - return; - } - - // Async by default unless explicitly overidden - async = typeof async === "undefined" ? true : async; - - var url = connection.url + "/abort" + "?transport=" + connection.transport.name + "&connectionToken=" + window.encodeURIComponent(connection.token); - url = transportLogic.prepareQueryString(connection, url); - - $.ajax( - $.extend({}, $.signalR.ajaxDefaults, { - xhrFields: { withCredentials: connection.withCredentials }, - url: url, - async: async, - timeout: 1000, - type: "POST", - contentType: connection.contentType, - dataType: connection.ajaxDataType, - data: {} - } - )); - - connection.log("Fired ajax abort async = " + async + "."); - }, - - tryInitialize: function (persistentResponse, onInitialized) { - if (persistentResponse.Initialized) { - onInitialized(); - } - }, - - triggerReceived: function (connection, data) { - if (!connection._.connectingMessageBuffer.tryBuffer(data)) { - $(connection).triggerHandler(events.onReceived, [data]); - } - }, - - processMessages: function (connection, minData, onInitialized) { - var data; - - // Update the last message time stamp - transportLogic.markLastMessage(connection); - - if (minData) { - data = transportLogic.maximizePersistentResponse(minData); - - if (data.Disconnect) { - connection.log("Disconnect command received from server."); - - // Disconnected by the server - connection.stop(false, false); - return; - } - - transportLogic.updateGroups(connection, data.GroupsToken); - - if (data.MessageId) { - connection.messageId = data.MessageId; - } - - if (data.Messages) { - $.each(data.Messages, function (index, message) { - transportLogic.triggerReceived(connection, message); - }); - - transportLogic.tryInitialize(data, onInitialized); - } - } - }, - - monitorKeepAlive: function (connection) { - var keepAliveData = connection._.keepAliveData; - - // If we haven't initiated the keep alive timeouts then we need to - if (!keepAliveData.monitoring) { - keepAliveData.monitoring = true; - - transportLogic.markLastMessage(connection); - - // Save the function so we can unbind it on stop - connection._.keepAliveData.reconnectKeepAliveUpdate = function () { - // Mark a new message so that keep alive doesn't time out connections - transportLogic.markLastMessage(connection); - }; - - // Update Keep alive on reconnect - $(connection).bind(events.onReconnect, connection._.keepAliveData.reconnectKeepAliveUpdate); - - connection.log("Now monitoring keep alive with a warning timeout of " + keepAliveData.timeoutWarning + " and a connection lost timeout of " + keepAliveData.timeout + "."); - } else { - connection.log("Tried to monitor keep alive but it's already being monitored."); - } - }, - - stopMonitoringKeepAlive: function (connection) { - var keepAliveData = connection._.keepAliveData; - - // Only attempt to stop the keep alive monitoring if its being monitored - if (keepAliveData.monitoring) { - // Stop monitoring - keepAliveData.monitoring = false; - - // Remove the updateKeepAlive function from the reconnect event - $(connection).unbind(events.onReconnect, connection._.keepAliveData.reconnectKeepAliveUpdate); - - // Clear all the keep alive data - connection._.keepAliveData = {}; - connection.log("Stopping the monitoring of the keep alive."); - } - }, - - startHeartbeat: function (connection) { - connection._.lastActiveAt = new Date().getTime(); - beat(connection); - }, - - markLastMessage: function (connection) { - connection._.lastMessageAt = new Date().getTime(); - }, - - markActive: function (connection) { - if (transportLogic.verifyLastActive(connection)) { - connection._.lastActiveAt = new Date().getTime(); - return true; - } - - return false; - }, - - isConnectedOrReconnecting: function (connection) { - return connection.state === signalR.connectionState.connected || - connection.state === signalR.connectionState.reconnecting; - }, - - ensureReconnectingState: function (connection) { - if (changeState(connection, - signalR.connectionState.connected, - signalR.connectionState.reconnecting) === true) { - $(connection).triggerHandler(events.onReconnecting); - } - return connection.state === signalR.connectionState.reconnecting; - }, - - clearReconnectTimeout: function (connection) { - if (connection && connection._.reconnectTimeout) { - window.clearTimeout(connection._.reconnectTimeout); - delete connection._.reconnectTimeout; - } - }, - - verifyLastActive: function (connection) { - if (new Date().getTime() - connection._.lastActiveAt >= connection.reconnectWindow) { - connection.log("There has not been an active server connection for an extended period of time. Stopping connection."); - connection.stop(); - return false; - } - - return true; - }, - - reconnect: function (connection, transportName) { - var transport = signalR.transports[transportName]; - - // We should only set a reconnectTimeout if we are currently connected - // and a reconnectTimeout isn't already set. - if (transportLogic.isConnectedOrReconnecting(connection) && !connection._.reconnectTimeout) { - // Need to verify before the setTimeout occurs because an application sleep could occur during the setTimeout duration. - if (!transportLogic.verifyLastActive(connection)) { - return; - } - - connection._.reconnectTimeout = window.setTimeout(function () { - if (!transportLogic.verifyLastActive(connection)) { - return; - } - - transport.stop(connection); - - if (transportLogic.ensureReconnectingState(connection)) { - connection.log(transportName + " reconnecting."); - transport.start(connection); - } - }, connection.reconnectDelay); - } - }, - - handleParseFailure: function (connection, result, error, onFailed, context) { - // If we're in the initialization phase trigger onFailed, otherwise stop the connection. - if (connection.state === signalR.connectionState.connecting) { - connection.log("Failed to parse server response while attempting to connect."); - onFailed(); - } else { - $(connection).triggerHandler(events.onError, [ - signalR._.transportError( - signalR._.format(signalR.resources.parseFailed, result), - connection.transport, - error, - context)]); - connection.stop(); - } - }, - - foreverFrame: { - count: 0, - connections: {} - } - }; - -}(window.jQuery, window)); -/* jquery.signalR.transports.webSockets.js */ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information. - -/*global window:false */ -/// - -(function ($, window, undefined) { - "use strict"; - - var signalR = $.signalR, - events = $.signalR.events, - changeState = $.signalR.changeState, - transportLogic = signalR.transports._logic; - - signalR.transports.webSockets = { - name: "webSockets", - - supportsKeepAlive: true, - - send: function (connection, data) { - var payload = transportLogic.stringifySend(connection, data); - - try { - connection.socket.send(payload); - } catch (ex) { - $(connection).triggerHandler(events.onError, - [signalR._.transportError( - signalR.resources.webSocketsInvalidState, - connection.transport, - ex, - connection.socket - ), - data]); - } - }, - - start: function (connection, onSuccess, onFailed) { - var url, - opened = false, - that = this, - reconnecting = !onSuccess, - $connection = $(connection); - - if (!window.WebSocket) { - onFailed(); - return; - } - - if (!connection.socket) { - if (connection.webSocketServerUrl) { - url = connection.webSocketServerUrl; - } else { - url = connection.wsProtocol + connection.host; - } - - url += transportLogic.getUrl(connection, this.name, reconnecting); - - connection.log("Connecting to websocket endpoint '" + url + "'."); - connection.socket = new window.WebSocket(url); - - connection.socket.onopen = function () { - opened = true; - connection.log("Websocket opened."); - - transportLogic.clearReconnectTimeout(connection); - - if (changeState(connection, - signalR.connectionState.reconnecting, - signalR.connectionState.connected) === true) { - $connection.triggerHandler(events.onReconnect); - } - }; - - connection.socket.onclose = function (event) { - // Only handle a socket close if the close is from the current socket. - // Sometimes on disconnect the server will push down an onclose event - // to an expired socket. - - if (this === connection.socket) { - if (!opened) { - if (onFailed) { - onFailed(); - } else if (reconnecting) { - that.reconnect(connection); - } - return; - } else if (typeof event.wasClean !== "undefined" && event.wasClean === false) { - // Ideally this would use the websocket.onerror handler (rather than checking wasClean in onclose) but - // I found in some circumstances Chrome won't call onerror. This implementation seems to work on all browsers. - $(connection).triggerHandler(events.onError, [signalR._.transportError( - signalR.resources.webSocketClosed, - connection.transport, - event)]); - connection.log("Unclean disconnect from websocket: " + event.reason || "[no reason given]."); - } else { - connection.log("Websocket closed."); - } - - that.reconnect(connection); - } - }; - - connection.socket.onmessage = function (event) { - var data; - - try { - data = connection._parseResponse(event.data); - } - catch (error) { - transportLogic.handleParseFailure(connection, event.data, error, onFailed, event); - return; - } - - if (data) { - // data.M is PersistentResponse.Messages - if ($.isEmptyObject(data) || data.M) { - transportLogic.processMessages(connection, data, onSuccess); - } else { - // For websockets we need to trigger onReceived - // for callbacks to outgoing hub calls. - transportLogic.triggerReceived(connection, data); - } - } - }; - } - }, - - reconnect: function (connection) { - transportLogic.reconnect(connection, this.name); - }, - - lostConnection: function (connection) { - this.reconnect(connection); - }, - - stop: function (connection) { - // Don't trigger a reconnect after stopping - transportLogic.clearReconnectTimeout(connection); - - if (connection.socket) { - connection.log("Closing the Websocket."); - connection.socket.close(); - connection.socket = null; - } - }, - - abort: function (connection, async) { - transportLogic.ajaxAbort(connection, async); - } - }; - -}(window.jQuery, window)); -/* jquery.signalR.transports.serverSentEvents.js */ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information. - -/*global window:false */ -/// - -(function ($, window, undefined) { - "use strict"; - - var signalR = $.signalR, - events = $.signalR.events, - changeState = $.signalR.changeState, - transportLogic = signalR.transports._logic; - - signalR.transports.serverSentEvents = { - name: "serverSentEvents", - - supportsKeepAlive: true, - - timeOut: 3000, - - start: function (connection, onSuccess, onFailed) { - var that = this, - opened = false, - $connection = $(connection), - reconnecting = !onSuccess, - url, - reconnectTimeout; - - if (connection.eventSource) { - connection.log("The connection already has an event source. Stopping it."); - connection.stop(); - } - - if (!window.EventSource) { - if (onFailed) { - connection.log("This browser doesn't support SSE."); - onFailed(); - } - return; - } - - url = transportLogic.getUrl(connection, this.name, reconnecting); - - try { - connection.log("Attempting to connect to SSE endpoint '" + url + "'."); - connection.eventSource = new window.EventSource(url, { withCredentials: connection.withCredentials }); - } - catch (e) { - connection.log("EventSource failed trying to connect with error " + e.Message + "."); - if (onFailed) { - // The connection failed, call the failed callback - onFailed(); - } else { - $connection.triggerHandler(events.onError, [signalR._.transportError(signalR.resources.eventSourceFailedToConnect, connection.transport, e)]); - if (reconnecting) { - // If we were reconnecting, rather than doing initial connect, then try reconnect again - that.reconnect(connection); - } - } - return; - } - - if (reconnecting) { - reconnectTimeout = window.setTimeout(function () { - if (opened === false) { - // If we're reconnecting and the event source is attempting to connect, - // don't keep retrying. This causes duplicate connections to spawn. - if (connection.eventSource.readyState !== window.EventSource.OPEN) { - // If we were reconnecting, rather than doing initial connect, then try reconnect again - that.reconnect(connection); - } - } - }, - that.timeOut); - } - - connection.eventSource.addEventListener("open", function (e) { - connection.log("EventSource connected."); - - if (reconnectTimeout) { - window.clearTimeout(reconnectTimeout); - } - - transportLogic.clearReconnectTimeout(connection); - - if (opened === false) { - opened = true; - - if (changeState(connection, - signalR.connectionState.reconnecting, - signalR.connectionState.connected) === true) { - $connection.triggerHandler(events.onReconnect); - } - } - }, false); - - connection.eventSource.addEventListener("message", function (e) { - var res; - - // process messages - if (e.data === "initialized") { - return; - } - - try { - res = connection._parseResponse(e.data); - } - catch (error) { - transportLogic.handleParseFailure(connection, e.data, error, onFailed, e); - return; - } - - transportLogic.processMessages(connection, res, onSuccess); - }, false); - - connection.eventSource.addEventListener("error", function (e) { - // Only handle an error if the error is from the current Event Source. - // Sometimes on disconnect the server will push down an error event - // to an expired Event Source. - if (this !== connection.eventSource) { - return; - } - - if (!opened) { - if (onFailed) { - onFailed(); - } - - return; - } - - connection.log("EventSource readyState: " + connection.eventSource.readyState + "."); - - if (e.eventPhase === window.EventSource.CLOSED) { - // We don't use the EventSource's native reconnect function as it - // doesn't allow us to change the URL when reconnecting. We need - // to change the URL to not include the /connect suffix, and pass - // the last message id we received. - connection.log("EventSource reconnecting due to the server connection ending."); - that.reconnect(connection); - } else { - // connection error - connection.log("EventSource error."); - $connection.triggerHandler(events.onError, [signalR._.transportError(signalR.resources.eventSourceError, connection.transport, e)]); - } - }, false); - }, - - reconnect: function (connection) { - transportLogic.reconnect(connection, this.name); - }, - - lostConnection: function (connection) { - this.reconnect(connection); - }, - - send: function (connection, data) { - transportLogic.ajaxSend(connection, data); - }, - - stop: function (connection) { - // Don't trigger a reconnect after stopping - transportLogic.clearReconnectTimeout(connection); - - if (connection && connection.eventSource) { - connection.log("EventSource calling close()."); - connection.eventSource.close(); - connection.eventSource = null; - delete connection.eventSource; - } - }, - - abort: function (connection, async) { - transportLogic.ajaxAbort(connection, async); - } - }; - -}(window.jQuery, window)); -/* jquery.signalR.transports.foreverFrame.js */ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information. - -/*global window:false */ -/// - -(function ($, window, undefined) { - "use strict"; - - var signalR = $.signalR, - events = $.signalR.events, - changeState = $.signalR.changeState, - transportLogic = signalR.transports._logic, - createFrame = function () { - var frame = window.document.createElement("iframe"); - frame.setAttribute("style", "position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;"); - return frame; - }, - // Used to prevent infinite loading icon spins in older versions of ie - // We build this object inside a closure so we don't pollute the rest of - // the foreverFrame transport with unnecessary functions/utilities. - loadPreventer = (function () { - var loadingFixIntervalId = null, - loadingFixInterval = 1000, - attachedTo = 0; - - return { - prevent: function () { - // Prevent additional iframe removal procedures from newer browsers - if (signalR._.ieVersion <= 8) { - // We only ever want to set the interval one time, so on the first attachedTo - if (attachedTo === 0) { - // Create and destroy iframe every 3 seconds to prevent loading icon, super hacky - loadingFixIntervalId = window.setInterval(function () { - var tempFrame = createFrame(); - - window.document.body.appendChild(tempFrame); - window.document.body.removeChild(tempFrame); - - tempFrame = null; - }, loadingFixInterval); - } - - attachedTo++; - } - }, - cancel: function () { - // Only clear the interval if there's only one more object that the loadPreventer is attachedTo - if (attachedTo === 1) { - window.clearInterval(loadingFixIntervalId); - } - - if (attachedTo > 0) { - attachedTo--; - } - } - }; - })(); - - signalR.transports.foreverFrame = { - name: "foreverFrame", - - supportsKeepAlive: true, - - // Added as a value here so we can create tests to verify functionality - iframeClearThreshold: 50, - - start: function (connection, onSuccess, onFailed) { - var that = this, - frameId = (transportLogic.foreverFrame.count += 1), - url, - frame = createFrame(), - frameLoadHandler = function () { - connection.log("Forever frame iframe finished loading and is no longer receiving messages."); - that.reconnect(connection); - }; - - if (window.EventSource) { - // If the browser supports SSE, don't use Forever Frame - if (onFailed) { - connection.log("This browser supports SSE, skipping Forever Frame."); - onFailed(); - } - return; - } - - frame.setAttribute("data-signalr-connection-id", connection.id); - - // Start preventing loading icon - // This will only perform work if the loadPreventer is not attached to another connection. - loadPreventer.prevent(); - - // Build the url - url = transportLogic.getUrl(connection, this.name); - url += "&frameId=" + frameId; - - // Set body prior to setting URL to avoid caching issues. - window.document.body.appendChild(frame); - - connection.log("Binding to iframe's load event."); - - if (frame.addEventListener) { - frame.addEventListener("load", frameLoadHandler, false); - } else if (frame.attachEvent) { - frame.attachEvent("onload", frameLoadHandler); - } - - frame.src = url; - transportLogic.foreverFrame.connections[frameId] = connection; - - connection.frame = frame; - connection.frameId = frameId; - - if (onSuccess) { - connection.onSuccess = function () { - connection.log("Iframe transport started."); - onSuccess(); - }; - } - }, - - reconnect: function (connection) { - var that = this; - - // Need to verify connection state and verify before the setTimeout occurs because an application sleep could occur during the setTimeout duration. - if (transportLogic.isConnectedOrReconnecting(connection) && transportLogic.verifyLastActive(connection)) { - window.setTimeout(function () { - // Verify that we're ok to reconnect. - if (!transportLogic.verifyLastActive(connection)) { - return; - } - - if (connection.frame && transportLogic.ensureReconnectingState(connection)) { - var frame = connection.frame, - src = transportLogic.getUrl(connection, that.name, true) + "&frameId=" + connection.frameId; - connection.log("Updating iframe src to '" + src + "'."); - frame.src = src; - } - }, connection.reconnectDelay); - } - }, - - lostConnection: function (connection) { - this.reconnect(connection); - }, - - send: function (connection, data) { - transportLogic.ajaxSend(connection, data); - }, - - receive: function (connection, data) { - var cw, - body; - - transportLogic.processMessages(connection, data, connection.onSuccess); - - // Protect against connection stopping from a callback trigger within the processMessages above. - if (connection.state === $.signalR.connectionState.connected) { - // Delete the script & div elements - connection.frameMessageCount = (connection.frameMessageCount || 0) + 1; - if (connection.frameMessageCount > signalR.transports.foreverFrame.iframeClearThreshold) { - connection.frameMessageCount = 0; - cw = connection.frame.contentWindow || connection.frame.contentDocument; - if (cw && cw.document && cw.document.body) { - body = cw.document.body; - - // Remove all the child elements from the iframe's body to conserver memory - while (body.firstChild) { - body.removeChild(body.firstChild); - } - } - } - } - }, - - stop: function (connection) { - var cw = null; - - // Stop attempting to prevent loading icon - loadPreventer.cancel(); - - if (connection.frame) { - if (connection.frame.stop) { - connection.frame.stop(); - } else { - try { - cw = connection.frame.contentWindow || connection.frame.contentDocument; - if (cw.document && cw.document.execCommand) { - cw.document.execCommand("Stop"); - } - } - catch (e) { - connection.log("Error occured when stopping foreverFrame transport. Message = " + e.message + "."); - } - } - - // Ensure the iframe is where we left it - if (connection.frame.parentNode === window.document.body) { - window.document.body.removeChild(connection.frame); - } - - delete transportLogic.foreverFrame.connections[connection.frameId]; - connection.frame = null; - connection.frameId = null; - delete connection.frame; - delete connection.frameId; - delete connection.onSuccess; - delete connection.frameMessageCount; - connection.log("Stopping forever frame."); - } - }, - - abort: function (connection, async) { - transportLogic.ajaxAbort(connection, async); - }, - - getConnection: function (id) { - return transportLogic.foreverFrame.connections[id]; - }, - - started: function (connection) { - if (changeState(connection, - signalR.connectionState.reconnecting, - signalR.connectionState.connected) === true) { - // If there's no onSuccess handler we assume this is a reconnect - $(connection).triggerHandler(events.onReconnect); - } - } - }; - -}(window.jQuery, window)); -/* jquery.signalR.transports.longPolling.js */ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information. - -/*global window:false */ -/// - -(function ($, window, undefined) { - "use strict"; - - var signalR = $.signalR, - events = $.signalR.events, - changeState = $.signalR.changeState, - isDisconnecting = $.signalR.isDisconnecting, - transportLogic = signalR.transports._logic; - - signalR.transports.longPolling = { - name: "longPolling", - - supportsKeepAlive: false, - - reconnectDelay: 3000, - - start: function (connection, onSuccess, onFailed) { - /// Starts the long polling connection - /// The SignalR connection to start - var that = this, - fireConnect = function () { - fireConnect = $.noop; - - connection.log("LongPolling connected."); - onSuccess(); - // Reset onFailed to null because it shouldn't be called again - onFailed = null; - }, - tryFailConnect = function () { - if (onFailed) { - onFailed(); - onFailed = null; - connection.log("LongPolling failed to connect."); - return true; - } - - return false; - }, - privateData = connection._, - reconnectErrors = 0, - fireReconnected = function (instance) { - window.clearTimeout(privateData.reconnectTimeoutId); - privateData.reconnectTimeoutId = null; - - if (changeState(instance, - signalR.connectionState.reconnecting, - signalR.connectionState.connected) === true) { - // Successfully reconnected! - instance.log("Raising the reconnect event"); - $(instance).triggerHandler(events.onReconnect); - } - }, - // 1 hour - maxFireReconnectedTimeout = 3600000; - - if (connection.pollXhr) { - connection.log("Polling xhr requests already exists, aborting."); - connection.stop(); - } - - connection.messageId = null; - - privateData.reconnectTimeoutId = null; - - privateData.pollTimeoutId = window.setTimeout(function () { - (function poll(instance, raiseReconnect) { - var messageId = instance.messageId, - connect = (messageId === null), - reconnecting = !connect, - polling = !raiseReconnect, - url = transportLogic.getUrl(instance, that.name, reconnecting, polling); - - // If we've disconnected during the time we've tried to re-instantiate the poll then stop. - if (isDisconnecting(instance) === true) { - return; - } - - connection.log("Opening long polling request to '" + url + "'."); - instance.pollXhr = $.ajax( - $.extend({}, $.signalR.ajaxDefaults, { - xhrFields: { withCredentials: connection.withCredentials }, - url: url, - type: "GET", - dataType: connection.ajaxDataType, - contentType: connection.contentType, - success: function (result) { - var minData, - delay = 0, - data, - shouldReconnect; - - connection.log("Long poll complete."); - - // Reset our reconnect errors so if we transition into a reconnecting state again we trigger - // reconnected quickly - reconnectErrors = 0; - - try { - minData = connection._parseResponse(result); - } - catch (error) { - transportLogic.handleParseFailure(instance, result, error, tryFailConnect, instance.pollXhr); - return; - } - - // If there's currently a timeout to trigger reconnect, fire it now before processing messages - if (privateData.reconnectTimeoutId !== null) { - fireReconnected(instance); - } - - if (minData) { - data = transportLogic.maximizePersistentResponse(minData); - } - - transportLogic.processMessages(instance, minData, fireConnect); - - if (data && - $.type(data.LongPollDelay) === "number") { - delay = data.LongPollDelay; - } - - if (data && data.Disconnect) { - return; - } - - if (isDisconnecting(instance) === true) { - return; - } - - shouldReconnect = data && data.ShouldReconnect; - if (shouldReconnect) { - // Transition into the reconnecting state - // If this fails then that means that the user transitioned the connection into a invalid state in processMessages. - if (!transportLogic.ensureReconnectingState(instance)) { - return; - } - } - - // We never want to pass a raiseReconnect flag after a successful poll. This is handled via the error function - if (delay > 0) { - privateData.pollTimeoutId = window.setTimeout(function () { - poll(instance, shouldReconnect); - }, delay); - } else { - poll(instance, shouldReconnect); - } - }, - - error: function (data, textStatus) { - // Stop trying to trigger reconnect, connection is in an error state - // If we're not in the reconnect state this will noop - window.clearTimeout(privateData.reconnectTimeoutId); - privateData.reconnectTimeoutId = null; - - if (textStatus === "abort") { - connection.log("Aborted xhr request."); - return; - } - - if (!tryFailConnect()) { - - // Increment our reconnect errors, we assume all errors to be reconnect errors - // In the case that it's our first error this will cause Reconnect to be fired - // after 1 second due to reconnectErrors being = 1. - reconnectErrors++; - - if (connection.state !== signalR.connectionState.reconnecting) { - connection.log("An error occurred using longPolling. Status = " + textStatus + ". Response = " + data.responseText + "."); - $(instance).triggerHandler(events.onError, [signalR._.transportError(signalR.resources.longPollFailed, connection.transport, data, instance.pollXhr)]); - } - - // We check the state here to verify that we're not in an invalid state prior to verifying Reconnect. - // If we're not in connected or reconnecting then the next ensureReconnectingState check will fail and will return. - // Therefore we don't want to change that failure code path. - if ((connection.state === signalR.connectionState.connected || - connection.state === signalR.connectionState.reconnecting) && - !transportLogic.verifyLastActive(connection)) { - return; - } - - // Transition into the reconnecting state - // If this fails then that means that the user transitioned the connection into the disconnected or connecting state within the above error handler trigger. - if (!transportLogic.ensureReconnectingState(instance)) { - return; - } - - // Call poll with the raiseReconnect flag as true after the reconnect delay - privateData.pollTimeoutId = window.setTimeout(function () { - poll(instance, true); - }, that.reconnectDelay); - } - } - } - )); - - - // This will only ever pass after an error has occured via the poll ajax procedure. - if (reconnecting && raiseReconnect === true) { - // We wait to reconnect depending on how many times we've failed to reconnect. - // This is essentially a heuristic that will exponentially increase in wait time before - // triggering reconnected. This depends on the "error" handler of Poll to cancel this - // timeout if it triggers before the Reconnected event fires. - // The Math.min at the end is to ensure that the reconnect timeout does not overflow. - privateData.reconnectTimeoutId = window.setTimeout(function () { fireReconnected(instance); }, Math.min(1000 * (Math.pow(2, reconnectErrors) - 1), maxFireReconnectedTimeout)); - } - }(connection)); - }, 250); // Have to delay initial poll so Chrome doesn't show loader spinner in tab - }, - - lostConnection: function (connection) { - throw new Error("Lost Connection not handled for LongPolling"); - }, - - send: function (connection, data) { - transportLogic.ajaxSend(connection, data); - }, - - stop: function (connection) { - /// Stops the long polling connection - /// The SignalR connection to stop - - window.clearTimeout(connection._.pollTimeoutId); - window.clearTimeout(connection._.reconnectTimeoutId); - - delete connection._.pollTimeoutId; - delete connection._.reconnectTimeoutId; - - if (connection.pollXhr) { - connection.pollXhr.abort(); - connection.pollXhr = null; - delete connection.pollXhr; - } - }, - - abort: function (connection, async) { - transportLogic.ajaxAbort(connection, async); - } - }; - -}(window.jQuery, window)); -/* jquery.signalR.hubs.js */ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information. - -/*global window:false */ -/// - -(function ($, window, undefined) { - "use strict"; - - var eventNamespace = ".hubProxy", - signalR = $.signalR; - - function makeEventName(event) { - return event + eventNamespace; - } - - // Equivalent to Array.prototype.map - function map(arr, fun, thisp) { - var i, - length = arr.length, - result = []; - for (i = 0; i < length; i += 1) { - if (arr.hasOwnProperty(i)) { - result[i] = fun.call(thisp, arr[i], i, arr); - } - } - return result; - } - - function getArgValue(a) { - return $.isFunction(a) ? null : ($.type(a) === "undefined" ? null : a); - } - - function hasMembers(obj) { - for (var key in obj) { - // If we have any properties in our callback map then we have callbacks and can exit the loop via return - if (obj.hasOwnProperty(key)) { - return true; - } - } - - return false; - } - - function clearInvocationCallbacks(connection, error) { - /// - var callbacks = connection._.invocationCallbacks, - callback; - - if (hasMembers(callbacks)) { - connection.log("Clearing hub invocation callbacks with error: " + error + "."); - } - - // Reset the callback cache now as we have a local var referencing it - connection._.invocationCallbackId = 0; - delete connection._.invocationCallbacks; - connection._.invocationCallbacks = {}; - - // Loop over the callbacks and invoke them. - // We do this using a local var reference and *after* we've cleared the cache - // so that if a fail callback itself tries to invoke another method we don't - // end up with its callback in the list we're looping over. - for (var callbackId in callbacks) { - callback = callbacks[callbackId]; - callback.method.call(callback.scope, { E: error }); - } - } - - // hubProxy - function hubProxy(hubConnection, hubName) { - /// - /// Creates a new proxy object for the given hub connection that can be used to invoke - /// methods on server hubs and handle client method invocation requests from the server. - /// - return new hubProxy.fn.init(hubConnection, hubName); - } - - hubProxy.fn = hubProxy.prototype = { - init: function (connection, hubName) { - this.state = {}; - this.connection = connection; - this.hubName = hubName; - this._ = { - callbackMap: {} - }; - }, - - hasSubscriptions: function () { - return hasMembers(this._.callbackMap); - }, - - on: function (eventName, callback) { - /// Wires up a callback to be invoked when a invocation request is received from the server hub. - /// The name of the hub event to register the callback for. - /// The callback to be invoked. - var that = this, - callbackMap = that._.callbackMap; - - // Normalize the event name to lowercase - eventName = eventName.toLowerCase(); - - // If there is not an event registered for this callback yet we want to create its event space in the callback map. - if (!callbackMap[eventName]) { - callbackMap[eventName] = {}; - } - - // Map the callback to our encompassed function - callbackMap[eventName][callback] = function (e, data) { - callback.apply(that, data); - }; - - $(that).bind(makeEventName(eventName), callbackMap[eventName][callback]); - - return that; - }, - - off: function (eventName, callback) { - /// Removes the callback invocation request from the server hub for the given event name. - /// The name of the hub event to unregister the callback for. - /// The callback to be invoked. - var that = this, - callbackMap = that._.callbackMap, - callbackSpace; - - // Normalize the event name to lowercase - eventName = eventName.toLowerCase(); - - callbackSpace = callbackMap[eventName]; - - // Verify that there is an event space to unbind - if (callbackSpace) { - // Only unbind if there's an event bound with eventName and a callback with the specified callback - if (callbackSpace[callback]) { - $(that).unbind(makeEventName(eventName), callbackSpace[callback]); - - // Remove the callback from the callback map - delete callbackSpace[callback]; - - // Check if there are any members left on the event, if not we need to destroy it. - if (!hasMembers(callbackSpace)) { - delete callbackMap[eventName]; - } - } else if (!callback) { // Check if we're removing the whole event and we didn't error because of an invalid callback - $(that).unbind(makeEventName(eventName)); - - delete callbackMap[eventName]; - } - } - - return that; - }, - - invoke: function (methodName) { - /// Invokes a server hub method with the given arguments. - /// The name of the server hub method. - - var that = this, - connection = that.connection, - args = $.makeArray(arguments).slice(1), - argValues = map(args, getArgValue), - data = { H: that.hubName, M: methodName, A: argValues, I: connection._.invocationCallbackId }, - d = $.Deferred(), - callback = function (minResult) { - var result = that._maximizeHubResponse(minResult), - source, - error; - - // Update the hub state - $.extend(that.state, result.State); - - if (result.Error) { - // Server hub method threw an exception, log it & reject the deferred - if (result.StackTrace) { - connection.log(result.Error + "\n" + result.StackTrace + "."); - } - - // result.ErrorData is only set if a HubException was thrown - source = result.IsHubException ? "HubException" : "Exception"; - error = signalR._.error(result.Error, source); - error.data = result.ErrorData; - - connection.log(that.hubName + "." + methodName + " failed to execute. Error: " + error.message); - d.rejectWith(that, [error]); - } else { - // Server invocation succeeded, resolve the deferred - connection.log("Invoked " + that.hubName + "." + methodName); - d.resolveWith(that, [result.Result]); - } - }; - - connection._.invocationCallbacks[connection._.invocationCallbackId.toString()] = { scope: that, method: callback }; - connection._.invocationCallbackId += 1; - - if (!$.isEmptyObject(that.state)) { - data.S = that.state; - } - - connection.log("Invoking " + that.hubName + "." + methodName); - connection.send(data); - - return d.promise(); - }, - - _maximizeHubResponse: function (minHubResponse) { - return { - State: minHubResponse.S, - Result: minHubResponse.R, - Id: minHubResponse.I, - IsHubException: minHubResponse.H, - Error: minHubResponse.E, - StackTrace: minHubResponse.T, - ErrorData: minHubResponse.D - }; - } - }; - - hubProxy.fn.init.prototype = hubProxy.fn; - - // hubConnection - function hubConnection(url, options) { - /// Creates a new hub connection. - /// [Optional] The hub route url, defaults to "/signalr". - /// [Optional] Settings to use when creating the hubConnection. - var settings = { - qs: null, - logging: false, - useDefaultPath: true - }; - - $.extend(settings, options); - - if (!url || settings.useDefaultPath) { - url = (url || "") + "/signalr"; - } - return new hubConnection.fn.init(url, settings); - } - - hubConnection.fn = hubConnection.prototype = $.connection(); - - hubConnection.fn.init = function (url, options) { - var settings = { - qs: null, - logging: false, - useDefaultPath: true - }, - connection = this; - - $.extend(settings, options); - - // Call the base constructor - $.signalR.fn.init.call(connection, url, settings.qs, settings.logging); - - // Object to store hub proxies for this connection - connection.proxies = {}; - - connection._.invocationCallbackId = 0; - connection._.invocationCallbacks = {}; - - // Wire up the received handler - connection.received(function (minData) { - var data, proxy, dataCallbackId, callback, hubName, eventName; - if (!minData) { - return; - } - - if (typeof (minData.I) !== "undefined") { - // We received the return value from a server method invocation, look up callback by id and call it - dataCallbackId = minData.I.toString(); - callback = connection._.invocationCallbacks[dataCallbackId]; - if (callback) { - // Delete the callback from the proxy - connection._.invocationCallbacks[dataCallbackId] = null; - delete connection._.invocationCallbacks[dataCallbackId]; - - // Invoke the callback - callback.method.call(callback.scope, minData); - } - } else { - data = this._maximizeClientHubInvocation(minData); - - // We received a client invocation request, i.e. broadcast from server hub - connection.log("Triggering client hub event '" + data.Method + "' on hub '" + data.Hub + "'."); - - // Normalize the names to lowercase - hubName = data.Hub.toLowerCase(); - eventName = data.Method.toLowerCase(); - - // Trigger the local invocation event - proxy = this.proxies[hubName]; - - // Update the hub state - $.extend(proxy.state, data.State); - $(proxy).triggerHandler(makeEventName(eventName), [data.Args]); - } - }); - - connection.error(function (errData, origData) { - var callbackId, callback; - - if (!origData) { - // No original data passed so this is not a send error - return; - } - - callbackId = origData.I; - callback = connection._.invocationCallbacks[callbackId]; - - // Verify that there is a callback bound (could have been cleared) - if (callback) { - // Delete the callback - connection._.invocationCallbacks[callbackId] = null; - delete connection._.invocationCallbacks[callbackId]; - - // Invoke the callback with an error to reject the promise - callback.method.call(callback.scope, { E: errData }); - } - }); - - connection.reconnecting(function () { - if (connection.transport && connection.transport.name === "webSockets") { - clearInvocationCallbacks(connection, "Connection started reconnecting before invocation result was received."); - } - }); - - connection.disconnected(function () { - clearInvocationCallbacks(connection, "Connection was disconnected before invocation result was received."); - }); - }; - - hubConnection.fn._maximizeClientHubInvocation = function (minClientHubInvocation) { - return { - Hub: minClientHubInvocation.H, - Method: minClientHubInvocation.M, - Args: minClientHubInvocation.A, - State: minClientHubInvocation.S - }; - }; - - hubConnection.fn._registerSubscribedHubs = function () { - /// - /// Sets the starting event to loop through the known hubs and register any new hubs - /// that have been added to the proxy. - /// - var connection = this; - - if (!connection._subscribedToHubs) { - connection._subscribedToHubs = true; - connection.starting(function () { - // Set the connection's data object with all the hub proxies with active subscriptions. - // These proxies will receive notifications from the server. - var subscribedHubs = []; - - $.each(connection.proxies, function (key) { - if (this.hasSubscriptions()) { - subscribedHubs.push({ name: key }); - connection.log("Client subscribed to hub '" + key + "'."); - } - }); - - if (subscribedHubs.length === 0) { - connection.log("No hubs have been subscribed to. The client will not receive data from hubs. To fix, declare at least one client side function prior to connection start for each hub you wish to subscribe to."); - } - - connection.data = connection.json.stringify(subscribedHubs); - }); - } - }; - - hubConnection.fn.createHubProxy = function (hubName) { - /// - /// Creates a new proxy object for the given hub connection that can be used to invoke - /// methods on server hubs and handle client method invocation requests from the server. - /// - /// - /// The name of the hub on the server to create the proxy for. - /// - - // Normalize the name to lowercase - hubName = hubName.toLowerCase(); - - var proxy = this.proxies[hubName]; - if (!proxy) { - proxy = hubProxy(this, hubName); - this.proxies[hubName] = proxy; - } - - this._registerSubscribedHubs(); - - return proxy; - }; - - hubConnection.fn.init.prototype = hubConnection.fn; - - $.hubConnection = hubConnection; - -}(window.jQuery, window)); -/* jquery.signalR.version.js */ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information. - -/*global window:false */ -/// -(function ($, undefined) { - $.signalR.version = "2.0.3"; -}(window.jQuery)); diff --git a/SampleWebApp/Scripts/jquery.signalR-2.0.3.min.js b/SampleWebApp/Scripts/jquery.signalR-2.0.3.min.js deleted file mode 100644 index 6c53ded..0000000 --- a/SampleWebApp/Scripts/jquery.signalR-2.0.3.min.js +++ /dev/null @@ -1,8 +0,0 @@ -/*! - * ASP.NET SignalR JavaScript Library v2.0.3 - * http://signalr.net/ - * - * Copyright (C) Microsoft Corporation. All rights reserved. - * - */ -(function(n,t,i){"use strict";function p(t,i){var u,f;if(n.isArray(t)){for(u=t.length-1;u>=0;u--)f=t[u],n.type(f)==="string"&&r.transports[f]||(i.log("Invalid transport: "+f+", removing it from the transports list."),t.splice(u,1));t.length===0&&(i.log("No transports remain within the specified transport array."),t=null)}else if(r.transports[t]||t==="auto"){if(t==="auto"&&r._.ieVersion<=8)return["longPolling"]}else i.log("Invalid transport: "+t.toString()+"."),t=null;return t}function w(n){return n==="http:"?80:n==="https:"?443:void 0}function l(n,t){return t.match(/:\d+$/)?t:t+":"+w(n)}function b(t,i){var u=this,r=[];u.tryBuffer=function(i){return t.state===n.signalR.connectionState.connecting?(r.push(i),!0):!1};u.drain=function(){if(t.state===n.signalR.connectionState.connected)while(r.length>0)i(r.shift())};u.clear=function(){r=[]}}var f={nojQuery:"jQuery was not found. Please ensure jQuery is referenced before the SignalR client JavaScript file.",noTransportOnInit:"No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization.",errorOnNegotiate:"Error during negotiation request.",stoppedWhileLoading:"The connection was stopped during page load.",stoppedWhileNegotiating:"The connection was stopped during the negotiate request.",errorParsingNegotiateResponse:"Error parsing negotiate response.",protocolIncompatible:"You are using a version of the client that isn't compatible with the server. Client version {0}, server version {1}.",sendFailed:"Send failed.",parseFailed:"Failed at parsing response: {0}",longPollFailed:"Long polling request failed.",eventSourceFailedToConnect:"EventSource failed to connect.",eventSourceError:"Error raised by EventSource",webSocketClosed:"WebSocket closed.",pingServerFailedInvalidResponse:"Invalid ping response when pinging server: '{0}'.",pingServerFailed:"Failed to ping server.",pingServerFailedStatusCode:"Failed to ping server. Server responded with status code {0}, stopping the connection.",pingServerFailedParse:"Failed to parse ping server response, stopping the connection.",noConnectionTransport:"Connection is in an invalid state, there is no transport active.",webSocketsInvalidState:"The Web Socket transport is in an invalid state, transitioning into reconnecting."};if(typeof n!="function")throw new Error(f.nojQuery);var r,h,s=t.document.readyState==="complete",e=n(t),c="__Negotiate Aborted__",u={onStart:"onStart",onStarting:"onStarting",onReceived:"onReceived",onError:"onError",onConnectionSlow:"onConnectionSlow",onReconnecting:"onReconnecting",onReconnect:"onReconnect",onStateChanged:"onStateChanged",onDisconnect:"onDisconnect"},a=function(n,i){if(i!==!1){var r;typeof t.console!="undefined"&&(r="["+(new Date).toTimeString()+"] SignalR: "+n,t.console.debug?t.console.debug(r):t.console.log&&t.console.log(r))}},o=function(t,i,r){return i===t.state?(t.state=r,n(t).triggerHandler(u.onStateChanged,[{oldState:i,newState:r}]),!0):!1},v=function(n){return n.state===r.connectionState.disconnected},y=function(n){var i,u;n._.configuredStopReconnectingTimeout||(u=function(n){n.log("Couldn't reconnect within the configured timeout ("+n.disconnectTimeout+"ms), disconnecting.");n.stop(!1,!1)},n.reconnecting(function(){var n=this;n.state===r.connectionState.reconnecting&&(i=t.setTimeout(function(){u(n)},n.disconnectTimeout))}),n.stateChanged(function(n){n.oldState===r.connectionState.reconnecting&&t.clearTimeout(i)}),n._.configuredStopReconnectingTimeout=!0)};r=function(n,t,i){return new r.fn.init(n,t,i)};r._={defaultContentType:"application/x-www-form-urlencoded; charset=UTF-8",ieVersion:function(){var i,n;return t.navigator.appName==="Microsoft Internet Explorer"&&(n=/MSIE ([0-9]+\.[0-9]+)/.exec(t.navigator.userAgent),n&&(i=t.parseFloat(n[1]))),i}(),error:function(n,t,i){var r=new Error(n);return r.source=t,typeof i!="undefined"&&(r.context=i),r},transportError:function(n,t,r,u){var f=this.error(n,r,u);return f.transport=t?t.name:i,f},format:function(){for(var t=arguments[0],n=0;n<\/script>.");}};e.load(function(){s=!0});r.fn=r.prototype={init:function(t,i,r){var f=n(this);this.url=t;this.qs=i;this._={keepAliveData:{},connectingMessageBuffer:new b(this,function(n){f.triggerHandler(u.onReceived,[n])}),onFailedTimeoutHandle:null,lastMessageAt:(new Date).getTime(),lastActiveAt:(new Date).getTime(),beatInterval:5e3,beatHandle:null,totalTransportConnectTimeout:0};typeof r=="boolean"&&(this.logging=r)},_parseResponse:function(n){var t=this;return n?typeof n=="string"?t.json.parse(n):n:n},json:t.JSON,isCrossDomain:function(i,r){var u;return(i=n.trim(i),r=r||t.location,i.indexOf("http")!==0)?!1:(u=t.document.createElement("a"),u.href=i,u.protocol+l(u.protocol,u.host)!==r.protocol+l(r.protocol,r.host))},ajaxDataType:"text",contentType:"application/json; charset=UTF-8",logging:!1,state:r.connectionState.disconnected,clientProtocol:"1.3",reconnectDelay:2e3,transportConnectTimeout:0,disconnectTimeout:3e4,reconnectWindow:3e4,keepAliveWarnAt:2/3,start:function(i,h){var l=this,a={pingInterval:3e5,waitForPageLoad:!0,transport:"auto",jsonp:!1},k,v=l._deferral||n.Deferred(),w=t.document.createElement("a"),b,d;if(l._deferral=v,!l.json)throw new Error("SignalR: No JSON parser found. Please ensure json2.js is referenced before the SignalR.js file if you need to support clients without native JSON parsing support, e.g. IE<8.");if(n.type(i)==="function"?h=i:n.type(i)==="object"&&(n.extend(a,i),n.type(a.callback)==="function"&&(h=a.callback)),a.transport=p(a.transport,l),!a.transport)throw new Error("SignalR: Invalid transport(s) specified, aborting start.");return(l._.config=a,!s&&a.waitForPageLoad===!0)?(l._.deferredStartHandler=function(){l.start(i,h)},e.bind("load",l._.deferredStartHandler),v.promise()):l.state===r.connectionState.connecting?v.promise():o(l,r.connectionState.disconnected,r.connectionState.connecting)===!1?(v.resolve(l),v.promise()):(y(l),w.href=l.url,w.protocol&&w.protocol!==":"?(l.protocol=w.protocol,l.host=w.host,l.baseUrl=w.protocol+"//"+w.host):(l.protocol=t.document.location.protocol,l.host=t.document.location.host,l.baseUrl=l.protocol+"//"+l.host),l.wsProtocol=l.protocol==="https:"?"wss://":"ws://",a.transport==="auto"&&a.jsonp===!0&&(a.transport="longPolling"),l.url.indexOf("//")===0&&(l.url=t.location.protocol+l.url,l.log("Protocol relative URL detected, normalizing it to '"+l.url+"'.")),this.isCrossDomain(l.url)&&(l.log("Auto detected cross domain url."),a.transport==="auto"&&(a.transport=["webSockets","serverSentEvents","longPolling"]),typeof a.withCredentials=="undefined"&&(a.withCredentials=!0),a.jsonp||(a.jsonp=!n.support.cors,a.jsonp&&l.log("Using jsonp because this browser doesn't support CORS.")),l.contentType=r._.defaultContentType),l.withCredentials=a.withCredentials,l.ajaxDataType=a.jsonp?"jsonp":"text",n(l).bind(u.onStart,function(){n.type(h)==="function"&&h.call(l);v.resolve(l)}),k=function(i,s){var y=r._.error(f.noTransportOnInit);if(s=s||0,s>=i.length){n(l).triggerHandler(u.onError,[y]);v.reject(y);l.stop();return}if(l.state!==r.connectionState.disconnected){var p=i[s],h=r.transports[p],c=!1,a=function(){c||(c=!0,t.clearTimeout(l._.onFailedTimeoutHandle),h.stop(l),k(i,s+1))};l.transport=h;try{l._.onFailedTimeoutHandle=t.setTimeout(function(){l.log(h.name+" timed out when trying to connect.");a()},l._.totalTransportConnectTimeout);h.start(l,function(){var i=r._.firefoxMajorVersion(t.navigator.userAgent)>=11,f=!!l.withCredentials&&i;l.state!==r.connectionState.disconnected&&(c||(c=!0,t.clearTimeout(l._.onFailedTimeoutHandle),h.supportsKeepAlive&&l._.keepAliveData.activated&&r.transports._logic.monitorKeepAlive(l),r.transports._logic.startHeartbeat(l),r._.configurePingInterval(l),o(l,r.connectionState.connecting,r.connectionState.connected),l._.connectingMessageBuffer.drain(),n(l).triggerHandler(u.onStart),e.bind("unload",function(){l.log("Window unloading, stopping the connection.");l.stop(f)}),i&&e.bind("beforeunload",function(){t.setTimeout(function(){l.stop(f)},0)})))},a)}catch(w){l.log(h.name+" transport threw '"+w.message+"' when attempting to start.");a()}}},b=l.url+"/negotiate",d=function(t,i){var e=r._.error(f.errorOnNegotiate,t,i._.negotiateRequest);n(i).triggerHandler(u.onError,e);v.reject(e);i.stop()},n(l).triggerHandler(u.onStarting),b=r.transports._logic.prepareQueryString(l,b),b=r.transports._logic.addQs(b,{clientProtocol:l.clientProtocol}),l.log("Negotiating with '"+b+"'."),l._.negotiateRequest=n.ajax(n.extend({},n.signalR.ajaxDefaults,{xhrFields:{withCredentials:l.withCredentials},url:b,type:"GET",contentType:l.contentType,data:{},dataType:l.ajaxDataType,error:function(n,t){t!==c?d(n,l):v.reject(r._.error(f.stoppedWhileNegotiating,null,l._.negotiateRequest))},success:function(t){var i,e,h,o=[],s=[];try{i=l._parseResponse(t)}catch(c){d(r._.error(f.errorParsingNegotiateResponse,c),l);return}if(e=l._.keepAliveData,l.appRelativeUrl=i.Url,l.id=i.ConnectionId,l.token=i.ConnectionToken,l.webSocketServerUrl=i.WebSocketServerUrl,l.disconnectTimeout=i.DisconnectTimeout*1e3,l._.totalTransportConnectTimeout=l.transportConnectTimeout+i.TransportConnectTimeout*1e3,i.KeepAliveTimeout?(e.activated=!0,e.timeout=i.KeepAliveTimeout*1e3,e.timeoutWarning=e.timeout*l.keepAliveWarnAt,l._.beatInterval=(e.timeout-e.timeoutWarning)/3):e.activated=!1,l.reconnectWindow=l.disconnectTimeout+(e.timeout||0),!i.ProtocolVersion||i.ProtocolVersion!==l.clientProtocol){h=r._.error(r._.format(f.protocolIncompatible,l.clientProtocol,i.ProtocolVersion));n(l).triggerHandler(u.onError,[h]);v.reject(h);return}n.each(r.transports,function(n){if(n.indexOf("_")===0||n==="webSockets"&&!i.TryWebSockets)return!0;s.push(n)});n.isArray(a.transport)?n.each(a.transport,function(t,i){n.inArray(i,s)>=0&&o.push(i)}):a.transport==="auto"?o=s:n.inArray(a.transport,s)>=0&&o.push(a.transport);k(o)}})),v.promise())},starting:function(t){var i=this;return n(i).bind(u.onStarting,function(){t.call(i)}),i},send:function(n){var t=this;if(t.state===r.connectionState.disconnected)throw new Error("SignalR: Connection must be started before data can be sent. Call .start() before .send()");if(t.state===r.connectionState.connecting)throw new Error("SignalR: Connection has not been fully initialized. Use .start().done() or .start().fail() to run logic after the connection has started.");return t.transport.send(t,n),t},received:function(t){var i=this;return n(i).bind(u.onReceived,function(n,r){t.call(i,r)}),i},stateChanged:function(t){var i=this;return n(i).bind(u.onStateChanged,function(n,r){t.call(i,r)}),i},error:function(t){var i=this;return n(i).bind(u.onError,function(n,r,u){t.call(i,r,u)}),i},disconnected:function(t){var i=this;return n(i).bind(u.onDisconnect,function(){t.call(i)}),i},connectionSlow:function(t){var i=this;return n(i).bind(u.onConnectionSlow,function(){t.call(i)}),i},reconnecting:function(t){var i=this;return n(i).bind(u.onReconnecting,function(){t.call(i)}),i},reconnected:function(t){var i=this;return n(i).bind(u.onReconnect,function(){t.call(i)}),i},stop:function(i,h){var l=this,a=l._deferral;if(l._.deferredStartHandler&&e.unbind("load",l._.deferredStartHandler),delete l._deferral,delete l._.config,delete l._.deferredStartHandler,!s&&(!l._.config||l._.config.waitForPageLoad===!0)){l.log("Stopping connection prior to negotiate.");a&&a.reject(r._.error(f.stoppedWhileLoading));return}if(l.state!==r.connectionState.disconnected)return l.log("Stopping connection."),o(l,l.state,r.connectionState.disconnected),t.clearTimeout(l._.beatHandle),t.clearTimeout(l._.onFailedTimeoutHandle),t.clearInterval(l._.pingIntervalId),l.transport&&(l.transport.stop(l),h!==!1&&l.transport.abort(l,i),l.transport.supportsKeepAlive&&l._.keepAliveData.activated&&r.transports._logic.stopMonitoringKeepAlive(l),l.transport=null),l._.negotiateRequest&&(l._.negotiateRequest.abort(c),delete l._.negotiateRequest),n(l).triggerHandler(u.onDisconnect),delete l.messageId,delete l.groupsToken,delete l.id,delete l._.pingIntervalId,delete l._.lastMessageAt,delete l._.lastActiveAt,l._.connectingMessageBuffer.clear(),l},log:function(n){a(n,this.logging)}};r.fn.init.prototype=r.fn;r.noConflict=function(){return n.connection===r&&(n.connection=h),r};n.connection&&(h=n.connection);n.connection=n.signalR=r})(window.jQuery,window),function(n,t){"use strict";function f(n){n._.keepAliveData.monitoring&&o(n);r.markActive(n)&&(n._.beatHandle=t.setTimeout(function(){f(n)},n._.beatInterval))}function o(t){var r=t._.keepAliveData,f;t.state===i.connectionState.connected&&(f=(new Date).getTime()-t._.lastMessageAt,f>=r.timeout?(t.log("Keep alive timed out. Notifying transport that connection has been lost."),t.transport.lostConnection(t)):f>=r.timeoutWarning?r.userNotified||(t.log("Keep alive has been missed, connection may be dead/slow."),n(t).triggerHandler(u.onConnectionSlow),r.userNotified=!0):r.userNotified=!1)}function s(n,i){var r=n.indexOf("?")!==-1?"&":"?";return i&&(n+=r+"connectionData="+t.encodeURIComponent(i)),n}var i=n.signalR,u=n.signalR.events,e=n.signalR.changeState,r;i.transports={};r=i.transports._logic={pingServer:function(t){var e,u=n.Deferred(),f;return t.transport?(e=t.url+"/ping",e=r.addQs(e,t.qs),f=n.ajax(n.extend({},n.signalR.ajaxDefaults,{xhrFields:{withCredentials:t.withCredentials},url:e,type:"GET",contentType:t.contentType,data:{},dataType:t.ajaxDataType,success:function(n){var r;try{r=t._parseResponse(n)}catch(e){u.reject(i._.transportError(i.resources.pingServerFailedParse,t.transport,e,f));t.stop();return}r.Response==="pong"?u.resolve():u.reject(i._.transportError(i._.format(i.resources.pingServerFailedInvalidResponse,n.responseText),t.transport,null,f))},error:function(n){n.status===401||n.status===403?(u.reject(i._.transportError(i._.format(i.resources.pingServerFailedStatusCode,n.status),t.transport,n,f)),t.stop()):u.reject(i._.transportError(i.resources.pingServerFailed,t.transport,n,f))}}))):u.reject(i._.transportError(i.resources.noConnectionTransport,t.transport)),u.promise()},prepareQueryString:function(n,t){return t=r.addQs(t,n.qs),s(t,n.data)},addQs:function(t,i){var r=t.indexOf("?")!==-1?"&":"?",u;if(!i)return t;if(typeof i=="object")return t+r+n.param(i);if(typeof i=="string")return u=i.charAt(0),(u==="?"||u==="&")&&(r=""),t+r+i;throw new Error("Query string property must be either a string or object.");},getUrl:function(n,i,u,f){var s=i==="webSockets"?"":n.baseUrl,e=s+n.appRelativeUrl,o="transport="+i+"&connectionToken="+t.encodeURIComponent(n.token);return n.groupsToken&&(o+="&groupsToken="+t.encodeURIComponent(n.groupsToken)),u?(e+=f?"/poll":"/reconnect",n.messageId&&(o+="&messageId="+t.encodeURIComponent(n.messageId))):e+="/connect",e+="?"+o,e=r.prepareQueryString(n,e),e+("&tid="+Math.floor(Math.random()*11))},maximizePersistentResponse:function(n){return{MessageId:n.C,Messages:n.M,Initialized:typeof n.S!="undefined"?!0:!1,Disconnect:typeof n.D!="undefined"?!0:!1,ShouldReconnect:typeof n.T!="undefined"?!0:!1,LongPollDelay:n.L,GroupsToken:n.G}},updateGroups:function(n,t){t&&(n.groupsToken=t)},stringifySend:function(n,t){return typeof t=="string"||typeof t=="undefined"||t===null?t:n.json.stringify(t)},ajaxSend:function(f,e){var c=r.stringifySend(f,e),o=f.url+"/send?transport="+f.transport.name+"&connectionToken="+t.encodeURIComponent(f.token),s,h=function(t,r){n(r).triggerHandler(u.onError,[i._.transportError(i.resources.sendFailed,r.transport,t,s),e])};return o=r.prepareQueryString(f,o),s=n.ajax(n.extend({},n.signalR.ajaxDefaults,{xhrFields:{withCredentials:f.withCredentials},url:o,type:f.ajaxDataType==="jsonp"?"GET":"POST",contentType:i._.defaultContentType,dataType:f.ajaxDataType,data:{data:c},success:function(n){var t;if(n){try{t=f._parseResponse(n)}catch(i){h(i,f);f.stop();return}r.triggerReceived(f,t)}},error:function(n,t){t!=="abort"&&t!=="parsererror"&&h(n,f)}}))},ajaxAbort:function(i,u){if(typeof i.transport!="undefined"){u=typeof u=="undefined"?!0:u;var f=i.url+"/abort?transport="+i.transport.name+"&connectionToken="+t.encodeURIComponent(i.token);f=r.prepareQueryString(i,f);n.ajax(n.extend({},n.signalR.ajaxDefaults,{xhrFields:{withCredentials:i.withCredentials},url:f,async:u,timeout:1e3,type:"POST",contentType:i.contentType,dataType:i.ajaxDataType,data:{}}));i.log("Fired ajax abort async = "+u+".")}},tryInitialize:function(n,t){n.Initialized&&t()},triggerReceived:function(t,i){t._.connectingMessageBuffer.tryBuffer(i)||n(t).triggerHandler(u.onReceived,[i])},processMessages:function(t,i,u){var f;if(r.markLastMessage(t),i){if(f=r.maximizePersistentResponse(i),f.Disconnect){t.log("Disconnect command received from server.");t.stop(!1,!1);return}r.updateGroups(t,f.GroupsToken);f.MessageId&&(t.messageId=f.MessageId);f.Messages&&(n.each(f.Messages,function(n,i){r.triggerReceived(t,i)}),r.tryInitialize(f,u))}},monitorKeepAlive:function(t){var i=t._.keepAliveData;i.monitoring?t.log("Tried to monitor keep alive but it's already being monitored."):(i.monitoring=!0,r.markLastMessage(t),t._.keepAliveData.reconnectKeepAliveUpdate=function(){r.markLastMessage(t)},n(t).bind(u.onReconnect,t._.keepAliveData.reconnectKeepAliveUpdate),t.log("Now monitoring keep alive with a warning timeout of "+i.timeoutWarning+" and a connection lost timeout of "+i.timeout+"."))},stopMonitoringKeepAlive:function(t){var i=t._.keepAliveData;i.monitoring&&(i.monitoring=!1,n(t).unbind(u.onReconnect,t._.keepAliveData.reconnectKeepAliveUpdate),t._.keepAliveData={},t.log("Stopping the monitoring of the keep alive."))},startHeartbeat:function(n){n._.lastActiveAt=(new Date).getTime();f(n)},markLastMessage:function(n){n._.lastMessageAt=(new Date).getTime()},markActive:function(n){return r.verifyLastActive(n)?(n._.lastActiveAt=(new Date).getTime(),!0):!1},isConnectedOrReconnecting:function(n){return n.state===i.connectionState.connected||n.state===i.connectionState.reconnecting},ensureReconnectingState:function(t){return e(t,i.connectionState.connected,i.connectionState.reconnecting)===!0&&n(t).triggerHandler(u.onReconnecting),t.state===i.connectionState.reconnecting},clearReconnectTimeout:function(n){n&&n._.reconnectTimeout&&(t.clearTimeout(n._.reconnectTimeout),delete n._.reconnectTimeout)},verifyLastActive:function(n){return(new Date).getTime()-n._.lastActiveAt>=n.reconnectWindow?(n.log("There has not been an active server connection for an extended period of time. Stopping connection."),n.stop(),!1):!0},reconnect:function(n,u){var f=i.transports[u];if(r.isConnectedOrReconnecting(n)&&!n._.reconnectTimeout){if(!r.verifyLastActive(n))return;n._.reconnectTimeout=t.setTimeout(function(){r.verifyLastActive(n)&&(f.stop(n),r.ensureReconnectingState(n)&&(n.log(u+" reconnecting."),f.start(n)))},n.reconnectDelay)}},handleParseFailure:function(t,r,f,e,o){t.state===i.connectionState.connecting?(t.log("Failed to parse server response while attempting to connect."),e()):(n(t).triggerHandler(u.onError,[i._.transportError(i._.format(i.resources.parseFailed,r),t.transport,f,o)]),t.stop())},foreverFrame:{count:0,connections:{}}}}(window.jQuery,window),function(n,t){"use strict";var r=n.signalR,u=n.signalR.events,f=n.signalR.changeState,i=r.transports._logic;r.transports.webSockets={name:"webSockets",supportsKeepAlive:!0,send:function(t,f){var e=i.stringifySend(t,f);try{t.socket.send(e)}catch(o){n(t).triggerHandler(u.onError,[r._.transportError(r.resources.webSocketsInvalidState,t.transport,o,t.socket),f])}},start:function(e,o,s){var h,c=!1,l=this,a=!o,v=n(e);if(!t.WebSocket){s();return}e.socket||(h=e.webSocketServerUrl?e.webSocketServerUrl:e.wsProtocol+e.host,h+=i.getUrl(e,this.name,a),e.log("Connecting to websocket endpoint '"+h+"'."),e.socket=new t.WebSocket(h),e.socket.onopen=function(){c=!0;e.log("Websocket opened.");i.clearReconnectTimeout(e);f(e,r.connectionState.reconnecting,r.connectionState.connected)===!0&&v.triggerHandler(u.onReconnect)},e.socket.onclose=function(t){if(this===e.socket){if(c)typeof t.wasClean!="undefined"&&t.wasClean===!1?(n(e).triggerHandler(u.onError,[r._.transportError(r.resources.webSocketClosed,e.transport,t)]),e.log("Unclean disconnect from websocket: "+t.reason||"[no reason given].")):e.log("Websocket closed.");else{s?s():a&&l.reconnect(e);return}l.reconnect(e)}},e.socket.onmessage=function(t){var r;try{r=e._parseResponse(t.data)}catch(u){i.handleParseFailure(e,t.data,u,s,t);return}r&&(n.isEmptyObject(r)||r.M?i.processMessages(e,r,o):i.triggerReceived(e,r))})},reconnect:function(n){i.reconnect(n,this.name)},lostConnection:function(n){this.reconnect(n)},stop:function(n){i.clearReconnectTimeout(n);n.socket&&(n.log("Closing the Websocket."),n.socket.close(),n.socket=null)},abort:function(n,t){i.ajaxAbort(n,t)}}}(window.jQuery,window),function(n,t){"use strict";var i=n.signalR,u=n.signalR.events,f=n.signalR.changeState,r=i.transports._logic;i.transports.serverSentEvents={name:"serverSentEvents",supportsKeepAlive:!0,timeOut:3e3,start:function(e,o,s){var h=this,c=!1,l=n(e),a=!o,v,y;if(e.eventSource&&(e.log("The connection already has an event source. Stopping it."),e.stop()),!t.EventSource){s&&(e.log("This browser doesn't support SSE."),s());return}v=r.getUrl(e,this.name,a);try{e.log("Attempting to connect to SSE endpoint '"+v+"'.");e.eventSource=new t.EventSource(v,{withCredentials:e.withCredentials})}catch(p){e.log("EventSource failed trying to connect with error "+p.Message+".");s?s():(l.triggerHandler(u.onError,[i._.transportError(i.resources.eventSourceFailedToConnect,e.transport,p)]),a&&h.reconnect(e));return}a&&(y=t.setTimeout(function(){c===!1&&e.eventSource.readyState!==t.EventSource.OPEN&&h.reconnect(e)},h.timeOut));e.eventSource.addEventListener("open",function(){e.log("EventSource connected.");y&&t.clearTimeout(y);r.clearReconnectTimeout(e);c===!1&&(c=!0,f(e,i.connectionState.reconnecting,i.connectionState.connected)===!0&&l.triggerHandler(u.onReconnect))},!1);e.eventSource.addEventListener("message",function(n){var t;if(n.data!=="initialized"){try{t=e._parseResponse(n.data)}catch(i){r.handleParseFailure(e,n.data,i,s,n);return}r.processMessages(e,t,o)}},!1);e.eventSource.addEventListener("error",function(n){if(this===e.eventSource){if(!c){s&&s();return}e.log("EventSource readyState: "+e.eventSource.readyState+".");n.eventPhase===t.EventSource.CLOSED?(e.log("EventSource reconnecting due to the server connection ending."),h.reconnect(e)):(e.log("EventSource error."),l.triggerHandler(u.onError,[i._.transportError(i.resources.eventSourceError,e.transport,n)]))}},!1)},reconnect:function(n){r.reconnect(n,this.name)},lostConnection:function(n){this.reconnect(n)},send:function(n,t){r.ajaxSend(n,t)},stop:function(n){r.clearReconnectTimeout(n);n&&n.eventSource&&(n.log("EventSource calling close()."),n.eventSource.close(),n.eventSource=null,delete n.eventSource)},abort:function(n,t){r.ajaxAbort(n,t)}}}(window.jQuery,window),function(n,t){"use strict";var r=n.signalR,e=n.signalR.events,o=n.signalR.changeState,i=r.transports._logic,u=function(){var n=t.document.createElement("iframe");return n.setAttribute("style","position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;"),n},f=function(){var i=null,f=1e3,n=0;return{prevent:function(){r._.ieVersion<=8&&(n===0&&(i=t.setInterval(function(){var n=u();t.document.body.appendChild(n);t.document.body.removeChild(n);n=null},f)),n++)},cancel:function(){n===1&&t.clearInterval(i);n>0&&n--}}}();r.transports.foreverFrame={name:"foreverFrame",supportsKeepAlive:!0,iframeClearThreshold:50,start:function(n,r,e){var l=this,s=i.foreverFrame.count+=1,h,o=u(),c=function(){n.log("Forever frame iframe finished loading and is no longer receiving messages.");l.reconnect(n)};if(t.EventSource){e&&(n.log("This browser supports SSE, skipping Forever Frame."),e());return}o.setAttribute("data-signalr-connection-id",n.id);f.prevent();h=i.getUrl(n,this.name);h+="&frameId="+s;t.document.body.appendChild(o);n.log("Binding to iframe's load event.");o.addEventListener?o.addEventListener("load",c,!1):o.attachEvent&&o.attachEvent("onload",c);o.src=h;i.foreverFrame.connections[s]=n;n.frame=o;n.frameId=s;r&&(n.onSuccess=function(){n.log("Iframe transport started.");r()})},reconnect:function(n){var r=this;i.isConnectedOrReconnecting(n)&&i.verifyLastActive(n)&&t.setTimeout(function(){if(i.verifyLastActive(n)&&n.frame&&i.ensureReconnectingState(n)){var u=n.frame,t=i.getUrl(n,r.name,!0)+"&frameId="+n.frameId;n.log("Updating iframe src to '"+t+"'.");u.src=t}},n.reconnectDelay)},lostConnection:function(n){this.reconnect(n)},send:function(n,t){i.ajaxSend(n,t)},receive:function(t,u){var f,e;if(i.processMessages(t,u,t.onSuccess),t.state===n.signalR.connectionState.connected&&(t.frameMessageCount=(t.frameMessageCount||0)+1,t.frameMessageCount>r.transports.foreverFrame.iframeClearThreshold&&(t.frameMessageCount=0,f=t.frame.contentWindow||t.frame.contentDocument,f&&f.document&&f.document.body)))for(e=f.document.body;e.firstChild;)e.removeChild(e.firstChild)},stop:function(n){var r=null;if(f.cancel(),n.frame){if(n.frame.stop)n.frame.stop();else try{r=n.frame.contentWindow||n.frame.contentDocument;r.document&&r.document.execCommand&&r.document.execCommand("Stop")}catch(u){n.log("Error occured when stopping foreverFrame transport. Message = "+u.message+".")}n.frame.parentNode===t.document.body&&t.document.body.removeChild(n.frame);delete i.foreverFrame.connections[n.frameId];n.frame=null;n.frameId=null;delete n.frame;delete n.frameId;delete n.onSuccess;delete n.frameMessageCount;n.log("Stopping forever frame.")}},abort:function(n,t){i.ajaxAbort(n,t)},getConnection:function(n){return i.foreverFrame.connections[n]},started:function(t){o(t,r.connectionState.reconnecting,r.connectionState.connected)===!0&&n(t).triggerHandler(e.onReconnect)}}}(window.jQuery,window),function(n,t){"use strict";var i=n.signalR,u=n.signalR.events,e=n.signalR.changeState,f=n.signalR.isDisconnecting,r=i.transports._logic;i.transports.longPolling={name:"longPolling",supportsKeepAlive:!1,reconnectDelay:3e3,start:function(o,s,h){var a=this,v=function(){v=n.noop;o.log("LongPolling connected.");s();h=null},y=function(){return h?(h(),h=null,o.log("LongPolling failed to connect."),!0):!1},c=o._,l=0,p=function(r){t.clearTimeout(c.reconnectTimeoutId);c.reconnectTimeoutId=null;e(r,i.connectionState.reconnecting,i.connectionState.connected)===!0&&(r.log("Raising the reconnect event"),n(r).triggerHandler(u.onReconnect))},w=36e5;o.pollXhr&&(o.log("Polling xhr requests already exists, aborting."),o.stop());o.messageId=null;c.reconnectTimeoutId=null;c.pollTimeoutId=t.setTimeout(function(){(function e(s,h){var d=s.messageId,g=d===null,b=!g,nt=!h,k=r.getUrl(s,a.name,b,nt);f(s)!==!0&&(o.log("Opening long polling request to '"+k+"'."),s.pollXhr=n.ajax(n.extend({},n.signalR.ajaxDefaults,{xhrFields:{withCredentials:o.withCredentials},url:k,type:"GET",dataType:o.ajaxDataType,contentType:o.contentType,success:function(i){var h,w=0,u,a;o.log("Long poll complete.");l=0;try{h=o._parseResponse(i)}catch(b){r.handleParseFailure(s,i,b,y,s.pollXhr);return}(c.reconnectTimeoutId!==null&&p(s),h&&(u=r.maximizePersistentResponse(h)),r.processMessages(s,h,v),u&&n.type(u.LongPollDelay)==="number"&&(w=u.LongPollDelay),u&&u.Disconnect)||f(s)!==!0&&(a=u&&u.ShouldReconnect,!a||r.ensureReconnectingState(s))&&(w>0?c.pollTimeoutId=t.setTimeout(function(){e(s,a)},w):e(s,a))},error:function(f,h){if(t.clearTimeout(c.reconnectTimeoutId),c.reconnectTimeoutId=null,h==="abort"){o.log("Aborted xhr request.");return}if(!y()){if(l++,o.state!==i.connectionState.reconnecting&&(o.log("An error occurred using longPolling. Status = "+h+". Response = "+f.responseText+"."),n(s).triggerHandler(u.onError,[i._.transportError(i.resources.longPollFailed,o.transport,f,s.pollXhr)])),(o.state===i.connectionState.connected||o.state===i.connectionState.reconnecting)&&!r.verifyLastActive(o))return;if(!r.ensureReconnectingState(s))return;c.pollTimeoutId=t.setTimeout(function(){e(s,!0)},a.reconnectDelay)}}})),b&&h===!0&&(c.reconnectTimeoutId=t.setTimeout(function(){p(s)},Math.min(1e3*(Math.pow(2,l)-1),w))))})(o)},250)},lostConnection:function(){throw new Error("Lost Connection not handled for LongPolling");},send:function(n,t){r.ajaxSend(n,t)},stop:function(n){t.clearTimeout(n._.pollTimeoutId);t.clearTimeout(n._.reconnectTimeoutId);delete n._.pollTimeoutId;delete n._.reconnectTimeoutId;n.pollXhr&&(n.pollXhr.abort(),n.pollXhr=null,delete n.pollXhr)},abort:function(n,t){r.ajaxAbort(n,t)}}}(window.jQuery,window),function(n){"use strict";function r(n){return n+e}function s(n,t,i){for(var f=n.length,u=[],r=0;r - /// Validates the selected form. This method sets up event handlers for submit, focus, - /// keyup, blur and click to trigger validation of the entire form or individual - /// elements. Each one can be disabled, see the onxxx options (onsubmit, onfocusout, - /// onkeyup, onclick). focusInvalid focuses elements when submitting a invalid form. - /// - /// - /// A set of key/value pairs that configure the validate. All options are optional. - /// - - // if nothing is selected, return nothing; can't chain anyway - if (!this.length) { - options && options.debug && window.console && console.warn( "nothing selected, can't validate, returning nothing" ); - return; - } - - // check if a validator for this form was already created - var validator = $.data(this[0], 'validator'); - if ( validator ) { - return validator; - } - - validator = new $.validator( options, this[0] ); - $.data(this[0], 'validator', validator); - - if ( validator.settings.onsubmit ) { - - // allow suppresing validation by adding a cancel class to the submit button - this.find("input, button").filter(".cancel").click(function() { - validator.cancelSubmit = true; - }); - - // when a submitHandler is used, capture the submitting button - if (validator.settings.submitHandler) { - this.find("input, button").filter(":submit").click(function() { - validator.submitButton = this; - }); - } - - // validate the form on submit - this.submit( function( event ) { - if ( validator.settings.debug ) - // prevent form submit to be able to see console output - event.preventDefault(); - - function handle() { - if ( validator.settings.submitHandler ) { - if (validator.submitButton) { - // insert a hidden input as a replacement for the missing submit button - var hidden = $("").attr("name", validator.submitButton.name).val(validator.submitButton.value).appendTo(validator.currentForm); - } - validator.settings.submitHandler.call( validator, validator.currentForm ); - if (validator.submitButton) { - // and clean up afterwards; thanks to no-block-scope, hidden can be referenced - hidden.remove(); - } - return false; - } - return true; - } - - // prevent submit for invalid forms or custom submit handlers - if ( validator.cancelSubmit ) { - validator.cancelSubmit = false; - return handle(); - } - if ( validator.form() ) { - if ( validator.pendingRequest ) { - validator.formSubmitted = true; - return false; - } - return handle(); - } else { - validator.focusInvalid(); - return false; - } - }); - } - - return validator; - }, - // http://docs.jquery.com/Plugins/Validation/valid - valid: function() { - /// - /// Checks if the selected form is valid or if all selected elements are valid. - /// validate() needs to be called on the form before checking it using this method. - /// - /// - - if ( $(this[0]).is('form')) { - return this.validate().form(); - } else { - var valid = true; - var validator = $(this[0].form).validate(); - this.each(function() { - valid &= validator.element(this); - }); - return valid; - } - }, - // attributes: space seperated list of attributes to retrieve and remove - removeAttrs: function(attributes) { - /// - /// Remove the specified attributes from the first matched element and return them. - /// - /// - /// A space-seperated list of attribute names to remove. - /// - - var result = {}, - $element = this; - $.each(attributes.split(/\s/), function(index, value) { - result[value] = $element.attr(value); - $element.removeAttr(value); - }); - return result; - }, - // http://docs.jquery.com/Plugins/Validation/rules - rules: function(command, argument) { - /// - /// Return the validations rules for the first selected element. - /// - /// - /// Can be either "add" or "remove". - /// - /// - /// A list of rules to add or remove. - /// - - var element = this[0]; - - if (command) { - var settings = $.data(element.form, 'validator').settings; - var staticRules = settings.rules; - var existingRules = $.validator.staticRules(element); - switch(command) { - case "add": - $.extend(existingRules, $.validator.normalizeRule(argument)); - staticRules[element.name] = existingRules; - if (argument.messages) - settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages ); - break; - case "remove": - if (!argument) { - delete staticRules[element.name]; - return existingRules; - } - var filtered = {}; - $.each(argument.split(/\s/), function(index, method) { - filtered[method] = existingRules[method]; - delete existingRules[method]; - }); - return filtered; - } - } - - var data = $.validator.normalizeRules( - $.extend( - {}, - $.validator.metadataRules(element), - $.validator.classRules(element), - $.validator.attributeRules(element), - $.validator.staticRules(element) - ), element); - - // make sure required is at front - if (data.required) { - var param = data.required; - delete data.required; - data = $.extend({required: param}, data); - } - - return data; - } -}); - -// Custom selectors -$.extend($.expr[":"], { - // http://docs.jquery.com/Plugins/Validation/blank - blank: function(a) {return !$.trim("" + a.value);}, - // http://docs.jquery.com/Plugins/Validation/filled - filled: function(a) {return !!$.trim("" + a.value);}, - // http://docs.jquery.com/Plugins/Validation/unchecked - unchecked: function(a) {return !a.checked;} -}); - -// constructor for validator -$.validator = function( options, form ) { - this.settings = $.extend( true, {}, $.validator.defaults, options ); - this.currentForm = form; - this.init(); -}; - -$.validator.format = function(source, params) { - /// - /// Replaces {n} placeholders with arguments. - /// One or more arguments can be passed, in addition to the string template itself, to insert - /// into the string. - /// - /// - /// The string to format. - /// - /// - /// The first argument to insert, or an array of Strings to insert - /// - /// - - if ( arguments.length == 1 ) - return function() { - var args = $.makeArray(arguments); - args.unshift(source); - return $.validator.format.apply( this, args ); - }; - if ( arguments.length > 2 && params.constructor != Array ) { - params = $.makeArray(arguments).slice(1); - } - if ( params.constructor != Array ) { - params = [ params ]; - } - $.each(params, function(i, n) { - source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n); - }); - return source; -}; - -$.extend($.validator, { - - defaults: { - messages: {}, - groups: {}, - rules: {}, - errorClass: "error", - validClass: "valid", - errorElement: "label", - focusInvalid: true, - errorContainer: $( [] ), - errorLabelContainer: $( [] ), - onsubmit: true, - ignore: [], - ignoreTitle: false, - onfocusin: function(element) { - this.lastActive = element; - - // hide error label and remove error class on focus if enabled - if ( this.settings.focusCleanup && !this.blockFocusCleanup ) { - this.settings.unhighlight && this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); - this.addWrapper(this.errorsFor(element)).hide(); - } - }, - onfocusout: function(element) { - if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) { - this.element(element); - } - }, - onkeyup: function(element) { - if ( element.name in this.submitted || element == this.lastElement ) { - this.element(element); - } - }, - onclick: function(element) { - // click on selects, radiobuttons and checkboxes - if ( element.name in this.submitted ) - this.element(element); - // or option elements, check parent select in that case - else if (element.parentNode.name in this.submitted) - this.element(element.parentNode); - }, - highlight: function( element, errorClass, validClass ) { - $(element).addClass(errorClass).removeClass(validClass); - }, - unhighlight: function( element, errorClass, validClass ) { - $(element).removeClass(errorClass).addClass(validClass); - } - }, - - // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults - setDefaults: function(settings) { - /// - /// Modify default settings for validation. - /// Accepts everything that Plugins/Validation/validate accepts. - /// - /// - /// Options to set as default. - /// - - $.extend( $.validator.defaults, settings ); - }, - - messages: { - required: "This field is required.", - remote: "Please fix this field.", - email: "Please enter a valid email address.", - url: "Please enter a valid URL.", - date: "Please enter a valid date.", - dateISO: "Please enter a valid date (ISO).", - number: "Please enter a valid number.", - digits: "Please enter only digits.", - creditcard: "Please enter a valid credit card number.", - equalTo: "Please enter the same value again.", - accept: "Please enter a value with a valid extension.", - maxlength: $.validator.format("Please enter no more than {0} characters."), - minlength: $.validator.format("Please enter at least {0} characters."), - rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."), - range: $.validator.format("Please enter a value between {0} and {1}."), - max: $.validator.format("Please enter a value less than or equal to {0}."), - min: $.validator.format("Please enter a value greater than or equal to {0}.") - }, - - autoCreateRanges: false, - - prototype: { - - init: function() { - this.labelContainer = $(this.settings.errorLabelContainer); - this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm); - this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer ); - this.submitted = {}; - this.valueCache = {}; - this.pendingRequest = 0; - this.pending = {}; - this.invalid = {}; - this.reset(); - - var groups = (this.groups = {}); - $.each(this.settings.groups, function(key, value) { - $.each(value.split(/\s/), function(index, name) { - groups[name] = key; - }); - }); - var rules = this.settings.rules; - $.each(rules, function(key, value) { - rules[key] = $.validator.normalizeRule(value); - }); - - function delegate(event) { - var validator = $.data(this[0].form, "validator"), - eventType = "on" + event.type.replace(/^validate/, ""); - validator.settings[eventType] && validator.settings[eventType].call(validator, this[0] ); - } - $(this.currentForm) - .validateDelegate(":text, :password, :file, select, textarea", "focusin focusout keyup", delegate) - .validateDelegate(":radio, :checkbox, select, option", "click", delegate); - - if (this.settings.invalidHandler) - $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler); - }, - - // http://docs.jquery.com/Plugins/Validation/Validator/form - form: function() { - /// - /// Validates the form, returns true if it is valid, false otherwise. - /// This behaves as a normal submit event, but returns the result. - /// - /// - - this.checkForm(); - $.extend(this.submitted, this.errorMap); - this.invalid = $.extend({}, this.errorMap); - if (!this.valid()) - $(this.currentForm).triggerHandler("invalid-form", [this]); - this.showErrors(); - return this.valid(); - }, - - checkForm: function() { - this.prepareForm(); - for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) { - this.check( elements[i] ); - } - return this.valid(); - }, - - // http://docs.jquery.com/Plugins/Validation/Validator/element - element: function( element ) { - /// - /// Validates a single element, returns true if it is valid, false otherwise. - /// This behaves as validation on blur or keyup, but returns the result. - /// - /// - /// An element to validate, must be inside the validated form. - /// - /// - - element = this.clean( element ); - this.lastElement = element; - this.prepareElement( element ); - this.currentElements = $(element); - var result = this.check( element ); - if ( result ) { - delete this.invalid[element.name]; - } else { - this.invalid[element.name] = true; - } - if ( !this.numberOfInvalids() ) { - // Hide error containers on last error - this.toHide = this.toHide.add( this.containers ); - } - this.showErrors(); - return result; - }, - - // http://docs.jquery.com/Plugins/Validation/Validator/showErrors - showErrors: function(errors) { - /// - /// Show the specified messages. - /// Keys have to refer to the names of elements, values are displayed for those elements, using the configured error placement. - /// - /// - /// One or more key/value pairs of input names and messages. - /// - - if(errors) { - // add items to error list and map - $.extend( this.errorMap, errors ); - this.errorList = []; - for ( var name in errors ) { - this.errorList.push({ - message: errors[name], - element: this.findByName(name)[0] - }); - } - // remove items from success list - this.successList = $.grep( this.successList, function(element) { - return !(element.name in errors); - }); - } - this.settings.showErrors - ? this.settings.showErrors.call( this, this.errorMap, this.errorList ) - : this.defaultShowErrors(); - }, - - // http://docs.jquery.com/Plugins/Validation/Validator/resetForm - resetForm: function() { - /// - /// Resets the controlled form. - /// Resets input fields to their original value (requires form plugin), removes classes - /// indicating invalid elements and hides error messages. - /// - - if ( $.fn.resetForm ) - $( this.currentForm ).resetForm(); - this.submitted = {}; - this.prepareForm(); - this.hideErrors(); - this.elements().removeClass( this.settings.errorClass ); - }, - - numberOfInvalids: function() { - /// - /// Returns the number of invalid fields. - /// This depends on the internal validator state. It covers all fields only after - /// validating the complete form (on submit or via $("form").valid()). After validating - /// a single element, only that element is counted. Most useful in combination with the - /// invalidHandler-option. - /// - /// - - return this.objectLength(this.invalid); - }, - - objectLength: function( obj ) { - var count = 0; - for ( var i in obj ) - count++; - return count; - }, - - hideErrors: function() { - this.addWrapper( this.toHide ).hide(); - }, - - valid: function() { - return this.size() == 0; - }, - - size: function() { - return this.errorList.length; - }, - - focusInvalid: function() { - if( this.settings.focusInvalid ) { - try { - $(this.findLastActive() || this.errorList.length && this.errorList[0].element || []) - .filter(":visible") - .focus() - // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find - .trigger("focusin"); - } catch(e) { - // ignore IE throwing errors when focusing hidden elements - } - } - }, - - findLastActive: function() { - var lastActive = this.lastActive; - return lastActive && $.grep(this.errorList, function(n) { - return n.element.name == lastActive.name; - }).length == 1 && lastActive; - }, - - elements: function() { - var validator = this, - rulesCache = {}; - - // select all valid inputs inside the form (no submit or reset buttons) - // workaround $Query([]).add until http://dev.jquery.com/ticket/2114 is solved - return $([]).add(this.currentForm.elements) - .filter(":input") - .not(":submit, :reset, :image, [disabled]") - .not( this.settings.ignore ) - .filter(function() { - !this.name && validator.settings.debug && window.console && console.error( "%o has no name assigned", this); - - // select only the first element for each name, and only those with rules specified - if ( this.name in rulesCache || !validator.objectLength($(this).rules()) ) - return false; - - rulesCache[this.name] = true; - return true; - }); - }, - - clean: function( selector ) { - return $( selector )[0]; - }, - - errors: function() { - return $( this.settings.errorElement + "." + this.settings.errorClass, this.errorContext ); - }, - - reset: function() { - this.successList = []; - this.errorList = []; - this.errorMap = {}; - this.toShow = $([]); - this.toHide = $([]); - this.currentElements = $([]); - }, - - prepareForm: function() { - this.reset(); - this.toHide = this.errors().add( this.containers ); - }, - - prepareElement: function( element ) { - this.reset(); - this.toHide = this.errorsFor(element); - }, - - check: function( element ) { - element = this.clean( element ); - - // if radio/checkbox, validate first element in group instead - if (this.checkable(element)) { - element = this.findByName(element.name).not(this.settings.ignore)[0]; - } - - var rules = $(element).rules(); - var dependencyMismatch = false; - for (var method in rules) { - var rule = { method: method, parameters: rules[method] }; - try { - var result = $.validator.methods[method].call( this, element.value.replace(/\r/g, ""), element, rule.parameters ); - - // if a method indicates that the field is optional and therefore valid, - // don't mark it as valid when there are no other rules - if ( result == "dependency-mismatch" ) { - dependencyMismatch = true; - continue; - } - dependencyMismatch = false; - - if ( result == "pending" ) { - this.toHide = this.toHide.not( this.errorsFor(element) ); - return; - } - - if( !result ) { - this.formatAndAdd( element, rule ); - return false; - } - } catch(e) { - this.settings.debug && window.console && console.log("exception occured when checking element " + element.id - + ", check the '" + rule.method + "' method", e); - throw e; - } - } - if (dependencyMismatch) - return; - if ( this.objectLength(rules) ) - this.successList.push(element); - return true; - }, - - // return the custom message for the given element and validation method - // specified in the element's "messages" metadata - customMetaMessage: function(element, method) { - if (!$.metadata) - return; - - var meta = this.settings.meta - ? $(element).metadata()[this.settings.meta] - : $(element).metadata(); - - return meta && meta.messages && meta.messages[method]; - }, - - // return the custom message for the given element name and validation method - customMessage: function( name, method ) { - var m = this.settings.messages[name]; - return m && (m.constructor == String - ? m - : m[method]); - }, - - // return the first defined argument, allowing empty strings - findDefined: function() { - for(var i = 0; i < arguments.length; i++) { - if (arguments[i] !== undefined) - return arguments[i]; - } - return undefined; - }, - - defaultMessage: function( element, method) { - return this.findDefined( - this.customMessage( element.name, method ), - this.customMetaMessage( element, method ), - // title is never undefined, so handle empty string as undefined - !this.settings.ignoreTitle && element.title || undefined, - $.validator.messages[method], - "Warning: No message defined for " + element.name + "" - ); - }, - - formatAndAdd: function( element, rule ) { - var message = this.defaultMessage( element, rule.method ), - theregex = /\$?\{(\d+)\}/g; - if ( typeof message == "function" ) { - message = message.call(this, rule.parameters, element); - } else if (theregex.test(message)) { - message = jQuery.format(message.replace(theregex, '{$1}'), rule.parameters); - } - this.errorList.push({ - message: message, - element: element - }); - - this.errorMap[element.name] = message; - this.submitted[element.name] = message; - }, - - addWrapper: function(toToggle) { - if ( this.settings.wrapper ) - toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) ); - return toToggle; - }, - - defaultShowErrors: function() { - for ( var i = 0; this.errorList[i]; i++ ) { - var error = this.errorList[i]; - this.settings.highlight && this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); - this.showLabel( error.element, error.message ); - } - if( this.errorList.length ) { - this.toShow = this.toShow.add( this.containers ); - } - if (this.settings.success) { - for ( var i = 0; this.successList[i]; i++ ) { - this.showLabel( this.successList[i] ); - } - } - if (this.settings.unhighlight) { - for ( var i = 0, elements = this.validElements(); elements[i]; i++ ) { - this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass ); - } - } - this.toHide = this.toHide.not( this.toShow ); - this.hideErrors(); - this.addWrapper( this.toShow ).show(); - }, - - validElements: function() { - return this.currentElements.not(this.invalidElements()); - }, - - invalidElements: function() { - return $(this.errorList).map(function() { - return this.element; - }); - }, - - showLabel: function(element, message) { - var label = this.errorsFor( element ); - if ( label.length ) { - // refresh error/success class - label.removeClass().addClass( this.settings.errorClass ); - - // check if we have a generated label, replace the message then - label.attr("generated") && label.html(message); - } else { - // create label - label = $("<" + this.settings.errorElement + "/>") - .attr({"for": this.idOrName(element), generated: true}) - .addClass(this.settings.errorClass) - .html(message || ""); - if ( this.settings.wrapper ) { - // make sure the element is visible, even in IE - // actually showing the wrapped element is handled elsewhere - label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent(); - } - if ( !this.labelContainer.append(label).length ) - this.settings.errorPlacement - ? this.settings.errorPlacement(label, $(element) ) - : label.insertAfter(element); - } - if ( !message && this.settings.success ) { - label.text(""); - typeof this.settings.success == "string" - ? label.addClass( this.settings.success ) - : this.settings.success( label ); - } - this.toShow = this.toShow.add(label); - }, - - errorsFor: function(element) { - var name = this.idOrName(element); - return this.errors().filter(function() { - return $(this).attr('for') == name; - }); - }, - - idOrName: function(element) { - return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name); - }, - - checkable: function( element ) { - return /radio|checkbox/i.test(element.type); - }, - - findByName: function( name ) { - // select by name and filter by form for performance over form.find("[name=...]") - var form = this.currentForm; - return $(document.getElementsByName(name)).map(function(index, element) { - return element.form == form && element.name == name && element || null; - }); - }, - - getLength: function(value, element) { - switch( element.nodeName.toLowerCase() ) { - case 'select': - return $("option:selected", element).length; - case 'input': - if( this.checkable( element) ) - return this.findByName(element.name).filter(':checked').length; - } - return value.length; - }, - - depend: function(param, element) { - return this.dependTypes[typeof param] - ? this.dependTypes[typeof param](param, element) - : true; - }, - - dependTypes: { - "boolean": function(param, element) { - return param; - }, - "string": function(param, element) { - return !!$(param, element.form).length; - }, - "function": function(param, element) { - return param(element); - } - }, - - optional: function(element) { - return !$.validator.methods.required.call(this, $.trim(element.value), element) && "dependency-mismatch"; - }, - - startRequest: function(element) { - if (!this.pending[element.name]) { - this.pendingRequest++; - this.pending[element.name] = true; - } - }, - - stopRequest: function(element, valid) { - this.pendingRequest--; - // sometimes synchronization fails, make sure pendingRequest is never < 0 - if (this.pendingRequest < 0) - this.pendingRequest = 0; - delete this.pending[element.name]; - if ( valid && this.pendingRequest == 0 && this.formSubmitted && this.form() ) { - $(this.currentForm).submit(); - this.formSubmitted = false; - } else if (!valid && this.pendingRequest == 0 && this.formSubmitted) { - $(this.currentForm).triggerHandler("invalid-form", [this]); - this.formSubmitted = false; - } - }, - - previousValue: function(element) { - return $.data(element, "previousValue") || $.data(element, "previousValue", { - old: null, - valid: true, - message: this.defaultMessage( element, "remote" ) - }); - } - - }, - - classRuleSettings: { - required: {required: true}, - email: {email: true}, - url: {url: true}, - date: {date: true}, - dateISO: {dateISO: true}, - dateDE: {dateDE: true}, - number: {number: true}, - numberDE: {numberDE: true}, - digits: {digits: true}, - creditcard: {creditcard: true} - }, - - addClassRules: function(className, rules) { - /// - /// Add a compound class method - useful to refactor common combinations of rules into a single - /// class. - /// - /// - /// The name of the class rule to add - /// - /// - /// The compound rules - /// - - className.constructor == String ? - this.classRuleSettings[className] = rules : - $.extend(this.classRuleSettings, className); - }, - - classRules: function(element) { - var rules = {}; - var classes = $(element).attr('class'); - classes && $.each(classes.split(' '), function() { - if (this in $.validator.classRuleSettings) { - $.extend(rules, $.validator.classRuleSettings[this]); - } - }); - return rules; - }, - - attributeRules: function(element) { - var rules = {}; - var $element = $(element); - - for (var method in $.validator.methods) { - var value = $element.attr(method); - if (value) { - rules[method] = value; - } - } - - // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs - if (rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength)) { - delete rules.maxlength; - } - - return rules; - }, - - metadataRules: function(element) { - if (!$.metadata) return {}; - - var meta = $.data(element.form, 'validator').settings.meta; - return meta ? - $(element).metadata()[meta] : - $(element).metadata(); - }, - - staticRules: function(element) { - var rules = {}; - var validator = $.data(element.form, 'validator'); - if (validator.settings.rules) { - rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {}; - } - return rules; - }, - - normalizeRules: function(rules, element) { - // handle dependency check - $.each(rules, function(prop, val) { - // ignore rule when param is explicitly false, eg. required:false - if (val === false) { - delete rules[prop]; - return; - } - if (val.param || val.depends) { - var keepRule = true; - switch (typeof val.depends) { - case "string": - keepRule = !!$(val.depends, element.form).length; - break; - case "function": - keepRule = val.depends.call(element, element); - break; - } - if (keepRule) { - rules[prop] = val.param !== undefined ? val.param : true; - } else { - delete rules[prop]; - } - } - }); - - // evaluate parameters - $.each(rules, function(rule, parameter) { - rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter; - }); - - // clean number parameters - $.each(['minlength', 'maxlength', 'min', 'max'], function() { - if (rules[this]) { - rules[this] = Number(rules[this]); - } - }); - $.each(['rangelength', 'range'], function() { - if (rules[this]) { - rules[this] = [Number(rules[this][0]), Number(rules[this][1])]; - } - }); - - if ($.validator.autoCreateRanges) { - // auto-create ranges - if (rules.min && rules.max) { - rules.range = [rules.min, rules.max]; - delete rules.min; - delete rules.max; - } - if (rules.minlength && rules.maxlength) { - rules.rangelength = [rules.minlength, rules.maxlength]; - delete rules.minlength; - delete rules.maxlength; - } - } - - // To support custom messages in metadata ignore rule methods titled "messages" - if (rules.messages) { - delete rules.messages; - } - - return rules; - }, - - // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true} - normalizeRule: function(data) { - if( typeof data == "string" ) { - var transformed = {}; - $.each(data.split(/\s/), function() { - transformed[this] = true; - }); - data = transformed; - } - return data; - }, - - // http://docs.jquery.com/Plugins/Validation/Validator/addMethod - addMethod: function(name, method, message) { - /// - /// Add a custom validation method. It must consist of a name (must be a legal javascript - /// identifier), a javascript based function and a default string message. - /// - /// - /// The name of the method, used to identify and referencing it, must be a valid javascript - /// identifier - /// - /// - /// The actual method implementation, returning true if an element is valid - /// - /// - /// (Optional) The default message to display for this method. Can be a function created by - /// jQuery.validator.format(value). When undefined, an already existing message is used - /// (handy for localization), otherwise the field-specific messages have to be defined. - /// - - $.validator.methods[name] = method; - $.validator.messages[name] = message != undefined ? message : $.validator.messages[name]; - if (method.length < 3) { - $.validator.addClassRules(name, $.validator.normalizeRule(name)); - } - }, - - methods: { - - // http://docs.jquery.com/Plugins/Validation/Methods/required - required: function(value, element, param) { - // check if dependency is met - if ( !this.depend(param, element) ) - return "dependency-mismatch"; - switch( element.nodeName.toLowerCase() ) { - case 'select': - // could be an array for select-multiple or a string, both are fine this way - var val = $(element).val(); - return val && val.length > 0; - case 'input': - if ( this.checkable(element) ) - return this.getLength(value, element) > 0; - default: - return $.trim(value).length > 0; - } - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/remote - remote: function(value, element, param) { - if ( this.optional(element) ) - return "dependency-mismatch"; - - var previous = this.previousValue(element); - if (!this.settings.messages[element.name] ) - this.settings.messages[element.name] = {}; - previous.originalMessage = this.settings.messages[element.name].remote; - this.settings.messages[element.name].remote = previous.message; - - param = typeof param == "string" && {url:param} || param; - - if ( this.pending[element.name] ) { - return "pending"; - } - if ( previous.old === value ) { - return previous.valid; - } - - previous.old = value; - var validator = this; - this.startRequest(element); - var data = {}; - data[element.name] = value; - $.ajax($.extend(true, { - url: param, - mode: "abort", - port: "validate" + element.name, - dataType: "json", - data: data, - success: function(response) { - validator.settings.messages[element.name].remote = previous.originalMessage; - var valid = response === true; - if ( valid ) { - var submitted = validator.formSubmitted; - validator.prepareElement(element); - validator.formSubmitted = submitted; - validator.successList.push(element); - validator.showErrors(); - } else { - var errors = {}; - var message = response || validator.defaultMessage(element, "remote"); - errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message; - validator.showErrors(errors); - } - previous.valid = valid; - validator.stopRequest(element, valid); - } - }, param)); - return "pending"; - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/minlength - minlength: function(value, element, param) { - return this.optional(element) || this.getLength($.trim(value), element) >= param; - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/maxlength - maxlength: function(value, element, param) { - return this.optional(element) || this.getLength($.trim(value), element) <= param; - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/rangelength - rangelength: function(value, element, param) { - var length = this.getLength($.trim(value), element); - return this.optional(element) || ( length >= param[0] && length <= param[1] ); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/min - min: function( value, element, param ) { - return this.optional(element) || value >= param; - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/max - max: function( value, element, param ) { - return this.optional(element) || value <= param; - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/range - range: function( value, element, param ) { - return this.optional(element) || ( value >= param[0] && value <= param[1] ); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/email - email: function(value, element) { - // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/ - return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/url - url: function(value, element) { - // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/ - return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/date - date: function(value, element) { - return this.optional(element) || !/Invalid|NaN/.test(new Date(value)); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/dateISO - dateISO: function(value, element) { - return this.optional(element) || /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(value); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/number - number: function(value, element) { - return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(value); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/digits - digits: function(value, element) { - return this.optional(element) || /^\d+$/.test(value); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/creditcard - // based on http://en.wikipedia.org/wiki/Luhn - creditcard: function(value, element) { - if ( this.optional(element) ) - return "dependency-mismatch"; - // accept only digits and dashes - if (/[^0-9-]+/.test(value)) - return false; - var nCheck = 0, - nDigit = 0, - bEven = false; - - value = value.replace(/\D/g, ""); - - for (var n = value.length - 1; n >= 0; n--) { - var cDigit = value.charAt(n); - var nDigit = parseInt(cDigit, 10); - if (bEven) { - if ((nDigit *= 2) > 9) - nDigit -= 9; - } - nCheck += nDigit; - bEven = !bEven; - } - - return (nCheck % 10) == 0; - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/accept - accept: function(value, element, param) { - param = typeof param == "string" ? param.replace(/,/g, '|') : "png|jpe?g|gif"; - return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i")); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/equalTo - equalTo: function(value, element, param) { - // bind to the blur event of the target in order to revalidate whenever the target field is updated - // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead - var target = $(param).unbind(".validate-equalTo").bind("blur.validate-equalTo", function() { - $(element).valid(); - }); - return value == target.val(); - } - - } - -}); - -// deprecated, use $.validator.format instead -$.format = $.validator.format; - -})(jQuery); - -// ajax mode: abort -// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); -// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() -;(function($) { - var pendingRequests = {}; - // Use a prefilter if available (1.5+) - if ( $.ajaxPrefilter ) { - $.ajaxPrefilter(function(settings, _, xhr) { - var port = settings.port; - if (settings.mode == "abort") { - if ( pendingRequests[port] ) { - pendingRequests[port].abort(); - } pendingRequests[port] = xhr; - } - }); - } else { - // Proxy ajax - var ajax = $.ajax; - $.ajax = function(settings) { - var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, - port = ( "port" in settings ? settings : $.ajaxSettings ).port; - if (mode == "abort") { - if ( pendingRequests[port] ) { - pendingRequests[port].abort(); - } - - return (pendingRequests[port] = ajax.apply(this, arguments)); - } - return ajax.apply(this, arguments); - }; - } -})(jQuery); - -// provides cross-browser focusin and focusout events -// IE has native support, in other browsers, use event caputuring (neither bubbles) - -// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation -// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target -;(function($) { - // only implement if not provided by jQuery core (since 1.4) - // TODO verify if jQuery 1.4's implementation is compatible with older jQuery special-event APIs - if (!jQuery.event.special.focusin && !jQuery.event.special.focusout && document.addEventListener) { - $.each({ - focus: 'focusin', - blur: 'focusout' - }, function( original, fix ){ - $.event.special[fix] = { - setup:function() { - this.addEventListener( original, handler, true ); - }, - teardown:function() { - this.removeEventListener( original, handler, true ); - }, - handler: function(e) { - arguments[0] = $.event.fix(e); - arguments[0].type = fix; - return $.event.handle.apply(this, arguments); - } - }; - function handler(e) { - e = $.event.fix(e); - e.type = fix; - return $.event.handle.call(this, e); - } - }); - }; - $.extend($.fn, { - validateDelegate: function(delegate, type, handler) { - return this.bind(type, function(event) { - var target = $(event.target); - if (target.is(delegate)) { - return handler.apply(target, arguments); - } - }); - } - }); -})(jQuery); diff --git a/SampleWebApp/Scripts/jquery.validate.js b/SampleWebApp/Scripts/jquery.validate.js deleted file mode 100644 index d0a9bc9..0000000 --- a/SampleWebApp/Scripts/jquery.validate.js +++ /dev/null @@ -1,1245 +0,0 @@ -/* NUGET: BEGIN LICENSE TEXT - * - * Microsoft grants you the right to use these script files for the sole - * purpose of either: (i) interacting through your browser with the Microsoft - * website or online service, subject to the applicable licensing or use - * terms; or (ii) using the files as included with a Microsoft product subject - * to that product's license terms. Microsoft reserves all other rights to the - * files not expressly granted by Microsoft, whether by implication, estoppel - * or otherwise. Insofar as a script file is dual licensed under GPL, - * Microsoft neither took the code under GPL nor distributes it thereunder but - * under the terms set out in this paragraph. All notices and licenses - * below are for informational purposes only. - * - * NUGET: END LICENSE TEXT */ -/*! - * jQuery Validation Plugin 1.11.1 - * - * http://bassistance.de/jquery-plugins/jquery-plugin-validation/ - * http://docs.jquery.com/Plugins/Validation - * - * Copyright 2013 Jörn Zaefferer - * Released under the MIT license: - * http://www.opensource.org/licenses/mit-license.php - */ - -(function($) { - -$.extend($.fn, { - // http://docs.jquery.com/Plugins/Validation/validate - validate: function( options ) { - - // if nothing is selected, return nothing; can't chain anyway - if ( !this.length ) { - if ( options && options.debug && window.console ) { - console.warn( "Nothing selected, can't validate, returning nothing." ); - } - return; - } - - // check if a validator for this form was already created - var validator = $.data( this[0], "validator" ); - if ( validator ) { - return validator; - } - - // Add novalidate tag if HTML5. - this.attr( "novalidate", "novalidate" ); - - validator = new $.validator( options, this[0] ); - $.data( this[0], "validator", validator ); - - if ( validator.settings.onsubmit ) { - - this.validateDelegate( ":submit", "click", function( event ) { - if ( validator.settings.submitHandler ) { - validator.submitButton = event.target; - } - // allow suppressing validation by adding a cancel class to the submit button - if ( $(event.target).hasClass("cancel") ) { - validator.cancelSubmit = true; - } - - // allow suppressing validation by adding the html5 formnovalidate attribute to the submit button - if ( $(event.target).attr("formnovalidate") !== undefined ) { - validator.cancelSubmit = true; - } - }); - - // validate the form on submit - this.submit( function( event ) { - if ( validator.settings.debug ) { - // prevent form submit to be able to see console output - event.preventDefault(); - } - function handle() { - var hidden; - if ( validator.settings.submitHandler ) { - if ( validator.submitButton ) { - // insert a hidden input as a replacement for the missing submit button - hidden = $("").attr("name", validator.submitButton.name).val( $(validator.submitButton).val() ).appendTo(validator.currentForm); - } - validator.settings.submitHandler.call( validator, validator.currentForm, event ); - if ( validator.submitButton ) { - // and clean up afterwards; thanks to no-block-scope, hidden can be referenced - hidden.remove(); - } - return false; - } - return true; - } - - // prevent submit for invalid forms or custom submit handlers - if ( validator.cancelSubmit ) { - validator.cancelSubmit = false; - return handle(); - } - if ( validator.form() ) { - if ( validator.pendingRequest ) { - validator.formSubmitted = true; - return false; - } - return handle(); - } else { - validator.focusInvalid(); - return false; - } - }); - } - - return validator; - }, - // http://docs.jquery.com/Plugins/Validation/valid - valid: function() { - if ( $(this[0]).is("form")) { - return this.validate().form(); - } else { - var valid = true; - var validator = $(this[0].form).validate(); - this.each(function() { - valid = valid && validator.element(this); - }); - return valid; - } - }, - // attributes: space seperated list of attributes to retrieve and remove - removeAttrs: function( attributes ) { - var result = {}, - $element = this; - $.each(attributes.split(/\s/), function( index, value ) { - result[value] = $element.attr(value); - $element.removeAttr(value); - }); - return result; - }, - // http://docs.jquery.com/Plugins/Validation/rules - rules: function( command, argument ) { - var element = this[0]; - - if ( command ) { - var settings = $.data(element.form, "validator").settings; - var staticRules = settings.rules; - var existingRules = $.validator.staticRules(element); - switch(command) { - case "add": - $.extend(existingRules, $.validator.normalizeRule(argument)); - // remove messages from rules, but allow them to be set separetely - delete existingRules.messages; - staticRules[element.name] = existingRules; - if ( argument.messages ) { - settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages ); - } - break; - case "remove": - if ( !argument ) { - delete staticRules[element.name]; - return existingRules; - } - var filtered = {}; - $.each(argument.split(/\s/), function( index, method ) { - filtered[method] = existingRules[method]; - delete existingRules[method]; - }); - return filtered; - } - } - - var data = $.validator.normalizeRules( - $.extend( - {}, - $.validator.classRules(element), - $.validator.attributeRules(element), - $.validator.dataRules(element), - $.validator.staticRules(element) - ), element); - - // make sure required is at front - if ( data.required ) { - var param = data.required; - delete data.required; - data = $.extend({required: param}, data); - } - - return data; - } -}); - -// Custom selectors -$.extend($.expr[":"], { - // http://docs.jquery.com/Plugins/Validation/blank - blank: function( a ) { return !$.trim("" + $(a).val()); }, - // http://docs.jquery.com/Plugins/Validation/filled - filled: function( a ) { return !!$.trim("" + $(a).val()); }, - // http://docs.jquery.com/Plugins/Validation/unchecked - unchecked: function( a ) { return !$(a).prop("checked"); } -}); - -// constructor for validator -$.validator = function( options, form ) { - this.settings = $.extend( true, {}, $.validator.defaults, options ); - this.currentForm = form; - this.init(); -}; - -$.validator.format = function( source, params ) { - if ( arguments.length === 1 ) { - return function() { - var args = $.makeArray(arguments); - args.unshift(source); - return $.validator.format.apply( this, args ); - }; - } - if ( arguments.length > 2 && params.constructor !== Array ) { - params = $.makeArray(arguments).slice(1); - } - if ( params.constructor !== Array ) { - params = [ params ]; - } - $.each(params, function( i, n ) { - source = source.replace( new RegExp("\\{" + i + "\\}", "g"), function() { - return n; - }); - }); - return source; -}; - -$.extend($.validator, { - - defaults: { - messages: {}, - groups: {}, - rules: {}, - errorClass: "error", - validClass: "valid", - errorElement: "label", - focusInvalid: true, - errorContainer: $([]), - errorLabelContainer: $([]), - onsubmit: true, - ignore: ":hidden", - ignoreTitle: false, - onfocusin: function( element, event ) { - this.lastActive = element; - - // hide error label and remove error class on focus if enabled - if ( this.settings.focusCleanup && !this.blockFocusCleanup ) { - if ( this.settings.unhighlight ) { - this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); - } - this.addWrapper(this.errorsFor(element)).hide(); - } - }, - onfocusout: function( element, event ) { - if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) { - this.element(element); - } - }, - onkeyup: function( element, event ) { - if ( event.which === 9 && this.elementValue(element) === "" ) { - return; - } else if ( element.name in this.submitted || element === this.lastElement ) { - this.element(element); - } - }, - onclick: function( element, event ) { - // click on selects, radiobuttons and checkboxes - if ( element.name in this.submitted ) { - this.element(element); - } - // or option elements, check parent select in that case - else if ( element.parentNode.name in this.submitted ) { - this.element(element.parentNode); - } - }, - highlight: function( element, errorClass, validClass ) { - if ( element.type === "radio" ) { - this.findByName(element.name).addClass(errorClass).removeClass(validClass); - } else { - $(element).addClass(errorClass).removeClass(validClass); - } - }, - unhighlight: function( element, errorClass, validClass ) { - if ( element.type === "radio" ) { - this.findByName(element.name).removeClass(errorClass).addClass(validClass); - } else { - $(element).removeClass(errorClass).addClass(validClass); - } - } - }, - - // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults - setDefaults: function( settings ) { - $.extend( $.validator.defaults, settings ); - }, - - messages: { - required: "This field is required.", - remote: "Please fix this field.", - email: "Please enter a valid email address.", - url: "Please enter a valid URL.", - date: "Please enter a valid date.", - dateISO: "Please enter a valid date (ISO).", - number: "Please enter a valid number.", - digits: "Please enter only digits.", - creditcard: "Please enter a valid credit card number.", - equalTo: "Please enter the same value again.", - maxlength: $.validator.format("Please enter no more than {0} characters."), - minlength: $.validator.format("Please enter at least {0} characters."), - rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."), - range: $.validator.format("Please enter a value between {0} and {1}."), - max: $.validator.format("Please enter a value less than or equal to {0}."), - min: $.validator.format("Please enter a value greater than or equal to {0}.") - }, - - autoCreateRanges: false, - - prototype: { - - init: function() { - this.labelContainer = $(this.settings.errorLabelContainer); - this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm); - this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer ); - this.submitted = {}; - this.valueCache = {}; - this.pendingRequest = 0; - this.pending = {}; - this.invalid = {}; - this.reset(); - - var groups = (this.groups = {}); - $.each(this.settings.groups, function( key, value ) { - if ( typeof value === "string" ) { - value = value.split(/\s/); - } - $.each(value, function( index, name ) { - groups[name] = key; - }); - }); - var rules = this.settings.rules; - $.each(rules, function( key, value ) { - rules[key] = $.validator.normalizeRule(value); - }); - - function delegate(event) { - var validator = $.data(this[0].form, "validator"), - eventType = "on" + event.type.replace(/^validate/, ""); - if ( validator.settings[eventType] ) { - validator.settings[eventType].call(validator, this[0], event); - } - } - $(this.currentForm) - .validateDelegate(":text, [type='password'], [type='file'], select, textarea, " + - "[type='number'], [type='search'] ,[type='tel'], [type='url'], " + - "[type='email'], [type='datetime'], [type='date'], [type='month'], " + - "[type='week'], [type='time'], [type='datetime-local'], " + - "[type='range'], [type='color'] ", - "focusin focusout keyup", delegate) - .validateDelegate("[type='radio'], [type='checkbox'], select, option", "click", delegate); - - if ( this.settings.invalidHandler ) { - $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler); - } - }, - - // http://docs.jquery.com/Plugins/Validation/Validator/form - form: function() { - this.checkForm(); - $.extend(this.submitted, this.errorMap); - this.invalid = $.extend({}, this.errorMap); - if ( !this.valid() ) { - $(this.currentForm).triggerHandler("invalid-form", [this]); - } - this.showErrors(); - return this.valid(); - }, - - checkForm: function() { - this.prepareForm(); - for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) { - this.check( elements[i] ); - } - return this.valid(); - }, - - // http://docs.jquery.com/Plugins/Validation/Validator/element - element: function( element ) { - element = this.validationTargetFor( this.clean( element ) ); - this.lastElement = element; - this.prepareElement( element ); - this.currentElements = $(element); - var result = this.check( element ) !== false; - if ( result ) { - delete this.invalid[element.name]; - } else { - this.invalid[element.name] = true; - } - if ( !this.numberOfInvalids() ) { - // Hide error containers on last error - this.toHide = this.toHide.add( this.containers ); - } - this.showErrors(); - return result; - }, - - // http://docs.jquery.com/Plugins/Validation/Validator/showErrors - showErrors: function( errors ) { - if ( errors ) { - // add items to error list and map - $.extend( this.errorMap, errors ); - this.errorList = []; - for ( var name in errors ) { - this.errorList.push({ - message: errors[name], - element: this.findByName(name)[0] - }); - } - // remove items from success list - this.successList = $.grep( this.successList, function( element ) { - return !(element.name in errors); - }); - } - if ( this.settings.showErrors ) { - this.settings.showErrors.call( this, this.errorMap, this.errorList ); - } else { - this.defaultShowErrors(); - } - }, - - // http://docs.jquery.com/Plugins/Validation/Validator/resetForm - resetForm: function() { - if ( $.fn.resetForm ) { - $(this.currentForm).resetForm(); - } - this.submitted = {}; - this.lastElement = null; - this.prepareForm(); - this.hideErrors(); - this.elements().removeClass( this.settings.errorClass ).removeData( "previousValue" ); - }, - - numberOfInvalids: function() { - return this.objectLength(this.invalid); - }, - - objectLength: function( obj ) { - var count = 0; - for ( var i in obj ) { - count++; - } - return count; - }, - - hideErrors: function() { - this.addWrapper( this.toHide ).hide(); - }, - - valid: function() { - return this.size() === 0; - }, - - size: function() { - return this.errorList.length; - }, - - focusInvalid: function() { - if ( this.settings.focusInvalid ) { - try { - $(this.findLastActive() || this.errorList.length && this.errorList[0].element || []) - .filter(":visible") - .focus() - // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find - .trigger("focusin"); - } catch(e) { - // ignore IE throwing errors when focusing hidden elements - } - } - }, - - findLastActive: function() { - var lastActive = this.lastActive; - return lastActive && $.grep(this.errorList, function( n ) { - return n.element.name === lastActive.name; - }).length === 1 && lastActive; - }, - - elements: function() { - var validator = this, - rulesCache = {}; - - // select all valid inputs inside the form (no submit or reset buttons) - return $(this.currentForm) - .find("input, select, textarea") - .not(":submit, :reset, :image, [disabled]") - .not( this.settings.ignore ) - .filter(function() { - if ( !this.name && validator.settings.debug && window.console ) { - console.error( "%o has no name assigned", this); - } - - // select only the first element for each name, and only those with rules specified - if ( this.name in rulesCache || !validator.objectLength($(this).rules()) ) { - return false; - } - - rulesCache[this.name] = true; - return true; - }); - }, - - clean: function( selector ) { - return $(selector)[0]; - }, - - errors: function() { - var errorClass = this.settings.errorClass.replace(" ", "."); - return $(this.settings.errorElement + "." + errorClass, this.errorContext); - }, - - reset: function() { - this.successList = []; - this.errorList = []; - this.errorMap = {}; - this.toShow = $([]); - this.toHide = $([]); - this.currentElements = $([]); - }, - - prepareForm: function() { - this.reset(); - this.toHide = this.errors().add( this.containers ); - }, - - prepareElement: function( element ) { - this.reset(); - this.toHide = this.errorsFor(element); - }, - - elementValue: function( element ) { - var type = $(element).attr("type"), - val = $(element).val(); - - if ( type === "radio" || type === "checkbox" ) { - return $("input[name='" + $(element).attr("name") + "']:checked").val(); - } - - if ( typeof val === "string" ) { - return val.replace(/\r/g, ""); - } - return val; - }, - - check: function( element ) { - element = this.validationTargetFor( this.clean( element ) ); - - var rules = $(element).rules(); - var dependencyMismatch = false; - var val = this.elementValue(element); - var result; - - for (var method in rules ) { - var rule = { method: method, parameters: rules[method] }; - try { - - result = $.validator.methods[method].call( this, val, element, rule.parameters ); - - // if a method indicates that the field is optional and therefore valid, - // don't mark it as valid when there are no other rules - if ( result === "dependency-mismatch" ) { - dependencyMismatch = true; - continue; - } - dependencyMismatch = false; - - if ( result === "pending" ) { - this.toHide = this.toHide.not( this.errorsFor(element) ); - return; - } - - if ( !result ) { - this.formatAndAdd( element, rule ); - return false; - } - } catch(e) { - if ( this.settings.debug && window.console ) { - console.log( "Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e ); - } - throw e; - } - } - if ( dependencyMismatch ) { - return; - } - if ( this.objectLength(rules) ) { - this.successList.push(element); - } - return true; - }, - - // return the custom message for the given element and validation method - // specified in the element's HTML5 data attribute - customDataMessage: function( element, method ) { - return $(element).data("msg-" + method.toLowerCase()) || (element.attributes && $(element).attr("data-msg-" + method.toLowerCase())); - }, - - // return the custom message for the given element name and validation method - customMessage: function( name, method ) { - var m = this.settings.messages[name]; - return m && (m.constructor === String ? m : m[method]); - }, - - // return the first defined argument, allowing empty strings - findDefined: function() { - for(var i = 0; i < arguments.length; i++) { - if ( arguments[i] !== undefined ) { - return arguments[i]; - } - } - return undefined; - }, - - defaultMessage: function( element, method ) { - return this.findDefined( - this.customMessage( element.name, method ), - this.customDataMessage( element, method ), - // title is never undefined, so handle empty string as undefined - !this.settings.ignoreTitle && element.title || undefined, - $.validator.messages[method], - "Warning: No message defined for " + element.name + "" - ); - }, - - formatAndAdd: function( element, rule ) { - var message = this.defaultMessage( element, rule.method ), - theregex = /\$?\{(\d+)\}/g; - if ( typeof message === "function" ) { - message = message.call(this, rule.parameters, element); - } else if (theregex.test(message)) { - message = $.validator.format(message.replace(theregex, "{$1}"), rule.parameters); - } - this.errorList.push({ - message: message, - element: element - }); - - this.errorMap[element.name] = message; - this.submitted[element.name] = message; - }, - - addWrapper: function( toToggle ) { - if ( this.settings.wrapper ) { - toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) ); - } - return toToggle; - }, - - defaultShowErrors: function() { - var i, elements; - for ( i = 0; this.errorList[i]; i++ ) { - var error = this.errorList[i]; - if ( this.settings.highlight ) { - this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); - } - this.showLabel( error.element, error.message ); - } - if ( this.errorList.length ) { - this.toShow = this.toShow.add( this.containers ); - } - if ( this.settings.success ) { - for ( i = 0; this.successList[i]; i++ ) { - this.showLabel( this.successList[i] ); - } - } - if ( this.settings.unhighlight ) { - for ( i = 0, elements = this.validElements(); elements[i]; i++ ) { - this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass ); - } - } - this.toHide = this.toHide.not( this.toShow ); - this.hideErrors(); - this.addWrapper( this.toShow ).show(); - }, - - validElements: function() { - return this.currentElements.not(this.invalidElements()); - }, - - invalidElements: function() { - return $(this.errorList).map(function() { - return this.element; - }); - }, - - showLabel: function( element, message ) { - var label = this.errorsFor( element ); - if ( label.length ) { - // refresh error/success class - label.removeClass( this.settings.validClass ).addClass( this.settings.errorClass ); - // replace message on existing label - label.html(message); - } else { - // create label - label = $("<" + this.settings.errorElement + ">") - .attr("for", this.idOrName(element)) - .addClass(this.settings.errorClass) - .html(message || ""); - if ( this.settings.wrapper ) { - // make sure the element is visible, even in IE - // actually showing the wrapped element is handled elsewhere - label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent(); - } - if ( !this.labelContainer.append(label).length ) { - if ( this.settings.errorPlacement ) { - this.settings.errorPlacement(label, $(element) ); - } else { - label.insertAfter(element); - } - } - } - if ( !message && this.settings.success ) { - label.text(""); - if ( typeof this.settings.success === "string" ) { - label.addClass( this.settings.success ); - } else { - this.settings.success( label, element ); - } - } - this.toShow = this.toShow.add(label); - }, - - errorsFor: function( element ) { - var name = this.idOrName(element); - return this.errors().filter(function() { - return $(this).attr("for") === name; - }); - }, - - idOrName: function( element ) { - return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name); - }, - - validationTargetFor: function( element ) { - // if radio/checkbox, validate first element in group instead - if ( this.checkable(element) ) { - element = this.findByName( element.name ).not(this.settings.ignore)[0]; - } - return element; - }, - - checkable: function( element ) { - return (/radio|checkbox/i).test(element.type); - }, - - findByName: function( name ) { - return $(this.currentForm).find("[name='" + name + "']"); - }, - - getLength: function( value, element ) { - switch( element.nodeName.toLowerCase() ) { - case "select": - return $("option:selected", element).length; - case "input": - if ( this.checkable( element) ) { - return this.findByName(element.name).filter(":checked").length; - } - } - return value.length; - }, - - depend: function( param, element ) { - return this.dependTypes[typeof param] ? this.dependTypes[typeof param](param, element) : true; - }, - - dependTypes: { - "boolean": function( param, element ) { - return param; - }, - "string": function( param, element ) { - return !!$(param, element.form).length; - }, - "function": function( param, element ) { - return param(element); - } - }, - - optional: function( element ) { - var val = this.elementValue(element); - return !$.validator.methods.required.call(this, val, element) && "dependency-mismatch"; - }, - - startRequest: function( element ) { - if ( !this.pending[element.name] ) { - this.pendingRequest++; - this.pending[element.name] = true; - } - }, - - stopRequest: function( element, valid ) { - this.pendingRequest--; - // sometimes synchronization fails, make sure pendingRequest is never < 0 - if ( this.pendingRequest < 0 ) { - this.pendingRequest = 0; - } - delete this.pending[element.name]; - if ( valid && this.pendingRequest === 0 && this.formSubmitted && this.form() ) { - $(this.currentForm).submit(); - this.formSubmitted = false; - } else if (!valid && this.pendingRequest === 0 && this.formSubmitted) { - $(this.currentForm).triggerHandler("invalid-form", [this]); - this.formSubmitted = false; - } - }, - - previousValue: function( element ) { - return $.data(element, "previousValue") || $.data(element, "previousValue", { - old: null, - valid: true, - message: this.defaultMessage( element, "remote" ) - }); - } - - }, - - classRuleSettings: { - required: {required: true}, - email: {email: true}, - url: {url: true}, - date: {date: true}, - dateISO: {dateISO: true}, - number: {number: true}, - digits: {digits: true}, - creditcard: {creditcard: true} - }, - - addClassRules: function( className, rules ) { - if ( className.constructor === String ) { - this.classRuleSettings[className] = rules; - } else { - $.extend(this.classRuleSettings, className); - } - }, - - classRules: function( element ) { - var rules = {}; - var classes = $(element).attr("class"); - if ( classes ) { - $.each(classes.split(" "), function() { - if ( this in $.validator.classRuleSettings ) { - $.extend(rules, $.validator.classRuleSettings[this]); - } - }); - } - return rules; - }, - - attributeRules: function( element ) { - var rules = {}; - var $element = $(element); - var type = $element[0].getAttribute("type"); - - for (var method in $.validator.methods) { - var value; - - // support for in both html5 and older browsers - if ( method === "required" ) { - value = $element.get(0).getAttribute(method); - // Some browsers return an empty string for the required attribute - // and non-HTML5 browsers might have required="" markup - if ( value === "" ) { - value = true; - } - // force non-HTML5 browsers to return bool - value = !!value; - } else { - value = $element.attr(method); - } - - // convert the value to a number for number inputs, and for text for backwards compability - // allows type="date" and others to be compared as strings - if ( /min|max/.test( method ) && ( type === null || /number|range|text/.test( type ) ) ) { - value = Number(value); - } - - if ( value ) { - rules[method] = value; - } else if ( type === method && type !== 'range' ) { - // exception: the jquery validate 'range' method - // does not test for the html5 'range' type - rules[method] = true; - } - } - - // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs - if ( rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength) ) { - delete rules.maxlength; - } - - return rules; - }, - - dataRules: function( element ) { - var method, value, - rules = {}, $element = $(element); - for (method in $.validator.methods) { - value = $element.data("rule-" + method.toLowerCase()); - if ( value !== undefined ) { - rules[method] = value; - } - } - return rules; - }, - - staticRules: function( element ) { - var rules = {}; - var validator = $.data(element.form, "validator"); - if ( validator.settings.rules ) { - rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {}; - } - return rules; - }, - - normalizeRules: function( rules, element ) { - // handle dependency check - $.each(rules, function( prop, val ) { - // ignore rule when param is explicitly false, eg. required:false - if ( val === false ) { - delete rules[prop]; - return; - } - if ( val.param || val.depends ) { - var keepRule = true; - switch (typeof val.depends) { - case "string": - keepRule = !!$(val.depends, element.form).length; - break; - case "function": - keepRule = val.depends.call(element, element); - break; - } - if ( keepRule ) { - rules[prop] = val.param !== undefined ? val.param : true; - } else { - delete rules[prop]; - } - } - }); - - // evaluate parameters - $.each(rules, function( rule, parameter ) { - rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter; - }); - - // clean number parameters - $.each(['minlength', 'maxlength'], function() { - if ( rules[this] ) { - rules[this] = Number(rules[this]); - } - }); - $.each(['rangelength', 'range'], function() { - var parts; - if ( rules[this] ) { - if ( $.isArray(rules[this]) ) { - rules[this] = [Number(rules[this][0]), Number(rules[this][1])]; - } else if ( typeof rules[this] === "string" ) { - parts = rules[this].split(/[\s,]+/); - rules[this] = [Number(parts[0]), Number(parts[1])]; - } - } - }); - - if ( $.validator.autoCreateRanges ) { - // auto-create ranges - if ( rules.min && rules.max ) { - rules.range = [rules.min, rules.max]; - delete rules.min; - delete rules.max; - } - if ( rules.minlength && rules.maxlength ) { - rules.rangelength = [rules.minlength, rules.maxlength]; - delete rules.minlength; - delete rules.maxlength; - } - } - - return rules; - }, - - // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true} - normalizeRule: function( data ) { - if ( typeof data === "string" ) { - var transformed = {}; - $.each(data.split(/\s/), function() { - transformed[this] = true; - }); - data = transformed; - } - return data; - }, - - // http://docs.jquery.com/Plugins/Validation/Validator/addMethod - addMethod: function( name, method, message ) { - $.validator.methods[name] = method; - $.validator.messages[name] = message !== undefined ? message : $.validator.messages[name]; - if ( method.length < 3 ) { - $.validator.addClassRules(name, $.validator.normalizeRule(name)); - } - }, - - methods: { - - // http://docs.jquery.com/Plugins/Validation/Methods/required - required: function( value, element, param ) { - // check if dependency is met - if ( !this.depend(param, element) ) { - return "dependency-mismatch"; - } - if ( element.nodeName.toLowerCase() === "select" ) { - // could be an array for select-multiple or a string, both are fine this way - var val = $(element).val(); - return val && val.length > 0; - } - if ( this.checkable(element) ) { - return this.getLength(value, element) > 0; - } - return $.trim(value).length > 0; - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/email - email: function( value, element ) { - // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/ - return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(value); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/url - url: function( value, element ) { - // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/ - return this.optional(element) || /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/date - date: function( value, element ) { - return this.optional(element) || !/Invalid|NaN/.test(new Date(value).toString()); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/dateISO - dateISO: function( value, element ) { - return this.optional(element) || /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/.test(value); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/number - number: function( value, element ) { - return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/digits - digits: function( value, element ) { - return this.optional(element) || /^\d+$/.test(value); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/creditcard - // based on http://en.wikipedia.org/wiki/Luhn - creditcard: function( value, element ) { - if ( this.optional(element) ) { - return "dependency-mismatch"; - } - // accept only spaces, digits and dashes - if ( /[^0-9 \-]+/.test(value) ) { - return false; - } - var nCheck = 0, - nDigit = 0, - bEven = false; - - value = value.replace(/\D/g, ""); - - for (var n = value.length - 1; n >= 0; n--) { - var cDigit = value.charAt(n); - nDigit = parseInt(cDigit, 10); - if ( bEven ) { - if ( (nDigit *= 2) > 9 ) { - nDigit -= 9; - } - } - nCheck += nDigit; - bEven = !bEven; - } - - return (nCheck % 10) === 0; - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/minlength - minlength: function( value, element, param ) { - var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element); - return this.optional(element) || length >= param; - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/maxlength - maxlength: function( value, element, param ) { - var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element); - return this.optional(element) || length <= param; - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/rangelength - rangelength: function( value, element, param ) { - var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element); - return this.optional(element) || ( length >= param[0] && length <= param[1] ); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/min - min: function( value, element, param ) { - return this.optional(element) || value >= param; - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/max - max: function( value, element, param ) { - return this.optional(element) || value <= param; - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/range - range: function( value, element, param ) { - return this.optional(element) || ( value >= param[0] && value <= param[1] ); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/equalTo - equalTo: function( value, element, param ) { - // bind to the blur event of the target in order to revalidate whenever the target field is updated - // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead - var target = $(param); - if ( this.settings.onfocusout ) { - target.unbind(".validate-equalTo").bind("blur.validate-equalTo", function() { - $(element).valid(); - }); - } - return value === target.val(); - }, - - // http://docs.jquery.com/Plugins/Validation/Methods/remote - remote: function( value, element, param ) { - if ( this.optional(element) ) { - return "dependency-mismatch"; - } - - var previous = this.previousValue(element); - if (!this.settings.messages[element.name] ) { - this.settings.messages[element.name] = {}; - } - previous.originalMessage = this.settings.messages[element.name].remote; - this.settings.messages[element.name].remote = previous.message; - - param = typeof param === "string" && {url:param} || param; - - if ( previous.old === value ) { - return previous.valid; - } - - previous.old = value; - var validator = this; - this.startRequest(element); - var data = {}; - data[element.name] = value; - $.ajax($.extend(true, { - url: param, - mode: "abort", - port: "validate" + element.name, - dataType: "json", - data: data, - success: function( response ) { - validator.settings.messages[element.name].remote = previous.originalMessage; - var valid = response === true || response === "true"; - if ( valid ) { - var submitted = validator.formSubmitted; - validator.prepareElement(element); - validator.formSubmitted = submitted; - validator.successList.push(element); - delete validator.invalid[element.name]; - validator.showErrors(); - } else { - var errors = {}; - var message = response || validator.defaultMessage( element, "remote" ); - errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message; - validator.invalid[element.name] = true; - validator.showErrors(errors); - } - previous.valid = valid; - validator.stopRequest(element, valid); - } - }, param)); - return "pending"; - } - - } - -}); - -// deprecated, use $.validator.format instead -$.format = $.validator.format; - -}(jQuery)); - -// ajax mode: abort -// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); -// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() -(function($) { - var pendingRequests = {}; - // Use a prefilter if available (1.5+) - if ( $.ajaxPrefilter ) { - $.ajaxPrefilter(function( settings, _, xhr ) { - var port = settings.port; - if ( settings.mode === "abort" ) { - if ( pendingRequests[port] ) { - pendingRequests[port].abort(); - } - pendingRequests[port] = xhr; - } - }); - } else { - // Proxy ajax - var ajax = $.ajax; - $.ajax = function( settings ) { - var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, - port = ( "port" in settings ? settings : $.ajaxSettings ).port; - if ( mode === "abort" ) { - if ( pendingRequests[port] ) { - pendingRequests[port].abort(); - } - pendingRequests[port] = ajax.apply(this, arguments); - return pendingRequests[port]; - } - return ajax.apply(this, arguments); - }; - } -}(jQuery)); - -// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation -// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target -(function($) { - $.extend($.fn, { - validateDelegate: function( delegate, type, handler ) { - return this.bind(type, function( event ) { - var target = $(event.target); - if ( target.is(delegate) ) { - return handler.apply(target, arguments); - } - }); - } - }); -}(jQuery)); diff --git a/SampleWebApp/Scripts/jquery.validate.unobtrusive.min.js b/SampleWebApp/Scripts/jquery.validate.unobtrusive.min.js deleted file mode 100644 index dfeaf38..0000000 --- a/SampleWebApp/Scripts/jquery.validate.unobtrusive.min.js +++ /dev/null @@ -1,19 +0,0 @@ -/* NUGET: BEGIN LICENSE TEXT - * - * Microsoft grants you the right to use these script files for the sole - * purpose of either: (i) interacting through your browser with the Microsoft - * website or online service, subject to the applicable licensing or use - * terms; or (ii) using the files as included with a Microsoft product subject - * to that product's license terms. Microsoft reserves all other rights to the - * files not expressly granted by Microsoft, whether by implication, estoppel - * or otherwise. Insofar as a script file is dual licensed under GPL, - * Microsoft neither took the code under GPL nor distributes it thereunder but - * under the terms set out in this paragraph. All notices and licenses - * below are for informational purposes only. - * - * NUGET: END LICENSE TEXT */ -/* -** Unobtrusive validation support library for jQuery and jQuery Validate -** Copyright (C) Microsoft Corporation. All rights reserved. -*/ -(function(a){var d=a.validator,b,e="unobtrusiveValidation";function c(a,b,c){a.rules[b]=c;if(a.message)a.messages[b]=a.message}function j(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function f(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function h(a){return a.substr(0,a.lastIndexOf(".")+1)}function g(a,b){if(a.indexOf("*.")===0)a=a.replace("*.",b);return a}function m(c,e){var b=a(this).find("[data-valmsg-for='"+f(e[0].name)+"']"),d=b.attr("data-valmsg-replace"),g=d?a.parseJSON(d)!==false:null;b.removeClass("field-validation-valid").addClass("field-validation-error");c.data("unobtrusiveContainer",b);if(g){b.empty();c.removeClass("input-validation-error").appendTo(b)}else c.hide()}function l(e,d){var c=a(this).find("[data-valmsg-summary=true]"),b=c.find("ul");if(b&&b.length&&d.errorList.length){b.empty();c.addClass("validation-summary-errors").removeClass("validation-summary-valid");a.each(d.errorList,function(){a("
  • ").html(this.message).appendTo(b)})}}function k(d){var b=d.data("unobtrusiveContainer"),c=b.attr("data-valmsg-replace"),e=c?a.parseJSON(c):null;if(b){b.addClass("field-validation-valid").removeClass("field-validation-error");d.removeData("unobtrusiveContainer");e&&b.empty()}}function n(){var b=a(this),c="__jquery_unobtrusive_validation_form_reset";if(b.data(c))return;b.data(c,true);try{b.data("validator").resetForm()}finally{b.removeData(c)}b.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors");b.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}function i(b){var c=a(b),f=c.data(e),i=a.proxy(n,b),g=d.unobtrusive.options||{},h=function(e,d){var c=g[e];c&&a.isFunction(c)&&c.apply(b,d)};if(!f){f={options:{errorClass:g.errorClass||"input-validation-error",errorElement:g.errorElement||"span",errorPlacement:function(){m.apply(b,arguments);h("errorPlacement",arguments)},invalidHandler:function(){l.apply(b,arguments);h("invalidHandler",arguments)},messages:{},rules:{},success:function(){k.apply(b,arguments);h("success",arguments)}},attachValidation:function(){c.off("reset."+e,i).on("reset."+e,i).validate(this.options)},validate:function(){c.validate();return c.valid()}};c.data(e,f)}return f}d.unobtrusive={adapters:[],parseElement:function(b,h){var d=a(b),f=d.parents("form")[0],c,e,g;if(!f)return;c=i(f);c.options.rules[b.name]=e={};c.options.messages[b.name]=g={};a.each(this.adapters,function(){var c="data-val-"+this.name,i=d.attr(c),h={};if(i!==undefined){c+="-";a.each(this.params,function(){h[this]=d.attr(c+this)});this.adapt({element:b,form:f,message:i,params:h,rules:e,messages:g})}});a.extend(e,{__dummy__:true});!h&&c.attachValidation()},parse:function(c){var b=a(c),e=b.parents().addBack().filter("form").add(b.find("form")).has("[data-val=true]");b.find("[data-val=true]").each(function(){d.unobtrusive.parseElement(this,true)});e.each(function(){var a=i(this);a&&a.attachValidation()})}};b=d.unobtrusive.adapters;b.add=function(c,a,b){if(!b){b=a;a=[]}this.push({name:c,params:a,adapt:b});return this};b.addBool=function(a,b){return this.add(a,function(d){c(d,b||a,true)})};b.addMinMax=function(e,g,f,a,d,b){return this.add(e,[d||"min",b||"max"],function(b){var e=b.params.min,d=b.params.max;if(e&&d)c(b,a,[e,d]);else if(e)c(b,g,e);else d&&c(b,f,d)})};b.addSingleVal=function(a,b,d){return this.add(a,[b||"val"],function(e){c(e,d||a,e.params[b])})};d.addMethod("__dummy__",function(){return true});d.addMethod("regex",function(b,c,d){var a;if(this.optional(c))return true;a=(new RegExp(d)).exec(b);return a&&a.index===0&&a[0].length===b.length});d.addMethod("nonalphamin",function(c,d,b){var a;if(b){a=c.match(/\W/g);a=a&&a.length>=b}return a});if(d.methods.extension){b.addSingleVal("accept","mimtype");b.addSingleVal("extension","extension")}else b.addSingleVal("extension","extension","accept");b.addSingleVal("regex","pattern");b.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");b.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range");b.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength");b.add("equalto",["other"],function(b){var i=h(b.element.name),j=b.params.other,d=g(j,i),e=a(b.form).find(":input").filter("[name='"+f(d)+"']")[0];c(b,"equalTo",e)});b.add("required",function(a){(a.element.tagName.toUpperCase()!=="INPUT"||a.element.type.toUpperCase()!=="CHECKBOX")&&c(a,"required",true)});b.add("remote",["url","type","additionalfields"],function(b){var d={url:b.params.url,type:b.params.type||"GET",data:{}},e=h(b.element.name);a.each(j(b.params.additionalfields||b.element.name),function(i,h){var c=g(h,e);d.data[c]=function(){var d=a(b.form).find(":input").filter("[name='"+f(c)+"']");return d.is(":checkbox")?d.filter(":checked").val()||d.filter(":hidden").val()||"":d.is(":radio")?d.filter(":checked").val()||"":d.val()}});c(b,"remote",d)});b.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&c(a,"minlength",a.params.min);a.params.nonalphamin&&c(a,"nonalphamin",a.params.nonalphamin);a.params.regex&&c(a,"regex",a.params.regex)});a(function(){d.unobtrusive.parse(document)})})(jQuery); \ No newline at end of file diff --git a/SampleWebApp/Scripts/modernizr-2.6.2.js b/SampleWebApp/Scripts/modernizr-2.6.2.js deleted file mode 100644 index cbfe1f3..0000000 --- a/SampleWebApp/Scripts/modernizr-2.6.2.js +++ /dev/null @@ -1,1416 +0,0 @@ -/* NUGET: BEGIN LICENSE TEXT - * - * Microsoft grants you the right to use these script files for the sole - * purpose of either: (i) interacting through your browser with the Microsoft - * website or online service, subject to the applicable licensing or use - * terms; or (ii) using the files as included with a Microsoft product subject - * to that product's license terms. Microsoft reserves all other rights to the - * files not expressly granted by Microsoft, whether by implication, estoppel - * or otherwise. Insofar as a script file is dual licensed under GPL, - * Microsoft neither took the code under GPL nor distributes it thereunder but - * under the terms set out in this paragraph. All notices and licenses - * below are for informational purposes only. - * - * Copyright (c) Faruk Ates, Paul Irish, Alex Sexton; http://www.modernizr.com/license/ - * - * Includes matchMedia polyfill; Copyright (c) 2010 Filament Group, Inc; http://opensource.org/licenses/MIT - * - * Includes material adapted from ES5-shim https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js; Copyright 2009-2012 by contributors; http://opensource.org/licenses/MIT - * - * Includes material from css-support; Copyright (c) 2005-2012 Diego Perini; https://github.com/dperini/css-support/blob/master/LICENSE - * - * NUGET: END LICENSE TEXT */ - -/*! - * Modernizr v2.6.2 - * www.modernizr.com - * - * Copyright (c) Faruk Ates, Paul Irish, Alex Sexton - * Available under the BSD and MIT licenses: www.modernizr.com/license/ - */ - -/* - * Modernizr tests which native CSS3 and HTML5 features are available in - * the current UA and makes the results available to you in two ways: - * as properties on a global Modernizr object, and as classes on the - * element. This information allows you to progressively enhance - * your pages with a granular level of control over the experience. - * - * Modernizr has an optional (not included) conditional resource loader - * called Modernizr.load(), based on Yepnope.js (yepnopejs.com). - * To get a build that includes Modernizr.load(), as well as choosing - * which tests to include, go to www.modernizr.com/download/ - * - * Authors Faruk Ates, Paul Irish, Alex Sexton - * Contributors Ryan Seddon, Ben Alman - */ - -window.Modernizr = (function( window, document, undefined ) { - - var version = '2.6.2', - - Modernizr = {}, - - /*>>cssclasses*/ - // option for enabling the HTML classes to be added - enableClasses = true, - /*>>cssclasses*/ - - docElement = document.documentElement, - - /** - * Create our "modernizr" element that we do most feature tests on. - */ - mod = 'modernizr', - modElem = document.createElement(mod), - mStyle = modElem.style, - - /** - * Create the input element for various Web Forms feature tests. - */ - inputElem /*>>inputelem*/ = document.createElement('input') /*>>inputelem*/ , - - /*>>smile*/ - smile = ':)', - /*>>smile*/ - - toString = {}.toString, - - // TODO :: make the prefixes more granular - /*>>prefixes*/ - // List of property values to set for css tests. See ticket #21 - prefixes = ' -webkit- -moz- -o- -ms- '.split(' '), - /*>>prefixes*/ - - /*>>domprefixes*/ - // Following spec is to expose vendor-specific style properties as: - // elem.style.WebkitBorderRadius - // and the following would be incorrect: - // elem.style.webkitBorderRadius - - // Webkit ghosts their properties in lowercase but Opera & Moz do not. - // Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+ - // erik.eae.net/archives/2008/03/10/21.48.10/ - - // More here: github.com/Modernizr/Modernizr/issues/issue/21 - omPrefixes = 'Webkit Moz O ms', - - cssomPrefixes = omPrefixes.split(' '), - - domPrefixes = omPrefixes.toLowerCase().split(' '), - /*>>domprefixes*/ - - /*>>ns*/ - ns = {'svg': 'http://www.w3.org/2000/svg'}, - /*>>ns*/ - - tests = {}, - inputs = {}, - attrs = {}, - - classes = [], - - slice = classes.slice, - - featureName, // used in testing loop - - - /*>>teststyles*/ - // Inject element with style element and some CSS rules - injectElementWithStyles = function( rule, callback, nodes, testnames ) { - - var style, ret, node, docOverflow, - div = document.createElement('div'), - // After page load injecting a fake body doesn't work so check if body exists - body = document.body, - // IE6 and 7 won't return offsetWidth or offsetHeight unless it's in the body element, so we fake it. - fakeBody = body || document.createElement('body'); - - if ( parseInt(nodes, 10) ) { - // In order not to give false positives we create a node for each test - // This also allows the method to scale for unspecified uses - while ( nodes-- ) { - node = document.createElement('div'); - node.id = testnames ? testnames[nodes] : mod + (nodes + 1); - div.appendChild(node); - } - } - - // '].join(''); - div.id = mod; - // IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody. - // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270 - (body ? div : fakeBody).innerHTML += style; - fakeBody.appendChild(div); - if ( !body ) { - //avoid crashing IE8, if background image is used - fakeBody.style.background = ''; - //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible - fakeBody.style.overflow = 'hidden'; - docOverflow = docElement.style.overflow; - docElement.style.overflow = 'hidden'; - docElement.appendChild(fakeBody); - } - - ret = callback(div, rule); - // If this is done after page load we don't want to remove the body so check if body exists - if ( !body ) { - fakeBody.parentNode.removeChild(fakeBody); - docElement.style.overflow = docOverflow; - } else { - div.parentNode.removeChild(div); - } - - return !!ret; - - }, - /*>>teststyles*/ - - /*>>mq*/ - // adapted from matchMedia polyfill - // by Scott Jehl and Paul Irish - // gist.github.com/786768 - testMediaQuery = function( mq ) { - - var matchMedia = window.matchMedia || window.msMatchMedia; - if ( matchMedia ) { - return matchMedia(mq).matches; - } - - var bool; - - injectElementWithStyles('@media ' + mq + ' { #' + mod + ' { position: absolute; } }', function( node ) { - bool = (window.getComputedStyle ? - getComputedStyle(node, null) : - node.currentStyle)['position'] == 'absolute'; - }); - - return bool; - - }, - /*>>mq*/ - - - /*>>hasevent*/ - // - // isEventSupported determines if a given element supports the given event - // kangax.github.com/iseventsupported/ - // - // The following results are known incorrects: - // Modernizr.hasEvent("webkitTransitionEnd", elem) // false negative - // Modernizr.hasEvent("textInput") // in Webkit. github.com/Modernizr/Modernizr/issues/333 - // ... - isEventSupported = (function() { - - var TAGNAMES = { - 'select': 'input', 'change': 'input', - 'submit': 'form', 'reset': 'form', - 'error': 'img', 'load': 'img', 'abort': 'img' - }; - - function isEventSupported( eventName, element ) { - - element = element || document.createElement(TAGNAMES[eventName] || 'div'); - eventName = 'on' + eventName; - - // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those - var isSupported = eventName in element; - - if ( !isSupported ) { - // If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element - if ( !element.setAttribute ) { - element = document.createElement('div'); - } - if ( element.setAttribute && element.removeAttribute ) { - element.setAttribute(eventName, ''); - isSupported = is(element[eventName], 'function'); - - // If property was created, "remove it" (by setting value to `undefined`) - if ( !is(element[eventName], 'undefined') ) { - element[eventName] = undefined; - } - element.removeAttribute(eventName); - } - } - - element = null; - return isSupported; - } - return isEventSupported; - })(), - /*>>hasevent*/ - - // TODO :: Add flag for hasownprop ? didn't last time - - // hasOwnProperty shim by kangax needed for Safari 2.0 support - _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; - - if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) { - hasOwnProp = function (object, property) { - return _hasOwnProperty.call(object, property); - }; - } - else { - hasOwnProp = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */ - return ((property in object) && is(object.constructor.prototype[property], 'undefined')); - }; - } - - // Adapted from ES5-shim https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js - // es5.github.com/#x15.3.4.5 - - if (!Function.prototype.bind) { - Function.prototype.bind = function bind(that) { - - var target = this; - - if (typeof target != "function") { - throw new TypeError(); - } - - var args = slice.call(arguments, 1), - bound = function () { - - if (this instanceof bound) { - - var F = function(){}; - F.prototype = target.prototype; - var self = new F(); - - var result = target.apply( - self, - args.concat(slice.call(arguments)) - ); - if (Object(result) === result) { - return result; - } - return self; - - } else { - - return target.apply( - that, - args.concat(slice.call(arguments)) - ); - - } - - }; - - return bound; - }; - } - - /** - * setCss applies given styles to the Modernizr DOM node. - */ - function setCss( str ) { - mStyle.cssText = str; - } - - /** - * setCssAll extrapolates all vendor-specific css strings. - */ - function setCssAll( str1, str2 ) { - return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); - } - - /** - * is returns a boolean for if typeof obj is exactly type. - */ - function is( obj, type ) { - return typeof obj === type; - } - - /** - * contains returns a boolean for if substr is found within str. - */ - function contains( str, substr ) { - return !!~('' + str).indexOf(substr); - } - - /*>>testprop*/ - - // testProps is a generic CSS / DOM property test. - - // In testing support for a given CSS property, it's legit to test: - // `elem.style[styleName] !== undefined` - // If the property is supported it will return an empty string, - // if unsupported it will return undefined. - - // We'll take advantage of this quick test and skip setting a style - // on our modernizr element, but instead just testing undefined vs - // empty string. - - // Because the testing of the CSS property names (with "-", as - // opposed to the camelCase DOM properties) is non-portable and - // non-standard but works in WebKit and IE (but not Gecko or Opera), - // we explicitly reject properties with dashes so that authors - // developing in WebKit or IE first don't end up with - // browser-specific content by accident. - - function testProps( props, prefixed ) { - for ( var i in props ) { - var prop = props[i]; - if ( !contains(prop, "-") && mStyle[prop] !== undefined ) { - return prefixed == 'pfx' ? prop : true; - } - } - return false; - } - /*>>testprop*/ - - // TODO :: add testDOMProps - /** - * testDOMProps is a generic DOM property test; if a browser supports - * a certain property, it won't return undefined for it. - */ - function testDOMProps( props, obj, elem ) { - for ( var i in props ) { - var item = obj[props[i]]; - if ( item !== undefined) { - - // return the property name as a string - if (elem === false) return props[i]; - - // let's bind a function - if (is(item, 'function')){ - // default to autobind unless override - return item.bind(elem || obj); - } - - // return the unbound function or obj or value - return item; - } - } - return false; - } - - /*>>testallprops*/ - /** - * testPropsAll tests a list of DOM properties we want to check against. - * We specify literally ALL possible (known and/or likely) properties on - * the element including the non-vendor prefixed one, for forward- - * compatibility. - */ - function testPropsAll( prop, prefixed, elem ) { - - var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), - props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' '); - - // did they call .prefixed('boxSizing') or are we just testing a prop? - if(is(prefixed, "string") || is(prefixed, "undefined")) { - return testProps(props, prefixed); - - // otherwise, they called .prefixed('requestAnimationFrame', window[, elem]) - } else { - props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' '); - return testDOMProps(props, prefixed, elem); - } - } - /*>>testallprops*/ - - - /** - * Tests - * ----- - */ - - // The *new* flexbox - // dev.w3.org/csswg/css3-flexbox - - tests['flexbox'] = function() { - return testPropsAll('flexWrap'); - }; - - // The *old* flexbox - // www.w3.org/TR/2009/WD-css3-flexbox-20090723/ - - tests['flexboxlegacy'] = function() { - return testPropsAll('boxDirection'); - }; - - // On the S60 and BB Storm, getContext exists, but always returns undefined - // so we actually have to call getContext() to verify - // github.com/Modernizr/Modernizr/issues/issue/97/ - - tests['canvas'] = function() { - var elem = document.createElement('canvas'); - return !!(elem.getContext && elem.getContext('2d')); - }; - - tests['canvastext'] = function() { - return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); - }; - - // webk.it/70117 is tracking a legit WebGL feature detect proposal - - // We do a soft detect which may false positive in order to avoid - // an expensive context creation: bugzil.la/732441 - - tests['webgl'] = function() { - return !!window.WebGLRenderingContext; - }; - - /* - * The Modernizr.touch test only indicates if the browser supports - * touch events, which does not necessarily reflect a touchscreen - * device, as evidenced by tablets running Windows 7 or, alas, - * the Palm Pre / WebOS (touch) phones. - * - * Additionally, Chrome (desktop) used to lie about its support on this, - * but that has since been rectified: crbug.com/36415 - * - * We also test for Firefox 4 Multitouch Support. - * - * For more info, see: modernizr.github.com/Modernizr/touch.html - */ - - tests['touch'] = function() { - var bool; - - if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { - bool = true; - } else { - injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) { - bool = node.offsetTop === 9; - }); - } - - return bool; - }; - - - // geolocation is often considered a trivial feature detect... - // Turns out, it's quite tricky to get right: - // - // Using !!navigator.geolocation does two things we don't want. It: - // 1. Leaks memory in IE9: github.com/Modernizr/Modernizr/issues/513 - // 2. Disables page caching in WebKit: webk.it/43956 - // - // Meanwhile, in Firefox < 8, an about:config setting could expose - // a false positive that would throw an exception: bugzil.la/688158 - - tests['geolocation'] = function() { - return 'geolocation' in navigator; - }; - - - tests['postmessage'] = function() { - return !!window.postMessage; - }; - - - // Chrome incognito mode used to throw an exception when using openDatabase - // It doesn't anymore. - tests['websqldatabase'] = function() { - return !!window.openDatabase; - }; - - // Vendors had inconsistent prefixing with the experimental Indexed DB: - // - Webkit's implementation is accessible through webkitIndexedDB - // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB - // For speed, we don't test the legacy (and beta-only) indexedDB - tests['indexedDB'] = function() { - return !!testPropsAll("indexedDB", window); - }; - - // documentMode logic from YUI to filter out IE8 Compat Mode - // which false positives. - tests['hashchange'] = function() { - return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7); - }; - - // Per 1.6: - // This used to be Modernizr.historymanagement but the longer - // name has been deprecated in favor of a shorter and property-matching one. - // The old API is still available in 1.6, but as of 2.0 will throw a warning, - // and in the first release thereafter disappear entirely. - tests['history'] = function() { - return !!(window.history && history.pushState); - }; - - tests['draganddrop'] = function() { - var div = document.createElement('div'); - return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div); - }; - - // FF3.6 was EOL'ed on 4/24/12, but the ESR version of FF10 - // will be supported until FF19 (2/12/13), at which time, ESR becomes FF17. - // FF10 still uses prefixes, so check for it until then. - // for more ESR info, see: mozilla.org/en-US/firefox/organizations/faq/ - tests['websockets'] = function() { - return 'WebSocket' in window || 'MozWebSocket' in window; - }; - - - // css-tricks.com/rgba-browser-support/ - tests['rgba'] = function() { - // Set an rgba() color and check the returned value - - setCss('background-color:rgba(150,255,150,.5)'); - - return contains(mStyle.backgroundColor, 'rgba'); - }; - - tests['hsla'] = function() { - // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally, - // except IE9 who retains it as hsla - - setCss('background-color:hsla(120,40%,100%,.5)'); - - return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla'); - }; - - tests['multiplebgs'] = function() { - // Setting multiple images AND a color on the background shorthand property - // and then querying the style.background property value for the number of - // occurrences of "url(" is a reliable method for detecting ACTUAL support for this! - - setCss('background:url(https://),url(https://),red url(https://)'); - - // If the UA supports multiple backgrounds, there should be three occurrences - // of the string "url(" in the return value for elemStyle.background - - return (/(url\s*\(.*?){3}/).test(mStyle.background); - }; - - - - // this will false positive in Opera Mini - // github.com/Modernizr/Modernizr/issues/396 - - tests['backgroundsize'] = function() { - return testPropsAll('backgroundSize'); - }; - - tests['borderimage'] = function() { - return testPropsAll('borderImage'); - }; - - - // Super comprehensive table about all the unique implementations of - // border-radius: muddledramblings.com/table-of-css3-border-radius-compliance - - tests['borderradius'] = function() { - return testPropsAll('borderRadius'); - }; - - // WebOS unfortunately false positives on this test. - tests['boxshadow'] = function() { - return testPropsAll('boxShadow'); - }; - - // FF3.0 will false positive on this test - tests['textshadow'] = function() { - return document.createElement('div').style.textShadow === ''; - }; - - - tests['opacity'] = function() { - // Browsers that actually have CSS Opacity implemented have done so - // according to spec, which means their return values are within the - // range of [0.0,1.0] - including the leading zero. - - setCssAll('opacity:.55'); - - // The non-literal . in this regex is intentional: - // German Chrome returns this value as 0,55 - // github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632 - return (/^0.55$/).test(mStyle.opacity); - }; - - - // Note, Android < 4 will pass this test, but can only animate - // a single property at a time - // daneden.me/2011/12/putting-up-with-androids-bullshit/ - tests['cssanimations'] = function() { - return testPropsAll('animationName'); - }; - - - tests['csscolumns'] = function() { - return testPropsAll('columnCount'); - }; - - - tests['cssgradients'] = function() { - /** - * For CSS Gradients syntax, please see: - * webkit.org/blog/175/introducing-css-gradients/ - * developer.mozilla.org/en/CSS/-moz-linear-gradient - * developer.mozilla.org/en/CSS/-moz-radial-gradient - * dev.w3.org/csswg/css3-images/#gradients- - */ - - var str1 = 'background-image:', - str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', - str3 = 'linear-gradient(left top,#9f9, white);'; - - setCss( - // legacy webkit syntax (FIXME: remove when syntax not in use anymore) - (str1 + '-webkit- '.split(' ').join(str2 + str1) + - // standard syntax // trailing 'background-image:' - prefixes.join(str3 + str1)).slice(0, -str1.length) - ); - - return contains(mStyle.backgroundImage, 'gradient'); - }; - - - tests['cssreflections'] = function() { - return testPropsAll('boxReflect'); - }; - - - tests['csstransforms'] = function() { - return !!testPropsAll('transform'); - }; - - - tests['csstransforms3d'] = function() { - - var ret = !!testPropsAll('perspective'); - - // Webkit's 3D transforms are passed off to the browser's own graphics renderer. - // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in - // some conditions. As a result, Webkit typically recognizes the syntax but - // will sometimes throw a false positive, thus we must do a more thorough check: - if ( ret && 'webkitPerspective' in docElement.style ) { - - // Webkit allows this media query to succeed only if the feature is enabled. - // `@media (transform-3d),(-webkit-transform-3d){ ... }` - injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) { - ret = node.offsetLeft === 9 && node.offsetHeight === 3; - }); - } - return ret; - }; - - - tests['csstransitions'] = function() { - return testPropsAll('transition'); - }; - - - /*>>fontface*/ - // @font-face detection routine by Diego Perini - // javascript.nwbox.com/CSSSupport/ - - // false positives: - // WebOS github.com/Modernizr/Modernizr/issues/342 - // WP7 github.com/Modernizr/Modernizr/issues/538 - tests['fontface'] = function() { - var bool; - - injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) { - var style = document.getElementById('smodernizr'), - sheet = style.sheet || style.styleSheet, - cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : ''; - - bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0; - }); - - return bool; - }; - /*>>fontface*/ - - // CSS generated content detection - tests['generatedcontent'] = function() { - var bool; - - injectElementWithStyles(['#',mod,'{font:0/0 a}#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) { - bool = node.offsetHeight >= 3; - }); - - return bool; - }; - - - - // These tests evaluate support of the video/audio elements, as well as - // testing what types of content they support. - // - // We're using the Boolean constructor here, so that we can extend the value - // e.g. Modernizr.video // true - // Modernizr.video.ogg // 'probably' - // - // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845 - // thx to NielsLeenheer and zcorpan - - // Note: in some older browsers, "no" was a return value instead of empty string. - // It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2 - // It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5 - - tests['video'] = function() { - var elem = document.createElement('video'), - bool = false; - - // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224 - try { - if ( bool = !!elem.canPlayType ) { - bool = new Boolean(bool); - bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,''); - - // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546 - bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,''); - - bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,''); - } - - } catch(e) { } - - return bool; - }; - - tests['audio'] = function() { - var elem = document.createElement('audio'), - bool = false; - - try { - if ( bool = !!elem.canPlayType ) { - bool = new Boolean(bool); - bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,''); - bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,''); - - // Mimetypes accepted: - // developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements - // bit.ly/iphoneoscodecs - bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,''); - bool.m4a = ( elem.canPlayType('audio/x-m4a;') || - elem.canPlayType('audio/aac;')) .replace(/^no$/,''); - } - } catch(e) { } - - return bool; - }; - - - // In FF4, if disabled, window.localStorage should === null. - - // Normally, we could not test that directly and need to do a - // `('localStorage' in window) && ` test first because otherwise Firefox will - // throw bugzil.la/365772 if cookies are disabled - - // Also in iOS5 Private Browsing mode, attempting to use localStorage.setItem - // will throw the exception: - // QUOTA_EXCEEDED_ERRROR DOM Exception 22. - // Peculiarly, getItem and removeItem calls do not throw. - - // Because we are forced to try/catch this, we'll go aggressive. - - // Just FWIW: IE8 Compat mode supports these features completely: - // www.quirksmode.org/dom/html5.html - // But IE8 doesn't support either with local files - - tests['localstorage'] = function() { - try { - localStorage.setItem(mod, mod); - localStorage.removeItem(mod); - return true; - } catch(e) { - return false; - } - }; - - tests['sessionstorage'] = function() { - try { - sessionStorage.setItem(mod, mod); - sessionStorage.removeItem(mod); - return true; - } catch(e) { - return false; - } - }; - - - tests['webworkers'] = function() { - return !!window.Worker; - }; - - - tests['applicationcache'] = function() { - return !!window.applicationCache; - }; - - - // Thanks to Erik Dahlstrom - tests['svg'] = function() { - return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect; - }; - - // specifically for SVG inline in HTML, not within XHTML - // test page: paulirish.com/demo/inline-svg - tests['inlinesvg'] = function() { - var div = document.createElement('div'); - div.innerHTML = ''; - return (div.firstChild && div.firstChild.namespaceURI) == ns.svg; - }; - - // SVG SMIL animation - tests['smil'] = function() { - return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate'))); - }; - - // This test is only for clip paths in SVG proper, not clip paths on HTML content - // demo: srufaculty.sru.edu/david.dailey/svg/newstuff/clipPath4.svg - - // However read the comments to dig into applying SVG clippaths to HTML content here: - // github.com/Modernizr/Modernizr/issues/213#issuecomment-1149491 - tests['svgclippaths'] = function() { - return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath'))); - }; - - /*>>webforms*/ - // input features and input types go directly onto the ret object, bypassing the tests loop. - // Hold this guy to execute in a moment. - function webforms() { - /*>>input*/ - // Run through HTML5's new input attributes to see if the UA understands any. - // We're using f which is the element created early on - // Mike Taylr has created a comprehensive resource for testing these attributes - // when applied to all input types: - // miketaylr.com/code/input-type-attr.html - // spec: www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary - - // Only input placeholder is tested while textarea's placeholder is not. - // Currently Safari 4 and Opera 11 have support only for the input placeholder - // Both tests are available in feature-detects/forms-placeholder.js - Modernizr['input'] = (function( props ) { - for ( var i = 0, len = props.length; i < len; i++ ) { - attrs[ props[i] ] = !!(props[i] in inputElem); - } - if (attrs.list){ - // safari false positive's on datalist: webk.it/74252 - // see also github.com/Modernizr/Modernizr/issues/146 - attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement); - } - return attrs; - })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' ')); - /*>>input*/ - - /*>>inputtypes*/ - // Run through HTML5's new input types to see if the UA understands any. - // This is put behind the tests runloop because it doesn't return a - // true/false like all the other tests; instead, it returns an object - // containing each input type with its corresponding true/false value - - // Big thanks to @miketaylr for the html5 forms expertise. miketaylr.com/ - Modernizr['inputtypes'] = (function(props) { - - for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) { - - inputElem.setAttribute('type', inputElemType = props[i]); - bool = inputElem.type !== 'text'; - - // We first check to see if the type we give it sticks.. - // If the type does, we feed it a textual value, which shouldn't be valid. - // If the value doesn't stick, we know there's input sanitization which infers a custom UI - if ( bool ) { - - inputElem.value = smile; - inputElem.style.cssText = 'position:absolute;visibility:hidden;'; - - if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) { - - docElement.appendChild(inputElem); - defaultView = document.defaultView; - - // Safari 2-4 allows the smiley as a value, despite making a slider - bool = defaultView.getComputedStyle && - defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' && - // Mobile android web browser has false positive, so must - // check the height to see if the widget is actually there. - (inputElem.offsetHeight !== 0); - - docElement.removeChild(inputElem); - - } else if ( /^(search|tel)$/.test(inputElemType) ){ - // Spec doesn't define any special parsing or detectable UI - // behaviors so we pass these through as true - - // Interestingly, opera fails the earlier test, so it doesn't - // even make it here. - - } else if ( /^(url|email)$/.test(inputElemType) ) { - // Real url and email support comes with prebaked validation. - bool = inputElem.checkValidity && inputElem.checkValidity() === false; - - } else { - // If the upgraded input compontent rejects the :) text, we got a winner - bool = inputElem.value != smile; - } - } - - inputs[ props[i] ] = !!bool; - } - return inputs; - })('search tel url email datetime date month week time datetime-local number range color'.split(' ')); - /*>>inputtypes*/ - } - /*>>webforms*/ - - - // End of test definitions - // ----------------------- - - - - // Run through all tests and detect their support in the current UA. - // todo: hypothetically we could be doing an array of tests and use a basic loop here. - for ( var feature in tests ) { - if ( hasOwnProp(tests, feature) ) { - // run the test, throw the return value into the Modernizr, - // then based on that boolean, define an appropriate className - // and push it into an array of classes we'll join later. - featureName = feature.toLowerCase(); - Modernizr[featureName] = tests[feature](); - - classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); - } - } - - /*>>webforms*/ - // input tests need to run. - Modernizr.input || webforms(); - /*>>webforms*/ - - - /** - * addTest allows the user to define their own feature tests - * the result will be added onto the Modernizr object, - * as well as an appropriate className set on the html element - * - * @param feature - String naming the feature - * @param test - Function returning true if feature is supported, false if not - */ - Modernizr.addTest = function ( feature, test ) { - if ( typeof feature == 'object' ) { - for ( var key in feature ) { - if ( hasOwnProp( feature, key ) ) { - Modernizr.addTest( key, feature[ key ] ); - } - } - } else { - - feature = feature.toLowerCase(); - - if ( Modernizr[feature] !== undefined ) { - // we're going to quit if you're trying to overwrite an existing test - // if we were to allow it, we'd do this: - // var re = new RegExp("\\b(no-)?" + feature + "\\b"); - // docElement.className = docElement.className.replace( re, '' ); - // but, no rly, stuff 'em. - return Modernizr; - } - - test = typeof test == 'function' ? test() : test; - - if (typeof enableClasses !== "undefined" && enableClasses) { - docElement.className += ' ' + (test ? '' : 'no-') + feature; - } - Modernizr[feature] = test; - - } - - return Modernizr; // allow chaining. - }; - - - // Reset modElem.cssText to nothing to reduce memory footprint. - setCss(''); - modElem = inputElem = null; - - /*>>shiv*/ - /*! HTML5 Shiv v3.6.1 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed */ - ;(function(window, document) { - /*jshint evil:true */ - /** Preset options */ - var options = window.html5 || {}; - - /** Used to skip problem elements */ - var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; - - /** Not all elements can be cloned in IE **/ - var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; - - /** Detect whether the browser supports default html5 styles */ - var supportsHtml5Styles; - - /** Name of the expando, to work with multiple documents or to re-shiv one document */ - var expando = '_html5shiv'; - - /** The id for the the documents expando */ - var expanID = 0; - - /** Cached data for each document */ - var expandoData = {}; - - /** Detect whether the browser supports unknown elements */ - var supportsUnknownElements; - - (function() { - try { - var a = document.createElement('a'); - a.innerHTML = ''; - //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles - supportsHtml5Styles = ('hidden' in a); - - supportsUnknownElements = a.childNodes.length == 1 || (function() { - // assign a false positive if unable to shiv - (document.createElement)('a'); - var frag = document.createDocumentFragment(); - return ( - typeof frag.cloneNode == 'undefined' || - typeof frag.createDocumentFragment == 'undefined' || - typeof frag.createElement == 'undefined' - ); - }()); - } catch(e) { - supportsHtml5Styles = true; - supportsUnknownElements = true; - } - - }()); - - /*--------------------------------------------------------------------------*/ - - /** - * Creates a style sheet with the given CSS text and adds it to the document. - * @private - * @param {Document} ownerDocument The document. - * @param {String} cssText The CSS text. - * @returns {StyleSheet} The style element. - */ - function addStyleSheet(ownerDocument, cssText) { - var p = ownerDocument.createElement('p'), - parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; - - p.innerHTML = 'x'; - return parent.insertBefore(p.lastChild, parent.firstChild); - } - - /** - * Returns the value of `html5.elements` as an array. - * @private - * @returns {Array} An array of shived element node names. - */ - function getElements() { - var elements = html5.elements; - return typeof elements == 'string' ? elements.split(' ') : elements; - } - - /** - * Returns the data associated to the given document - * @private - * @param {Document} ownerDocument The document. - * @returns {Object} An object of data. - */ - function getExpandoData(ownerDocument) { - var data = expandoData[ownerDocument[expando]]; - if (!data) { - data = {}; - expanID++; - ownerDocument[expando] = expanID; - expandoData[expanID] = data; - } - return data; - } - - /** - * returns a shived element for the given nodeName and document - * @memberOf html5 - * @param {String} nodeName name of the element - * @param {Document} ownerDocument The context document. - * @returns {Object} The shived element. - */ - function createElement(nodeName, ownerDocument, data){ - if (!ownerDocument) { - ownerDocument = document; - } - if(supportsUnknownElements){ - return ownerDocument.createElement(nodeName); - } - if (!data) { - data = getExpandoData(ownerDocument); - } - var node; - - if (data.cache[nodeName]) { - node = data.cache[nodeName].cloneNode(); - } else if (saveClones.test(nodeName)) { - node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); - } else { - node = data.createElem(nodeName); - } - - // Avoid adding some elements to fragments in IE < 9 because - // * Attributes like `name` or `type` cannot be set/changed once an element - // is inserted into a document/fragment - // * Link elements with `src` attributes that are inaccessible, as with - // a 403 response, will cause the tab/window to crash - // * Script elements appended to fragments will execute when their `src` - // or `text` property is set - return node.canHaveChildren && !reSkip.test(nodeName) ? data.frag.appendChild(node) : node; - } - - /** - * returns a shived DocumentFragment for the given document - * @memberOf html5 - * @param {Document} ownerDocument The context document. - * @returns {Object} The shived DocumentFragment. - */ - function createDocumentFragment(ownerDocument, data){ - if (!ownerDocument) { - ownerDocument = document; - } - if(supportsUnknownElements){ - return ownerDocument.createDocumentFragment(); - } - data = data || getExpandoData(ownerDocument); - var clone = data.frag.cloneNode(), - i = 0, - elems = getElements(), - l = elems.length; - for(;i>shiv*/ - - // Assign private properties to the return object with prefix - Modernizr._version = version; - - // expose these for the plugin API. Look in the source for how to join() them against your input - /*>>prefixes*/ - Modernizr._prefixes = prefixes; - /*>>prefixes*/ - /*>>domprefixes*/ - Modernizr._domPrefixes = domPrefixes; - Modernizr._cssomPrefixes = cssomPrefixes; - /*>>domprefixes*/ - - /*>>mq*/ - // Modernizr.mq tests a given media query, live against the current state of the window - // A few important notes: - // * If a browser does not support media queries at all (eg. oldIE) the mq() will always return false - // * A max-width or orientation query will be evaluated against the current state, which may change later. - // * You must specify values. Eg. If you are testing support for the min-width media query use: - // Modernizr.mq('(min-width:0)') - // usage: - // Modernizr.mq('only screen and (max-width:768)') - Modernizr.mq = testMediaQuery; - /*>>mq*/ - - /*>>hasevent*/ - // Modernizr.hasEvent() detects support for a given event, with an optional element to test on - // Modernizr.hasEvent('gesturestart', elem) - Modernizr.hasEvent = isEventSupported; - /*>>hasevent*/ - - /*>>testprop*/ - // Modernizr.testProp() investigates whether a given style property is recognized - // Note that the property names must be provided in the camelCase variant. - // Modernizr.testProp('pointerEvents') - Modernizr.testProp = function(prop){ - return testProps([prop]); - }; - /*>>testprop*/ - - /*>>testallprops*/ - // Modernizr.testAllProps() investigates whether a given style property, - // or any of its vendor-prefixed variants, is recognized - // Note that the property names must be provided in the camelCase variant. - // Modernizr.testAllProps('boxSizing') - Modernizr.testAllProps = testPropsAll; - /*>>testallprops*/ - - - /*>>teststyles*/ - // Modernizr.testStyles() allows you to add custom styles to the document and test an element afterwards - // Modernizr.testStyles('#modernizr { position:absolute }', function(elem, rule){ ... }) - Modernizr.testStyles = injectElementWithStyles; - /*>>teststyles*/ - - - /*>>prefixed*/ - // Modernizr.prefixed() returns the prefixed or nonprefixed property name variant of your input - // Modernizr.prefixed('boxSizing') // 'MozBoxSizing' - - // Properties must be passed as dom-style camelcase, rather than `box-sizing` hypentated style. - // Return values will also be the camelCase variant, if you need to translate that to hypenated style use: - // - // str.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-'); - - // If you're trying to ascertain which transition end event to bind to, you might do something like... - // - // var transEndEventNames = { - // 'WebkitTransition' : 'webkitTransitionEnd', - // 'MozTransition' : 'transitionend', - // 'OTransition' : 'oTransitionEnd', - // 'msTransition' : 'MSTransitionEnd', - // 'transition' : 'transitionend' - // }, - // transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ]; - - Modernizr.prefixed = function(prop, obj, elem){ - if(!obj) { - return testPropsAll(prop, 'pfx'); - } else { - // Testing DOM property e.g. Modernizr.prefixed('requestAnimationFrame', window) // 'mozRequestAnimationFrame' - return testPropsAll(prop, obj, elem); - } - }; - /*>>prefixed*/ - - - /*>>cssclasses*/ - // Remove "no-js" class from element, if it exists: - docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') + - - // Add the new classes to the element. - (enableClasses ? ' js ' + classes.join(' ') : ''); - /*>>cssclasses*/ - - return Modernizr; - -})(this, this.document); diff --git a/SampleWebApp/Scripts/npm.js b/SampleWebApp/Scripts/npm.js deleted file mode 100644 index bf6aa80..0000000 --- a/SampleWebApp/Scripts/npm.js +++ /dev/null @@ -1,13 +0,0 @@ -// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. -require('../../js/transition.js') -require('../../js/alert.js') -require('../../js/button.js') -require('../../js/carousel.js') -require('../../js/collapse.js') -require('../../js/dropdown.js') -require('../../js/modal.js') -require('../../js/tooltip.js') -require('../../js/popover.js') -require('../../js/scrollspy.js') -require('../../js/tab.js') -require('../../js/affix.js') \ No newline at end of file diff --git a/SampleWebApp/Scripts/respond.js b/SampleWebApp/Scripts/respond.js deleted file mode 100644 index 378d773..0000000 --- a/SampleWebApp/Scripts/respond.js +++ /dev/null @@ -1,340 +0,0 @@ -/* NUGET: BEGIN LICENSE TEXT - * - * Microsoft grants you the right to use these script files for the sole - * purpose of either: (i) interacting through your browser with the Microsoft - * website or online service, subject to the applicable licensing or use - * terms; or (ii) using the files as included with a Microsoft product subject - * to that product's license terms. Microsoft reserves all other rights to the - * files not expressly granted by Microsoft, whether by implication, estoppel - * or otherwise. Insofar as a script file is dual licensed under GPL, - * Microsoft neither took the code under GPL nor distributes it thereunder but - * under the terms set out in this paragraph. All notices and licenses - * below are for informational purposes only. - * - * NUGET: END LICENSE TEXT */ -/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ -/*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */ -window.matchMedia = window.matchMedia || (function(doc, undefined){ - - var bool, - docElem = doc.documentElement, - refNode = docElem.firstElementChild || docElem.firstChild, - // fakeBody required for - fakeBody = doc.createElement('body'), - div = doc.createElement('div'); - - div.id = 'mq-test-1'; - div.style.cssText = "position:absolute;top:-100em"; - fakeBody.style.background = "none"; - fakeBody.appendChild(div); - - return function(q){ - - div.innerHTML = '­'; - - docElem.insertBefore(fakeBody, refNode); - bool = div.offsetWidth == 42; - docElem.removeChild(fakeBody); - - return { matches: bool, media: q }; - }; - -})(document); - - - - -/*! Respond.js v1.2.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */ -(function( win ){ - //exposed namespace - win.respond = {}; - - //define update even in native-mq-supporting browsers, to avoid errors - respond.update = function(){}; - - //expose media query support flag for external use - respond.mediaQueriesSupported = win.matchMedia && win.matchMedia( "only all" ).matches; - - //if media queries are supported, exit here - if( respond.mediaQueriesSupported ){ return; } - - //define vars - var doc = win.document, - docElem = doc.documentElement, - mediastyles = [], - rules = [], - appendedEls = [], - parsedSheets = {}, - resizeThrottle = 30, - head = doc.getElementsByTagName( "head" )[0] || docElem, - base = doc.getElementsByTagName( "base" )[0], - links = head.getElementsByTagName( "link" ), - requestQueue = [], - - //loop stylesheets, send text content to translate - ripCSS = function(){ - var sheets = links, - sl = sheets.length, - i = 0, - //vars for loop: - sheet, href, media, isCSS; - - for( ; i < sl; i++ ){ - sheet = sheets[ i ], - href = sheet.href, - media = sheet.media, - isCSS = sheet.rel && sheet.rel.toLowerCase() === "stylesheet"; - - //only links plz and prevent re-parsing - if( !!href && isCSS && !parsedSheets[ href ] ){ - // selectivizr exposes css through the rawCssText expando - if (sheet.styleSheet && sheet.styleSheet.rawCssText) { - translate( sheet.styleSheet.rawCssText, href, media ); - parsedSheets[ href ] = true; - } else { - if( (!/^([a-zA-Z:]*\/\/)/.test( href ) && !base) - || href.replace( RegExp.$1, "" ).split( "/" )[0] === win.location.host ){ - requestQueue.push( { - href: href, - media: media - } ); - } - } - } - } - makeRequests(); - }, - - //recurse through request queue, get css text - makeRequests = function(){ - if( requestQueue.length ){ - var thisRequest = requestQueue.shift(); - - ajax( thisRequest.href, function( styles ){ - translate( styles, thisRequest.href, thisRequest.media ); - parsedSheets[ thisRequest.href ] = true; - makeRequests(); - } ); - } - }, - - //find media blocks in css text, convert to style blocks - translate = function( styles, href, media ){ - var qs = styles.match( /@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi ), - ql = qs && qs.length || 0, - //try to get CSS path - href = href.substring( 0, href.lastIndexOf( "/" )), - repUrls = function( css ){ - return css.replace( /(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g, "$1" + href + "$2$3" ); - }, - useMedia = !ql && media, - //vars used in loop - i = 0, - j, fullq, thisq, eachq, eql; - - //if path exists, tack on trailing slash - if( href.length ){ href += "/"; } - - //if no internal queries exist, but media attr does, use that - //note: this currently lacks support for situations where a media attr is specified on a link AND - //its associated stylesheet has internal CSS media queries. - //In those cases, the media attribute will currently be ignored. - if( useMedia ){ - ql = 1; - } - - - for( ; i < ql; i++ ){ - j = 0; - - //media attr - if( useMedia ){ - fullq = media; - rules.push( repUrls( styles ) ); - } - //parse for styles - else{ - fullq = qs[ i ].match( /@media *([^\{]+)\{([\S\s]+?)$/ ) && RegExp.$1; - rules.push( RegExp.$2 && repUrls( RegExp.$2 ) ); - } - - eachq = fullq.split( "," ); - eql = eachq.length; - - for( ; j < eql; j++ ){ - thisq = eachq[ j ]; - mediastyles.push( { - media : thisq.split( "(" )[ 0 ].match( /(only\s+)?([a-zA-Z]+)\s?/ ) && RegExp.$2 || "all", - rules : rules.length - 1, - hasquery: thisq.indexOf("(") > -1, - minw : thisq.match( /\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ), - maxw : thisq.match( /\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ) - } ); - } - } - - applyMedia(); - }, - - lastCall, - - resizeDefer, - - // returns the value of 1em in pixels - getEmValue = function() { - var ret, - div = doc.createElement('div'), - body = doc.body, - fakeUsed = false; - - div.style.cssText = "position:absolute;font-size:1em;width:1em"; - - if( !body ){ - body = fakeUsed = doc.createElement( "body" ); - body.style.background = "none"; - } - - body.appendChild( div ); - - docElem.insertBefore( body, docElem.firstChild ); - - ret = div.offsetWidth; - - if( fakeUsed ){ - docElem.removeChild( body ); - } - else { - body.removeChild( div ); - } - - //also update eminpx before returning - ret = eminpx = parseFloat(ret); - - return ret; - }, - - //cached container for 1em value, populated the first time it's needed - eminpx, - - //enable/disable styles - applyMedia = function( fromResize ){ - var name = "clientWidth", - docElemProp = docElem[ name ], - currWidth = doc.compatMode === "CSS1Compat" && docElemProp || doc.body[ name ] || docElemProp, - styleBlocks = {}, - lastLink = links[ links.length-1 ], - now = (new Date()).getTime(); - - //throttle resize calls - if( fromResize && lastCall && now - lastCall < resizeThrottle ){ - clearTimeout( resizeDefer ); - resizeDefer = setTimeout( applyMedia, resizeThrottle ); - return; - } - else { - lastCall = now; - } - - for( var i in mediastyles ){ - var thisstyle = mediastyles[ i ], - min = thisstyle.minw, - max = thisstyle.maxw, - minnull = min === null, - maxnull = max === null, - em = "em"; - - if( !!min ){ - min = parseFloat( min ) * ( min.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 ); - } - if( !!max ){ - max = parseFloat( max ) * ( max.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 ); - } - - // if there's no media query at all (the () part), or min or max is not null, and if either is present, they're true - if( !thisstyle.hasquery || ( !minnull || !maxnull ) && ( minnull || currWidth >= min ) && ( maxnull || currWidth <= max ) ){ - if( !styleBlocks[ thisstyle.media ] ){ - styleBlocks[ thisstyle.media ] = []; - } - styleBlocks[ thisstyle.media ].push( rules[ thisstyle.rules ] ); - } - } - - //remove any existing respond style element(s) - for( var i in appendedEls ){ - if( appendedEls[ i ] && appendedEls[ i ].parentNode === head ){ - head.removeChild( appendedEls[ i ] ); - } - } - - //inject active styles, grouped by media type - for( var i in styleBlocks ){ - var ss = doc.createElement( "style" ), - css = styleBlocks[ i ].join( "\n" ); - - ss.type = "text/css"; - ss.media = i; - - //originally, ss was appended to a documentFragment and sheets were appended in bulk. - //this caused crashes in IE in a number of circumstances, such as when the HTML element had a bg image set, so appending beforehand seems best. Thanks to @dvelyk for the initial research on this one! - head.insertBefore( ss, lastLink.nextSibling ); - - if ( ss.styleSheet ){ - ss.styleSheet.cssText = css; - } - else { - ss.appendChild( doc.createTextNode( css ) ); - } - - //push to appendedEls to track for later removal - appendedEls.push( ss ); - } - }, - //tweaked Ajax functions from Quirksmode - ajax = function( url, callback ) { - var req = xmlHttp(); - if (!req){ - return; - } - req.open( "GET", url, true ); - req.onreadystatechange = function () { - if ( req.readyState != 4 || req.status != 200 && req.status != 304 ){ - return; - } - callback( req.responseText ); - } - if ( req.readyState == 4 ){ - return; - } - req.send( null ); - }, - //define ajax obj - xmlHttp = (function() { - var xmlhttpmethod = false; - try { - xmlhttpmethod = new XMLHttpRequest(); - } - catch( e ){ - xmlhttpmethod = new ActiveXObject( "Microsoft.XMLHTTP" ); - } - return function(){ - return xmlhttpmethod; - }; - })(); - - //translate CSS - ripCSS(); - - //expose update for re-running respond later on - respond.update = ripCSS; - - //adjust on resize - function callMedia(){ - applyMedia( true ); - } - if( win.addEventListener ){ - win.addEventListener( "resize", callMedia, false ); - } - else if( win.attachEvent ){ - win.attachEvent( "onresize", callMedia ); - } -})(this); diff --git a/SampleWebApp/Scripts/respond.min.js b/SampleWebApp/Scripts/respond.min.js deleted file mode 100644 index a848137..0000000 --- a/SampleWebApp/Scripts/respond.min.js +++ /dev/null @@ -1,20 +0,0 @@ -/* NUGET: BEGIN LICENSE TEXT - * - * Microsoft grants you the right to use these script files for the sole - * purpose of either: (i) interacting through your browser with the Microsoft - * website or online service, subject to the applicable licensing or use - * terms; or (ii) using the files as included with a Microsoft product subject - * to that product's license terms. Microsoft reserves all other rights to the - * files not expressly granted by Microsoft, whether by implication, estoppel - * or otherwise. Insofar as a script file is dual licensed under GPL, - * Microsoft neither took the code under GPL nor distributes it thereunder but - * under the terms set out in this paragraph. All notices and licenses - * below are for informational purposes only. - * - * NUGET: END LICENSE TEXT */ -/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ -/*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */ -window.matchMedia=window.matchMedia||(function(e,f){var c,a=e.documentElement,b=a.firstElementChild||a.firstChild,d=e.createElement("body"),g=e.createElement("div");g.id="mq-test-1";g.style.cssText="position:absolute;top:-100em";d.style.background="none";d.appendChild(g);return function(h){g.innerHTML='­';a.insertBefore(d,b);c=g.offsetWidth==42;a.removeChild(d);return{matches:c,media:h}}})(document); - -/*! Respond.js v1.2.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */ -(function(e){e.respond={};respond.update=function(){};respond.mediaQueriesSupported=e.matchMedia&&e.matchMedia("only all").matches;if(respond.mediaQueriesSupported){return}var w=e.document,s=w.documentElement,i=[],k=[],q=[],o={},h=30,f=w.getElementsByTagName("head")[0]||s,g=w.getElementsByTagName("base")[0],b=f.getElementsByTagName("link"),d=[],a=function(){var D=b,y=D.length,B=0,A,z,C,x;for(;B-1,minw:F.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:F.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}}j()},l,r,v=function(){var z,A=w.createElement("div"),x=w.body,y=false;A.style.cssText="position:absolute;font-size:1em;width:1em";if(!x){x=y=w.createElement("body");x.style.background="none"}x.appendChild(A);s.insertBefore(x,s.firstChild);z=A.offsetWidth;if(y){s.removeChild(x)}else{x.removeChild(A)}z=p=parseFloat(z);return z},p,j=function(I){var x="clientWidth",B=s[x],H=w.compatMode==="CSS1Compat"&&B||w.body[x]||B,D={},G=b[b.length-1],z=(new Date()).getTime();if(I&&l&&z-l-1?(p||v()):1)}if(!!J){J=parseFloat(J)*(J.indexOf(y)>-1?(p||v()):1)}if(!K.hasquery||(!A||!L)&&(A||H>=C)&&(L||H<=J)){if(!D[K.media]){D[K.media]=[]}D[K.media].push(k[K.rules])}}for(var E in q){if(q[E]&&q[E].parentNode===f){f.removeChild(q[E])}}for(var E in D){var M=w.createElement("style"),F=D[E].join("\n");M.type="text/css";M.media=E;f.insertBefore(M,G.nextSibling);if(M.styleSheet){M.styleSheet.cssText=F}else{M.appendChild(w.createTextNode(F))}q.push(M)}},n=function(x,z){var y=c();if(!y){return}y.open("GET",x,true);y.onreadystatechange=function(){if(y.readyState!=4||y.status!=200&&y.status!=304){return}z(y.responseText)};if(y.readyState==4){return}y.send(null)},c=(function(){var x=false;try{x=new XMLHttpRequest()}catch(y){x=new ActiveXObject("Microsoft.XMLHTTP")}return function(){return x}})();a();respond.update=a;function t(){j(true)}if(e.addEventListener){e.addEventListener("resize",t,false)}else{if(e.attachEvent){e.attachEvent("onresize",t)}}})(this); \ No newline at end of file diff --git a/SampleWebApp/Views/Blogs/Index.cshtml b/SampleWebApp/Views/Blogs/Index.cshtml index 98bb41d..e334799 100644 --- a/SampleWebApp/Views/Blogs/Index.cshtml +++ b/SampleWebApp/Views/Blogs/Index.cshtml @@ -9,7 +9,7 @@ } @if (TempData["errorMessage"] != null) { -
    @TempData["errorMessage"]
    +
    @Html.Raw(TempData["errorMessage"])
    }

    diff --git a/SampleWebApp/Views/Posts/Create.cshtml b/SampleWebApp/Views/Posts/Create.cshtml index ca33689..7930ab0 100644 --- a/SampleWebApp/Views/Posts/Create.cshtml +++ b/SampleWebApp/Views/Posts/Create.cshtml @@ -39,7 +39,7 @@

    - @Html.Label("Tags", htmlAttributes: new { @class = "control-label col-md-2" }) + @Html.Label("Tags", "Tags", htmlAttributes: new { @class = "control-label col-md-2" })
    @Html.EditorFor(model => model.UserChosenTags, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.UserChosenTags, "", new { @class = "text-danger" }) @@ -60,5 +60,6 @@ @Html.Partial("PostValidation") @section Scripts { - @Scripts.Render("~/bundles/jqueryval") + + } diff --git a/SampleWebApp/Views/Posts/Edit.cshtml b/SampleWebApp/Views/Posts/Edit.cshtml index 49d666f..69787a5 100644 --- a/SampleWebApp/Views/Posts/Edit.cshtml +++ b/SampleWebApp/Views/Posts/Edit.cshtml @@ -42,7 +42,7 @@
    - @Html.Label("Tags", htmlAttributes: new { @class = "control-label col-md-2" }) + @Html.Label("Tags", "Tags", htmlAttributes: new { @class = "control-label col-md-2" })
    @Html.EditorFor(model => model.UserChosenTags, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.UserChosenTags, "", new { @class = "text-danger" }) @@ -65,5 +65,6 @@ @section Scripts { - @Scripts.Render("~/bundles/jqueryval") + + } diff --git a/SampleWebApp/Views/Posts/Index.cshtml b/SampleWebApp/Views/Posts/Index.cshtml index 197b2f6..e4c389d 100644 --- a/SampleWebApp/Views/Posts/Index.cshtml +++ b/SampleWebApp/Views/Posts/Index.cshtml @@ -12,7 +12,7 @@ } @if (TempData["errorMessage"] != null) { -
    @TempData["errorMessage"]
    +
    @Html.Raw(TempData["errorMessage"])
    }

    diff --git a/SampleWebApp/Views/PostsAsync/Create.cshtml b/SampleWebApp/Views/PostsAsync/Create.cshtml index d22f305..e4c0f66 100644 --- a/SampleWebApp/Views/PostsAsync/Create.cshtml +++ b/SampleWebApp/Views/PostsAsync/Create.cshtml @@ -39,7 +39,7 @@

    - @Html.Label("Tags", htmlAttributes: new { @class = "control-label col-md-2" }) + @Html.Label("Tags", "Tags", htmlAttributes: new { @class = "control-label col-md-2" })
    @Html.EditorFor(model => model.UserChosenTags, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.UserChosenTags, "", new { @class = "text-danger" }) @@ -60,5 +60,6 @@ @Html.Partial("PostValidation") @section Scripts { - @Scripts.Render("~/bundles/jqueryval") + + } diff --git a/SampleWebApp/Views/PostsAsync/Edit.cshtml b/SampleWebApp/Views/PostsAsync/Edit.cshtml index 75b1706..22eb70e 100644 --- a/SampleWebApp/Views/PostsAsync/Edit.cshtml +++ b/SampleWebApp/Views/PostsAsync/Edit.cshtml @@ -41,7 +41,7 @@
    - @Html.Label("Tags", htmlAttributes: new { @class = "control-label col-md-2" }) + @Html.Label("Tags", "Tags", htmlAttributes: new { @class = "control-label col-md-2" })
    @Html.EditorFor(model => model.UserChosenTags, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.UserChosenTags, "", new { @class = "text-danger" }) @@ -63,5 +63,6 @@ @Html.Partial("PostValidation") @section Scripts { - @Scripts.Render("~/bundles/jqueryval") + + } diff --git a/SampleWebApp/Views/PostsAsync/Index.cshtml b/SampleWebApp/Views/PostsAsync/Index.cshtml index 706400f..a04efa0 100644 --- a/SampleWebApp/Views/PostsAsync/Index.cshtml +++ b/SampleWebApp/Views/PostsAsync/Index.cshtml @@ -12,7 +12,7 @@ } @if (TempData["errorMessage"] != null) { -
    @TempData["errorMessage"]
    +
    @Html.Raw(TempData["errorMessage"])
    }

    diff --git a/SampleWebApp/Views/Shared/Error.cshtml b/SampleWebApp/Views/Shared/Error.cshtml index be55b17..26ebcf0 100644 --- a/SampleWebApp/Views/Shared/Error.cshtml +++ b/SampleWebApp/Views/Shared/Error.cshtml @@ -1,9 +1,6 @@ -@model System.Web.Mvc.HandleErrorInfo - -@{ +@{ ViewBag.Title = "Error"; }

    Error.

    An error occurred while processing your request.

    - diff --git a/SampleWebApp/Views/Shared/_Layout.cshtml b/SampleWebApp/Views/Shared/_Layout.cshtml index 345fd91..bdc5736 100644 --- a/SampleWebApp/Views/Shared/_Layout.cshtml +++ b/SampleWebApp/Views/Shared/_Layout.cshtml @@ -1,11 +1,11 @@ -@using SampleWebApp.Infrastructure - + @ViewBag.Title - SampleMvcWebApp - @Styles.Render("~/Content/css") + + @@ -50,11 +50,12 @@
    An open source project under the MIT licence, created by Jon Smith. - Hosted on @WebUiInitialise.HostType + Hosted on ASP.NET Core (.NET 10)
    - @Scripts.Render("~/bundles/javascript") - @RenderSection("scripts", required: false) + + + @await RenderSectionAsync("scripts", required: false) diff --git a/SampleWebApp/Views/Tags/Index.cshtml b/SampleWebApp/Views/Tags/Index.cshtml index 03c2653..a371c20 100644 --- a/SampleWebApp/Views/Tags/Index.cshtml +++ b/SampleWebApp/Views/Tags/Index.cshtml @@ -12,7 +12,7 @@ } @if (TempData["errorMessage"] != null) { -
    @TempData["errorMessage"]
    +
    @Html.Raw(TempData["errorMessage"])
    }

    diff --git a/SampleWebApp/Views/TagsAsync/Index.cshtml b/SampleWebApp/Views/TagsAsync/Index.cshtml index 92c0337..5188df8 100644 --- a/SampleWebApp/Views/TagsAsync/Index.cshtml +++ b/SampleWebApp/Views/TagsAsync/Index.cshtml @@ -12,7 +12,7 @@ } @if (TempData["errorMessage"] != null) { -

    @TempData["errorMessage"]
    +
    @Html.Raw(TempData["errorMessage"])
    } @Html.ValidationSummary(false, "", new { @class = "text-danger" }) diff --git a/SampleWebApp/Views/Web.config b/SampleWebApp/Views/Web.config deleted file mode 100644 index ba26898..0000000 --- a/SampleWebApp/Views/Web.config +++ /dev/null @@ -1,35 +0,0 @@ - - - - - -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SampleWebApp/Views/_ViewImports.cshtml b/SampleWebApp/Views/_ViewImports.cshtml new file mode 100644 index 0000000..d8681e7 --- /dev/null +++ b/SampleWebApp/Views/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using SampleWebApp +@using SampleWebApp.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/SampleWebApp/Views/_ViewStart.cshtml b/SampleWebApp/Views/_ViewStart.cshtml index 2de6241..a5f1004 100644 --- a/SampleWebApp/Views/_ViewStart.cshtml +++ b/SampleWebApp/Views/_ViewStart.cshtml @@ -1,3 +1,3 @@ @{ - Layout = "~/Views/Shared/_Layout.cshtml"; + Layout = "_Layout"; } diff --git a/SampleWebApp/Web.AzureRelease.config b/SampleWebApp/Web.AzureRelease.config deleted file mode 100644 index 7ea0845..0000000 --- a/SampleWebApp/Web.AzureRelease.config +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - Azure - - - Azure - - - - \ No newline at end of file diff --git a/SampleWebApp/Web.Debug.config b/SampleWebApp/Web.Debug.config deleted file mode 100644 index 680849f..0000000 --- a/SampleWebApp/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - diff --git a/SampleWebApp/Web.Release.config b/SampleWebApp/Web.Release.config deleted file mode 100644 index 943c9c0..0000000 --- a/SampleWebApp/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - diff --git a/SampleWebApp/Web.WebWizRelease.config b/SampleWebApp/Web.WebWizRelease.config deleted file mode 100644 index 451e45b..0000000 --- a/SampleWebApp/Web.WebWizRelease.config +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - WebWiz - - - jonsmith_ - - - - \ No newline at end of file diff --git a/SampleWebApp/Web.config b/SampleWebApp/Web.config deleted file mode 100644 index 02309cc..0000000 --- a/SampleWebApp/Web.config +++ /dev/null @@ -1,110 +0,0 @@ - - - - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - LocalHost - - - jonsmith_ - - - - \ No newline at end of file diff --git a/SampleWebApp/appsettings.Development.json b/SampleWebApp/appsettings.Development.json new file mode 100644 index 0000000..8a6bb8c --- /dev/null +++ b/SampleWebApp/appsettings.Development.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "SampleWebAppDb": "Server=localhost,1433;Database=SampleWebAppDb;User Id=sa;Password=ChangeMe_Passw0rd;TrustServerCertificate=True;MultipleActiveResultSets=True" + } +} diff --git a/SampleWebApp/appsettings.json b/SampleWebApp/appsettings.json new file mode 100644 index 0000000..9b94cab --- /dev/null +++ b/SampleWebApp/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "SampleWebAppDb": "" + } +} diff --git a/SampleWebApp/packages.config b/SampleWebApp/packages.config deleted file mode 100644 index bc278c0..0000000 --- a/SampleWebApp/packages.config +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SampleWebApp/Content/Site.css b/SampleWebApp/wwwroot/css/Site.css similarity index 100% rename from SampleWebApp/Content/Site.css rename to SampleWebApp/wwwroot/css/Site.css diff --git a/SampleWebApp/Content/bootstrap-theme.css b/SampleWebApp/wwwroot/css/bootstrap-theme.css similarity index 100% rename from SampleWebApp/Content/bootstrap-theme.css rename to SampleWebApp/wwwroot/css/bootstrap-theme.css diff --git a/SampleWebApp/Content/bootstrap-theme.css.map b/SampleWebApp/wwwroot/css/bootstrap-theme.css.map similarity index 100% rename from SampleWebApp/Content/bootstrap-theme.css.map rename to SampleWebApp/wwwroot/css/bootstrap-theme.css.map diff --git a/SampleWebApp/Content/bootstrap-theme.min.css b/SampleWebApp/wwwroot/css/bootstrap-theme.min.css similarity index 100% rename from SampleWebApp/Content/bootstrap-theme.min.css rename to SampleWebApp/wwwroot/css/bootstrap-theme.min.css diff --git a/SampleWebApp/Content/bootstrap.css b/SampleWebApp/wwwroot/css/bootstrap.css similarity index 100% rename from SampleWebApp/Content/bootstrap.css rename to SampleWebApp/wwwroot/css/bootstrap.css diff --git a/SampleWebApp/Content/bootstrap.css.map b/SampleWebApp/wwwroot/css/bootstrap.css.map similarity index 100% rename from SampleWebApp/Content/bootstrap.css.map rename to SampleWebApp/wwwroot/css/bootstrap.css.map diff --git a/SampleWebApp/Content/bootstrap.min.css b/SampleWebApp/wwwroot/css/bootstrap.min.css similarity index 100% rename from SampleWebApp/Content/bootstrap.min.css rename to SampleWebApp/wwwroot/css/bootstrap.min.css diff --git a/SampleWebApp/Content/img/setup-progress.gif b/SampleWebApp/wwwroot/css/img/setup-progress.gif similarity index 100% rename from SampleWebApp/Content/img/setup-progress.gif rename to SampleWebApp/wwwroot/css/img/setup-progress.gif diff --git a/SampleWebApp/Content/img/task-progress.gif b/SampleWebApp/wwwroot/css/img/task-progress.gif similarity index 100% rename from SampleWebApp/Content/img/task-progress.gif rename to SampleWebApp/wwwroot/css/img/task-progress.gif diff --git a/SampleWebApp/favicon.ico b/SampleWebApp/wwwroot/favicon.ico similarity index 100% rename from SampleWebApp/favicon.ico rename to SampleWebApp/wwwroot/favicon.ico diff --git a/SampleWebApp/fonts/glyphicons-halflings-regular.eot b/SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from SampleWebApp/fonts/glyphicons-halflings-regular.eot rename to SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.eot diff --git a/SampleWebApp/fonts/glyphicons-halflings-regular.svg b/SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from SampleWebApp/fonts/glyphicons-halflings-regular.svg rename to SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.svg diff --git a/SampleWebApp/fonts/glyphicons-halflings-regular.ttf b/SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from SampleWebApp/fonts/glyphicons-halflings-regular.ttf rename to SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.ttf diff --git a/SampleWebApp/fonts/glyphicons-halflings-regular.woff b/SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from SampleWebApp/fonts/glyphicons-halflings-regular.woff rename to SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.woff diff --git a/SampleWebApp/fonts/glyphicons-halflings-regular.woff2 b/SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from SampleWebApp/fonts/glyphicons-halflings-regular.woff2 rename to SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.woff2 diff --git a/SampleWebApp/Scripts/bootstrap.min.js b/SampleWebApp/wwwroot/js/bootstrap.min.js similarity index 100% rename from SampleWebApp/Scripts/bootstrap.min.js rename to SampleWebApp/wwwroot/js/bootstrap.min.js diff --git a/SampleWebApp/Scripts/jquery-1.10.2.min.js b/SampleWebApp/wwwroot/js/jquery-1.10.2.min.js similarity index 100% rename from SampleWebApp/Scripts/jquery-1.10.2.min.js rename to SampleWebApp/wwwroot/js/jquery-1.10.2.min.js diff --git a/SampleWebApp/Scripts/jquery.validate.min.js b/SampleWebApp/wwwroot/js/jquery.validate.min.js similarity index 100% rename from SampleWebApp/Scripts/jquery.validate.min.js rename to SampleWebApp/wwwroot/js/jquery.validate.min.js diff --git a/SampleWebApp/Scripts/jquery.validate.unobtrusive.js b/SampleWebApp/wwwroot/js/jquery.validate.unobtrusive.js similarity index 100% rename from SampleWebApp/Scripts/jquery.validate.unobtrusive.js rename to SampleWebApp/wwwroot/js/jquery.validate.unobtrusive.js From 4fff08105ae8ef89289215c54ffdb9013979fcaf Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 04:38:08 +0000 Subject: [PATCH 04/11] feature: add EF Core InitialCreate migration and design-time context factory Co-Authored-By: Parker Duff --- DataLayer/DesignTimeDbContextFactory.cs | 27 +++ .../20260716043708_InitialCreate.Designer.cs | 155 ++++++++++++++++++ .../20260716043708_InitialCreate.cs | 121 ++++++++++++++ .../Migrations/SampleWebAppDbModelSnapshot.cs | 152 +++++++++++++++++ 4 files changed, 455 insertions(+) create mode 100644 DataLayer/DesignTimeDbContextFactory.cs create mode 100644 DataLayer/Migrations/20260716043708_InitialCreate.Designer.cs create mode 100644 DataLayer/Migrations/20260716043708_InitialCreate.cs create mode 100644 DataLayer/Migrations/SampleWebAppDbModelSnapshot.cs diff --git a/DataLayer/DesignTimeDbContextFactory.cs b/DataLayer/DesignTimeDbContextFactory.cs new file mode 100644 index 0000000..5e09be0 --- /dev/null +++ b/DataLayer/DesignTimeDbContextFactory.cs @@ -0,0 +1,27 @@ +using System; +using DataLayer.DataClasses; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace DataLayer +{ + /// + /// Used by the EF Core tools (dotnet ef) at design time so that creating/applying + /// migrations does not need to run the web application's host and startup seeding. + /// The connection string here is only used by the tooling; the running application + /// supplies its own connection string via configuration. + /// + public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory + { + public SampleWebAppDb CreateDbContext(string[] args) + { + var connectionString = + Environment.GetEnvironmentVariable("SAMPLEWEBAPPDB_CONNECTION") + ?? "Server=localhost,1433;Database=SampleWebAppDb;User Id=sa;Password=Design_Time_Only!;TrustServerCertificate=True;MultipleActiveResultSets=True"; + + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlServer(connectionString); + return new SampleWebAppDb(optionsBuilder.Options); + } + } +} diff --git a/DataLayer/Migrations/20260716043708_InitialCreate.Designer.cs b/DataLayer/Migrations/20260716043708_InitialCreate.Designer.cs new file mode 100644 index 0000000..f9a4a88 --- /dev/null +++ b/DataLayer/Migrations/20260716043708_InitialCreate.Designer.cs @@ -0,0 +1,155 @@ +// +using System; +using DataLayer.DataClasses; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DataLayer.Migrations +{ + [DbContext(typeof(SampleWebAppDb))] + [Migration("20260716043708_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Blog", b => + { + b.Property("BlogId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("BlogId")); + + b.Property("EmailAddress") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.HasKey("BlogId"); + + b.ToTable("Blogs"); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Post", b => + { + b.Property("PostId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("PostId")); + + b.Property("BlogId") + .HasColumnType("int"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("PostId"); + + b.HasIndex("BlogId"); + + b.ToTable("Posts"); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Tag", b => + { + b.Property("TagId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("TagId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.HasKey("TagId"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("PostTag", b => + { + b.Property("PostsPostId") + .HasColumnType("int"); + + b.Property("TagsTagId") + .HasColumnType("int"); + + b.HasKey("PostsPostId", "TagsTagId"); + + b.HasIndex("TagsTagId"); + + b.ToTable("PostTag"); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Post", b => + { + b.HasOne("DataLayer.DataClasses.Concrete.Blog", "Blogger") + .WithMany("Posts") + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Blogger"); + }); + + modelBuilder.Entity("PostTag", b => + { + b.HasOne("DataLayer.DataClasses.Concrete.Post", null) + .WithMany() + .HasForeignKey("PostsPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DataLayer.DataClasses.Concrete.Tag", null) + .WithMany() + .HasForeignKey("TagsTagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Blog", b => + { + b.Navigation("Posts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DataLayer/Migrations/20260716043708_InitialCreate.cs b/DataLayer/Migrations/20260716043708_InitialCreate.cs new file mode 100644 index 0000000..8af75c2 --- /dev/null +++ b/DataLayer/Migrations/20260716043708_InitialCreate.cs @@ -0,0 +1,121 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DataLayer.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Blogs", + columns: table => new + { + BlogId = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + EmailAddress = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Blogs", x => x.BlogId); + }); + + migrationBuilder.CreateTable( + name: "Tags", + columns: table => new + { + TagId = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Slug = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Tags", x => x.TagId); + }); + + migrationBuilder.CreateTable( + name: "Posts", + columns: table => new + { + PostId = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Title = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Content = table.Column(type: "nvarchar(max)", nullable: false), + BlogId = table.Column(type: "int", nullable: false), + LastUpdated = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Posts", x => x.PostId); + table.ForeignKey( + name: "FK_Posts_Blogs_BlogId", + column: x => x.BlogId, + principalTable: "Blogs", + principalColumn: "BlogId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PostTag", + columns: table => new + { + PostsPostId = table.Column(type: "int", nullable: false), + TagsTagId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PostTag", x => new { x.PostsPostId, x.TagsTagId }); + table.ForeignKey( + name: "FK_PostTag_Posts_PostsPostId", + column: x => x.PostsPostId, + principalTable: "Posts", + principalColumn: "PostId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PostTag_Tags_TagsTagId", + column: x => x.TagsTagId, + principalTable: "Tags", + principalColumn: "TagId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Posts_BlogId", + table: "Posts", + column: "BlogId"); + + migrationBuilder.CreateIndex( + name: "IX_PostTag_TagsTagId", + table: "PostTag", + column: "TagsTagId"); + + migrationBuilder.CreateIndex( + name: "IX_Tags_Slug", + table: "Tags", + column: "Slug", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PostTag"); + + migrationBuilder.DropTable( + name: "Posts"); + + migrationBuilder.DropTable( + name: "Tags"); + + migrationBuilder.DropTable( + name: "Blogs"); + } + } +} diff --git a/DataLayer/Migrations/SampleWebAppDbModelSnapshot.cs b/DataLayer/Migrations/SampleWebAppDbModelSnapshot.cs new file mode 100644 index 0000000..66c781d --- /dev/null +++ b/DataLayer/Migrations/SampleWebAppDbModelSnapshot.cs @@ -0,0 +1,152 @@ +// +using System; +using DataLayer.DataClasses; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DataLayer.Migrations +{ + [DbContext(typeof(SampleWebAppDb))] + partial class SampleWebAppDbModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Blog", b => + { + b.Property("BlogId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("BlogId")); + + b.Property("EmailAddress") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.HasKey("BlogId"); + + b.ToTable("Blogs"); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Post", b => + { + b.Property("PostId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("PostId")); + + b.Property("BlogId") + .HasColumnType("int"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("PostId"); + + b.HasIndex("BlogId"); + + b.ToTable("Posts"); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Tag", b => + { + b.Property("TagId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("TagId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.HasKey("TagId"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("PostTag", b => + { + b.Property("PostsPostId") + .HasColumnType("int"); + + b.Property("TagsTagId") + .HasColumnType("int"); + + b.HasKey("PostsPostId", "TagsTagId"); + + b.HasIndex("TagsTagId"); + + b.ToTable("PostTag"); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Post", b => + { + b.HasOne("DataLayer.DataClasses.Concrete.Blog", "Blogger") + .WithMany("Posts") + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Blogger"); + }); + + modelBuilder.Entity("PostTag", b => + { + b.HasOne("DataLayer.DataClasses.Concrete.Post", null) + .WithMany() + .HasForeignKey("PostsPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DataLayer.DataClasses.Concrete.Tag", null) + .WithMany() + .HasForeignKey("TagsTagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Blog", b => + { + b.Navigation("Posts"); + }); +#pragma warning restore 612, 618 + } + } +} From ddc4e69d62dfa24038d2a7cd8833e49223052227 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 04:39:38 +0000 Subject: [PATCH 05/11] feature: update README for ASP.NET Core / .NET 10 / EF Core Co-Authored-By: Parker Duff --- README.md | 109 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 21e465e..01b4292 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,96 @@ SampleMvcWebApp =============== -SampleMvcWebApp is a ASP.NET MVC5 web site designed to show number of useful methods for building enterprise - grade web applications using ASP.NET MVC5 and Entity Framework 6. -The code for this sample MVC web application, and the associated -[GenericServices Framework](https://github.com/JonPSmith/GenericServices) are both an open source project -by [Jon Smith](http://www.thereformedprogrammer.net/about-me/) +SampleMvcWebApp is an **ASP.NET Core MVC** web site (targeting **.NET 10**) that demonstrates a +number of useful patterns for building enterprise‑grade web applications using ASP.NET Core MVC and +**Entity Framework Core**. + +This project was originally an ASP.NET MVC 5 / .NET Framework 4.5.1 / Entity Framework 6 sample by +[Jon Smith](http://www.thereformedprogrammer.net/about-me/) built around the EF6 +[GenericServices](https://github.com/JonPSmith/GenericServices) framework. It has been re‑platformed +to ASP.NET Core / .NET 10 / EF Core using the EF Core successor +[EfCore.GenericServices](https://github.com/JonPSmith/EfCore.GenericServices). It remains open source under the [MIT licence](http://opensource.org/licenses/MIT). -This code is available as a [live web site](http://samplemvcwebapp.net/) which includes explanations -of the code - see an example of this on the [Posts code explanation](http://samplemvcwebapp.net/Posts/CodeView) page. +See [`MIGRATION_NOTES.md`](./MIGRATION_NOTES.md) for the detailed record of what changed during the +migration (the gotchas, the affected files, and the chosen replacement for each legacy dependency). + +Solution layout +--------------- + +| Project | Purpose | +| -------------- | ------------------------------------------------------------------------ | +| `DataLayer` | EF Core `SampleWebAppDb` DbContext, entities, migrations and XML seeding. | +| `BizLayer` | Business‑logic layer (DI extension point). | +| `ServiceLayer` | `EfCore.GenericServices` DTOs + `IPostCrudHelper`, DI registration. | +| `SampleWebApp` | ASP.NET Core MVC web front end (minimal hosting `Program.cs`). | +| `Tests` | NUnit test project. | + +Technology +---------- + +- ASP.NET Core MVC on .NET 10 (SDK‑style projects, `Program.cs` minimal hosting, endpoint routing). +- Entity Framework Core 10 with SQL Server, code‑first migrations. +- `EfCore.GenericServices` (`ICrudServices` / `ICrudServicesAsync`) for the generic CRUD/DTO layer. +- Built‑in `Microsoft.Extensions.DependencyInjection` (the legacy Autofac + `DiModelBinder` + action‑parameter injection was replaced with constructor / `[FromServices]` injection). +- Static assets served from `wwwroot/` (the legacy `System.Web.Optimization` bundling was removed). + +Features demonstrated +--------------------- + +- Synchronous DTO‑shaped access – `PostsController` (`ICrudServices`). +- Asynchronous DTO‑shaped access – `PostsAsyncController` (`ICrudServicesAsync`). +- Direct entity access – `TagsController` / `TagsAsyncController`. +- Dependency injection throughout, including the many‑to‑many Post/Tag "secondary data" + (blogger dropdown + tags multi‑select) handled by `IPostCrudHelper`. + +Running locally +--------------- + +Prerequisites: the [.NET 10 SDK](https://dotnet.microsoft.com/download) and a reachable SQL Server +instance (LocalDB on Windows, or SQL Server in Docker on Linux/macOS). + +1. **Start SQL Server** (example, Docker – Linux/macOS): -The GenericService Framework is available on [GitHub](https://github.com/JonPSmith/GenericServices) and soon via NuGet (when the release is stable). + ```bash + docker run -d --name sqlserver -e "ACCEPT_EULA=Y" \ + -e "MSSQL_SA_PASSWORD=Your_Strong_Passw0rd!" -p 1433:1433 \ + mcr.microsoft.com/mssql/server:2022-latest + ``` -**GenericServices is now available on NuGet.** -See [NuGet Package Page](https://www.nuget.org/packages/GenericServices/) for more details. +2. **Set the connection string.** The app reads the connection string named `SampleWebAppDb`. + Put it in `SampleWebApp/appsettings.Development.json`, or override it with an environment + variable (recommended, keeps secrets out of source): -**An additinal, more complex example is now available.** -Visit [Complex.SampleMvcWebApp](http://complex.samplemvcwebapp.net/) to see more. + ```bash + export ConnectionStrings__SampleWebAppDb="Server=localhost,1433;Database=SampleWebAppDb;User Id=sa;Password=Your_Strong_Passw0rd!;TrustServerCertificate=True;MultipleActiveResultSets=True" + ``` + On Windows with LocalDB you can instead use: + `Server=(localdb)\\mssqllocaldb;Database=SampleWebAppDb;Trusted_Connection=True;MultipleActiveResultSets=True`. -The specific features in the code in this example are: +3. **Run the app.** On startup `Program.cs` applies the EF Core migration and seeds the sample + blogs/posts/tags if the database is empty: -### 1. Simple, but robust database services + ```bash + dotnet run --project SampleWebApp + ``` -Database accesses are normally a big part of enterprise systems build with APS.NET MVC. -However, my experience is that creating these services in a robust and comprehensive form can lead to -a lot of repetative code that does the same thing, but for different data. -My aim has been to produce a generic framework that handles most of the cases, and is -easily extensible when special handling is required. Examples of there use on this web site are: + Then browse to the URL shown in the console (e.g. `http://localhost:5080`). - - See normal, synchronous access using a DTO for shaping in the [Posts Controller](https://github.com/JonPSmith/SampleMvcWebApp/blob/master/SampleWebApp/Controllers/PostsController.cs) - - See new EF6 async access using a DTO for shaping in the [PostsAsync Controller](https://github.com/JonPSmith/SampleMvcWebApp/blob/master/SampleWebApp/Controllers/PostsAsyncController.cs) - - See normal, synchronous access directly via data class in the [Tags Controller](https://github.com/JonPSmith/SampleMvcWebApp/blob/master/SampleWebApp/Controllers/TagsController.cs) - - See new EF6 async access directly via data class in the [TagsAsync Controller](https://github.com/JonPSmith/SampleMvcWebApp/blob/master/SampleWebApp/Controllers/TagsAsyncController.cs) + To create/apply migrations manually you can use the EF Core tools: -### 1. Use of Dependency Injection + ```bash + dotnet tool install --global dotnet-ef + dotnet ef database update --project DataLayer --startup-project SampleWebApp + ``` -The GenericService framework is designed specifically to work with Dependency Injection (DI). -DI is used throughout this web site, but specific examples are: +Running the tests +----------------- - - Inserting the required services into a controller by action parameter injection. - - DI is also used for creating the GenericService etc. See Code Explanation for more information. +```bash +dotnet test +``` -Note that the SampleMvcWebApp uses AutoFac dependency injection framework, -but the framework allows you to replace AutoFac with your own favourite DI tool. +The tests use an in‑memory SQLite `SampleWebAppDb` so they do not require a running SQL Server. From 999420ec60df71e8bc1dc59490182ae2584f2734 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 04:48:10 +0000 Subject: [PATCH 06/11] feature: migrate Tests project to SDK-style net10.0 / EF Core / built-in DI Convert the Tests project to an SDK-style net10.0 project and update every test to the migrated EF Core / EfCore.GenericServices / Microsoft.Extensions.DependencyInjection APIs. - SDK-style csproj with NUnit 4, NUnit3TestAdapter, Microsoft.NET.Test.Sdk, Moq, Microsoft.EntityFrameworkCore.Sqlite, EfCore.GenericServices; drops SampleWebApp ref. - New TestDbContext helper builds a SampleWebAppDb over an open in-memory SQLite connection. - Group01/03/06 tests ported to EF Core, MS DI, and AspNetCore ModelStateDictionary. - bug: DataLayer set GenerateAssemblyInfo=false, which silently dropped the item; replaced with an explicit AssemblyInfo.cs so internal types (LoadDbDataFromXml) are visible to the Tests assembly again. Co-Authored-By: Parker Duff --- DataLayer/DataLayer.csproj | 4 - DataLayer/Properties/AssemblyInfo.cs | 5 + Tests/App.config | 76 ----- Tests/Helpers/DbSnapShot.cs | 4 +- .../Helpers/DummyIDbContextWithValidation.cs | 85 ------ Tests/Helpers/ExtendAsserts.cs | 26 +- Tests/Helpers/JsonHelper.cs | 129 --------- Tests/Helpers/ModelStateTester.cs | 74 +++-- Tests/Helpers/SimpleTagDto.cs | 56 ---- Tests/Helpers/SimpleTagDtoAsync.cs | 59 ---- Tests/Helpers/TestDbContext.cs | 68 +++++ Tests/Helpers/TestFileHelpers.cs | 108 -------- Tests/Properties/AssemblyInfo.cs | 62 ----- Tests/Properties/Settings.Designer.cs | 38 --- Tests/Properties/Settings.settings | 9 - Tests/Tests.csproj | 244 ++-------------- .../Group01DataLayer/Test10SetupBlogs.cs | 24 +- .../Group01DataLayer/Test13Validation.cs | 202 +++++++------- .../Group01DataLayer/Test14ReadWriteBlogs.cs | 99 ++++--- .../Group03ServiceLayer/Test10DiSimple.cs | 262 ++++++++---------- .../Test11AutoFacModules.cs | 167 ----------- .../Group03ServiceLayer/Test11DiExtensions.cs | 137 +++++++++ .../UnitTests/Group06Mvc/Test02Validation.cs | 142 +--------- Tests/packages.config | 23 -- 24 files changed, 588 insertions(+), 1515 deletions(-) create mode 100644 DataLayer/Properties/AssemblyInfo.cs delete mode 100644 Tests/App.config delete mode 100644 Tests/Helpers/DummyIDbContextWithValidation.cs delete mode 100644 Tests/Helpers/JsonHelper.cs delete mode 100644 Tests/Helpers/SimpleTagDto.cs delete mode 100644 Tests/Helpers/SimpleTagDtoAsync.cs create mode 100644 Tests/Helpers/TestDbContext.cs delete mode 100644 Tests/Helpers/TestFileHelpers.cs delete mode 100644 Tests/Properties/AssemblyInfo.cs delete mode 100644 Tests/Properties/Settings.Designer.cs delete mode 100644 Tests/Properties/Settings.settings delete mode 100644 Tests/UnitTests/Group03ServiceLayer/Test11AutoFacModules.cs create mode 100644 Tests/UnitTests/Group03ServiceLayer/Test11DiExtensions.cs delete mode 100644 Tests/packages.config diff --git a/DataLayer/DataLayer.csproj b/DataLayer/DataLayer.csproj index 25e8b74..c769854 100644 --- a/DataLayer/DataLayer.csproj +++ b/DataLayer/DataLayer.csproj @@ -9,10 +9,6 @@ DataLayer - - - - diff --git a/DataLayer/Properties/AssemblyInfo.cs b/DataLayer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..36150d9 --- /dev/null +++ b/DataLayer/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +//The MSBuild item is only emitted when GenerateAssemblyInfo is true, but this +//project sets GenerateAssemblyInfo=false, so the attribute is declared explicitly here instead. +[assembly: InternalsVisibleTo("Tests")] diff --git a/Tests/App.config b/Tests/App.config deleted file mode 100644 index 875486b..0000000 --- a/Tests/App.config +++ /dev/null @@ -1,76 +0,0 @@ - - - - -
    - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jonsmith_ - - - - \ No newline at end of file diff --git a/Tests/Helpers/DbSnapShot.cs b/Tests/Helpers/DbSnapShot.cs index a28d446..b399b10 100644 --- a/Tests/Helpers/DbSnapShot.cs +++ b/Tests/Helpers/DbSnapShot.cs @@ -43,7 +43,9 @@ public class DbSnapShot public DbSnapShot(SampleWebAppDb db) { NumBlogs = db.Blogs.Count(); - NumPostTagLinks = db.Database.SqlQuery("SELECT COUNT(*) FROM dbo.TagPosts").First(); + //The Post<->Tag many-to-many join table is named "PostTag" by EF Core. There is no + //db.Database.SqlQuery in EF Core the way EF6 had it, so count the join rows via the model. + NumPostTagLinks = db.Posts.SelectMany(p => p.Tags).Count(); NumPosts = db.Posts.Count(); NumTags = db.Tags.Count(); } diff --git a/Tests/Helpers/DummyIDbContextWithValidation.cs b/Tests/Helpers/DummyIDbContextWithValidation.cs deleted file mode 100644 index dd1544c..0000000 --- a/Tests/Helpers/DummyIDbContextWithValidation.cs +++ /dev/null @@ -1,85 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: DummyIDbContextWithValidation.cs -// Date Created: 2014/06/26 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System; -using System.Collections.Generic; -using System.Data.Entity; -using System.Data.Entity.Infrastructure; -using System.Data.Entity.Validation; -using System.Threading.Tasks; -using GenericServices; -using GenericServices.Core; - -namespace Tests.Helpers -{ - public class DummyIDbContextWithValidation : IGenericServicesDbContext - { - - public bool SaveChangesWithValidationCalled { get; private set; } - - - public DbSet Set() where TEntity : class - { - throw new NotImplementedException(); - } - - public DbSet Set(Type entityType) - { - throw new NotImplementedException(); - } - - public int SaveChanges() - { - SaveChangesWithValidationCalled = true; - return 1; - } - - public async Task SaveChangesAsync() - { - SaveChangesWithValidationCalled = true; - return 1; - } - - public IEnumerable GetValidationErrors() - { - throw new NotImplementedException(); - } - - public DbEntityEntry Entry(TEntity entity) where TEntity : class - { - throw new NotImplementedException(); - } - - public DbEntityEntry Entry(object entity) - { - throw new NotImplementedException(); - } - - public void Dispose() - { - } - } -} diff --git a/Tests/Helpers/ExtendAsserts.cs b/Tests/Helpers/ExtendAsserts.cs index 8e85c05..d4c3fb3 100644 --- a/Tests/Helpers/ExtendAsserts.cs +++ b/Tests/Helpers/ExtendAsserts.cs @@ -27,7 +27,7 @@ using System.Collections.Generic; using System.Linq; using System.ComponentModel.DataAnnotations; -using NUnit.Framework; +using NUnit.Framework.Legacy; namespace Tests.Helpers { @@ -35,7 +35,7 @@ internal static class ExtendAsserts { internal static void ShouldEqual(this string actualValue, string expectedValue, string errorMessage = null) { - Assert.AreEqual(expectedValue, actualValue, errorMessage); + ClassicAssert.AreEqual(expectedValue, actualValue, errorMessage); } internal static void ShouldStartWith(this string actualValue, string expectedValue, string errorMessage = null) @@ -55,57 +55,57 @@ internal static void ShouldContain(this string actualValue, string expectedValue internal static void ShouldNotEqual(this string actualValue, string expectedValue, string errorMessage = null) { - Assert.True(expectedValue != actualValue, errorMessage); + ClassicAssert.True(expectedValue != actualValue, errorMessage); } internal static void ShouldEqualWithTolerance(this float actualValue, double expectedValue, double tolerance, string errorMessage = null) { - Assert.AreEqual(expectedValue, actualValue, tolerance, errorMessage); + ClassicAssert.AreEqual(expectedValue, actualValue, tolerance, errorMessage); } internal static void ShouldEqualWithTolerance(this long actualValue, long expectedValue, int tolerance, string errorMessage = null) { - Assert.AreEqual(expectedValue, actualValue, tolerance, errorMessage); + ClassicAssert.AreEqual(expectedValue, actualValue, tolerance, errorMessage); } internal static void ShouldEqualWithTolerance(this double actualValue, double expectedValue, double tolerance, string errorMessage = null) { - Assert.AreEqual(expectedValue, actualValue, tolerance, errorMessage); + ClassicAssert.AreEqual(expectedValue, actualValue, tolerance, errorMessage); } internal static void ShouldEqualWithTolerance(this int actualValue, int expectedValue, int tolerance, string errorMessage = null) { - Assert.AreEqual(expectedValue, actualValue, tolerance, errorMessage); + ClassicAssert.AreEqual(expectedValue, actualValue, tolerance, errorMessage); } internal static void ShouldEqual( this T actualValue, T expectedValue, string errorMessage = null) { - Assert.AreEqual(expectedValue, actualValue, errorMessage); + ClassicAssert.AreEqual(expectedValue, actualValue, errorMessage); } internal static void ShouldEqual(this T actualValue, T expectedValue, IEnumerable errorMessages) { - Assert.AreEqual(expectedValue, actualValue, string.Join("\n", errorMessages)); + ClassicAssert.AreEqual(expectedValue, actualValue, string.Join("\n", errorMessages)); } internal static void ShouldEqual(this T actualValue, T expectedValue, IEnumerable validationResults) { - Assert.AreEqual(expectedValue, actualValue, string.Join("\n", validationResults.Select( x => x.ErrorMessage))); + ClassicAssert.AreEqual(expectedValue, actualValue, string.Join("\n", validationResults.Select( x => x.ErrorMessage))); } internal static void ShouldNotEqual(this T actualValue, T unexpectedValue, string errorMessage = null) { - Assert.AreNotEqual(unexpectedValue, actualValue); + ClassicAssert.AreNotEqual(unexpectedValue, actualValue); } internal static void ShouldNotEqualNull(this T actualValue, string errorMessage = null) where T : class { - Assert.NotNull( actualValue); + ClassicAssert.NotNull( actualValue); } internal static void IsA(this object actualValue, string errorMessage = null) { - Assert.True(actualValue.GetType() == typeof(T)); + ClassicAssert.True(actualValue.GetType() == typeof(T)); } } } diff --git a/Tests/Helpers/JsonHelper.cs b/Tests/Helpers/JsonHelper.cs deleted file mode 100644 index 2bf617f..0000000 --- a/Tests/Helpers/JsonHelper.cs +++ /dev/null @@ -1,129 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: JsonHelper.cs -// Date Created: 2014/05/31 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System; -using System.ComponentModel.DataAnnotations; -using System.Linq.Expressions; -using System.Reflection; -using System.Web.Helpers; -using Newtonsoft.Json; -using NUnit.Framework; - -namespace Tests.Helpers -{ - static class JsonHelper - { - - //public static string SerialiseToJsonUsingJsonNet(this object data) - //{ - // JsonConvert.SerializeObject(data); - // - //} - - //public static string SerialiseToJsonIndentedUsingJsonNet(this object data) - //{ - // return JsonConvert.SerializeObject(data, Formatting.Indented); - //} - - public static string SerialiseToJson(this object data) - { - return Json.Encode(data); - } - - public static string AssertJsonPropertyPresentAndReturnValue - (this string jsonString, - TSource source, Expression> propertyLambda) - { - return jsonString.AssertJsonPropertyInOrOutAndReturnValue(source, propertyLambda, false); - } - - public static void AssertJsonPropertyNotPresent - (this string jsonString, - TSource source, Expression> propertyLambda) - { - jsonString.AssertJsonPropertyInOrOutAndReturnValue(source, propertyLambda, true); - } - - //----------------------------------------------------------------- - - private static string AssertJsonPropertyInOrOutAndReturnValue - (this string jsonString, - TSource source, Expression> propertyLambda, - bool doesNotContain) - { - - var stringToFind = string.Format("\"{0}\":", source.GetPropertyInfo(propertyLambda).Name); - var startIndex = jsonString.IndexOf(stringToFind, StringComparison.InvariantCultureIgnoreCase); - if (doesNotContain) - { - if (startIndex == -1) return null; //all good - Assert.Fail("The property '{0}' should NOT be in the in json string", stringToFind); - } - - //otherwise we expect it to be in - if (startIndex == -1) - Assert.Fail("Looked for '{0}' in json and could not find it", stringToFind); - - //now return value after it - - var closingIndex = jsonString.IndexOf('\n', startIndex + 1); - if (closingIndex == -1) - throw new ValidationException("This only works on indented json, and this doesn't seem to be indented"); - - var result = jsonString.Substring(startIndex + stringToFind.Length, closingIndex - startIndex - stringToFind.Length).Trim(); - return result.EndsWith(",") ? result.Substring(0, result.Length - 1).Trim() : result; - } - - public static PropertyInfo GetPropertyInfo( - this TSource source, - Expression> propertyLambda) - { - Type type = typeof(TSource); - - MemberExpression member = propertyLambda.Body as MemberExpression; - if (member == null) - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a method, not a property.", - propertyLambda.ToString())); - - PropertyInfo propInfo = member.Member as PropertyInfo; - if (propInfo == null) - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a field, not a property.", - propertyLambda.ToString())); - - if (type != propInfo.ReflectedType && - !type.IsSubclassOf(propInfo.ReflectedType)) - throw new ArgumentException(string.Format( - "Expresion '{0}' refers to a property that is not from type {1}.", - propertyLambda.ToString(), - type)); - - return propInfo; - } - - } -} diff --git a/Tests/Helpers/ModelStateTester.cs b/Tests/Helpers/ModelStateTester.cs index 8f3068b..58dc8ab 100644 --- a/Tests/Helpers/ModelStateTester.cs +++ b/Tests/Helpers/ModelStateTester.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: ModelStateTester.cs @@ -25,14 +25,13 @@ // SOFTWARE. #endregion using System.Collections.Generic; -using System.Collections.Specialized; using System.ComponentModel.DataAnnotations; -using System.Globalization; -using System.Web.Mvc; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Tests.Helpers { - static class ModelStateTester + public static class ModelStateTester { public class TestModel : IValidatableObject @@ -67,36 +66,53 @@ public TestModel(string myString, int myInt, bool createValidationError) } } - - private class TestController : Controller - { - public ActionResult ValidDateTestModel(TestModel model) - { - // ReSharper disable once Mvc.ViewNotResolved - return View(model); - } - } - + /// + /// The old version drove System.Web.Mvc's DefaultModelBinder (which no longer exists). This rebuilds the + /// same result the ASP.NET Core MVC validation pipeline produces: the property-level DataAnnotations are + /// validated first and, only if there are no property errors, the object-level IValidatableObject.Validate + /// is run. Errors are written into an ASP.NET Core keyed by member name, + /// with top-level (no member) errors under the "" key. + /// public static ModelStateDictionary ReturnModelState(this TestModel model) { - var testController = new TestController(); + var modelState = new ModelStateDictionary(); - var modelBinder = new ModelBindingContext() + var hasPropertyErrors = false; + foreach (var property in model.GetType().GetProperties()) { - ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType( - () => model, model.GetType()), - ValueProvider = new NameValueCollectionValueProvider( - new NameValueCollection(), CultureInfo.InvariantCulture) - }; - var binder = new DefaultModelBinder().BindModel( - new ControllerContext(), modelBinder); - testController.ModelState.Clear(); - testController.ModelState.Merge(modelBinder.ModelState); + var value = property.GetValue(model); + var context = new ValidationContext(model) { MemberName = property.Name }; - var viewResult = (ViewResult) testController.ValidDateTestModel(model); - return viewResult.ViewData.ModelState; - } + //Each DataAnnotations attribute is evaluated independently (as the old MVC validators did). + //Validator.TryValidateProperty is not used because it short-circuits after a failing + //RequiredAttribute, which would hide the other attribute errors the tests expect. + foreach (var attribute in property.GetCustomAttributes(true).OfType()) + { + var result = attribute.GetValidationResult(value, context); + if (result != ValidationResult.Success) + { + hasPropertyErrors = true; + modelState.AddModelError(property.Name, result.ErrorMessage); + } + } + } + //ASP.NET Core (like the old MVC binder) only runs IValidatableObject.Validate when there are no + //property-level attribute errors. + if (!hasPropertyErrors) + { + foreach (var result in model.Validate(new ValidationContext(model))) + { + var members = result.MemberNames?.ToList() ?? new List(); + if (members.Count == 0) + modelState.AddModelError("", result.ErrorMessage); + else + foreach (var member in members) + modelState.AddModelError(member, result.ErrorMessage); + } + } + return modelState; + } } } diff --git a/Tests/Helpers/SimpleTagDto.cs b/Tests/Helpers/SimpleTagDto.cs deleted file mode 100644 index 62d4dc8..0000000 --- a/Tests/Helpers/SimpleTagDto.cs +++ /dev/null @@ -1,56 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: SimpleTagDto.cs -// Date Created: 2014/06/26 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System.ComponentModel.DataAnnotations; -using DataLayer.DataClasses.Concrete; -using GenericServices.Core; - -namespace Tests.Helpers -{ - class SimpleTagDto : InstrumentedEfGenericDto - { - - [Key] - public int TagId { get; set; } - - [MaxLength(64)] - [Required] - [RegularExpression(@"\w*", ErrorMessage = "The slug must not contain spaces or non-alphanumeric characters.")] - public string Slug { get; set; } - - [MaxLength(128)] - [Required] - public string Name { get; set; } - - //-------------------------------------- - - protected internal override CrudFunctions SupportedFunctions - { - get { return CrudFunctions.AllCrud; } - } - - } -} diff --git a/Tests/Helpers/SimpleTagDtoAsync.cs b/Tests/Helpers/SimpleTagDtoAsync.cs deleted file mode 100644 index 60256b4..0000000 --- a/Tests/Helpers/SimpleTagDtoAsync.cs +++ /dev/null @@ -1,59 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: SimpleTagDtoAsync.cs -// Date Created: 2014/06/26 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System.ComponentModel.DataAnnotations; -using DataLayer.DataClasses.Concrete; -using GenericServices.Core; - -namespace Tests.Helpers -{ - class SimpleTagDtoAsync : InstrumentedEfGenericDtoAsync - { - - - [Key] - public int TagId { get; set; } - - [MaxLength(64)] - [Required] - [RegularExpression(@"\w*", ErrorMessage = "The slug must not contain spaces or non-alphanumeric characters.")] - public string Slug { get; set; } - - [MaxLength(128)] - [Required] - public string Name { get; set; } - - - //-------------------------------------- - - - protected internal override CrudFunctions SupportedFunctions - { - get { return CrudFunctions.AllCrud; } - } - - } -} diff --git a/Tests/Helpers/TestDbContext.cs b/Tests/Helpers/TestDbContext.cs new file mode 100644 index 0000000..72bbbc7 --- /dev/null +++ b/Tests/Helpers/TestDbContext.cs @@ -0,0 +1,68 @@ +#region licence +// The MIT License (MIT) +// +// Filename: TestDbContext.cs +// +// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#endregion +using DataLayer.DataClasses; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; + +namespace Tests.Helpers +{ + /// + /// Builds instances backed by an in-memory SQLite database. + /// The relational SQLite provider is used (rather than the EF Core in-memory provider) because it + /// honours the schema, including the unique index on Tag.Slug that the Slug tests rely on. + /// + /// A SQLite in-memory database only lives for as long as its connection is open, so the connection + /// is created and kept open by the test; several contexts can be built + /// on the same connection to mimic the old "new SampleWebAppDb()" pattern (a fresh change tracker + /// over the same underlying data). + /// + public static class TestDbContext + { + /// + /// Opens a new in-memory SQLite connection and creates the schema on it. + /// Keep the returned connection open for the lifetime of the test and dispose it when done. + /// + public static SqliteConnection CreateOpenConnection() + { + var connection = new SqliteConnection("DataSource=:memory:"); + connection.Open(); + using (var db = CreateContext(connection)) + db.Database.EnsureCreated(); + return connection; + } + + /// + /// Builds a over an already-open connection. + /// + public static SampleWebAppDb CreateContext(SqliteConnection connection) + { + var options = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; + return new SampleWebAppDb(options); + } + } +} diff --git a/Tests/Helpers/TestFileHelpers.cs b/Tests/Helpers/TestFileHelpers.cs deleted file mode 100644 index bb2080b..0000000 --- a/Tests/Helpers/TestFileHelpers.cs +++ /dev/null @@ -1,108 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: TestFileHelpers.cs -// Date Created: 2014/06/27 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System; -using System.IO; - -namespace Tests.Helpers -{ - internal static class TestFileHelpers - { - private const string TestFileDirectoryName = @"\TestData"; - - //------------------------------------------------------------------- - - internal static string GetTestFileFilePath(string searchPattern) - { - string[] fileList = GetTestFileFilesOfGivenName(searchPattern); - - if (fileList.Length != 1) - throw new Exception(string.Format("GetTestFileFilePath: The searchString {0} found {1} file. Either not there or ambiguous", - searchPattern, fileList.Length)); - - return fileList[0]; - } - - internal static string GetTestFileContent(string searchPattern) - { - var filePath = GetTestFileFilePath(searchPattern); - return File.ReadAllText(filePath); - } - - internal static string[] GetTestFileFilesOfGivenName(string searchPattern = "") - { - var directory = GetTestDataFileDirectory(); - if (searchPattern.Contains(@"\")) - { - //Has subdirectory in search pattern, so change directory - directory = Path.Combine(directory, searchPattern.Substring(0, searchPattern.LastIndexOf('\\'))); - searchPattern = searchPattern.Substring(searchPattern.LastIndexOf('\\')+1); - } - - string[] fileList = Directory.GetFiles(directory, searchPattern); - - return fileList; - } - - - //------------------------------------------------------------------------------ - - public static string GetTestDataFileDirectory(string alternateTestDir = TestFileDirectoryName) - { - string pathToManipulate = Environment.CurrentDirectory; - const string debugEnding = @"\bin\debug"; - const string releaseEnding = @"\bin\release"; - - if (pathToManipulate.EndsWith(debugEnding, StringComparison.InvariantCultureIgnoreCase)) - return pathToManipulate.Substring(0, pathToManipulate.Length - debugEnding.Length) + alternateTestDir; - if (pathToManipulate.EndsWith(releaseEnding, StringComparison.InvariantCultureIgnoreCase)) - return pathToManipulate.Substring(0, pathToManipulate.Length - releaseEnding.Length) + alternateTestDir; - - throw new Exception("bad news guys. Not the expected path"); - - } - - public static string GetSolutionDirectory() - { - string pathToManipulate = Environment.CurrentDirectory; - const string debugEnding = @"\bin\debug"; - const string releaseEnding = @"\bin\release"; - - string projectDir = null; - if (pathToManipulate.EndsWith(debugEnding, StringComparison.InvariantCultureIgnoreCase)) - projectDir = pathToManipulate.Substring(0, pathToManipulate.Length - debugEnding.Length); - if (pathToManipulate.EndsWith(releaseEnding, StringComparison.InvariantCultureIgnoreCase)) - projectDir = pathToManipulate.Substring(0, pathToManipulate.Length - releaseEnding.Length); - - if (projectDir == null) - throw new Exception("bad news guys. Not the expected path"); - - return projectDir.Substring(0, projectDir.LastIndexOf("\\", StringComparison.Ordinal)); - - } - - } -} diff --git a/Tests/Properties/AssemblyInfo.cs b/Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index b722a8d..0000000 --- a/Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,62 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: AssemblyInfo.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Tests")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("edff866f-292e-46db-9cdf-74c70d23322d")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tests/Properties/Settings.Designer.cs b/Tests/Properties/Settings.Designer.cs deleted file mode 100644 index f2bf076..0000000 --- a/Tests/Properties/Settings.Designer.cs +++ /dev/null @@ -1,38 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.18444 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Tests.Properties { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("jonsmith_")] - public string DatabaseLoginPrefix { - get { - return ((string)(this["DatabaseLoginPrefix"])); - } - set { - this["DatabaseLoginPrefix"] = value; - } - } - } -} diff --git a/Tests/Properties/Settings.settings b/Tests/Properties/Settings.settings deleted file mode 100644 index 74f592a..0000000 --- a/Tests/Properties/Settings.settings +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - jonsmith_ - - - \ No newline at end of file diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 5dca393..5e8f888 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,231 +1,33 @@ - - - + + - Debug - AnyCPU - {6D9E7904-B2AC-49E3-83A7-6B48876F46B9} - Library - Properties + net10.0 + disable + disable + false Tests Tests - v4.5.1 - 512 + false - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - bin\ReleaseAzure\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\AzureRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\WebWizRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - False - ..\packages\Autofac.3.5.0\lib\net40\Autofac.dll - - - ..\packages\AutoMapper.4.2.1\lib\net45\AutoMapper.dll - True - - - ..\packages\DelegateDecompiler.0.18.0\lib\net40-Client\DelegateDecompiler.dll - True - - - ..\packages\DelegateDecompiler.EntityFramework.0.18.0\lib\net45\DelegateDecompiler.EntityFramework.dll - True - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - True - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - True - - - ..\packages\GenericLibsBase.1.0.1\lib\GenericLibsBase.dll - True - - - ..\packages\GenericServices.1.0.9\lib\GenericServices.dll - True - - - False - ..\packages\log4net.2.0.3\lib\net40-full\log4net.dll - - - False - ..\packages\Microsoft.AspNet.SignalR.Core.2.0.3\lib\net45\Microsoft.AspNet.SignalR.Core.dll - - - False - ..\packages\Microsoft.Owin.2.1.0\lib\net45\Microsoft.Owin.dll - - - False - ..\packages\Microsoft.Owin.Security.2.1.0\lib\net45\Microsoft.Owin.Security.dll - - - True - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - - ..\packages\Mono.Reflection.1.0.0.0\lib\Mono.Reflection.dll - - - ..\packages\Moq.4.2.1408.0717\lib\net40\Moq.dll - - - False - ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - - - ..\packages\NUnit.2.6.3\lib\nunit.framework.dll - - - False - ..\packages\Owin.1.0\lib\net40\Owin.dll - - - - - - - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - True - - - ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll - True - - - ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll - True - - - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll - True - - - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll - True - - - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Settings.settings - - - - - - - - - - - Designer - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - + - - {264e1878-12de-4099-b8d7-cc53a73fea49} - DataLayer - - - {cffee5e0-3b99-46e0-9a82-2e74621c17c5} - SampleWebApp - - - {d2813927-0f38-43c3-b47c-ae8f00d50cae} - ServiceLayer - + + - + + + + + + + + - - + + + - - - \ No newline at end of file + + diff --git a/Tests/UnitTests/Group01DataLayer/Test10SetupBlogs.cs b/Tests/UnitTests/Group01DataLayer/Test10SetupBlogs.cs index 7a03ba0..9f9b1c4 100644 --- a/Tests/UnitTests/Group01DataLayer/Test10SetupBlogs.cs +++ b/Tests/UnitTests/Group01DataLayer/Test10SetupBlogs.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: Test10SetupBlogs.cs @@ -26,7 +26,6 @@ #endregion using System; using System.Linq; -using DataLayer.DataClasses; using DataLayer.Startup; using DataLayer.Startup.Internal; using NUnit.Framework; @@ -34,7 +33,7 @@ namespace Tests.UnitTests.Group01DataLayer { - class Test10SetupBlogs + public class Test10SetupBlogs { [Test] public void Check01XmlFileLoadOk() @@ -69,10 +68,10 @@ public void Check02XmlFileLoadBad() [Test] public void Check10BlogsResetSmallOk() { - using (var db = new SampleWebAppDb()) + using (var connection = TestDbContext.CreateOpenConnection()) + using (var db = TestDbContext.CreateContext(connection)) { //SETUP - DataLayerInitialise.InitialiseThis(false, true); //ATTEMPT DataLayerInitialise.ResetBlogs(db, TestDataSelection.Small); @@ -87,10 +86,10 @@ public void Check10BlogsResetSmallOk() [Test] public void Check11BlogsResetMediumOk() { - using (var db = new SampleWebAppDb()) + using (var connection = TestDbContext.CreateOpenConnection()) + using (var db = TestDbContext.CreateContext(connection)) { //SETUP - DataLayerInitialise.InitialiseThis(false, true); //ATTEMPT DataLayerInitialise.ResetBlogs(db, TestDataSelection.Medium); @@ -105,13 +104,16 @@ public void Check11BlogsResetMediumOk() //--------------------------------------------------------- [Test] - public void Check20NullInitialiserOk() + public void Check20ResetBlogsIsRepeatableOk() { - Check10BlogsResetSmallOk(); //we call this to ensure the database is setup - using (var db = new SampleWebAppDb()) + //The old test toggled the EF6 "null database initialiser" (Database.SetInitializer) which no longer + //exists under EF Core. The behaviour worth preserving is that ResetBlogs can be run repeatedly against + //an already-seeded database (it deletes then reseeds) and leaves the same counts each time. + using (var connection = TestDbContext.CreateOpenConnection()) + using (var db = TestDbContext.CreateContext(connection)) { //SETUP - DataLayerInitialise.InitialiseThis(false, false); //select null initialiser + DataLayerInitialise.ResetBlogs(db, TestDataSelection.Small); //ATTEMPT DataLayerInitialise.ResetBlogs(db, TestDataSelection.Small); diff --git a/Tests/UnitTests/Group01DataLayer/Test13Validation.cs b/Tests/UnitTests/Group01DataLayer/Test13Validation.cs index 5bba1c2..df1779b 100644 --- a/Tests/UnitTests/Group01DataLayer/Test13Validation.cs +++ b/Tests/UnitTests/Group01DataLayer/Test13Validation.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: Test13Validation.cs @@ -26,72 +26,92 @@ #endregion using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; -using DataLayer.DataClasses; using DataLayer.DataClasses.Concrete; using DataLayer.Startup; -using GenericServices; +using Microsoft.Data.Sqlite; using NUnit.Framework; using Tests.Helpers; namespace Tests.UnitTests.Group01DataLayer { - class Test13Validation + public class Test13Validation { + private SqliteConnection _connection; - [TestFixtureSetUp] - public void SetUpFixture() + [SetUp] + public void SetUp() { - using (var db = new SampleWebAppDb()) - { - DataLayerInitialise.InitialiseThis(false, true); + _connection = TestDbContext.CreateOpenConnection(); + using (var db = TestDbContext.CreateContext(_connection)) DataLayerInitialise.ResetBlogs(db, TestDataSelection.Small); - } } + [TearDown] + public void TearDown() + { + _connection?.Dispose(); + } + + //-------------------------------------------------------------------- + //Tag.Slug uniqueness is now enforced by SampleWebAppDb.SaveChanges (CheckForUniqueSlugs), + //which throws a ValidationException on a duplicate Slug. The old GenericServices + //SaveChangesWithChecking()/SuccessOrErrors status object no longer exists. + [Test] public void Check01ValidateTagOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP var snap = new DbSnapShot(db); //ATTEMPT - var dupTag = new Tag { Name = "non-duplicate slug", Slug = Guid.NewGuid().ToString("N") }; - db.Tags.Add(dupTag); - var status = db.SaveChangesWithChecking(); + var newTag = new Tag { Name = "non-duplicate slug", Slug = Guid.NewGuid().ToString("N") }; + db.Tags.Add(newTag); + db.SaveChanges(); //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); - snap.CheckSnapShot(db, 0,0,0,1); + snap.CheckSnapShot(db, 0, 0, 0, 1); } } [Test] public void Check02ValidateTagError() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP var existingTag = db.Tags.First(); //ATTEMPT - var dupTag = new Tag {Name = "duplicate slug", Slug = existingTag.Slug}; + var dupTag = new Tag { Name = "duplicate slug", Slug = existingTag.Slug }; db.Tags.Add(dupTag); - var status = db.SaveChangesWithChecking();; + var ex = Assert.Throws(() => db.SaveChanges()); //VERIFY - status.IsValid.ShouldEqual(false); - status.Errors.Count.ShouldEqual(1); - status.Errors[0].ErrorMessage.ShouldEqual("The Slug on tag 'duplicate slug' must be unique and is already being used."); + ex.Message.ShouldEqual("The Slug on tag 'duplicate slug' must be unique and is already being used."); } } + //-------------------------------------------------------------------- + //Post content/title rules live on Post as DataAnnotations + IValidatableObject. Under EF Core the + //DbContext no longer runs entity validation on SaveChanges (that moved to MVC ModelState / the + //PostCrudHelper), so these tests assert the Post's own validation via Validator.TryValidateObject, + //which is exactly the rule-set the removed SaveChangesWithChecking() used to enforce. + + private static IList ValidatePost(Post post) + { + var results = new List(); + Validator.TryValidateObject(post, new ValidationContext(post), results, true); + return results; + } + [Test] public void Check10ValidatePostOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP var snap = new DbSnapShot(db); @@ -106,125 +126,91 @@ public void Check10ValidatePostOk() Content = "Nothing special", Tags = new[] { existingTag } }; + var errors = ValidatePost(newPost); db.Posts.Add(newPost); - var status = db.SaveChangesWithChecking();; + db.SaveChanges(); //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); - snap.CheckSnapShot(db,1,1); + errors.Count.ShouldEqual(0); + snap.CheckSnapShot(db, 1, 1); } } - [Test] public void Check15ValidatePostTitleError() { - using (var db = new SampleWebAppDb()) + //SETUP + var newPost = new Post() { - //SETUP - var existingTag = db.Tags.First(); - var existingBlogger = db.Blogs.First(); + Title = "Test post!", + Content = "Nothing special", + Tags = new[] { new Tag() } + }; - //ATTEMPT - var newPost = new Post() - { - Blogger = existingBlogger, - Title = "Test post!", - Content = "Nothing special", - Tags = new[] { existingTag } - }; - db.Posts.Add(newPost); - var status = db.SaveChangesWithChecking();; + //ATTEMPT + var errors = ValidatePost(newPost); - //VERIFY - status.IsValid.ShouldEqual(false); - status.Errors.Count.ShouldEqual(1); - status.Errors[0].ErrorMessage.ShouldEqual("Sorry, but you can't get too excited and include a ! in the title."); - } + //VERIFY + errors.Count.ShouldEqual(1); + errors[0].ErrorMessage.ShouldEqual("Sorry, but you can't get too excited and include a ! in the title."); } [Test] public void Check16ValidatePostTitleError() { - using (var db = new SampleWebAppDb()) + //SETUP + var newPost = new Post() { - //SETUP - var existingTag = db.Tags.First(); - var existingBlogger = db.Blogs.First(); + Title = "Test post?", + Content = "Nothing special", + Tags = new[] { new Tag() } + }; - //ATTEMPT - var newPost = new Post() - { - Blogger = existingBlogger, - Title = "Test post?", - Content = "Nothing special", - Tags = new[] { existingTag } - }; - db.Posts.Add(newPost); - var status = db.SaveChangesWithChecking();; + //ATTEMPT + var errors = ValidatePost(newPost); - //VERIFY - status.IsValid.ShouldEqual(false); - status.Errors.Count.ShouldEqual(1); - status.Errors[0].ErrorMessage.ShouldEqual("Sorry, but you can't ask a question, i.e. the title can't end with '?'."); - } + //VERIFY + errors.Count.ShouldEqual(1); + errors[0].ErrorMessage.ShouldEqual("Sorry, but you can't ask a question, i.e. the title can't end with '?'."); } [Test] public void Check20ValidatePostContentOneError() { - using (var db = new SampleWebAppDb()) + //SETUP + var newPost = new Post() { - //SETUP - var existingTag = db.Tags.First(); - var existingBlogger = db.Blogs.First(); + Title = "Test post", + Content = "Should not end sentence with sheep.", + Tags = new[] { new Tag() } + }; - //ATTEMPT - var newPost = new Post() - { - Blogger = existingBlogger, - Title = "Test post", - Content = "Should not end sentence with sheep.", - Tags = new[] { existingTag } - }; - db.Posts.Add(newPost); - var status = db.SaveChangesWithChecking();; + //ATTEMPT + var errors = ValidatePost(newPost); - //VERIFY - status.IsValid.ShouldEqual(false); - status.Errors.Count.ShouldEqual(1); - status.Errors[0].ErrorMessage.ShouldEqual("Sorry. Not allowed to end a sentance with 'sheep'."); - } + //VERIFY + errors.Count.ShouldEqual(1); + errors[0].ErrorMessage.ShouldEqual("Sorry. Not allowed to end a sentance with 'sheep'."); } - [Test] public void Check21ValidatePostContentTwoErrors() { - using (var db = new SampleWebAppDb()) + //SETUP + var newPost = new Post() { - //SETUP - var existingTag = db.Tags.First(); - var existingBlogger = db.Blogs.First(); - - //ATTEMPT - var newPost = new Post() - { - Blogger = existingBlogger, - Title = "Test post", - Content = "Should not end sentence with sheep. Nor end sentence with lamb.", - Tags = new[] { existingTag } - }; - db.Posts.Add(newPost); - var status = db.SaveChangesWithChecking();; - - //VERIFY - status.IsValid.ShouldEqual(false); - status.Errors.Count.ShouldEqual(2); - status.Errors[0].ErrorMessage.ShouldEqual("Sorry. Not allowed to end a sentance with 'sheep'."); - status.Errors[1].ErrorMessage.ShouldEqual("Sorry. Not allowed to end a sentance with 'lamb'."); - } + Title = "Test post", + Content = "Should not end sentence with sheep. Nor end sentence with lamb.", + Tags = new[] { new Tag() } + }; + + //ATTEMPT + var errors = ValidatePost(newPost); + + //VERIFY + errors.Count.ShouldEqual(2); + errors[0].ErrorMessage.ShouldEqual("Sorry. Not allowed to end a sentance with 'sheep'."); + errors[1].ErrorMessage.ShouldEqual("Sorry. Not allowed to end a sentance with 'lamb'."); } } } - diff --git a/Tests/UnitTests/Group01DataLayer/Test14ReadWriteBlogs.cs b/Tests/UnitTests/Group01DataLayer/Test14ReadWriteBlogs.cs index 9c70554..e738077 100644 --- a/Tests/UnitTests/Group01DataLayer/Test14ReadWriteBlogs.cs +++ b/Tests/UnitTests/Group01DataLayer/Test14ReadWriteBlogs.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: Test14ReadWriteBlogs.cs @@ -25,35 +25,40 @@ // SOFTWARE. #endregion using System; -using System.Data.Entity; using System.Linq; using System.Threading; -using DataLayer.DataClasses; using DataLayer.DataClasses.Concrete; using DataLayer.Startup; -using GenericServices; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; using NUnit.Framework; +using NUnit.Framework.Legacy; using Tests.Helpers; namespace Tests.UnitTests.Group01DataLayer { - class Test14ReadWriteBlogs + public class Test14ReadWriteBlogs { + private SqliteConnection _connection; [SetUp] public void SetUp() { - using (var db = new SampleWebAppDb()) - { - DataLayerInitialise.InitialiseThis(false, true); + _connection = TestDbContext.CreateOpenConnection(); + using (var db = TestDbContext.CreateContext(_connection)) DataLayerInitialise.ResetBlogs(db, TestDataSelection.Small); - } + } + + [TearDown] + public void TearDown() + { + _connection?.Dispose(); } [Test] public void Check01ReadBlogsNoPostsOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP @@ -62,6 +67,7 @@ public void Check01ReadBlogsNoPostsOk() //VERIFY blogs.Count.ShouldEqual(2); + //EF Core does not lazy-load, so a navigation that was not Included stays null blogs.All(x => x.Posts == null).ShouldEqual(true); } } @@ -69,7 +75,7 @@ public void Check01ReadBlogsNoPostsOk() [Test] public void Check02ReadBlogsWithPostsOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP @@ -86,12 +92,12 @@ public void Check02ReadBlogsWithPostsOk() [Test] public void Check03ReadBlogsWithPostTagsOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP //ATTEMPT - var blogs = db.Blogs.Include(x => x.Posts.Select(y => y.Tags)).ToList(); + var blogs = db.Blogs.Include(x => x.Posts).ThenInclude(y => y.Tags).ToList(); //VERIFY blogs.Count.ShouldEqual(2); @@ -104,7 +110,7 @@ public void Check03ReadBlogsWithPostTagsOk() [Test] public void Check05ReadPostsOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP @@ -113,7 +119,9 @@ public void Check05ReadPostsOk() //VERIFY posts.Count.ShouldEqual(3); - posts.All(x => x.Blogger != null).ShouldEqual(true); + //Under EF6 the virtual Blogger was lazy-loaded; EF Core has no lazy loading configured, + //so neither the Blogger nor the Tags navigation is populated without an explicit Include. + posts.All(x => x.Blogger == null).ShouldEqual(true); posts.All(x => x.Tags == null).ShouldEqual(true); } } @@ -122,7 +130,7 @@ public void Check05ReadPostsOk() [Test] public void Check06ReadPostsWithTagsOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP @@ -131,7 +139,8 @@ public void Check06ReadPostsWithTagsOk() //VERIFY posts.Count.ShouldEqual(3); - posts.All(x => x.Blogger != null).ShouldEqual(true); + //Tags were Included so they are loaded; Blogger was not Included and (no lazy loading) stays null + posts.All(x => x.Blogger == null).ShouldEqual(true); posts.All(x => x.Tags != null).ShouldEqual(true); } } @@ -139,7 +148,7 @@ public void Check06ReadPostsWithTagsOk() [Test] public void Check10ReadTAllocatedTagsWithUglySlugOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP @@ -159,7 +168,7 @@ public void Check10ReadTAllocatedTagsWithUglySlugOk() [Test] public void Check20AddPostOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP var snap = new DbSnapShot(db); @@ -176,10 +185,9 @@ public void Check20AddPostOk() }; db.Posts.Add(newPost); - var status = db.SaveChangesWithChecking(); + db.SaveChanges(); //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); snap.CheckSnapShot(db, 1, 1); var uglyPosts = db.Tags.Include(x => x.Posts).Single(y => y.Slug == "ugly").Posts; uglyPosts.Count.ShouldEqual(3); @@ -189,7 +197,7 @@ public void Check20AddPostOk() [Test] public void Check21CheckUpdateSimpleOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP var snap = new DbSnapShot(db); @@ -198,10 +206,9 @@ public void Check21CheckUpdateSimpleOk() //ATTEMPT var firstPost = db.Posts.First(); firstPost.Title = newGuid; - var status = db.SaveChangesWithChecking(); + db.SaveChanges(); //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); snap.CheckSnapShot(db); db.Posts.First().Title.ShouldEqual(newGuid); } @@ -211,7 +218,7 @@ public void Check21CheckUpdateSimpleOk() [Test] public void Check22CheckUpdateLastUpdatedOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP var snap = new DbSnapShot(db); @@ -221,19 +228,18 @@ public void Check22CheckUpdateLastUpdatedOk() //ATTEMPT firstPost.Title = Guid.NewGuid().ToString(); - var status = db.SaveChangesWithChecking(); + db.SaveChanges(); //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); snap.CheckSnapShot(db); - Assert.GreaterOrEqual(db.Posts.First().LastUpdated.Subtract(originalDateTime).Milliseconds, 400); + ClassicAssert.GreaterOrEqual(db.Posts.First().LastUpdated.Subtract(originalDateTime).TotalMilliseconds, 400); } } [Test] public void Check25UpdatePostToAddTagOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP var snap = new DbSnapShot(db); @@ -243,12 +249,11 @@ public void Check25UpdatePostToAddTagOk() //ATTEMPT db.Entry(firstPost).Collection(x => x.Tags).Load(); firstPost.Tags.Add(badTag); - var status = db.SaveChangesWithChecking(); + db.SaveChanges(); //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); snap.CheckSnapShot(db, 0, 1); - firstPost = db.Blogs.Include(x => x.Posts.Select(y => y.Tags)).First().Posts.First(); + firstPost = db.Blogs.Include(x => x.Posts).ThenInclude(y => y.Tags).First().Posts.First(); firstPost.Tags.Count.ShouldEqual(3); } } @@ -256,7 +261,7 @@ public void Check25UpdatePostToAddTagOk() [Test] public void Check26ReplaceTagsOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP var snap = new DbSnapShot(db); @@ -267,12 +272,11 @@ public void Check26ReplaceTagsOk() db.Entry(firstPost).Collection(x => x.Tags).Load(); firstPost.Tags = tagsNotInFirstPostTracked; - var status = db.SaveChangesWithChecking(); + db.SaveChanges(); //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); snap.CheckSnapShot(db, 0, -1); - firstPost = db.Blogs.Include(x => x.Posts.Select(y => y.Tags)).First().Posts.First(); + firstPost = db.Blogs.Include(x => x.Posts).ThenInclude(y => y.Tags).First().Posts.First(); firstPost.Tags.Count.ShouldEqual(1); } } @@ -280,7 +284,7 @@ public void Check26ReplaceTagsOk() [Test] public void Check30CheckCreateLastUpdatedOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP var snap = new DbSnapShot(db); @@ -289,43 +293,46 @@ public void Check30CheckCreateLastUpdatedOk() Thread.Sleep(400); //ATTEMPT + //Reset the PK so EF Core treats this detached copy as a brand-new row (EF6 ignored the + //store-generated key on Add; EF Core would otherwise try to insert the explicit PostId). + firstPostUntracked.PostId = 0; firstPostUntracked.Title = Guid.NewGuid().ToString(); firstPostUntracked.Blogger = db.Blogs.First(); firstPostUntracked.Tags = db.Tags.Take(2).ToList(); db.Posts.Add(firstPostUntracked); - var status = db.SaveChangesWithChecking(); + db.SaveChanges(); //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); snap.CheckSnapShot(db,1,2); var loadedPost = db.Posts.Single(x => x.PostId == firstPostUntracked.PostId); - Assert.GreaterOrEqual(loadedPost.LastUpdated.Subtract(originalDateTime).Milliseconds, 400); + ClassicAssert.GreaterOrEqual(loadedPost.LastUpdated.Subtract(originalDateTime).TotalMilliseconds, 400); } } [Test] public void Check31CheckCreateDataOk() { - using (var db = new SampleWebAppDb()) + using (var db = TestDbContext.CreateContext(_connection)) { //SETUP var snap = new DbSnapShot(db); var firstPostUntracked = db.Posts.AsNoTracking().First(); + var firstTwoTags = db.Tags.Take(2).ToList(); //ATTEMPT + firstPostUntracked.PostId = 0; firstPostUntracked.Title = Guid.NewGuid().ToString(); firstPostUntracked.Blogger = db.Blogs.First(); - firstPostUntracked.Tags = db.Tags.Take(2).ToList(); + firstPostUntracked.Tags = firstTwoTags; db.Posts.Add(firstPostUntracked); - var status = db.SaveChangesWithChecking(); + db.SaveChanges(); //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); snap.CheckSnapShot(db,1,2); var loadedPost = db.Posts.Include( x => x.Blogger).Include( x => x.Tags).Single(x => x.PostId == firstPostUntracked.PostId); loadedPost.Blogger.BlogId.ShouldEqual(db.Blogs.First().BlogId); - CollectionAssert.AreEquivalent(db.Tags.Take(2).Select(x => x.TagId), loadedPost.Tags.Select(x => x.TagId)); + CollectionAssert.AreEquivalent(firstTwoTags.Select(x => x.TagId), loadedPost.Tags.Select(x => x.TagId)); } } } -} \ No newline at end of file +} diff --git a/Tests/UnitTests/Group03ServiceLayer/Test10DiSimple.cs b/Tests/UnitTests/Group03ServiceLayer/Test10DiSimple.cs index b502ff5..b15d47e 100644 --- a/Tests/UnitTests/Group03ServiceLayer/Test10DiSimple.cs +++ b/Tests/UnitTests/Group03ServiceLayer/Test10DiSimple.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: Test10DiSimple.cs @@ -25,136 +25,129 @@ // SOFTWARE. #endregion using System; -using System.Linq; -using System.Reflection; -using Autofac; -using Autofac.Core; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using NUnit.Framework.Legacy; using Tests.DependencyItems; using Tests.Helpers; namespace Tests.UnitTests.Group03ServiceLayer { - class Test10DiSimple + /// + /// The application swapped Autofac for the built-in Microsoft.Extensions.DependencyInjection container. + /// These tests assert the same lifetime/registration semantics (transient, singleton, scoped, constructor + /// parameters, disposal, open generics, non-resolvable private constructors) against that container. + /// + public class Test10DiSimple { [Test] - public void Test01AutoFacSimple() + public void Test01SimpleResolveOk() { //SETUP - var builder = new ContainerBuilder(); - builder.RegisterType().As(); - var container = builder.Build(); + var services = new ServiceCollection(); + services.AddTransient(); + var provider = services.BuildServiceProvider(); //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) + using (var scope = provider.CreateScope()) { - var instance = lifetimeScope.Resolve(); - Assert.NotNull(instance); + var instance = scope.ServiceProvider.GetService(); + ClassicAssert.NotNull(instance); (instance is SimpleClass).ShouldEqual(true); } - } [Test] - public void Test02AutoFacTransient() + public void Test02TransientGivesDifferentInstances() { - //Setup - var builder = new ContainerBuilder(); - builder.RegisterType().As(); - var container = builder.Build(); + //SETUP + var services = new ServiceCollection(); + services.AddTransient(); + var provider = services.BuildServiceProvider(); - //Attempt + //ATTEMPT ISimpleClass instance1; - using (var lifetimeScope = container.BeginLifetimeScope()) - instance1 = lifetimeScope.Resolve(); + using (var scope = provider.CreateScope()) + instance1 = scope.ServiceProvider.GetService(); ISimpleClass instance2; - using (var lifetimeScope = container.BeginLifetimeScope()) - instance2 = lifetimeScope.Resolve(); - - //Verify - Assert.NotNull(instance1); - Assert.NotNull(instance2); - Assert.AreNotSame(instance1, instance2); + using (var scope = provider.CreateScope()) + instance2 = scope.ServiceProvider.GetService(); + //VERIFY + ClassicAssert.NotNull(instance1); + ClassicAssert.NotNull(instance2); + ClassicAssert.AreNotSame(instance1, instance2); } [Test] - public void Test03AutoFacSingle() + public void Test03SingletonGivesSameInstance() { - //Setup - var builder = new ContainerBuilder(); - builder.RegisterType().As().SingleInstance(); - var container = builder.Build(); + //SETUP + var services = new ServiceCollection(); + services.AddSingleton(); + var provider = services.BuildServiceProvider(); - //Attempt + //ATTEMPT ISimpleClass instance1; - using (var lifetimeScope = container.BeginLifetimeScope()) - instance1 = lifetimeScope.Resolve(); + using (var scope = provider.CreateScope()) + instance1 = scope.ServiceProvider.GetService(); ISimpleClass instance2; - using (var lifetimeScope = container.BeginLifetimeScope()) - instance2 = lifetimeScope.Resolve(); - - //Verify - Assert.NotNull(instance1); - Assert.NotNull(instance2); - Assert.AreSame(instance1, instance2); + using (var scope = provider.CreateScope()) + instance2 = scope.ServiceProvider.GetService(); + //VERIFY + ClassicAssert.NotNull(instance1); + ClassicAssert.NotNull(instance2); + ClassicAssert.AreSame(instance1, instance2); } [Test] - public void Test04AutoFacLifeTimeScope() + public void Test04ScopedGivesSameInstanceWithinScopeButDifferentAcrossScopes() { - //Setup - var builder = new ContainerBuilder(); - builder.RegisterType().As().InstancePerLifetimeScope(); - var container = builder.Build(); + //SETUP + var services = new ServiceCollection(); + services.AddScoped(); + var provider = services.BuildServiceProvider(); - //Attempt and VERIFY + //ATTEMPT and VERIFY ISimpleClass scope1Instance1; - ISimpleClass scope1Instance2; - using (var lifetimeScope = container.BeginLifetimeScope()) + using (var scope = provider.CreateScope()) { - scope1Instance1 = lifetimeScope.Resolve(); - scope1Instance2 = lifetimeScope.Resolve(); - Assert.NotNull(scope1Instance1); - Assert.NotNull(scope1Instance2); - Assert.AreSame(scope1Instance1, scope1Instance2); + scope1Instance1 = scope.ServiceProvider.GetService(); + var scope1Instance2 = scope.ServiceProvider.GetService(); + ClassicAssert.NotNull(scope1Instance1); + ClassicAssert.NotNull(scope1Instance2); + ClassicAssert.AreSame(scope1Instance1, scope1Instance2); } - using (var lifetimeScope = container.BeginLifetimeScope()) + using (var scope = provider.CreateScope()) { - ISimpleClass scope2Instance1 = lifetimeScope.Resolve(); - Assert.NotNull(scope2Instance1); - Assert.NotNull(scope1Instance1); - Assert.NotNull(scope1Instance2); - Assert.AreNotSame(scope1Instance1, scope2Instance1); - Assert.AreNotSame(scope1Instance1, scope2Instance1); + var scope2Instance1 = scope.ServiceProvider.GetService(); + ClassicAssert.NotNull(scope2Instance1); + ClassicAssert.AreNotSame(scope1Instance1, scope2Instance1); } - } //----------------------------------------------------------- //item with constructor param [Test] - public void Test05AutoFacConstructor() + public void Test05ConstructorParameterOk() { //SETUP - var builder = new ContainerBuilder(); - builder.RegisterType().As() - .WithParameter("myInt", 42); - var container = builder.Build(); + var services = new ServiceCollection(); + services.AddTransient(_ => new ConstructorParamClass(42)); + var provider = services.BuildServiceProvider(); //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) + using (var scope = provider.CreateScope()) { - var instance = lifetimeScope.Resolve(); - Assert.NotNull(instance); + var instance = scope.ServiceProvider.GetService(); + ClassicAssert.NotNull(instance); instance.MyInt.ShouldEqual(42); } - } @@ -164,133 +157,124 @@ public void Test05AutoFacConstructor() private int _numTimeDisposeCalled; [Test] - public void Test15AutoFacDisposeCreate() + public void Test15DisposeNotCalledWhileScopeAlive() { - //Setup - var builder = new ContainerBuilder(); + //SETUP + _numTimeDisposeCalled = 0; Action checker = (() => _numTimeDisposeCalled++); - builder.RegisterType().As().WithParameter("disposeWasCalled", checker); - var container = builder.Build(); + var services = new ServiceCollection(); + services.AddTransient(_ => new MyDisposableClass(checker)); + var provider = services.BuildServiceProvider(); - //Attempt - _numTimeDisposeCalled = 0; - var mydisp = container.Resolve(); + //ATTEMPT + var scope = provider.CreateScope(); + var mydisp = scope.ServiceProvider.GetService(); - //Verify - Assert.NotNull(mydisp); + //VERIFY + ClassicAssert.NotNull(mydisp); _numTimeDisposeCalled.ShouldEqual(0); - } [Test] - public void Test16AutoFacDisposeCalled() + public void Test16DisposeCalledWhenScopeDisposed() { - //Setup - var builder = new ContainerBuilder(); + //SETUP + _numTimeDisposeCalled = 0; Action checker = (() => _numTimeDisposeCalled++); - builder.RegisterType().As().WithParameter("disposeWasCalled", checker); - var container = builder.Build(); + var services = new ServiceCollection(); + services.AddTransient(_ => new MyDisposableClass(checker)); + var provider = services.BuildServiceProvider(); - //Attempt - _numTimeDisposeCalled = 0; - using (var lifetimeScope = container.BeginLifetimeScope()) + //ATTEMPT + using (var scope = provider.CreateScope()) { - var mydisp = lifetimeScope.Resolve(); - Assert.NotNull(mydisp); + var mydisp = scope.ServiceProvider.GetService(); + ClassicAssert.NotNull(mydisp); } - //Verify + //VERIFY _numTimeDisposeCalled.ShouldEqual(1); - } //-------------------------------------------------------------- //register generic [Test] - public void Test20AutoFacRegisterGeneric() + public void Test20RegisterOpenGenericOk() { //SETUP - var builder = new ContainerBuilder(); - builder.RegisterGeneric(typeof(GenericInterface<>)).As(typeof(IGenericInterface<>)); - var container = builder.Build(); + var services = new ServiceCollection(); + services.AddTransient(typeof(IGenericInterface<>), typeof(GenericInterface<>)); + var provider = services.BuildServiceProvider(); //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) + using (var scope = provider.CreateScope()) { - var instance = lifetimeScope.Resolve>(); - Assert.NotNull(instance); + var instance = scope.ServiceProvider.GetService>(); + ClassicAssert.NotNull(instance); (instance is GenericInterface).ShouldEqual(true); instance.GetTypeName().ShouldEqual(typeof(SimpleClass).Name); } - } [Test] - public void Test21AutoFacRegisterGenericAfterRegisterAssembly() + public void Test21RegisterOpenGenericAlongsideOtherServicesOk() { //SETUP - var builder = new ContainerBuilder(); - builder.RegisterAssemblyTypes(GetType().Assembly).AsImplementedInterfaces(); - builder.RegisterGeneric(typeof(GenericInterface<>)).As(typeof(IGenericInterface<>)); - var container = builder.Build(); + var services = new ServiceCollection(); + services.AddTransient(); + services.AddTransient(_ => new ConstructorParamClass(1)); + services.AddTransient(typeof(IGenericInterface<>), typeof(GenericInterface<>)); + var provider = services.BuildServiceProvider(); //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) + using (var scope = provider.CreateScope()) { - var instance = lifetimeScope.Resolve>(); - Assert.NotNull(instance); + var instance = scope.ServiceProvider.GetService>(); + ClassicAssert.NotNull(instance); (instance is GenericInterface).ShouldEqual(true); instance.GetTypeName().ShouldEqual(typeof(SimpleClass).Name); } - } //--------------------------------------------------------- //tests on what happens if ctor is private [Test] - public void Test30AutoFacRegisterClassWithPrivateCtorBad() + public void Test30ResolveClassWithPrivateCtorBad() { //SETUP - var builder = new ContainerBuilder(); - builder.RegisterType().As(); - var container = builder.Build(); + var services = new ServiceCollection(); + services.AddTransient(); + var provider = services.BuildServiceProvider(); //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) + using (var scope = provider.CreateScope()) { - var ex = Assert.Throws(() => lifetimeScope.Resolve()); - ex.Message.ShouldStartWith("No constructors on type"); + //The built-in container can only activate a type through a public constructor. + var ex = Assert.Throws(() => scope.ServiceProvider.GetService()); + ex.Message.ShouldContain("ClassWithPrivateCtor"); } - } [Test] - public void Test31AutoFacTryCtorWithPrivateCtorAsOptionOk() + public void Test31ResolveOpenGenericWithPublicCtorOk() { + //The old Autofac version used an OnActivating hook to inject the resolved type into a class that + //itself only had a private-ctor dependency. The built-in container has no activation hook, so this + //test now just verifies the open-generic type (which has a public constructor) resolves correctly. //SETUP - var builder = new ContainerBuilder(); - builder.RegisterType().As(); - builder.RegisterGeneric(typeof (ClassToTestClassWithPrivateCtor<>)) - .As(typeof (IClassToTestClassWithPrivateCtor<>)) - .OnActivating(e => - { - var interfaceToLookup = e.Instance.GetType().GetGenericArguments()[0]; - var resolvedInterface = - e.Context.ComponentRegistry.RegistrationsFor(new TypedService(interfaceToLookup)).SingleOrDefault(); - ((ISetType)e.Instance).SetType(resolvedInterface.Activator.LimitType); - }); - var container = builder.Build(); + var services = new ServiceCollection(); + services.AddTransient(typeof(IClassToTestClassWithPrivateCtor<>), typeof(ClassToTestClassWithPrivateCtor<>)); + var provider = services.BuildServiceProvider(); //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) + using (var scope = provider.CreateScope()) { - var instance = lifetimeScope.Resolve>(); - Assert.NotNull(instance); + var instance = scope.ServiceProvider.GetService>(); + ClassicAssert.NotNull(instance); (instance is ClassToTestClassWithPrivateCtor).ShouldEqual(true); } - } } } diff --git a/Tests/UnitTests/Group03ServiceLayer/Test11AutoFacModules.cs b/Tests/UnitTests/Group03ServiceLayer/Test11AutoFacModules.cs deleted file mode 100644 index 3d4ff00..0000000 --- a/Tests/UnitTests/Group03ServiceLayer/Test11AutoFacModules.cs +++ /dev/null @@ -1,167 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: Test11AutoFacModules.cs -// Date Created: 2014/05/22 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System.Linq; -using Autofac; -using DataLayer.DataClasses; -using DataLayer.DataClasses.Concrete; -using DataLayer.Startup; -using GenericServices; -using GenericServices.Services.Concrete; -using NUnit.Framework; -using SampleWebApp.Infrastructure; -using ServiceLayer.Startup; -using Tests.Helpers; - -namespace Tests.UnitTests.Group03ServiceLayer -{ - [TestFixture] - public class Test11AutoFacModules - { - - [TestFixtureSetUp] - public void FixtureSetUp() - { - using (var db = new SampleWebAppDb()) - { - DataLayerInitialise.InitialiseThis(false, true); - DataLayerInitialise.ResetBlogs(db, TestDataSelection.Small); - } - } - - - //------------------------------------- - //DataLayer - - [Test] - public void CheckSetupDbContextLifetimeScopeItems() - { - //SETUP - var builder = new ContainerBuilder(); - builder.RegisterModule( new DataLayerModule()); - var container = builder.Build(); - - //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) - { - var instance1 = lifetimeScope.Resolve(); - var instance2 = lifetimeScope.Resolve(); - Assert.NotNull(instance1); - (instance1 is SampleWebAppDb).ShouldEqual(true); - Assert.AreSame(instance1, instance2); //check that lifetimescope is working - } - } - - - //--------------------------------------------- - //ServiceLayer, which also resolves DataLayer - - [Test] - public void Test10ServiceSetupServiceLayer() - { - //SETUP - var builder = new ContainerBuilder(); - builder.RegisterModule(new ServiceLayerModule()); - var container = builder.Build(); - - //ATTEMPT & VERIFY - CheckExampleServicesResolve(container); - } - - - [Test] - public void Test15SetupServiceLayerDirectGenerics() - { - //SETUP - var builder = new ContainerBuilder(); - builder.RegisterModule(new ServiceLayerModule()); - var container = builder.Build(); - - //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) - { - var instance = lifetimeScope.Resolve(); - Assert.NotNull(instance); - (instance is ListService).ShouldEqual(true); - } - } - - [Test] - public void Test16UseServiceLayerDirectGenerics() - { - //SETUP - var builder = new ContainerBuilder(); - builder.RegisterModule(new ServiceLayerModule()); - var container = builder.Build(); - - //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) - { - var service = lifetimeScope.Resolve(); - var posts = service.GetAll().ToList(); - posts.Count.ShouldEqual(3); - } - } - - //------------------------------------------------------ - //MVC layer - - [Test] - public void Test20ViaMvcSetup() - { - //SETUP - var container = AutofacDi.SetupDependency(); - - //ATTEMPT & VERIFY - CheckExampleServicesResolve(container); - - } - - //------------------------------------------------------- - //private helper - - private static void CheckExampleServicesResolve(IContainer container) - { - using (var lifetimeScope = container.BeginLifetimeScope()) - { - //DataLayer - Data classes - - //DataLayer - repositories - var db1 = lifetimeScope.Resolve(); - var db2 = lifetimeScope.Resolve(); - Assert.NotNull(db1); - Assert.AreSame(db1, db2); //check that lifetimescope is working - - //ServiceLayer - complex - var service1 = lifetimeScope.Resolve(); - var service2 = lifetimeScope.Resolve(); - Assert.NotNull(service1); - Assert.AreNotSame(service1, service2); //check transient - (service1 is ListService).ShouldEqual(true); - } - } - } -} diff --git a/Tests/UnitTests/Group03ServiceLayer/Test11DiExtensions.cs b/Tests/UnitTests/Group03ServiceLayer/Test11DiExtensions.cs new file mode 100644 index 0000000..f3cf652 --- /dev/null +++ b/Tests/UnitTests/Group03ServiceLayer/Test11DiExtensions.cs @@ -0,0 +1,137 @@ +#region licence +// The MIT License (MIT) +// +// Filename: Test11AutoFacModules.cs +// Date Created: 2014/05/22 +// +// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#endregion +using System; +using System.Linq; +using BizLayer.Startup; +using DataLayer.DataClasses; +using DataLayer.Startup; +using GenericServices; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using ServiceLayer.PostServices; +using ServiceLayer.Startup; +using Tests.Helpers; + +namespace Tests.UnitTests.Group03ServiceLayer +{ + /// + /// The Autofac modules (DataLayerModule/ServiceLayerModule/BizLayerModule) were replaced by the + /// IServiceCollection extension methods AddServiceLayer()/AddBizLayer(). This fixture builds a + /// ServiceCollection the same way the web app's Program.cs does and asserts the service layer resolves + /// and behaves (ICrudServices/IPostCrudHelper resolve, the DbContext is scoped, and a read works). + /// + [TestFixture] + public class Test11DiExtensions + { + private SqliteConnection _connection; + private ServiceProvider _provider; + + [OneTimeSetUp] + public void FixtureSetUp() + { + _connection = TestDbContext.CreateOpenConnection(); + + var services = new ServiceCollection(); + services.AddDbContext(options => options.UseSqlite(_connection)); + services.AddServiceLayer(); + services.AddBizLayer(); + _provider = services.BuildServiceProvider(); + + using (var scope = _provider.CreateScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + DataLayerInitialise.ResetBlogs(db, TestDataSelection.Small); + } + } + + [OneTimeTearDown] + public void FixtureTearDown() + { + _provider?.Dispose(); + _connection?.Dispose(); + } + + //------------------------------------- + //DataLayer - the DbContext must be scoped + + [Test] + public void CheckDbContextIsScoped() + { + //SETUP & ATTEMPT & VERIFY + SampleWebAppDb scope1Instance; + using (var scope = _provider.CreateScope()) + { + scope1Instance = scope.ServiceProvider.GetRequiredService(); + var sameScopeInstance = scope.ServiceProvider.GetRequiredService(); + ClassicAssert.NotNull(scope1Instance); + ClassicAssert.AreSame(scope1Instance, sameScopeInstance); //same instance within a scope + } + + using (var scope = _provider.CreateScope()) + { + var scope2Instance = scope.ServiceProvider.GetRequiredService(); + ClassicAssert.AreNotSame(scope1Instance, scope2Instance); //different instance across scopes + } + } + + //--------------------------------------------- + //ServiceLayer, which also resolves DataLayer + + [Test] + public void Test10ServiceLayerServicesResolve() + { + //SETUP & ATTEMPT & VERIFY + using (var scope = _provider.CreateScope()) + { + var crudServices = scope.ServiceProvider.GetService(); + var crudServicesAsync = scope.ServiceProvider.GetService(); + var postCrudHelper = scope.ServiceProvider.GetService(); + + ClassicAssert.NotNull(crudServices); + ClassicAssert.NotNull(crudServicesAsync); + ClassicAssert.NotNull(postCrudHelper); + } + } + + [Test] + public void Test16UseCrudServicesReadPosts() + { + //SETUP & ATTEMPT + using (var scope = _provider.CreateScope()) + { + var service = scope.ServiceProvider.GetRequiredService(); + var posts = service.ReadManyNoTracked().ToList(); + + //VERIFY + posts.Count.ShouldEqual(3); + } + } + } +} diff --git a/Tests/UnitTests/Group06Mvc/Test02Validation.cs b/Tests/UnitTests/Group06Mvc/Test02Validation.cs index 7ef1d18..5037ef2 100644 --- a/Tests/UnitTests/Group06Mvc/Test02Validation.cs +++ b/Tests/UnitTests/Group06Mvc/Test02Validation.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: Test02Validation.cs @@ -24,17 +24,14 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #endregion -using System; using System.Linq; -using GenericLibsBase.Core; -using GenericServices.Core; using NUnit.Framework; -using SampleWebApp.Infrastructure; +using NUnit.Framework.Legacy; using Tests.Helpers; namespace Tests.UnitTests.Group06Mvc { - class Test02Validation + public class Test02Validation { [Test] @@ -66,7 +63,7 @@ public void Check05TestModelStateValidateOnly() //VERIFY modelState.IsValid.ShouldEqual(false); - modelState.Keys.Count.ShouldEqual(1); + modelState.Keys.Count().ShouldEqual(1); modelState.Keys.First().ShouldEqual(""); modelState[modelState.Keys.First()].Errors.Count.ShouldEqual(2); modelState[modelState.Keys.First()].Errors[0].ErrorMessage.ShouldEqual("This is a top level error caused by CreateValidationError being set."); @@ -84,7 +81,7 @@ public void Check06TestModelStateValidateOneOnly() //VERIFY modelState.IsValid.ShouldEqual(false); - modelState.Keys.Count.ShouldEqual(1); + modelState.Keys.Count().ShouldEqual(1); modelState.Keys.First().ShouldEqual(""); modelState[modelState.Keys.First()].Errors.Count.ShouldEqual(1); modelState[modelState.Keys.First()].Errors[0].ErrorMessage.ShouldEqual("This is a top level error caused by CreateValidationError being set."); @@ -101,7 +98,7 @@ public void Check06TestModelStateIntAttributeOnly() //VERIFY modelState.IsValid.ShouldEqual(false); - modelState.Keys.Count.ShouldEqual(1); + modelState.Keys.Count().ShouldEqual(1); modelState.Keys.First().ShouldEqual("MyInt"); modelState[modelState.Keys.First()].Errors.Count.ShouldEqual(1); modelState[modelState.Keys.First()].Errors[0].ErrorMessage.ShouldEqual("The field MyInt must be between 0 and 100."); @@ -119,7 +116,7 @@ public void Check07TestModelStateStringAttributeOnly() //VERIFY modelState.IsValid.ShouldEqual(false); - modelState.Keys.Count.ShouldEqual(1); + modelState.Keys.Count().ShouldEqual(1); modelState.Keys.First().ShouldEqual("MyString"); CollectionAssert.AreEquivalent(new[] { @@ -148,126 +145,9 @@ public void Check08TestModelStateMixedErrorsOnly() } //--------------------------------------------------------------------- - //now use to check ValidationHelper ReturnModelErrorsAsJson - - [Test] - public void Check15TestModelStateValidateOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("123", 50, true); - - //ATTEMPT - var jsonResult = model.ReturnModelState().ReturnModelErrorsAsJson(); - - //VERIFY - var json = jsonResult.Data.SerialiseToJson(); - json.ShouldEqual("{\"errorsDict\":{\"\":{\"errors\":[\"This is a top level error caused by CreateValidationError being set.\",\"This is a top level error caused by MyInt having value 50.\"]}}}"); - } - - [Test] - public void Check16TestModelStateValidateOneOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("123", 2, true); - - //ATTEMPT - var jsonResult = model.ReturnModelState().ReturnModelErrorsAsJson(); - - //VERIFY - var json = jsonResult.Data.SerialiseToJson(); - json.ShouldEqual("{\"errorsDict\":{\"\":{\"errors\":[\"This is a top level error caused by CreateValidationError being set.\"]}}}"); - } - - - [Test] - public void Check16TestModelStateIntAttributeOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("123", -1, false); - - //ATTEMPT - var jsonResult = model.ReturnModelState().ReturnModelErrorsAsJson(); - - //VERIFY - var json = jsonResult.Data.SerialiseToJson(); - json.ShouldEqual("{\"errorsDict\":{\"MyInt\":{\"errors\":[\"The field MyInt must be between 0 and 100.\"]}}}"); - } - - [Test] - public void Check17TestModelStateStringAttributeOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("", 2, false); - - //ATTEMPT - var jsonResult = model.ReturnModelState().ReturnModelErrorsAsJson(); - - //VERIFY - var json = jsonResult.Data.SerialiseToJson(); - const string order1 = "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The field MyString must be a string or array type with a minimum length of '2'.\",\"The MyString field is required.\"]}}}"; - const string order1Json = "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The field MyString must be a string or array type with a minimum length of \\u00272\\u0027.\",\"The MyString field is required.\"]}}}"; - - const string order2 = "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The MyString field is required.\",\"The field MyString must be a string or array type with a minimum length of '2'.\"]}}}"; - const string order2Json = - "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The MyString field is required.\",\"The field MyString must be a string or array type with a minimum length of \\u00272\\u0027.\"]}}}"; - (json == order1Json || json == order2Json).ShouldEqual(true); - - } - - [Test] - public void Check18TestModelStateMixedErrorsOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("", -1, true); - - //ATTEMPT - var jsonResult = model.ReturnModelState().ReturnModelErrorsAsJson(); - - //VERIFY - var json = jsonResult.Data.SerialiseToJson(); - const string order1 = "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The field MyString must be a string or array type with a minimum length of '2'.\",\"The MyString field is required.\"]},"; - const string order1Json = "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The field MyString must be a string or array type with a minimum length of \\u00272\\u0027.\",\"The MyString field is required.\"]},"; - const string order2 = "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The MyString field is required.\",\"The field MyString must be a string or array type with a minimum length of '2'.\"]},"; - const string order2Json = - "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The MyString field is required.\",\"The field MyString must be a string or array type with a minimum length of \\u00272\\u0027.\"]},"; - const string part2 = "\"MyInt\":{\"errors\":[\"The field MyInt must be between 0 and 100.\"]}}}"; - (json == order1Json + part2 || json == order2Json + part2).ShouldEqual(true); - } - - //------------------------------------------------------------------- - //now the ReturnErrorsAsJson - - [Test] - public void Check20StatusToJsonTopLevel() - { - //SETUP - var status = new SuccessOrErrors(); - var dto = new {MyInt = 1}; - - //ATTEMPT - status.AddSingleError("This is a top level error."); - var jsonResult = status.ReturnErrorsAsJson(dto); - - //VERIFY - var json = jsonResult.Data.SerialiseToJson(); - json.ShouldEqual("{\"errorsDict\":{\"\":{\"errors\":[\"This is a top level error.\"]}}}"); - } - - [Test] - public void Check21StatusToJsonProperty() - { - //SETUP - var status = new SuccessOrErrors(); - var dto = new { MyInt = 1 }; - - //ATTEMPT - status.AddNamedParameterError("MyInt", "This is a property level error."); - var jsonResult = status.ReturnErrorsAsJson(dto); - - //VERIFY - var json = jsonResult.Data.SerialiseToJson(); - json.ShouldEqual("{\"errorsDict\":{\"MyInt\":{\"errors\":[\"This is a property level error.\"]}}}"); - } - + //The old Check15-Check21 tests exercised SampleWebApp.Infrastructure.ValidationHelper + //(ReturnModelErrorsAsJson / JsonNetResult) and the EF6 GenericServices SuccessOrErrors type. + //Both of those types were deleted in the ASP.NET Core / EfCore.GenericServices migration, so the + //JSON-serialisation tests no longer have anything to test and have been removed. } } diff --git a/Tests/packages.config b/Tests/packages.config deleted file mode 100644 index 1638b50..0000000 --- a/Tests/packages.config +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From bca074f486326b7f19168c8b15bb7ef7be45670b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 04:51:15 +0000 Subject: [PATCH 07/11] feature: add BizLayer to solution and finalize .NET 10 migration merge Co-Authored-By: Parker Duff --- SampleWebApp.sln | 98 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/SampleWebApp.sln b/SampleWebApp.sln index b474189..1529f32 100644 --- a/SampleWebApp.sln +++ b/SampleWebApp.sln @@ -17,45 +17,143 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizLayer", "BizLayer\BizLayer.csproj", "{BD21703D-2A7B-4229-86D5-56FA4585D309}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AzureRelease|Any CPU = AzureRelease|Any CPU + AzureRelease|x64 = AzureRelease|x64 + AzureRelease|x86 = AzureRelease|x86 Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 WebWizRelease|Any CPU = WebWizRelease|Any CPU + WebWizRelease|x64 = WebWizRelease|x64 + WebWizRelease|x86 = WebWizRelease|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.AzureRelease|Any CPU.ActiveCfg = AzureRelease|Any CPU {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.AzureRelease|Any CPU.Build.0 = AzureRelease|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.AzureRelease|x64.ActiveCfg = AzureRelease|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.AzureRelease|x64.Build.0 = AzureRelease|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.AzureRelease|x86.ActiveCfg = AzureRelease|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.AzureRelease|x86.Build.0 = AzureRelease|Any CPU {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Debug|x64.ActiveCfg = Debug|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Debug|x64.Build.0 = Debug|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Debug|x86.ActiveCfg = Debug|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Debug|x86.Build.0 = Debug|Any CPU {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Release|Any CPU.Build.0 = Release|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Release|x64.ActiveCfg = Release|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Release|x64.Build.0 = Release|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Release|x86.ActiveCfg = Release|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Release|x86.Build.0 = Release|Any CPU {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.WebWizRelease|Any CPU.ActiveCfg = WebWizRelease|Any CPU {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.WebWizRelease|Any CPU.Build.0 = WebWizRelease|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.WebWizRelease|x64.ActiveCfg = WebWizRelease|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.WebWizRelease|x64.Build.0 = WebWizRelease|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.WebWizRelease|x86.ActiveCfg = WebWizRelease|Any CPU + {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.WebWizRelease|x86.Build.0 = WebWizRelease|Any CPU {264E1878-12DE-4099-B8D7-CC53A73FEA49}.AzureRelease|Any CPU.ActiveCfg = AzureRelease|Any CPU {264E1878-12DE-4099-B8D7-CC53A73FEA49}.AzureRelease|Any CPU.Build.0 = AzureRelease|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.AzureRelease|x64.ActiveCfg = AzureRelease|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.AzureRelease|x64.Build.0 = AzureRelease|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.AzureRelease|x86.ActiveCfg = AzureRelease|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.AzureRelease|x86.Build.0 = AzureRelease|Any CPU {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Debug|x64.ActiveCfg = Debug|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Debug|x64.Build.0 = Debug|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Debug|x86.ActiveCfg = Debug|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Debug|x86.Build.0 = Debug|Any CPU {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Release|Any CPU.ActiveCfg = Release|Any CPU {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Release|Any CPU.Build.0 = Release|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Release|x64.ActiveCfg = Release|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Release|x64.Build.0 = Release|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Release|x86.ActiveCfg = Release|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Release|x86.Build.0 = Release|Any CPU {264E1878-12DE-4099-B8D7-CC53A73FEA49}.WebWizRelease|Any CPU.ActiveCfg = WebWizRelease|Any CPU {264E1878-12DE-4099-B8D7-CC53A73FEA49}.WebWizRelease|Any CPU.Build.0 = WebWizRelease|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.WebWizRelease|x64.ActiveCfg = WebWizRelease|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.WebWizRelease|x64.Build.0 = WebWizRelease|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.WebWizRelease|x86.ActiveCfg = WebWizRelease|Any CPU + {264E1878-12DE-4099-B8D7-CC53A73FEA49}.WebWizRelease|x86.Build.0 = WebWizRelease|Any CPU {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.AzureRelease|Any CPU.ActiveCfg = AzureRelease|Any CPU {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.AzureRelease|Any CPU.Build.0 = AzureRelease|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.AzureRelease|x64.ActiveCfg = AzureRelease|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.AzureRelease|x64.Build.0 = AzureRelease|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.AzureRelease|x86.ActiveCfg = AzureRelease|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.AzureRelease|x86.Build.0 = AzureRelease|Any CPU {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Debug|x64.ActiveCfg = Debug|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Debug|x64.Build.0 = Debug|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Debug|x86.ActiveCfg = Debug|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Debug|x86.Build.0 = Debug|Any CPU {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Release|Any CPU.Build.0 = Release|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Release|x64.ActiveCfg = Release|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Release|x64.Build.0 = Release|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Release|x86.ActiveCfg = Release|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Release|x86.Build.0 = Release|Any CPU {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.WebWizRelease|Any CPU.ActiveCfg = WebWizRelease|Any CPU {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.WebWizRelease|Any CPU.Build.0 = WebWizRelease|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.WebWizRelease|x64.ActiveCfg = WebWizRelease|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.WebWizRelease|x64.Build.0 = WebWizRelease|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.WebWizRelease|x86.ActiveCfg = WebWizRelease|Any CPU + {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.WebWizRelease|x86.Build.0 = WebWizRelease|Any CPU {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.AzureRelease|Any CPU.ActiveCfg = AzureRelease|Any CPU {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.AzureRelease|Any CPU.Build.0 = AzureRelease|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.AzureRelease|x64.ActiveCfg = AzureRelease|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.AzureRelease|x64.Build.0 = AzureRelease|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.AzureRelease|x86.ActiveCfg = AzureRelease|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.AzureRelease|x86.Build.0 = AzureRelease|Any CPU {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Debug|x64.ActiveCfg = Debug|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Debug|x64.Build.0 = Debug|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Debug|x86.ActiveCfg = Debug|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Debug|x86.Build.0 = Debug|Any CPU {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Release|Any CPU.Build.0 = Release|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Release|x64.ActiveCfg = Release|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Release|x64.Build.0 = Release|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Release|x86.ActiveCfg = Release|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Release|x86.Build.0 = Release|Any CPU {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.WebWizRelease|Any CPU.ActiveCfg = WebWizRelease|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.WebWizRelease|x64.ActiveCfg = WebWizRelease|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.WebWizRelease|x64.Build.0 = WebWizRelease|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.WebWizRelease|x86.ActiveCfg = WebWizRelease|Any CPU + {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.WebWizRelease|x86.Build.0 = WebWizRelease|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.AzureRelease|Any CPU.ActiveCfg = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.AzureRelease|Any CPU.Build.0 = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.AzureRelease|x64.ActiveCfg = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.AzureRelease|x64.Build.0 = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.AzureRelease|x86.ActiveCfg = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.AzureRelease|x86.Build.0 = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.Debug|x64.ActiveCfg = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.Debug|x64.Build.0 = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.Debug|x86.ActiveCfg = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.Debug|x86.Build.0 = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.Release|Any CPU.Build.0 = Release|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.Release|x64.ActiveCfg = Release|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.Release|x64.Build.0 = Release|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.Release|x86.ActiveCfg = Release|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.Release|x86.Build.0 = Release|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.WebWizRelease|Any CPU.ActiveCfg = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.WebWizRelease|Any CPU.Build.0 = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.WebWizRelease|x64.ActiveCfg = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.WebWizRelease|x64.Build.0 = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.WebWizRelease|x86.ActiveCfg = Debug|Any CPU + {BD21703D-2A7B-4229-86D5-56FA4585D309}.WebWizRelease|x86.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 2d2e7c9cdd31765de2251532ea3254659f211e22 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 04:51:42 +0000 Subject: [PATCH 08/11] feature: document migration outcome and known warnings in MIGRATION_NOTES Co-Authored-By: Parker Duff --- MIGRATION_NOTES.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/MIGRATION_NOTES.md b/MIGRATION_NOTES.md index b10f9c3..9a5cfeb 100644 --- a/MIGRATION_NOTES.md +++ b/MIGRATION_NOTES.md @@ -349,3 +349,43 @@ After merge: `dotnet ef migrations add InitialCreate` for `SampleWebAppDb`, upda (list, details, create, edit, delete) end-to-end through the `EfCore.GenericServices` path; confirm persistence. - No SignalR behavior to verify (feature already removed, §5). - Record a screen video of the CRUD flows as proof and reference the artifact here / in the final summary. + +--- + +## 13. Migration outcome (what was actually done) + +Executed across three parallel child sessions off this branch, then merged and finalized here: + +- **A — Data/Service/Biz** (PR #24): SDK-style `net10.0`; EF Core 10; `SampleWebAppDb(DbContextOptions<>)`; + `HandleChangeTracking` ported into `SaveChanges`/`SaveChangesAsync` (the EF6 early-`return` bug fixed to + `continue`); `Tag.Slug` uniqueness = unique index in `OnModelCreating` **plus** a pre-save duplicate check; + `EfConfiguration`/initializers removed; DTOs re-based on `ILinkToEntity`; the old `SetupSecondaryData` + dropdown/multiselect lifecycle moved into a hand-written `IPostCrudHelper` (Post create/update run there, + not through `ICrudServices.CreateAndSave`, so the many-to-many Tag + blogger selection and `IValidatableObject` + rules are preserved); Autofac modules → `IServiceCollection` extensions (`AddDataLayer`/`AddServiceLayer`/`AddBizLayer`). +- **B — Web app** (PR #25): SDK-style `Microsoft.NET.Sdk.Web`; single `Program.cs` (minimal hosting, endpoint + routing, `AddControllersWithViews`, `AddDbContext(UseSqlServer)`); `Global.asax`/`App_Start`/OWIN/ + `DiModelBinder`/`WebUiInitialise`/`AutofacDi` all deleted; controllers on `Microsoft.AspNetCore.Mvc` with + `[FromServices]`; `MvcHtmlString`→`Html.Raw`; a hand-written `CopyErrorsToModelState(IStatusGeneric)` (the + `EfCore.GenericServices.AspNetCore` helper has no 10.x release); `Content`/`Scripts`/`fonts`→`wwwroot` with + plain ``/`