diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fc10d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vs +*.xproj.user +project.lock.json diff --git a/Microsoft.AspNet.NodeServices.Angular/.gitignore b/Microsoft.AspNet.NodeServices.Angular/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/Microsoft.AspNet.NodeServices.Angular/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/Microsoft.AspNet.NodeServices.Angular/AngularPrerenderTagHelper.cs b/Microsoft.AspNet.NodeServices.Angular/AngularPrerenderTagHelper.cs new file mode 100644 index 0000000..9a5292b --- /dev/null +++ b/Microsoft.AspNet.NodeServices.Angular/AngularPrerenderTagHelper.cs @@ -0,0 +1,50 @@ +using System.Threading.Tasks; +using Microsoft.AspNet.Razor.Runtime.TagHelpers; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.NodeServices; +using Microsoft.AspNet.Http.Extensions; + +namespace Microsoft.AspNet.NodeServices.Angular +{ + [HtmlTargetElement(Attributes = PrerenderModuleAttributeName)] + public class AngularRunAtServerTagHelper : TagHelper + { + static StringAsTempFile nodeScript; + + static AngularRunAtServerTagHelper() { + // Consider populating this lazily + var script = EmbeddedResourceReader.Read(typeof (AngularRunAtServerTagHelper), "/Content/Node/angular-rendering.js"); + nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit + } + + const string PrerenderModuleAttributeName = "aspnet-ng2-prerender-module"; + const string PrerenderExportAttributeName = "aspnet-ng2-prerender-export"; + + private static NodeInstance nodeInstance = new NodeInstance(); + + [HtmlAttributeName(PrerenderModuleAttributeName)] + public string ModuleName { get; set; } + + [HtmlAttributeName(PrerenderExportAttributeName)] + public string ExportName { get; set; } + + private IHttpContextAccessor contextAccessor; + + public AngularRunAtServerTagHelper(IHttpContextAccessor contextAccessor) + { + this.contextAccessor = contextAccessor; + } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + var result = await nodeInstance.InvokeExport(nodeScript.FileName, "renderComponent", new { + componentModule = this.ModuleName, + componentExport = this.ExportName, + tagName = output.TagName, + baseUrl = UriHelper.GetEncodedUrl(this.contextAccessor.HttpContext.Request) + }); + output.SuppressOutput(); + output.PostElement.AppendEncoded(result); + } + } +} diff --git a/Microsoft.AspNet.NodeServices.Angular/Content/Node/angular-rendering.js b/Microsoft.AspNet.NodeServices.Angular/Content/Node/angular-rendering.js new file mode 100644 index 0000000..57b7df8 --- /dev/null +++ b/Microsoft.AspNet.NodeServices.Angular/Content/Node/angular-rendering.js @@ -0,0 +1,28 @@ +var path = require('path'); +var ngUniversal = require('angular2-universal-patched'); +var ng = require('angular2/angular2'); +var ngRouter = require('angular2/router'); + +module.exports = { + renderComponent: function(callback, options) { + // Find the component class. Use options.componentExport if specified, otherwise convert tag-name to PascalCase. + var loadedModule = require(path.resolve(process.cwd(), options.componentModule)); + var componentExport = options.componentExport || options.tagName.replace(/(-|^)([a-z])/g, function (m1, m2, char) { return char.toUpperCase(); }); + var component = loadedModule[componentExport]; + if (!component) { + throw new Error('The module "' + options.componentModule + '" has no export named "' + componentExport + '"'); + } + + var serverBindings = [ + ngRouter.ROUTER_BINDINGS, + ngUniversal.HTTP_PROVIDERS, + ng.provide(ngUniversal.BASE_URL, { useValue: options.baseUrl }), + ngUniversal.SERVER_LOCATION_PROVIDERS + ]; + + return ngUniversal.renderToString(component, serverBindings).then( + function(successValue) { callback(null, successValue); }, + function(errorValue) { callback(errorValue); } + ); + } +}; diff --git a/Microsoft.AspNet.NodeServices.Angular/project.json b/Microsoft.AspNet.NodeServices.Angular/project.json new file mode 100644 index 0000000..133451b --- /dev/null +++ b/Microsoft.AspNet.NodeServices.Angular/project.json @@ -0,0 +1,34 @@ +{ + "version": "1.0.0-alpha1", + "description": "Microsoft.AspNet.NodeServices.Angular Class Library", + "authors": [ + "Microsoft" + ], + "tags": [ + "" + ], + "projectUrl": "", + "licenseUrl": "", + "tooling": { + "defaultNamespace": "Microsoft.AspNet.NodeServices.Angular" + }, + "frameworks": { + "dnx451": {}, + "dnxcore50": { + "dependencies": { + "Microsoft.CSharp": "4.0.1-beta-*", + "System.Collections": "4.0.11-beta-*", + "System.Linq": "4.0.1-beta-*", + "System.Runtime": "4.0.21-beta-*", + "System.Threading": "4.0.11-beta-*" + } + } + }, + "dependencies": { + "Microsoft.AspNet.NodeServices": "1.0.0-alpha1", + "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-beta8" + }, + "resource": [ + "Content/**/*" + ] +} \ No newline at end of file diff --git a/Microsoft.AspNet.NodeServices.React/.gitignore b/Microsoft.AspNet.NodeServices.React/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/Microsoft.AspNet.NodeServices.React/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/Microsoft.AspNet.NodeServices.React/Content/Node/react-rendering.js b/Microsoft.AspNet.NodeServices.React/Content/Node/react-rendering.js new file mode 100644 index 0000000..27e97c7 --- /dev/null +++ b/Microsoft.AspNet.NodeServices.React/Content/Node/react-rendering.js @@ -0,0 +1,40 @@ +var fs = require('fs'); +var path = require('path'); +var React = require('react'); +var ReactDOMServer = require('react-dom/server'); +var createMemoryHistory = require('history/lib/createMemoryHistory'); +var babelCore = require('babel-core'); +var babelConfig = {}; + +var origJsLoader = require.extensions['.js']; +require.extensions['.js'] = loadViaBabel; +require.extensions['.jsx'] = loadViaBabel; + +function loadViaBabel(module, filename) { + // Assume that all the app's own code is ES2015+ (optionally with JSX), but that none of the node_modules are. + // The distinction is important because ES2015+ forces strict mode, and it may break ES3/5 if you try to run it in strict + // mode when the developer didn't expect that (e.g., current versions of underscore.js can't be loaded in strict mode). + var useBabel = filename.indexOf('node_modules') < 0; + if (useBabel) { + var transformedFile = babelCore.transformFileSync(filename, babelConfig); + return module._compile(transformedFile.code, filename); + } else { + return origJsLoader.apply(this, arguments); + } +} + +module.exports = { + renderToString: function(callback, options) { + var resolvedPath = path.resolve(process.cwd(), options.moduleName); + var requestedModule = require(resolvedPath); + var component = requestedModule[options.exportName]; + if (!component) { + throw new Error('The module "' + resolvedPath + '" has no export named "' + options.exportName + '"'); + } + + var history = createMemoryHistory(options.baseUrl); + var reactElement = React.createElement(component, { history: history }); + var html = ReactDOMServer.renderToString(reactElement); + callback(null, html); + } +}; diff --git a/Microsoft.AspNet.NodeServices.React/ReactRenderer.cs b/Microsoft.AspNet.NodeServices.React/ReactRenderer.cs new file mode 100644 index 0000000..653e52a --- /dev/null +++ b/Microsoft.AspNet.NodeServices.React/ReactRenderer.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; + +namespace Microsoft.AspNet.NodeServices.React +{ + public static class ReactRenderer + { + private static StringAsTempFile nodeScript; + private static NodeInstance nodeInstance = new NodeInstance(); + + static ReactRenderer() { + // Consider populating this lazily + var script = EmbeddedResourceReader.Read(typeof (ReactRenderer), "/Content/Node/react-rendering.js"); + nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit + } + + public static async Task RenderToString(string moduleName, string exportName, string baseUrl) { + return await nodeInstance.InvokeExport(nodeScript.FileName, "renderToString", new { + moduleName, + exportName, + baseUrl + }); + } + } +} diff --git a/Microsoft.AspNet.NodeServices.React/project.json b/Microsoft.AspNet.NodeServices.React/project.json new file mode 100644 index 0000000..3500b73 --- /dev/null +++ b/Microsoft.AspNet.NodeServices.React/project.json @@ -0,0 +1,33 @@ +{ + "version": "1.0.0-alpha1", + "description": "Microsoft.AspNet.NodeServices.React Class Library", + "authors": [ + "Microsoft" + ], + "tags": [ + "" + ], + "projectUrl": "", + "licenseUrl": "", + "tooling": { + "defaultNamespace": "Microsoft.AspNet.NodeServices.React" + }, + "frameworks": { + "dnx451": {}, + "dnxcore50": { + "dependencies": { + "Microsoft.CSharp": "4.0.1-beta-*", + "System.Collections": "4.0.11-beta-*", + "System.Linq": "4.0.1-beta-*", + "System.Runtime": "4.0.21-beta-*", + "System.Threading": "4.0.11-beta-*" + } + } + }, + "dependencies": { + "Microsoft.AspNet.NodeServices": "1.0.0-alpha1" + }, + "resource": [ + "Content/**/*" + ] +} \ No newline at end of file diff --git a/Microsoft.AspNet.NodeServices/.gitignore b/Microsoft.AspNet.NodeServices/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/Microsoft.AspNet.NodeServices/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/Microsoft.AspNet.NodeServices/Content/Node/entrypoint-http.js b/Microsoft.AspNet.NodeServices/Content/Node/entrypoint-http.js new file mode 100644 index 0000000..35ed458 --- /dev/null +++ b/Microsoft.AspNet.NodeServices/Content/Node/entrypoint-http.js @@ -0,0 +1,62 @@ +var path = require('path'); +var express = require('express'); +var bodyParser = require('body-parser') +var requestedPortOrZero = parseInt(process.argv[2]) || 0; // 0 means 'let the OS decide' + +autoQuitOnFileChange(process.cwd(), ['.js', '.json', '.html']); + +var app = express(); +app.use(bodyParser.json()); + +app.all('/', function (req, res) { + var resolvedPath = path.resolve(process.cwd(), req.body.moduleName); + var invokedModule = require(resolvedPath); + var func = req.body.exportedFunctionName ? invokedModule[req.body.exportedFunctionName] : invokedModule; + if (!func) { + throw new Error('The module "' + resolvedPath + '" has no export named "' + req.body.exportedFunctionName + '"'); + } + + var hasSentResult = false; + var callback = function(errorValue, successValue) { + if (!hasSentResult) { + hasSentResult = true; + if (errorValue) { + res.status(500).send(errorValue); + } else { + sendResult(res, successValue); + } + } + }; + + func.apply(null, [callback].concat(req.body.args)); +}); + +var listener = app.listen(requestedPortOrZero, 'localhost', function () { + // Signal to HttpNodeHost which port it should make its HTTP connections on + console.log('[Microsoft.AspNet.NodeServices.HttpNodeHost:Listening on port ' + listener.address().port + '\]'); + + // Signal to the NodeServices base class that we're ready to accept invocations + console.log('[Microsoft.AspNet.NodeServices:Listening]'); +}); + +function sendResult(response, result) { + if (typeof result === 'object') { + response.json(result); + } else { + response.send(result); + } +} + +function autoQuitOnFileChange(rootDir, extensions) { + // Note: This will only work on Windows/OS X, because the 'recursive' option isn't supported on Linux. + // Consider using a different watch mechanism (though ideally without forcing further NPM dependencies). + var fs = require('fs'); + var path = require('path'); + fs.watch(rootDir, { persistent: false, recursive: true }, function(event, filename) { + var ext = path.extname(filename); + if (extensions.indexOf(ext) >= 0) { + console.log('Restarting due to file change: ' + filename); + process.exit(0); + } + }); +} diff --git a/Microsoft.AspNet.NodeServices/Content/Node/entrypoint-stream.js b/Microsoft.AspNet.NodeServices/Content/Node/entrypoint-stream.js new file mode 100644 index 0000000..7f5218e --- /dev/null +++ b/Microsoft.AspNet.NodeServices/Content/Node/entrypoint-stream.js @@ -0,0 +1,23 @@ +var path = require('path'); +var readline = require('readline'); +var invocationPrefix = 'invoke:'; + +function invocationCallback(errorValue, successValue) { + if (errorValue) { + throw new Error('InputOutputStreamHost doesn\'t support errors. Got error: ' + errorValue.toString()); + } else { + var serializedResult = typeof successValue === 'object' ? JSON.stringify(successValue) : successValue; + console.log(serializedResult); + } +} + +readline.createInterface({ input: process.stdin }).on('line', function (message) { + if (message && message.substring(0, invocationPrefix.length) === invocationPrefix) { + var invocation = JSON.parse(message.substring(invocationPrefix.length)); + var invokedModule = require(path.resolve(process.cwd(), invocation.moduleName)); + var func = invocation.exportedFunctionName ? invokedModule[invocation.exportedFunctionName] : invokedModule; + func.apply(null, [invocationCallback].concat(invocation.args)); + } +}); + +console.log('[Microsoft.AspNet.NodeServices:Listening]'); // The .NET app waits for this signal before sending any invocations diff --git a/Microsoft.AspNet.NodeServices/HostingModels/EmbeddedResourceReader.cs b/Microsoft.AspNet.NodeServices/HostingModels/EmbeddedResourceReader.cs new file mode 100644 index 0000000..4369ea7 --- /dev/null +++ b/Microsoft.AspNet.NodeServices/HostingModels/EmbeddedResourceReader.cs @@ -0,0 +1,17 @@ +using System; +using System.IO; +using System.Reflection; + +namespace Microsoft.AspNet.NodeServices { + public static class EmbeddedResourceReader { + public static string Read(Type assemblyContainingType, string path) { + var asm = assemblyContainingType.GetTypeInfo().Assembly; + var embeddedResourceName = asm.GetName().Name + path.Replace("/", "."); + + using (var stream = asm.GetManifestResourceStream(embeddedResourceName)) + using (var sr = new StreamReader(stream)) { + return sr.ReadToEnd(); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.AspNet.NodeServices/HostingModels/HttpNodeHost.cs b/Microsoft.AspNet.NodeServices/HostingModels/HttpNodeHost.cs new file mode 100644 index 0000000..14dd3ef --- /dev/null +++ b/Microsoft.AspNet.NodeServices/HostingModels/HttpNodeHost.cs @@ -0,0 +1,50 @@ +using System.Net.Http; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.AspNet.NodeServices { + internal class HttpNodeHost : OutOfProcessNodeRunner { + private readonly static Regex PortMessageRegex = new Regex(@"^\[Microsoft.AspNet.NodeServices.HttpNodeHost:Listening on port (\d+)\]$"); + + private readonly static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + private int _portNumber; + + public HttpNodeHost(int port = 0) + : base(EmbeddedResourceReader.Read(typeof(HttpNodeHost), "/Content/Node/entrypoint-http.js"), port.ToString()) + { + } + + public override async Task Invoke(NodeInvocationInfo invocationInfo) { + await this.EnsureReady(); + + using (var client = new HttpClient()) { + // TODO: Use System.Net.Http.Formatting (PostAsJsonAsync etc.) + var payloadJson = JsonConvert.SerializeObject(invocationInfo, jsonSerializerSettings); + var payload = new StringContent(payloadJson, Encoding.UTF8, "application/json"); + var response = await client.PostAsync("http://localhost:" + this._portNumber, payload); + var responseString = await response.Content.ReadAsStringAsync(); + return responseString; + } + } + + protected override void OnOutputDataReceived(string outputData) { + var match = this._portNumber != 0 ? null : PortMessageRegex.Match(outputData); + if (match != null && match.Success) { + this._portNumber = int.Parse(match.Groups[1].Captures[0].Value); + } else { + base.OnOutputDataReceived(outputData); + } + } + + protected override void OnBeforeLaunchProcess() { + // Prepare to receive a new port number + this._portNumber = 0; + } + } +} diff --git a/Microsoft.AspNet.NodeServices/HostingModels/InputOutputStreamNodeHost.cs b/Microsoft.AspNet.NodeServices/HostingModels/InputOutputStreamNodeHost.cs new file mode 100644 index 0000000..f8a7ca7 --- /dev/null +++ b/Microsoft.AspNet.NodeServices/HostingModels/InputOutputStreamNodeHost.cs @@ -0,0 +1,57 @@ +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.AspNet.NodeServices { + // This is just to demonstrate that other transports are possible. This implementation is extremely + // dubious - if the Node-side code fails to conform to the expected protocol in any way (e.g., has an + // error), then it will just hang forever. So don't use this. + // + // But it's fast - the communication round-trip time is about 0.2ms (tested on OS X on a recent machine), + // versus 2-3ms for the HTTP transport. + // + // Instead of directly using stdin/stdout, we could use either regular sockets (TCP) or use named pipes + // on Windows and domain sockets on Linux / OS X, but either way would need a system for framing the + // requests, associating them with responses, and scheduling use of the comms channel. + internal class InputOutputStreamNodeHost : OutOfProcessNodeRunner + { + private SemaphoreSlim _invocationSemaphore = new SemaphoreSlim(1); + private TaskCompletionSource _currentInvocationResult; + + private readonly static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + public InputOutputStreamNodeHost() + : base(EmbeddedResourceReader.Read(typeof(InputOutputStreamNodeHost), "/Content/Node/entrypoint-stream.js")) + { + } + + public override async Task Invoke(NodeInvocationInfo invocationInfo) { + await this._invocationSemaphore.WaitAsync(); + try { + await this.EnsureReady(); + + var payloadJson = JsonConvert.SerializeObject(invocationInfo, jsonSerializerSettings); + var nodeProcess = this.NodeProcess; + this._currentInvocationResult = new TaskCompletionSource(); + nodeProcess.StandardInput.Write("\ninvoke:"); + nodeProcess.StandardInput.WriteLine(payloadJson); // WriteLineAsync isn't supported cross-platform + return await this._currentInvocationResult.Task; + } finally { + this._invocationSemaphore.Release(); + this._currentInvocationResult = null; + } + } + + protected override void OnOutputDataReceived(string outputData) { + if (this._currentInvocationResult != null) { + this._currentInvocationResult.SetResult(outputData); + } else { + base.OnOutputDataReceived(outputData); + } + } + } +} diff --git a/Microsoft.AspNet.NodeServices/HostingModels/NodeHost.cs b/Microsoft.AspNet.NodeServices/HostingModels/NodeHost.cs new file mode 100644 index 0000000..a66f978 --- /dev/null +++ b/Microsoft.AspNet.NodeServices/HostingModels/NodeHost.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace Microsoft.AspNet.NodeServices { + public abstract class NodeHost : System.IDisposable + { + public abstract Task Invoke(NodeInvocationInfo invocationInfo); + + public abstract void Dispose(); + } +} diff --git a/Microsoft.AspNet.NodeServices/HostingModels/NodeInvocationInfo.cs b/Microsoft.AspNet.NodeServices/HostingModels/NodeInvocationInfo.cs new file mode 100644 index 0000000..682877f --- /dev/null +++ b/Microsoft.AspNet.NodeServices/HostingModels/NodeInvocationInfo.cs @@ -0,0 +1,8 @@ +namespace Microsoft.AspNet.NodeServices { + public class NodeInvocationInfo + { + public string ModuleName; + public string ExportedFunctionName; + public object[] Args; + } +} diff --git a/Microsoft.AspNet.NodeServices/HostingModels/OutOfProcessNodeRunner.cs b/Microsoft.AspNet.NodeServices/HostingModels/OutOfProcessNodeRunner.cs new file mode 100644 index 0000000..fb3d06f --- /dev/null +++ b/Microsoft.AspNet.NodeServices/HostingModels/OutOfProcessNodeRunner.cs @@ -0,0 +1,133 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.NodeServices { + /** + * Class responsible for launching the Node child process, determining when it is ready to accept invocations, + * and finally killing it when the parent process exits. Also it restarts the child process if it dies. + */ + internal abstract class OutOfProcessNodeRunner : NodeHost { + private object _childProcessLauncherLock; + private bool disposed; + private StringAsTempFile _entryPointScript; + private string _commandLineArguments; + private Process _nodeProcess; + private TaskCompletionSource _nodeProcessIsReadySource; + + protected Process NodeProcess { + get { + // This is only exposed to support the UnreliableStreamNodeHost, which is just to verify that + // other hosting/transport mechanisms are possible. This shouldn't really be exposed. + return this._nodeProcess; + } + } + + public OutOfProcessNodeRunner(string entryPointScript, string commandLineArguments = null) + { + this._childProcessLauncherLock = new object(); + this._entryPointScript = new StringAsTempFile(entryPointScript); + this._commandLineArguments = commandLineArguments ?? string.Empty; + } + + protected async Task EnsureReady() { + lock (this._childProcessLauncherLock) { + if (this._nodeProcess == null || this._nodeProcess.HasExited) { + var startInfo = new ProcessStartInfo("node") { + Arguments = this._entryPointScript.FileName + " " + this._commandLineArguments, + UseShellExecute = false, + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + // Append current directory to NODE_PATH so it can locate node_modules + var existingNodePath = Environment.GetEnvironmentVariable("NODE_PATH") ?? string.Empty; + if (existingNodePath != string.Empty) { + existingNodePath += ":"; + } + + var nodePathValue = existingNodePath + Path.Combine(Directory.GetCurrentDirectory(), "node_modules"); + #if DNX451 + startInfo.EnvironmentVariables.Add("NODE_PATH", nodePathValue); + #else + startInfo.Environment.Add("NODE_PATH", nodePathValue); + #endif + + this.OnBeforeLaunchProcess(); + this._nodeProcess = Process.Start(startInfo); + this.ConnectToInputOutputStreams(); + } + } + + var initializationSucceeded = await this._nodeProcessIsReadySource.Task; + if (!initializationSucceeded) { + throw new InvalidOperationException("The Node.js process failed to initialize"); + } + } + + private void ConnectToInputOutputStreams() { + var initializationIsCompleted = false; // TODO: Make this thread-safe? (Interlocked.Exchange etc.) + this._nodeProcessIsReadySource = new TaskCompletionSource(); + + this._nodeProcess.OutputDataReceived += (sender, evt) => { + if (evt.Data == "[Microsoft.AspNet.NodeServices:Listening]" && !initializationIsCompleted) { + this._nodeProcessIsReadySource.SetResult(true); + initializationIsCompleted = true; + } else if (evt.Data != null) { + this.OnOutputDataReceived(evt.Data); + } + }; + + this._nodeProcess.ErrorDataReceived += (sender, evt) => { + if (evt.Data != null) { + this.OnErrorDataReceived(evt.Data); + if (!initializationIsCompleted) { + this._nodeProcessIsReadySource.SetResult(false); + initializationIsCompleted = true; + } + } + }; + + this._nodeProcess.BeginOutputReadLine(); + this._nodeProcess.BeginErrorReadLine(); + } + + protected virtual void OnBeforeLaunchProcess() { + } + + protected virtual void OnOutputDataReceived(string outputData) { + Console.WriteLine("[Node] " + outputData); + } + + protected virtual void OnErrorDataReceived(string errorData) { + Console.WriteLine("[Node] " + errorData); + } + + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposed) { + if (disposing) { + this._entryPointScript.Dispose(); + } + + if (this._nodeProcess != null && !this._nodeProcess.HasExited) { + this._nodeProcess.Kill(); // TODO: Is there a more graceful way to end it? Or does this still let it perform any cleanup? System.Console.WriteLine("Killed"); + } + + disposed = true; + } + } + + ~OutOfProcessNodeRunner() { + Dispose (false); + } + } +} \ No newline at end of file diff --git a/Microsoft.AspNet.NodeServices/NodeHostingModel.cs b/Microsoft.AspNet.NodeServices/NodeHostingModel.cs new file mode 100644 index 0000000..49a8fc5 --- /dev/null +++ b/Microsoft.AspNet.NodeServices/NodeHostingModel.cs @@ -0,0 +1,6 @@ +namespace Microsoft.AspNet.NodeServices { + public enum NodeHostingModel { + Http, + InputOutputStream, + } +} \ No newline at end of file diff --git a/Microsoft.AspNet.NodeServices/NodeInstance.cs b/Microsoft.AspNet.NodeServices/NodeInstance.cs new file mode 100644 index 0000000..7768f18 --- /dev/null +++ b/Microsoft.AspNet.NodeServices/NodeInstance.cs @@ -0,0 +1,38 @@ +using System; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.NodeServices { + public class NodeInstance : IDisposable { + private readonly NodeHost _nodeHost; + + public NodeInstance(NodeHostingModel hostingModel = NodeHostingModel.Http) { + switch (hostingModel) { + case NodeHostingModel.Http: + this._nodeHost = new HttpNodeHost(); + break; + case NodeHostingModel.InputOutputStream: + this._nodeHost = new InputOutputStreamNodeHost(); + break; + default: + throw new ArgumentException("Unknown hosting model: " + hostingModel.ToString()); + } + } + + public Task Invoke(string moduleName, params object[] args) { + return this.InvokeExport(moduleName, null, args); + } + + public async Task InvokeExport(string moduleName, string exportedFunctionName, params object[] args) { + return await this._nodeHost.Invoke(new NodeInvocationInfo { + ModuleName = moduleName, + ExportedFunctionName = exportedFunctionName, + Args = args + }); + } + + public void Dispose() + { + this._nodeHost.Dispose(); + } + } +} diff --git a/Microsoft.AspNet.NodeServices/StringAsTempFile.cs b/Microsoft.AspNet.NodeServices/StringAsTempFile.cs new file mode 100644 index 0000000..023e743 --- /dev/null +++ b/Microsoft.AspNet.NodeServices/StringAsTempFile.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; + +namespace Microsoft.AspNet.NodeServices { + // Makes it easier to pass script files to Node in a way that's sure to clean up after the process exits + public sealed class StringAsTempFile : IDisposable { + public string FileName { get; private set; } + + private bool _disposedValue; + + public StringAsTempFile(string content) { + this.FileName = Path.GetTempFileName(); + File.WriteAllText(this.FileName, content); + } + + private void DisposeImpl(bool disposing) + { + if (!_disposedValue) { + if (disposing) { + // TODO: dispose managed state (managed objects). + } + + File.Delete(this.FileName); + + _disposedValue = true; + } + } + + public void Dispose() + { + DisposeImpl(true); + GC.SuppressFinalize(this); + } + + ~StringAsTempFile() { + DisposeImpl(false); + } + } +} diff --git a/Microsoft.AspNet.NodeServices/project.json b/Microsoft.AspNet.NodeServices/project.json new file mode 100644 index 0000000..d94a210 --- /dev/null +++ b/Microsoft.AspNet.NodeServices/project.json @@ -0,0 +1,34 @@ +{ + "version": "1.0.0-alpha1", + "description": "Microsoft.AspNet.NodeServices", + "authors": [ "Microsoft" ], + "tags": [""], + "projectUrl": "", + "licenseUrl": "", + + "dependencies": { + "System.Net.Http": "4.0.1-beta-23409", + "Newtonsoft.Json": "8.0.1-beta1" + }, + + "frameworks": { + "dnx451": { }, + "dnxcore50": { + "dependencies": { + "Microsoft.CSharp": "4.0.1-beta-23217", + "System.Collections": "4.0.11-beta-23217", + "System.Linq": "4.0.1-beta-23217", + "System.Runtime": "4.0.21-beta-23217", + "System.Threading": "4.0.11-beta-23217", + "System.Text.RegularExpressions": "4.0.11-beta-23409", + "System.Diagnostics.Process": "4.1.0-beta-23409", + "System.IO.FileSystem": "4.0.1-beta-23409", + "System.Console": "4.0.0-beta-23409" + } + } + }, + + "resource": [ + "Content/**/*" + ] +} diff --git a/samples/angular/MusicStore/.gitignore b/samples/angular/MusicStore/.gitignore new file mode 100644 index 0000000..051e6c1 --- /dev/null +++ b/samples/angular/MusicStore/.gitignore @@ -0,0 +1,5 @@ +/node_modules/ +/wwwroot/lib/ +/wwwroot/ng-app/**/*.js +/project.lock.json +/music-db.sqlite diff --git a/samples/angular/MusicStore/Apis/AlbumsApiController.cs b/samples/angular/MusicStore/Apis/AlbumsApiController.cs new file mode 100644 index 0000000..fdb4464 --- /dev/null +++ b/samples/angular/MusicStore/Apis/AlbumsApiController.cs @@ -0,0 +1,210 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Authorization; +using Microsoft.AspNet.Mvc; +using Microsoft.Data.Entity; +using AutoMapper; +using MusicStore.Models; +using MusicStore.Infrastructure; + +namespace MusicStore.Apis +{ + [Route("api/albums")] + public class AlbumsApiController : Controller + { + private readonly MusicStoreContext _storeContext; + + public AlbumsApiController(MusicStoreContext storeContext) + { + _storeContext = storeContext; + } + + [HttpGet] + [NoCache] + public async Task Paged(int page = 1, int pageSize = 50, string sortBy = null) + { + await _storeContext.Genres.LoadAsync(); + await _storeContext.Artists.LoadAsync(); + + var albums = await _storeContext.Albums + // .Include(a => a.Genre) + // .Include(a => a.Artist) + .ToPagedListAsync(page, pageSize, sortBy, + a => a.Title, // sortExpression + SortDirection.Ascending, // defaultSortDirection + a => Mapper.Map(a, new AlbumResultDto())); // selector + + return Json(albums); + } + + [HttpGet("all")] + [NoCache] + public async Task All() + { + var albums = await _storeContext.Albums + //.Include(a => a.Genre) + //.Include(a => a.Artist) + .OrderBy(a => a.Title) + .ToListAsync(); + + return Json(albums.Select(a => Mapper.Map(a, new AlbumResultDto()))); + } + + [HttpGet("mostPopular")] + [NoCache] + public async Task MostPopular(int count = 6) + { + count = count > 0 && count < 20 ? count : 6; + var albums = await _storeContext.Albums + .OrderByDescending(a => a.OrderDetails.Count()) + .Take(count) + .ToListAsync(); + + // TODO: Move the .Select() to end of albums query when EF supports it + return Json(albums.Select(a => Mapper.Map(a, new AlbumResultDto()))); + } + + [HttpGet("{albumId:int}")] + [NoCache] + public async Task Details(int albumId) + { + await _storeContext.Genres.LoadAsync(); + await _storeContext.Artists.LoadAsync(); + + var album = await _storeContext.Albums + //.Include(a => a.Artist) + //.Include(a => a.Genre) + .Where(a => a.AlbumId == albumId) + .SingleOrDefaultAsync(); + + var albumResult = Mapper.Map(album, new AlbumResultDto()); + + // TODO: Get these from the related entities when EF supports that again, i.e. when .Include() works + //album.Artist.Name = (await _storeContext.Artists.SingleOrDefaultAsync(a => a.ArtistId == album.ArtistId)).Name; + //album.Genre.Name = (await _storeContext.Genres.SingleOrDefaultAsync(g => g.GenreId == album.GenreId)).Name; + + // TODO: Add null checking and return 404 in that case + + return Json(albumResult); + } + + [HttpPost] + [Authorize("app-ManageStore")] + public async Task CreateAlbum([FromBody]AlbumChangeDto album) + { + if (!ModelState.IsValid) + { + // Return the model errors + return new ApiResult(ModelState); + } + + // Save the changes to the DB + var dbAlbum = new Album(); + _storeContext.Albums.Add(Mapper.Map(album, dbAlbum)); + await _storeContext.SaveChangesAsync(); + + // TODO: Handle missing record, key violations, concurrency issues, etc. + + return new ApiResult + { + Data = dbAlbum.AlbumId, + Message = "Album created successfully." + }; + } + + [HttpPut("{albumId:int}/update")] + [Authorize("app-ManageStore")] + public async Task UpdateAlbum(int albumId, [FromBody]AlbumChangeDto album) + { + if (!ModelState.IsValid) + { + // Return the model errors + return new ApiResult(ModelState); + } + + var dbAlbum = await _storeContext.Albums.SingleOrDefaultAsync(a => a.AlbumId == albumId); + + if (dbAlbum == null) + { + return new ApiResult + { + StatusCode = 404, + Message = string.Format("The album with ID {0} was not found.", albumId) + }; + } + + // Save the changes to the DB + Mapper.Map(album, dbAlbum); + await _storeContext.SaveChangesAsync(); + + // TODO: Handle missing record, key violations, concurrency issues, etc. + + return new ApiResult + { + Message = "Album updated successfully." + }; + } + + [HttpDelete("{albumId:int}")] + [Authorize("app-ManageStore")] + public async Task DeleteAlbum(int albumId) + { + var album = await _storeContext.Albums.SingleOrDefaultAsync(a => a.AlbumId == albumId); + //var album = _storeContext.Albums.SingleOrDefault(a => a.AlbumId == albumId); + + if (album != null) + { + _storeContext.Albums.Remove(album); + + // Save the changes to the DB + await _storeContext.SaveChangesAsync(); + + // TODO: Handle missing record, key violations, concurrency issues, etc. + } + + return new ApiResult + { + Message = "Album deleted successfully." + }; + } + } + + [ModelMetadataType(typeof(Album))] + public class AlbumChangeDto + { + public int GenreId { get; set; } + + public int ArtistId { get; set; } + + public string Title { get; set; } + + public decimal Price { get; set; } + + public string AlbumArtUrl { get; set; } + } + + public class AlbumResultDto : AlbumChangeDto + { + public AlbumResultDto() + { + Artist = new ArtistResultDto(); + Genre = new GenreResultDto(); + } + + public int AlbumId { get; set; } + + public ArtistResultDto Artist { get; private set; } + + public GenreResultDto Genre { get; private set; } + } + + public class ArtistResultDto + { + public string Name { get; set; } + } + + public class GenreResultDto + { + public string Name { get; set; } + } +} diff --git a/samples/angular/MusicStore/Apis/ArtistsApiController.cs b/samples/angular/MusicStore/Apis/ArtistsApiController.cs new file mode 100644 index 0000000..0efe110 --- /dev/null +++ b/samples/angular/MusicStore/Apis/ArtistsApiController.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc; +using Microsoft.Data.Entity; +using MusicStore.Models; + +namespace MusicStore.Apis +{ + [Route("api/artists")] + public class ArtistsApiController : Controller + { + private readonly MusicStoreContext _storeContext; + + public ArtistsApiController(MusicStoreContext storeContext) + { + _storeContext = storeContext; + } + + [HttpGet("lookup")] + public async Task Lookup() + { + var artists = await _storeContext.Artists + .OrderBy(a => a.Name) + .ToListAsync(); + + return Json(artists); + } + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/Apis/GenresApiController.cs b/samples/angular/MusicStore/Apis/GenresApiController.cs new file mode 100644 index 0000000..e5d6ba1 --- /dev/null +++ b/samples/angular/MusicStore/Apis/GenresApiController.cs @@ -0,0 +1,70 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc; +using Microsoft.Data.Entity; +using MusicStore.Models; +using MusicStore.Infrastructure; + +namespace MusicStore.Apis +{ + [Route("api/genres")] + public class GenresApiController : Controller + { + private readonly MusicStoreContext _storeContext; + + public GenresApiController(MusicStoreContext storeContext) + { + _storeContext = storeContext; + } + + [HttpGet] + public async Task GenreList() + { + var genres = await _storeContext.Genres + //.Include(g => g.Albums) + .OrderBy(g => g.Name) + .ToListAsync(); + + return Json(genres); + } + + [HttpGet("genre-lookup")] + public async Task Lookup() + { + var genres = await _storeContext.Genres + .Select(g => new { g.GenreId, g.Name }) + .ToListAsync(); + + return Json(genres); + } + + [HttpGet("menu")] + public async Task GenreMenuList(int count = 9) + { + count = count > 0 && count < 20 ? count : 9; + + var genres = await _storeContext.Genres + .OrderByDescending(g => + g.Albums.Sum(a => + a.OrderDetails.Sum(od => od.Quantity))) + .Take(count) + .ToListAsync(); + + return Json(genres); + } + + [HttpGet("{genreId:int}/albums")] + [NoCache] + public async Task GenreAlbums(int genreId) + { + var albums = await _storeContext.Albums + .Where(a => a.GenreId == genreId) + //.Include(a => a.Genre) + //.Include(a => a.Artist) + //.OrderBy(a => a.Genre.Name) + .ToListAsync(); + + return Json(albums); + } + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/Apis/Models/AccountViewModels.cs b/samples/angular/MusicStore/Apis/Models/AccountViewModels.cs new file mode 100644 index 0000000..657a4f0 --- /dev/null +++ b/samples/angular/MusicStore/Apis/Models/AccountViewModels.cs @@ -0,0 +1,63 @@ +using System.ComponentModel.DataAnnotations; + +namespace MusicStore.Models +{ + public class ExternalLoginConfirmationViewModel + { + [Required] + [Display(Name = "User name")] + public string UserName { get; set; } + } + + public class ManageUserViewModel + { + [Required] + [DataType(DataType.Password)] + [Display(Name = "Current password")] + public string OldPassword { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string NewPassword { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } + + public class LoginViewModel + { + [Required] + [Display(Name = "User name")] + public string UserName { get; set; } + + [Required] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } + } + + public class RegisterViewModel + { + [Required] + [Display(Name = "User name")] + public string UserName { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/Apis/Models/Album.cs b/samples/angular/MusicStore/Apis/Models/Album.cs new file mode 100644 index 0000000..35f03d9 --- /dev/null +++ b/samples/angular/MusicStore/Apis/Models/Album.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace MusicStore.Models +{ + public class Album + { + public Album() + { + // TODO: Temporary hack to populate the orderdetails until EF does this automatically. + OrderDetails = new List(); + } + + [ScaffoldColumn(false)] + public int AlbumId { get; set; } + + public int GenreId { get; set; } + + public int ArtistId { get; set; } + + [Required] + [StringLength(160, MinimumLength = 2)] + public string Title { get; set; } + + [Required] + [Range(0.01, 100.00)] + [DataType(DataType.Currency)] + public decimal Price { get; set; } + + [Display(Name = "Album Art URL")] + [StringLength(1024)] + public string AlbumArtUrl { get; set; } + + public virtual Genre Genre { get; set; } + + public virtual Artist Artist { get; set; } + + public virtual ICollection OrderDetails { get; set; } + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/Apis/Models/Artist.cs b/samples/angular/MusicStore/Apis/Models/Artist.cs new file mode 100644 index 0000000..43d677c --- /dev/null +++ b/samples/angular/MusicStore/Apis/Models/Artist.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace MusicStore.Models +{ + public class Artist + { + public int ArtistId { get; set; } + + [Required] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/Apis/Models/CartItem.cs b/samples/angular/MusicStore/Apis/Models/CartItem.cs new file mode 100644 index 0000000..64550ab --- /dev/null +++ b/samples/angular/MusicStore/Apis/Models/CartItem.cs @@ -0,0 +1,21 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace MusicStore.Models +{ + public class CartItem + { + [Key] + public int CartItemId { get; set; } + + [Required] + public string CartId { get; set; } + public int AlbumId { get; set; } + public int Count { get; set; } + + [DataType(DataType.DateTime)] + public DateTime DateCreated { get; set; } + + public virtual Album Album { get; set; } + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/Apis/Models/Genre.cs b/samples/angular/MusicStore/Apis/Models/Genre.cs new file mode 100644 index 0000000..a7fdb34 --- /dev/null +++ b/samples/angular/MusicStore/Apis/Models/Genre.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace MusicStore.Models +{ + public class Genre + { + public Genre() + { + Albums = new List(); + } + + public int GenreId { get; set; } + + [Required] + public string Name { get; set; } + + public string Description { get; set; } + + [JsonIgnore] + public virtual ICollection Albums { get; set; } + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/Apis/Models/MusicStoreContext.cs b/samples/angular/MusicStore/Apis/Models/MusicStoreContext.cs new file mode 100644 index 0000000..d63ff69 --- /dev/null +++ b/samples/angular/MusicStore/Apis/Models/MusicStoreContext.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq; +using Microsoft.AspNet.Identity; +using Microsoft.AspNet.Identity.EntityFramework; +using Microsoft.Data.Entity; +using Microsoft.Data.Entity.Metadata; +using Microsoft.Framework.OptionsModel; + +namespace MusicStore.Models +{ + public class ApplicationUser : IdentityUser { } + + public class MusicStoreContext : IdentityDbContext + { + public MusicStoreContext() + { + } + + public DbSet Albums { get; set; } + public DbSet Artists { get; set; } + public DbSet Orders { get; set; } + public DbSet Genres { get; set; } + public DbSet CartItems { get; set; } + public DbSet OrderDetails { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + // Configure pluralization + builder.Entity().ToTable("Albums"); + builder.Entity().ToTable("Artists"); + builder.Entity().ToTable("Orders"); + builder.Entity().ToTable("Genres"); + builder.Entity().ToTable("CartItems"); + builder.Entity().ToTable("OrderDetails"); + + base.OnModelCreating(builder); + } + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/Apis/Models/Order.cs b/samples/angular/MusicStore/Apis/Models/Order.cs new file mode 100644 index 0000000..0461dbc --- /dev/null +++ b/samples/angular/MusicStore/Apis/Models/Order.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace MusicStore.Models +{ + //[Bind(Include = "FirstName,LastName,Address,City,State,PostalCode,Country,Phone,Email")] + public class Order + { + public Order() + { + OrderDetails = new List(); + } + + [ScaffoldColumn(false)] + public int OrderId { get; set; } + + [ScaffoldColumn(false)] + public DateTime OrderDate { get; set; } + + [Required] + [ScaffoldColumn(false)] + public string Username { get; set; } + + [Required] + [Display(Name = "First Name")] + [StringLength(160)] + public string FirstName { get; set; } + + [Required] + [Display(Name = "Last Name")] + [StringLength(160)] + public string LastName { get; set; } + + [Required] + [StringLength(70, MinimumLength = 3)] + public string Address { get; set; } + + [Required] + [StringLength(40)] + public string City { get; set; } + + [Required] + [StringLength(40)] + public string State { get; set; } + + [Required] + [Display(Name = "Postal Code")] + [StringLength(10, MinimumLength = 5)] + public string PostalCode { get; set; } + + [Required] + [StringLength(40)] + public string Country { get; set; } + + [Required] + [StringLength(24)] + [DataType(DataType.PhoneNumber)] + public string Phone { get; set; } + + [Required] + [Display(Name = "Email Address")] + [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", + ErrorMessage = "Email is not valid.")] + [DataType(DataType.EmailAddress)] + public string Email { get; set; } + + [ScaffoldColumn(false)] + public decimal Total { get; set; } + + public ICollection OrderDetails { get; set; } + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/Apis/Models/OrderDetail.cs b/samples/angular/MusicStore/Apis/Models/OrderDetail.cs new file mode 100644 index 0000000..29f8798 --- /dev/null +++ b/samples/angular/MusicStore/Apis/Models/OrderDetail.cs @@ -0,0 +1,14 @@ +namespace MusicStore.Models +{ + public class OrderDetail + { + public int OrderDetailId { get; set; } + public int OrderId { get; set; } + public int AlbumId { get; set; } + public int Quantity { get; set; } + public decimal UnitPrice { get; set; } + + public virtual Album Album { get; set; } + public virtual Order Order { get; set; } + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/Apis/Models/SampleData.cs b/samples/angular/MusicStore/Apis/Models/SampleData.cs new file mode 100644 index 0000000..25e2a5c --- /dev/null +++ b/samples/angular/MusicStore/Apis/Models/SampleData.cs @@ -0,0 +1,944 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNet.Identity; +using Microsoft.AspNet.Identity.EntityFramework; +using Microsoft.Data.Entity; +using Microsoft.Data.Entity.Storage; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.OptionsModel; +using MusicStore; +using MusicStore.Models; + +namespace MusicStore.Models +{ + public static class SampleData + { + const string imgUrl = "/images/placeholder.png"; + + public static async Task InitializeMusicStoreDatabaseAsync(IServiceProvider serviceProvider) + { + using (var db = serviceProvider.GetService()) + { + if (await db.Database.EnsureCreatedAsync()) + { + await InsertTestData(serviceProvider); + await CreateAdminUser(serviceProvider); + } + } + } + + private static async Task CreateAdminUser(IServiceProvider serviceProvider) + { + return; + + var settings = serviceProvider.GetService>().Value; + const string adminRole = "Administrator"; + + var userManager = serviceProvider.GetService>(); + var roleManager = serviceProvider.GetService>(); + + if (!await roleManager.RoleExistsAsync(adminRole)) + { + await roleManager.CreateAsync(new IdentityRole(adminRole)); + } + + var user = await userManager.FindByNameAsync(settings.DefaultAdminUsername); + if (user == null) + { + user = new ApplicationUser { UserName = settings.DefaultAdminUsername }; + await userManager.CreateAsync(user, settings.DefaultAdminPassword); + await userManager.AddToRoleAsync(user, adminRole); + await userManager.AddClaimAsync(user, new Claim("app-ManageStore", "Allowed")); + } + } + + private static async Task InsertTestData(IServiceProvider serviceProvider) + { + var albums = GetAlbums(imgUrl, Genres, Artists); + await AddOrUpdateAsync(serviceProvider, g => g.GenreId, Genres.Select(genre => genre.Value)); + await AddOrUpdateAsync(serviceProvider, a => a.ArtistId, Artists.Select(artist => artist.Value)); + await AddOrUpdateAsync(serviceProvider, a => a.AlbumId, albums); + } + + // TODO [EF] This may be replaced by a first class mechanism in EF + private static async Task AddOrUpdateAsync( + IServiceProvider serviceProvider, + Func propertyToMatch, IEnumerable entities) + where TEntity : class + { + // Query in a separate context so that we can attach existing entities as modified + List existingData; + + using (var scope = serviceProvider.GetRequiredService().CreateScope()) + using (var db = scope.ServiceProvider.GetService()) + { + existingData = db.Set().ToList(); + } + + using (var scope = serviceProvider.GetRequiredService().CreateScope()) + using (var db = scope.ServiceProvider.GetService()) + { + foreach (var item in entities) + { + db.Entry(item).State = existingData.Any(g => propertyToMatch(g).Equals(propertyToMatch(item))) + ? EntityState.Modified + : EntityState.Added; + } + + await db.SaveChangesAsync(); + } + } + + private static Album[] GetAlbums(string imgUrl, Dictionary genres, Dictionary artists) + { + var albums = new Album[] + { + new Album { Title = "The Best Of The Men At Work", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Men At Work"], AlbumArtUrl = imgUrl }, + new Album { Title = "...And Justice For All", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "עד גבול האור", Genre = genres["World"], Price = 8.99M, Artist = artists["אריק אינשטיין"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black Light Syndrome", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Terry Bozzio, Tony Levin & Steve Stevens"], AlbumArtUrl = imgUrl }, + new Album { Title = "10,000 Days", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tool"], AlbumArtUrl = imgUrl }, + new Album { Title = "11i", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Supreme Beings of Leisure"], AlbumArtUrl = imgUrl }, + new Album { Title = "1960", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Soul-Junk"], AlbumArtUrl = imgUrl }, + new Album { Title = "4x4=12 ", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["deadmau5"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Copland Celebration, Vol. I", Genre = genres["Classical"], Price = 8.99M, Artist = artists["London Symphony Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Lively Mind", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Paul Oakenfold"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Matter of Life and Death", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Real Dead One", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Real Live One", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Rush of Blood to the Head", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Coldplay"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Soprano Inspired", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Britten Sinfonia, Ivor Bolton & Lesley Garrett"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Winter Symphony", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Abbey Road", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Beatles"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ace Of Spades", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Motörhead"], AlbumArtUrl = imgUrl }, + new Album { Title = "Achtung Baby", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Acústico MTV", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Os Paralamas Do Sucesso"], AlbumArtUrl = imgUrl }, + new Album { Title = "Adams, John: The Chairman Dances", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Edo de Waart & San Francisco Symphony"], AlbumArtUrl = imgUrl }, + new Album { Title = "Adrenaline", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deftones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ænima", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Tool"], AlbumArtUrl = imgUrl }, + new Album { Title = "Afrociberdelia", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Chico Science & Nação Zumbi"], AlbumArtUrl = imgUrl }, + new Album { Title = "After the Goldrush", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Neil Young"], AlbumArtUrl = imgUrl }, + new Album { Title = "Airdrawn Dagger", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Sasha"], AlbumArtUrl = imgUrl }, + new Album { Title = "Album Title Goes Here", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["deadmau5"], AlbumArtUrl = imgUrl }, + new Album { Title = "Alcohol Fueled Brewtality Live! [Disc 1]", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Black Label Society"], AlbumArtUrl = imgUrl }, + new Album { Title = "Alcohol Fueled Brewtality Live! [Disc 2]", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Black Label Society"], AlbumArtUrl = imgUrl }, + new Album { Title = "Alive 2007", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Daft Punk"], AlbumArtUrl = imgUrl }, + new Album { Title = "All I Ask of You", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Amen (So Be It)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Paddy Casey"], AlbumArtUrl = imgUrl }, + new Album { Title = "Animal Vehicle", Genre = genres["Pop"], Price = 8.99M, Artist = artists["The Axis of Awesome"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ao Vivo [IMPORT]", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Zeca Pagodinho"], AlbumArtUrl = imgUrl }, + new Album { Title = "Apocalyptic Love", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Slash"], AlbumArtUrl = imgUrl }, + new Album { Title = "Appetite for Destruction", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Guns N' Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Are You Experienced?", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Jimi Hendrix"], AlbumArtUrl = imgUrl }, + new Album { Title = "Arquivo II", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Os Paralamas Do Sucesso"], AlbumArtUrl = imgUrl }, + new Album { Title = "Arquivo Os Paralamas Do Sucesso", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Os Paralamas Do Sucesso"], AlbumArtUrl = imgUrl }, + new Album { Title = "A-Sides", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Soundgarden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Audioslave", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Audioslave"], AlbumArtUrl = imgUrl }, + new Album { Title = "Automatic for the People", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["R.E.M."], AlbumArtUrl = imgUrl }, + new Album { Title = "Axé Bahia 2001", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Various Artists"], AlbumArtUrl = imgUrl }, + new Album { Title = "Babel", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Mumford & Sons"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bach: Goldberg Variations", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Wilhelm Kempff"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bach: The Brandenburg Concertos", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Orchestra of The Age of Enlightenment"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bach: The Cello Suites", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Yo-Yo Ma"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bach: Toccata & Fugue in D Minor", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Ton Koopman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bad Motorfinger", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Soundgarden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Balls to the Wall", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Accept"], AlbumArtUrl = imgUrl }, + new Album { Title = "Banadeek Ta'ala", Genre = genres["World"], Price = 8.99M, Artist = artists["Amr Diab"], AlbumArtUrl = imgUrl }, + new Album { Title = "Barbie Girl", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Aqua"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bark at the Moon (Remastered)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bartok: Violin & Viola Concertos", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Yehudi Menuhin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Barulhinho Bom", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Marisa Monte"], AlbumArtUrl = imgUrl }, + new Album { Title = "BBC Sessions [Disc 1] [Live]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "BBC Sessions [Disc 2] [Live]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Be Here Now", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Oasis"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bedrock 11 Compiled & Mixed", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["John Digweed"], AlbumArtUrl = imgUrl }, + new Album { Title = "Berlioz: Symphonie Fantastique", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Michael Tilson Thomas"], AlbumArtUrl = imgUrl }, + new Album { Title = "Beyond Good And Evil", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Cult"], AlbumArtUrl = imgUrl }, + new Album { Title = "Big Bad Wolf ", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Armand Van Helden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Big Ones", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Aerosmith"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black Album", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black Sabbath Vol. 4 (Remaster)", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Black Sabbath"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black Sabbath", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Black Sabbath"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blackwater Park", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Opeth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blizzard of Ozz", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blood", Genre = genres["Rock"], Price = 8.99M, Artist = artists["In This Moment"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blue Moods", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Incognito"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blue", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Weezer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bongo Fury", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Frank Zappa & Captain Beefheart"], AlbumArtUrl = imgUrl }, + new Album { Title = "Boys & Girls", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Alabama Shakes"], AlbumArtUrl = imgUrl }, + new Album { Title = "Brave New World", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "B-Sides 1980-1990", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bunkka", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Paul Oakenfold"], AlbumArtUrl = imgUrl }, + new Album { Title = "By The Way", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Red Hot Chili Peppers"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cake: B-Sides and Rarities", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Cake"], AlbumArtUrl = imgUrl }, + new Album { Title = "Californication", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Red Hot Chili Peppers"], AlbumArtUrl = imgUrl }, + new Album { Title = "Carmina Burana", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Boston Symphony Orchestra & Seiji Ozawa"], AlbumArtUrl = imgUrl }, + new Album { Title = "Carried to Dust (Bonus Track Version)", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Calexico"], AlbumArtUrl = imgUrl }, + new Album { Title = "Carry On", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Chris Cornell"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cássia Eller - Sem Limite [Disc 1]", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Cássia Eller"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chemical Wedding", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Bruce Dickinson"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chill: Brazil (Disc 1)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Marcos Valle"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chill: Brazil (Disc 2)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Antônio Carlos Jobim"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chocolate Starfish And The Hot Dog Flavored Water", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Limp Bizkit"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chronicle, Vol. 1", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Creedence Clearwater Revival"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chronicle, Vol. 2", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Creedence Clearwater Revival"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ciao, Baby", Genre = genres["Rock"], Price = 8.99M, Artist = artists["TheStart"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cidade Negra - Hits", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Cidade Negra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Classic Munkle: Turbo Edition", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Munkle"], AlbumArtUrl = imgUrl }, + new Album { Title = "Classics: The Best of Sarah Brightman", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Coda", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Come Away With Me", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Norah Jones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Come Taste The Band", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Comfort Eagle", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Cake"], AlbumArtUrl = imgUrl }, + new Album { Title = "Common Reaction", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Uh Huh Her "], AlbumArtUrl = imgUrl }, + new Album { Title = "Compositores", Genre = genres["Rock"], Price = 8.99M, Artist = artists["O Terço"], AlbumArtUrl = imgUrl }, + new Album { Title = "Contraband", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Velvet Revolver"], AlbumArtUrl = imgUrl }, + new Album { Title = "Core", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Stone Temple Pilots"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cornerstone", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Styx"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cosmicolor", Genre = genres["Rap"], Price = 8.99M, Artist = artists["M-Flo"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cross", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Justice"], AlbumArtUrl = imgUrl }, + new Album { Title = "Culture of Fear", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Thievery Corporation"], AlbumArtUrl = imgUrl }, + new Album { Title = "Da Lama Ao Caos", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Chico Science & Nação Zumbi"], AlbumArtUrl = imgUrl }, + new Album { Title = "Dakshina", Genre = genres["World"], Price = 8.99M, Artist = artists["Deva Premal"], AlbumArtUrl = imgUrl }, + new Album { Title = "Dark Side of the Moon", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pink Floyd"], AlbumArtUrl = imgUrl }, + new Album { Title = "Death Magnetic", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Deep End of Down", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Above the Fold"], AlbumArtUrl = imgUrl }, + new Album { Title = "Deep Purple In Rock", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Deixa Entrar", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Falamansa"], AlbumArtUrl = imgUrl }, + new Album { Title = "Deja Vu", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Crosby, Stills, Nash, and Young"], AlbumArtUrl = imgUrl }, + new Album { Title = "Di Korpu Ku Alma", Genre = genres["World"], Price = 8.99M, Artist = artists["Lura"], AlbumArtUrl = imgUrl }, + new Album { Title = "Diary of a Madman (Remastered)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Diary of a Madman", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Dirt", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Alice in Chains"], AlbumArtUrl = imgUrl }, + new Album { Title = "Diver Down", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Djavan Ao Vivo - Vol. 02", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Djavan"], AlbumArtUrl = imgUrl }, + new Album { Title = "Djavan Ao Vivo - Vol. 1", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Djavan"], AlbumArtUrl = imgUrl }, + new Album { Title = "Drum'n'bass for Papa", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Plug"], AlbumArtUrl = imgUrl }, + new Album { Title = "Duluth", Genre = genres["Country"], Price = 8.99M, Artist = artists["Trampled By Turtles"], AlbumArtUrl = imgUrl }, + new Album { Title = "Dummy", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Portishead"], AlbumArtUrl = imgUrl }, + new Album { Title = "Duos II", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Luciana Souza/Romero Lubambo"], AlbumArtUrl = imgUrl }, + new Album { Title = "Earl Scruggs and Friends", Genre = genres["Country"], Price = 8.99M, Artist = artists["Earl Scruggs"], AlbumArtUrl = imgUrl }, + new Album { Title = "Eden", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "El Camino", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Black Keys"], AlbumArtUrl = imgUrl }, + new Album { Title = "Elegant Gypsy", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Al di Meola"], AlbumArtUrl = imgUrl }, + new Album { Title = "Elements Of Life", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Tiësto"], AlbumArtUrl = imgUrl }, + new Album { Title = "Elis Regina-Minha História", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Elis Regina"], AlbumArtUrl = imgUrl }, + new Album { Title = "Emergency On Planet Earth", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Jamiroquai"], AlbumArtUrl = imgUrl }, + new Album { Title = "Emotion", Genre = genres["World"], Price = 8.99M, Artist = artists["Papa Wemba"], AlbumArtUrl = imgUrl }, + new Album { Title = "English Renaissance", Genre = genres["Classical"], Price = 8.99M, Artist = artists["The King's Singers"], AlbumArtUrl = imgUrl }, + new Album { Title = "Every Kind of Light", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Posies"], AlbumArtUrl = imgUrl }, + new Album { Title = "Faceless", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Godsmack"], AlbumArtUrl = imgUrl }, + new Album { Title = "Facelift", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Alice in Chains"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fair Warning", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fear of a Black Planet", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Public Enemy"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fear Of The Dark", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Feels Like Home", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Norah Jones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fireball", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fly", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "For Those About To Rock We Salute You", Genre = genres["Rock"], Price = 8.99M, Artist = artists["AC/DC"], AlbumArtUrl = imgUrl }, + new Album { Title = "Four", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Blues Traveler"], AlbumArtUrl = imgUrl }, + new Album { Title = "Frank", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Amy Winehouse"], AlbumArtUrl = imgUrl }, + new Album { Title = "Further Down the Spiral", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Nine Inch Nails"], AlbumArtUrl = imgUrl }, + new Album { Title = "Garage Inc. (Disc 1)", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Garage Inc. (Disc 2)", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Garbage", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Garbage"], AlbumArtUrl = imgUrl }, + new Album { Title = "Good News For People Who Love Bad News", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Modest Mouse"], AlbumArtUrl = imgUrl }, + new Album { Title = "Gordon", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Barenaked Ladies"], AlbumArtUrl = imgUrl }, + new Album { Title = "Górecki: Symphony No. 3", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Adrian Leaper & Doreen de Feis"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits I", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Queen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits II", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Queen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Duck Sauce"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Lenny Kravitz"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Lenny Kravitz"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Kiss", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Kiss"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greetings from Michigan", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Sufjan Stevens"], AlbumArtUrl = imgUrl }, + new Album { Title = "Group Therapy", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Above & Beyond"], AlbumArtUrl = imgUrl }, + new Album { Title = "Handel: The Messiah (Highlights)", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Scholars Baroque Ensemble"], AlbumArtUrl = imgUrl }, + new Album { Title = "Haydn: Symphonies 99 - 104", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Royal Philharmonic Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Heart of the Night", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Spyro Gyra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Heart On", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Eagles of Death Metal"], AlbumArtUrl = imgUrl }, + new Album { Title = "Holy Diver", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Dio"], AlbumArtUrl = imgUrl }, + new Album { Title = "Homework", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Daft Punk"], AlbumArtUrl = imgUrl }, + new Album { Title = "Hot Rocks, 1964-1971 (Disc 1)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Rolling Stones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Houses Of The Holy", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "How To Dismantle An Atomic Bomb", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Human", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Projected"], AlbumArtUrl = imgUrl }, + new Album { Title = "Hunky Dory", Genre = genres["Rock"], Price = 8.99M, Artist = artists["David Bowie"], AlbumArtUrl = imgUrl }, + new Album { Title = "Hymns", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Projected"], AlbumArtUrl = imgUrl }, + new Album { Title = "Hysteria", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Def Leppard"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Absentia", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Porcupine Tree"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Between", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Paul Van Dyk"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Rainbows", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Radiohead"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Step", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Stevie Ray Vaughan & Double Trouble"], AlbumArtUrl = imgUrl }, + new Album { Title = "In the court of the Crimson King", Genre = genres["Rock"], Price = 8.99M, Artist = artists["King Crimson"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Through The Out Door", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Your Honor [Disc 1]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Foo Fighters"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Your Honor [Disc 2]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Foo Fighters"], AlbumArtUrl = imgUrl }, + new Album { Title = "Indestructible", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Rancid"], AlbumArtUrl = imgUrl }, + new Album { Title = "Infinity", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Journey"], AlbumArtUrl = imgUrl }, + new Album { Title = "Into The Light", Genre = genres["Rock"], Price = 8.99M, Artist = artists["David Coverdale"], AlbumArtUrl = imgUrl }, + new Album { Title = "Introspective", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Pet Shop Boys"], AlbumArtUrl = imgUrl }, + new Album { Title = "Iron Maiden", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "ISAM", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Amon Tobin"], AlbumArtUrl = imgUrl }, + new Album { Title = "IV", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Jagged Little Pill", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Alanis Morissette"], AlbumArtUrl = imgUrl }, + new Album { Title = "Jagged Little Pill", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Alanis Morissette"], AlbumArtUrl = imgUrl }, + new Album { Title = "Jorge Ben Jor 25 Anos", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Jorge Ben"], AlbumArtUrl = imgUrl }, + new Album { Title = "Jota Quest-1995", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Jota Quest"], AlbumArtUrl = imgUrl }, + new Album { Title = "Kick", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["INXS"], AlbumArtUrl = imgUrl }, + new Album { Title = "Kill 'Em All", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Kind of Blue", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Miles Davis"], AlbumArtUrl = imgUrl }, + new Album { Title = "King For A Day Fool For A Lifetime", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Faith No More"], AlbumArtUrl = imgUrl }, + new Album { Title = "Kiss", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Carly Rae Jepsen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Last Call", Genre = genres["Country"], Price = 8.99M, Artist = artists["Cayouche"], AlbumArtUrl = imgUrl }, + new Album { Title = "Le Freak", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Chic"], AlbumArtUrl = imgUrl }, + new Album { Title = "Le Tigre", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Le Tigre"], AlbumArtUrl = imgUrl }, + new Album { Title = "Led Zeppelin I", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Led Zeppelin II", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Led Zeppelin III", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Let There Be Rock", Genre = genres["Rock"], Price = 8.99M, Artist = artists["AC/DC"], AlbumArtUrl = imgUrl }, + new Album { Title = "Little Earthquakes", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Tori Amos"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live [Disc 1]", Genre = genres["Blues"], Price = 8.99M, Artist = artists["The Black Crowes"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live [Disc 2]", Genre = genres["Blues"], Price = 8.99M, Artist = artists["The Black Crowes"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live After Death", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live At Donington 1992 (Disc 1)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live At Donington 1992 (Disc 2)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live on Earth", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["The Cat Empire"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live On Two Legs [Live]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pearl Jam"], AlbumArtUrl = imgUrl }, + new Album { Title = "Living After Midnight", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Judas Priest"], AlbumArtUrl = imgUrl }, + new Album { Title = "Living", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Paddy Casey"], AlbumArtUrl = imgUrl }, + new Album { Title = "Load", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Love Changes Everything", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "MacArthur Park Suite", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Donna Summer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Machine Head", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Magical Mystery Tour", Genre = genres["Pop"], Price = 8.99M, Artist = artists["The Beatles"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mais Do Mesmo", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Legião Urbana"], AlbumArtUrl = imgUrl }, + new Album { Title = "Maquinarama", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Skank"], AlbumArtUrl = imgUrl }, + new Album { Title = "Marasim", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Jagjit Singh"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mascagni: Cavalleria Rusticana", Genre = genres["Classical"], Price = 8.99M, Artist = artists["James Levine"], AlbumArtUrl = imgUrl }, + new Album { Title = "Master of Puppets", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mechanics & Mathematics", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Venus Hum"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mental Jewelry", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Live"], AlbumArtUrl = imgUrl }, + new Album { Title = "Metallics", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "meteora", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Linkin Park"], AlbumArtUrl = imgUrl }, + new Album { Title = "Meus Momentos", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Gonzaguinha"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mezmerize", Genre = genres["Metal"], Price = 8.99M, Artist = artists["System Of A Down"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mezzanine", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Massive Attack"], AlbumArtUrl = imgUrl }, + new Album { Title = "Miles Ahead", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Miles Davis"], AlbumArtUrl = imgUrl }, + new Album { Title = "Milton Nascimento Ao Vivo", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Milton Nascimento"], AlbumArtUrl = imgUrl }, + new Album { Title = "Minas", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Milton Nascimento"], AlbumArtUrl = imgUrl }, + new Album { Title = "Minha Historia", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Chico Buarque"], AlbumArtUrl = imgUrl }, + new Album { Title = "Misplaced Childhood", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Marillion"], AlbumArtUrl = imgUrl }, + new Album { Title = "MK III The Final Concerts [Disc 1]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Morning Dance", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Spyro Gyra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Motley Crue Greatest Hits", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Mötley Crüe"], AlbumArtUrl = imgUrl }, + new Album { Title = "Moving Pictures", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Rush"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mozart: Chamber Music", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Nash Ensemble"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mozart: Symphonies Nos. 40 & 41", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Berliner Philharmoniker"], AlbumArtUrl = imgUrl }, + new Album { Title = "Murder Ballads", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Nick Cave and the Bad Seeds"], AlbumArtUrl = imgUrl }, + new Album { Title = "Music For The Jilted Generation", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["The Prodigy"], AlbumArtUrl = imgUrl }, + new Album { Title = "My Generation - The Very Best Of The Who", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Who"], AlbumArtUrl = imgUrl }, + new Album { Title = "My Name is Skrillex", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Skrillex"], AlbumArtUrl = imgUrl }, + new Album { Title = "Na Pista", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Cláudio Zoli"], AlbumArtUrl = imgUrl }, + new Album { Title = "Nevermind", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Nirvana"], AlbumArtUrl = imgUrl }, + new Album { Title = "New Adventures In Hi-Fi", Genre = genres["Rock"], Price = 8.99M, Artist = artists["R.E.M."], AlbumArtUrl = imgUrl }, + new Album { Title = "New Divide", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Linkin Park"], AlbumArtUrl = imgUrl }, + new Album { Title = "New York Dolls", Genre = genres["Punk"], Price = 8.99M, Artist = artists["New York Dolls"], AlbumArtUrl = imgUrl }, + new Album { Title = "News Of The World", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Queen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Nielsen: The Six Symphonies", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Göteborgs Symfoniker & Neeme Järvi"], AlbumArtUrl = imgUrl }, + new Album { Title = "Night At The Opera", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Queen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Night Castle", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Trans-Siberian Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Nkolo", Genre = genres["World"], Price = 8.99M, Artist = artists["Lokua Kanza"], AlbumArtUrl = imgUrl }, + new Album { Title = "No More Tears (Remastered)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "No Prayer For The Dying", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "No Security", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Rolling Stones"], AlbumArtUrl = imgUrl }, + new Album { Title = "O Brother, Where Art Thou?", Genre = genres["Country"], Price = 8.99M, Artist = artists["Alison Krauss"], AlbumArtUrl = imgUrl }, + new Album { Title = "O Samba Poconé", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Skank"], AlbumArtUrl = imgUrl }, + new Album { Title = "O(+>", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Prince"], AlbumArtUrl = imgUrl }, + new Album { Title = "Oceania", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Smashing Pumpkins"], AlbumArtUrl = imgUrl }, + new Album { Title = "Off the Deep End", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Weird Al"], AlbumArtUrl = imgUrl }, + new Album { Title = "OK Computer", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Radiohead"], AlbumArtUrl = imgUrl }, + new Album { Title = "Olodum", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Olodum"], AlbumArtUrl = imgUrl }, + new Album { Title = "One Love", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["David Guetta"], AlbumArtUrl = imgUrl }, + new Album { Title = "Operation: Mindcrime", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Queensrÿche"], AlbumArtUrl = imgUrl }, + new Album { Title = "Opiate", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tool"], AlbumArtUrl = imgUrl }, + new Album { Title = "Outbreak", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Dennis Chambers"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pachelbel: Canon & Gigue", Genre = genres["Classical"], Price = 8.99M, Artist = artists["English Concert & Trevor Pinnock"], AlbumArtUrl = imgUrl }, + new Album { Title = "Paid in Full", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Eric B. and Rakim"], AlbumArtUrl = imgUrl }, + new Album { Title = "Para Siempre", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Vicente Fernandez"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pause", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Four Tet"], AlbumArtUrl = imgUrl }, + new Album { Title = "Peace Sells... but Who's Buying", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Megadeth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Physical Graffiti [Disc 1]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Physical Graffiti [Disc 2]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Physical Graffiti", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Piece Of Mind", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pinkerton", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Weezer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Plays Metallica By Four Cellos", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Apocalyptica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pop", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Powerslave", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Prenda Minha", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Caetano Veloso"], AlbumArtUrl = imgUrl }, + new Album { Title = "Presence", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pretty Hate Machine", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Nine Inch Nails"], AlbumArtUrl = imgUrl }, + new Album { Title = "Prisoner", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Jezabels"], AlbumArtUrl = imgUrl }, + new Album { Title = "Privateering", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Mark Knopfler"], AlbumArtUrl = imgUrl }, + new Album { Title = "Prokofiev: Romeo & Juliet", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Michael Tilson Thomas"], AlbumArtUrl = imgUrl }, + new Album { Title = "Prokofiev: Symphony No.1", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sergei Prokofiev & Yuri Temirkanov"], AlbumArtUrl = imgUrl }, + new Album { Title = "PSY's Best 6th Part 1", Genre = genres["Pop"], Price = 8.99M, Artist = artists["PSY"], AlbumArtUrl = imgUrl }, + new Album { Title = "Purcell: The Fairy Queen", Genre = genres["Classical"], Price = 8.99M, Artist = artists["London Classical Players"], AlbumArtUrl = imgUrl }, + new Album { Title = "Purpendicular", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Purple", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Stone Temple Pilots"], AlbumArtUrl = imgUrl }, + new Album { Title = "Quanta Gente Veio Ver (Live)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Gilberto Gil"], AlbumArtUrl = imgUrl }, + new Album { Title = "Quanta Gente Veio ver--Bônus De Carnaval", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Gilberto Gil"], AlbumArtUrl = imgUrl }, + new Album { Title = "Quiet Songs", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Aisha Duo"], AlbumArtUrl = imgUrl }, + new Album { Title = "Raices", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Los Tigres del Norte"], AlbumArtUrl = imgUrl }, + new Album { Title = "Raising Hell", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Run DMC"], AlbumArtUrl = imgUrl }, + new Album { Title = "Raoul and the Kings of Spain ", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tears For Fears"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rattle And Hum", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Raul Seixas", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Raul Seixas"], AlbumArtUrl = imgUrl }, + new Album { Title = "Recovery [Explicit]", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Eminem"], AlbumArtUrl = imgUrl }, + new Album { Title = "Reign In Blood", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Slayer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Relayed", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Yes"], AlbumArtUrl = imgUrl }, + new Album { Title = "ReLoad", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Respighi:Pines of Rome", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Eugene Ormandy"], AlbumArtUrl = imgUrl }, + new Album { Title = "Restless and Wild", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Accept"], AlbumArtUrl = imgUrl }, + new Album { Title = "Retrospective I (1974-1980)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Rush"], AlbumArtUrl = imgUrl }, + new Album { Title = "Revelations", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Audioslave"], AlbumArtUrl = imgUrl }, + new Album { Title = "Revolver", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Beatles"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ride the Lighting ", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ride The Lightning", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ring My Bell", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Anita Ward"], AlbumArtUrl = imgUrl }, + new Album { Title = "Riot Act", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pearl Jam"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rise of the Phoenix", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Before the Dawn"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rock In Rio [CD1]", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rock In Rio [CD2]", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rock In Rio [CD2]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Roda De Funk", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Funk Como Le Gusta"], AlbumArtUrl = imgUrl }, + new Album { Title = "Room for Squares", Genre = genres["Pop"], Price = 8.99M, Artist = artists["John Mayer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Root Down", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Jimmy Smith"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rounds", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Four Tet"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rubber Factory", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Black Keys"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rust in Peace", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Megadeth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Sambas De Enredo 2001", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Various Artists"], AlbumArtUrl = imgUrl }, + new Album { Title = "Santana - As Years Go By", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Santana"], AlbumArtUrl = imgUrl }, + new Album { Title = "Santana Live", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Santana"], AlbumArtUrl = imgUrl }, + new Album { Title = "Saturday Night Fever", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Bee Gees"], AlbumArtUrl = imgUrl }, + new Album { Title = "Scary Monsters and Nice Sprites", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Skrillex"], AlbumArtUrl = imgUrl }, + new Album { Title = "Scheherazade", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Chicago Symphony Orchestra & Fritz Reiner"], AlbumArtUrl = imgUrl }, + new Album { Title = "SCRIABIN: Vers la flamme", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Christopher O'Riley"], AlbumArtUrl = imgUrl }, + new Album { Title = "Second Coming", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Stone Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Serie Sem Limite (Disc 1)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Tim Maia"], AlbumArtUrl = imgUrl }, + new Album { Title = "Serie Sem Limite (Disc 2)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Tim Maia"], AlbumArtUrl = imgUrl }, + new Album { Title = "Serious About Men", Genre = genres["Rap"], Price = 8.99M, Artist = artists["The Rubberbandits"], AlbumArtUrl = imgUrl }, + new Album { Title = "Seventh Son of a Seventh Son", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Short Bus", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Filter"], AlbumArtUrl = imgUrl }, + new Album { Title = "Sibelius: Finlandia", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Berliner Philharmoniker"], AlbumArtUrl = imgUrl }, + new Album { Title = "Singles Collection", Genre = genres["Rock"], Price = 8.99M, Artist = artists["David Bowie"], AlbumArtUrl = imgUrl }, + new Album { Title = "Six Degrees of Inner Turbulence", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Dream Theater"], AlbumArtUrl = imgUrl }, + new Album { Title = "Slave To The Empire", Genre = genres["Metal"], Price = 8.99M, Artist = artists["T&N"], AlbumArtUrl = imgUrl }, + new Album { Title = "Slaves And Masters", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Slouching Towards Bethlehem", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Robert James"], AlbumArtUrl = imgUrl }, + new Album { Title = "Smash", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Offspring"], AlbumArtUrl = imgUrl }, + new Album { Title = "Something Special", Genre = genres["Country"], Price = 8.99M, Artist = artists["Dolly Parton"], AlbumArtUrl = imgUrl }, + new Album { Title = "Somewhere in Time", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Song(s) You Know By Heart", Genre = genres["Country"], Price = 8.99M, Artist = artists["Jimmy Buffett"], AlbumArtUrl = imgUrl }, + new Album { Title = "Sound of Music", Genre = genres["Punk"], Price = 8.99M, Artist = artists["Adicts"], AlbumArtUrl = imgUrl }, + new Album { Title = "South American Getaway", Genre = genres["Classical"], Price = 8.99M, Artist = artists["The 12 Cellists of The Berlin Philharmonic"], AlbumArtUrl = imgUrl }, + new Album { Title = "Sozinho Remix Ao Vivo", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Caetano Veloso"], AlbumArtUrl = imgUrl }, + new Album { Title = "Speak of the Devil", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Spiritual State", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Nujabes"], AlbumArtUrl = imgUrl }, + new Album { Title = "St. Anger", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Still Life", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Opeth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Stop Making Sense", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Talking Heads"], AlbumArtUrl = imgUrl }, + new Album { Title = "Stormbringer", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Stranger than Fiction", Genre = genres["Punk"], Price = 8.99M, Artist = artists["Bad Religion"], AlbumArtUrl = imgUrl }, + new Album { Title = "Strauss: Waltzes", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Eugene Ormandy"], AlbumArtUrl = imgUrl }, + new Album { Title = "Supermodified", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Amon Tobin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Supernatural", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Santana"], AlbumArtUrl = imgUrl }, + new Album { Title = "Surfing with the Alien (Remastered)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Joe Satriani"], AlbumArtUrl = imgUrl }, + new Album { Title = "Switched-On Bach", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Wendy Carlos"], AlbumArtUrl = imgUrl }, + new Album { Title = "Symphony", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Szymanowski: Piano Works, Vol. 1", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Martin Roscoe"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tchaikovsky: The Nutcracker", Genre = genres["Classical"], Price = 8.99M, Artist = artists["London Symphony Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ted Nugent", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ted Nugent"], AlbumArtUrl = imgUrl }, + new Album { Title = "Teflon Don", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Rick Ross"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tell Another Joke at the Ol' Choppin' Block", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Danielson Famile"], AlbumArtUrl = imgUrl }, + new Album { Title = "Temple of the Dog", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Temple of the Dog"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ten", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pearl Jam"], AlbumArtUrl = imgUrl }, + new Album { Title = "Texas Flood", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Stevie Ray Vaughan"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Battle Rages On", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Beast Live", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Paul D'Ianno"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best Of 1980-1990", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best of 1990–2000", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best of Beethoven", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Nicolaus Esterhazy Sinfonia"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best Of Billy Cobham", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Billy Cobham"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best of Ed Motta", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Ed Motta"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best Of Van Halen, Vol. I", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Bridge", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Melanie Fiona"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Cage", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tygers of Pan Tang"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Chicago Transit Authority", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Chicago "], AlbumArtUrl = imgUrl }, + new Album { Title = "The Chronic", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Dr. Dre"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Colour And The Shape", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Foo Fighters"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Crane Wife", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["The Decemberists"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Cream Of Clapton", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Eric Clapton"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Cure", Genre = genres["Pop"], Price = 8.99M, Artist = artists["The Cure"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Dark Side Of The Moon", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pink Floyd"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Divine Conspiracy", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Epica"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Doors", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Doors"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Dream of the Blue Turtles", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Sting"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Essential Miles Davis [Disc 1]", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Miles Davis"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Essential Miles Davis [Disc 2]", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Miles Davis"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Final Concerts (Disc 2)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Final Frontier", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Head and the Heart", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Head and the Heart"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Joshua Tree", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Last Night of the Proms", Genre = genres["Classical"], Price = 8.99M, Artist = artists["BBC Concert Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Lumineers", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Lumineers"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Number of The Beast", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Number of The Beast", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Police Greatest Hits", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Police"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Song Remains The Same (Disc 1)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Song Remains The Same (Disc 2)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Southern Harmony and Musical Companion", Genre = genres["Blues"], Price = 8.99M, Artist = artists["The Black Crowes"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Spade", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Butch Walker & The Black Widows"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Stone Roses", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Stone Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Suburbs", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Arcade Fire"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Three Tenors Disc1/Disc2", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Carreras, Pavarotti, Domingo"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Trees They Grow So High", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Wall", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pink Floyd"], AlbumArtUrl = imgUrl }, + new Album { Title = "The X Factor", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Them Crooked Vultures", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Them Crooked Vultures"], AlbumArtUrl = imgUrl }, + new Album { Title = "This Is Happening", Genre = genres["Rock"], Price = 8.99M, Artist = artists["LCD Soundsystem"], AlbumArtUrl = imgUrl }, + new Album { Title = "Thunder, Lightning, Strike", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Go! Team"], AlbumArtUrl = imgUrl }, + new Album { Title = "Time to Say Goodbye", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Time, Love & Tenderness", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Michael Bolton"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tomorrow Starts Today", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Mobile"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tribute", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tuesday Night Music Club", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Sheryl Crow"], AlbumArtUrl = imgUrl }, + new Album { Title = "Umoja", Genre = genres["Rock"], Price = 8.99M, Artist = artists["BLØF"], AlbumArtUrl = imgUrl }, + new Album { Title = "Under the Pink", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Tori Amos"], AlbumArtUrl = imgUrl }, + new Album { Title = "Undertow", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tool"], AlbumArtUrl = imgUrl }, + new Album { Title = "Un-Led-Ed", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Dread Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Unplugged [Live]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Kiss"], AlbumArtUrl = imgUrl }, + new Album { Title = "Unplugged", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Eric Clapton"], AlbumArtUrl = imgUrl }, + new Album { Title = "Unplugged", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Eric Clapton"], AlbumArtUrl = imgUrl }, + new Album { Title = "Untrue", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Burial"], AlbumArtUrl = imgUrl }, + new Album { Title = "Use Your Illusion I", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Guns N' Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Use Your Illusion II", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Guns N' Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Use Your Illusion II", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Guns N' Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Van Halen III", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Van Halen", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Version 2.0", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Garbage"], AlbumArtUrl = imgUrl }, + new Album { Title = "Vinicius De Moraes", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Vinícius De Moraes"], AlbumArtUrl = imgUrl }, + new Album { Title = "Virtual XI", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Voodoo Lounge", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Rolling Stones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Vozes do MPB", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Various Artists"], AlbumArtUrl = imgUrl }, + new Album { Title = "Vs.", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pearl Jam"], AlbumArtUrl = imgUrl }, + new Album { Title = "Wagner: Favourite Overtures", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sir Georg Solti & Wiener Philharmoniker"], AlbumArtUrl = imgUrl }, + new Album { Title = "Walking Into Clarksdale", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Page & Plant"], AlbumArtUrl = imgUrl }, + new Album { Title = "Wapi Yo", Genre = genres["World"], Price = 8.99M, Artist = artists["Lokua Kanza"], AlbumArtUrl = imgUrl }, + new Album { Title = "War", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Warner 25 Anos", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Antônio Carlos Jobim"], AlbumArtUrl = imgUrl }, + new Album { Title = "Wasteland R&Btheque", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Raunchy"], AlbumArtUrl = imgUrl }, + new Album { Title = "Watermark", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Enya"], AlbumArtUrl = imgUrl }, + new Album { Title = "We Were Exploding Anyway", Genre = genres["Rock"], Price = 8.99M, Artist = artists["65daysofstatic"], AlbumArtUrl = imgUrl }, + new Album { Title = "Weill: The Seven Deadly Sins", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Orchestre de l'Opéra de Lyon"], AlbumArtUrl = imgUrl }, + new Album { Title = "White Pony", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deftones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Who's Next", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Who"], AlbumArtUrl = imgUrl }, + new Album { Title = "Wish You Were Here", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pink Floyd"], AlbumArtUrl = imgUrl }, + new Album { Title = "With Oden on Our Side", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Amon Amarth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Worlds", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Aaron Goldberg"], AlbumArtUrl = imgUrl }, + new Album { Title = "Worship Music", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Anthrax"], AlbumArtUrl = imgUrl }, + new Album { Title = "X&Y", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Coldplay"], AlbumArtUrl = imgUrl }, + new Album { Title = "Xinti", Genre = genres["World"], Price = 8.99M, Artist = artists["Sara Tavares"], AlbumArtUrl = imgUrl }, + new Album { Title = "Yano", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Yano"], AlbumArtUrl = imgUrl }, + new Album { Title = "Yesterday Once More Disc 1/Disc 2", Genre = genres["Pop"], Price = 8.99M, Artist = artists["The Carpenters"], AlbumArtUrl = imgUrl }, + new Album { Title = "Zooropa", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Zoso", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + }; + + foreach (var album in albums) + { + album.ArtistId = album.Artist.ArtistId; + album.GenreId = album.Genre.GenreId; + } + + return albums; + } + + private static Dictionary artists; + public static Dictionary Artists + { + get + { + if (artists == null) + { + var artistsList = new Artist[] + { + new Artist { Name = "65daysofstatic" }, + new Artist { Name = "Aaron Goldberg" }, + new Artist { Name = "Above & Beyond" }, + new Artist { Name = "Above the Fold" }, + new Artist { Name = "AC/DC" }, + new Artist { Name = "Accept" }, + new Artist { Name = "Adicts" }, + new Artist { Name = "Adrian Leaper & Doreen de Feis" }, + new Artist { Name = "Aerosmith" }, + new Artist { Name = "Aisha Duo" }, + new Artist { Name = "Al di Meola" }, + new Artist { Name = "Alabama Shakes" }, + new Artist { Name = "Alanis Morissette" }, + new Artist { Name = "Alberto Turco & Nova Schola Gregoriana" }, + new Artist { Name = "Alice in Chains" }, + new Artist { Name = "Alison Krauss" }, + new Artist { Name = "Amon Amarth" }, + new Artist { Name = "Amon Tobin" }, + new Artist { Name = "Amr Diab" }, + new Artist { Name = "Amy Winehouse" }, + new Artist { Name = "Anita Ward" }, + new Artist { Name = "Anthrax" }, + new Artist { Name = "Antônio Carlos Jobim" }, + new Artist { Name = "Apocalyptica" }, + new Artist { Name = "Aqua" }, + new Artist { Name = "Armand Van Helden" }, + new Artist { Name = "Arcade Fire" }, + new Artist { Name = "Audioslave" }, + new Artist { Name = "Bad Religion" }, + new Artist { Name = "Barenaked Ladies" }, + new Artist { Name = "BBC Concert Orchestra" }, + new Artist { Name = "Bee Gees" }, + new Artist { Name = "Before the Dawn" }, + new Artist { Name = "Berliner Philharmoniker" }, + new Artist { Name = "Billy Cobham" }, + new Artist { Name = "Black Label Society" }, + new Artist { Name = "Black Sabbath" }, + new Artist { Name = "BLØF" }, + new Artist { Name = "Blues Traveler" }, + new Artist { Name = "Boston Symphony Orchestra & Seiji Ozawa" }, + new Artist { Name = "Britten Sinfonia, Ivor Bolton & Lesley Garrett" }, + new Artist { Name = "Bruce Dickinson" }, + new Artist { Name = "Buddy Guy" }, + new Artist { Name = "Burial" }, + new Artist { Name = "Butch Walker & The Black Widows" }, + new Artist { Name = "Caetano Veloso" }, + new Artist { Name = "Cake" }, + new Artist { Name = "Calexico" }, + new Artist { Name = "Carly Rae Jepsen" }, + new Artist { Name = "Carreras, Pavarotti, Domingo" }, + new Artist { Name = "Cássia Eller" }, + new Artist { Name = "Cayouche" }, + new Artist { Name = "Chic" }, + new Artist { Name = "Chicago " }, + new Artist { Name = "Chicago Symphony Orchestra & Fritz Reiner" }, + new Artist { Name = "Chico Buarque" }, + new Artist { Name = "Chico Science & Nação Zumbi" }, + new Artist { Name = "Choir Of Westminster Abbey & Simon Preston" }, + new Artist { Name = "Chris Cornell" }, + new Artist { Name = "Christopher O'Riley" }, + new Artist { Name = "Cidade Negra" }, + new Artist { Name = "Cláudio Zoli" }, + new Artist { Name = "Coldplay" }, + new Artist { Name = "Creedence Clearwater Revival" }, + new Artist { Name = "Crosby, Stills, Nash, and Young" }, + new Artist { Name = "Daft Punk" }, + new Artist { Name = "Danielson Famile" }, + new Artist { Name = "David Bowie" }, + new Artist { Name = "David Coverdale" }, + new Artist { Name = "David Guetta" }, + new Artist { Name = "deadmau5" }, + new Artist { Name = "Deep Purple" }, + new Artist { Name = "Def Leppard" }, + new Artist { Name = "Deftones" }, + new Artist { Name = "Dennis Chambers" }, + new Artist { Name = "Deva Premal" }, + new Artist { Name = "Dio" }, + new Artist { Name = "Djavan" }, + new Artist { Name = "Dolly Parton" }, + new Artist { Name = "Donna Summer" }, + new Artist { Name = "Dr. Dre" }, + new Artist { Name = "Dread Zeppelin" }, + new Artist { Name = "Dream Theater" }, + new Artist { Name = "Duck Sauce" }, + new Artist { Name = "Earl Scruggs" }, + new Artist { Name = "Ed Motta" }, + new Artist { Name = "Edo de Waart & San Francisco Symphony" }, + new Artist { Name = "Elis Regina" }, + new Artist { Name = "Eminem" }, + new Artist { Name = "English Concert & Trevor Pinnock" }, + new Artist { Name = "Enya" }, + new Artist { Name = "Epica" }, + new Artist { Name = "Eric B. and Rakim" }, + new Artist { Name = "Eric Clapton" }, + new Artist { Name = "Eugene Ormandy" }, + new Artist { Name = "Faith No More" }, + new Artist { Name = "Falamansa" }, + new Artist { Name = "Filter" }, + new Artist { Name = "Foo Fighters" }, + new Artist { Name = "Four Tet" }, + new Artist { Name = "Frank Zappa & Captain Beefheart" }, + new Artist { Name = "Fretwork" }, + new Artist { Name = "Funk Como Le Gusta" }, + new Artist { Name = "Garbage" }, + new Artist { Name = "Gerald Moore" }, + new Artist { Name = "Gilberto Gil" }, + new Artist { Name = "Godsmack" }, + new Artist { Name = "Gonzaguinha" }, + new Artist { Name = "Göteborgs Symfoniker & Neeme Järvi" }, + new Artist { Name = "Guns N' Roses" }, + new Artist { Name = "Gustav Mahler" }, + new Artist { Name = "In This Moment" }, + new Artist { Name = "Incognito" }, + new Artist { Name = "INXS" }, + new Artist { Name = "Iron Maiden" }, + new Artist { Name = "Jagjit Singh" }, + new Artist { Name = "James Levine" }, + new Artist { Name = "Jamiroquai" }, + new Artist { Name = "Jimi Hendrix" }, + new Artist { Name = "Jimmy Buffett" }, + new Artist { Name = "Jimmy Smith" }, + new Artist { Name = "Joe Satriani" }, + new Artist { Name = "John Digweed" }, + new Artist { Name = "John Mayer" }, + new Artist { Name = "Jorge Ben" }, + new Artist { Name = "Jota Quest" }, + new Artist { Name = "Journey" }, + new Artist { Name = "Judas Priest" }, + new Artist { Name = "Julian Bream" }, + new Artist { Name = "Justice" }, + new Artist { Name = "Orchestre de l'Opéra de Lyon" }, + new Artist { Name = "King Crimson" }, + new Artist { Name = "Kiss" }, + new Artist { Name = "LCD Soundsystem" }, + new Artist { Name = "Le Tigre" }, + new Artist { Name = "Led Zeppelin" }, + new Artist { Name = "Legião Urbana" }, + new Artist { Name = "Lenny Kravitz" }, + new Artist { Name = "Les Arts Florissants & William Christie" }, + new Artist { Name = "Limp Bizkit" }, + new Artist { Name = "Linkin Park" }, + new Artist { Name = "Live" }, + new Artist { Name = "Lokua Kanza" }, + new Artist { Name = "London Symphony Orchestra" }, + new Artist { Name = "Los Tigres del Norte" }, + new Artist { Name = "Luciana Souza/Romero Lubambo" }, + new Artist { Name = "Lulu Santos" }, + new Artist { Name = "Lura" }, + new Artist { Name = "Marcos Valle" }, + new Artist { Name = "Marillion" }, + new Artist { Name = "Marisa Monte" }, + new Artist { Name = "Mark Knopfler" }, + new Artist { Name = "Martin Roscoe" }, + new Artist { Name = "Massive Attack" }, + new Artist { Name = "Maurizio Pollini" }, + new Artist { Name = "Megadeth" }, + new Artist { Name = "Mela Tenenbaum, Pro Musica Prague & Richard Kapp" }, + new Artist { Name = "Melanie Fiona" }, + new Artist { Name = "Men At Work" }, + new Artist { Name = "Metallica" }, + new Artist { Name = "M-Flo" }, + new Artist { Name = "Michael Bolton" }, + new Artist { Name = "Michael Tilson Thomas" }, + new Artist { Name = "Miles Davis" }, + new Artist { Name = "Milton Nascimento" }, + new Artist { Name = "Mobile" }, + new Artist { Name = "Modest Mouse" }, + new Artist { Name = "Mötley Crüe" }, + new Artist { Name = "Motörhead" }, + new Artist { Name = "Mumford & Sons" }, + new Artist { Name = "Munkle" }, + new Artist { Name = "Nash Ensemble" }, + new Artist { Name = "Neil Young" }, + new Artist { Name = "New York Dolls" }, + new Artist { Name = "Nick Cave and the Bad Seeds" }, + new Artist { Name = "Nicolaus Esterhazy Sinfonia" }, + new Artist { Name = "Nine Inch Nails" }, + new Artist { Name = "Nirvana" }, + new Artist { Name = "Norah Jones" }, + new Artist { Name = "Nujabes" }, + new Artist { Name = "O Terço" }, + new Artist { Name = "Oasis" }, + new Artist { Name = "Olodum" }, + new Artist { Name = "Opeth" }, + new Artist { Name = "Orchestra of The Age of Enlightenment" }, + new Artist { Name = "Os Paralamas Do Sucesso" }, + new Artist { Name = "Ozzy Osbourne" }, + new Artist { Name = "Paddy Casey" }, + new Artist { Name = "Page & Plant" }, + new Artist { Name = "Papa Wemba" }, + new Artist { Name = "Paul D'Ianno" }, + new Artist { Name = "Paul Oakenfold" }, + new Artist { Name = "Paul Van Dyk" }, + new Artist { Name = "Pearl Jam" }, + new Artist { Name = "Pet Shop Boys" }, + new Artist { Name = "Pink Floyd" }, + new Artist { Name = "Plug" }, + new Artist { Name = "Porcupine Tree" }, + new Artist { Name = "Portishead" }, + new Artist { Name = "Prince" }, + new Artist { Name = "Projected" }, + new Artist { Name = "PSY" }, + new Artist { Name = "Public Enemy" }, + new Artist { Name = "Queen" }, + new Artist { Name = "Queensrÿche" }, + new Artist { Name = "R.E.M." }, + new Artist { Name = "Radiohead" }, + new Artist { Name = "Rancid" }, + new Artist { Name = "Raul Seixas" }, + new Artist { Name = "Raunchy" }, + new Artist { Name = "Red Hot Chili Peppers" }, + new Artist { Name = "Rick Ross" }, + new Artist { Name = "Robert James" }, + new Artist { Name = "London Classical Players" }, + new Artist { Name = "Royal Philharmonic Orchestra" }, + new Artist { Name = "Run DMC" }, + new Artist { Name = "Rush" }, + new Artist { Name = "Santana" }, + new Artist { Name = "Sara Tavares" }, + new Artist { Name = "Sarah Brightman" }, + new Artist { Name = "Sasha" }, + new Artist { Name = "Scholars Baroque Ensemble" }, + new Artist { Name = "Scorpions" }, + new Artist { Name = "Sergei Prokofiev & Yuri Temirkanov" }, + new Artist { Name = "Sheryl Crow" }, + new Artist { Name = "Sir Georg Solti & Wiener Philharmoniker" }, + new Artist { Name = "Skank" }, + new Artist { Name = "Skrillex" }, + new Artist { Name = "Slash" }, + new Artist { Name = "Slayer" }, + new Artist { Name = "Soul-Junk" }, + new Artist { Name = "Soundgarden" }, + new Artist { Name = "Spyro Gyra" }, + new Artist { Name = "Stevie Ray Vaughan & Double Trouble" }, + new Artist { Name = "Stevie Ray Vaughan" }, + new Artist { Name = "Sting" }, + new Artist { Name = "Stone Temple Pilots" }, + new Artist { Name = "Styx" }, + new Artist { Name = "Sufjan Stevens" }, + new Artist { Name = "Supreme Beings of Leisure" }, + new Artist { Name = "System Of A Down" }, + new Artist { Name = "T&N" }, + new Artist { Name = "Talking Heads" }, + new Artist { Name = "Tears For Fears" }, + new Artist { Name = "Ted Nugent" }, + new Artist { Name = "Temple of the Dog" }, + new Artist { Name = "Terry Bozzio, Tony Levin & Steve Stevens" }, + new Artist { Name = "The 12 Cellists of The Berlin Philharmonic" }, + new Artist { Name = "The Axis of Awesome" }, + new Artist { Name = "The Beatles" }, + new Artist { Name = "The Black Crowes" }, + new Artist { Name = "The Black Keys" }, + new Artist { Name = "The Carpenters" }, + new Artist { Name = "The Cat Empire" }, + new Artist { Name = "The Cult" }, + new Artist { Name = "The Cure" }, + new Artist { Name = "The Decemberists" }, + new Artist { Name = "The Doors" }, + new Artist { Name = "The Eagles of Death Metal" }, + new Artist { Name = "The Go! Team" }, + new Artist { Name = "The Head and the Heart" }, + new Artist { Name = "The Jezabels" }, + new Artist { Name = "The King's Singers" }, + new Artist { Name = "The Lumineers" }, + new Artist { Name = "The Offspring" }, + new Artist { Name = "The Police" }, + new Artist { Name = "The Posies" }, + new Artist { Name = "The Prodigy" }, + new Artist { Name = "The Rolling Stones" }, + new Artist { Name = "The Rubberbandits" }, + new Artist { Name = "The Smashing Pumpkins" }, + new Artist { Name = "The Stone Roses" }, + new Artist { Name = "The Who" }, + new Artist { Name = "Them Crooked Vultures" }, + new Artist { Name = "TheStart" }, + new Artist { Name = "Thievery Corporation" }, + new Artist { Name = "Tiësto" }, + new Artist { Name = "Tim Maia" }, + new Artist { Name = "Ton Koopman" }, + new Artist { Name = "Tool" }, + new Artist { Name = "Tori Amos" }, + new Artist { Name = "Trampled By Turtles" }, + new Artist { Name = "Trans-Siberian Orchestra" }, + new Artist { Name = "Tygers of Pan Tang" }, + new Artist { Name = "U2" }, + new Artist { Name = "UB40" }, + new Artist { Name = "Uh Huh Her " }, + new Artist { Name = "Van Halen" }, + new Artist { Name = "Various Artists" }, + new Artist { Name = "Velvet Revolver" }, + new Artist { Name = "Venus Hum" }, + new Artist { Name = "Vicente Fernandez" }, + new Artist { Name = "Vinícius De Moraes" }, + new Artist { Name = "Weezer" }, + new Artist { Name = "Weird Al" }, + new Artist { Name = "Wendy Carlos" }, + new Artist { Name = "Wilhelm Kempff" }, + new Artist { Name = "Yano" }, + new Artist { Name = "Yehudi Menuhin" }, + new Artist { Name = "Yes" }, + new Artist { Name = "Yo-Yo Ma" }, + new Artist { Name = "Zeca Pagodinho" }, + new Artist { Name = "אריק אינשטיין"} + }; + + // TODO [EF] Swap to store generated keys when available + int artistId = 1; + artists = new Dictionary(); + foreach (Artist artist in artistsList) + { + artist.ArtistId = artistId++; + artists.Add(artist.Name, artist); + } + } + + return artists; + } + } + + private static Dictionary genres; + public static Dictionary Genres + { + get + { + if (genres == null) + { + var genresList = new Genre[] + { + new Genre { Name = "Pop" }, + new Genre { Name = "Rock" }, + new Genre { Name = "Jazz" }, + new Genre { Name = "Metal" }, + new Genre { Name = "Electronic" }, + new Genre { Name = "Blues" }, + new Genre { Name = "Latin" }, + new Genre { Name = "Rap" }, + new Genre { Name = "Classical" }, + new Genre { Name = "Alternative" }, + new Genre { Name = "Country" }, + new Genre { Name = "R&B" }, + new Genre { Name = "Indie" }, + new Genre { Name = "Punk" }, + new Genre { Name = "World" } + }; + + genres = new Dictionary(); + // TODO [EF] Swap to store generated keys when available + int genreId = 1; + foreach (Genre genre in genresList) + { + genre.GenreId = genreId++; + + // TODO [EF] Remove when null values are supported by update pipeline + genre.Description = genre.Name + " is great music (if you like it)."; + + genres.Add(genre.Name, genre); + } + } + + return genres; + } + } + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/Apis/Models/ShoppingCart.cs b/samples/angular/MusicStore/Apis/Models/ShoppingCart.cs new file mode 100644 index 0000000..41b3277 --- /dev/null +++ b/samples/angular/MusicStore/Apis/Models/ShoppingCart.cs @@ -0,0 +1,207 @@ +using Microsoft.AspNet.Http; +using Microsoft.Data.Entity; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MusicStore.Models +{ + public partial class ShoppingCart + { + MusicStoreContext _db; + string ShoppingCartId { get; set; } + + public ShoppingCart(MusicStoreContext db) + { + _db = db; + } + + public static ShoppingCart GetCart(MusicStoreContext db, HttpContext context) + { + var cart = new ShoppingCart(db); + cart.ShoppingCartId = cart.GetCartId(context); + return cart; + } + + public void AddToCart(Album album) + { + // Get the matching cart and album instances + var cartItem = _db.CartItems.SingleOrDefault( + c => c.CartId == ShoppingCartId + && c.AlbumId == album.AlbumId); + + if (cartItem == null) + { + // TODO [EF] Swap to store generated key once we support identity pattern + var nextCartItemId = _db.CartItems.Any() + ? _db.CartItems.Max(c => c.CartItemId) + 1 + : 1; + + // Create a new cart item if no cart item exists + cartItem = new CartItem + { + CartItemId = nextCartItemId, + AlbumId = album.AlbumId, + CartId = ShoppingCartId, + Count = 1, + DateCreated = DateTime.Now + }; + + _db.CartItems.Add(cartItem); + } + else + { + // If the item does exist in the cart, then add one to the quantity + cartItem.Count++; + + // TODO [EF] Remove this line once change detection is available + _db.Update(cartItem); + } + } + + public int RemoveFromCart(int id) + { + // Get the cart + var cartItem = _db.CartItems.Single( + cart => cart.CartId == ShoppingCartId + && cart.CartItemId == id); + + int itemCount = 0; + + if (cartItem != null) + { + if (cartItem.Count > 1) + { + cartItem.Count--; + + // TODO [EF] Remove this line once change detection is available + _db.Update(cartItem); + + itemCount = cartItem.Count; + } + else + { + _db.CartItems.Remove(cartItem); + } + } + + return itemCount; + } + + public void EmptyCart() + { + var cartItems = _db.CartItems.Where(cart => cart.CartId == ShoppingCartId); + + foreach (var cartItem in cartItems) + { + _db.Remove(cartItem); + } + } + + public List GetCartItems() + { + var cartItems = _db.CartItems.Where(cart => cart.CartId == ShoppingCartId).ToList(); + //TODO: Auto population of the related album data not available until EF feature is lighted up. + foreach (var cartItem in cartItems) + { + cartItem.Album = _db.Albums.Single(a => a.AlbumId == cartItem.AlbumId); + } + + return cartItems; + } + + public int GetCount() + { + // Get the count of each item in the cart and sum them up + int? count = (from cartItems in _db.CartItems + where cartItems.CartId == ShoppingCartId + select (int?)cartItems.Count).Sum(); + + // Return 0 if all entries are null + return count ?? 0; + } + + public decimal GetTotal() + { + // Multiply album price by count of that album to get + // the current price for each of those albums in the cart + // sum all album price totals to get the cart total + + // TODO Collapse to a single query once EF supports querying related data + decimal total = 0; + foreach (var item in _db.CartItems.Where(c => c.CartId == ShoppingCartId)) + { + var album = _db.Albums.Single(a => a.AlbumId == item.AlbumId); + total += item.Count * album.Price; + } + + return total; + } + + public int CreateOrder(Order order) + { + decimal orderTotal = 0; + + var cartItems = GetCartItems(); + + // TODO [EF] Swap to store generated identity key when supported + var nextId = _db.OrderDetails.Any() + ? _db.OrderDetails.Max(o => o.OrderDetailId) + 1 + : 1; + + // Iterate over the items in the cart, adding the order details for each + foreach (var item in cartItems) + { + //var album = _db.Albums.Find(item.AlbumId); + var album = _db.Albums.Single(a => a.AlbumId == item.AlbumId); + + var orderDetail = new OrderDetail + { + OrderDetailId = nextId, + AlbumId = item.AlbumId, + OrderId = order.OrderId, + UnitPrice = album.Price, + Quantity = item.Count, + }; + + // Set the order total of the shopping cart + orderTotal += (item.Count * album.Price); + + _db.OrderDetails.Add(orderDetail); + + nextId++; + } + + // Set the order's total to the orderTotal count + order.Total = orderTotal; + + // Empty the shopping cart + EmptyCart(); + + // Return the OrderId as the confirmation number + return order.OrderId; + } + + // We're using HttpContextBase to allow access to cookies. + public string GetCartId(HttpContext context) + { + var sessionCookie = context.Request.Cookies["Session"]; + string cartId = null; + + if (string.IsNullOrWhiteSpace(sessionCookie)) + { + //A GUID to hold the cartId. + cartId = Guid.NewGuid().ToString(); + + // Send cart Id as a cookie to the client. + context.Response.Cookies.Append("Session", cartId); + } + else + { + cartId = sessionCookie; + } + + return cartId; + } + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/Controllers/HomeController.cs b/samples/angular/MusicStore/Controllers/HomeController.cs new file mode 100755 index 0000000..aabaed6 --- /dev/null +++ b/samples/angular/MusicStore/Controllers/HomeController.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc; + +namespace MusicStore.Controllers +{ + public class HomeController : Controller + { + public IActionResult Index() + { + var url = Request.Path.Value; + if (url.EndsWith(".ico") || url.EndsWith(".map")) { + return new HttpStatusCodeResult(404); + } else { + return View(); + } + } + + public IActionResult Error() + { + return View("~/Views/Shared/Error.cshtml"); + } + } +} diff --git a/samples/angular/MusicStore/Infrastructure/ApiResult.cs b/samples/angular/MusicStore/Infrastructure/ApiResult.cs new file mode 100644 index 0000000..9aae780 --- /dev/null +++ b/samples/angular/MusicStore/Infrastructure/ApiResult.cs @@ -0,0 +1,63 @@ +using Microsoft.AspNet.Mvc; +using Microsoft.AspNet.Mvc.ModelBinding; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MusicStore.Infrastructure +{ + public class ApiResult : ActionResult + { + public ApiResult(ModelStateDictionary modelState) + : this() + { + if (modelState.Any(m => m.Value.Errors.Count > 0)) + { + StatusCode = 400; + Message = "The model submitted was invalid. Please correct the specified errors and try again."; + ModelErrors = modelState + .SelectMany(m => m.Value.Errors.Select(me => new ModelError + { + FieldName = m.Key, + ErrorMessage = me.ErrorMessage + })); + } + } + + public ApiResult() + { + + } + + [JsonIgnore] + public int? StatusCode { get; set; } + + public string Message { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public object Data { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public IEnumerable ModelErrors { get; set; } + + public override Task ExecuteResultAsync(ActionContext context) + { + if (StatusCode.HasValue) + { + context.HttpContext.Response.StatusCode = StatusCode.Value; + } + + var json = new JsonResult(this); + return json.ExecuteResultAsync(context); + } + + public class ModelError + { + public string FieldName { get; set; } + + public string ErrorMessage { get; set; } + } + } +} diff --git a/samples/angular/MusicStore/Infrastructure/NoCacheAttribute.cs b/samples/angular/MusicStore/Infrastructure/NoCacheAttribute.cs new file mode 100644 index 0000000..7c0a9cd --- /dev/null +++ b/samples/angular/MusicStore/Infrastructure/NoCacheAttribute.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNet.Mvc; +using System; +using Microsoft.AspNet.Mvc.Filters; + +namespace MusicStore.Infrastructure +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class NoCacheAttribute : ActionFilterAttribute + { + public override void OnResultExecuting(ResultExecutingContext context) + { + context.HttpContext.Response.Headers["Cache-Control"] = "no-cache, no-store, max-age=0"; + context.HttpContext.Response.Headers["Pragma"] = "no-cache"; + context.HttpContext.Response.Headers["Expires"] = "-1"; + + base.OnResultExecuting(context); + } + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/Infrastructure/PagedList.cs b/samples/angular/MusicStore/Infrastructure/PagedList.cs new file mode 100644 index 0000000..98c5b13 --- /dev/null +++ b/samples/angular/MusicStore/Infrastructure/PagedList.cs @@ -0,0 +1,150 @@ +using Microsoft.Data.Entity; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace MusicStore.Infrastructure +{ + public interface IPagedList + { + IEnumerable Data { get; } + + int Page { get; } + + int PageSize { get; } + + int TotalCount { get; } + } + + internal class PagedList : IPagedList + { + public PagedList(IEnumerable data, int page, int pageSize, int totalCount) + { + Data = data; + Page = page; + PageSize = pageSize; + TotalCount = totalCount; + } + + public IEnumerable Data { get; private set; } + + public int Page { get; private set; } + + public int PageSize { get; private set; } + + public int TotalCount { get; private set; } + } + + public static class PagedListExtensions + { + public static IPagedList ToPagedList(this IQueryable query, int page, int pageSize) + { + if (query == null) + { + throw new ArgumentNullException("query"); + } + + var pagingConfig = new PagingConfig(page, pageSize); + var skipCount = ValidatePagePropertiesAndGetSkipCount(pagingConfig); + + var data = query + .Skip(skipCount) + .Take(pagingConfig.PageSize) + .ToList(); + + if (skipCount > 0 && data.Count == 0) + { + // Requested page has no records, just return the first page + pagingConfig.Page = 1; + data = query + .Take(pagingConfig.PageSize) + .ToList(); + } + + return new PagedList(data, pagingConfig.Page, pagingConfig.PageSize, query.Count()); + } + + public static Task> ToPagedListAsync(this IQueryable query, int page, int pageSize, string sortExpression, Expression> defaultSortExpression, SortDirection defaultSortDirection = SortDirection.Ascending) + where TModel : class + { + return ToPagedListAsync(query, page, pageSize, sortExpression, defaultSortExpression, defaultSortDirection, null); + } + + public static async Task> ToPagedListAsync(this IQueryable query, int page, int pageSize, string sortExpression, Expression> defaultSortExpression, SortDirection defaultSortDirection, Func selector) + where TModel : class + where TResult : class + { + if (query == null) + { + throw new ArgumentNullException("query"); + } + + var pagingConfig = new PagingConfig(page, pageSize); + var skipCount = ValidatePagePropertiesAndGetSkipCount(pagingConfig); + var dataQuery = query; + + if (defaultSortExpression != null) + { + dataQuery = dataQuery + .SortBy(sortExpression, defaultSortExpression); + } + + var data = await dataQuery + .Skip(skipCount) + .Take(pagingConfig.PageSize) + .ToListAsync(); + + if (skipCount > 0 && data.Count == 0) + { + // Requested page has no records, just return the first page + pagingConfig.Page = 1; + data = await dataQuery + .Take(pagingConfig.PageSize) + .ToListAsync(); + } + + var count = await query.CountAsync(); + + var resultData = selector != null + ? data.Select(selector) + : data.Cast(); + + return new PagedList(resultData, pagingConfig.Page, pagingConfig.PageSize, count); + } + + private static int ValidatePagePropertiesAndGetSkipCount(PagingConfig pagingConfig) + { + if (pagingConfig.Page < 1) + { + pagingConfig.Page = 1; + } + + if (pagingConfig.PageSize < 10) + { + pagingConfig.PageSize = 10; + } + + if (pagingConfig.PageSize > 100) + { + pagingConfig.PageSize = 100; + } + + return pagingConfig.PageSize * (pagingConfig.Page - 1); + } + + internal class PagingConfig + { + public PagingConfig(int page, int pageSize) + { + Page = page; + PageSize = pageSize; + } + + public int Page { get; set; } + + public int PageSize { get; set; } + } + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/Infrastructure/SortDirection.cs b/samples/angular/MusicStore/Infrastructure/SortDirection.cs new file mode 100644 index 0000000..28f7e86 --- /dev/null +++ b/samples/angular/MusicStore/Infrastructure/SortDirection.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MusicStore.Infrastructure +{ + public enum SortDirection + { + Ascending, + Descending + } +} diff --git a/samples/angular/MusicStore/Infrastructure/SortExpression.cs b/samples/angular/MusicStore/Infrastructure/SortExpression.cs new file mode 100644 index 0000000..279efb7 --- /dev/null +++ b/samples/angular/MusicStore/Infrastructure/SortExpression.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.ViewFeatures; + +namespace MusicStore.Infrastructure +{ + public static class SortExpression + { + private const string SORT_DIRECTION_DESC = " DESC"; + + public static IQueryable SortBy(this IQueryable query, string sortExpression, Expression> defaultSortExpression, SortDirection defaultSortDirection = SortDirection.Ascending) where TModel : class + { + return SortBy(query, sortExpression ?? Create(defaultSortExpression, defaultSortDirection)); + } + + public static string Create(Expression> expression, SortDirection sortDirection = SortDirection.Ascending) where TModel : class + { + var expressionText = ExpressionHelper.GetExpressionText(expression); + // TODO: Validate the expression depth, etc. + + var sortExpression = expressionText; + + if (sortDirection == SortDirection.Descending) + { + sortExpression += SORT_DIRECTION_DESC; + } + + return sortExpression; + } + + public static IQueryable SortBy(this IQueryable source, string sortExpression) where T : class + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + + if (String.IsNullOrWhiteSpace(sortExpression)) + { + return source; + } + + sortExpression = sortExpression.Trim(); + var isDescending = false; + + // DataSource control passes the sort parameter with a direction + // if the direction is descending + if (sortExpression.EndsWith(SORT_DIRECTION_DESC, StringComparison.OrdinalIgnoreCase)) + { + isDescending = true; + var descIndex = sortExpression.Length - SORT_DIRECTION_DESC.Length; + sortExpression = sortExpression.Substring(0, descIndex).Trim(); + } + + if (string.IsNullOrEmpty(sortExpression)) + { + return source; + } + + ParameterExpression parameter = Expression.Parameter(source.ElementType, String.Empty); + + // Build up the property expression, e.g.: (m => m.Foo.Bar) + var sortExpressionParts = sortExpression.Split('.'); + Expression propertyExpression = parameter; + foreach (var property in sortExpressionParts) + { + propertyExpression = Expression.Property(propertyExpression, property); + } + + LambdaExpression lambda = Expression.Lambda(propertyExpression, parameter); + + var methodName = (isDescending) ? "OrderByDescending" : "OrderBy"; + + Expression methodCallExpression = Expression.Call( + typeof(Queryable), + methodName, + new[] { source.ElementType, propertyExpression.Type }, + source.Expression, + Expression.Quote(lambda)); + + return (IQueryable)source.Provider.CreateQuery(methodCallExpression); + } + } +} diff --git a/samples/angular/MusicStore/SiteSettings.cs b/samples/angular/MusicStore/SiteSettings.cs new file mode 100644 index 0000000..50a86c2 --- /dev/null +++ b/samples/angular/MusicStore/SiteSettings.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MusicStore +{ + public class SiteSettings + { + public string DefaultAdminUsername { get; set; } + public string DefaultAdminPassword { get; set; } + } +} diff --git a/samples/angular/MusicStore/Startup.cs b/samples/angular/MusicStore/Startup.cs new file mode 100755 index 0000000..89fb223 --- /dev/null +++ b/samples/angular/MusicStore/Startup.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Microsoft.AspNet.Authorization; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Hosting; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Identity.EntityFramework; +using Microsoft.AspNet.NodeServices; +using Microsoft.Data.Entity; +using Microsoft.Dnx.Runtime; +using Microsoft.Framework.Configuration; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; +using MusicStore.Apis; +using MusicStore.Models; + +namespace MusicStore +{ + public class Startup + { + public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv) + { + // Setup configuration sources. + var builder = new ConfigurationBuilder() + .SetBasePath(appEnv.ApplicationBasePath) + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; set; } + + // This method gets called by the runtime. + public void ConfigureServices(IServiceCollection services) + { + services.Configure(settings => + { + settings.DefaultAdminUsername = Configuration["DefaultAdminUsername"]; + settings.DefaultAdminPassword = Configuration["DefaultAdminPassword"]; + }); + + // Add MVC services to the services container. + services.AddMvc(); + + // Uncomment the following line to add Web API services which makes it easier to port Web API 2 controllers. + // You will also need to add the Microsoft.AspNet.Mvc.WebApiCompatShim package to the 'dependencies' section of project.json. + // services.AddWebApiConventions(); + + // Add EF services to the service container + services.AddEntityFramework() + .AddSqlite() + .AddDbContext(options => { + options.UseSqlite(Configuration["DbConnectionString"]); + }); + + // Add Identity services to the services container + services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + // Uncomment the following line to add Web API services which makes it easier to port Web API 2 controllers. + // You will also need to add the Microsoft.AspNet.Mvc.WebApiCompatShim package to the 'dependencies' section of project.json. + // services.AddWebApiConventions(); + + // Configure Auth + services.Configure(options => + { + options.AddPolicy("app-ManageStore", new AuthorizationPolicyBuilder().RequireClaim("app-ManageStore", "Allowed").Build()); + }); + + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + } + + // Configure is called after ConfigureServices is called. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + // Initialize the sample data + SampleData.InitializeMusicStoreDatabaseAsync(app.ApplicationServices).Wait(); + + loggerFactory.MinimumLevel = LogLevel.Information; + loggerFactory.AddConsole(); + loggerFactory.AddDebug(); + + // Configure the HTTP request pipeline. + + // Add the platform handler to the request pipeline. + app.UseIISPlatformHandler(); + + // Add the following to the request pipeline only in development environment. + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + // Add Error handling middleware which catches all application specific errors and + // send the request to the following path or controller action. + app.UseExceptionHandler("/Home/Error"); + } + + var nodeInstance = new NodeInstance(); + app.Use(async (context, next) => { + if (context.Request.Path.Value.EndsWith(".less")) { + // Note: check for directory traversal + var output = await nodeInstance.Invoke("lessCompiler.js", env.WebRootPath + context.Request.Path.Value); + await context.Response.WriteAsync(output); + } else { + await next(); + } + }); + + // Add static files to the request pipeline. + app.UseStaticFiles(); + + // Add MVC to the request pipeline. + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + + routes.MapRoute("spa-fallback", "{*anything}", new { controller = "Home", action = "Index" }); + + // Uncomment the following line to add a route for porting Web API 2 controllers. + // routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}"); + }); + } + } +} diff --git a/samples/angular/MusicStore/Views/Home/Index.cshtml b/samples/angular/MusicStore/Views/Home/Index.cshtml new file mode 100755 index 0000000..9019251 --- /dev/null +++ b/samples/angular/MusicStore/Views/Home/Index.cshtml @@ -0,0 +1,21 @@ +@{ + ViewData["Title"] = "Home Page"; +} + + + + Loading... + + + +@section scripts { + + + + + + + + + +} diff --git a/samples/angular/MusicStore/Views/Shared/Error.cshtml b/samples/angular/MusicStore/Views/Shared/Error.cshtml new file mode 100755 index 0000000..a288cb0 --- /dev/null +++ b/samples/angular/MusicStore/Views/Shared/Error.cshtml @@ -0,0 +1,6 @@ +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

diff --git a/samples/angular/MusicStore/Views/Shared/_Layout.cshtml b/samples/angular/MusicStore/Views/Shared/_Layout.cshtml new file mode 100755 index 0000000..21900d3 --- /dev/null +++ b/samples/angular/MusicStore/Views/Shared/_Layout.cshtml @@ -0,0 +1,40 @@ + + + + + + Music Store + + + + + + + + + + + + + @RenderBody() + + + + + + + + + + + @RenderSection("scripts", required: false) + + diff --git a/samples/angular/MusicStore/Views/_ViewImports.cshtml b/samples/angular/MusicStore/Views/_ViewImports.cshtml new file mode 100755 index 0000000..808b5ca --- /dev/null +++ b/samples/angular/MusicStore/Views/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using MusicStore +@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers" +@addTagHelper "*, Microsoft.AspNet.NodeServices.Angular" diff --git a/samples/angular/MusicStore/Views/_ViewStart.cshtml b/samples/angular/MusicStore/Views/_ViewStart.cshtml new file mode 100755 index 0000000..66b5da2 --- /dev/null +++ b/samples/angular/MusicStore/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/samples/angular/MusicStore/appsettings.json b/samples/angular/MusicStore/appsettings.json new file mode 100755 index 0000000..c8d7a43 --- /dev/null +++ b/samples/angular/MusicStore/appsettings.json @@ -0,0 +1,3 @@ +{ + "DbConnectionString": "Data Source=music-db.sqlite" +} diff --git a/samples/angular/MusicStore/gulpfile.js b/samples/angular/MusicStore/gulpfile.js new file mode 100755 index 0000000..70895ec --- /dev/null +++ b/samples/angular/MusicStore/gulpfile.js @@ -0,0 +1,53 @@ +/// + +"use strict"; + +var path = require('path'); +var gulp = require('gulp'); +var del = require('del'); +var eventStream = require('event-stream'); +var typescript = require('gulp-typescript'); +var inlineNg2Template = require('gulp-inline-ng2-template'); +var sourcemaps = require('gulp-sourcemaps'); + +var project = require("./project.json"); +var webroot = "./" + project.webroot + "/"; + +var config = { + libBase: 'node_modules', + lib: [ + require.resolve('bootstrap/dist/css/bootstrap.css'), + path.dirname(require.resolve('bootstrap/dist/fonts/glyphicons-halflings-regular.woff')) + '/**', + require.resolve('traceur/bin/traceur-runtime.js'), + require.resolve('es6-module-loader/dist/es6-module-loader-sans-promises.js'), + require.resolve('reflect-metadata/Reflect.js'), + require.resolve('systemjs/dist/system.src.js'), + require.resolve('angular2/bundles/angular2.dev.js'), + require.resolve('angular2/bundles/router.dev.js'), + require.resolve('angular2/bundles/http.dev.js'), + require.resolve('jquery/dist/jquery.js'), + require.resolve('bootstrap/dist/js/bootstrap.js') + ] +}; + +gulp.task('build.lib', function () { + return gulp.src(config.lib, { base: config.libBase }) + .pipe(gulp.dest(webroot + 'lib')); +}); + +gulp.task('build', ['build.lib'], function () { + var tsProject = typescript.createProject('./tsconfig.json', { typescript: require('typescript') }); + var tsSrcInlined = gulp.src([webroot + '**/*.ts'], { base: webroot }) + .pipe(inlineNg2Template({ base: webroot })); + return eventStream.merge(tsSrcInlined, gulp.src('Typings/**/*.ts')) + .pipe(sourcemaps.init()) + .pipe(typescript(tsProject)) + .pipe(sourcemaps.write()) + .pipe(gulp.dest(webroot)); +}); + +gulp.task('clean', function () { + return del([webroot + 'lib']); +}); + +gulp.task('default', ['build']); diff --git a/samples/angular/MusicStore/package.json b/samples/angular/MusicStore/package.json new file mode 100644 index 0000000..f1db973 --- /dev/null +++ b/samples/angular/MusicStore/package.json @@ -0,0 +1,27 @@ +{ + "name": "MusicStore", + "version": "0.0.0", + "dependencies": { + "angular2": "2.0.0-alpha.44", + "angular2-universal-patched": "^0.5.4", + "body-parser": "^1.14.1", + "bootstrap": "^3.3.5", + "del": "^2.0.2", + "es6-module-loader": "^0.15.0", + "express": "^4.13.3", + "jquery": "^2.1.4", + "less": "^2.5.3", + "reflect-metadata": "^0.1.2", + "systemjs": "^0.19.3", + "traceur": "0.0.91" + }, + "devDependencies": { + "del": "^2.0.2", + "event-stream": "^3.3.1", + "gulp": "^3.9.0", + "gulp-inline-ng2-template": "0.0.7", + "gulp-sourcemaps": "^1.6.0", + "gulp-typescript": "^2.9.0", + "typescript": "^1.6.2" + } +} diff --git a/samples/angular/MusicStore/project.json b/samples/angular/MusicStore/project.json new file mode 100755 index 0000000..7a9f18c --- /dev/null +++ b/samples/angular/MusicStore/project.json @@ -0,0 +1,49 @@ +{ + "webroot": "wwwroot", + "version": "1.0.0-*", + "tooling": { + "defaultNamespace": "MusicStore" + }, + "dependencies": { + "Microsoft.AspNet.Diagnostics": "1.0.0-beta8", + "Microsoft.AspNet.IISPlatformHandler": "1.0.0-beta8", + "Microsoft.AspNet.Mvc": "6.0.0-beta8", + "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-beta8", + "Microsoft.AspNet.Server.Kestrel": "1.0.0-beta8", + "Microsoft.AspNet.StaticFiles": "1.0.0-beta8", + "Microsoft.AspNet.Tooling.Razor": "1.0.0-beta8", + "Microsoft.Framework.Configuration.Json": "1.0.0-beta8", + "Microsoft.Framework.Logging": "1.0.0-beta8", + "Microsoft.Framework.Logging.Console": "1.0.0-beta8", + "Microsoft.Framework.Logging.Debug": "1.0.0-beta8", + "EntityFramework.SQLite": "7.0.0-beta8", + "Microsoft.AspNet.Identity.EntityFramework": "3.0.0-beta8", + "AutoMapper": "4.0.0-alpha1", + "Microsoft.AspNet.NodeServices.Angular": "1.0.0-alpha1" + }, + "commands": { + "web": "Microsoft.AspNet.Server.Kestrel" + }, + "frameworks": { + "dnx451": {}, + "dnxcore50": {} + }, + "exclude": [ + "wwwroot", + "node_modules", + "bower_components" + ], + "publishExclude": [ + "node_modules", + "bower_components", + "**.xproj", + "**.user", + "**.vspscc" + ], + "scripts": { + "prepublish": [ + "npm install", + "gulp" + ] + } +} \ No newline at end of file diff --git a/samples/angular/MusicStore/tsconfig.json b/samples/angular/MusicStore/tsconfig.json new file mode 100644 index 0000000..a4c5eeb --- /dev/null +++ b/samples/angular/MusicStore/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "sourceMap": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "noLib": false + }, + "exclude": [ + "node_modules" + ] +} diff --git a/samples/angular/MusicStore/wwwroot/css/site.css b/samples/angular/MusicStore/wwwroot/css/site.css new file mode 100644 index 0000000..952e03f --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/css/site.css @@ -0,0 +1,3 @@ +body { + padding-top: 50px; +} diff --git a/samples/angular/MusicStore/wwwroot/css/styles.less b/samples/angular/MusicStore/wwwroot/css/styles.less new file mode 100644 index 0000000..564f8f4 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/css/styles.less @@ -0,0 +1,14 @@ +@base: #f938ab; + +.box-shadow(@style, @c) when (iscolor(@c)) { + -webkit-box-shadow: @style @c; + box-shadow: @style @c; +} +.box-shadow(@style, @alpha: 50%) when (isnumber(@alpha)) { + .box-shadow(@style, rgba(0, 0, 0, @alpha)); +} +.box { + color: saturate(@base, 5%); + border-color: lighten(@base, 30%); + div { .box-shadow(0 0 5px, 30%) } +} diff --git a/samples/angular/MusicStore/wwwroot/images/home-showcase.png b/samples/angular/MusicStore/wwwroot/images/home-showcase.png new file mode 100644 index 0000000..258c19d Binary files /dev/null and b/samples/angular/MusicStore/wwwroot/images/home-showcase.png differ diff --git a/samples/angular/MusicStore/wwwroot/images/logo.png b/samples/angular/MusicStore/wwwroot/images/logo.png new file mode 100644 index 0000000..d334c86 Binary files /dev/null and b/samples/angular/MusicStore/wwwroot/images/logo.png differ diff --git a/samples/angular/MusicStore/wwwroot/images/placeholder.png b/samples/angular/MusicStore/wwwroot/images/placeholder.png new file mode 100644 index 0000000..1f73dbb Binary files /dev/null and b/samples/angular/MusicStore/wwwroot/images/placeholder.png differ diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/admin/admin-home/admin-home.html b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/admin-home/admin-home.html new file mode 100644 index 0000000..79e4fcd --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/admin-home/admin-home.html @@ -0,0 +1,3 @@ +

Store Manager

+ + diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/admin/admin-home/admin-home.ts b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/admin-home/admin-home.ts new file mode 100644 index 0000000..73eeafc --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/admin-home/admin-home.ts @@ -0,0 +1,21 @@ +import * as ng from 'angular2/angular2'; +import * as router from 'angular2/router'; +import { AlbumsList } from '../albums-list/albums-list'; +import { AlbumDetails } from '../album-details/album-details'; +import { AlbumEdit } from '../album-edit/album-edit'; + +@ng.Component({ + selector: 'admin-home' +}) +@router.RouteConfig([ + { path: 'albums', as: 'Albums', component: AlbumsList }, + { path: 'album/details/:albumId', as: 'AlbumDetails', component: AlbumDetails }, + { path: 'album/edit/:albumId', as: 'AlbumEdit', component: AlbumEdit } +]) +@ng.View({ + templateUrl: './ng-app/components/admin/admin-home/admin-home.html', + directives: [router.ROUTER_DIRECTIVES] +}) +export class AdminHome { + +} diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-delete-prompt/album-delete-prompt.html b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-delete-prompt/album-delete-prompt.html new file mode 100644 index 0000000..a04250c --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-delete-prompt/album-delete-prompt.html @@ -0,0 +1,17 @@ + diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-delete-prompt/album-delete-prompt.ts b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-delete-prompt/album-delete-prompt.ts new file mode 100644 index 0000000..ca2b2e1 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-delete-prompt/album-delete-prompt.ts @@ -0,0 +1,25 @@ +import * as ng from 'angular2/angular2'; +import * as models from '../../../models/models'; + +@ng.Component({ + selector: 'album-delete-prompt' +}) +@ng.View({ + templateUrl: './ng-app/components/admin/album-delete-prompt/album-delete-prompt.html', + directives: [ng.NgIf] +}) +export class AlbumDeletePrompt { + private modalElement: any; + public album: models.Album; + + constructor(@ng.Inject(ng.ElementRef) elementRef: ng.ElementRef) { + if (typeof window !== 'undefined') { + this.modalElement = (window).jQuery(".modal", elementRef.nativeElement); + } + } + + public show(album: models.Album) { + this.album = album; + this.modalElement.modal(); + } +} diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-details/album-details.html b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-details/album-details.html new file mode 100644 index 0000000..1f66a63 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-details/album-details.html @@ -0,0 +1,50 @@ +

Album Details

+
+ +
+
+ +
+

{{ albumData.Artist.Name }}

+
+
+
+ +
+

{{ albumData.Genre.Name }}

+
+
+
+ +
+

{{ albumData.Title }}

+
+
+
+ +
+

{{ albumData.Price | currency:'USD':true }}

+
+
+
+ +
+

{{ albumData.AlbumArtUrl }}

+
+
+
+ +
+ {{ albumData.Title }} +
+
+
+
+ Edit + + Back to List +
+
+
+ + \ No newline at end of file diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-details/album-details.ts b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-details/album-details.ts new file mode 100644 index 0000000..bf38fe4 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-details/album-details.ts @@ -0,0 +1,22 @@ +import * as ng from 'angular2/angular2'; +import * as router from 'angular2/router'; +import * as models from '../../../models/models'; +import { Http, HTTP_BINDINGS } from 'angular2/http'; +import { AlbumDeletePrompt } from '../album-delete-prompt/album-delete-prompt'; + +@ng.Component({ + selector: 'album-details' +}) +@ng.View({ + templateUrl: './ng-app/components/admin/album-details/album-details.html', + directives: [router.ROUTER_DIRECTIVES, ng.NgIf, AlbumDeletePrompt] +}) +export class AlbumDetails { + public albumData: models.Album; + + constructor(http: Http, routeParam: router.RouteParams) { + http.get('/api/albums/' + routeParam.params['albumId']).subscribe(result => { + this.albumData = result.json(); + }); + } +} diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-edit/album-edit.html b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-edit/album-edit.html new file mode 100644 index 0000000..1276a38 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-edit/album-edit.html @@ -0,0 +1,45 @@ +

Album Edit

+
+ +
+ + + + + + + + + + + + + +
+ $ + +
+
+ + + + + + + + + + + + + Back to List + +
+ + \ No newline at end of file diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-edit/album-edit.ts b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-edit/album-edit.ts new file mode 100644 index 0000000..e199e22 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-edit/album-edit.ts @@ -0,0 +1,82 @@ +import * as ng from 'angular2/angular2'; +import * as router from 'angular2/router'; +import * as models from '../../../models/models'; +import { Http, HTTP_BINDINGS, Headers } from 'angular2/http'; +import { AlbumDeletePrompt } from '../album-delete-prompt/album-delete-prompt'; +import { FormField } from '../form-field/form-field'; + +@ng.Component({ + selector: 'album-edit' +}) +@ng.View({ + templateUrl: './ng-app/components/admin/album-edit/album-edit.html', + directives: [router.ROUTER_DIRECTIVES, ng.NgIf, ng.NgFor, AlbumDeletePrompt, FormField, ng.FORM_DIRECTIVES] +}) +export class AlbumEdit { + public form: ng.ControlGroup; + public artists: models.Artist[]; + public genres: models.Genre[]; + public originalAlbum: models.Album; + + private _http: Http; + + constructor(fb: ng.FormBuilder, http: Http, routeParam: router.RouteParams) { + this._http = http; + + http.get('/api/albums/' + routeParam.params['albumId']).subscribe(result => { + var json = result.json(); + this.originalAlbum = json; + (this.form.controls['title']).updateValue(json.Title); + (this.form.controls['price']).updateValue(json.Price); + (this.form.controls['artist']).updateValue(json.ArtistId); + (this.form.controls['genre']).updateValue(json.GenreId); + (this.form.controls['albumArtUrl']).updateValue(json.AlbumArtUrl); + }); + + http.get('/api/artists/lookup').subscribe(result => { + this.artists = result.json(); + }); + + http.get('/api/genres/genre-lookup').subscribe(result => { + this.genres = result.json(); + }); + + this.form = fb.group({ + artist: fb.control('', ng.Validators.required), + genre: fb.control('', ng.Validators.required), + title: fb.control('', ng.Validators.required), + price: fb.control('', ng.Validators.compose([ng.Validators.required, AlbumEdit._validatePrice])), + albumArtUrl: fb.control('', ng.Validators.required) + }); + } + + public onSubmitModelBased() { + // Force all fields to show any validation errors even if they haven't been touched + Object.keys(this.form.controls).forEach(controlName => { + this.form.controls[controlName].markAsTouched(); + }); + + if (this.form.valid) { + var controls = this.form.controls; + var albumId = this.originalAlbum.AlbumId; + (window).fetch(`/api/albums/${ albumId }/update`, { + method: 'put', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + AlbumArtUrl: controls['albumArtUrl'].value, + AlbumId: albumId, + ArtistId: controls['artist'].value, + GenreId: controls['genre'].value, + Price: controls['price'].value, + Title: controls['title'].value + }) + }).then(response => { + console.log(response); + }); + } + } + + private static _validatePrice(control: ng.Control): { [key: string]: boolean } { + return /^\d+\.\d+$/.test(control.value) ? null : { price: true }; + } +} diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/admin/albums-list/albums-list.html b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/albums-list/albums-list.html new file mode 100644 index 0000000..ac3a4e6 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/albums-list/albums-list.html @@ -0,0 +1,44 @@ +

Albums

+ + + + + + + + + + + + + + + + + + + + + + +
GenreArtistTitlePrice
{{ row.Genre.Name }}{{ row.Artist.Name }}{{ row.Title }}{{ row.Price | currency:'USD':true }} +
+ Details + Edit + Delete +
+
+ +
+ + + + + +
+ +

{{ totalCount }} total albums

diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/admin/albums-list/albums-list.ts b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/albums-list/albums-list.ts new file mode 100644 index 0000000..49b2f55 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/albums-list/albums-list.ts @@ -0,0 +1,70 @@ +import * as ng from 'angular2/angular2'; +import * as router from 'angular2/router'; +import { Http, HTTP_BINDINGS } from 'angular2/http'; +import * as models from '../../../models/models'; +import { AlbumDeletePrompt } from '../album-delete-prompt/album-delete-prompt'; + +@ng.Component({ + selector: 'albums-list' +}) +@ng.View({ + templateUrl: './ng-app/components/admin/albums-list/albums-list.html', + directives: [ng.NgFor, ng.NgClass, router.ROUTER_DIRECTIVES, AlbumDeletePrompt] +}) +export class AlbumsList { + public rows: models.Album[]; + public canGoBack: boolean; + public canGoForward: boolean; + public pageLinks: any[]; + public totalCount: number; + public get pageIndex() { + return this._pageIndex; + } + + private _http: Http; + private _pageIndex = 1; + private _sortBy = "Title"; + private _sortByDesc = false; + + constructor(http: Http) { + this._http = http; + this.refreshData(); + } + + public sortBy(col: string) { + this._sortByDesc = col === this._sortBy ? !this._sortByDesc : false; + this._sortBy = col; + this.refreshData(); + } + + public goToPage(pageIndex: number) { + this._pageIndex = pageIndex; + this.refreshData(); + } + + public goToLast() { + this.goToPage(this.pageLinks[this.pageLinks.length - 1].index); + } + + refreshData() { + var sortBy = this._sortBy + (this._sortByDesc ? ' DESC' : ''); + this._http.get(`/api/albums?page=${ this._pageIndex }&pageSize=50&sortBy=${ sortBy }`).subscribe(result => { + var json = result.json(); + this.rows = json.Data; + + var numPages = Math.ceil(json.TotalCount / json.PageSize); + this.pageLinks = []; + for (var i = 1; i <= numPages; i++) { + this.pageLinks.push({ + index: i, + text: i.toString(), + isCurrent: i === json.Page + }); + } + + this.canGoBack = this.pageLinks.length && !this.pageLinks[0].isCurrent; + this.canGoForward = this.pageLinks.length && !this.pageLinks[this.pageLinks.length - 1].isCurrent; + this.totalCount = json.TotalCount; + }); + } +} diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/admin/form-field/form-field.html b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/form-field/form-field.html new file mode 100644 index 0000000..b2ba22d --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/form-field/form-field.html @@ -0,0 +1,9 @@ +
+ +
+ + +
+
diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/admin/form-field/form-field.ts b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/form-field/form-field.ts new file mode 100644 index 0000000..7cf7a30 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/admin/form-field/form-field.ts @@ -0,0 +1,20 @@ +import * as ng from 'angular2/angular2'; + +@ng.Component({ + selector: 'form-field', + properties: ['label', 'validate'] +}) +@ng.View({ + templateUrl: './ng-app/components/admin/form-field/form-field.html', + directives: [ng.NgIf, ng.NgFor] +}) +export class FormField { + private validate: ng.AbstractControl; + + public get errorMessages() { + var errors = (this.validate && this.validate.touched && this.validate.errors) || {}; + return Object.keys(errors).map(key => { + return 'Error: ' + key; + }); + } +} diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/app/app.css b/samples/angular/MusicStore/wwwroot/ng-app/components/app/app.css new file mode 100644 index 0000000..e69de29 diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/app/app.html b/samples/angular/MusicStore/wwwroot/ng-app/components/app/app.html new file mode 100644 index 0000000..6d620ad --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/app/app.html @@ -0,0 +1,30 @@ + +
+ +
diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/app/app.ts b/samples/angular/MusicStore/wwwroot/ng-app/components/app/app.ts new file mode 100644 index 0000000..1da7523 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/app/app.ts @@ -0,0 +1,35 @@ +import * as ng from 'angular2/angular2'; +import * as router from 'angular2/router'; +import { Http, HTTP_BINDINGS } from 'angular2/http'; +import { Home } from '../public/home/home'; +import { AlbumDetails } from '../public/album-details/album-details'; +import { GenreContents } from '../public/genre-contents/genre-contents'; +import { GenresList } from '../public/genres-list/genres-list'; +import { AdminHome } from '../admin/admin-home/admin-home'; +import * as models from '../../models/models'; + +@ng.Component({ + selector: 'app' +}) +@router.RouteConfig([ + { path: '/', component: Home, as: 'Home' }, + { path: '/album/:albumId', component: AlbumDetails, as: 'Album' }, + { path: '/genre/:genreId', component: GenreContents, as: 'Genre' }, + { path: '/genres', component: GenresList, as: 'GenresList' }, + { path: '/admin/...', component: AdminHome, as: 'Admin' } +]) +@ng.View({ + templateUrl: './ng-app/components/app/app.html', + styleUrls: ['./ng-app/components/app/app.css'], + directives: [router.ROUTER_DIRECTIVES, ng.NgFor] +}) +export class App { + public genres: models.Genre[]; + + constructor(http: Http) { + http.get('/api/genres/menu').subscribe(result => { + this.genres = result.json(); + }); + } +} + diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/app/bootstrap.ts b/samples/angular/MusicStore/wwwroot/ng-app/components/app/bootstrap.ts new file mode 100644 index 0000000..a7d6cf3 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/app/bootstrap.ts @@ -0,0 +1,6 @@ +import * as ng from 'angular2/angular2'; +import * as router from 'angular2/router'; +import { Http, HTTP_BINDINGS } from 'angular2/http'; +import { App } from './app'; + +ng.bootstrap(App, [router.ROUTER_BINDINGS, HTTP_BINDINGS, ng.FormBuilder]); diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/public/album-details/album-details.html b/samples/angular/MusicStore/wwwroot/ng-app/components/public/album-details/album-details.html new file mode 100644 index 0000000..b05a0f7 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/public/album-details/album-details.html @@ -0,0 +1,26 @@ +
+

{{ albumData.Title }}

+ +

+ {{ albumData.Title }} +

+ +
+

+ Genre: + {{ albumData.Genre.Name }} +

+

+ Artist: + {{ albumData.Artist.Name }} +

+

+ Price: + {{ albumData.Price | currency:'USD':true }} +

+

+ + Add to cart +

+
+
\ No newline at end of file diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/public/album-details/album-details.ts b/samples/angular/MusicStore/wwwroot/ng-app/components/public/album-details/album-details.ts new file mode 100644 index 0000000..96c869b --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/public/album-details/album-details.ts @@ -0,0 +1,21 @@ +import * as ng from 'angular2/angular2'; +import * as router from 'angular2/router'; +import { Http } from 'angular2/http'; +import * as models from '../../../models/models'; + +@ng.Component({ + selector: 'album-details' +}) +@ng.View({ + templateUrl: './ng-app/components/public/album-details/album-details.html', + directives: [ng.NgIf] +}) +export class AlbumDetails { + public albumData: models.Album; + + constructor(http: Http, routeParam: router.RouteParams) { + http.get('/api/albums/' + routeParam.params['albumId']).subscribe(result => { + this.albumData = result.json(); + }); + } +} diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/public/album-tile/album-tile.html b/samples/angular/MusicStore/wwwroot/ng-app/components/public/album-tile/album-tile.html new file mode 100644 index 0000000..00edd75 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/public/album-tile/album-tile.html @@ -0,0 +1,4 @@ + + {{ albumData.Title }} +

{{ albumData.Title }}

+
diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/public/album-tile/album-tile.ts b/samples/angular/MusicStore/wwwroot/ng-app/components/public/album-tile/album-tile.ts new file mode 100644 index 0000000..345a60b --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/public/album-tile/album-tile.ts @@ -0,0 +1,14 @@ +import * as ng from 'angular2/angular2'; +import * as router from 'angular2/router'; +import * as models from '../../../models/models'; + +@ng.Component({ + selector: 'album-tile', + properties: ['albumData: albumdata'] +}) +@ng.View({ + templateUrl: './ng-app/components/public/album-tile/album-tile.html', + directives: [router.ROUTER_DIRECTIVES] +}) +export class AlbumTile { +} diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/public/genre-contents/genre-contents.html b/samples/angular/MusicStore/wwwroot/ng-app/components/public/genre-contents/genre-contents.html new file mode 100644 index 0000000..1911ae5 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/public/genre-contents/genre-contents.html @@ -0,0 +1,7 @@ +

Albums

+ +
    +
  • + +
  • +
diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/public/genre-contents/genre-contents.ts b/samples/angular/MusicStore/wwwroot/ng-app/components/public/genre-contents/genre-contents.ts new file mode 100644 index 0000000..814ac49 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/public/genre-contents/genre-contents.ts @@ -0,0 +1,22 @@ +import * as ng from 'angular2/angular2'; +import * as router from 'angular2/router'; +import { Http } from 'angular2/http'; +import * as models from '../../../models/models'; +import { AlbumTile } from '../album-tile/album-tile'; + +@ng.Component({ + selector: 'genre-contents' +}) +@ng.View({ + templateUrl: './ng-app/components/public/genre-contents/genre-contents.html', + directives: [ng.NgFor, AlbumTile] +}) +export class GenreContents { + public albums: models.Album[]; + + constructor(http: Http, routeParam: router.RouteParams) { + http.get(`/api/genres/${ routeParam.params['genreId'] }/albums`).subscribe(result => { + this.albums = result.json(); + }); + } +} diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/public/genres-list/genres-list.html b/samples/angular/MusicStore/wwwroot/ng-app/components/public/genres-list/genres-list.html new file mode 100644 index 0000000..dc87efd --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/public/genres-list/genres-list.html @@ -0,0 +1,13 @@ +

Browse Genres

+ +

+ Select from {{ genres.length }} genres: +

+ + diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/public/genres-list/genres-list.ts b/samples/angular/MusicStore/wwwroot/ng-app/components/public/genres-list/genres-list.ts new file mode 100644 index 0000000..f7742b0 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/public/genres-list/genres-list.ts @@ -0,0 +1,21 @@ +import * as ng from 'angular2/angular2'; +import * as router from 'angular2/router'; +import { Http } from 'angular2/http'; +import * as models from '../../../models/models'; + +@ng.Component({ + selector: 'genres-list' +}) +@ng.View({ + templateUrl: './ng-app/components/public/genres-list/genres-list.html', + directives: [router.ROUTER_DIRECTIVES, ng.NgIf, ng.NgFor] +}) +export class GenresList { + public genres: models.Genre[]; + + constructor(http: Http) { + http.get('/api/genres').subscribe(result => { + this.genres = result.json(); + }); + } +} diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/public/home/home.html b/samples/angular/MusicStore/wwwroot/ng-app/components/public/home/home.html new file mode 100644 index 0000000..b0fbb3d --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/public/home/home.html @@ -0,0 +1,10 @@ +
+

MVC Music Store

+ +
+ +
    +
  • + +
  • +
diff --git a/samples/angular/MusicStore/wwwroot/ng-app/components/public/home/home.ts b/samples/angular/MusicStore/wwwroot/ng-app/components/public/home/home.ts new file mode 100644 index 0000000..cf1ad9f --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/components/public/home/home.ts @@ -0,0 +1,21 @@ +import * as ng from 'angular2/angular2'; +import { Http } from 'angular2/http'; +import { AlbumTile } from '../album-tile/album-tile'; +import * as models from '../../../models/models'; + +@ng.Component({ + selector: 'home' +}) +@ng.View({ + templateUrl: './ng-app/components/public/home/home.html', + directives: [ng.NgFor, AlbumTile] +}) +export class Home { + public mostPopular: models.Album[]; + + constructor(http: Http) { + http.get('/api/albums/mostPopular').subscribe(result => { + this.mostPopular = result.json(); + }); + } +} diff --git a/samples/angular/MusicStore/wwwroot/ng-app/models/models.ts b/samples/angular/MusicStore/wwwroot/ng-app/models/models.ts new file mode 100644 index 0000000..44c705a --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/ng-app/models/models.ts @@ -0,0 +1,16 @@ +export interface Album { + AlbumId: number; + Title: string; + AlbumArtUrl: string; +} + +export interface Genre { + GenreId: number; + Name: string; + Description: string; +} + +export interface Artist { + ArtistId: number; + Name: string; +} diff --git a/samples/angular/MusicStore/wwwroot/system.config.js b/samples/angular/MusicStore/wwwroot/system.config.js new file mode 100644 index 0000000..400211f --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/system.config.js @@ -0,0 +1,3 @@ +System.config({ + defaultJSExtensions: true +}); diff --git a/samples/angular/MusicStore/wwwroot/web.config b/samples/angular/MusicStore/wwwroot/web.config new file mode 100644 index 0000000..db6e6f4 --- /dev/null +++ b/samples/angular/MusicStore/wwwroot/web.config @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/samples/react/ReactGrid/.gitignore b/samples/react/ReactGrid/.gitignore new file mode 100644 index 0000000..ef7d547 --- /dev/null +++ b/samples/react/ReactGrid/.gitignore @@ -0,0 +1,5 @@ +/node_modules/ +project.lock.json +/wwwroot/bundle.* +/wwwroot/*.svg +/wwwroot/*.css diff --git a/samples/react/ReactGrid/Controllers/HomeController.cs b/samples/react/ReactGrid/Controllers/HomeController.cs new file mode 100755 index 0000000..dd9a1ab --- /dev/null +++ b/samples/react/ReactGrid/Controllers/HomeController.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc; +using Microsoft.AspNet.NodeServices.React; + +namespace ReactExample.Controllers +{ + public class HomeController : Controller + { + public async Task Index(int pageIndex) + { + ViewData["ReactOutput"] = await ReactRenderer.RenderToString( + moduleName: "ReactApp/components/ReactApp.jsx", + exportName: "ReactApp", + baseUrl: Request.Path + ); + return View(); + } + + public IActionResult Error() + { + return View("~/Views/Shared/Error.cshtml"); + } + } +} diff --git a/samples/react/ReactGrid/README.txt b/samples/react/ReactGrid/README.txt new file mode 100644 index 0000000..e702b30 --- /dev/null +++ b/samples/react/ReactGrid/README.txt @@ -0,0 +1,2 @@ +Portions of this sample application (particularly, the fake data) are based +on https://github.com/DavidWells/isomorphic-react-example diff --git a/samples/react/ReactGrid/ReactApp/boot-client.jsx b/samples/react/ReactGrid/ReactApp/boot-client.jsx new file mode 100644 index 0000000..623daf4 --- /dev/null +++ b/samples/react/ReactGrid/ReactApp/boot-client.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import createBrowserHistory from 'history/lib/createBrowserHistory'; +import { ReactApp } from './components/ReactApp.jsx'; + +// In the browser, we render into a DOM node and hook up to the browser's history APIs +var history = createBrowserHistory(); +ReactDOM.render(, document.getElementById('react-app')); diff --git a/samples/react/ReactGrid/ReactApp/components/CustomPager.jsx b/samples/react/ReactGrid/ReactApp/components/CustomPager.jsx new file mode 100644 index 0000000..44ce1b5 --- /dev/null +++ b/samples/react/ReactGrid/ReactApp/components/CustomPager.jsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { Link } from 'react-router'; + +export class CustomPager extends React.Component { + pageChange(event) { + this.props.setPage(parseInt(event.target.getAttribute("data-value"))); + } + + render() { + var previous = ""; + var next = ""; + + if(this.props.currentPage > 0){ + previous =
{this.props.previousText}
; + } + + if(this.props.currentPage != (this.props.maxPage -1)){ + next =
{this.props.nextText}
; + } + + var options = []; + + var startIndex = Math.max(this.props.currentPage - 5, 0); + var endIndex = Math.min(startIndex + 11, this.props.maxPage); + + if (this.props.maxPage >= 11 && (endIndex - startIndex) <= 10) { + startIndex = endIndex - 11; + } + + for(var i = startIndex; i < endIndex ; i++){ + var selected = this.props.currentPage == i ? "btn-default" : ""; + options.push(
{i+1}
); + } + + return ( +
+ {previous} + {options} + {next} +
+ ); + } +} + +CustomPager.defaultProps = { + maxPage: 0, + nextText: '', + previousText: '', + currentPage: 0 +}; \ No newline at end of file diff --git a/samples/react/ReactGrid/ReactApp/components/PeopleGrid.jsx b/samples/react/ReactGrid/ReactApp/components/PeopleGrid.jsx new file mode 100644 index 0000000..c6662fa --- /dev/null +++ b/samples/react/ReactGrid/ReactApp/components/PeopleGrid.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import Griddle from 'griddle-react'; +import { CustomPager } from './CustomPager.jsx'; +import { fakeData } from '../data/fakeData.js'; +import { columnMeta } from '../data/columnMeta.js'; +const resultsPerPage = 10; + +export class PeopleGrid extends React.Component { + render() { + var pageIndex = this.props.params ? (this.props.params.pageIndex || 1) - 1 : 0; + return ( +
+

People

+
+ +
+
+ ); + } +} diff --git a/samples/react/ReactGrid/ReactApp/components/ReactApp.jsx b/samples/react/ReactGrid/ReactApp/components/ReactApp.jsx new file mode 100644 index 0000000..45c9bf1 --- /dev/null +++ b/samples/react/ReactGrid/ReactApp/components/ReactApp.jsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { Router, Route } from 'react-router'; +import { PeopleGrid } from './PeopleGrid.jsx'; + +export class ReactApp extends React.Component { + render() { + return ( + + + + + ); + } +} diff --git a/samples/react/ReactGrid/ReactApp/data/columnMeta.js b/samples/react/ReactGrid/ReactApp/data/columnMeta.js new file mode 100644 index 0000000..f470451 --- /dev/null +++ b/samples/react/ReactGrid/ReactApp/data/columnMeta.js @@ -0,0 +1,47 @@ +var columnMeta = [ + { + "columnName": "id", + "order": 1, + "locked": false, + "visible": true + }, + { + "columnName": "name", + "order": 2, + "locked": false, + "visible": true + }, + { + "columnName": "city", + "order": 3, + "locked": false, + "visible": true + }, + { + "columnName": "state", + "order": 4, + "locked": false, + "visible": true + }, + { + "columnName": "country", + "order": 5, + "locked": false, + "visible": true + }, + { + "columnName": "company", + "order": 6, + "locked": false, + "visible": true + }, + { + "columnName": "favoriteNumber", + "order": 7, + "locked": false, + "visible": true + } +]; + +export var columnMeta; + diff --git a/samples/react/ReactGrid/ReactApp/data/fakeData.js b/samples/react/ReactGrid/ReactApp/data/fakeData.js new file mode 100644 index 0000000..d959fb7 --- /dev/null +++ b/samples/react/ReactGrid/ReactApp/data/fakeData.js @@ -0,0 +1,2489 @@ +var fakeData = [ + { + "id": 0, + "name": "Mayer Leonard", + "city": "Kapowsin", + "state": "Hawaii", + "country": "United Kingdom", + "company": "Ovolo", + "favoriteNumber": 7 + }, + { + "id": 1, + "name": "Koch Becker", + "city": "Johnsonburg", + "state": "New Jersey", + "country": "Madagascar", + "company": "Eventage", + "favoriteNumber": 2 + }, + { + "id": 2, + "name": "Lowery Hopkins", + "city": "Blanco", + "state": "Arizona", + "country": "Ukraine", + "company": "Comtext", + "favoriteNumber": 3 + }, + { + "id": 3, + "name": "Walters Mays", + "city": "Glendale", + "state": "Illinois", + "country": "New Zealand", + "company": "Corporana", + "favoriteNumber": 6 + }, + { + "id": 4, + "name": "Shaw Lowe", + "city": "Coultervillle", + "state": "Wyoming", + "country": "Ecuador", + "company": "Isologica", + "favoriteNumber": 2 + }, + { + "id": 5, + "name": "Ola Fernandez", + "city": "Deltaville", + "state": "Delaware", + "country": "Virgin Islands (US)", + "company": "Pawnagra", + "favoriteNumber": 7 + }, + { + "id": 6, + "name": "Park Carr", + "city": "Welda", + "state": "Kentucky", + "country": "Sri Lanka", + "company": "Cosmetex", + "favoriteNumber": 7 + }, + { + "id": 7, + "name": "Laverne Johnson", + "city": "Rosburg", + "state": "New Mexico", + "country": "Croatia", + "company": "Housedown", + "favoriteNumber": 9 + }, + { + "id": 8, + "name": "Lizzie Nelson", + "city": "Chumuckla", + "state": "Montana", + "country": "Turks & Caicos", + "company": "Everest", + "favoriteNumber": 2 + }, + { + "id": 9, + "name": "Clarke Clemons", + "city": "Inkerman", + "state": "Rhode Island", + "country": "Cambodia", + "company": "Apexia", + "favoriteNumber": 3 + }, + { + "id": 10, + "name": "Cindy Phelps", + "city": "Hachita", + "state": "North Carolina", + "country": "Namibia", + "company": "Pholio", + "favoriteNumber": 6 + }, + { + "id": 11, + "name": "Danielle Keller", + "city": "Stockdale", + "state": "Maryland", + "country": "Cape Verde", + "company": "Netility", + "favoriteNumber": 10 + }, + { + "id": 12, + "name": "Duke Hutchinson", + "city": "Needmore", + "state": "Indiana", + "country": "Brunei", + "company": "Electonic", + "favoriteNumber": 1 + }, + { + "id": 13, + "name": "Aimee Duffy", + "city": "Brownlee", + "state": "Vermont", + "country": "Lebanon", + "company": "Repetwire", + "favoriteNumber": 2 + }, + { + "id": 14, + "name": "Meadows Jimenez", + "city": "Winesburg", + "state": "Kansas", + "country": "Timor L'Este", + "company": "Quonk", + "favoriteNumber": 0 + }, + { + "id": 15, + "name": "Karla Potts", + "city": "Juarez", + "state": "Alaska", + "country": "Samoa", + "company": "Zentime", + "favoriteNumber": 3 + }, + { + "id": 16, + "name": "Rita Jensen", + "city": "Elwood", + "state": "North Dakota", + "country": "Greece", + "company": "Valpreal", + "favoriteNumber": 9 + }, + { + "id": 17, + "name": "Jackie Burke", + "city": "Delwood", + "state": "Arkansas", + "country": "Greenland", + "company": "Magmina", + "favoriteNumber": 4 + }, + { + "id": 18, + "name": "Corinne Moreno", + "city": "Wollochet", + "state": "New Hampshire", + "country": "Sierra Leone", + "company": "Marketoid", + "favoriteNumber": 1 + }, + { + "id": 19, + "name": "Giles Cohen", + "city": "Carbonville", + "state": "Massachusetts", + "country": "Tonga", + "company": "Ginkogene", + "favoriteNumber": 10 + }, + { + "id": 20, + "name": "Maynard Barnes", + "city": "Boling", + "state": "Utah", + "country": "Nepal", + "company": "Kyaguru", + "favoriteNumber": 8 + }, + { + "id": 21, + "name": "Singleton Lindsay", + "city": "Weogufka", + "state": "Tennessee", + "country": "Falkland Islands", + "company": "Egypto", + "favoriteNumber": 5 + }, + { + "id": 22, + "name": "Etta Kemp", + "city": "Como", + "state": "Pennsylvania", + "country": "Syria", + "company": "Marqet", + "favoriteNumber": 3 + }, + { + "id": 23, + "name": "Whitney Pennington", + "city": "Farmington", + "state": "Louisiana", + "country": "Suriname", + "company": "Prosure", + "favoriteNumber": 10 + }, + { + "id": 24, + "name": "Sophie Ellison", + "city": "Whitewater", + "state": "Idaho", + "country": "Malta", + "company": "Evidends", + "favoriteNumber": 1 + }, + { + "id": 25, + "name": "Logan Forbes", + "city": "Idledale", + "state": "Michigan", + "country": "Dominican Republic", + "company": "Pigzart", + "favoriteNumber": 3 + }, + { + "id": 26, + "name": "Haley Mcclure", + "city": "Eggertsville", + "state": "Colorado", + "country": "Honduras", + "company": "Ginkle", + "favoriteNumber": 8 + }, + { + "id": 27, + "name": "Williamson Hurley", + "city": "Edgar", + "state": "Texas", + "country": "Yemen", + "company": "Tetratrex", + "favoriteNumber": 3 + }, + { + "id": 28, + "name": "Heidi Hurst", + "city": "Curtice", + "state": "Nebraska", + "country": "Aruba", + "company": "Vendblend", + "favoriteNumber": 10 + }, + { + "id": 29, + "name": "Barker Long", + "city": "Orovada", + "state": "West Virginia", + "country": "Egypt", + "company": "Uniworld", + "favoriteNumber": 8 + }, + { + "id": 30, + "name": "Richard Patrick", + "city": "Gordon", + "state": "Oregon", + "country": "Malawi", + "company": "Quarx", + "favoriteNumber": 8 + }, + { + "id": 31, + "name": "Cameron Graham", + "city": "Noblestown", + "state": "Oklahoma", + "country": "Slovenia", + "company": "Zilidium", + "favoriteNumber": 5 + }, + { + "id": 32, + "name": "Lucy Quinn", + "city": "Greenock", + "state": "Ohio", + "country": "Australia", + "company": "Geoform", + "favoriteNumber": 10 + }, + { + "id": 33, + "name": "Dickson Greene", + "city": "Jeff", + "state": "Virginia", + "country": "Iraq", + "company": "Niquent", + "favoriteNumber": 6 + }, + { + "id": 34, + "name": "Jasmine Brock", + "city": "Tolu", + "state": "Mississippi", + "country": "Hungary", + "company": "Cytrek", + "favoriteNumber": 8 + }, + { + "id": 35, + "name": "Byers Donaldson", + "city": "Jugtown", + "state": "South Dakota", + "country": "Mongolia", + "company": "Slambda", + "favoriteNumber": 4 + }, + { + "id": 36, + "name": "Burns Blake", + "city": "Shawmut", + "state": "Iowa", + "country": "Ethiopia", + "company": "Comstar", + "favoriteNumber": 9 + }, + { + "id": 37, + "name": "Norman Wynn", + "city": "Hasty", + "state": "Washington", + "country": "Bangladesh", + "company": "Netplode", + "favoriteNumber": 7 + }, + { + "id": 38, + "name": "Anthony Weeks", + "city": "Chautauqua", + "state": "Florida", + "country": "Sudan", + "company": "Rubadub", + "favoriteNumber": 9 + }, + { + "id": 39, + "name": "Courtney Marshall", + "city": "Grazierville", + "state": "California", + "country": "Zambia", + "company": "Medicroix", + "favoriteNumber": 0 + }, + { + "id": 40, + "name": "Wilda Foster", + "city": "Ebro", + "state": "New York", + "country": "Cameroon", + "company": "Xixan", + "favoriteNumber": 0 + }, + { + "id": 41, + "name": "Buckner Hyde", + "city": "Century", + "state": "Minnesota", + "country": "Mexico", + "company": "Plasmos", + "favoriteNumber": 6 + }, + { + "id": 42, + "name": "Montgomery Woodard", + "city": "Nadine", + "state": "Georgia", + "country": "Zimbabwe", + "company": "Neptide", + "favoriteNumber": 1 + }, + { + "id": 43, + "name": "Shirley Boyle", + "city": "Groveville", + "state": "Connecticut", + "country": "Tunisia", + "company": "Interodeo", + "favoriteNumber": 1 + }, + { + "id": 44, + "name": "Mavis Welch", + "city": "Springhill", + "state": "South Carolina", + "country": "Italy", + "company": "Asimiline", + "favoriteNumber": 9 + }, + { + "id": 45, + "name": "Barr Flowers", + "city": "Bowden", + "state": "Missouri", + "country": "South Korea", + "company": "Terragen", + "favoriteNumber": 7 + }, + { + "id": 46, + "name": "Cabrera Koch", + "city": "Wanship", + "state": "Maine", + "country": "Mauritius", + "company": "Norsul", + "favoriteNumber": 9 + }, + { + "id": 47, + "name": "Williams Gamble", + "city": "Homestead", + "state": "Wisconsin", + "country": "Romania", + "company": "Gynk", + "favoriteNumber": 4 + }, + { + "id": 48, + "name": "Angelica Washington", + "city": "Roulette", + "state": "Alabama", + "country": "South Africa", + "company": "Exoswitch", + "favoriteNumber": 3 + }, + { + "id": 49, + "name": "Morse Navarro", + "city": "Balm", + "state": "Hawaii", + "country": "Malaysia", + "company": "Comtours", + "favoriteNumber": 7 + }, + { + "id": 50, + "name": "Harding Chambers", + "city": "Lupton", + "state": "New Jersey", + "country": "Oman", + "company": "Gadtron", + "favoriteNumber": 6 + }, + { + "id": 51, + "name": "Frederick Mcdowell", + "city": "Kimmell", + "state": "Arizona", + "country": "Ireland", + "company": "Delphide", + "favoriteNumber": 2 + }, + { + "id": 52, + "name": "Valentine Turner", + "city": "Hobucken", + "state": "Illinois", + "country": "France", + "company": "Sloganaut", + "favoriteNumber": 0 + }, + { + "id": 53, + "name": "Ruby Cooper", + "city": "Connerton", + "state": "Wyoming", + "country": "Iceland", + "company": "Exospace", + "favoriteNumber": 5 + }, + { + "id": 54, + "name": "Natalia Nielsen", + "city": "Holtville", + "state": "Delaware", + "country": "Equatorial Guinea", + "company": "Isoswitch", + "favoriteNumber": 6 + }, + { + "id": 55, + "name": "Bobbie Silva", + "city": "Fivepointville", + "state": "Kentucky", + "country": "Luxembourg", + "company": "Futuris", + "favoriteNumber": 0 + }, + { + "id": 56, + "name": "Clarice Hays", + "city": "Floriston", + "state": "New Mexico", + "country": "Cruise Ship", + "company": "Skyplex", + "favoriteNumber": 5 + }, + { + "id": 57, + "name": "Leblanc Bartlett", + "city": "Catherine", + "state": "Montana", + "country": "Belarus", + "company": "Ezentia", + "favoriteNumber": 10 + }, + { + "id": 58, + "name": "Jodie Martinez", + "city": "Edneyville", + "state": "Rhode Island", + "country": "Antigua & Barbuda", + "company": "Satiance", + "favoriteNumber": 7 + }, + { + "id": 59, + "name": "Pennington Townsend", + "city": "Ahwahnee", + "state": "North Carolina", + "country": "Chad", + "company": "Orbiflex", + "favoriteNumber": 8 + }, + { + "id": 60, + "name": "Garrison Buchanan", + "city": "Coinjock", + "state": "Maryland", + "country": "Reunion", + "company": "Zanity", + "favoriteNumber": 3 + }, + { + "id": 61, + "name": "Cardenas Reeves", + "city": "Greensburg", + "state": "Indiana", + "country": "Gabon", + "company": "Cogentry", + "favoriteNumber": 1 + }, + { + "id": 62, + "name": "Angeline Jacobson", + "city": "Freeburn", + "state": "Vermont", + "country": "Fiji", + "company": "Pearlessa", + "favoriteNumber": 4 + }, + { + "id": 63, + "name": "Turner Franks", + "city": "Fairforest", + "state": "Kansas", + "country": "New Caledonia", + "company": "Maximind", + "favoriteNumber": 1 + }, + { + "id": 64, + "name": "Murphy Santos", + "city": "Waiohinu", + "state": "Alaska", + "country": "Haiti", + "company": "Isodrive", + "favoriteNumber": 0 + }, + { + "id": 65, + "name": "Walls Cherry", + "city": "Avalon", + "state": "North Dakota", + "country": "Mozambique", + "company": "Bolax", + "favoriteNumber": 10 + }, + { + "id": 66, + "name": "Carney Olson", + "city": "Nanafalia", + "state": "Arkansas", + "country": "Pakistan", + "company": "Unq", + "favoriteNumber": 10 + }, + { + "id": 67, + "name": "Jennings Bowers", + "city": "Kenwood", + "state": "New Hampshire", + "country": "Cayman Islands", + "company": "Deepends", + "favoriteNumber": 10 + }, + { + "id": 68, + "name": "Browning Wooten", + "city": "Jessie", + "state": "Massachusetts", + "country": "Guam", + "company": "Eventex", + "favoriteNumber": 5 + }, + { + "id": 69, + "name": "Preston Britt", + "city": "Dennard", + "state": "Utah", + "country": "Cyprus", + "company": "Sureplex", + "favoriteNumber": 4 + }, + { + "id": 70, + "name": "Holly Martin", + "city": "Carrizo", + "state": "Tennessee", + "country": "Nicaragua", + "company": "Sonique", + "favoriteNumber": 1 + }, + { + "id": 71, + "name": "Zelma Barker", + "city": "Zarephath", + "state": "Pennsylvania", + "country": "Czech Republic", + "company": "Xanide", + "favoriteNumber": 9 + }, + { + "id": 72, + "name": "Burgess Zamora", + "city": "Tampico", + "state": "Louisiana", + "country": "Poland", + "company": "Isopop", + "favoriteNumber": 10 + }, + { + "id": 73, + "name": "Galloway Rich", + "city": "Zeba", + "state": "Idaho", + "country": "Uzbekistan", + "company": "Dragbot", + "favoriteNumber": 4 + }, + { + "id": 74, + "name": "Morris Lott", + "city": "Wattsville", + "state": "Michigan", + "country": "Turkmenistan", + "company": "Slumberia", + "favoriteNumber": 3 + }, + { + "id": 75, + "name": "Paul Mcleod", + "city": "Glenbrook", + "state": "Colorado", + "country": "Cuba", + "company": "Candecor", + "favoriteNumber": 6 + }, + { + "id": 76, + "name": "Phoebe Orr", + "city": "Holcombe", + "state": "Texas", + "country": "Faroe Islands", + "company": "Cubicide", + "favoriteNumber": 4 + }, + { + "id": 77, + "name": "Dalton Christensen", + "city": "Rossmore", + "state": "Nebraska", + "country": "Belgium", + "company": "Enormo", + "favoriteNumber": 4 + }, + { + "id": 78, + "name": "Flora Goff", + "city": "Gila", + "state": "West Virginia", + "country": "Philippines", + "company": "Miracula", + "favoriteNumber": 4 + }, + { + "id": 79, + "name": "Sheree Ross", + "city": "Welch", + "state": "Oregon", + "country": "French Polynesia", + "company": "Illumity", + "favoriteNumber": 0 + }, + { + "id": 80, + "name": "Nita Jefferson", + "city": "Calverton", + "state": "Oklahoma", + "country": "Estonia", + "company": "Cincyr", + "favoriteNumber": 2 + }, + { + "id": 81, + "name": "Elma Mendoza", + "city": "Cornfields", + "state": "Ohio", + "country": "Botswana", + "company": "Isotronic", + "favoriteNumber": 6 + }, + { + "id": 82, + "name": "Garcia Hensley", + "city": "Kohatk", + "state": "Virginia", + "country": "Congo", + "company": "Plasmox", + "favoriteNumber": 4 + }, + { + "id": 83, + "name": "Delgado Osborn", + "city": "Nescatunga", + "state": "Mississippi", + "country": "Montenegro", + "company": "Magneato", + "favoriteNumber": 1 + }, + { + "id": 84, + "name": "Chavez Simmons", + "city": "Roderfield", + "state": "South Dakota", + "country": "Norway", + "company": "Waab", + "favoriteNumber": 1 + }, + { + "id": 85, + "name": "Stuart Roach", + "city": "Hebron", + "state": "Iowa", + "country": "Georgia", + "company": "Applica", + "favoriteNumber": 0 + }, + { + "id": 86, + "name": "Georgia Henson", + "city": "Greenbackville", + "state": "Washington", + "country": "Guinea Bissau", + "company": "Talkalot", + "favoriteNumber": 7 + }, + { + "id": 87, + "name": "Ila Sanders", + "city": "Zortman", + "state": "Florida", + "country": "Brazil", + "company": "Koffee", + "favoriteNumber": 10 + }, + { + "id": 88, + "name": "Shepard Maldonado", + "city": "Lawrence", + "state": "California", + "country": "Netherlands", + "company": "Knowlysis", + "favoriteNumber": 1 + }, + { + "id": 89, + "name": "Ramirez Collins", + "city": "Healy", + "state": "New York", + "country": "Guernsey", + "company": "Entroflex", + "favoriteNumber": 4 + }, + { + "id": 90, + "name": "Magdalena Mcgee", + "city": "Goldfield", + "state": "Minnesota", + "country": "Qatar", + "company": "Xelegyl", + "favoriteNumber": 0 + }, + { + "id": 91, + "name": "Crystal Kinney", + "city": "Nogal", + "state": "Georgia", + "country": "Kuwait", + "company": "Zork", + "favoriteNumber": 3 + }, + { + "id": 92, + "name": "Witt Colon", + "city": "Yorklyn", + "state": "Connecticut", + "country": "Singapore", + "company": "Techmania", + "favoriteNumber": 8 + }, + { + "id": 93, + "name": "Joyce Randolph", + "city": "Leland", + "state": "South Carolina", + "country": "Dominica", + "company": "Realmo", + "favoriteNumber": 2 + }, + { + "id": 94, + "name": "Ora Oneil", + "city": "Gilgo", + "state": "Missouri", + "country": "Bahamas", + "company": "Hinway", + "favoriteNumber": 7 + }, + { + "id": 95, + "name": "Hansen Rose", + "city": "Starks", + "state": "Maine", + "country": "Iran", + "company": "Virxo", + "favoriteNumber": 6 + }, + { + "id": 96, + "name": "Isabelle Rush", + "city": "Datil", + "state": "Wisconsin", + "country": "Switzerland", + "company": "Ecraze", + "favoriteNumber": 4 + }, + { + "id": 97, + "name": "Hoffman Crosby", + "city": "Trucksville", + "state": "Alabama", + "country": "Indonesia", + "company": "Multron", + "favoriteNumber": 10 + }, + { + "id": 98, + "name": "Louella Cotton", + "city": "Shelby", + "state": "Hawaii", + "country": "Tajikistan", + "company": "Supportal", + "favoriteNumber": 0 + }, + { + "id": 99, + "name": "Elvia Drake", + "city": "Albrightsville", + "state": "New Jersey", + "country": "Grenada", + "company": "Kiosk", + "favoriteNumber": 10 + }, + { + "id": 100, + "name": "Tyson Guerra", + "city": "Sutton", + "state": "Arizona", + "country": "Benin", + "company": "Dadabase", + "favoriteNumber": 4 + }, + { + "id": 101, + "name": "Marion Sloan", + "city": "Winchester", + "state": "Illinois", + "country": "Venezuela", + "company": "Exostream", + "favoriteNumber": 0 + }, + { + "id": 102, + "name": "Faulkner Diaz", + "city": "Logan", + "state": "Wyoming", + "country": "Monaco", + "company": "Oceanica", + "favoriteNumber": 10 + }, + { + "id": 103, + "name": "Penelope Price", + "city": "Alafaya", + "state": "Delaware", + "country": "Chile", + "company": "Nebulean", + "favoriteNumber": 10 + }, + { + "id": 104, + "name": "Kaitlin Glover", + "city": "Succasunna", + "state": "Kentucky", + "country": "Puerto Rico", + "company": "Orbean", + "favoriteNumber": 4 + }, + { + "id": 105, + "name": "Elena English", + "city": "Wedgewood", + "state": "New Mexico", + "country": "Algeria", + "company": "Kiggle", + "favoriteNumber": 0 + }, + { + "id": 106, + "name": "Clemons Sweeney", + "city": "Saranap", + "state": "Montana", + "country": "Ghana", + "company": "Konnect", + "favoriteNumber": 8 + }, + { + "id": 107, + "name": "Kelsey Blevins", + "city": "Vincent", + "state": "Rhode Island", + "country": "Albania", + "company": "Xymonk", + "favoriteNumber": 7 + }, + { + "id": 108, + "name": "Schroeder Craft", + "city": "Roosevelt", + "state": "North Carolina", + "country": "Satellite", + "company": "Isotrack", + "favoriteNumber": 8 + }, + { + "id": 109, + "name": "Hill Clark", + "city": "Elrama", + "state": "Maryland", + "country": "Slovakia", + "company": "Waterbaby", + "favoriteNumber": 0 + }, + { + "id": 110, + "name": "Glover Meyers", + "city": "Riviera", + "state": "Indiana", + "country": "Liberia", + "company": "Digigene", + "favoriteNumber": 0 + }, + { + "id": 111, + "name": "Lola Parrish", + "city": "Ellerslie", + "state": "Vermont", + "country": "Azerbaijan", + "company": "Myopium", + "favoriteNumber": 1 + }, + { + "id": 112, + "name": "Nora Rivers", + "city": "Belvoir", + "state": "Kansas", + "country": "Afghanistan", + "company": "Comtrek", + "favoriteNumber": 10 + }, + { + "id": 113, + "name": "Cohen Pacheco", + "city": "Bethpage", + "state": "Alaska", + "country": "Netherlands Antilles", + "company": "Accufarm", + "favoriteNumber": 7 + }, + { + "id": 114, + "name": "Diann Horn", + "city": "Derwood", + "state": "North Dakota", + "country": "Seychelles", + "company": "Synkgen", + "favoriteNumber": 3 + }, + { + "id": 115, + "name": "Amalia Nicholson", + "city": "Hendersonville", + "state": "Arkansas", + "country": "Lesotho", + "company": "Geekus", + "favoriteNumber": 2 + }, + { + "id": 116, + "name": "Mcgee Kane", + "city": "Dante", + "state": "New Hampshire", + "country": "Nigeria", + "company": "Kinetica", + "favoriteNumber": 9 + }, + { + "id": 117, + "name": "Shaffer Simpson", + "city": "Verdi", + "state": "Massachusetts", + "country": "Costa Rica", + "company": "Orbixtar", + "favoriteNumber": 0 + }, + { + "id": 118, + "name": "Lott Heath", + "city": "Castleton", + "state": "Utah", + "country": "Burkina Faso", + "company": "Reversus", + "favoriteNumber": 8 + }, + { + "id": 119, + "name": "Sasha Alvarez", + "city": "Foxworth", + "state": "Tennessee", + "country": "Montserrat", + "company": "Farmex", + "favoriteNumber": 9 + }, + { + "id": 120, + "name": "Sonja Rhodes", + "city": "Trona", + "state": "Pennsylvania", + "country": "Liechtenstein", + "company": "Pyramis", + "favoriteNumber": 2 + }, + { + "id": 121, + "name": "Rachel Elliott", + "city": "Hessville", + "state": "Louisiana", + "country": "Vietnam", + "company": "Centice", + "favoriteNumber": 0 + }, + { + "id": 122, + "name": "Elisa Justice", + "city": "Urie", + "state": "Idaho", + "country": "Senegal", + "company": "Dancerity", + "favoriteNumber": 2 + }, + { + "id": 123, + "name": "Velazquez Anderson", + "city": "Lowell", + "state": "Michigan", + "country": "Burundi", + "company": "Digial", + "favoriteNumber": 5 + }, + { + "id": 124, + "name": "Janet Ford", + "city": "Darlington", + "state": "Colorado", + "country": "Turkey", + "company": "Sportan", + "favoriteNumber": 10 + }, + { + "id": 125, + "name": "Simon Peterson", + "city": "Linwood", + "state": "Texas", + "country": "Kazakhstan", + "company": "Zytrac", + "favoriteNumber": 7 + }, + { + "id": 126, + "name": "Smith Baird", + "city": "Marne", + "state": "Nebraska", + "country": "Swaziland", + "company": "Genmy", + "favoriteNumber": 7 + }, + { + "id": 127, + "name": "Rogers Peters", + "city": "Tedrow", + "state": "West Virginia", + "country": "Spain", + "company": "Octocore", + "favoriteNumber": 1 + }, + { + "id": 128, + "name": "Bowers Ayers", + "city": "Matthews", + "state": "Oregon", + "country": "St Lucia", + "company": "Biotica", + "favoriteNumber": 6 + }, + { + "id": 129, + "name": "Paulette Delaney", + "city": "Riegelwood", + "state": "Oklahoma", + "country": "Guinea", + "company": "Netbook", + "favoriteNumber": 3 + }, + { + "id": 130, + "name": "Pat Klein", + "city": "Jacksonburg", + "state": "Ohio", + "country": "El Salvador", + "company": "Recrisys", + "favoriteNumber": 5 + }, + { + "id": 131, + "name": "Dena Rosa", + "city": "Hollymead", + "state": "Virginia", + "country": "Russia", + "company": "Sealoud", + "favoriteNumber": 6 + }, + { + "id": 132, + "name": "Rochelle Barnett", + "city": "Genoa", + "state": "Mississippi", + "country": "Kenya", + "company": "Ersum", + "favoriteNumber": 6 + }, + { + "id": 133, + "name": "Odom Schultz", + "city": "Blende", + "state": "South Dakota", + "country": "Papua New Guinea", + "company": "Elentrix", + "favoriteNumber": 0 + }, + { + "id": 134, + "name": "Anderson Franco", + "city": "Yardville", + "state": "Iowa", + "country": "Sweden", + "company": "Wazzu", + "favoriteNumber": 4 + }, + { + "id": 135, + "name": "Rebecca Wyatt", + "city": "Berwind", + "state": "Washington", + "country": "St Vincent", + "company": "Bristo", + "favoriteNumber": 2 + }, + { + "id": 136, + "name": "Dollie Hooper", + "city": "Richmond", + "state": "Florida", + "country": "Uruguay", + "company": "Vantage", + "favoriteNumber": 1 + }, + { + "id": 137, + "name": "Mathews Sharpe", + "city": "Glenshaw", + "state": "California", + "country": "Trinidad & Tobago", + "company": "Quilk", + "favoriteNumber": 4 + }, + { + "id": 138, + "name": "Debra Skinner", + "city": "Leming", + "state": "New York", + "country": "Saint Pierre & Miquelon", + "company": "Billmed", + "favoriteNumber": 1 + }, + { + "id": 139, + "name": "Cross Wells", + "city": "Caroline", + "state": "Minnesota", + "country": "Mauritania", + "company": "Strozen", + "favoriteNumber": 3 + }, + { + "id": 140, + "name": "Dodson Aguirre", + "city": "Nash", + "state": "Georgia", + "country": "Palestine", + "company": "Tripsch", + "favoriteNumber": 5 + }, + { + "id": 141, + "name": "Edna Copeland", + "city": "Harrison", + "state": "Connecticut", + "country": "Macedonia", + "company": "Flum", + "favoriteNumber": 10 + }, + { + "id": 142, + "name": "Dominguez Goodwin", + "city": "Condon", + "state": "South Carolina", + "country": "Laos", + "company": "Interloo", + "favoriteNumber": 0 + }, + { + "id": 143, + "name": "Fry Leach", + "city": "Advance", + "state": "Missouri", + "country": "Angola", + "company": "Recritube", + "favoriteNumber": 7 + }, + { + "id": 144, + "name": "Mann Malone", + "city": "Lumberton", + "state": "Maine", + "country": "India", + "company": "Xylar", + "favoriteNumber": 9 + }, + { + "id": 145, + "name": "Bridget Ayala", + "city": "Bellamy", + "state": "Wisconsin", + "country": "Cote D Ivoire", + "company": "Comvex", + "favoriteNumber": 7 + }, + { + "id": 146, + "name": "Blackwell Blanchard", + "city": "Ticonderoga", + "state": "Alabama", + "country": "Barbados", + "company": "Applideck", + "favoriteNumber": 7 + }, + { + "id": 147, + "name": "Maxine Irwin", + "city": "Longoria", + "state": "Hawaii", + "country": "Armenia", + "company": "Pearlesex", + "favoriteNumber": 10 + }, + { + "id": 148, + "name": "Laura Bryant", + "city": "Chicopee", + "state": "New Jersey", + "country": "Bosnia & Herzegovina", + "company": "Poshome", + "favoriteNumber": 0 + }, + { + "id": 149, + "name": "Zimmerman Little", + "city": "Rosewood", + "state": "Arizona", + "country": "Guatemala", + "company": "Boink", + "favoriteNumber": 4 + }, + { + "id": 150, + "name": "Barlow Reed", + "city": "Buxton", + "state": "Illinois", + "country": "Tanzania", + "company": "Premiant", + "favoriteNumber": 5 + }, + { + "id": 151, + "name": "Anita Briggs", + "city": "Laurelton", + "state": "Wyoming", + "country": "United Arab Emirates", + "company": "Codact", + "favoriteNumber": 3 + }, + { + "id": 152, + "name": "Ortiz Newton", + "city": "Blandburg", + "state": "Delaware", + "country": "Moldova", + "company": "Enersave", + "favoriteNumber": 7 + }, + { + "id": 153, + "name": "Cox Monroe", + "city": "Dupuyer", + "state": "Kentucky", + "country": "Taiwan", + "company": "Uneeq", + "favoriteNumber": 3 + }, + { + "id": 154, + "name": "Elinor Hughes", + "city": "Yukon", + "state": "New Mexico", + "country": "Bulgaria", + "company": "Bovis", + "favoriteNumber": 5 + }, + { + "id": 155, + "name": "Ronda Burks", + "city": "Ferney", + "state": "Montana", + "country": "Isle of Man", + "company": "Signity", + "favoriteNumber": 6 + }, + { + "id": 156, + "name": "Lourdes Walls", + "city": "Norwood", + "state": "Rhode Island", + "country": "Argentina", + "company": "Snacktion", + "favoriteNumber": 5 + }, + { + "id": 157, + "name": "Susana Mcintosh", + "city": "Manchester", + "state": "North Carolina", + "country": "Israel", + "company": "Teraprene", + "favoriteNumber": 5 + }, + { + "id": 158, + "name": "Alfreda Henry", + "city": "Wilsonia", + "state": "Maryland", + "country": "Bhutan", + "company": "Coash", + "favoriteNumber": 3 + }, + { + "id": 159, + "name": "Tiffany Chaney", + "city": "Carrsville", + "state": "Indiana", + "country": "Morocco", + "company": "Cinaster", + "favoriteNumber": 7 + }, + { + "id": 160, + "name": "Morton Edwards", + "city": "Barstow", + "state": "Vermont", + "country": "Hong Kong", + "company": "Ultrasure", + "favoriteNumber": 10 + }, + { + "id": 161, + "name": "Marcy Serrano", + "city": "Idamay", + "state": "Kansas", + "country": "Finland", + "company": "Isbol", + "favoriteNumber": 3 + }, + { + "id": 162, + "name": "Wendi Gutierrez", + "city": "Camas", + "state": "Alaska", + "country": "Andorra", + "company": "Turnling", + "favoriteNumber": 6 + }, + { + "id": 163, + "name": "Miriam Gates", + "city": "Helen", + "state": "North Dakota", + "country": "Djibouti", + "company": "Undertap", + "favoriteNumber": 8 + }, + { + "id": 164, + "name": "Adrienne Horne", + "city": "Snyderville", + "state": "Arkansas", + "country": "Gambia", + "company": "Olympix", + "favoriteNumber": 2 + }, + { + "id": 165, + "name": "Steele Morales", + "city": "Kenvil", + "state": "New Hampshire", + "country": "Macau", + "company": "Animalia", + "favoriteNumber": 3 + }, + { + "id": 166, + "name": "Ericka Morgan", + "city": "Leroy", + "state": "Massachusetts", + "country": "Kyrgyz Republic", + "company": "Opticall", + "favoriteNumber": 5 + }, + { + "id": 167, + "name": "Deborah Davenport", + "city": "Albany", + "state": "Utah", + "country": "Thailand", + "company": "Vetron", + "favoriteNumber": 10 + }, + { + "id": 168, + "name": "Tameka Mcneil", + "city": "Frierson", + "state": "Tennessee", + "country": "St. Lucia", + "company": "Martgo", + "favoriteNumber": 1 + }, + { + "id": 169, + "name": "Jewell Shields", + "city": "Bannock", + "state": "Pennsylvania", + "country": "Maldives", + "company": "Lotron", + "favoriteNumber": 8 + }, + { + "id": 170, + "name": "Crawford Fox", + "city": "Nicholson", + "state": "Louisiana", + "country": "Rwanda", + "company": "Progenex", + "favoriteNumber": 8 + }, + { + "id": 171, + "name": "Vaughan Tanner", + "city": "Cuylerville", + "state": "Idaho", + "country": "Jamaica", + "company": "Zeam", + "favoriteNumber": 3 + }, + { + "id": 172, + "name": "Shauna Wagner", + "city": "Disautel", + "state": "Michigan", + "country": "China", + "company": "Isologia", + "favoriteNumber": 6 + }, + { + "id": 173, + "name": "Meagan Hines", + "city": "Whitmer", + "state": "Colorado", + "country": "Jordan", + "company": "Grainspot", + "favoriteNumber": 8 + }, + { + "id": 174, + "name": "Palmer Bender", + "city": "Beechmont", + "state": "Texas", + "country": "Japan", + "company": "Vurbo", + "favoriteNumber": 10 + }, + { + "id": 175, + "name": "Amanda Buck", + "city": "Elfrida", + "state": "Nebraska", + "country": "Latvia", + "company": "Frosnex", + "favoriteNumber": 5 + }, + { + "id": 176, + "name": "Kristin Cleveland", + "city": "Richville", + "state": "West Virginia", + "country": "Lithuania", + "company": "Honotron", + "favoriteNumber": 2 + }, + { + "id": 177, + "name": "Harrell Vaughan", + "city": "Munjor", + "state": "Oregon", + "country": "Anguilla", + "company": "Orbalix", + "favoriteNumber": 9 + }, + { + "id": 178, + "name": "Stanley Webb", + "city": "Harleigh", + "state": "Oklahoma", + "country": "Mali", + "company": "Motovate", + "favoriteNumber": 6 + }, + { + "id": 179, + "name": "Briana Mitchell", + "city": "Kansas", + "state": "Ohio", + "country": "Libya", + "company": "Zillatide", + "favoriteNumber": 10 + }, + { + "id": 180, + "name": "Lillian Osborne", + "city": "Eastmont", + "state": "Virginia", + "country": "Belize", + "company": "Circum", + "favoriteNumber": 6 + }, + { + "id": 181, + "name": "Hughes Morse", + "city": "Herlong", + "state": "Mississippi", + "country": "French West Indies", + "company": "Endipine", + "favoriteNumber": 0 + }, + { + "id": 182, + "name": "Elise Whitehead", + "city": "Hailesboro", + "state": "South Dakota", + "country": "Saudi Arabia", + "company": "Geekmosis", + "favoriteNumber": 10 + }, + { + "id": 183, + "name": "Alyce Chavez", + "city": "Bendon", + "state": "Iowa", + "country": "Portugal", + "company": "Dognost", + "favoriteNumber": 8 + }, + { + "id": 184, + "name": "Goff Walker", + "city": "Sultana", + "state": "Washington", + "country": "Germany", + "company": "Uncorp", + "favoriteNumber": 4 + }, + { + "id": 185, + "name": "Brennan Melton", + "city": "Baker", + "state": "Florida", + "country": "Austria", + "company": "Thredz", + "favoriteNumber": 0 + }, + { + "id": 186, + "name": "Toni Brennan", + "city": "Newry", + "state": "California", + "country": "Serbia", + "company": "Bitendrex", + "favoriteNumber": 0 + }, + { + "id": 187, + "name": "Mcmillan Lane", + "city": "Thornport", + "state": "New York", + "country": "Panama", + "company": "Kengen", + "favoriteNumber": 2 + }, + { + "id": 188, + "name": "Yang Trujillo", + "city": "Falmouth", + "state": "Minnesota", + "country": "Paraguay", + "company": "Vitricomp", + "favoriteNumber": 7 + }, + { + "id": 189, + "name": "Osborn Love", + "city": "Rehrersburg", + "state": "Georgia", + "country": "Peru", + "company": "Newcube", + "favoriteNumber": 7 + }, + { + "id": 190, + "name": "Randolph Giles", + "city": "Sandston", + "state": "Connecticut", + "country": "Niger", + "company": "Manglo", + "favoriteNumber": 6 + }, + { + "id": 191, + "name": "Alison Eaton", + "city": "Wauhillau", + "state": "South Carolina", + "country": "St Kitts & Nevis", + "company": "Buzzmaker", + "favoriteNumber": 7 + }, + { + "id": 192, + "name": "Frankie Pollard", + "city": "Salix", + "state": "Missouri", + "country": "Uganda", + "company": "Jasper", + "favoriteNumber": 3 + }, + { + "id": 193, + "name": "Shields Cole", + "city": "Olney", + "state": "Maine", + "country": "British Virgin Islands", + "company": "Anivet", + "favoriteNumber": 3 + }, + { + "id": 194, + "name": "Frieda Wilkins", + "city": "Darrtown", + "state": "Wisconsin", + "country": "Gibraltar", + "company": "Elemantra", + "favoriteNumber": 6 + }, + { + "id": 195, + "name": "Parker Meyer", + "city": "Deseret", + "state": "Alabama", + "country": "Bermuda", + "company": "Cablam", + "favoriteNumber": 6 + }, + { + "id": 196, + "name": "Sharpe Blankenship", + "city": "Sparkill", + "state": "Hawaii", + "country": "Jersey", + "company": "Affluex", + "favoriteNumber": 0 + }, + { + "id": 197, + "name": "Fletcher Pope", + "city": "Libertytown", + "state": "New Jersey", + "country": "Bahrain", + "company": "Veraq", + "favoriteNumber": 6 + }, + { + "id": 198, + "name": "Brittany Holland", + "city": "Stonybrook", + "state": "Arizona", + "country": "Cook Islands", + "company": "Menbrain", + "favoriteNumber": 6 + }, + { + "id": 199, + "name": "Tammi Good", + "city": "Gwynn", + "state": "Illinois", + "country": "Denmark", + "company": "Kangle", + "favoriteNumber": 5 + }, + { + "id": 200, + "name": "Durham Valentine", + "city": "Dodge", + "state": "Wyoming", + "country": "Bolivia", + "company": "Amtas", + "favoriteNumber": 9 + }, + { + "id": 201, + "name": "Gina Savage", + "city": "Camptown", + "state": "Delaware", + "country": "San Marino", + "company": "Golistic", + "favoriteNumber": 8 + }, + { + "id": 202, + "name": "Faith Crane", + "city": "Kingstowne", + "state": "Kentucky", + "country": "Guyana", + "company": "Providco", + "favoriteNumber": 1 + }, + { + "id": 203, + "name": "Mullins Hewitt", + "city": "Courtland", + "state": "New Mexico", + "country": "Colombia", + "company": "Paprikut", + "favoriteNumber": 0 + }, + { + "id": 204, + "name": "Kemp Barber", + "city": "Morriston", + "state": "Montana", + "country": "United Kingdom", + "company": "Geekfarm", + "favoriteNumber": 3 + }, + { + "id": 205, + "name": "Sheppard Shaw", + "city": "Vandiver", + "state": "Rhode Island", + "country": "Madagascar", + "company": "Fossiel", + "favoriteNumber": 9 + }, + { + "id": 206, + "name": "Keith Bradshaw", + "city": "Mulberry", + "state": "North Carolina", + "country": "Ukraine", + "company": "Comtent", + "favoriteNumber": 9 + }, + { + "id": 207, + "name": "Dianne Conley", + "city": "Tonopah", + "state": "Maryland", + "country": "New Zealand", + "company": "Joviold", + "favoriteNumber": 5 + }, + { + "id": 208, + "name": "Love Griffin", + "city": "Day", + "state": "Indiana", + "country": "Ecuador", + "company": "Vortexaco", + "favoriteNumber": 6 + }, + { + "id": 209, + "name": "Melody Delacruz", + "city": "Hanover", + "state": "Vermont", + "country": "Virgin Islands (US)", + "company": "Pyramia", + "favoriteNumber": 6 + }, + { + "id": 210, + "name": "Patsy Kramer", + "city": "Southmont", + "state": "Kansas", + "country": "Sri Lanka", + "company": "Bedder", + "favoriteNumber": 8 + }, + { + "id": 211, + "name": "Becky Richard", + "city": "Crenshaw", + "state": "Alaska", + "country": "Croatia", + "company": "Polarium", + "favoriteNumber": 1 + }, + { + "id": 212, + "name": "Leon Rivera", + "city": "Gibbsville", + "state": "North Dakota", + "country": "Turks & Caicos", + "company": "Micronaut", + "favoriteNumber": 3 + }, + { + "id": 213, + "name": "Simpson Randall", + "city": "Cetronia", + "state": "Arkansas", + "country": "Cambodia", + "company": "Intergeek", + "favoriteNumber": 2 + }, + { + "id": 214, + "name": "Daugherty Duke", + "city": "Levant", + "state": "New Hampshire", + "country": "Namibia", + "company": "Nutralab", + "favoriteNumber": 3 + }, + { + "id": 215, + "name": "Payne Morton", + "city": "Jamestown", + "state": "Massachusetts", + "country": "Cape Verde", + "company": "Insource", + "favoriteNumber": 7 + }, + { + "id": 216, + "name": "Perkins Leblanc", + "city": "Boyd", + "state": "Utah", + "country": "Brunei", + "company": "Dognosis", + "favoriteNumber": 1 + }, + { + "id": 217, + "name": "Phillips Douglas", + "city": "Wikieup", + "state": "Tennessee", + "country": "Lebanon", + "company": "Ziore", + "favoriteNumber": 9 + }, + { + "id": 218, + "name": "Graves Stark", + "city": "Barrelville", + "state": "Pennsylvania", + "country": "Timor L'Este", + "company": "Exovent", + "favoriteNumber": 8 + }, + { + "id": 219, + "name": "Munoz Johns", + "city": "Wyano", + "state": "Louisiana", + "country": "Samoa", + "company": "Escenta", + "favoriteNumber": 10 + }, + { + "id": 220, + "name": "Myra Salazar", + "city": "Gorst", + "state": "Idaho", + "country": "Greece", + "company": "Acrodance", + "favoriteNumber": 10 + }, + { + "id": 221, + "name": "Flynn Miranda", + "city": "Movico", + "state": "Michigan", + "country": "Greenland", + "company": "Artiq", + "favoriteNumber": 6 + }, + { + "id": 222, + "name": "Eloise Barr", + "city": "Greer", + "state": "Colorado", + "country": "Sierra Leone", + "company": "Insuresys", + "favoriteNumber": 5 + }, + { + "id": 223, + "name": "Harrington Daniels", + "city": "Dawn", + "state": "Texas", + "country": "Tonga", + "company": "Pharmex", + "favoriteNumber": 3 + }, + { + "id": 224, + "name": "Lester Carey", + "city": "Keller", + "state": "Nebraska", + "country": "Nepal", + "company": "Melbacor", + "favoriteNumber": 4 + }, + { + "id": 225, + "name": "Malinda Pittman", + "city": "Wyoming", + "state": "West Virginia", + "country": "Falkland Islands", + "company": "Rodeocean", + "favoriteNumber": 1 + }, + { + "id": 226, + "name": "Crane Smith", + "city": "Hoagland", + "state": "Oregon", + "country": "Syria", + "company": "Eclipsent", + "favoriteNumber": 8 + }, + { + "id": 227, + "name": "Ellison Underwood", + "city": "Neahkahnie", + "state": "Oklahoma", + "country": "Suriname", + "company": "Visualix", + "favoriteNumber": 4 + }, + { + "id": 228, + "name": "Shelby Hardy", + "city": "Bascom", + "state": "Ohio", + "country": "Malta", + "company": "Genekom", + "favoriteNumber": 8 + }, + { + "id": 229, + "name": "Sheena Maynard", + "city": "Morningside", + "state": "Virginia", + "country": "Dominican Republic", + "company": "Zillar", + "favoriteNumber": 10 + }, + { + "id": 230, + "name": "Tamera Roman", + "city": "Freelandville", + "state": "Mississippi", + "country": "Honduras", + "company": "Comtract", + "favoriteNumber": 6 + }, + { + "id": 231, + "name": "Juliette Hammond", + "city": "Lindcove", + "state": "South Dakota", + "country": "Yemen", + "company": "Coriander", + "favoriteNumber": 5 + }, + { + "id": 232, + "name": "Dean Holden", + "city": "Brantleyville", + "state": "Iowa", + "country": "Aruba", + "company": "Plexia", + "favoriteNumber": 5 + }, + { + "id": 233, + "name": "Whitfield Meadows", + "city": "Fedora", + "state": "Washington", + "country": "Egypt", + "company": "Isosure", + "favoriteNumber": 1 + }, + { + "id": 234, + "name": "Wiley Kelley", + "city": "Torboy", + "state": "Florida", + "country": "Malawi", + "company": "Zilladyne", + "favoriteNumber": 5 + }, + { + "id": 235, + "name": "Sherry Scott", + "city": "Garfield", + "state": "California", + "country": "Slovenia", + "company": "Otherway", + "favoriteNumber": 5 + }, + { + "id": 236, + "name": "Aline Sosa", + "city": "Martinez", + "state": "New York", + "country": "Australia", + "company": "Comstruct", + "favoriteNumber": 4 + }, + { + "id": 237, + "name": "Leta Rice", + "city": "Utting", + "state": "Minnesota", + "country": "Iraq", + "company": "Jumpstack", + "favoriteNumber": 8 + }, + { + "id": 238, + "name": "Ford Ingram", + "city": "Lafferty", + "state": "Georgia", + "country": "Hungary", + "company": "Sarasonic", + "favoriteNumber": 3 + }, + { + "id": 239, + "name": "Chan David", + "city": "Collins", + "state": "Connecticut", + "country": "Mongolia", + "company": "Velos", + "favoriteNumber": 6 + }, + { + "id": 240, + "name": "Jeanne Murray", + "city": "Carlos", + "state": "South Carolina", + "country": "Ethiopia", + "company": "Equitox", + "favoriteNumber": 6 + }, + { + "id": 241, + "name": "Fernandez Dean", + "city": "Wintersburg", + "state": "Missouri", + "country": "Bangladesh", + "company": "Orboid", + "favoriteNumber": 5 + }, + { + "id": 242, + "name": "Jordan Cox", + "city": "Orin", + "state": "Maine", + "country": "Sudan", + "company": "Roboid", + "favoriteNumber": 6 + }, + { + "id": 243, + "name": "Catherine Harper", + "city": "Bedias", + "state": "Wisconsin", + "country": "Zambia", + "company": "Photobin", + "favoriteNumber": 2 + }, + { + "id": 244, + "name": "Suarez Kelly", + "city": "Cressey", + "state": "Alabama", + "country": "Cameroon", + "company": "Xurban", + "favoriteNumber": 7 + }, + { + "id": 245, + "name": "Henderson Mcdonald", + "city": "Adamstown", + "state": "Hawaii", + "country": "Mexico", + "company": "Skinserve", + "favoriteNumber": 1 + }, + { + "id": 246, + "name": "Hardy Gibbs", + "city": "Chical", + "state": "New Jersey", + "country": "Zimbabwe", + "company": "Limage", + "favoriteNumber": 6 + }, + { + "id": 247, + "name": "Kimberley Yang", + "city": "Kenmar", + "state": "Arizona", + "country": "Tunisia", + "company": "Exotechno", + "favoriteNumber": 9 + }, + { + "id": 248, + "name": "Kristie Gilmore", + "city": "Fairview", + "state": "Illinois", + "country": "Italy", + "company": "Equicom", + "favoriteNumber": 4 + }, + { + "id": 249, + "name": "Jewel Hansen", + "city": "Worton", + "state": "Wyoming", + "country": "South Korea", + "company": "Sulfax", + "favoriteNumber": 6 + }, + { + "id": 250, + "name": "Cheryl Carter", + "city": "Caron", + "state": "Delaware", + "country": "Mauritius", + "company": "Netropic", + "favoriteNumber": 9 + }, + { + "id": 251, + "name": "Keisha Snider", + "city": "Waterloo", + "state": "Kentucky", + "country": "Romania", + "company": "Besto", + "favoriteNumber": 6 + }, + { + "id": 252, + "name": "Minnie Michael", + "city": "Cascades", + "state": "New Mexico", + "country": "South Africa", + "company": "Kidgrease", + "favoriteNumber": 10 + }, + { + "id": 253, + "name": "Sandy Mccullough", + "city": "Remington", + "state": "Montana", + "country": "Malaysia", + "company": "Zosis", + "favoriteNumber": 3 + }, + { + "id": 254, + "name": "Cervantes Maddox", + "city": "Sisquoc", + "state": "Rhode Island", + "country": "Oman", + "company": "Extremo", + "favoriteNumber": 6 + }, + { + "id": 255, + "name": "Sophia Logan", + "city": "Winfred", + "state": "North Carolina", + "country": "Ireland", + "company": "Enaut", + "favoriteNumber": 4 + }, + { + "id": 256, + "name": "Bertha Watson", + "city": "Caroleen", + "state": "Maryland", + "country": "France", + "company": "Musix", + "favoriteNumber": 9 + }, + { + "id": 257, + "name": "Emily Wilson", + "city": "Fairacres", + "state": "Indiana", + "country": "Iceland", + "company": "Comvene", + "favoriteNumber": 8 + }, + { + "id": 258, + "name": "Winters Petersen", + "city": "Wildwood", + "state": "Vermont", + "country": "Equatorial Guinea", + "company": "Webiotic", + "favoriteNumber": 0 + }, + { + "id": 259, + "name": "Chambers Finch", + "city": "Topaz", + "state": "Kansas", + "country": "Luxembourg", + "company": "Sultraxin", + "favoriteNumber": 5 + }, + { + "id": 260, + "name": "Byrd Mills", + "city": "Cloverdale", + "state": "Alaska", + "country": "Cruise Ship", + "company": "Accel", + "favoriteNumber": 2 + }, + { + "id": 261, + "name": "Gross Jacobs", + "city": "Duryea", + "state": "North Dakota", + "country": "Belarus", + "company": "Insuron", + "favoriteNumber": 5 + }, + { + "id": 262, + "name": "Jackson Sherman", + "city": "Waterview", + "state": "Arkansas", + "country": "Antigua & Barbuda", + "company": "Viagreat", + "favoriteNumber": 2 + }, + { + "id": 263, + "name": "Rhodes Boyer", + "city": "Enlow", + "state": "New Hampshire", + "country": "Chad", + "company": "Conferia", + "favoriteNumber": 4 + }, + { + "id": 264, + "name": "Campbell Rodgers", + "city": "Cumminsville", + "state": "Massachusetts", + "country": "Reunion", + "company": "Frolix", + "favoriteNumber": 10 + }, + { + "id": 265, + "name": "Bryant Sawyer", + "city": "Guilford", + "state": "Utah", + "country": "Gabon", + "company": "Rooforia", + "favoriteNumber": 10 + }, + { + "id": 266, + "name": "Joan Browning", + "city": "Elizaville", + "state": "Tennessee", + "country": "Fiji", + "company": "Kneedles", + "favoriteNumber": 6 + }, + { + "id": 267, + "name": "Jennie Mcintyre", + "city": "Draper", + "state": "Pennsylvania", + "country": "New Caledonia", + "company": "Isostream", + "favoriteNumber": 4 + }, + { + "id": 268, + "name": "Merle Jones", + "city": "Caspar", + "state": "Louisiana", + "country": "Haiti", + "company": "Tubesys", + "favoriteNumber": 4 + }, + { + "id": 269, + "name": "Ortega Burgess", + "city": "Thermal", + "state": "Idaho", + "country": "Mozambique", + "company": "Lovepad", + "favoriteNumber": 9 + }, + { + "id": 270, + "name": "Deanna Grimes", + "city": "Flintville", + "state": "Michigan", + "country": "Pakistan", + "company": "Omatom", + "favoriteNumber": 10 + }, + { + "id": 271, + "name": "Jeanie Ochoa", + "city": "Ruckersville", + "state": "Colorado", + "country": "Cayman Islands", + "company": "Momentia", + "favoriteNumber": 8 + }, + { + "id": 272, + "name": "Morrow Valencia", + "city": "Roberts", + "state": "Texas", + "country": "Guam", + "company": "Permadyne", + "favoriteNumber": 4 + }, + { + "id": 273, + "name": "Hull Wade", + "city": "Monument", + "state": "Nebraska", + "country": "Cyprus", + "company": "Indexia", + "favoriteNumber": 10 + }, + { + "id": 274, + "name": "Blanca Sheppard", + "city": "Wadsworth", + "state": "West Virginia", + "country": "Nicaragua", + "company": "Gogol", + "favoriteNumber": 7 + }, + { + "id": 275, + "name": "Stella Luna", + "city": "Dubois", + "state": "Oregon", + "country": "Czech Republic", + "company": "Intrawear", + "favoriteNumber": 1 + } +]; + +export var fakeData; + diff --git a/samples/react/ReactGrid/Startup.cs b/samples/react/ReactGrid/Startup.cs new file mode 100755 index 0000000..46ddd60 --- /dev/null +++ b/samples/react/ReactGrid/Startup.cs @@ -0,0 +1,68 @@ +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Hosting; +using Microsoft.Dnx.Runtime; +using Microsoft.Framework.Configuration; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; + +namespace ReactExample +{ + public class Startup + { + public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv) + { + // Setup configuration sources. + var builder = new ConfigurationBuilder() + .SetBasePath(appEnv.ApplicationBasePath) + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; set; } + + // This method gets called by the runtime. + public void ConfigureServices(IServiceCollection services) + { + // Add MVC services to the services container. + services.AddMvc(); + } + + // Configure is called after ConfigureServices is called. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.MinimumLevel = LogLevel.Information; + loggerFactory.AddConsole(); + loggerFactory.AddDebug(); + + // Configure the HTTP request pipeline. + + // Add the platform handler to the request pipeline. + app.UseIISPlatformHandler(); + + // Add the following to the request pipeline only in development environment. + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + // Add Error handling middleware which catches all application specific errors and + // send the request to the following path or controller action. + app.UseExceptionHandler("/Home/Error"); + } + + // Add static files to the request pipeline. + app.UseStaticFiles(); + + // Add MVC to the request pipeline. + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{pageIndex?}", + defaults: new { controller="Home", action = "Index" }); + }); + } + } +} diff --git a/samples/react/ReactGrid/Views/Home/Index.cshtml b/samples/react/ReactGrid/Views/Home/Index.cshtml new file mode 100755 index 0000000..bef6868 --- /dev/null +++ b/samples/react/ReactGrid/Views/Home/Index.cshtml @@ -0,0 +1,5 @@ +
@Html.Raw(ViewData["ReactOutput"])
+ +@section scripts { + +} diff --git a/samples/react/ReactGrid/Views/Shared/Error.cshtml b/samples/react/ReactGrid/Views/Shared/Error.cshtml new file mode 100755 index 0000000..a288cb0 --- /dev/null +++ b/samples/react/ReactGrid/Views/Shared/Error.cshtml @@ -0,0 +1,6 @@ +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

diff --git a/samples/react/ReactGrid/Views/Shared/_Layout.cshtml b/samples/react/ReactGrid/Views/Shared/_Layout.cshtml new file mode 100755 index 0000000..4e83db4 --- /dev/null +++ b/samples/react/ReactGrid/Views/Shared/_Layout.cshtml @@ -0,0 +1,12 @@ + + + + + ReactExample + + + + @RenderBody() + @RenderSection("scripts", required: false) + + diff --git a/samples/react/ReactGrid/Views/_ViewImports.cshtml b/samples/react/ReactGrid/Views/_ViewImports.cshtml new file mode 100755 index 0000000..7839f6c --- /dev/null +++ b/samples/react/ReactGrid/Views/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@using ReactExample +@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers" diff --git a/samples/react/ReactGrid/Views/_ViewStart.cshtml b/samples/react/ReactGrid/Views/_ViewStart.cshtml new file mode 100755 index 0000000..66b5da2 --- /dev/null +++ b/samples/react/ReactGrid/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/samples/react/ReactGrid/appsettings.json b/samples/react/ReactGrid/appsettings.json new file mode 100755 index 0000000..0967ef4 --- /dev/null +++ b/samples/react/ReactGrid/appsettings.json @@ -0,0 +1 @@ +{} diff --git a/samples/react/ReactGrid/jsconfig.json b/samples/react/ReactGrid/jsconfig.json new file mode 100644 index 0000000..875bb90 --- /dev/null +++ b/samples/react/ReactGrid/jsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "commonjs" + } +} diff --git a/samples/react/ReactGrid/package.json b/samples/react/ReactGrid/package.json new file mode 100644 index 0000000..86bf49d --- /dev/null +++ b/samples/react/ReactGrid/package.json @@ -0,0 +1,25 @@ +{ + "name": "ReactExample", + "version": "0.0.0", + "dependencies": { + "babel-core": "^5.8.29", + "body-parser": "^1.14.1", + "bootstrap": "^3.3.5", + "express": "^4.13.3", + "griddle-react": "^0.2.14", + "history": "^1.12.6", + "react": "^0.14.0", + "react-dom": "^0.14.0", + "react-router": "^1.0.0-rc3", + "underscore": "^1.8.3" + }, + "devDependencies": { + "babel-loader": "^5.3.2", + "css-loader": "^0.21.0", + "extract-text-webpack-plugin": "^0.8.2", + "file-loader": "^0.8.4", + "style-loader": "^0.13.0", + "url-loader": "^0.5.6", + "webpack": "^1.12.2" + } +} diff --git a/samples/react/ReactGrid/project.json b/samples/react/ReactGrid/project.json new file mode 100755 index 0000000..5a346c8 --- /dev/null +++ b/samples/react/ReactGrid/project.json @@ -0,0 +1,45 @@ +{ + "webroot": "wwwroot", + "version": "1.0.0-*", + "tooling": { + "defaultNamespace": "ReactExample" + }, + "dependencies": { + "Microsoft.AspNet.Diagnostics": "1.0.0-beta8", + "Microsoft.AspNet.IISPlatformHandler": "1.0.0-beta8", + "Microsoft.AspNet.Mvc": "6.0.0-beta8", + "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-beta8", + "Microsoft.AspNet.Server.Kestrel": "1.0.0-beta8", + "Microsoft.AspNet.StaticFiles": "1.0.0-beta8", + "Microsoft.AspNet.Tooling.Razor": "1.0.0-beta8", + "Microsoft.Framework.Configuration.Json": "1.0.0-beta8", + "Microsoft.Framework.Logging": "1.0.0-beta8", + "Microsoft.Framework.Logging.Console": "1.0.0-beta8", + "Microsoft.Framework.Logging.Debug": "1.0.0-beta8", + "Microsoft.AspNet.NodeServices.React": "1.0.0-alpha1" + }, + "commands": { + "web": "Microsoft.AspNet.Server.Kestrel" + }, + "frameworks": { + "dnx451": {}, + "dnxcore50": {} + }, + "exclude": [ + "wwwroot", + "node_modules", + "bower_components" + ], + "publishExclude": [ + "node_modules", + "bower_components", + "**.xproj", + "**.user", + "**.vspscc" + ], + "scripts": { + "prepublish": [ + "npm install" + ] + } +} \ No newline at end of file diff --git a/samples/react/ReactGrid/webpack.config.js b/samples/react/ReactGrid/webpack.config.js new file mode 100644 index 0000000..02884bc --- /dev/null +++ b/samples/react/ReactGrid/webpack.config.js @@ -0,0 +1,19 @@ +var ExtractTextPlugin = require('extract-text-webpack-plugin'); + +module.exports = { + entry: './ReactApp/boot-client.jsx', + output: { + path: './wwwroot', + filename: 'bundle.js' + }, + module: { + loaders: [ + { test: /\.jsx?$/, loader: 'babel-loader' }, + { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader') }, + { test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' } + ] + }, + plugins: [ + new ExtractTextPlugin('main.css') + ] +}; diff --git a/samples/react/ReactGrid/wwwroot/favicon.ico b/samples/react/ReactGrid/wwwroot/favicon.ico new file mode 100755 index 0000000..a3a7999 Binary files /dev/null and b/samples/react/ReactGrid/wwwroot/favicon.ico differ diff --git a/samples/react/ReactGrid/wwwroot/web.config b/samples/react/ReactGrid/wwwroot/web.config new file mode 100644 index 0000000..db6e6f4 --- /dev/null +++ b/samples/react/ReactGrid/wwwroot/web.config @@ -0,0 +1,9 @@ + + + + + + + + +