mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-22 17:47:53 +00:00
Initial state
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.vs
|
||||||
|
*.xproj.user
|
||||||
|
project.lock.json
|
||||||
1
Microsoft.AspNet.NodeServices.Angular/.gitignore
vendored
Normal file
1
Microsoft.AspNet.NodeServices.Angular/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/bin/
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Microsoft.AspNet.NodeServices.Angular/Content/Node/angular-rendering.js
vendored
Normal file
28
Microsoft.AspNet.NodeServices.Angular/Content/Node/angular-rendering.js
vendored
Normal file
@@ -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); }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
34
Microsoft.AspNet.NodeServices.Angular/project.json
Normal file
34
Microsoft.AspNet.NodeServices.Angular/project.json
Normal file
@@ -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/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
1
Microsoft.AspNet.NodeServices.React/.gitignore
vendored
Normal file
1
Microsoft.AspNet.NodeServices.React/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/bin/
|
||||||
40
Microsoft.AspNet.NodeServices.React/Content/Node/react-rendering.js
vendored
Normal file
40
Microsoft.AspNet.NodeServices.React/Content/Node/react-rendering.js
vendored
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
24
Microsoft.AspNet.NodeServices.React/ReactRenderer.cs
Normal file
24
Microsoft.AspNet.NodeServices.React/ReactRenderer.cs
Normal file
@@ -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<string> RenderToString(string moduleName, string exportName, string baseUrl) {
|
||||||
|
return await nodeInstance.InvokeExport(nodeScript.FileName, "renderToString", new {
|
||||||
|
moduleName,
|
||||||
|
exportName,
|
||||||
|
baseUrl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Microsoft.AspNet.NodeServices.React/project.json
Normal file
33
Microsoft.AspNet.NodeServices.React/project.json
Normal file
@@ -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/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
1
Microsoft.AspNet.NodeServices/.gitignore
vendored
Normal file
1
Microsoft.AspNet.NodeServices/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/bin/
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
Microsoft.AspNet.NodeServices/HostingModels/HttpNodeHost.cs
Normal file
50
Microsoft.AspNet.NodeServices/HostingModels/HttpNodeHost.cs
Normal file
@@ -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<string> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<string> _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<string> 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<string>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Microsoft.AspNet.NodeServices/HostingModels/NodeHost.cs
Normal file
10
Microsoft.AspNet.NodeServices/HostingModels/NodeHost.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.NodeServices {
|
||||||
|
public abstract class NodeHost : System.IDisposable
|
||||||
|
{
|
||||||
|
public abstract Task<string> Invoke(NodeInvocationInfo invocationInfo);
|
||||||
|
|
||||||
|
public abstract void Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Microsoft.AspNet.NodeServices {
|
||||||
|
public class NodeInvocationInfo
|
||||||
|
{
|
||||||
|
public string ModuleName;
|
||||||
|
public string ExportedFunctionName;
|
||||||
|
public object[] Args;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<bool> _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<bool>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
Microsoft.AspNet.NodeServices/NodeHostingModel.cs
Normal file
6
Microsoft.AspNet.NodeServices/NodeHostingModel.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Microsoft.AspNet.NodeServices {
|
||||||
|
public enum NodeHostingModel {
|
||||||
|
Http,
|
||||||
|
InputOutputStream,
|
||||||
|
}
|
||||||
|
}
|
||||||
38
Microsoft.AspNet.NodeServices/NodeInstance.cs
Normal file
38
Microsoft.AspNet.NodeServices/NodeInstance.cs
Normal file
@@ -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<string> Invoke(string moduleName, params object[] args) {
|
||||||
|
return this.InvokeExport(moduleName, null, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Microsoft.AspNet.NodeServices/StringAsTempFile.cs
Normal file
39
Microsoft.AspNet.NodeServices/StringAsTempFile.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Microsoft.AspNet.NodeServices/project.json
Normal file
34
Microsoft.AspNet.NodeServices/project.json
Normal file
@@ -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/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
5
samples/angular/MusicStore/.gitignore
vendored
Normal file
5
samples/angular/MusicStore/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/node_modules/
|
||||||
|
/wwwroot/lib/
|
||||||
|
/wwwroot/ng-app/**/*.js
|
||||||
|
/project.lock.json
|
||||||
|
/music-db.sqlite
|
||||||
210
samples/angular/MusicStore/Apis/AlbumsApiController.cs
Normal file
210
samples/angular/MusicStore/Apis/AlbumsApiController.cs
Normal file
@@ -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<ActionResult> 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<ActionResult> 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<ActionResult> 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<ActionResult> 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<ActionResult> 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<ActionResult> 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<ActionResult> 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
30
samples/angular/MusicStore/Apis/ArtistsApiController.cs
Normal file
30
samples/angular/MusicStore/Apis/ArtistsApiController.cs
Normal file
@@ -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<ActionResult> Lookup()
|
||||||
|
{
|
||||||
|
var artists = await _storeContext.Artists
|
||||||
|
.OrderBy(a => a.Name)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Json(artists);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
70
samples/angular/MusicStore/Apis/GenresApiController.cs
Normal file
70
samples/angular/MusicStore/Apis/GenresApiController.cs
Normal file
@@ -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<ActionResult> GenreList()
|
||||||
|
{
|
||||||
|
var genres = await _storeContext.Genres
|
||||||
|
//.Include(g => g.Albums)
|
||||||
|
.OrderBy(g => g.Name)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Json(genres);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("genre-lookup")]
|
||||||
|
public async Task<ActionResult> Lookup()
|
||||||
|
{
|
||||||
|
var genres = await _storeContext.Genres
|
||||||
|
.Select(g => new { g.GenreId, g.Name })
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Json(genres);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("menu")]
|
||||||
|
public async Task<ActionResult> 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<ActionResult> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
samples/angular/MusicStore/Apis/Models/AccountViewModels.cs
Normal file
63
samples/angular/MusicStore/Apis/Models/AccountViewModels.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
40
samples/angular/MusicStore/Apis/Models/Album.cs
Normal file
40
samples/angular/MusicStore/Apis/Models/Album.cs
Normal file
@@ -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<OrderDetail>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[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<OrderDetail> OrderDetails { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
12
samples/angular/MusicStore/Apis/Models/Artist.cs
Normal file
12
samples/angular/MusicStore/Apis/Models/Artist.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
21
samples/angular/MusicStore/Apis/Models/CartItem.cs
Normal file
21
samples/angular/MusicStore/Apis/Models/CartItem.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
24
samples/angular/MusicStore/Apis/Models/Genre.cs
Normal file
24
samples/angular/MusicStore/Apis/Models/Genre.cs
Normal file
@@ -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<Album>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GenreId { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public virtual ICollection<Album> Albums { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
39
samples/angular/MusicStore/Apis/Models/MusicStoreContext.cs
Normal file
39
samples/angular/MusicStore/Apis/Models/MusicStoreContext.cs
Normal file
@@ -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<ApplicationUser>
|
||||||
|
{
|
||||||
|
public MusicStoreContext()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbSet<Album> Albums { get; set; }
|
||||||
|
public DbSet<Artist> Artists { get; set; }
|
||||||
|
public DbSet<Order> Orders { get; set; }
|
||||||
|
public DbSet<Genre> Genres { get; set; }
|
||||||
|
public DbSet<CartItem> CartItems { get; set; }
|
||||||
|
public DbSet<OrderDetail> OrderDetails { get; set; }
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
|
{
|
||||||
|
// Configure pluralization
|
||||||
|
builder.Entity<Album>().ToTable("Albums");
|
||||||
|
builder.Entity<Artist>().ToTable("Artists");
|
||||||
|
builder.Entity<Order>().ToTable("Orders");
|
||||||
|
builder.Entity<Genre>().ToTable("Genres");
|
||||||
|
builder.Entity<CartItem>().ToTable("CartItems");
|
||||||
|
builder.Entity<OrderDetail>().ToTable("OrderDetails");
|
||||||
|
|
||||||
|
base.OnModelCreating(builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
samples/angular/MusicStore/Apis/Models/Order.cs
Normal file
73
samples/angular/MusicStore/Apis/Models/Order.cs
Normal file
@@ -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<OrderDetail>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[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<OrderDetail> OrderDetails { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
14
samples/angular/MusicStore/Apis/Models/OrderDetail.cs
Normal file
14
samples/angular/MusicStore/Apis/Models/OrderDetail.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
944
samples/angular/MusicStore/Apis/Models/SampleData.cs
Normal file
944
samples/angular/MusicStore/Apis/Models/SampleData.cs
Normal file
@@ -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<MusicStoreContext>())
|
||||||
|
{
|
||||||
|
if (await db.Database.EnsureCreatedAsync())
|
||||||
|
{
|
||||||
|
await InsertTestData(serviceProvider);
|
||||||
|
await CreateAdminUser(serviceProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task CreateAdminUser(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
|
||||||
|
var settings = serviceProvider.GetService<IOptions<SiteSettings>>().Value;
|
||||||
|
const string adminRole = "Administrator";
|
||||||
|
|
||||||
|
var userManager = serviceProvider.GetService<UserManager<ApplicationUser>>();
|
||||||
|
var roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();
|
||||||
|
|
||||||
|
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<TEntity>(
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
Func<TEntity, object> propertyToMatch, IEnumerable<TEntity> entities)
|
||||||
|
where TEntity : class
|
||||||
|
{
|
||||||
|
// Query in a separate context so that we can attach existing entities as modified
|
||||||
|
List<TEntity> existingData;
|
||||||
|
|
||||||
|
using (var scope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
|
||||||
|
using (var db = scope.ServiceProvider.GetService<MusicStoreContext>())
|
||||||
|
{
|
||||||
|
existingData = db.Set<TEntity>().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var scope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
|
||||||
|
using (var db = scope.ServiceProvider.GetService<MusicStoreContext>())
|
||||||
|
{
|
||||||
|
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<string, Genre> genres, Dictionary<string, Artist> 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<string, Artist> artists;
|
||||||
|
public static Dictionary<string, Artist> 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<string, Artist>();
|
||||||
|
foreach (Artist artist in artistsList)
|
||||||
|
{
|
||||||
|
artist.ArtistId = artistId++;
|
||||||
|
artists.Add(artist.Name, artist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return artists;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, Genre> genres;
|
||||||
|
public static Dictionary<string, Genre> 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<string, Genre>();
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
207
samples/angular/MusicStore/Apis/Models/ShoppingCart.cs
Normal file
207
samples/angular/MusicStore/Apis/Models/ShoppingCart.cs
Normal file
@@ -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<CartItem> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
samples/angular/MusicStore/Controllers/HomeController.cs
Executable file
27
samples/angular/MusicStore/Controllers/HomeController.cs
Executable file
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
samples/angular/MusicStore/Infrastructure/ApiResult.cs
Normal file
63
samples/angular/MusicStore/Infrastructure/ApiResult.cs
Normal file
@@ -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<ModelError> 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
150
samples/angular/MusicStore/Infrastructure/PagedList.cs
Normal file
150
samples/angular/MusicStore/Infrastructure/PagedList.cs
Normal file
@@ -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<T>
|
||||||
|
{
|
||||||
|
IEnumerable<T> Data { get; }
|
||||||
|
|
||||||
|
int Page { get; }
|
||||||
|
|
||||||
|
int PageSize { get; }
|
||||||
|
|
||||||
|
int TotalCount { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class PagedList<T> : IPagedList<T>
|
||||||
|
{
|
||||||
|
public PagedList(IEnumerable<T> data, int page, int pageSize, int totalCount)
|
||||||
|
{
|
||||||
|
Data = data;
|
||||||
|
Page = page;
|
||||||
|
PageSize = pageSize;
|
||||||
|
TotalCount = totalCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<T> 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<T> ToPagedList<T>(this IQueryable<T> 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<T>(data, pagingConfig.Page, pagingConfig.PageSize, query.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task<IPagedList<TModel>> ToPagedListAsync<TModel, TProperty>(this IQueryable<TModel> query, int page, int pageSize, string sortExpression, Expression<Func<TModel, TProperty>> defaultSortExpression, SortDirection defaultSortDirection = SortDirection.Ascending)
|
||||||
|
where TModel : class
|
||||||
|
{
|
||||||
|
return ToPagedListAsync<TModel, TProperty, TModel>(query, page, pageSize, sortExpression, defaultSortExpression, defaultSortDirection, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<IPagedList<TResult>> ToPagedListAsync<TModel, TProperty, TResult>(this IQueryable<TModel> query, int page, int pageSize, string sortExpression, Expression<Func<TModel, TProperty>> defaultSortExpression, SortDirection defaultSortDirection, Func<TModel, TResult> 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<TResult>();
|
||||||
|
|
||||||
|
return new PagedList<TResult>(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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
samples/angular/MusicStore/Infrastructure/SortDirection.cs
Normal file
13
samples/angular/MusicStore/Infrastructure/SortDirection.cs
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
87
samples/angular/MusicStore/Infrastructure/SortExpression.cs
Normal file
87
samples/angular/MusicStore/Infrastructure/SortExpression.cs
Normal file
@@ -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<TModel> SortBy<TModel, TProperty>(this IQueryable<TModel> query, string sortExpression, Expression<Func<TModel, TProperty>> defaultSortExpression, SortDirection defaultSortDirection = SortDirection.Ascending) where TModel : class
|
||||||
|
{
|
||||||
|
return SortBy(query, sortExpression ?? Create(defaultSortExpression, defaultSortDirection));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Create<TModel, TProperty>(Expression<Func<TModel, TProperty>> 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<T> SortBy<T>(this IQueryable<T> 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<T>)source.Provider.CreateQuery(methodCallExpression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
samples/angular/MusicStore/SiteSettings.cs
Normal file
13
samples/angular/MusicStore/SiteSettings.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
139
samples/angular/MusicStore/Startup.cs
Executable file
139
samples/angular/MusicStore/Startup.cs
Executable file
@@ -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<SiteSettings>(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<MusicStoreContext>(options => {
|
||||||
|
options.UseSqlite(Configuration["DbConnectionString"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add Identity services to the services container
|
||||||
|
services.AddIdentity<ApplicationUser, IdentityRole>()
|
||||||
|
.AddEntityFrameworkStores<MusicStoreContext>()
|
||||||
|
.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<AuthorizationOptions>(options =>
|
||||||
|
{
|
||||||
|
options.AddPolicy("app-ManageStore", new AuthorizationPolicyBuilder().RequireClaim("app-ManageStore", "Allowed").Build());
|
||||||
|
});
|
||||||
|
|
||||||
|
Mapper.CreateMap<AlbumChangeDto, Album>();
|
||||||
|
Mapper.CreateMap<Album, AlbumChangeDto>();
|
||||||
|
Mapper.CreateMap<Album, AlbumResultDto>();
|
||||||
|
Mapper.CreateMap<AlbumResultDto, Album>();
|
||||||
|
Mapper.CreateMap<Artist, ArtistResultDto>();
|
||||||
|
Mapper.CreateMap<ArtistResultDto, Artist>();
|
||||||
|
Mapper.CreateMap<Genre, GenreResultDto>();
|
||||||
|
Mapper.CreateMap<GenreResultDto, Genre>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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?}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
samples/angular/MusicStore/Views/Home/Index.cshtml
Executable file
21
samples/angular/MusicStore/Views/Home/Index.cshtml
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Home Page";
|
||||||
|
}
|
||||||
|
|
||||||
|
<cache vary-by="@Context.Request.Path">
|
||||||
|
<app aspnet-ng2-prerender-module="wwwroot/ng-app/components/app/app">
|
||||||
|
Loading...
|
||||||
|
</app>
|
||||||
|
</cache>
|
||||||
|
|
||||||
|
@section scripts {
|
||||||
|
<script src="~/lib/traceur/bin/traceur-runtime.js"></script>
|
||||||
|
<script src="~/lib/es6-module-loader/dist/es6-module-loader-sans-promises.js"></script>
|
||||||
|
<script src="~/lib/reflect-metadata/Reflect.js"></script>
|
||||||
|
<script src="~/lib/systemjs/dist/system.src.js"></script>
|
||||||
|
<script src="~/system.config.js"></script>
|
||||||
|
<script src="~/lib/angular2/bundles/angular2.dev.js"></script>
|
||||||
|
<script src="~/lib/angular2/bundles/router.dev.js"></script>
|
||||||
|
<script src="~/lib/angular2/bundles/http.dev.js"></script>
|
||||||
|
<script>System.import('./ng-app/components/app/bootstrap');</script>
|
||||||
|
}
|
||||||
6
samples/angular/MusicStore/Views/Shared/Error.cshtml
Executable file
6
samples/angular/MusicStore/Views/Shared/Error.cshtml
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Error";
|
||||||
|
}
|
||||||
|
|
||||||
|
<h1 class="text-danger">Error.</h1>
|
||||||
|
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||||
40
samples/angular/MusicStore/Views/Shared/_Layout.cshtml
Executable file
40
samples/angular/MusicStore/Views/Shared/_Layout.cshtml
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Music Store</title>
|
||||||
|
<base href="/" />
|
||||||
|
|
||||||
|
<environment names="Development">
|
||||||
|
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
|
||||||
|
<link rel="stylesheet" href="~/css/site.css" />
|
||||||
|
</environment>
|
||||||
|
<environment names="Staging,Production">
|
||||||
|
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/css/bootstrap.min.css"
|
||||||
|
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
|
||||||
|
asp-fallback-test-class="hidden" asp-fallback-test-property="visibility" asp-fallback-test-value="hidden" />
|
||||||
|
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||||
|
</environment>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
@RenderBody()
|
||||||
|
|
||||||
|
<environment names="Development">
|
||||||
|
<script src="~/lib/jquery/dist/jquery.js"></script>
|
||||||
|
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
|
||||||
|
</environment>
|
||||||
|
<environment names="Staging,Production">
|
||||||
|
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.1.4.min.js"
|
||||||
|
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
|
||||||
|
asp-fallback-test="window.jQuery">
|
||||||
|
</script>
|
||||||
|
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/bootstrap.min.js"
|
||||||
|
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
|
||||||
|
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal">
|
||||||
|
</script>
|
||||||
|
</environment>
|
||||||
|
|
||||||
|
@RenderSection("scripts", required: false)
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3
samples/angular/MusicStore/Views/_ViewImports.cshtml
Executable file
3
samples/angular/MusicStore/Views/_ViewImports.cshtml
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
@using MusicStore
|
||||||
|
@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
|
||||||
|
@addTagHelper "*, Microsoft.AspNet.NodeServices.Angular"
|
||||||
3
samples/angular/MusicStore/Views/_ViewStart.cshtml
Executable file
3
samples/angular/MusicStore/Views/_ViewStart.cshtml
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
@{
|
||||||
|
Layout = "_Layout";
|
||||||
|
}
|
||||||
3
samples/angular/MusicStore/appsettings.json
Executable file
3
samples/angular/MusicStore/appsettings.json
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"DbConnectionString": "Data Source=music-db.sqlite"
|
||||||
|
}
|
||||||
53
samples/angular/MusicStore/gulpfile.js
vendored
Executable file
53
samples/angular/MusicStore/gulpfile.js
vendored
Executable file
@@ -0,0 +1,53 @@
|
|||||||
|
/// <binding AfterBuild='build' Clean='clean' />
|
||||||
|
|
||||||
|
"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']);
|
||||||
27
samples/angular/MusicStore/package.json
Normal file
27
samples/angular/MusicStore/package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
49
samples/angular/MusicStore/project.json
Executable file
49
samples/angular/MusicStore/project.json
Executable file
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
13
samples/angular/MusicStore/tsconfig.json
Normal file
13
samples/angular/MusicStore/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es5",
|
||||||
|
"sourceMap": false,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"noLib": false
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
samples/angular/MusicStore/wwwroot/css/site.css
Normal file
3
samples/angular/MusicStore/wwwroot/css/site.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
body {
|
||||||
|
padding-top: 50px;
|
||||||
|
}
|
||||||
14
samples/angular/MusicStore/wwwroot/css/styles.less
Normal file
14
samples/angular/MusicStore/wwwroot/css/styles.less
Normal file
@@ -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%) }
|
||||||
|
}
|
||||||
BIN
samples/angular/MusicStore/wwwroot/images/home-showcase.png
Normal file
BIN
samples/angular/MusicStore/wwwroot/images/home-showcase.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 248 KiB |
BIN
samples/angular/MusicStore/wwwroot/images/logo.png
Normal file
BIN
samples/angular/MusicStore/wwwroot/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
BIN
samples/angular/MusicStore/wwwroot/images/placeholder.png
Normal file
BIN
samples/angular/MusicStore/wwwroot/images/placeholder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<h1>Store Manager</h1>
|
||||||
|
|
||||||
|
<router-outlet></router-outlet>
|
||||||
@@ -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 {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<div class="modal fade">
|
||||||
|
<div class="modal-dialog" *ng-if="album">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title">Delete {{ album.Title }}</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Really delete <strong>{{ album.Title }}</strong> by <strong>{{ album.Artist.Name }}</strong>?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-danger">Confirm Delete</button>
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div><!-- /.modal-content -->
|
||||||
|
</div><!-- /.modal-dialog -->
|
||||||
|
</div><!-- /.modal -->
|
||||||
@@ -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 = (<any>window).jQuery(".modal", elementRef.nativeElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public show(album: models.Album) {
|
||||||
|
this.album = album;
|
||||||
|
this.modalElement.modal();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<h2>Album <small>Details</small></h2>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<form class="form-horizontal" role="form" *ng-if="albumData">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label">Artist</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<p class="form-control-static">{{ albumData.Artist.Name }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label">Genre</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<p class="form-control-static">{{ albumData.Genre.Name }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label">Title</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<p class="form-control-static">{{ albumData.Title }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label">Price</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<p class="form-control-static">{{ albumData.Price | currency:'USD':true }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label">Album Art URL</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<p class="form-control-static">{{ albumData.AlbumArtUrl }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label">Album Art</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<img alt="{{ albumData.Title }}" src="{{ albumData.AlbumArtUrl }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-md-offset-2 col-md-10">
|
||||||
|
<a class="btn btn-primary" [router-link]="['/Admin/AlbumEdit', { albumId: albumData.AlbumId }]">Edit</a>
|
||||||
|
<button type="button" class="btn btn-danger" (click)="deleteprompt.show(albumData)">Delete</button>
|
||||||
|
<a class="btn btn-default" [router-link]="['/Admin/Albums']">Back to List</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<album-delete-prompt #deleteprompt></album-delete-prompt>
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<h2>Album <small>Edit</small></h2>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<form class="form-horizontal" [ng-form-model]="form" (ng-submit)="onSubmitModelBased()">
|
||||||
|
<form-field label="Artist" [validate]="form.controls.artist">
|
||||||
|
<select class="form-control" ng-control="artist">
|
||||||
|
<option value="">-- choose Artist --</option>
|
||||||
|
<option *ng-for="#artist of artists" [value]="artist.ArtistId">{{ artist.Name }}</option>
|
||||||
|
</select>
|
||||||
|
</form-field>
|
||||||
|
|
||||||
|
<form-field label="Genre" [validate]="form.controls.genre">
|
||||||
|
<select class="form-control" ng-control="genre">
|
||||||
|
<option value="">-- choose Genre --</option>
|
||||||
|
<option *ng-for="#genre of genres" [value]="genre.GenreId">{{ genre.Name }}</option>
|
||||||
|
</select>
|
||||||
|
</form-field>
|
||||||
|
|
||||||
|
<form-field label="Title" [validate]="form.controls.title">
|
||||||
|
<input class="form-control" type="text" ng-control="title">
|
||||||
|
</form-field>
|
||||||
|
|
||||||
|
<form-field label="Price" [validate]="form.controls.price">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-addon">$</span>
|
||||||
|
<input class="form-control" type="text" ng-control="price">
|
||||||
|
</div>
|
||||||
|
</form-field>
|
||||||
|
|
||||||
|
<form-field label="Album Art URL" [validate]="form.controls.albumArtUrl">
|
||||||
|
<input class="form-control" ng-control="albumArtUrl">
|
||||||
|
</form-field>
|
||||||
|
|
||||||
|
<form-field label="Album Art">
|
||||||
|
<img src="{{ form.controls.albumArtUrl.value }}">
|
||||||
|
</form-field>
|
||||||
|
|
||||||
|
<form-field>
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
<button type="button" class="btn btn-danger" (click)="deleteprompt.show(originalAlbum)">Delete</button>
|
||||||
|
<a class="btn btn-default" [router-link]="['/Admin/Albums']">Back to List</a>
|
||||||
|
</form-field>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<album-delete-prompt #deleteprompt></album-delete-prompt>
|
||||||
@@ -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;
|
||||||
|
(<ng.Control>this.form.controls['title']).updateValue(json.Title);
|
||||||
|
(<ng.Control>this.form.controls['price']).updateValue(json.Price);
|
||||||
|
(<ng.Control>this.form.controls['artist']).updateValue(json.ArtistId);
|
||||||
|
(<ng.Control>this.form.controls['genre']).updateValue(json.GenreId);
|
||||||
|
(<ng.Control>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(<any>{
|
||||||
|
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;
|
||||||
|
(<any>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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<h2>Albums</h2>
|
||||||
|
|
||||||
|
<album-delete-prompt #deleteprompt></album-delete-prompt>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><a>Genre</a></th>
|
||||||
|
<th><a>Artist</a></th>
|
||||||
|
<th><a (click)="sortBy('Title')">Title</a></th>
|
||||||
|
<th><a (click)="sortBy('Price')">Price</a></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ng-for="#row of rows">
|
||||||
|
<td>{{ row.Genre.Name }}</td>
|
||||||
|
<td>{{ row.Artist.Name }}</td>
|
||||||
|
<td>{{ row.Title }}</td>
|
||||||
|
<td>{{ row.Price | currency:'USD':true }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group btn-group-xs">
|
||||||
|
<a class="btn btn-default" [router-link]="['/Admin/AlbumDetails', { albumId: row.AlbumId }]">Details</a>
|
||||||
|
<a class="btn btn-default" [router-link]="['/Admin/AlbumEdit', { albumId: row.AlbumId }]">Edit</a>
|
||||||
|
<a class="btn btn-default" (click)="deleteprompt.show(row)">Delete</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-default" [disabled]="!canGoBack" (click)="goToPage(1)">First</button>
|
||||||
|
<button class="btn btn-default" [disabled]="!canGoBack" (click)="goToPage(pageIndex - 1)">Previous</button>
|
||||||
|
<button class="btn" *ng-for="#page of pageLinks"
|
||||||
|
[ng-class]="{ 'btn-info': page.isCurrent, 'btn-default': !page.isCurrent }"
|
||||||
|
(click)="goToPage(page.index)">
|
||||||
|
{{ page.text }}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-default" [disabled]="!canGoForward" (click)="goToPage(pageIndex + 1)">Next</button>
|
||||||
|
<button class="btn btn-default" [disabled]="!canGoForward" (click)="goToLast()">Last</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>{{ totalCount }} total albums</p>
|
||||||
@@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<div class="form-group" [class.has-error]="errorMessages.length">
|
||||||
|
<label class="col-md-2 control-label">{{ label }}</label>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
<div class="alert alert-danger" role="alert" *ng-if="errorMessages.length">
|
||||||
|
<p *ng-for="#message of errorMessages">{{ message }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<nav class="navbar navbar-inverse navbar-fixed-top">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<a class="navbar-brand" [router-link]="['/Home']">Music Store</a>
|
||||||
|
</div>
|
||||||
|
<div class="collapse navbar-collapse">
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li><a [router-link]="['/Home']">Home</a></li>
|
||||||
|
<li class="dropdown">
|
||||||
|
<a class="dropdown-toggle" data-toggle="dropdown">Store <b class="caret"></b></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li *ng-for="#genre of genres">
|
||||||
|
<a title="{{ genre.Description }}" [router-link]="['/Genre', { genreId: genre.GenreId }]">
|
||||||
|
{{ genre.Name }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="divider"></li>
|
||||||
|
<li>
|
||||||
|
<a [router-link]="['/GenresList']">More…</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><a [router-link]="['/Admin/Albums']">Admin</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="container body-content">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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]);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<div *ng-if="albumData">
|
||||||
|
<h2>{{ albumData.Title }}</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<img alt="{{ albumData.Title }}" src="{{ albumData.AlbumArtUrl }}">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div id="album-details">
|
||||||
|
<p>
|
||||||
|
<em>Genre:</em>
|
||||||
|
{{ albumData.Genre.Name }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<em>Artist:</em>
|
||||||
|
{{ albumData.Artist.Name }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<em>Price:</em>
|
||||||
|
{{ albumData.Price | currency:'USD':true }}
|
||||||
|
</p>
|
||||||
|
<p class="button">
|
||||||
|
<!-- TODO: Shopping cart functionality -->
|
||||||
|
Add to cart
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<a [router-link]="['/Album', { albumId: albumData.AlbumId }]">
|
||||||
|
<img alt="{{ albumData.Title }}" src="{{ albumData.AlbumArtUrl }}">
|
||||||
|
<h4>{{ albumData.Title }}</h4>
|
||||||
|
</a>
|
||||||
@@ -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 {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<h3>Albums</h3>
|
||||||
|
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li *ng-for="#album of albums" class="col-lg-2 col-md-2 col-sm-2 col-xs-4 container">
|
||||||
|
<album-tile [albumData]="album"></album-tile>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<h3>Browse Genres</h3>
|
||||||
|
|
||||||
|
<p *ng-if="genres">
|
||||||
|
Select from {{ genres.length }} genres:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul class="list-group">
|
||||||
|
<li *ng-for="#genre of genres" class="list-group-item">
|
||||||
|
<a title="{{genre.Description}}" [router-link]="['/Genre', { genreId: genre.GenreId }]">
|
||||||
|
{{ genre.Name }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<div class="jumbotron">
|
||||||
|
<h1>MVC Music Store</h1>
|
||||||
|
<img src="/Images/home-showcase.png">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="row list-unstyled" id="album-list">
|
||||||
|
<li *ng-for="#album of mostPopular" class="col-lg-2 col-md-2 col-sm-2 col-xs-4 container">
|
||||||
|
<album-tile [albumData]="album"></album-tile>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
16
samples/angular/MusicStore/wwwroot/ng-app/models/models.ts
Normal file
16
samples/angular/MusicStore/wwwroot/ng-app/models/models.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
3
samples/angular/MusicStore/wwwroot/system.config.js
Normal file
3
samples/angular/MusicStore/wwwroot/system.config.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
System.config({
|
||||||
|
defaultJSExtensions: true
|
||||||
|
});
|
||||||
9
samples/angular/MusicStore/wwwroot/web.config
Normal file
9
samples/angular/MusicStore/wwwroot/web.config
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<system.webServer>
|
||||||
|
<handlers>
|
||||||
|
<add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/>
|
||||||
|
</handlers>
|
||||||
|
<httpPlatform processPath="%DNX_PATH%" arguments="%DNX_ARGS%" stdoutLogEnabled="false"/>
|
||||||
|
</system.webServer>
|
||||||
|
</configuration>
|
||||||
5
samples/react/ReactGrid/.gitignore
vendored
Normal file
5
samples/react/ReactGrid/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/node_modules/
|
||||||
|
project.lock.json
|
||||||
|
/wwwroot/bundle.*
|
||||||
|
/wwwroot/*.svg
|
||||||
|
/wwwroot/*.css
|
||||||
24
samples/react/ReactGrid/Controllers/HomeController.cs
Executable file
24
samples/react/ReactGrid/Controllers/HomeController.cs
Executable file
@@ -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<IActionResult> 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
samples/react/ReactGrid/README.txt
Normal file
2
samples/react/ReactGrid/README.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Portions of this sample application (particularly, the fake data) are based
|
||||||
|
on https://github.com/DavidWells/isomorphic-react-example
|
||||||
8
samples/react/ReactGrid/ReactApp/boot-client.jsx
Normal file
8
samples/react/ReactGrid/ReactApp/boot-client.jsx
Normal file
@@ -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(<ReactApp history={ history } />, document.getElementById('react-app'));
|
||||||
50
samples/react/ReactGrid/ReactApp/components/CustomPager.jsx
Normal file
50
samples/react/ReactGrid/ReactApp/components/CustomPager.jsx
Normal file
@@ -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 = <div className="btn btn-default"><Link className="previous" to={'/' + (this.props.currentPage)}><i className="glyphicon glyphicon-arrow-left"></i>{this.props.previousText}</Link></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.props.currentPage != (this.props.maxPage -1)){
|
||||||
|
next = <div className="btn btn-default"><Link className="next" to={'/' + (this.props.currentPage + 2)}><i className="glyphicon glyphicon-arrow-right"></i>{this.props.nextText}</Link></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(<div key={i} className={"btn " + selected}><Link to={'/' + (i+1)}>{i+1}</Link></div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="btn-group">
|
||||||
|
{previous}
|
||||||
|
{options}
|
||||||
|
{next}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomPager.defaultProps = {
|
||||||
|
maxPage: 0,
|
||||||
|
nextText: '',
|
||||||
|
previousText: '',
|
||||||
|
currentPage: 0
|
||||||
|
};
|
||||||
26
samples/react/ReactGrid/ReactApp/components/PeopleGrid.jsx
Normal file
26
samples/react/ReactGrid/ReactApp/components/PeopleGrid.jsx
Normal file
@@ -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 (
|
||||||
|
<div>
|
||||||
|
<h1>People</h1>
|
||||||
|
<div id="table-area">
|
||||||
|
<Griddle results={fakeData}
|
||||||
|
columnMetadata={columnMeta}
|
||||||
|
resultsPerPage={resultsPerPage}
|
||||||
|
tableClassName="table"
|
||||||
|
useCustomPagerComponent="true"
|
||||||
|
customPagerComponent={CustomPager}
|
||||||
|
externalCurrentPage={pageIndex} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
samples/react/ReactGrid/ReactApp/components/ReactApp.jsx
Normal file
14
samples/react/ReactGrid/ReactApp/components/ReactApp.jsx
Normal file
@@ -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 (
|
||||||
|
<Router history={this.props.history}>
|
||||||
|
<Route path="/" component={PeopleGrid} />
|
||||||
|
<Route path="/:pageIndex" component={PeopleGrid} />
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
samples/react/ReactGrid/ReactApp/data/columnMeta.js
Normal file
47
samples/react/ReactGrid/ReactApp/data/columnMeta.js
Normal file
@@ -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;
|
||||||
|
|
||||||
2489
samples/react/ReactGrid/ReactApp/data/fakeData.js
Normal file
2489
samples/react/ReactGrid/ReactApp/data/fakeData.js
Normal file
File diff suppressed because it is too large
Load Diff
68
samples/react/ReactGrid/Startup.cs
Executable file
68
samples/react/ReactGrid/Startup.cs
Executable file
@@ -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" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
samples/react/ReactGrid/Views/Home/Index.cshtml
Executable file
5
samples/react/ReactGrid/Views/Home/Index.cshtml
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
<div id="react-app">@Html.Raw(ViewData["ReactOutput"])</div>
|
||||||
|
|
||||||
|
@section scripts {
|
||||||
|
<script src="bundle.js"></script>
|
||||||
|
}
|
||||||
6
samples/react/ReactGrid/Views/Shared/Error.cshtml
Executable file
6
samples/react/ReactGrid/Views/Shared/Error.cshtml
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Error";
|
||||||
|
}
|
||||||
|
|
||||||
|
<h1 class="text-danger">Error.</h1>
|
||||||
|
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user