├── .gitignore ├── AWS ├── APIGatewayProxyFunction.cs ├── APIGatewayProxyFunction{TStartup}.cs ├── Internal │ ├── ApiGatewayServer.cs │ ├── InvokeFeatures.cs │ └── Utilities.cs ├── ResponseContentEncoding.cs └── WebHostBuilderExtensions.cs ├── AspNetNow.csproj ├── Controllers └── HomeController.cs ├── LambdaEntryPoint.cs ├── Models └── ErrorViewModel.cs ├── Program.cs ├── Startup.cs ├── Views ├── Home │ ├── About.cshtml │ ├── Contact.cshtml │ ├── Index.cshtml │ └── Privacy.cshtml ├── Shared │ ├── Error.cshtml │ ├── _CookieConsentPartial.cshtml │ ├── _Layout.cshtml │ └── _ValidationScriptsPartial.cshtml ├── _ViewImports.cshtml └── _ViewStart.cshtml ├── aws-lambda-tools-defaults.json ├── now.json ├── serverless.template └── wwwroot ├── css └── site.css ├── favicon.ico ├── images ├── banner1.svg ├── banner2.svg └── banner3.svg ├── js └── site.js ├── lib ├── bootstrap │ ├── .bower.json │ ├── LICENSE │ └── dist │ │ ├── css │ │ ├── bootstrap-theme.css │ │ └── bootstrap.css │ │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ ├── bootstrap.js │ │ └── npm.js ├── jquery-validation-unobtrusive │ ├── .bower.json │ ├── LICENSE.txt │ └── jquery.validate.unobtrusive.js ├── jquery-validation │ ├── .bower.json │ ├── LICENSE.md │ └── dist │ │ ├── additional-methods.js │ │ └── jquery.validate.js └── jquery │ ├── .bower.json │ ├── LICENSE.txt │ └── dist │ └── jquery.js └── robots.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # The 5 approved on boarding samples that need wwwroot/lib/ can add a local .gitignore that allows wwwroot/lib/. 2 | # with 2.1 templates and excluding .min & .map -> 27 files and < 1 KB 3 | # 3.0 templates should be even less 4 | # wwwroot/lib/ 5 | # When wwwroot/lib/ is commented out, excluse .min and .map files 6 | *.min.css 7 | *.min.js 8 | *.map 9 | 10 | _build/ 11 | _site/ 12 | Properties/ 13 | 14 | # See #577 15 | # Use git add -f to force override .sln when required. Not needed in most cases. 16 | # git add -f myProj.sln 17 | *.sln 18 | 19 | Project_Readme.html 20 | 21 | ## Ignore Visual Studio temporary files, build results, and 22 | ## files generated by popular Visual Studio add-ons. 23 | 24 | # User-specific files 25 | *.suo 26 | *.user 27 | *.userosscache 28 | *.sln.docstates 29 | .vscode/ 30 | !.vscode/extensions.json 31 | !.vscode/settings.json 32 | # User-specific files (MonoDevelop/Xamarin Studio) 33 | *.userprefs 34 | 35 | # Build results 36 | [Dd]ebug/ 37 | [Dd]ebugPublic/ 38 | [Rr]elease/ 39 | [Rr]eleases/ 40 | x64/ 41 | x86/ 42 | build/ 43 | bld/ 44 | [Bb]in/ 45 | [Oo]bj/ 46 | 47 | # Visual Studo 2015 cache/options directory 48 | .vs/ 49 | 50 | # MSTest test Results 51 | [Tt]est[Rr]esult*/ 52 | [Bb]uild[Ll]og.* 53 | 54 | # NUNIT 55 | *.VisualState.xml 56 | TestResult.xml 57 | 58 | # Build Results of an ATL Project 59 | [Dd]ebugPS/ 60 | [Rr]eleasePS/ 61 | dlldata.c 62 | 63 | *_i.c 64 | *_p.c 65 | *_i.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.pch 70 | *.pdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opensdf 96 | *.sdf 97 | *.cachefile 98 | 99 | # Visual Studio profiler 100 | *.psess 101 | *.vsp 102 | *.vspx 103 | 104 | # TFS 2012 Local Workspace 105 | $tf/ 106 | 107 | # Guidance Automation Toolkit 108 | *.gpState 109 | 110 | # ReSharper is a .NET coding add-in 111 | _ReSharper*/ 112 | *.[Rr]e[Ss]harper 113 | *.DotSettings.user 114 | 115 | # JustCode is a .NET coding addin-in 116 | .JustCode 117 | 118 | # TeamCity is a build add-in 119 | _TeamCity* 120 | 121 | # DotCover is a Code Coverage Tool 122 | *.dotCover 123 | 124 | # NCrunch 125 | _NCrunch_* 126 | .*crunch*.local.xml 127 | 128 | # MightyMoose 129 | *.mm.* 130 | AutoTest.Net/ 131 | 132 | # Web workbench (sass) 133 | .sass-cache/ 134 | 135 | # Installshield output folder 136 | [Ee]xpress/ 137 | 138 | # DocProject is a documentation generator add-in 139 | DocProject/buildhelp/ 140 | DocProject/Help/*.HxT 141 | DocProject/Help/*.HxC 142 | DocProject/Help/*.hhc 143 | DocProject/Help/*.hhk 144 | DocProject/Help/*.hhp 145 | DocProject/Help/Html2 146 | DocProject/Help/html 147 | 148 | # Click-Once directory 149 | publish/ 150 | 151 | # Publish Web Output 152 | *.[Pp]ublish.xml 153 | *.azurePubxml 154 | # TODO: Comment the next line if you want to checkin your web deploy settings 155 | # but database connection strings (with potential passwords) will be unencrypted 156 | *.pubxml 157 | *.publishproj 158 | 159 | # NuGet Packages 160 | *.nupkg 161 | # The packages folder can be ignored because of Package Restore 162 | **/packages/* 163 | # except build/, which is used as an MSBuild target. 164 | !**/packages/build/ 165 | # Uncomment if necessary however generally it will be regenerated when needed 166 | #!**/packages/repositories.config 167 | 168 | # Windows Azure Build Output 169 | csx/ 170 | *.build.csdef 171 | 172 | # Windows Store app package directory 173 | AppPackages/ 174 | 175 | # Others 176 | *.[Cc]ache 177 | ClientBin/ 178 | [Ss]tyle[Cc]op.* 179 | ~$* 180 | *~ 181 | *.dbmdl 182 | *.dbproj.schemaview 183 | *.pfx 184 | *.publishsettings 185 | node_modules/ 186 | bower_components/ 187 | 188 | # RIA/Silverlight projects 189 | Generated_Code/ 190 | 191 | # Backup & report files from converting an old project file 192 | # to a newer Visual Studio version. Backup files are not needed, 193 | # because we have git ;-) 194 | _UpgradeReport_Files/ 195 | Backup*/ 196 | UpgradeLog*.XML 197 | UpgradeLog*.htm 198 | 199 | # SQL Server files 200 | *.mdf 201 | *.ldf 202 | 203 | # Business Intelligence projects 204 | *.rdl.data 205 | *.bim.layout 206 | *.bim_*.settings 207 | 208 | # Microsoft Fakes 209 | FakesAssemblies/ 210 | 211 | # Node.js Tools for Visual Studio 212 | .ntvs_analysis.dat 213 | 214 | # Visual Studio 6 build log 215 | *.plg 216 | 217 | # Visual Studio 6 workspace options file 218 | *.opt 219 | 220 | project.lock.json 221 | __pycache__/ 222 | 223 | #Mac OSX 224 | .DS_Store 225 | 226 | # Windows thumbnail cache files 227 | Thumbs.db -------------------------------------------------------------------------------- /AWS/APIGatewayProxyFunction.cs: -------------------------------------------------------------------------------- 1 | using Amazon.Lambda.APIGatewayEvents; 2 | using Amazon.Lambda.Core; 3 | using Microsoft.AspNetCore; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.Hosting.Internal; 6 | using Microsoft.AspNetCore.Http.Features; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Logging; 10 | using Microsoft.Extensions.Primitives; 11 | using Newtonsoft.Json; 12 | using AspNetNow.AWS.Internal; 13 | using System; 14 | using System.Collections.Generic; 15 | using System.IO; 16 | using System.Linq; 17 | using System.Net; 18 | using System.Reflection; 19 | using System.Security.Claims; 20 | using System.Text; 21 | using System.Text.Encodings.Web; 22 | using System.Threading.Tasks; 23 | 24 | namespace AspNetNow.AWS 25 | { 26 | /// 27 | /// ApiGatewayProxyFunction is the base class that is implemented in a ASP.NET Core Web API. The derived class implements 28 | /// the Init method similar to Main function in the ASP.NET Core. The function handler for the Lambda function will point 29 | /// to this base class FunctionHandlerAsync method. 30 | /// 31 | public abstract class APIGatewayProxyFunction 32 | { 33 | /// 34 | /// Key to access the ILambdaContext object from the HttpContext.Items collection. 35 | /// 36 | public const string LAMBDA_CONTEXT = "LambdaContext"; 37 | 38 | /// 39 | /// Key to access the APIGatewayProxyRequest object from the HttpContext.Items collection. 40 | /// 41 | public const string APIGATEWAY_REQUEST = "APIGatewayRequest"; 42 | 43 | private AspNetCoreStartupMode _startupMode; 44 | private IWebHost _host; 45 | private APIGatewayServer _server; 46 | private ILogger _logger; 47 | 48 | // Defines a mapping from registered content types to the response encoding format 49 | // which dictates what transformations should be applied before returning response content 50 | private Dictionary _responseContentEncodingForContentType = new Dictionary 51 | { 52 | // The complete list of registered MIME content-types can be found at: 53 | // http://www.iana.org/assignments/media-types/media-types.xhtml 54 | 55 | // Here we just include a few commonly used content types found in 56 | // Web API responses and allow users to add more as needed below 57 | 58 | ["text/plain"] = ResponseContentEncoding.Default, 59 | ["text/xml"] = ResponseContentEncoding.Default, 60 | ["application/xml"] = ResponseContentEncoding.Default, 61 | ["application/json"] = ResponseContentEncoding.Default, 62 | ["text/html"] = ResponseContentEncoding.Default, 63 | ["text/css"] = ResponseContentEncoding.Default, 64 | ["text/javascript"] = ResponseContentEncoding.Default, 65 | ["text/ecmascript"] = ResponseContentEncoding.Default, 66 | ["text/markdown"] = ResponseContentEncoding.Default, 67 | ["text/csv"] = ResponseContentEncoding.Default, 68 | 69 | ["application/octet-stream"] = ResponseContentEncoding.Base64, 70 | ["image/png"] = ResponseContentEncoding.Base64, 71 | ["image/gif"] = ResponseContentEncoding.Base64, 72 | ["image/jpeg"] = ResponseContentEncoding.Base64, 73 | ["image/jpg"] = ResponseContentEncoding.Base64, 74 | ["application/zip"] = ResponseContentEncoding.Base64, 75 | ["application/pdf"] = ResponseContentEncoding.Base64, 76 | }; 77 | 78 | // Manage the serialization so the raw requests and responses can be logged. 79 | ILambdaSerializer _serializer = new Amazon.Lambda.Serialization.Json.JsonSerializer(); 80 | 81 | /// 82 | /// Defines the default treatment of response content. 83 | /// 84 | public ResponseContentEncoding DefaultResponseContentEncoding { get; set; } = ResponseContentEncoding.Default; 85 | 86 | /// 87 | /// The modes for when the ASP.NET Core framework will be initialized. 88 | /// 89 | public enum AspNetCoreStartupMode 90 | { 91 | /// 92 | /// Initialize during the construction of APIGatewayProxyFunction 93 | /// 94 | Constructor, 95 | 96 | /// 97 | /// Initialize during the first incoming request 98 | /// 99 | FirstRequest 100 | } 101 | 102 | /// 103 | /// Default Constructor. The ASP.NET Core Framework will be initialized as part of the construction. 104 | /// 105 | protected APIGatewayProxyFunction() 106 | : this(AspNetCoreStartupMode.Constructor) 107 | { 108 | 109 | } 110 | 111 | /// 112 | /// 113 | /// 114 | /// Configure when the ASP.NET Core framework will be initialized 115 | protected APIGatewayProxyFunction(AspNetCoreStartupMode startupMode) 116 | { 117 | _startupMode = startupMode; 118 | 119 | if (_startupMode == AspNetCoreStartupMode.Constructor) 120 | { 121 | Start(); 122 | } 123 | } 124 | 125 | private bool IsStarted 126 | { 127 | get 128 | { 129 | return _server != null; 130 | } 131 | } 132 | 133 | /// 134 | /// Should be called in the derived constructor 135 | /// 136 | protected void Start() 137 | { 138 | var builder = CreateWebHostBuilder(); 139 | Init(builder); 140 | 141 | 142 | _host = builder.Build(); 143 | _host.Start(); 144 | 145 | _server = _host.Services.GetService(typeof(Microsoft.AspNetCore.Hosting.Server.IServer)) as APIGatewayServer; 146 | if (_server == null) 147 | { 148 | throw new Exception("Failed to find the implementation APIGatewayServer for the IServer registration. This can happen if UseApiGateway was not called."); 149 | } 150 | _logger = ActivatorUtilities.CreateInstance>(this._host.Services); 151 | } 152 | 153 | /// 154 | /// Method to initialize the web builder before starting the web host. In a typical Web API this is similar to the main function. 155 | /// Setting the Startup class is required in this method. 156 | /// 157 | /// 158 | /// 159 | /// protected override void Init(IWebHostBuilder builder) 160 | /// { 161 | /// builder 162 | /// .UseStartup<Startup>(); 163 | /// } 164 | /// 165 | /// 166 | /// 167 | protected abstract void Init(IWebHostBuilder builder); 168 | 169 | /// 170 | /// Creates the IWebHostBuilder similar to WebHost.CreateDefaultBuilder but replacing the registration of the Kestrel web server with a 171 | /// registration for ApiGateway. 172 | /// 173 | /// 174 | protected virtual IWebHostBuilder CreateWebHostBuilder() 175 | { 176 | var builder = new WebHostBuilder() 177 | .UseContentRoot(Directory.GetCurrentDirectory()) 178 | .ConfigureAppConfiguration((hostingContext, config) => 179 | { 180 | var env = hostingContext.HostingEnvironment; 181 | 182 | config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 183 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); 184 | 185 | if (env.IsDevelopment()) 186 | { 187 | var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); 188 | if (appAssembly != null) 189 | { 190 | config.AddUserSecrets(appAssembly, optional: true); 191 | } 192 | } 193 | 194 | config.AddEnvironmentVariables(); 195 | }) 196 | .ConfigureLogging((hostingContext, logging) => 197 | { 198 | if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("LAMBDA_TASK_ROOT"))) 199 | { 200 | logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); 201 | logging.AddConsole(); 202 | logging.AddDebug(); 203 | } 204 | else 205 | { 206 | logging.AddLambdaLogger(hostingContext.Configuration, "Logging"); 207 | } 208 | }) 209 | .UseDefaultServiceProvider((hostingContext, options) => 210 | { 211 | options.ValidateScopes = hostingContext.HostingEnvironment.IsDevelopment(); 212 | }) 213 | .UseApiGateway(); 214 | 215 | 216 | return builder; 217 | } 218 | 219 | /// 220 | /// This method is what the Lambda function handler points to. 221 | /// 222 | /// 223 | /// 224 | /// 225 | [LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] 226 | public virtual async Task FunctionHandlerAsync(Stream input, ILambdaContext lambdaContext) 227 | { 228 | if (!IsStarted) 229 | { 230 | Start(); 231 | } 232 | 233 | try 234 | { 235 | 236 | string jsonInput = null; 237 | using (var reader = new StreamReader(input, Encoding.UTF8)) 238 | { 239 | jsonInput = reader.ReadToEnd(); 240 | } 241 | 242 | if (jsonInput == null) 243 | throw new ArgumentNullException(nameof(input)); 244 | 245 | var zeitRequest = JsonConvert.DeserializeObject (jsonInput); 246 | 247 | ZeitBody zeitBody = null; 248 | if (zeitRequest.Body != null) 249 | zeitBody = JsonConvert.DeserializeObject (zeitRequest.Body); 250 | 251 | var request = new APIGatewayProxyRequest() 252 | { 253 | Body = zeitBody.Body, 254 | Headers = zeitBody.Headers, 255 | Path = zeitBody.Path, 256 | HttpMethod = zeitBody.Method, 257 | IsBase64Encoded = zeitBody.Enconding?.Equals("base64") ?? false 258 | }; 259 | 260 | _logger.LogDebug($"Incoming {request.HttpMethod} requests to {request.Path}"); 261 | 262 | InvokeFeatures features = new InvokeFeatures(); 263 | MarshallRequest(features, request, lambdaContext); 264 | _logger.LogDebug($"ASP.NET Core Request PathBase: {((IHttpRequestFeature)features).PathBase}, Path: {((IHttpRequestFeature)features).Path}"); 265 | 266 | var context = this.CreateContext(features); 267 | 268 | if (request?.RequestContext?.Authorizer?.Claims != null) 269 | { 270 | var identity = new ClaimsIdentity(request.RequestContext.Authorizer.Claims.Select( 271 | entry => new Claim(entry.Key, entry.Value.ToString())), "AuthorizerIdentity"); 272 | 273 | _logger.LogDebug($"Configuring HttpContext.User with {request.RequestContext.Authorizer.Claims.Count} claims coming from API Gateway's Request Context"); 274 | context.HttpContext.User = new ClaimsPrincipal(identity); 275 | } 276 | 277 | // Add along the Lambda objects to the HttpContext to give access to Lambda to them in the ASP.NET Core application 278 | context.HttpContext.Items[LAMBDA_CONTEXT] = lambdaContext; 279 | context.HttpContext.Items[APIGATEWAY_REQUEST] = request; 280 | 281 | // Allow the context to be customized before passing the request to ASP.NET Core. 282 | PostCreateContext(context, request, lambdaContext); 283 | 284 | var response = await this.ProcessRequest(lambdaContext, context, features); 285 | 286 | return response; 287 | 288 | } 289 | catch (Exception e) 290 | { 291 | throw new Exception(e.Message + e.InnerException + e.StackTrace); 292 | } 293 | } 294 | 295 | /// 296 | /// Registers a mapping from a MIME content type to a . 297 | /// 298 | /// 299 | /// The mappings in combination with the 300 | /// setting will dictate if and how response content should be transformed before being 301 | /// returned to the calling API Gateway instance. 302 | /// 303 | /// The interface between the API Gateway and Lambda provides for repsonse content to 304 | /// be returned as a UTF-8 string. In order to return binary content without incurring 305 | /// any loss or corruption due to transformations to the UTF-8 encoding, it is necessary 306 | /// to encode the raw response content in Base64 and to annotate the response that it is 307 | /// Base64-encoded. 308 | /// 309 | /// NOTE: In order to use this mechanism to return binary response content, in 310 | /// addition to registering here any binary MIME content types that will be returned by 311 | /// your application, it also necessary to register those same content types with the API 312 | /// Gateway using either the console or the REST interface. Check the developer guide for 313 | /// further information. 314 | /// http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings-configure-with-console.html 315 | /// http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings-configure-with-control-service-api.html 316 | /// 317 | /// 318 | public void RegisterResponseContentEncodingForContentType(string contentType, ResponseContentEncoding encoding) 319 | { 320 | _responseContentEncodingForContentType[contentType] = encoding; 321 | } 322 | 323 | /// 324 | /// Creates a object using the field in the class. 325 | /// 326 | /// implementation. 327 | protected HostingApplication.Context CreateContext(IFeatureCollection features) 328 | { 329 | return _server.Application.CreateContext(features); 330 | } 331 | 332 | /// 333 | /// Processes the current request. 334 | /// 335 | /// implementation. 336 | /// The hosting application request context object. 337 | /// An instance. 338 | /// 339 | /// If specified, an unhandled exception will be rethrown for custom error handling. 340 | /// Ensure that the error handling code calls 'this.MarshallResponse(features, 500);' after handling the error to return a to the user. 341 | /// 342 | protected async Task ProcessRequest(ILambdaContext lambdaContext, HostingApplication.Context context, InvokeFeatures features, bool rethrowUnhandledError = false) 343 | { 344 | var defaultStatusCode = 200; 345 | Exception ex = null; 346 | try 347 | { 348 | await this._server.Application.ProcessRequestAsync(context); 349 | } 350 | catch (AggregateException agex) 351 | { 352 | ex = agex; 353 | _logger.LogError($"Caught AggregateException: '{agex}'"); 354 | var sb = new StringBuilder(); 355 | foreach (var newEx in agex.InnerExceptions) 356 | { 357 | sb.AppendLine(this.ErrorReport(newEx)); 358 | } 359 | 360 | _logger.LogError(sb.ToString()); 361 | defaultStatusCode = 500; 362 | } 363 | catch (ReflectionTypeLoadException rex) 364 | { 365 | ex = rex; 366 | _logger.LogError($"Caught ReflectionTypeLoadException: '{rex}'"); 367 | var sb = new StringBuilder(); 368 | foreach (var loaderException in rex.LoaderExceptions) 369 | { 370 | var fileNotFoundException = loaderException as FileNotFoundException; 371 | if (fileNotFoundException != null && !string.IsNullOrEmpty(fileNotFoundException.FileName)) 372 | { 373 | sb.AppendLine($"Missing file: {fileNotFoundException.FileName}"); 374 | } 375 | else 376 | { 377 | sb.AppendLine(this.ErrorReport(loaderException)); 378 | } 379 | } 380 | 381 | _logger.LogError(sb.ToString()); 382 | defaultStatusCode = 500; 383 | } 384 | catch (Exception e) 385 | { 386 | ex = e; 387 | if (rethrowUnhandledError) throw; 388 | _logger.LogError($"Unknown error responding to request: {this.ErrorReport(e)}"); 389 | defaultStatusCode = 500; 390 | } 391 | finally 392 | { 393 | this._server.Application.DisposeContext(context, ex); 394 | } 395 | 396 | if(features.ResponseStartingEvents != null) 397 | { 398 | await features.ResponseStartingEvents.ExecuteAsync(); 399 | } 400 | var response = this.MarshallResponse(features, lambdaContext, defaultStatusCode); 401 | 402 | _logger.LogDebug($"Response Base 64 Encoded: {response.IsBase64Encoded}"); 403 | 404 | if (ex != null) 405 | response.Headers.Add(new KeyValuePair("ErrorType", ex.GetType().Name)); 406 | 407 | if (features.ResponseCompletedEvents != null) 408 | { 409 | await features.ResponseCompletedEvents.ExecuteAsync(); 410 | } 411 | 412 | return response; 413 | } 414 | 415 | /// 416 | /// Formats an Exception into a string, including all inner exceptions. 417 | /// 418 | /// instance. 419 | protected string ErrorReport(Exception e) 420 | { 421 | var sb = new StringBuilder(); 422 | sb.AppendLine($"{e.GetType().Name}:\n{e}"); 423 | 424 | Exception inner = e; 425 | while (inner != null) 426 | { 427 | // Append the messages to the StringBuilder. 428 | sb.AppendLine($"{inner.GetType().Name}:\n{inner}"); 429 | inner = inner.InnerException; 430 | } 431 | 432 | return sb.ToString(); 433 | } 434 | 435 | /// 436 | /// This method is called after the APIGatewayProxyFunction has marshalled the incoming API Gateway request 437 | /// into ASP.NET Core's IHttpRequestFeature. Derived classes can overwrite this method to alter 438 | /// the how the marshalling was done. 439 | /// 440 | /// 441 | /// 442 | /// 443 | protected virtual void PostMarshallRequestFeature(IHttpRequestFeature aspNetCoreRequestFeature, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext) 444 | { 445 | 446 | } 447 | 448 | /// 449 | /// This method is called after the APIGatewayProxyFunction has marshalled the incoming API Gateway request 450 | /// into ASP.NET Core's IHttpConnectionFeature. Derived classes can overwrite this method to alter 451 | /// the how the marshalling was done. 452 | /// 453 | /// 454 | /// 455 | /// 456 | protected virtual void PostMarshallConnectionFeature(IHttpConnectionFeature aspNetCoreConnectionFeature, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext) 457 | { 458 | 459 | } 460 | 461 | /// 462 | /// This method is called after the APIGatewayProxyFunction has marshalled IHttpResponseFeature that came 463 | /// back from making the request into ASP.NET Core into API Gateway's response object APIGatewayProxyResponse. Derived classes can overwrite this method to alter 464 | /// the how the marshalling was done. 465 | /// 466 | /// 467 | /// 468 | /// 469 | protected virtual void PostMarshallResponseFeature(IHttpResponseFeature aspNetCoreResponseFeature, APIGatewayProxyResponse apiGatewayResponse, ILambdaContext lambdaContext) 470 | { 471 | 472 | } 473 | 474 | /// 475 | /// This method is called after the HostingApplication.Context has been created. Derived classes can overwrite this method to alter 476 | /// the context before passing the request to ASP.NET Core to process the request. 477 | /// 478 | /// 479 | /// 480 | /// 481 | protected virtual void PostCreateContext(HostingApplication.Context context, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext) 482 | { 483 | 484 | } 485 | 486 | /// 487 | /// Convert the JSON document received from API Gateway into the InvokeFeatures object. 488 | /// InvokeFeatures is then passed into IHttpApplication to create the ASP.NET Core request objects. 489 | /// 490 | /// 491 | /// 492 | /// 493 | protected void MarshallRequest(InvokeFeatures features, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext) 494 | { 495 | { 496 | var requestFeatures = (IHttpRequestFeature)features; 497 | requestFeatures.Scheme = "https"; 498 | requestFeatures.Method = apiGatewayRequest.HttpMethod; 499 | 500 | string path = null; 501 | if (apiGatewayRequest.PathParameters != null && apiGatewayRequest.PathParameters.ContainsKey("proxy") && !string.IsNullOrEmpty(apiGatewayRequest.Resource)) 502 | { 503 | var proxyPath = apiGatewayRequest.PathParameters["proxy"]; 504 | path = apiGatewayRequest.Resource.Replace("{proxy+}", proxyPath); 505 | } 506 | 507 | if (string.IsNullOrEmpty(path)) 508 | { 509 | path = apiGatewayRequest.Path; 510 | } 511 | 512 | if (!path.StartsWith("/")) 513 | { 514 | path = "/" + path; 515 | } 516 | 517 | requestFeatures.Path = path; 518 | 519 | requestFeatures.PathBase = string.Empty; 520 | if (!string.IsNullOrEmpty(apiGatewayRequest?.RequestContext?.Path)) 521 | { 522 | // This is to cover the case where the request coming in is https://myapigatewayid.execute-api.us-west-2.amazonaws.com/Prod where 523 | // Prod is the stage name and there is no ending '/'. Path will be set to '/' so to make sure we detect the correct base path 524 | // append '/' on the end to make the later EndsWith and substring work correctly. 525 | var requestContextPath = apiGatewayRequest.RequestContext.Path; 526 | if (path.EndsWith("/") && !requestContextPath.EndsWith("/")) 527 | { 528 | requestContextPath += "/"; 529 | } 530 | else if (!path.EndsWith("/") && requestContextPath.EndsWith("/")) 531 | { 532 | // Handle a trailing slash in the request path: e.g. https://myapigatewayid.execute-api.us-west-2.amazonaws.com/Prod/foo/ 533 | requestFeatures.Path = path += "/"; 534 | } 535 | 536 | if (requestContextPath.EndsWith(path)) 537 | { 538 | requestFeatures.PathBase = requestContextPath.Substring(0, requestContextPath.Length - requestFeatures.Path.Length); 539 | } 540 | } 541 | 542 | 543 | // API Gateway delivers the query string in a dictionary but must be reconstructed into the full query string 544 | // before passing into ASP.NET Core framework. 545 | var queryStringParameters = apiGatewayRequest.QueryStringParameters; 546 | if (queryStringParameters != null) 547 | { 548 | StringBuilder sb = new StringBuilder("?"); 549 | foreach (var kvp in queryStringParameters) 550 | { 551 | if (sb.Length > 1) 552 | { 553 | sb.Append("&"); 554 | } 555 | sb.Append($"{WebUtility.UrlEncode(kvp.Key)}={WebUtility.UrlEncode(kvp.Value.ToString())}"); 556 | } 557 | requestFeatures.QueryString = sb.ToString(); 558 | } 559 | else 560 | { 561 | requestFeatures.QueryString = string.Empty; 562 | } 563 | 564 | var headers = apiGatewayRequest.Headers; 565 | if (headers != null) 566 | { 567 | foreach (var kvp in headers) 568 | { 569 | requestFeatures.Headers[kvp.Key] = kvp.Value?.ToString(); 570 | } 571 | } 572 | 573 | if (!requestFeatures.Headers.ContainsKey("Host")) 574 | { 575 | var apiId = apiGatewayRequest.RequestContext?.ApiId ?? ""; 576 | var stage = apiGatewayRequest.RequestContext?.Stage ?? ""; 577 | 578 | requestFeatures.Headers["Host"] = $"apigateway-{apiId}-{stage}"; 579 | } 580 | 581 | 582 | if (!string.IsNullOrEmpty(apiGatewayRequest.Body)) 583 | { 584 | Byte[] binaryBody; 585 | if (apiGatewayRequest.IsBase64Encoded) 586 | { 587 | binaryBody = Convert.FromBase64String(apiGatewayRequest.Body); 588 | } 589 | else 590 | { 591 | binaryBody = UTF8Encoding.UTF8.GetBytes(apiGatewayRequest.Body); 592 | } 593 | requestFeatures.Body = new MemoryStream(binaryBody); 594 | } 595 | 596 | // Call consumers customize method in case they want to change how API Gateway's request 597 | // was marshalled into ASP.NET Core request. 598 | PostMarshallRequestFeature(requestFeatures, apiGatewayRequest, lambdaContext); 599 | } 600 | 601 | 602 | { 603 | // set up connection features 604 | var connectionFeatures = (IHttpConnectionFeature)features; 605 | 606 | IPAddress remoteIpAddress; 607 | if (!string.IsNullOrEmpty(apiGatewayRequest?.RequestContext?.Identity?.SourceIp) && 608 | IPAddress.TryParse(apiGatewayRequest.RequestContext.Identity.SourceIp, out remoteIpAddress)) 609 | { 610 | connectionFeatures.RemoteIpAddress = remoteIpAddress; 611 | } 612 | 613 | if (apiGatewayRequest?.Headers?.ContainsKey("X-Forwarded-Port") == true) 614 | { 615 | connectionFeatures.RemotePort = int.Parse(apiGatewayRequest.Headers["X-Forwarded-Port"]); 616 | } 617 | 618 | // Call consumers customize method in case they want to change how API Gateway's request 619 | // was marshalled into ASP.NET Core request. 620 | PostMarshallConnectionFeature(connectionFeatures, apiGatewayRequest, lambdaContext); 621 | } 622 | 623 | } 624 | 625 | /// 626 | /// Convert the response coming from ASP.NET Core into APIGatewayProxyResponse which is 627 | /// serialized into the JSON object that API Gateway expects. 628 | /// 629 | /// 630 | /// Sometimes the ASP.NET server doesn't set the status code correctly when successful, so this parameter will be used when the value is 0. 631 | /// 632 | /// 633 | protected APIGatewayProxyResponse MarshallResponse(IHttpResponseFeature responseFeatures, ILambdaContext lambdaContext, int statusCodeIfNotSet = 200) 634 | { 635 | var response = new APIGatewayProxyResponse 636 | { 637 | StatusCode = responseFeatures.StatusCode != 0 ? responseFeatures.StatusCode : statusCodeIfNotSet 638 | }; 639 | 640 | string contentType = null; 641 | if (responseFeatures.Headers != null) 642 | { 643 | response.Headers = new Dictionary(); 644 | foreach (var kvp in responseFeatures.Headers) 645 | { 646 | if(string.Equals(kvp.Key, "Set-Cookie", StringComparison.OrdinalIgnoreCase)) 647 | { 648 | ProcessCookies(response.Headers, kvp.Value); 649 | } 650 | else if (kvp.Value.Count == 1) 651 | { 652 | response.Headers[kvp.Key] = kvp.Value[0]; 653 | } 654 | else 655 | { 656 | response.Headers[kvp.Key] = string.Join(",", kvp.Value); 657 | } 658 | 659 | // Remember the Content-Type for possible later use 660 | if (kvp.Key.Equals("Content-Type", StringComparison.CurrentCultureIgnoreCase)) 661 | contentType = response.Headers[kvp.Key]; 662 | } 663 | } 664 | 665 | if(contentType == null) 666 | { 667 | response.Headers["Content-Type"] = null; 668 | } 669 | 670 | if (responseFeatures.Body != null) 671 | { 672 | // Figure out how we should treat the response content 673 | var rcEncoding = DefaultResponseContentEncoding; 674 | if (contentType != null) 675 | { 676 | // ASP.NET Core will typically return content type with encoding like this "application/json; charset=utf-8" 677 | // To find the content type in the dictionary we need to strip the encoding off. 678 | var contentTypeWithoutEncoding = contentType.Split(';')[0].Trim(); 679 | if (_responseContentEncodingForContentType.ContainsKey(contentTypeWithoutEncoding)) 680 | { 681 | rcEncoding = _responseContentEncodingForContentType[contentTypeWithoutEncoding]; 682 | } 683 | } 684 | 685 | // Do we encode the response content in Base64 or treat it as UTF-8 686 | if (rcEncoding == ResponseContentEncoding.Base64) 687 | { 688 | // We want to read the response content "raw" and then Base64 encode it 689 | byte[] bodyBytes; 690 | if (responseFeatures.Body is MemoryStream) 691 | { 692 | bodyBytes = ((MemoryStream)responseFeatures.Body).ToArray(); 693 | } 694 | else 695 | { 696 | using (var ms = new MemoryStream()) 697 | { 698 | responseFeatures.Body.CopyTo(ms); 699 | bodyBytes = ms.ToArray(); 700 | } 701 | } 702 | response.Body = Convert.ToBase64String(bodyBytes); 703 | response.IsBase64Encoded = true; 704 | } 705 | else if (responseFeatures.Body is MemoryStream) 706 | { 707 | response.Body = UTF8Encoding.UTF8.GetString(((MemoryStream)responseFeatures.Body).ToArray()); 708 | } 709 | else 710 | { 711 | responseFeatures.Body.Position = 0; 712 | using (StreamReader reader = new StreamReader(responseFeatures.Body, Encoding.UTF8)) 713 | { 714 | response.Body = reader.ReadToEnd(); 715 | } 716 | } 717 | } 718 | 719 | PostMarshallResponseFeature(responseFeatures, response, lambdaContext); 720 | 721 | return response; 722 | } 723 | 724 | /// 725 | /// To work around API Gateway's limitation only allowing one value per header name return back 726 | /// multiple cookie values with different casing of header values. 727 | /// 728 | /// 729 | /// 730 | public static void ProcessCookies(IDictionary apiGatewayHeaders, StringValues cookies) 731 | { 732 | int i = 0; 733 | foreach (var cookieName in Utilities.Permute("set-cookie")) 734 | { 735 | apiGatewayHeaders[cookieName] = cookies[i++]; 736 | if (cookies.Count <= i) 737 | { 738 | break; 739 | } 740 | } 741 | } 742 | } 743 | 744 | public class ZeitRequest 745 | { 746 | public string Action { get; set; } 747 | 748 | [JsonProperty (PropertyName = "body")] 749 | public string Body { get; set; } 750 | } 751 | 752 | public class ZeitBody 753 | { 754 | [JsonProperty (PropertyName = "path")] 755 | public string Path { get; set; } 756 | 757 | [JsonProperty (PropertyName = "body")] 758 | public string Body { get; set; } 759 | 760 | [JsonProperty (PropertyName = "enconding")] 761 | public string Enconding { get; set; } 762 | 763 | [JsonProperty (PropertyName = "method")] 764 | public string Method { get; set; } 765 | 766 | [JsonProperty (PropertyName = "host")] 767 | public string Host { get; set; } 768 | 769 | [JsonProperty (PropertyName = "headers")] 770 | public Dictionary Headers { get; set; } 771 | } 772 | } 773 | -------------------------------------------------------------------------------- /AWS/APIGatewayProxyFunction{TStartup}.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | 3 | namespace AspNetNow.AWS 4 | { 5 | /// 6 | /// ApiGatewayProxyFunction is the base class that is implemented in a ASP.NET Core Web API. The derived class implements 7 | /// the Init method similar to Main function in the ASP.NET Core and provides typed Startup. The function handler for 8 | /// the Lambda function will point to this base class FunctionHandlerAsync method. 9 | /// 10 | /// The type containing the startup methods for the application. 11 | public abstract class APIGatewayProxyFunction : APIGatewayProxyFunction where TStartup : class 12 | { 13 | /// 14 | protected override IWebHostBuilder CreateWebHostBuilder() => 15 | base.CreateWebHostBuilder().UseStartup(); 16 | 17 | /// 18 | protected override void Init(IWebHostBuilder builder) 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AWS/Internal/ApiGatewayServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | using Microsoft.AspNetCore.Hosting.Server; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.Http.Features; 10 | using Microsoft.AspNetCore.Hosting.Internal; 11 | 12 | namespace AspNetNow.AWS.Internal 13 | { 14 | /// 15 | /// Implements the ASP.NET Core IServer interface and exposes the application object for the Lambda function 16 | /// to initiate a web request. 17 | /// 18 | internal class APIGatewayServer : IServer 19 | { 20 | /// 21 | /// The application is used by the Lambda function to initiate a web request through the ASP.NET Core framework. 22 | /// 23 | public IHttpApplication Application { get; set; } 24 | public IFeatureCollection Features { get; } = new FeatureCollection(); 25 | 26 | public void Dispose() 27 | { 28 | } 29 | 30 | public Task StartAsync(IHttpApplication application, CancellationToken cancellationToken) 31 | { 32 | this.Application = application as IHttpApplication; 33 | return Task.CompletedTask; 34 | } 35 | 36 | public Task StopAsync(CancellationToken cancellationToken) 37 | { 38 | return Task.CompletedTask; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /AWS/Internal/InvokeFeatures.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Threading.Tasks; 8 | 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.AspNetCore.Http.Features; 11 | 12 | #pragma warning disable 1591 13 | 14 | namespace AspNetNow.AWS.Internal 15 | { 16 | public class InvokeFeatures : IFeatureCollection, 17 | IHttpRequestFeature, 18 | IHttpResponseFeature, 19 | IHttpConnectionFeature 20 | /* 21 | , 22 | IHttpUpgradeFeature, 23 | IHttpRequestLifetimeFeature*/ 24 | { 25 | 26 | public InvokeFeatures() 27 | { 28 | _features[typeof(IHttpRequestFeature)] = this; 29 | _features[typeof(IHttpResponseFeature)] = this; 30 | _features[typeof(IHttpConnectionFeature)] = this; 31 | } 32 | 33 | #region IFeatureCollection 34 | public bool IsReadOnly => false; 35 | 36 | IDictionary _features = new Dictionary(); 37 | 38 | public int Revision => 0; 39 | 40 | public object this[Type key] 41 | { 42 | get 43 | { 44 | object feature; 45 | if (_features.TryGetValue(key, out feature)) 46 | { 47 | return feature; 48 | } 49 | 50 | return null; 51 | } 52 | 53 | set 54 | { 55 | _features[key] = value; 56 | } 57 | } 58 | 59 | public TFeature Get() 60 | { 61 | object feature; 62 | if (_features.TryGetValue(typeof(TFeature), out feature)) 63 | { 64 | return (TFeature)feature; 65 | } 66 | 67 | return default(TFeature); 68 | } 69 | 70 | public IEnumerator> GetEnumerator() 71 | { 72 | return this._features.GetEnumerator(); 73 | } 74 | 75 | public void Set(TFeature instance) 76 | { 77 | if (instance == null) 78 | return; 79 | 80 | this._features[typeof(TFeature)] = instance; 81 | } 82 | 83 | IEnumerator IEnumerable.GetEnumerator() 84 | { 85 | return this._features.GetEnumerator(); 86 | } 87 | 88 | #endregion 89 | 90 | #region IHttpRequestFeature 91 | string IHttpRequestFeature.Protocol { get; set; } 92 | 93 | string IHttpRequestFeature.Scheme { get; set; } 94 | 95 | string IHttpRequestFeature.Method { get; set; } 96 | 97 | string IHttpRequestFeature.PathBase { get; set; } 98 | 99 | string IHttpRequestFeature.Path { get; set; } 100 | 101 | string IHttpRequestFeature.QueryString { get; set; } 102 | 103 | string IHttpRequestFeature.RawTarget { get; set; } 104 | 105 | IHeaderDictionary IHttpRequestFeature.Headers { get; set; } = new HeaderDictionary(); 106 | 107 | Stream IHttpRequestFeature.Body { get; set; } = new MemoryStream(); 108 | 109 | #endregion 110 | 111 | #region IHttpResponseFeature 112 | int IHttpResponseFeature.StatusCode 113 | { 114 | get; 115 | set; 116 | } 117 | 118 | string IHttpResponseFeature.ReasonPhrase 119 | { 120 | get; 121 | set; 122 | } 123 | 124 | bool IHttpResponseFeature.HasStarted 125 | { 126 | get; 127 | } 128 | 129 | IHeaderDictionary IHttpResponseFeature.Headers 130 | { 131 | get; 132 | set; 133 | } = new HeaderDictionary(); 134 | 135 | Stream IHttpResponseFeature.Body 136 | { 137 | get; 138 | set; 139 | } = new MemoryStream(); 140 | 141 | internal EventCallbacks ResponseStartingEvents { get; private set; } 142 | void IHttpResponseFeature.OnStarting(Func callback, object state) 143 | { 144 | if (ResponseStartingEvents == null) 145 | this.ResponseStartingEvents = new EventCallbacks(); 146 | 147 | this.ResponseStartingEvents.Add(callback, state); 148 | } 149 | 150 | internal EventCallbacks ResponseCompletedEvents { get; private set; } 151 | void IHttpResponseFeature.OnCompleted(Func callback, object state) 152 | { 153 | if (this.ResponseCompletedEvents == null) 154 | this.ResponseCompletedEvents = new EventCallbacks(); 155 | 156 | this.ResponseCompletedEvents.Add(callback, state); 157 | } 158 | 159 | internal class EventCallbacks 160 | { 161 | List _callbacks = new List(); 162 | 163 | internal void Add(Func callback, object state) 164 | { 165 | this._callbacks.Add(new EventCallback(callback, state)); 166 | } 167 | 168 | internal async Task ExecuteAsync() 169 | { 170 | foreach(var callback in _callbacks) 171 | { 172 | await callback.ExecuteAsync(); 173 | } 174 | } 175 | 176 | internal class EventCallback 177 | { 178 | internal EventCallback(Func callback, object state) 179 | { 180 | this.Callback = callback; 181 | this.State = state; 182 | } 183 | 184 | Func Callback { get; } 185 | object State { get; } 186 | 187 | internal Task ExecuteAsync() 188 | { 189 | var task = Callback(this.State); 190 | return task; 191 | } 192 | } 193 | } 194 | 195 | #endregion 196 | 197 | #region IHttpConnectionFeature 198 | 199 | string IHttpConnectionFeature.ConnectionId { get; set; } 200 | 201 | IPAddress IHttpConnectionFeature.RemoteIpAddress { get; set; } 202 | 203 | IPAddress IHttpConnectionFeature.LocalIpAddress { get; set; } 204 | 205 | int IHttpConnectionFeature.RemotePort { get; set; } 206 | 207 | int IHttpConnectionFeature.LocalPort { get; set; } 208 | 209 | #endregion 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /AWS/Internal/Utilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspNetNow.AWS.Internal 6 | { 7 | /// 8 | /// 9 | /// 10 | public static class Utilities 11 | { 12 | 13 | /// 14 | /// Generate different casing permutations of the input string. 15 | /// 16 | /// 17 | /// 18 | public static IEnumerable Permute(string input) 19 | { 20 | // Determine the number of alpha characters that can be toggled. 21 | int alphaCharCount = 0; 22 | foreach(var c in input) 23 | { 24 | if(char.IsLetter(c)) 25 | { 26 | alphaCharCount++; 27 | } 28 | } 29 | 30 | // Map the indexes to the position of the alpha characters in the original input string. 31 | var alphaIndexes = new int[alphaCharCount]; 32 | var alphaIndex = 0; 33 | for(int i = 0; i < input.Length; i++) 34 | { 35 | if(char.IsLetter(input[i])) 36 | { 37 | alphaIndexes[alphaIndex++] = i; 38 | } 39 | } 40 | 41 | // Number of permutations is 2^n 42 | int max = 1 << alphaCharCount; 43 | 44 | // Converting string 45 | // to lower case 46 | input = input.ToLower(); 47 | 48 | // Using all subsequences 49 | // and permuting them 50 | for (int i = 0; i < max; i++) 51 | { 52 | char[] combination = input.ToCharArray(); 53 | 54 | // If j-th bit is set, we 55 | // convert it to upper case 56 | for (int j = 0; j < alphaIndexes.Length; j++) 57 | { 58 | if (((i >> j) & 1) == 1) 59 | { 60 | combination[alphaIndexes[j]] = (char)(combination[alphaIndexes[j]] - 32); 61 | } 62 | } 63 | 64 | yield return new string(combination); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /AWS/ResponseContentEncoding.cs: -------------------------------------------------------------------------------- 1 | namespace AspNetNow.AWS 2 | { 3 | /// 4 | /// Indicates how response content from a controller action should be treated, 5 | /// possibly requiring a transformation to comply with the expected binary disposition. 6 | /// 7 | public enum ResponseContentEncoding 8 | { 9 | /// Indicates the response content should already be UTF-8-friendly and should be 10 | /// returned without any further transformation or encoding. This typically 11 | /// indicates a text response. 12 | Default = 0, 13 | 14 | /// Indicates the response content should be Base64 encoded before being returned. 15 | /// This is typically used to indicate a binary response. 16 | Base64, 17 | } 18 | } -------------------------------------------------------------------------------- /AWS/WebHostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | using Microsoft.AspNetCore.Hosting.Server; 9 | using AspNetNow.AWS.Internal; 10 | using Microsoft.AspNetCore.Hosting; 11 | 12 | namespace AspNetNow.AWS 13 | { 14 | /// 15 | /// This class is a container for extensions methods to the IWebHostBuilder 16 | /// 17 | public static class WebHostBuilderExtensions 18 | { 19 | /// 20 | /// Extension method for configuring API Gateway as the server for an ASP.NET Core application. 21 | /// This is called instead of UseKestrel. If UseKestrel was called before this it will remove 22 | /// the service description that was added to the IServiceCollection. 23 | /// 24 | /// 25 | /// 26 | public static IWebHostBuilder UseApiGateway(this IWebHostBuilder hostBuilder) 27 | { 28 | return hostBuilder.ConfigureServices(services => 29 | { 30 | var serviceDescription = services.FirstOrDefault(x => x.ServiceType == typeof(IServer)); 31 | if(serviceDescription != null) 32 | { 33 | // If Api Gateway server has already been added the skip out. 34 | if (serviceDescription.ImplementationType == typeof(APIGatewayServer)) 35 | return; 36 | // If there is already an IServer registeried then remove. This is mostly likely caused 37 | // by leaving the UseKestrel call. 38 | else 39 | services.Remove(serviceDescription); 40 | } 41 | 42 | services.AddSingleton(); 43 | }); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /AspNetNow.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | true 6 | Lambda 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using AspNetNow.Models; 8 | 9 | namespace AspNetNow.Controllers 10 | { 11 | public class HomeController : Controller 12 | { 13 | public IActionResult Index() 14 | { 15 | return View(); 16 | } 17 | 18 | public IActionResult About() 19 | { 20 | ViewData["Message"] = "Your application description page."; 21 | 22 | return View(); 23 | } 24 | 25 | public IActionResult Contact() 26 | { 27 | ViewData["Message"] = "Your contact page."; 28 | 29 | return View(); 30 | } 31 | 32 | public IActionResult Privacy() 33 | { 34 | return View(); 35 | } 36 | 37 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 38 | public IActionResult Error() 39 | { 40 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LambdaEntryPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | using System.IO; 8 | using AspNetNow; 9 | 10 | namespace AspNetNow 11 | { 12 | /// 13 | /// This class extends from APIGatewayProxyFunction which contains the method FunctionHandlerAsync which is the 14 | /// actual Lambda function entry point. The Lambda handler field should be set to 15 | /// 16 | /// NowAwsAspNetLambda::NowAwsAspNetLambda.LambdaEntryPoint::FunctionHandlerAsync 17 | /// 18 | public class LambdaEntryPoint : AspNetNow.AWS.APIGatewayProxyFunction 19 | { 20 | /// 21 | /// The builder has configuration, logging and Amazon API Gateway already configured. The startup class 22 | /// needs to be configured in this method using the UseStartup<>() method. 23 | /// 24 | /// 25 | protected override void Init(IWebHostBuilder builder) 26 | { 27 | builder 28 | .UseStartup(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AspNetNow.Models 4 | { 5 | public class ErrorViewModel 6 | { 7 | public string RequestId { get; set; } 8 | 9 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 10 | } 11 | } -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace AspNetNow 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.HttpsPolicy; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Microsoft.Extensions.Configuration; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Robotify.AspNetCore; 13 | 14 | namespace AspNetNow 15 | { 16 | public class Startup 17 | { 18 | public Startup(IConfiguration configuration) 19 | { 20 | Configuration = configuration; 21 | } 22 | 23 | public IConfiguration Configuration { get; } 24 | 25 | // This method gets called by the runtime. Use this method to add services to the container. 26 | public void ConfigureServices(IServiceCollection services) 27 | { 28 | services.Configure(options => 29 | { 30 | // This lambda determines whether user consent for non-essential cookies is needed for a given request. 31 | options.CheckConsentNeeded = context => true; 32 | options.MinimumSameSitePolicy = SameSiteMode.None; 33 | }); 34 | 35 | services.AddRobotify(); 36 | 37 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 38 | } 39 | 40 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 41 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 42 | { 43 | if (env.IsDevelopment()) 44 | { 45 | app.UseDeveloperExceptionPage(); 46 | } 47 | else 48 | { 49 | app.UseExceptionHandler("/Home/Error"); 50 | app.UseHsts(); 51 | } 52 | 53 | app.UseHttpsRedirection(); 54 | app.UseStaticFiles(); 55 | app.UseCookiePolicy(); 56 | 57 | app.UseMvc(routes => 58 | { 59 | routes.MapRoute( 60 | name: "default", 61 | template: "{controller=Home}/{action=Index}/{id?}"); 62 | }); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Views/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "About"; 3 | } 4 |

@ViewData["Title"]

5 |

@ViewData["Message"]

6 | 7 |

Use this area to provide additional information.

8 | -------------------------------------------------------------------------------- /Views/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Contact"; 3 | } 4 |

@ViewData["Title"]

5 |

@ViewData["Message"]

6 | 7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P: 11 | 425.555.0100 12 |
13 | 14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com 17 |
18 | -------------------------------------------------------------------------------- /Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 |

ASP.NET Core on Zeit Now

6 |
7 |

Find this project on GitHub:

8 |

https://github.com/josemnbcamacho/aspnet-lambda-now

-------------------------------------------------------------------------------- /Views/Home/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Privacy Policy"; 3 | } 4 |

@ViewData["Title"]

5 | 6 |

Use this page to detail your site's privacy policy.

7 | -------------------------------------------------------------------------------- /Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model ErrorViewModel 2 | @{ 3 | ViewData["Title"] = "Error"; 4 | } 5 | 6 |

Error.

7 |

An error occurred while processing your request.

8 | 9 | @if (Model.ShowRequestId) 10 | { 11 |

12 | Request ID: @Model.RequestId 13 |

14 | } 15 | 16 |

Development Mode

17 |

18 | Swapping to Development environment will display more detailed information about the error that occurred. 19 |

20 |

21 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 22 |

23 | -------------------------------------------------------------------------------- /Views/Shared/_CookieConsentPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Http.Features 2 | 3 | @{ 4 | var consentFeature = Context.Features.Get(); 5 | var showBanner = !consentFeature?.CanTrack ?? false; 6 | var cookieString = consentFeature?.CreateConsentCookie(); 7 | } 8 | 9 | @if (showBanner) 10 | { 11 | 33 | 41 | } -------------------------------------------------------------------------------- /Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - AspNet Core on Zeit Now! 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 40 | 41 | 42 | 43 |
44 | @RenderBody() 45 |
46 |
47 |

© 2019 - AspNet Core on Zeit Now!

48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 69 | 70 | 71 | 72 | @RenderSection("Scripts", required: false) 73 | 74 | 75 | -------------------------------------------------------------------------------- /Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using AspNetNow 2 | @using AspNetNow.Models 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /aws-lambda-tools-defaults.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "Information" : [ 4 | "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", 5 | "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", 6 | 7 | "dotnet lambda help", 8 | 9 | "All the command line options for the Lambda command can be specified in this file." 10 | ], 11 | 12 | "profile":"", 13 | "region" : "", 14 | "configuration" : "Release", 15 | "framework" : "netcoreapp2.1", 16 | "s3-prefix" : "NowAwsAspNetLambda/", 17 | "template" : "serverless.template", 18 | "template-parameters" : "ShouldCreateBucket=true;BucketName=", 19 | "function-runtime":"dotnetcore2.1", 20 | "function-memory-size" : 256, 21 | "function-timeout" : 30, 22 | "function-handler" : "AspNetNow::AspNetNow.LambdaEntryPoint::FunctionHandlerAsync" 23 | } -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "asp-net-core", 4 | "builds": [ 5 | { 6 | "src": "AspNetNow.csproj", 7 | "use": "now-dotnet-aws-lambda-bin" 8 | }, 9 | { 10 | "src": "/wwwroot/**", 11 | "use": "@now/static" 12 | } 13 | ], 14 | "routes": [ 15 | {"src": "^/js/(.*)", "dest": "/wwwroot/js/$1"}, 16 | {"src": "^/css/(.*)", "dest": "/wwwroot/css/$1"}, 17 | {"src": "^/images/(.*)", "dest": "/wwwroot/images/$1"}, 18 | {"src": "^/lib/(.*)", "dest": "/wwwroot/lib/$1"}, 19 | { "src": ".*", "dest": "index" } 20 | ] 21 | } -------------------------------------------------------------------------------- /serverless.template: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion" : "2010-09-09", 3 | "Transform" : "AWS::Serverless-2016-10-31", 4 | "Description" : "An AWS Serverless Application that uses the ASP.NET Core framework running in Amazon Lambda.", 5 | 6 | "Parameters" : { 7 | "ShouldCreateBucket" : { 8 | "Type" : "String", 9 | "AllowedValues" : ["true", "false"], 10 | "Description" : "If true then the S3 bucket that will be proxied will be created with the CloudFormation stack." 11 | }, 12 | "BucketName" : { 13 | "Type" : "String", 14 | "Description" : "Name of S3 bucket that will be proxied. If left blank a name will be generated.", 15 | "MinLength" : "0" 16 | } 17 | }, 18 | 19 | "Conditions" : { 20 | "CreateS3Bucket" : {"Fn::Equals" : [{"Ref" : "ShouldCreateBucket"}, "true"]}, 21 | "BucketNameGenerated" : {"Fn::Equals" : [{"Ref" : "BucketName"}, ""]} 22 | }, 23 | 24 | "Resources" : { 25 | 26 | "AspNetCoreFunction" : { 27 | "Type" : "AWS::Serverless::Function", 28 | "Properties": { 29 | "Handler": "AspNetNow::AspNetNow.LambdaEntryPoint::FunctionHandlerAsync", 30 | "Runtime": "dotnetcore2.1", 31 | "CodeUri": "", 32 | "MemorySize": 256, 33 | "Timeout": 30, 34 | "Role": null, 35 | "Policies": [ "AWSLambdaFullAccess" ], 36 | "Environment" : { 37 | "Variables" : { 38 | "AppS3Bucket" : { "Fn::If" : ["CreateS3Bucket", {"Ref":"Bucket"}, { "Ref" : "BucketName" } ] } 39 | } 40 | }, 41 | "Events": { 42 | "PutResource": { 43 | "Type": "Api", 44 | "Properties": { 45 | "Path": "/{proxy+}", 46 | "Method": "ANY" 47 | } 48 | } 49 | } 50 | } 51 | }, 52 | 53 | "Bucket" : { 54 | "Type" : "AWS::S3::Bucket", 55 | "Condition" : "CreateS3Bucket", 56 | "Properties" : { 57 | "BucketName" : { "Fn::If" : ["BucketNameGenerated", {"Ref" : "AWS::NoValue" }, { "Ref" : "BucketName" } ] } 58 | } 59 | } 60 | }, 61 | 62 | "Outputs" : { 63 | "ApiURL" : { 64 | "Description" : "API endpoint URL for Prod environment", 65 | "Value" : { "Fn::Sub" : "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" } 66 | }, 67 | "S3ProxyBucket" : { 68 | "Value" : { "Fn::If" : ["CreateS3Bucket", {"Ref":"Bucket"}, { "Ref" : "BucketName" } ] } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification\ 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | body { 4 | padding-top: 50px; 5 | padding-bottom: 20px; 6 | } 7 | 8 | /* Wrapping element */ 9 | /* Set some basic padding to keep content from hitting the edges */ 10 | .body-content { 11 | padding-left: 15px; 12 | padding-right: 15px; 13 | } 14 | 15 | /* Carousel */ 16 | .carousel-caption p { 17 | font-size: 20px; 18 | line-height: 1.4; 19 | } 20 | 21 | /* Make .svg files in the carousel display properly in older browsers */ 22 | .carousel-inner .item img[src$=".svg"] { 23 | width: 100%; 24 | } 25 | 26 | /* QR code generator */ 27 | #qrCode { 28 | margin: 15px; 29 | } 30 | 31 | /* Hide/rearrange for smaller screens */ 32 | @media screen and (max-width: 767px) { 33 | /* Hide captions */ 34 | .carousel-caption { 35 | display: none; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josemnbcamacho/aspnet-lambda-now/650b6071ed48b5734639a0397f2a788aac1bc922/wwwroot/favicon.ico -------------------------------------------------------------------------------- /wwwroot/images/banner1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wwwroot/images/banner2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wwwroot/images/banner3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap", 3 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", 4 | "keywords": [ 5 | "css", 6 | "js", 7 | "less", 8 | "mobile-first", 9 | "responsive", 10 | "front-end", 11 | "framework", 12 | "web" 13 | ], 14 | "homepage": "http://getbootstrap.com", 15 | "license": "MIT", 16 | "moduleType": "globals", 17 | "main": [ 18 | "less/bootstrap.less", 19 | "dist/js/bootstrap.js" 20 | ], 21 | "ignore": [ 22 | "/.*", 23 | "_config.yml", 24 | "CNAME", 25 | "composer.json", 26 | "CONTRIBUTING.md", 27 | "docs", 28 | "js/tests", 29 | "test-infra" 30 | ], 31 | "dependencies": { 32 | "jquery": "1.9.1 - 3" 33 | }, 34 | "version": "3.3.7", 35 | "_release": "3.3.7", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.3.7", 39 | "commit": "0b9c4a4007c44201dce9a6cc1a38407005c26c86" 40 | }, 41 | "_source": "https://github.com/twbs/bootstrap.git", 42 | "_target": "v3.3.7", 43 | "_originalSource": "bootstrap", 44 | "_direct": true 45 | } -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2016 Twitter, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | .btn-default, 7 | .btn-primary, 8 | .btn-success, 9 | .btn-info, 10 | .btn-warning, 11 | .btn-danger { 12 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); 13 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 14 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 15 | } 16 | .btn-default:active, 17 | .btn-primary:active, 18 | .btn-success:active, 19 | .btn-info:active, 20 | .btn-warning:active, 21 | .btn-danger:active, 22 | .btn-default.active, 23 | .btn-primary.active, 24 | .btn-success.active, 25 | .btn-info.active, 26 | .btn-warning.active, 27 | .btn-danger.active { 28 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 29 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 30 | } 31 | .btn-default.disabled, 32 | .btn-primary.disabled, 33 | .btn-success.disabled, 34 | .btn-info.disabled, 35 | .btn-warning.disabled, 36 | .btn-danger.disabled, 37 | .btn-default[disabled], 38 | .btn-primary[disabled], 39 | .btn-success[disabled], 40 | .btn-info[disabled], 41 | .btn-warning[disabled], 42 | .btn-danger[disabled], 43 | fieldset[disabled] .btn-default, 44 | fieldset[disabled] .btn-primary, 45 | fieldset[disabled] .btn-success, 46 | fieldset[disabled] .btn-info, 47 | fieldset[disabled] .btn-warning, 48 | fieldset[disabled] .btn-danger { 49 | -webkit-box-shadow: none; 50 | box-shadow: none; 51 | } 52 | .btn-default .badge, 53 | .btn-primary .badge, 54 | .btn-success .badge, 55 | .btn-info .badge, 56 | .btn-warning .badge, 57 | .btn-danger .badge { 58 | text-shadow: none; 59 | } 60 | .btn:active, 61 | .btn.active { 62 | background-image: none; 63 | } 64 | .btn-default { 65 | text-shadow: 0 1px 0 #fff; 66 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); 67 | background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); 68 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); 69 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); 70 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 71 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 72 | background-repeat: repeat-x; 73 | border-color: #dbdbdb; 74 | border-color: #ccc; 75 | } 76 | .btn-default:hover, 77 | .btn-default:focus { 78 | background-color: #e0e0e0; 79 | background-position: 0 -15px; 80 | } 81 | .btn-default:active, 82 | .btn-default.active { 83 | background-color: #e0e0e0; 84 | border-color: #dbdbdb; 85 | } 86 | .btn-default.disabled, 87 | .btn-default[disabled], 88 | fieldset[disabled] .btn-default, 89 | .btn-default.disabled:hover, 90 | .btn-default[disabled]:hover, 91 | fieldset[disabled] .btn-default:hover, 92 | .btn-default.disabled:focus, 93 | .btn-default[disabled]:focus, 94 | fieldset[disabled] .btn-default:focus, 95 | .btn-default.disabled.focus, 96 | .btn-default[disabled].focus, 97 | fieldset[disabled] .btn-default.focus, 98 | .btn-default.disabled:active, 99 | .btn-default[disabled]:active, 100 | fieldset[disabled] .btn-default:active, 101 | .btn-default.disabled.active, 102 | .btn-default[disabled].active, 103 | fieldset[disabled] .btn-default.active { 104 | background-color: #e0e0e0; 105 | background-image: none; 106 | } 107 | .btn-primary { 108 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); 109 | background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); 110 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); 111 | background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); 112 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); 113 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 114 | background-repeat: repeat-x; 115 | border-color: #245580; 116 | } 117 | .btn-primary:hover, 118 | .btn-primary:focus { 119 | background-color: #265a88; 120 | background-position: 0 -15px; 121 | } 122 | .btn-primary:active, 123 | .btn-primary.active { 124 | background-color: #265a88; 125 | border-color: #245580; 126 | } 127 | .btn-primary.disabled, 128 | .btn-primary[disabled], 129 | fieldset[disabled] .btn-primary, 130 | .btn-primary.disabled:hover, 131 | .btn-primary[disabled]:hover, 132 | fieldset[disabled] .btn-primary:hover, 133 | .btn-primary.disabled:focus, 134 | .btn-primary[disabled]:focus, 135 | fieldset[disabled] .btn-primary:focus, 136 | .btn-primary.disabled.focus, 137 | .btn-primary[disabled].focus, 138 | fieldset[disabled] .btn-primary.focus, 139 | .btn-primary.disabled:active, 140 | .btn-primary[disabled]:active, 141 | fieldset[disabled] .btn-primary:active, 142 | .btn-primary.disabled.active, 143 | .btn-primary[disabled].active, 144 | fieldset[disabled] .btn-primary.active { 145 | background-color: #265a88; 146 | background-image: none; 147 | } 148 | .btn-success { 149 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 150 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); 151 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); 152 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 153 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 154 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 155 | background-repeat: repeat-x; 156 | border-color: #3e8f3e; 157 | } 158 | .btn-success:hover, 159 | .btn-success:focus { 160 | background-color: #419641; 161 | background-position: 0 -15px; 162 | } 163 | .btn-success:active, 164 | .btn-success.active { 165 | background-color: #419641; 166 | border-color: #3e8f3e; 167 | } 168 | .btn-success.disabled, 169 | .btn-success[disabled], 170 | fieldset[disabled] .btn-success, 171 | .btn-success.disabled:hover, 172 | .btn-success[disabled]:hover, 173 | fieldset[disabled] .btn-success:hover, 174 | .btn-success.disabled:focus, 175 | .btn-success[disabled]:focus, 176 | fieldset[disabled] .btn-success:focus, 177 | .btn-success.disabled.focus, 178 | .btn-success[disabled].focus, 179 | fieldset[disabled] .btn-success.focus, 180 | .btn-success.disabled:active, 181 | .btn-success[disabled]:active, 182 | fieldset[disabled] .btn-success:active, 183 | .btn-success.disabled.active, 184 | .btn-success[disabled].active, 185 | fieldset[disabled] .btn-success.active { 186 | background-color: #419641; 187 | background-image: none; 188 | } 189 | .btn-info { 190 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 191 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 192 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); 193 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 194 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 195 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 196 | background-repeat: repeat-x; 197 | border-color: #28a4c9; 198 | } 199 | .btn-info:hover, 200 | .btn-info:focus { 201 | background-color: #2aabd2; 202 | background-position: 0 -15px; 203 | } 204 | .btn-info:active, 205 | .btn-info.active { 206 | background-color: #2aabd2; 207 | border-color: #28a4c9; 208 | } 209 | .btn-info.disabled, 210 | .btn-info[disabled], 211 | fieldset[disabled] .btn-info, 212 | .btn-info.disabled:hover, 213 | .btn-info[disabled]:hover, 214 | fieldset[disabled] .btn-info:hover, 215 | .btn-info.disabled:focus, 216 | .btn-info[disabled]:focus, 217 | fieldset[disabled] .btn-info:focus, 218 | .btn-info.disabled.focus, 219 | .btn-info[disabled].focus, 220 | fieldset[disabled] .btn-info.focus, 221 | .btn-info.disabled:active, 222 | .btn-info[disabled]:active, 223 | fieldset[disabled] .btn-info:active, 224 | .btn-info.disabled.active, 225 | .btn-info[disabled].active, 226 | fieldset[disabled] .btn-info.active { 227 | background-color: #2aabd2; 228 | background-image: none; 229 | } 230 | .btn-warning { 231 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 232 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 233 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); 234 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 235 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 236 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 237 | background-repeat: repeat-x; 238 | border-color: #e38d13; 239 | } 240 | .btn-warning:hover, 241 | .btn-warning:focus { 242 | background-color: #eb9316; 243 | background-position: 0 -15px; 244 | } 245 | .btn-warning:active, 246 | .btn-warning.active { 247 | background-color: #eb9316; 248 | border-color: #e38d13; 249 | } 250 | .btn-warning.disabled, 251 | .btn-warning[disabled], 252 | fieldset[disabled] .btn-warning, 253 | .btn-warning.disabled:hover, 254 | .btn-warning[disabled]:hover, 255 | fieldset[disabled] .btn-warning:hover, 256 | .btn-warning.disabled:focus, 257 | .btn-warning[disabled]:focus, 258 | fieldset[disabled] .btn-warning:focus, 259 | .btn-warning.disabled.focus, 260 | .btn-warning[disabled].focus, 261 | fieldset[disabled] .btn-warning.focus, 262 | .btn-warning.disabled:active, 263 | .btn-warning[disabled]:active, 264 | fieldset[disabled] .btn-warning:active, 265 | .btn-warning.disabled.active, 266 | .btn-warning[disabled].active, 267 | fieldset[disabled] .btn-warning.active { 268 | background-color: #eb9316; 269 | background-image: none; 270 | } 271 | .btn-danger { 272 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 273 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 274 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); 275 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 276 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 277 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 278 | background-repeat: repeat-x; 279 | border-color: #b92c28; 280 | } 281 | .btn-danger:hover, 282 | .btn-danger:focus { 283 | background-color: #c12e2a; 284 | background-position: 0 -15px; 285 | } 286 | .btn-danger:active, 287 | .btn-danger.active { 288 | background-color: #c12e2a; 289 | border-color: #b92c28; 290 | } 291 | .btn-danger.disabled, 292 | .btn-danger[disabled], 293 | fieldset[disabled] .btn-danger, 294 | .btn-danger.disabled:hover, 295 | .btn-danger[disabled]:hover, 296 | fieldset[disabled] .btn-danger:hover, 297 | .btn-danger.disabled:focus, 298 | .btn-danger[disabled]:focus, 299 | fieldset[disabled] .btn-danger:focus, 300 | .btn-danger.disabled.focus, 301 | .btn-danger[disabled].focus, 302 | fieldset[disabled] .btn-danger.focus, 303 | .btn-danger.disabled:active, 304 | .btn-danger[disabled]:active, 305 | fieldset[disabled] .btn-danger:active, 306 | .btn-danger.disabled.active, 307 | .btn-danger[disabled].active, 308 | fieldset[disabled] .btn-danger.active { 309 | background-color: #c12e2a; 310 | background-image: none; 311 | } 312 | .thumbnail, 313 | .img-thumbnail { 314 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 315 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 316 | } 317 | .dropdown-menu > li > a:hover, 318 | .dropdown-menu > li > a:focus { 319 | background-color: #e8e8e8; 320 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 321 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 322 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 323 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 324 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 325 | background-repeat: repeat-x; 326 | } 327 | .dropdown-menu > .active > a, 328 | .dropdown-menu > .active > a:hover, 329 | .dropdown-menu > .active > a:focus { 330 | background-color: #2e6da4; 331 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 332 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 333 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 334 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 335 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 336 | background-repeat: repeat-x; 337 | } 338 | .navbar-default { 339 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); 340 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); 341 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); 342 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); 343 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 344 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 345 | background-repeat: repeat-x; 346 | border-radius: 4px; 347 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 348 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 349 | } 350 | .navbar-default .navbar-nav > .open > a, 351 | .navbar-default .navbar-nav > .active > a { 352 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 353 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 354 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); 355 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); 356 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); 357 | background-repeat: repeat-x; 358 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 359 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 360 | } 361 | .navbar-brand, 362 | .navbar-nav > li > a { 363 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25); 364 | } 365 | .navbar-inverse { 366 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); 367 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); 368 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); 369 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); 370 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 371 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 372 | background-repeat: repeat-x; 373 | border-radius: 4px; 374 | } 375 | .navbar-inverse .navbar-nav > .open > a, 376 | .navbar-inverse .navbar-nav > .active > a { 377 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); 378 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); 379 | background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); 380 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); 381 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); 382 | background-repeat: repeat-x; 383 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 384 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 385 | } 386 | .navbar-inverse .navbar-brand, 387 | .navbar-inverse .navbar-nav > li > a { 388 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); 389 | } 390 | .navbar-static-top, 391 | .navbar-fixed-top, 392 | .navbar-fixed-bottom { 393 | border-radius: 0; 394 | } 395 | @media (max-width: 767px) { 396 | .navbar .navbar-nav .open .dropdown-menu > .active > a, 397 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, 398 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { 399 | color: #fff; 400 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 401 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 402 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 403 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 405 | background-repeat: repeat-x; 406 | } 407 | } 408 | .alert { 409 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2); 410 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 411 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 412 | } 413 | .alert-success { 414 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 415 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 416 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); 417 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 418 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 419 | background-repeat: repeat-x; 420 | border-color: #b2dba1; 421 | } 422 | .alert-info { 423 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 424 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 425 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); 426 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 427 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 428 | background-repeat: repeat-x; 429 | border-color: #9acfea; 430 | } 431 | .alert-warning { 432 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 433 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 434 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); 435 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 436 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 437 | background-repeat: repeat-x; 438 | border-color: #f5e79e; 439 | } 440 | .alert-danger { 441 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 442 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 443 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); 444 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 445 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 446 | background-repeat: repeat-x; 447 | border-color: #dca7a7; 448 | } 449 | .progress { 450 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 451 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 452 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); 453 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 454 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 455 | background-repeat: repeat-x; 456 | } 457 | .progress-bar { 458 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); 459 | background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); 460 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); 461 | background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); 462 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); 463 | background-repeat: repeat-x; 464 | } 465 | .progress-bar-success { 466 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 467 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); 468 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); 469 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 470 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 471 | background-repeat: repeat-x; 472 | } 473 | .progress-bar-info { 474 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 475 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 476 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); 477 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 478 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 479 | background-repeat: repeat-x; 480 | } 481 | .progress-bar-warning { 482 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 483 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 484 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); 485 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 486 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 487 | background-repeat: repeat-x; 488 | } 489 | .progress-bar-danger { 490 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 491 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); 492 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); 493 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 494 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 495 | background-repeat: repeat-x; 496 | } 497 | .progress-bar-striped { 498 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 499 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 500 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 501 | } 502 | .list-group { 503 | border-radius: 4px; 504 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 505 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 506 | } 507 | .list-group-item.active, 508 | .list-group-item.active:hover, 509 | .list-group-item.active:focus { 510 | text-shadow: 0 -1px 0 #286090; 511 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); 512 | background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); 513 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); 514 | background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); 515 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); 516 | background-repeat: repeat-x; 517 | border-color: #2b669a; 518 | } 519 | .list-group-item.active .badge, 520 | .list-group-item.active:hover .badge, 521 | .list-group-item.active:focus .badge { 522 | text-shadow: none; 523 | } 524 | .panel { 525 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 526 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 527 | } 528 | .panel-default > .panel-heading { 529 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 530 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 531 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 532 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 533 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 534 | background-repeat: repeat-x; 535 | } 536 | .panel-primary > .panel-heading { 537 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 538 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 539 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 540 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 541 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 542 | background-repeat: repeat-x; 543 | } 544 | .panel-success > .panel-heading { 545 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 546 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 547 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); 548 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 549 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 550 | background-repeat: repeat-x; 551 | } 552 | .panel-info > .panel-heading { 553 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 554 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 555 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); 556 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 557 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 558 | background-repeat: repeat-x; 559 | } 560 | .panel-warning > .panel-heading { 561 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 562 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 563 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); 564 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 565 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 566 | background-repeat: repeat-x; 567 | } 568 | .panel-danger > .panel-heading { 569 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 570 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 571 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); 572 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 573 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 574 | background-repeat: repeat-x; 575 | } 576 | .well { 577 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 578 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 579 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); 580 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 581 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 582 | background-repeat: repeat-x; 583 | border-color: #dcdcdc; 584 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 585 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 586 | } 587 | /*# sourceMappingURL=bootstrap-theme.css.map */ 588 | -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josemnbcamacho/aspnet-lambda-now/650b6071ed48b5734639a0397f2a788aac1bc922/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josemnbcamacho/aspnet-lambda-now/650b6071ed48b5734639a0397f2a788aac1bc922/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josemnbcamacho/aspnet-lambda-now/650b6071ed48b5734639a0397f2a788aac1bc922/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josemnbcamacho/aspnet-lambda-now/650b6071ed48b5734639a0397f2a788aac1bc922/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation-unobtrusive/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation-unobtrusive", 3 | "homepage": "https://github.com/aspnet/jquery-validation-unobtrusive", 4 | "version": "3.2.9", 5 | "_release": "3.2.9", 6 | "_resolution": { 7 | "type": "version", 8 | "tag": "v3.2.9", 9 | "commit": "a91f5401898e125f10771c5f5f0909d8c4c82396" 10 | }, 11 | "_source": "https://github.com/aspnet/jquery-validation-unobtrusive.git", 12 | "_target": "^3.2.9", 13 | "_originalSource": "jquery-validation-unobtrusive", 14 | "_direct": true 15 | } -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js: -------------------------------------------------------------------------------- 1 | // Unobtrusive validation support library for jQuery and jQuery Validate 2 | // Copyright (C) Microsoft Corporation. All rights reserved. 3 | // @version v3.2.9 4 | 5 | /*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */ 6 | /*global document: false, jQuery: false */ 7 | 8 | (function (factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | // AMD. Register as an anonymous module. 11 | define("jquery.validate.unobtrusive", ['jquery.validation'], factory); 12 | } else if (typeof module === 'object' && module.exports) { 13 | // CommonJS-like environments that support module.exports 14 | module.exports = factory(require('jquery-validation')); 15 | } else { 16 | // Browser global 17 | jQuery.validator.unobtrusive = factory(jQuery); 18 | } 19 | }(function ($) { 20 | var $jQval = $.validator, 21 | adapters, 22 | data_validation = "unobtrusiveValidation"; 23 | 24 | function setValidationValues(options, ruleName, value) { 25 | options.rules[ruleName] = value; 26 | if (options.message) { 27 | options.messages[ruleName] = options.message; 28 | } 29 | } 30 | 31 | function splitAndTrim(value) { 32 | return value.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/g); 33 | } 34 | 35 | function escapeAttributeValue(value) { 36 | // As mentioned on http://api.jquery.com/category/selectors/ 37 | return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1"); 38 | } 39 | 40 | function getModelPrefix(fieldName) { 41 | return fieldName.substr(0, fieldName.lastIndexOf(".") + 1); 42 | } 43 | 44 | function appendModelPrefix(value, prefix) { 45 | if (value.indexOf("*.") === 0) { 46 | value = value.replace("*.", prefix); 47 | } 48 | return value; 49 | } 50 | 51 | function onError(error, inputElement) { // 'this' is the form element 52 | var container = $(this).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"), 53 | replaceAttrValue = container.attr("data-valmsg-replace"), 54 | replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null; 55 | 56 | container.removeClass("field-validation-valid").addClass("field-validation-error"); 57 | error.data("unobtrusiveContainer", container); 58 | 59 | if (replace) { 60 | container.empty(); 61 | error.removeClass("input-validation-error").appendTo(container); 62 | } 63 | else { 64 | error.hide(); 65 | } 66 | } 67 | 68 | function onErrors(event, validator) { // 'this' is the form element 69 | var container = $(this).find("[data-valmsg-summary=true]"), 70 | list = container.find("ul"); 71 | 72 | if (list && list.length && validator.errorList.length) { 73 | list.empty(); 74 | container.addClass("validation-summary-errors").removeClass("validation-summary-valid"); 75 | 76 | $.each(validator.errorList, function () { 77 | $("
  • ").html(this.message).appendTo(list); 78 | }); 79 | } 80 | } 81 | 82 | function onSuccess(error) { // 'this' is the form element 83 | var container = error.data("unobtrusiveContainer"); 84 | 85 | if (container) { 86 | var replaceAttrValue = container.attr("data-valmsg-replace"), 87 | replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) : null; 88 | 89 | container.addClass("field-validation-valid").removeClass("field-validation-error"); 90 | error.removeData("unobtrusiveContainer"); 91 | 92 | if (replace) { 93 | container.empty(); 94 | } 95 | } 96 | } 97 | 98 | function onReset(event) { // 'this' is the form element 99 | var $form = $(this), 100 | key = '__jquery_unobtrusive_validation_form_reset'; 101 | if ($form.data(key)) { 102 | return; 103 | } 104 | // Set a flag that indicates we're currently resetting the form. 105 | $form.data(key, true); 106 | try { 107 | $form.data("validator").resetForm(); 108 | } finally { 109 | $form.removeData(key); 110 | } 111 | 112 | $form.find(".validation-summary-errors") 113 | .addClass("validation-summary-valid") 114 | .removeClass("validation-summary-errors"); 115 | $form.find(".field-validation-error") 116 | .addClass("field-validation-valid") 117 | .removeClass("field-validation-error") 118 | .removeData("unobtrusiveContainer") 119 | .find(">*") // If we were using valmsg-replace, get the underlying error 120 | .removeData("unobtrusiveContainer"); 121 | } 122 | 123 | function validationInfo(form) { 124 | var $form = $(form), 125 | result = $form.data(data_validation), 126 | onResetProxy = $.proxy(onReset, form), 127 | defaultOptions = $jQval.unobtrusive.options || {}, 128 | execInContext = function (name, args) { 129 | var func = defaultOptions[name]; 130 | func && $.isFunction(func) && func.apply(form, args); 131 | }; 132 | 133 | if (!result) { 134 | result = { 135 | options: { // options structure passed to jQuery Validate's validate() method 136 | errorClass: defaultOptions.errorClass || "input-validation-error", 137 | errorElement: defaultOptions.errorElement || "span", 138 | errorPlacement: function () { 139 | onError.apply(form, arguments); 140 | execInContext("errorPlacement", arguments); 141 | }, 142 | invalidHandler: function () { 143 | onErrors.apply(form, arguments); 144 | execInContext("invalidHandler", arguments); 145 | }, 146 | messages: {}, 147 | rules: {}, 148 | success: function () { 149 | onSuccess.apply(form, arguments); 150 | execInContext("success", arguments); 151 | } 152 | }, 153 | attachValidation: function () { 154 | $form 155 | .off("reset." + data_validation, onResetProxy) 156 | .on("reset." + data_validation, onResetProxy) 157 | .validate(this.options); 158 | }, 159 | validate: function () { // a validation function that is called by unobtrusive Ajax 160 | $form.validate(); 161 | return $form.valid(); 162 | } 163 | }; 164 | $form.data(data_validation, result); 165 | } 166 | 167 | return result; 168 | } 169 | 170 | $jQval.unobtrusive = { 171 | adapters: [], 172 | 173 | parseElement: function (element, skipAttach) { 174 | /// 175 | /// Parses a single HTML element for unobtrusive validation attributes. 176 | /// 177 | /// The HTML element to be parsed. 178 | /// [Optional] true to skip attaching the 179 | /// validation to the form. If parsing just this single element, you should specify true. 180 | /// If parsing several elements, you should specify false, and manually attach the validation 181 | /// to the form when you are finished. The default is false. 182 | var $element = $(element), 183 | form = $element.parents("form")[0], 184 | valInfo, rules, messages; 185 | 186 | if (!form) { // Cannot do client-side validation without a form 187 | return; 188 | } 189 | 190 | valInfo = validationInfo(form); 191 | valInfo.options.rules[element.name] = rules = {}; 192 | valInfo.options.messages[element.name] = messages = {}; 193 | 194 | $.each(this.adapters, function () { 195 | var prefix = "data-val-" + this.name, 196 | message = $element.attr(prefix), 197 | paramValues = {}; 198 | 199 | if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy) 200 | prefix += "-"; 201 | 202 | $.each(this.params, function () { 203 | paramValues[this] = $element.attr(prefix + this); 204 | }); 205 | 206 | this.adapt({ 207 | element: element, 208 | form: form, 209 | message: message, 210 | params: paramValues, 211 | rules: rules, 212 | messages: messages 213 | }); 214 | } 215 | }); 216 | 217 | $.extend(rules, { "__dummy__": true }); 218 | 219 | if (!skipAttach) { 220 | valInfo.attachValidation(); 221 | } 222 | }, 223 | 224 | parse: function (selector) { 225 | /// 226 | /// Parses all the HTML elements in the specified selector. It looks for input elements decorated 227 | /// with the [data-val=true] attribute value and enables validation according to the data-val-* 228 | /// attribute values. 229 | /// 230 | /// Any valid jQuery selector. 231 | 232 | // $forms includes all forms in selector's DOM hierarchy (parent, children and self) that have at least one 233 | // element with data-val=true 234 | var $selector = $(selector), 235 | $forms = $selector.parents() 236 | .addBack() 237 | .filter("form") 238 | .add($selector.find("form")) 239 | .has("[data-val=true]"); 240 | 241 | $selector.find("[data-val=true]").each(function () { 242 | $jQval.unobtrusive.parseElement(this, true); 243 | }); 244 | 245 | $forms.each(function () { 246 | var info = validationInfo(this); 247 | if (info) { 248 | info.attachValidation(); 249 | } 250 | }); 251 | } 252 | }; 253 | 254 | adapters = $jQval.unobtrusive.adapters; 255 | 256 | adapters.add = function (adapterName, params, fn) { 257 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation. 258 | /// The name of the adapter to be added. This matches the name used 259 | /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). 260 | /// [Optional] An array of parameter names (strings) that will 261 | /// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and 262 | /// mmmm is the parameter name). 263 | /// The function to call, which adapts the values from the HTML 264 | /// attributes into jQuery Validate rules and/or messages. 265 | /// 266 | if (!fn) { // Called with no params, just a function 267 | fn = params; 268 | params = []; 269 | } 270 | this.push({ name: adapterName, params: params, adapt: fn }); 271 | return this; 272 | }; 273 | 274 | adapters.addBool = function (adapterName, ruleName) { 275 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where 276 | /// the jQuery Validate validation rule has no parameter values. 277 | /// The name of the adapter to be added. This matches the name used 278 | /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). 279 | /// [Optional] The name of the jQuery Validate rule. If not provided, the value 280 | /// of adapterName will be used instead. 281 | /// 282 | return this.add(adapterName, function (options) { 283 | setValidationValues(options, ruleName || adapterName, true); 284 | }); 285 | }; 286 | 287 | adapters.addMinMax = function (adapterName, minRuleName, maxRuleName, minMaxRuleName, minAttribute, maxAttribute) { 288 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where 289 | /// the jQuery Validate validation has three potential rules (one for min-only, one for max-only, and 290 | /// one for min-and-max). The HTML parameters are expected to be named -min and -max. 291 | /// The name of the adapter to be added. This matches the name used 292 | /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). 293 | /// The name of the jQuery Validate rule to be used when you only 294 | /// have a minimum value. 295 | /// The name of the jQuery Validate rule to be used when you only 296 | /// have a maximum value. 297 | /// The name of the jQuery Validate rule to be used when you 298 | /// have both a minimum and maximum value. 299 | /// [Optional] The name of the HTML attribute that 300 | /// contains the minimum value. The default is "min". 301 | /// [Optional] The name of the HTML attribute that 302 | /// contains the maximum value. The default is "max". 303 | /// 304 | return this.add(adapterName, [minAttribute || "min", maxAttribute || "max"], function (options) { 305 | var min = options.params.min, 306 | max = options.params.max; 307 | 308 | if (min && max) { 309 | setValidationValues(options, minMaxRuleName, [min, max]); 310 | } 311 | else if (min) { 312 | setValidationValues(options, minRuleName, min); 313 | } 314 | else if (max) { 315 | setValidationValues(options, maxRuleName, max); 316 | } 317 | }); 318 | }; 319 | 320 | adapters.addSingleVal = function (adapterName, attribute, ruleName) { 321 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where 322 | /// the jQuery Validate validation rule has a single value. 323 | /// The name of the adapter to be added. This matches the name used 324 | /// in the data-val-nnnn HTML attribute(where nnnn is the adapter name). 325 | /// [Optional] The name of the HTML attribute that contains the value. 326 | /// The default is "val". 327 | /// [Optional] The name of the jQuery Validate rule. If not provided, the value 328 | /// of adapterName will be used instead. 329 | /// 330 | return this.add(adapterName, [attribute || "val"], function (options) { 331 | setValidationValues(options, ruleName || adapterName, options.params[attribute]); 332 | }); 333 | }; 334 | 335 | $jQval.addMethod("__dummy__", function (value, element, params) { 336 | return true; 337 | }); 338 | 339 | $jQval.addMethod("regex", function (value, element, params) { 340 | var match; 341 | if (this.optional(element)) { 342 | return true; 343 | } 344 | 345 | match = new RegExp(params).exec(value); 346 | return (match && (match.index === 0) && (match[0].length === value.length)); 347 | }); 348 | 349 | $jQval.addMethod("nonalphamin", function (value, element, nonalphamin) { 350 | var match; 351 | if (nonalphamin) { 352 | match = value.match(/\W/g); 353 | match = match && match.length >= nonalphamin; 354 | } 355 | return match; 356 | }); 357 | 358 | if ($jQval.methods.extension) { 359 | adapters.addSingleVal("accept", "mimtype"); 360 | adapters.addSingleVal("extension", "extension"); 361 | } else { 362 | // for backward compatibility, when the 'extension' validation method does not exist, such as with versions 363 | // of JQuery Validation plugin prior to 1.10, we should use the 'accept' method for 364 | // validating the extension, and ignore mime-type validations as they are not supported. 365 | adapters.addSingleVal("extension", "extension", "accept"); 366 | } 367 | 368 | adapters.addSingleVal("regex", "pattern"); 369 | adapters.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"); 370 | adapters.addMinMax("length", "minlength", "maxlength", "rangelength").addMinMax("range", "min", "max", "range"); 371 | adapters.addMinMax("minlength", "minlength").addMinMax("maxlength", "minlength", "maxlength"); 372 | adapters.add("equalto", ["other"], function (options) { 373 | var prefix = getModelPrefix(options.element.name), 374 | other = options.params.other, 375 | fullOtherName = appendModelPrefix(other, prefix), 376 | element = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(fullOtherName) + "']")[0]; 377 | 378 | setValidationValues(options, "equalTo", element); 379 | }); 380 | adapters.add("required", function (options) { 381 | // jQuery Validate equates "required" with "mandatory" for checkbox elements 382 | if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") { 383 | setValidationValues(options, "required", true); 384 | } 385 | }); 386 | adapters.add("remote", ["url", "type", "additionalfields"], function (options) { 387 | var value = { 388 | url: options.params.url, 389 | type: options.params.type || "GET", 390 | data: {} 391 | }, 392 | prefix = getModelPrefix(options.element.name); 393 | 394 | $.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) { 395 | var paramName = appendModelPrefix(fieldName, prefix); 396 | value.data[paramName] = function () { 397 | var field = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(paramName) + "']"); 398 | // For checkboxes and radio buttons, only pick up values from checked fields. 399 | if (field.is(":checkbox")) { 400 | return field.filter(":checked").val() || field.filter(":hidden").val() || ''; 401 | } 402 | else if (field.is(":radio")) { 403 | return field.filter(":checked").val() || ''; 404 | } 405 | return field.val(); 406 | }; 407 | }); 408 | 409 | setValidationValues(options, "remote", value); 410 | }); 411 | adapters.add("password", ["min", "nonalphamin", "regex"], function (options) { 412 | if (options.params.min) { 413 | setValidationValues(options, "minlength", options.params.min); 414 | } 415 | if (options.params.nonalphamin) { 416 | setValidationValues(options, "nonalphamin", options.params.nonalphamin); 417 | } 418 | if (options.params.regex) { 419 | setValidationValues(options, "regex", options.params.regex); 420 | } 421 | }); 422 | adapters.add("fileextensions", ["extensions"], function (options) { 423 | setValidationValues(options, "extension", options.params.extensions); 424 | }); 425 | 426 | $(function () { 427 | $jQval.unobtrusive.parse(document); 428 | }); 429 | 430 | return $jQval.unobtrusive; 431 | })); -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation", 3 | "homepage": "https://jqueryvalidation.org/", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/jquery-validation/jquery-validation.git" 7 | }, 8 | "authors": [ 9 | "Jörn Zaefferer " 10 | ], 11 | "description": "Form validation made easy", 12 | "main": "dist/jquery.validate.js", 13 | "keywords": [ 14 | "forms", 15 | "validation", 16 | "validate" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "demo", 25 | "lib" 26 | ], 27 | "dependencies": { 28 | "jquery": ">= 1.7.2" 29 | }, 30 | "version": "1.17.0", 31 | "_release": "1.17.0", 32 | "_resolution": { 33 | "type": "version", 34 | "tag": "1.17.0", 35 | "commit": "fc9b12d3bfaa2d0c04605855b896edb2934c0772" 36 | }, 37 | "_source": "https://github.com/jzaefferer/jquery-validation.git", 38 | "_target": "^1.17.0", 39 | "_originalSource": "jquery-validation", 40 | "_direct": true 41 | } -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation/dist/additional-methods.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Validation Plugin v1.17.0 3 | * 4 | * https://jqueryvalidation.org/ 5 | * 6 | * Copyright (c) 2017 Jörn Zaefferer 7 | * Released under the MIT license 8 | */ 9 | (function( factory ) { 10 | if ( typeof define === "function" && define.amd ) { 11 | define( ["jquery", "./jquery.validate"], factory ); 12 | } else if (typeof module === "object" && module.exports) { 13 | module.exports = factory( require( "jquery" ) ); 14 | } else { 15 | factory( jQuery ); 16 | } 17 | }(function( $ ) { 18 | 19 | ( function() { 20 | 21 | function stripHtml( value ) { 22 | 23 | // Remove html tags and space chars 24 | return value.replace( /<.[^<>]*?>/g, " " ).replace( / | /gi, " " ) 25 | 26 | // Remove punctuation 27 | .replace( /[.(),;:!?%#$'\"_+=\/\-“”’]*/g, "" ); 28 | } 29 | 30 | $.validator.addMethod( "maxWords", function( value, element, params ) { 31 | return this.optional( element ) || stripHtml( value ).match( /\b\w+\b/g ).length <= params; 32 | }, $.validator.format( "Please enter {0} words or less." ) ); 33 | 34 | $.validator.addMethod( "minWords", function( value, element, params ) { 35 | return this.optional( element ) || stripHtml( value ).match( /\b\w+\b/g ).length >= params; 36 | }, $.validator.format( "Please enter at least {0} words." ) ); 37 | 38 | $.validator.addMethod( "rangeWords", function( value, element, params ) { 39 | var valueStripped = stripHtml( value ), 40 | regex = /\b\w+\b/g; 41 | return this.optional( element ) || valueStripped.match( regex ).length >= params[ 0 ] && valueStripped.match( regex ).length <= params[ 1 ]; 42 | }, $.validator.format( "Please enter between {0} and {1} words." ) ); 43 | 44 | }() ); 45 | 46 | // Accept a value from a file input based on a required mimetype 47 | $.validator.addMethod( "accept", function( value, element, param ) { 48 | 49 | // Split mime on commas in case we have multiple types we can accept 50 | var typeParam = typeof param === "string" ? param.replace( /\s/g, "" ) : "image/*", 51 | optionalValue = this.optional( element ), 52 | i, file, regex; 53 | 54 | // Element is optional 55 | if ( optionalValue ) { 56 | return optionalValue; 57 | } 58 | 59 | if ( $( element ).attr( "type" ) === "file" ) { 60 | 61 | // Escape string to be used in the regex 62 | // see: https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex 63 | // Escape also "/*" as "/.*" as a wildcard 64 | typeParam = typeParam 65 | .replace( /[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g, "\\$&" ) 66 | .replace( /,/g, "|" ) 67 | .replace( /\/\*/g, "/.*" ); 68 | 69 | // Check if the element has a FileList before checking each file 70 | if ( element.files && element.files.length ) { 71 | regex = new RegExp( ".?(" + typeParam + ")$", "i" ); 72 | for ( i = 0; i < element.files.length; i++ ) { 73 | file = element.files[ i ]; 74 | 75 | // Grab the mimetype from the loaded file, verify it matches 76 | if ( !file.type.match( regex ) ) { 77 | return false; 78 | } 79 | } 80 | } 81 | } 82 | 83 | // Either return true because we've validated each file, or because the 84 | // browser does not support element.files and the FileList feature 85 | return true; 86 | }, $.validator.format( "Please enter a value with a valid mimetype." ) ); 87 | 88 | $.validator.addMethod( "alphanumeric", function( value, element ) { 89 | return this.optional( element ) || /^\w+$/i.test( value ); 90 | }, "Letters, numbers, and underscores only please" ); 91 | 92 | /* 93 | * Dutch bank account numbers (not 'giro' numbers) have 9 digits 94 | * and pass the '11 check'. 95 | * We accept the notation with spaces, as that is common. 96 | * acceptable: 123456789 or 12 34 56 789 97 | */ 98 | $.validator.addMethod( "bankaccountNL", function( value, element ) { 99 | if ( this.optional( element ) ) { 100 | return true; 101 | } 102 | if ( !( /^[0-9]{9}|([0-9]{2} ){3}[0-9]{3}$/.test( value ) ) ) { 103 | return false; 104 | } 105 | 106 | // Now '11 check' 107 | var account = value.replace( / /g, "" ), // Remove spaces 108 | sum = 0, 109 | len = account.length, 110 | pos, factor, digit; 111 | for ( pos = 0; pos < len; pos++ ) { 112 | factor = len - pos; 113 | digit = account.substring( pos, pos + 1 ); 114 | sum = sum + factor * digit; 115 | } 116 | return sum % 11 === 0; 117 | }, "Please specify a valid bank account number" ); 118 | 119 | $.validator.addMethod( "bankorgiroaccountNL", function( value, element ) { 120 | return this.optional( element ) || 121 | ( $.validator.methods.bankaccountNL.call( this, value, element ) ) || 122 | ( $.validator.methods.giroaccountNL.call( this, value, element ) ); 123 | }, "Please specify a valid bank or giro account number" ); 124 | 125 | /** 126 | * BIC is the business identifier code (ISO 9362). This BIC check is not a guarantee for authenticity. 127 | * 128 | * BIC pattern: BBBBCCLLbbb (8 or 11 characters long; bbb is optional) 129 | * 130 | * Validation is case-insensitive. Please make sure to normalize input yourself. 131 | * 132 | * BIC definition in detail: 133 | * - First 4 characters - bank code (only letters) 134 | * - Next 2 characters - ISO 3166-1 alpha-2 country code (only letters) 135 | * - Next 2 characters - location code (letters and digits) 136 | * a. shall not start with '0' or '1' 137 | * b. second character must be a letter ('O' is not allowed) or digit ('0' for test (therefore not allowed), '1' denoting passive participant, '2' typically reverse-billing) 138 | * - Last 3 characters - branch code, optional (shall not start with 'X' except in case of 'XXX' for primary office) (letters and digits) 139 | */ 140 | $.validator.addMethod( "bic", function( value, element ) { 141 | return this.optional( element ) || /^([A-Z]{6}[A-Z2-9][A-NP-Z1-9])(X{3}|[A-WY-Z0-9][A-Z0-9]{2})?$/.test( value.toUpperCase() ); 142 | }, "Please specify a valid BIC code" ); 143 | 144 | /* 145 | * Código de identificación fiscal ( CIF ) is the tax identification code for Spanish legal entities 146 | * Further rules can be found in Spanish on http://es.wikipedia.org/wiki/C%C3%B3digo_de_identificaci%C3%B3n_fiscal 147 | * 148 | * Spanish CIF structure: 149 | * 150 | * [ T ][ P ][ P ][ N ][ N ][ N ][ N ][ N ][ C ] 151 | * 152 | * Where: 153 | * 154 | * T: 1 character. Kind of Organization Letter: [ABCDEFGHJKLMNPQRSUVW] 155 | * P: 2 characters. Province. 156 | * N: 5 characters. Secuencial Number within the province. 157 | * C: 1 character. Control Digit: [0-9A-J]. 158 | * 159 | * [ T ]: Kind of Organizations. Possible values: 160 | * 161 | * A. Corporations 162 | * B. LLCs 163 | * C. General partnerships 164 | * D. Companies limited partnerships 165 | * E. Communities of goods 166 | * F. Cooperative Societies 167 | * G. Associations 168 | * H. Communities of homeowners in horizontal property regime 169 | * J. Civil Societies 170 | * K. Old format 171 | * L. Old format 172 | * M. Old format 173 | * N. Nonresident entities 174 | * P. Local authorities 175 | * Q. Autonomous bodies, state or not, and the like, and congregations and religious institutions 176 | * R. Congregations and religious institutions (since 2008 ORDER EHA/451/2008) 177 | * S. Organs of State Administration and regions 178 | * V. Agrarian Transformation 179 | * W. Permanent establishments of non-resident in Spain 180 | * 181 | * [ C ]: Control Digit. It can be a number or a letter depending on T value: 182 | * [ T ] --> [ C ] 183 | * ------ ---------- 184 | * A Number 185 | * B Number 186 | * E Number 187 | * H Number 188 | * K Letter 189 | * P Letter 190 | * Q Letter 191 | * S Letter 192 | * 193 | */ 194 | $.validator.addMethod( "cifES", function( value, element ) { 195 | "use strict"; 196 | 197 | if ( this.optional( element ) ) { 198 | return true; 199 | } 200 | 201 | var cifRegEx = new RegExp( /^([ABCDEFGHJKLMNPQRSUVW])(\d{7})([0-9A-J])$/gi ); 202 | var letter = value.substring( 0, 1 ), // [ T ] 203 | number = value.substring( 1, 8 ), // [ P ][ P ][ N ][ N ][ N ][ N ][ N ] 204 | control = value.substring( 8, 9 ), // [ C ] 205 | all_sum = 0, 206 | even_sum = 0, 207 | odd_sum = 0, 208 | i, n, 209 | control_digit, 210 | control_letter; 211 | 212 | function isOdd( n ) { 213 | return n % 2 === 0; 214 | } 215 | 216 | // Quick format test 217 | if ( value.length !== 9 || !cifRegEx.test( value ) ) { 218 | return false; 219 | } 220 | 221 | for ( i = 0; i < number.length; i++ ) { 222 | n = parseInt( number[ i ], 10 ); 223 | 224 | // Odd positions 225 | if ( isOdd( i ) ) { 226 | 227 | // Odd positions are multiplied first. 228 | n *= 2; 229 | 230 | // If the multiplication is bigger than 10 we need to adjust 231 | odd_sum += n < 10 ? n : n - 9; 232 | 233 | // Even positions 234 | // Just sum them 235 | } else { 236 | even_sum += n; 237 | } 238 | } 239 | 240 | all_sum = even_sum + odd_sum; 241 | control_digit = ( 10 - ( all_sum ).toString().substr( -1 ) ).toString(); 242 | control_digit = parseInt( control_digit, 10 ) > 9 ? "0" : control_digit; 243 | control_letter = "JABCDEFGHI".substr( control_digit, 1 ).toString(); 244 | 245 | // Control must be a digit 246 | if ( letter.match( /[ABEH]/ ) ) { 247 | return control === control_digit; 248 | 249 | // Control must be a letter 250 | } else if ( letter.match( /[KPQS]/ ) ) { 251 | return control === control_letter; 252 | } 253 | 254 | // Can be either 255 | return control === control_digit || control === control_letter; 256 | 257 | }, "Please specify a valid CIF number." ); 258 | 259 | /* 260 | * Brazillian CPF number (Cadastrado de Pessoas Físicas) is the equivalent of a Brazilian tax registration number. 261 | * CPF numbers have 11 digits in total: 9 numbers followed by 2 check numbers that are being used for validation. 262 | */ 263 | $.validator.addMethod( "cpfBR", function( value ) { 264 | 265 | // Removing special characters from value 266 | value = value.replace( /([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g, "" ); 267 | 268 | // Checking value to have 11 digits only 269 | if ( value.length !== 11 ) { 270 | return false; 271 | } 272 | 273 | var sum = 0, 274 | firstCN, secondCN, checkResult, i; 275 | 276 | firstCN = parseInt( value.substring( 9, 10 ), 10 ); 277 | secondCN = parseInt( value.substring( 10, 11 ), 10 ); 278 | 279 | checkResult = function( sum, cn ) { 280 | var result = ( sum * 10 ) % 11; 281 | if ( ( result === 10 ) || ( result === 11 ) ) { 282 | result = 0; 283 | } 284 | return ( result === cn ); 285 | }; 286 | 287 | // Checking for dump data 288 | if ( value === "" || 289 | value === "00000000000" || 290 | value === "11111111111" || 291 | value === "22222222222" || 292 | value === "33333333333" || 293 | value === "44444444444" || 294 | value === "55555555555" || 295 | value === "66666666666" || 296 | value === "77777777777" || 297 | value === "88888888888" || 298 | value === "99999999999" 299 | ) { 300 | return false; 301 | } 302 | 303 | // Step 1 - using first Check Number: 304 | for ( i = 1; i <= 9; i++ ) { 305 | sum = sum + parseInt( value.substring( i - 1, i ), 10 ) * ( 11 - i ); 306 | } 307 | 308 | // If first Check Number (CN) is valid, move to Step 2 - using second Check Number: 309 | if ( checkResult( sum, firstCN ) ) { 310 | sum = 0; 311 | for ( i = 1; i <= 10; i++ ) { 312 | sum = sum + parseInt( value.substring( i - 1, i ), 10 ) * ( 12 - i ); 313 | } 314 | return checkResult( sum, secondCN ); 315 | } 316 | return false; 317 | 318 | }, "Please specify a valid CPF number" ); 319 | 320 | // https://jqueryvalidation.org/creditcard-method/ 321 | // based on https://en.wikipedia.org/wiki/Luhn_algorithm 322 | $.validator.addMethod( "creditcard", function( value, element ) { 323 | if ( this.optional( element ) ) { 324 | return "dependency-mismatch"; 325 | } 326 | 327 | // Accept only spaces, digits and dashes 328 | if ( /[^0-9 \-]+/.test( value ) ) { 329 | return false; 330 | } 331 | 332 | var nCheck = 0, 333 | nDigit = 0, 334 | bEven = false, 335 | n, cDigit; 336 | 337 | value = value.replace( /\D/g, "" ); 338 | 339 | // Basing min and max length on 340 | // https://developer.ean.com/general_info/Valid_Credit_Card_Types 341 | if ( value.length < 13 || value.length > 19 ) { 342 | return false; 343 | } 344 | 345 | for ( n = value.length - 1; n >= 0; n-- ) { 346 | cDigit = value.charAt( n ); 347 | nDigit = parseInt( cDigit, 10 ); 348 | if ( bEven ) { 349 | if ( ( nDigit *= 2 ) > 9 ) { 350 | nDigit -= 9; 351 | } 352 | } 353 | 354 | nCheck += nDigit; 355 | bEven = !bEven; 356 | } 357 | 358 | return ( nCheck % 10 ) === 0; 359 | }, "Please enter a valid credit card number." ); 360 | 361 | /* NOTICE: Modified version of Castle.Components.Validator.CreditCardValidator 362 | * Redistributed under the the Apache License 2.0 at http://www.apache.org/licenses/LICENSE-2.0 363 | * Valid Types: mastercard, visa, amex, dinersclub, enroute, discover, jcb, unknown, all (overrides all other settings) 364 | */ 365 | $.validator.addMethod( "creditcardtypes", function( value, element, param ) { 366 | if ( /[^0-9\-]+/.test( value ) ) { 367 | return false; 368 | } 369 | 370 | value = value.replace( /\D/g, "" ); 371 | 372 | var validTypes = 0x0000; 373 | 374 | if ( param.mastercard ) { 375 | validTypes |= 0x0001; 376 | } 377 | if ( param.visa ) { 378 | validTypes |= 0x0002; 379 | } 380 | if ( param.amex ) { 381 | validTypes |= 0x0004; 382 | } 383 | if ( param.dinersclub ) { 384 | validTypes |= 0x0008; 385 | } 386 | if ( param.enroute ) { 387 | validTypes |= 0x0010; 388 | } 389 | if ( param.discover ) { 390 | validTypes |= 0x0020; 391 | } 392 | if ( param.jcb ) { 393 | validTypes |= 0x0040; 394 | } 395 | if ( param.unknown ) { 396 | validTypes |= 0x0080; 397 | } 398 | if ( param.all ) { 399 | validTypes = 0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0010 | 0x0020 | 0x0040 | 0x0080; 400 | } 401 | if ( validTypes & 0x0001 && /^(5[12345])/.test( value ) ) { // Mastercard 402 | return value.length === 16; 403 | } 404 | if ( validTypes & 0x0002 && /^(4)/.test( value ) ) { // Visa 405 | return value.length === 16; 406 | } 407 | if ( validTypes & 0x0004 && /^(3[47])/.test( value ) ) { // Amex 408 | return value.length === 15; 409 | } 410 | if ( validTypes & 0x0008 && /^(3(0[012345]|[68]))/.test( value ) ) { // Dinersclub 411 | return value.length === 14; 412 | } 413 | if ( validTypes & 0x0010 && /^(2(014|149))/.test( value ) ) { // Enroute 414 | return value.length === 15; 415 | } 416 | if ( validTypes & 0x0020 && /^(6011)/.test( value ) ) { // Discover 417 | return value.length === 16; 418 | } 419 | if ( validTypes & 0x0040 && /^(3)/.test( value ) ) { // Jcb 420 | return value.length === 16; 421 | } 422 | if ( validTypes & 0x0040 && /^(2131|1800)/.test( value ) ) { // Jcb 423 | return value.length === 15; 424 | } 425 | if ( validTypes & 0x0080 ) { // Unknown 426 | return true; 427 | } 428 | return false; 429 | }, "Please enter a valid credit card number." ); 430 | 431 | /** 432 | * Validates currencies with any given symbols by @jameslouiz 433 | * Symbols can be optional or required. Symbols required by default 434 | * 435 | * Usage examples: 436 | * currency: ["£", false] - Use false for soft currency validation 437 | * currency: ["$", false] 438 | * currency: ["RM", false] - also works with text based symbols such as "RM" - Malaysia Ringgit etc 439 | * 440 | * 441 | * 442 | * Soft symbol checking 443 | * currencyInput: { 444 | * currency: ["$", false] 445 | * } 446 | * 447 | * Strict symbol checking (default) 448 | * currencyInput: { 449 | * currency: "$" 450 | * //OR 451 | * currency: ["$", true] 452 | * } 453 | * 454 | * Multiple Symbols 455 | * currencyInput: { 456 | * currency: "$,£,¢" 457 | * } 458 | */ 459 | $.validator.addMethod( "currency", function( value, element, param ) { 460 | var isParamString = typeof param === "string", 461 | symbol = isParamString ? param : param[ 0 ], 462 | soft = isParamString ? true : param[ 1 ], 463 | regex; 464 | 465 | symbol = symbol.replace( /,/g, "" ); 466 | symbol = soft ? symbol + "]" : symbol + "]?"; 467 | regex = "^[" + symbol + "([1-9]{1}[0-9]{0,2}(\\,[0-9]{3})*(\\.[0-9]{0,2})?|[1-9]{1}[0-9]{0,}(\\.[0-9]{0,2})?|0(\\.[0-9]{0,2})?|(\\.[0-9]{1,2})?)$"; 468 | regex = new RegExp( regex ); 469 | return this.optional( element ) || regex.test( value ); 470 | 471 | }, "Please specify a valid currency" ); 472 | 473 | $.validator.addMethod( "dateFA", function( value, element ) { 474 | return this.optional( element ) || /^[1-4]\d{3}\/((0?[1-6]\/((3[0-1])|([1-2][0-9])|(0?[1-9])))|((1[0-2]|(0?[7-9]))\/(30|([1-2][0-9])|(0?[1-9]))))$/.test( value ); 475 | }, $.validator.messages.date ); 476 | 477 | /** 478 | * Return true, if the value is a valid date, also making this formal check dd/mm/yyyy. 479 | * 480 | * @example $.validator.methods.date("01/01/1900") 481 | * @result true 482 | * 483 | * @example $.validator.methods.date("01/13/1990") 484 | * @result false 485 | * 486 | * @example $.validator.methods.date("01.01.1900") 487 | * @result false 488 | * 489 | * @example 490 | * @desc Declares an optional input element whose value must be a valid date. 491 | * 492 | * @name $.validator.methods.dateITA 493 | * @type Boolean 494 | * @cat Plugins/Validate/Methods 495 | */ 496 | $.validator.addMethod( "dateITA", function( value, element ) { 497 | var check = false, 498 | re = /^\d{1,2}\/\d{1,2}\/\d{4}$/, 499 | adata, gg, mm, aaaa, xdata; 500 | if ( re.test( value ) ) { 501 | adata = value.split( "/" ); 502 | gg = parseInt( adata[ 0 ], 10 ); 503 | mm = parseInt( adata[ 1 ], 10 ); 504 | aaaa = parseInt( adata[ 2 ], 10 ); 505 | xdata = new Date( Date.UTC( aaaa, mm - 1, gg, 12, 0, 0, 0 ) ); 506 | if ( ( xdata.getUTCFullYear() === aaaa ) && ( xdata.getUTCMonth() === mm - 1 ) && ( xdata.getUTCDate() === gg ) ) { 507 | check = true; 508 | } else { 509 | check = false; 510 | } 511 | } else { 512 | check = false; 513 | } 514 | return this.optional( element ) || check; 515 | }, $.validator.messages.date ); 516 | 517 | $.validator.addMethod( "dateNL", function( value, element ) { 518 | return this.optional( element ) || /^(0?[1-9]|[12]\d|3[01])[\.\/\-](0?[1-9]|1[012])[\.\/\-]([12]\d)?(\d\d)$/.test( value ); 519 | }, $.validator.messages.date ); 520 | 521 | // Older "accept" file extension method. Old docs: http://docs.jquery.com/Plugins/Validation/Methods/accept 522 | $.validator.addMethod( "extension", function( value, element, param ) { 523 | param = typeof param === "string" ? param.replace( /,/g, "|" ) : "png|jpe?g|gif"; 524 | return this.optional( element ) || value.match( new RegExp( "\\.(" + param + ")$", "i" ) ); 525 | }, $.validator.format( "Please enter a value with a valid extension." ) ); 526 | 527 | /** 528 | * Dutch giro account numbers (not bank numbers) have max 7 digits 529 | */ 530 | $.validator.addMethod( "giroaccountNL", function( value, element ) { 531 | return this.optional( element ) || /^[0-9]{1,7}$/.test( value ); 532 | }, "Please specify a valid giro account number" ); 533 | 534 | /** 535 | * IBAN is the international bank account number. 536 | * It has a country - specific format, that is checked here too 537 | * 538 | * Validation is case-insensitive. Please make sure to normalize input yourself. 539 | */ 540 | $.validator.addMethod( "iban", function( value, element ) { 541 | 542 | // Some quick simple tests to prevent needless work 543 | if ( this.optional( element ) ) { 544 | return true; 545 | } 546 | 547 | // Remove spaces and to upper case 548 | var iban = value.replace( / /g, "" ).toUpperCase(), 549 | ibancheckdigits = "", 550 | leadingZeroes = true, 551 | cRest = "", 552 | cOperator = "", 553 | countrycode, ibancheck, charAt, cChar, bbanpattern, bbancountrypatterns, ibanregexp, i, p; 554 | 555 | // Check for IBAN code length. 556 | // It contains: 557 | // country code ISO 3166-1 - two letters, 558 | // two check digits, 559 | // Basic Bank Account Number (BBAN) - up to 30 chars 560 | var minimalIBANlength = 5; 561 | if ( iban.length < minimalIBANlength ) { 562 | return false; 563 | } 564 | 565 | // Check the country code and find the country specific format 566 | countrycode = iban.substring( 0, 2 ); 567 | bbancountrypatterns = { 568 | "AL": "\\d{8}[\\dA-Z]{16}", 569 | "AD": "\\d{8}[\\dA-Z]{12}", 570 | "AT": "\\d{16}", 571 | "AZ": "[\\dA-Z]{4}\\d{20}", 572 | "BE": "\\d{12}", 573 | "BH": "[A-Z]{4}[\\dA-Z]{14}", 574 | "BA": "\\d{16}", 575 | "BR": "\\d{23}[A-Z][\\dA-Z]", 576 | "BG": "[A-Z]{4}\\d{6}[\\dA-Z]{8}", 577 | "CR": "\\d{17}", 578 | "HR": "\\d{17}", 579 | "CY": "\\d{8}[\\dA-Z]{16}", 580 | "CZ": "\\d{20}", 581 | "DK": "\\d{14}", 582 | "DO": "[A-Z]{4}\\d{20}", 583 | "EE": "\\d{16}", 584 | "FO": "\\d{14}", 585 | "FI": "\\d{14}", 586 | "FR": "\\d{10}[\\dA-Z]{11}\\d{2}", 587 | "GE": "[\\dA-Z]{2}\\d{16}", 588 | "DE": "\\d{18}", 589 | "GI": "[A-Z]{4}[\\dA-Z]{15}", 590 | "GR": "\\d{7}[\\dA-Z]{16}", 591 | "GL": "\\d{14}", 592 | "GT": "[\\dA-Z]{4}[\\dA-Z]{20}", 593 | "HU": "\\d{24}", 594 | "IS": "\\d{22}", 595 | "IE": "[\\dA-Z]{4}\\d{14}", 596 | "IL": "\\d{19}", 597 | "IT": "[A-Z]\\d{10}[\\dA-Z]{12}", 598 | "KZ": "\\d{3}[\\dA-Z]{13}", 599 | "KW": "[A-Z]{4}[\\dA-Z]{22}", 600 | "LV": "[A-Z]{4}[\\dA-Z]{13}", 601 | "LB": "\\d{4}[\\dA-Z]{20}", 602 | "LI": "\\d{5}[\\dA-Z]{12}", 603 | "LT": "\\d{16}", 604 | "LU": "\\d{3}[\\dA-Z]{13}", 605 | "MK": "\\d{3}[\\dA-Z]{10}\\d{2}", 606 | "MT": "[A-Z]{4}\\d{5}[\\dA-Z]{18}", 607 | "MR": "\\d{23}", 608 | "MU": "[A-Z]{4}\\d{19}[A-Z]{3}", 609 | "MC": "\\d{10}[\\dA-Z]{11}\\d{2}", 610 | "MD": "[\\dA-Z]{2}\\d{18}", 611 | "ME": "\\d{18}", 612 | "NL": "[A-Z]{4}\\d{10}", 613 | "NO": "\\d{11}", 614 | "PK": "[\\dA-Z]{4}\\d{16}", 615 | "PS": "[\\dA-Z]{4}\\d{21}", 616 | "PL": "\\d{24}", 617 | "PT": "\\d{21}", 618 | "RO": "[A-Z]{4}[\\dA-Z]{16}", 619 | "SM": "[A-Z]\\d{10}[\\dA-Z]{12}", 620 | "SA": "\\d{2}[\\dA-Z]{18}", 621 | "RS": "\\d{18}", 622 | "SK": "\\d{20}", 623 | "SI": "\\d{15}", 624 | "ES": "\\d{20}", 625 | "SE": "\\d{20}", 626 | "CH": "\\d{5}[\\dA-Z]{12}", 627 | "TN": "\\d{20}", 628 | "TR": "\\d{5}[\\dA-Z]{17}", 629 | "AE": "\\d{3}\\d{16}", 630 | "GB": "[A-Z]{4}\\d{14}", 631 | "VG": "[\\dA-Z]{4}\\d{16}" 632 | }; 633 | 634 | bbanpattern = bbancountrypatterns[ countrycode ]; 635 | 636 | // As new countries will start using IBAN in the 637 | // future, we only check if the countrycode is known. 638 | // This prevents false negatives, while almost all 639 | // false positives introduced by this, will be caught 640 | // by the checksum validation below anyway. 641 | // Strict checking should return FALSE for unknown 642 | // countries. 643 | if ( typeof bbanpattern !== "undefined" ) { 644 | ibanregexp = new RegExp( "^[A-Z]{2}\\d{2}" + bbanpattern + "$", "" ); 645 | if ( !( ibanregexp.test( iban ) ) ) { 646 | return false; // Invalid country specific format 647 | } 648 | } 649 | 650 | // Now check the checksum, first convert to digits 651 | ibancheck = iban.substring( 4, iban.length ) + iban.substring( 0, 4 ); 652 | for ( i = 0; i < ibancheck.length; i++ ) { 653 | charAt = ibancheck.charAt( i ); 654 | if ( charAt !== "0" ) { 655 | leadingZeroes = false; 656 | } 657 | if ( !leadingZeroes ) { 658 | ibancheckdigits += "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".indexOf( charAt ); 659 | } 660 | } 661 | 662 | // Calculate the result of: ibancheckdigits % 97 663 | for ( p = 0; p < ibancheckdigits.length; p++ ) { 664 | cChar = ibancheckdigits.charAt( p ); 665 | cOperator = "" + cRest + "" + cChar; 666 | cRest = cOperator % 97; 667 | } 668 | return cRest === 1; 669 | }, "Please specify a valid IBAN" ); 670 | 671 | $.validator.addMethod( "integer", function( value, element ) { 672 | return this.optional( element ) || /^-?\d+$/.test( value ); 673 | }, "A positive or negative non-decimal number please" ); 674 | 675 | $.validator.addMethod( "ipv4", function( value, element ) { 676 | return this.optional( element ) || /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i.test( value ); 677 | }, "Please enter a valid IP v4 address." ); 678 | 679 | $.validator.addMethod( "ipv6", function( value, element ) { 680 | return this.optional( element ) || /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i.test( value ); 681 | }, "Please enter a valid IP v6 address." ); 682 | 683 | $.validator.addMethod( "lettersonly", function( value, element ) { 684 | return this.optional( element ) || /^[a-z]+$/i.test( value ); 685 | }, "Letters only please" ); 686 | 687 | $.validator.addMethod( "letterswithbasicpunc", function( value, element ) { 688 | return this.optional( element ) || /^[a-z\-.,()'"\s]+$/i.test( value ); 689 | }, "Letters or punctuation only please" ); 690 | 691 | $.validator.addMethod( "mobileNL", function( value, element ) { 692 | return this.optional( element ) || /^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)6((\s|\s?\-\s?)?[0-9]){8}$/.test( value ); 693 | }, "Please specify a valid mobile number" ); 694 | 695 | /* For UK phone functions, do the following server side processing: 696 | * Compare original input with this RegEx pattern: 697 | * ^\(?(?:(?:00\)?[\s\-]?\(?|\+)(44)\)?[\s\-]?\(?(?:0\)?[\s\-]?\(?)?|0)([1-9]\d{1,4}\)?[\s\d\-]+)$ 698 | * Extract $1 and set $prefix to '+44' if $1 is '44', otherwise set $prefix to '0' 699 | * Extract $2 and remove hyphens, spaces and parentheses. Phone number is combined $prefix and $2. 700 | * A number of very detailed GB telephone number RegEx patterns can also be found at: 701 | * http://www.aa-asterisk.org.uk/index.php/Regular_Expressions_for_Validating_and_Formatting_GB_Telephone_Numbers 702 | */ 703 | $.validator.addMethod( "mobileUK", function( phone_number, element ) { 704 | phone_number = phone_number.replace( /\(|\)|\s+|-/g, "" ); 705 | return this.optional( element ) || phone_number.length > 9 && 706 | phone_number.match( /^(?:(?:(?:00\s?|\+)44\s?|0)7(?:[1345789]\d{2}|624)\s?\d{3}\s?\d{3})$/ ); 707 | }, "Please specify a valid mobile number" ); 708 | 709 | $.validator.addMethod( "netmask", function( value, element ) { 710 | return this.optional( element ) || /^(254|252|248|240|224|192|128)\.0\.0\.0|255\.(254|252|248|240|224|192|128|0)\.0\.0|255\.255\.(254|252|248|240|224|192|128|0)\.0|255\.255\.255\.(254|252|248|240|224|192|128|0)/i.test( value ); 711 | }, "Please enter a valid netmask." ); 712 | 713 | /* 714 | * The NIE (Número de Identificación de Extranjero) is a Spanish tax identification number assigned by the Spanish 715 | * authorities to any foreigner. 716 | * 717 | * The NIE is the equivalent of a Spaniards Número de Identificación Fiscal (NIF) which serves as a fiscal 718 | * identification number. The CIF number (Certificado de Identificación Fiscal) is equivalent to the NIF, but applies to 719 | * companies rather than individuals. The NIE consists of an 'X' or 'Y' followed by 7 or 8 digits then another letter. 720 | */ 721 | $.validator.addMethod( "nieES", function( value, element ) { 722 | "use strict"; 723 | 724 | if ( this.optional( element ) ) { 725 | return true; 726 | } 727 | 728 | var nieRegEx = new RegExp( /^[MXYZ]{1}[0-9]{7,8}[TRWAGMYFPDXBNJZSQVHLCKET]{1}$/gi ); 729 | var validChars = "TRWAGMYFPDXBNJZSQVHLCKET", 730 | letter = value.substr( value.length - 1 ).toUpperCase(), 731 | number; 732 | 733 | value = value.toString().toUpperCase(); 734 | 735 | // Quick format test 736 | if ( value.length > 10 || value.length < 9 || !nieRegEx.test( value ) ) { 737 | return false; 738 | } 739 | 740 | // X means same number 741 | // Y means number + 10000000 742 | // Z means number + 20000000 743 | value = value.replace( /^[X]/, "0" ) 744 | .replace( /^[Y]/, "1" ) 745 | .replace( /^[Z]/, "2" ); 746 | 747 | number = value.length === 9 ? value.substr( 0, 8 ) : value.substr( 0, 9 ); 748 | 749 | return validChars.charAt( parseInt( number, 10 ) % 23 ) === letter; 750 | 751 | }, "Please specify a valid NIE number." ); 752 | 753 | /* 754 | * The Número de Identificación Fiscal ( NIF ) is the way tax identification used in Spain for individuals 755 | */ 756 | $.validator.addMethod( "nifES", function( value, element ) { 757 | "use strict"; 758 | 759 | if ( this.optional( element ) ) { 760 | return true; 761 | } 762 | 763 | value = value.toUpperCase(); 764 | 765 | // Basic format test 766 | if ( !value.match( "((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)" ) ) { 767 | return false; 768 | } 769 | 770 | // Test NIF 771 | if ( /^[0-9]{8}[A-Z]{1}$/.test( value ) ) { 772 | return ( "TRWAGMYFPDXBNJZSQVHLCKE".charAt( value.substring( 8, 0 ) % 23 ) === value.charAt( 8 ) ); 773 | } 774 | 775 | // Test specials NIF (starts with K, L or M) 776 | if ( /^[KLM]{1}/.test( value ) ) { 777 | return ( value[ 8 ] === "TRWAGMYFPDXBNJZSQVHLCKE".charAt( value.substring( 8, 1 ) % 23 ) ); 778 | } 779 | 780 | return false; 781 | 782 | }, "Please specify a valid NIF number." ); 783 | 784 | /* 785 | * Numer identyfikacji podatkowej ( NIP ) is the way tax identification used in Poland for companies 786 | */ 787 | $.validator.addMethod( "nipPL", function( value ) { 788 | "use strict"; 789 | 790 | value = value.replace( /[^0-9]/g, "" ); 791 | 792 | if ( value.length !== 10 ) { 793 | return false; 794 | } 795 | 796 | var arrSteps = [ 6, 5, 7, 2, 3, 4, 5, 6, 7 ]; 797 | var intSum = 0; 798 | for ( var i = 0; i < 9; i++ ) { 799 | intSum += arrSteps[ i ] * value[ i ]; 800 | } 801 | var int2 = intSum % 11; 802 | var intControlNr = ( int2 === 10 ) ? 0 : int2; 803 | 804 | return ( intControlNr === parseInt( value[ 9 ], 10 ) ); 805 | }, "Please specify a valid NIP number." ); 806 | 807 | $.validator.addMethod( "notEqualTo", function( value, element, param ) { 808 | return this.optional( element ) || !$.validator.methods.equalTo.call( this, value, element, param ); 809 | }, "Please enter a different value, values must not be the same." ); 810 | 811 | $.validator.addMethod( "nowhitespace", function( value, element ) { 812 | return this.optional( element ) || /^\S+$/i.test( value ); 813 | }, "No white space please" ); 814 | 815 | /** 816 | * Return true if the field value matches the given format RegExp 817 | * 818 | * @example $.validator.methods.pattern("AR1004",element,/^AR\d{4}$/) 819 | * @result true 820 | * 821 | * @example $.validator.methods.pattern("BR1004",element,/^AR\d{4}$/) 822 | * @result false 823 | * 824 | * @name $.validator.methods.pattern 825 | * @type Boolean 826 | * @cat Plugins/Validate/Methods 827 | */ 828 | $.validator.addMethod( "pattern", function( value, element, param ) { 829 | if ( this.optional( element ) ) { 830 | return true; 831 | } 832 | if ( typeof param === "string" ) { 833 | param = new RegExp( "^(?:" + param + ")$" ); 834 | } 835 | return param.test( value ); 836 | }, "Invalid format." ); 837 | 838 | /** 839 | * Dutch phone numbers have 10 digits (or 11 and start with +31). 840 | */ 841 | $.validator.addMethod( "phoneNL", function( value, element ) { 842 | return this.optional( element ) || /^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)[1-9]((\s|\s?\-\s?)?[0-9]){8}$/.test( value ); 843 | }, "Please specify a valid phone number." ); 844 | 845 | /* For UK phone functions, do the following server side processing: 846 | * Compare original input with this RegEx pattern: 847 | * ^\(?(?:(?:00\)?[\s\-]?\(?|\+)(44)\)?[\s\-]?\(?(?:0\)?[\s\-]?\(?)?|0)([1-9]\d{1,4}\)?[\s\d\-]+)$ 848 | * Extract $1 and set $prefix to '+44' if $1 is '44', otherwise set $prefix to '0' 849 | * Extract $2 and remove hyphens, spaces and parentheses. Phone number is combined $prefix and $2. 850 | * A number of very detailed GB telephone number RegEx patterns can also be found at: 851 | * http://www.aa-asterisk.org.uk/index.php/Regular_Expressions_for_Validating_and_Formatting_GB_Telephone_Numbers 852 | */ 853 | 854 | // Matches UK landline + mobile, accepting only 01-3 for landline or 07 for mobile to exclude many premium numbers 855 | $.validator.addMethod( "phonesUK", function( phone_number, element ) { 856 | phone_number = phone_number.replace( /\(|\)|\s+|-/g, "" ); 857 | return this.optional( element ) || phone_number.length > 9 && 858 | phone_number.match( /^(?:(?:(?:00\s?|\+)44\s?|0)(?:1\d{8,9}|[23]\d{9}|7(?:[1345789]\d{8}|624\d{6})))$/ ); 859 | }, "Please specify a valid uk phone number" ); 860 | 861 | /* For UK phone functions, do the following server side processing: 862 | * Compare original input with this RegEx pattern: 863 | * ^\(?(?:(?:00\)?[\s\-]?\(?|\+)(44)\)?[\s\-]?\(?(?:0\)?[\s\-]?\(?)?|0)([1-9]\d{1,4}\)?[\s\d\-]+)$ 864 | * Extract $1 and set $prefix to '+44' if $1 is '44', otherwise set $prefix to '0' 865 | * Extract $2 and remove hyphens, spaces and parentheses. Phone number is combined $prefix and $2. 866 | * A number of very detailed GB telephone number RegEx patterns can also be found at: 867 | * http://www.aa-asterisk.org.uk/index.php/Regular_Expressions_for_Validating_and_Formatting_GB_Telephone_Numbers 868 | */ 869 | $.validator.addMethod( "phoneUK", function( phone_number, element ) { 870 | phone_number = phone_number.replace( /\(|\)|\s+|-/g, "" ); 871 | return this.optional( element ) || phone_number.length > 9 && 872 | phone_number.match( /^(?:(?:(?:00\s?|\+)44\s?)|(?:\(?0))(?:\d{2}\)?\s?\d{4}\s?\d{4}|\d{3}\)?\s?\d{3}\s?\d{3,4}|\d{4}\)?\s?(?:\d{5}|\d{3}\s?\d{3})|\d{5}\)?\s?\d{4,5})$/ ); 873 | }, "Please specify a valid phone number" ); 874 | 875 | /** 876 | * Matches US phone number format 877 | * 878 | * where the area code may not start with 1 and the prefix may not start with 1 879 | * allows '-' or ' ' as a separator and allows parens around area code 880 | * some people may want to put a '1' in front of their number 881 | * 882 | * 1(212)-999-2345 or 883 | * 212 999 2344 or 884 | * 212-999-0983 885 | * 886 | * but not 887 | * 111-123-5434 888 | * and not 889 | * 212 123 4567 890 | */ 891 | $.validator.addMethod( "phoneUS", function( phone_number, element ) { 892 | phone_number = phone_number.replace( /\s+/g, "" ); 893 | return this.optional( element ) || phone_number.length > 9 && 894 | phone_number.match( /^(\+?1-?)?(\([2-9]([02-9]\d|1[02-9])\)|[2-9]([02-9]\d|1[02-9]))-?[2-9]([02-9]\d|1[02-9])-?\d{4}$/ ); 895 | }, "Please specify a valid phone number" ); 896 | 897 | /* 898 | * Valida CEPs do brasileiros: 899 | * 900 | * Formatos aceitos: 901 | * 99999-999 902 | * 99.999-999 903 | * 99999999 904 | */ 905 | $.validator.addMethod( "postalcodeBR", function( cep_value, element ) { 906 | return this.optional( element ) || /^\d{2}.\d{3}-\d{3}?$|^\d{5}-?\d{3}?$/.test( cep_value ); 907 | }, "Informe um CEP válido." ); 908 | 909 | /** 910 | * Matches a valid Canadian Postal Code 911 | * 912 | * @example jQuery.validator.methods.postalCodeCA( "H0H 0H0", element ) 913 | * @result true 914 | * 915 | * @example jQuery.validator.methods.postalCodeCA( "H0H0H0", element ) 916 | * @result false 917 | * 918 | * @name jQuery.validator.methods.postalCodeCA 919 | * @type Boolean 920 | * @cat Plugins/Validate/Methods 921 | */ 922 | $.validator.addMethod( "postalCodeCA", function( value, element ) { 923 | return this.optional( element ) || /^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ] *\d[ABCEGHJKLMNPRSTVWXYZ]\d$/i.test( value ); 924 | }, "Please specify a valid postal code" ); 925 | 926 | /* Matches Italian postcode (CAP) */ 927 | $.validator.addMethod( "postalcodeIT", function( value, element ) { 928 | return this.optional( element ) || /^\d{5}$/.test( value ); 929 | }, "Please specify a valid postal code" ); 930 | 931 | $.validator.addMethod( "postalcodeNL", function( value, element ) { 932 | return this.optional( element ) || /^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test( value ); 933 | }, "Please specify a valid postal code" ); 934 | 935 | // Matches UK postcode. Does not match to UK Channel Islands that have their own postcodes (non standard UK) 936 | $.validator.addMethod( "postcodeUK", function( value, element ) { 937 | return this.optional( element ) || /^((([A-PR-UWYZ][0-9])|([A-PR-UWYZ][0-9][0-9])|([A-PR-UWYZ][A-HK-Y][0-9])|([A-PR-UWYZ][A-HK-Y][0-9][0-9])|([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY]))\s?([0-9][ABD-HJLNP-UW-Z]{2})|(GIR)\s?(0AA))$/i.test( value ); 938 | }, "Please specify a valid UK postcode" ); 939 | 940 | /* 941 | * Lets you say "at least X inputs that match selector Y must be filled." 942 | * 943 | * The end result is that neither of these inputs: 944 | * 945 | * 946 | * 947 | * 948 | * ...will validate unless at least one of them is filled. 949 | * 950 | * partnumber: {require_from_group: [1,".productinfo"]}, 951 | * description: {require_from_group: [1,".productinfo"]} 952 | * 953 | * options[0]: number of fields that must be filled in the group 954 | * options[1]: CSS selector that defines the group of conditionally required fields 955 | */ 956 | $.validator.addMethod( "require_from_group", function( value, element, options ) { 957 | var $fields = $( options[ 1 ], element.form ), 958 | $fieldsFirst = $fields.eq( 0 ), 959 | validator = $fieldsFirst.data( "valid_req_grp" ) ? $fieldsFirst.data( "valid_req_grp" ) : $.extend( {}, this ), 960 | isValid = $fields.filter( function() { 961 | return validator.elementValue( this ); 962 | } ).length >= options[ 0 ]; 963 | 964 | // Store the cloned validator for future validation 965 | $fieldsFirst.data( "valid_req_grp", validator ); 966 | 967 | // If element isn't being validated, run each require_from_group field's validation rules 968 | if ( !$( element ).data( "being_validated" ) ) { 969 | $fields.data( "being_validated", true ); 970 | $fields.each( function() { 971 | validator.element( this ); 972 | } ); 973 | $fields.data( "being_validated", false ); 974 | } 975 | return isValid; 976 | }, $.validator.format( "Please fill at least {0} of these fields." ) ); 977 | 978 | /* 979 | * Lets you say "either at least X inputs that match selector Y must be filled, 980 | * OR they must all be skipped (left blank)." 981 | * 982 | * The end result, is that none of these inputs: 983 | * 984 | * 985 | * 986 | * 987 | * 988 | * ...will validate unless either at least two of them are filled, 989 | * OR none of them are. 990 | * 991 | * partnumber: {skip_or_fill_minimum: [2,".productinfo"]}, 992 | * description: {skip_or_fill_minimum: [2,".productinfo"]}, 993 | * color: {skip_or_fill_minimum: [2,".productinfo"]} 994 | * 995 | * options[0]: number of fields that must be filled in the group 996 | * options[1]: CSS selector that defines the group of conditionally required fields 997 | * 998 | */ 999 | $.validator.addMethod( "skip_or_fill_minimum", function( value, element, options ) { 1000 | var $fields = $( options[ 1 ], element.form ), 1001 | $fieldsFirst = $fields.eq( 0 ), 1002 | validator = $fieldsFirst.data( "valid_skip" ) ? $fieldsFirst.data( "valid_skip" ) : $.extend( {}, this ), 1003 | numberFilled = $fields.filter( function() { 1004 | return validator.elementValue( this ); 1005 | } ).length, 1006 | isValid = numberFilled === 0 || numberFilled >= options[ 0 ]; 1007 | 1008 | // Store the cloned validator for future validation 1009 | $fieldsFirst.data( "valid_skip", validator ); 1010 | 1011 | // If element isn't being validated, run each skip_or_fill_minimum field's validation rules 1012 | if ( !$( element ).data( "being_validated" ) ) { 1013 | $fields.data( "being_validated", true ); 1014 | $fields.each( function() { 1015 | validator.element( this ); 1016 | } ); 1017 | $fields.data( "being_validated", false ); 1018 | } 1019 | return isValid; 1020 | }, $.validator.format( "Please either skip these fields or fill at least {0} of them." ) ); 1021 | 1022 | /* Validates US States and/or Territories by @jdforsythe 1023 | * Can be case insensitive or require capitalization - default is case insensitive 1024 | * Can include US Territories or not - default does not 1025 | * Can include US Military postal abbreviations (AA, AE, AP) - default does not 1026 | * 1027 | * Note: "States" always includes DC (District of Colombia) 1028 | * 1029 | * Usage examples: 1030 | * 1031 | * This is the default - case insensitive, no territories, no military zones 1032 | * stateInput: { 1033 | * caseSensitive: false, 1034 | * includeTerritories: false, 1035 | * includeMilitary: false 1036 | * } 1037 | * 1038 | * Only allow capital letters, no territories, no military zones 1039 | * stateInput: { 1040 | * caseSensitive: false 1041 | * } 1042 | * 1043 | * Case insensitive, include territories but not military zones 1044 | * stateInput: { 1045 | * includeTerritories: true 1046 | * } 1047 | * 1048 | * Only allow capital letters, include territories and military zones 1049 | * stateInput: { 1050 | * caseSensitive: true, 1051 | * includeTerritories: true, 1052 | * includeMilitary: true 1053 | * } 1054 | * 1055 | */ 1056 | $.validator.addMethod( "stateUS", function( value, element, options ) { 1057 | var isDefault = typeof options === "undefined", 1058 | caseSensitive = ( isDefault || typeof options.caseSensitive === "undefined" ) ? false : options.caseSensitive, 1059 | includeTerritories = ( isDefault || typeof options.includeTerritories === "undefined" ) ? false : options.includeTerritories, 1060 | includeMilitary = ( isDefault || typeof options.includeMilitary === "undefined" ) ? false : options.includeMilitary, 1061 | regex; 1062 | 1063 | if ( !includeTerritories && !includeMilitary ) { 1064 | regex = "^(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$"; 1065 | } else if ( includeTerritories && includeMilitary ) { 1066 | regex = "^(A[AEKLPRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$"; 1067 | } else if ( includeTerritories ) { 1068 | regex = "^(A[KLRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$"; 1069 | } else { 1070 | regex = "^(A[AEKLPRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$"; 1071 | } 1072 | 1073 | regex = caseSensitive ? new RegExp( regex ) : new RegExp( regex, "i" ); 1074 | return this.optional( element ) || regex.test( value ); 1075 | }, "Please specify a valid state" ); 1076 | 1077 | // TODO check if value starts with <, otherwise don't try stripping anything 1078 | $.validator.addMethod( "strippedminlength", function( value, element, param ) { 1079 | return $( value ).text().length >= param; 1080 | }, $.validator.format( "Please enter at least {0} characters" ) ); 1081 | 1082 | $.validator.addMethod( "time", function( value, element ) { 1083 | return this.optional( element ) || /^([01]\d|2[0-3]|[0-9])(:[0-5]\d){1,2}$/.test( value ); 1084 | }, "Please enter a valid time, between 00:00 and 23:59" ); 1085 | 1086 | $.validator.addMethod( "time12h", function( value, element ) { 1087 | return this.optional( element ) || /^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test( value ); 1088 | }, "Please enter a valid time in 12-hour am/pm format" ); 1089 | 1090 | // Same as url, but TLD is optional 1091 | $.validator.addMethod( "url2", function( value, element ) { 1092 | 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 ); 1093 | }, $.validator.messages.url ); 1094 | 1095 | /** 1096 | * Return true, if the value is a valid vehicle identification number (VIN). 1097 | * 1098 | * Works with all kind of text inputs. 1099 | * 1100 | * @example 1101 | * @desc Declares a required input element whose value must be a valid vehicle identification number. 1102 | * 1103 | * @name $.validator.methods.vinUS 1104 | * @type Boolean 1105 | * @cat Plugins/Validate/Methods 1106 | */ 1107 | $.validator.addMethod( "vinUS", function( v ) { 1108 | if ( v.length !== 17 ) { 1109 | return false; 1110 | } 1111 | 1112 | var LL = [ "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" ], 1113 | VL = [ 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 7, 9, 2, 3, 4, 5, 6, 7, 8, 9 ], 1114 | FL = [ 8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2 ], 1115 | rs = 0, 1116 | i, n, d, f, cd, cdv; 1117 | 1118 | for ( i = 0; i < 17; i++ ) { 1119 | f = FL[ i ]; 1120 | d = v.slice( i, i + 1 ); 1121 | if ( i === 8 ) { 1122 | cdv = d; 1123 | } 1124 | if ( !isNaN( d ) ) { 1125 | d *= f; 1126 | } else { 1127 | for ( n = 0; n < LL.length; n++ ) { 1128 | if ( d.toUpperCase() === LL[ n ] ) { 1129 | d = VL[ n ]; 1130 | d *= f; 1131 | if ( isNaN( cdv ) && n === 8 ) { 1132 | cdv = LL[ n ]; 1133 | } 1134 | break; 1135 | } 1136 | } 1137 | } 1138 | rs += d; 1139 | } 1140 | cd = rs % 11; 1141 | if ( cd === 10 ) { 1142 | cd = "X"; 1143 | } 1144 | if ( cd === cdv ) { 1145 | return true; 1146 | } 1147 | return false; 1148 | }, "The specified vehicle identification number (VIN) is invalid." ); 1149 | 1150 | $.validator.addMethod( "zipcodeUS", function( value, element ) { 1151 | return this.optional( element ) || /^\d{5}(-\d{4})?$/.test( value ); 1152 | }, "The specified US ZIP Code is invalid" ); 1153 | 1154 | $.validator.addMethod( "ziprange", function( value, element ) { 1155 | return this.optional( element ) || /^90[2-5]\d\{2\}-\d{4}$/.test( value ); 1156 | }, "Your ZIP-code must be in the range 902xx-xxxx to 905xx-xxxx" ); 1157 | return $; 1158 | })); -------------------------------------------------------------------------------- /wwwroot/lib/jquery/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery", 3 | "main": "dist/jquery.js", 4 | "license": "MIT", 5 | "ignore": [ 6 | "package.json" 7 | ], 8 | "keywords": [ 9 | "jquery", 10 | "javascript", 11 | "browser", 12 | "library" 13 | ], 14 | "homepage": "https://github.com/jquery/jquery-dist", 15 | "version": "3.3.1", 16 | "_release": "3.3.1", 17 | "_resolution": { 18 | "type": "version", 19 | "tag": "3.3.1", 20 | "commit": "9e8ec3d10fad04748176144f108d7355662ae75e" 21 | }, 22 | "_source": "https://github.com/jquery/jquery-dist.git", 23 | "_target": "^3.3.1", 24 | "_originalSource": "jquery", 25 | "_direct": true 26 | } -------------------------------------------------------------------------------- /wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors, https://js.foundation/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /wwwroot/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / --------------------------------------------------------------------------------