Remove deprecated SPA and data projects.
This commit is contained in:
Родитель
6f225b02c9
Коммит
bcb1cb5156
60
Runtime.sln
60
Runtime.sln
|
@ -63,27 +63,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Http.SelfHost",
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Http.WebHost", "src\System.Web.Http.WebHost\System.Web.Http.WebHost.csproj", "{A0187BC2-8325-4BB2-8697-7F955CF4173E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Web.Http.Data", "src\Microsoft.Web.Http.Data\Microsoft.Web.Http.Data.csproj", "{ACE91549-D86E-4EB6-8C2A-5FF51386BB68}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Web.Http.Data.EntityFramework", "src\Microsoft.Web.Http.Data.EntityFramework\Microsoft.Web.Http.Data.EntityFramework.csproj", "{653F3946-541C-42D3-BBC1-CE89B392BDA9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Web.Http.Data.Test", "test\Microsoft.Web.Http.Data.Test\Microsoft.Web.Http.Data.Test.csproj", "{81876811-6C36-492A-9609-F0E85990FBC9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Web.Http.Data.Helpers.Test", "test\Microsoft.Web.Http.Data.Helpers.Test\Microsoft.Web.Http.Data.Helpers.Test.csproj", "{F6C0671C-B832-4807-BA9A-9206BD35A650}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Http.Integration.Test", "test\System.Web.Http.Integration.Test\System.Web.Http.Integration.Test.csproj", "{3267DFC6-B34D-4011-BC0F-D3B56AF6F608}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Http.WebHost.Test", "test\System.Web.Http.WebHost.Test\System.Web.Http.WebHost.Test.csproj", "{EA62944F-BD25-4730-9405-9BE8FF5BEACD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Web.Http.Data.Helpers", "src\Microsoft.Web.Http.Data.Helpers\Microsoft.Web.Http.Data.Helpers.csproj", "{B6895A1B-382F-4A69-99EC-E965E19B0AB3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SPA", "src\SPA\SPA.csproj", "{1ACEF677-B6A0-4680-A076-7893DE176D6B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SPA.Test", "test\SPA.Test\SPA.Test.csproj", "{7B8601F8-8D1F-4B9C-8C20-772B673A2FA6}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{1ACEF677-B6A0-4680-A076-7893DE176D6B} = {1ACEF677-B6A0-4680-A076-7893DE176D6B}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Web.WebPages.OAuth", "src\Microsoft.Web.WebPages.OAuth\Microsoft.Web.WebPages.OAuth.csproj", "{4CBFC7D3-1600-4CE5-BC6B-AC7BC2D6F853}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Web.WebPages.OAuth.Test", "test\Microsoft.Web.WebPages.OAuth.Test\Microsoft.Web.WebPages.OAuth.Test.csproj", "{694C6EDF-EA52-438F-B745-82B025ECC0E7}"
|
||||
|
@ -271,27 +254,6 @@ Global
|
|||
{A0187BC2-8325-4BB2-8697-7F955CF4173E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A0187BC2-8325-4BB2-8697-7F955CF4173E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A0187BC2-8325-4BB2-8697-7F955CF4173E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{ACE91549-D86E-4EB6-8C2A-5FF51386BB68}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU
|
||||
{ACE91549-D86E-4EB6-8C2A-5FF51386BB68}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU
|
||||
{ACE91549-D86E-4EB6-8C2A-5FF51386BB68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ACE91549-D86E-4EB6-8C2A-5FF51386BB68}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ACE91549-D86E-4EB6-8C2A-5FF51386BB68}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ACE91549-D86E-4EB6-8C2A-5FF51386BB68}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{653F3946-541C-42D3-BBC1-CE89B392BDA9}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU
|
||||
{653F3946-541C-42D3-BBC1-CE89B392BDA9}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU
|
||||
{653F3946-541C-42D3-BBC1-CE89B392BDA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{653F3946-541C-42D3-BBC1-CE89B392BDA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{653F3946-541C-42D3-BBC1-CE89B392BDA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{653F3946-541C-42D3-BBC1-CE89B392BDA9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{81876811-6C36-492A-9609-F0E85990FBC9}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU
|
||||
{81876811-6C36-492A-9609-F0E85990FBC9}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU
|
||||
{81876811-6C36-492A-9609-F0E85990FBC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{81876811-6C36-492A-9609-F0E85990FBC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{81876811-6C36-492A-9609-F0E85990FBC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81876811-6C36-492A-9609-F0E85990FBC9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F6C0671C-B832-4807-BA9A-9206BD35A650}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU
|
||||
{F6C0671C-B832-4807-BA9A-9206BD35A650}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F6C0671C-B832-4807-BA9A-9206BD35A650}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3267DFC6-B34D-4011-BC0F-D3B56AF6F608}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU
|
||||
{3267DFC6-B34D-4011-BC0F-D3B56AF6F608}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU
|
||||
{3267DFC6-B34D-4011-BC0F-D3B56AF6F608}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
@ -304,21 +266,6 @@ Global
|
|||
{EA62944F-BD25-4730-9405-9BE8FF5BEACD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EA62944F-BD25-4730-9405-9BE8FF5BEACD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EA62944F-BD25-4730-9405-9BE8FF5BEACD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B6895A1B-382F-4A69-99EC-E965E19B0AB3}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU
|
||||
{B6895A1B-382F-4A69-99EC-E965E19B0AB3}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU
|
||||
{B6895A1B-382F-4A69-99EC-E965E19B0AB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B6895A1B-382F-4A69-99EC-E965E19B0AB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B6895A1B-382F-4A69-99EC-E965E19B0AB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B6895A1B-382F-4A69-99EC-E965E19B0AB3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1ACEF677-B6A0-4680-A076-7893DE176D6B}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU
|
||||
{1ACEF677-B6A0-4680-A076-7893DE176D6B}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU
|
||||
{1ACEF677-B6A0-4680-A076-7893DE176D6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1ACEF677-B6A0-4680-A076-7893DE176D6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1ACEF677-B6A0-4680-A076-7893DE176D6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1ACEF677-B6A0-4680-A076-7893DE176D6B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7B8601F8-8D1F-4B9C-8C20-772B673A2FA6}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU
|
||||
{7B8601F8-8D1F-4B9C-8C20-772B673A2FA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7B8601F8-8D1F-4B9C-8C20-772B673A2FA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4CBFC7D3-1600-4CE5-BC6B-AC7BC2D6F853}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU
|
||||
{4CBFC7D3-1600-4CE5-BC6B-AC7BC2D6F853}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU
|
||||
{4CBFC7D3-1600-4CE5-BC6B-AC7BC2D6F853}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
@ -357,10 +304,6 @@ Global
|
|||
{668E9021-CE84-49D9-98FB-DF125A9FCDB0} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}
|
||||
{66492E69-CE4C-4FB1-9B1F-88DEE09D06F1} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}
|
||||
{A0187BC2-8325-4BB2-8697-7F955CF4173E} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}
|
||||
{ACE91549-D86E-4EB6-8C2A-5FF51386BB68} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}
|
||||
{653F3946-541C-42D3-BBC1-CE89B392BDA9} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}
|
||||
{B6895A1B-382F-4A69-99EC-E965E19B0AB3} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}
|
||||
{1ACEF677-B6A0-4680-A076-7893DE176D6B} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}
|
||||
{4CBFC7D3-1600-4CE5-BC6B-AC7BC2D6F853} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}
|
||||
{0BB62A1D-E6B5-49FA-9E3C-6AF679A66DFE} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
|
||||
{268DEE9D-F323-4A00-8ED8-3784388C3E3A} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
|
||||
|
@ -376,11 +319,8 @@ Global
|
|||
{7F2C796F-43B2-4F8F-ABFF-A154EC8AAFA1} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
|
||||
{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
|
||||
{7AF77741-9158-4D5F-8782-8F21FADF025F} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
|
||||
{81876811-6C36-492A-9609-F0E85990FBC9} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
|
||||
{F6C0671C-B832-4807-BA9A-9206BD35A650} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
|
||||
{3267DFC6-B34D-4011-BC0F-D3B56AF6F608} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
|
||||
{EA62944F-BD25-4730-9405-9BE8FF5BEACD} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
|
||||
{7B8601F8-8D1F-4B9C-8C20-772B673A2FA6} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
|
||||
{694C6EDF-EA52-438F-B745-82B025ECC0E7} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
|
||||
{7F29EE87-6A63-43C6-B7FF-74DD06815830} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
|
||||
EndGlobalSection
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Data.Entity;
|
||||
using System.Data.Entity.Infrastructure;
|
||||
using System.Data.Objects;
|
||||
using System.Web.Http;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// DbContext extension methods
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static class DbContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension method used to attach the specified entity as modified,
|
||||
/// with the specified original state.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">The entity Type</typeparam>
|
||||
/// <param name="dbSet">The <see cref="DbSet"/> to attach to.</param>
|
||||
/// <param name="current">The current entity.</param>
|
||||
/// <param name="original">The original entity.</param>
|
||||
/// <param name="dbContext">The corresponding <see cref="DbContext"/></param>
|
||||
public static void AttachAsModified<TEntity>(this DbSet<TEntity> dbSet, TEntity current, TEntity original, DbContext dbContext) where TEntity : class
|
||||
{
|
||||
if (dbSet == null)
|
||||
{
|
||||
throw Error.ArgumentNull("dbSet");
|
||||
}
|
||||
if (current == null)
|
||||
{
|
||||
throw Error.ArgumentNull("current");
|
||||
}
|
||||
if (original == null)
|
||||
{
|
||||
throw Error.ArgumentNull("original");
|
||||
}
|
||||
if (dbContext == null)
|
||||
{
|
||||
throw Error.ArgumentNull("dbContext");
|
||||
}
|
||||
|
||||
DbEntityEntry<TEntity> entityEntry = dbContext.Entry(current);
|
||||
if (entityEntry.State == EntityState.Detached)
|
||||
{
|
||||
dbSet.Attach(current);
|
||||
}
|
||||
else
|
||||
{
|
||||
entityEntry.State = EntityState.Modified;
|
||||
}
|
||||
|
||||
ObjectContext objectContext = (dbContext as IObjectContextAdapter).ObjectContext;
|
||||
ObjectStateEntry stateEntry = ObjectContextUtilities.AttachAsModifiedInternal<TEntity>(current, original, objectContext);
|
||||
|
||||
if (stateEntry.State != EntityState.Modified)
|
||||
{
|
||||
// Ensure that when we leave this method, the entity is in a
|
||||
// Modified state. For example, if current and original are the
|
||||
// same, we still need to force the state transition
|
||||
entityEntry.State = EntityState.Modified;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method used to attach the specified entity as modified,
|
||||
/// with the specified original state. This is a non-generic version.
|
||||
/// </summary>
|
||||
/// <param name="dbSet">The <see cref="DbSet"/> to attach to.</param>
|
||||
/// <param name="current">The current entity.</param>
|
||||
/// <param name="original">The original entity.</param>
|
||||
/// <param name="dbContext">The corresponding <see cref="DbContext"/></param>
|
||||
public static void AttachAsModified(this DbSet dbSet, object current, object original, DbContext dbContext)
|
||||
{
|
||||
if (dbSet == null)
|
||||
{
|
||||
throw Error.ArgumentNull("dbSet");
|
||||
}
|
||||
if (current == null)
|
||||
{
|
||||
throw Error.ArgumentNull("current");
|
||||
}
|
||||
if (original == null)
|
||||
{
|
||||
throw Error.ArgumentNull("original");
|
||||
}
|
||||
if (dbContext == null)
|
||||
{
|
||||
throw Error.ArgumentNull("dbContext");
|
||||
}
|
||||
|
||||
DbEntityEntry entityEntry = dbContext.Entry(current);
|
||||
if (entityEntry.State == EntityState.Detached)
|
||||
{
|
||||
dbSet.Attach(current);
|
||||
}
|
||||
else
|
||||
{
|
||||
entityEntry.State = EntityState.Modified;
|
||||
}
|
||||
|
||||
ObjectContext objectContext = (dbContext as IObjectContextAdapter).ObjectContext;
|
||||
ObjectStateEntry stateEntry = ObjectContextUtilities.AttachAsModifiedInternal(current, original, objectContext);
|
||||
|
||||
if (stateEntry.State != EntityState.Modified)
|
||||
{
|
||||
// Ensure that when we leave this method, the entity is in a
|
||||
// Modified state. For example, if current and original are the
|
||||
// same, we still need to force the state transition
|
||||
entityEntry.State = EntityState.Modified;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method used to attach the specified entity as modified. This overload
|
||||
/// can be used in cases where the entity has a Timestamp member.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">The entity type</typeparam>
|
||||
/// <param name="dbSet">The <see cref="DbSet"/> to attach to</param>
|
||||
/// <param name="entity">The current entity</param>
|
||||
/// <param name="dbContext">The coresponding <see cref="DbContext"/></param>
|
||||
public static void AttachAsModified<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, DbContext dbContext) where TEntity : class
|
||||
{
|
||||
if (dbSet == null)
|
||||
{
|
||||
throw Error.ArgumentNull("dbSet");
|
||||
}
|
||||
if (entity == null)
|
||||
{
|
||||
throw Error.ArgumentNull("entity");
|
||||
}
|
||||
if (dbContext == null)
|
||||
{
|
||||
throw Error.ArgumentNull("dbContext");
|
||||
}
|
||||
|
||||
DbEntityEntry<TEntity> entityEntry = dbContext.Entry(entity);
|
||||
if (entityEntry.State == EntityState.Detached)
|
||||
{
|
||||
// attach the entity
|
||||
dbSet.Attach(entity);
|
||||
}
|
||||
|
||||
// transition the entity to the modified state
|
||||
entityEntry.State = EntityState.Modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method used to attach the specified entity as modified. This overload
|
||||
/// can be used in cases where the entity has a Timestamp member. This is a non-generic version
|
||||
/// </summary>
|
||||
/// <param name="dbSet">The <see cref="DbSet"/> to attach to</param>
|
||||
/// <param name="entity">The current entity</param>
|
||||
/// <param name="dbContext">The coresponding <see cref="DbContext"/></param>
|
||||
public static void AttachAsModified(this DbSet dbSet, object entity, DbContext dbContext)
|
||||
{
|
||||
if (dbSet == null)
|
||||
{
|
||||
throw Error.ArgumentNull("dbSet");
|
||||
}
|
||||
if (entity == null)
|
||||
{
|
||||
throw Error.ArgumentNull("entity");
|
||||
}
|
||||
if (dbContext == null)
|
||||
{
|
||||
throw Error.ArgumentNull("dbContext");
|
||||
}
|
||||
|
||||
DbEntityEntry entityEntry = dbContext.Entry(entity);
|
||||
if (entityEntry.State == EntityState.Detached)
|
||||
{
|
||||
// attach the entity
|
||||
dbSet.Attach(entity);
|
||||
}
|
||||
|
||||
// transition the entity to the modified state
|
||||
entityEntry.State = EntityState.Modified;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,322 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Data.Entity;
|
||||
using System.Data.Entity.Infrastructure;
|
||||
using System.Data.Objects;
|
||||
using System.Linq;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using Microsoft.Web.Http.Data.EntityFramework.Metadata;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework
|
||||
{
|
||||
[DbMetadataProvider]
|
||||
public abstract class DbDataController<TContext> : DataController
|
||||
where TContext : DbContext, new()
|
||||
{
|
||||
private TContext _dbContext;
|
||||
private ObjectContext _refreshContext;
|
||||
|
||||
/// <summary>
|
||||
/// Protected constructor for the abstract class.
|
||||
/// </summary>
|
||||
protected DbDataController()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ObjectContext"/> used for retrieving store values
|
||||
/// </summary>
|
||||
private ObjectContext RefreshContext
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_refreshContext == null)
|
||||
{
|
||||
DbContext dbContext = CreateDbContext();
|
||||
_refreshContext = (dbContext as IObjectContextAdapter).ObjectContext;
|
||||
}
|
||||
return _refreshContext;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="DbContext"/>
|
||||
/// </summary>
|
||||
protected TContext DbContext
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_dbContext == null)
|
||||
{
|
||||
_dbContext = CreateDbContext();
|
||||
}
|
||||
return _dbContext;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="DbDataController{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="controllerContext">The <see cref="HttpControllerContext"/> for this <see cref="DataController"/>
|
||||
/// instance. Overrides must call the base method.</param>
|
||||
protected override void Initialize(HttpControllerContext controllerContext)
|
||||
{
|
||||
base.Initialize(controllerContext);
|
||||
|
||||
ObjectContext objectContext = ((IObjectContextAdapter)DbContext).ObjectContext;
|
||||
// We turn this off, since our deserializer isn't going to create
|
||||
// the EF proxy types anyways. Proxies only really work if the entities
|
||||
// are queried on the server.
|
||||
objectContext.ContextOptions.ProxyCreationEnabled = false;
|
||||
|
||||
// Turn off DbContext validation.
|
||||
DbContext.Configuration.ValidateOnSaveEnabled = false;
|
||||
|
||||
// Turn off AutoDetectChanges.
|
||||
DbContext.Configuration.AutoDetectChangesEnabled = false;
|
||||
|
||||
DbContext.Configuration.LazyLoadingEnabled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the DbContext object.
|
||||
/// </summary>
|
||||
/// <returns>The created DbContext object.</returns>
|
||||
protected virtual TContext CreateDbContext()
|
||||
{
|
||||
return new TContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is called to finalize changes after all the operations in the specified changeset
|
||||
/// have been invoked. All changes are committed to the DbContext, and any resulting optimistic
|
||||
/// concurrency errors are processed.
|
||||
/// </summary>
|
||||
/// <returns><c>True</c> if the <see cref="ChangeSet"/> was persisted successfully, <c>false</c> otherwise.</returns>
|
||||
protected override bool PersistChangeSet()
|
||||
{
|
||||
return InvokeSaveChanges(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is called to finalize changes after all the operations in the specified changeset
|
||||
/// have been invoked. All changes are committed to the DbContext.
|
||||
/// <remarks>If the submit fails due to concurrency conflicts <see cref="ResolveConflicts"/> will be called.
|
||||
/// If <see cref="ResolveConflicts"/> returns true a single resubmit will be attempted.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="conflicts">The list of concurrency conflicts that occurred</param>
|
||||
/// <returns>Returns <c>true</c> if the <see cref="ChangeSet"/> was persisted successfully, <c>false</c> otherwise.</returns>
|
||||
protected virtual bool ResolveConflicts(IEnumerable<DbEntityEntry> conflicts)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="IDisposable"/>.
|
||||
/// </summary>
|
||||
/// <param name="disposing">A <see cref="Boolean"/> indicating whether or not the instance is currently disposing.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (DbContext != null)
|
||||
{
|
||||
DbContext.Dispose();
|
||||
}
|
||||
if (_refreshContext != null)
|
||||
{
|
||||
_refreshContext.Dispose();
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by PersistChangeSet method to save the changes to the database.
|
||||
/// </summary>
|
||||
/// <param name="retryOnConflict">Flag indicating whether to retry after resolving conflicts.</param>
|
||||
/// <returns><c>true</c> if saved successfully and <c>false</c> otherwise.</returns>
|
||||
private bool InvokeSaveChanges(bool retryOnConflict)
|
||||
{
|
||||
try
|
||||
{
|
||||
DbContext.SaveChanges();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException ex)
|
||||
{
|
||||
// Map the operations that could have caused a conflict to an entity.
|
||||
Dictionary<DbEntityEntry, ChangeSetEntry> operationConflictMap = new Dictionary<DbEntityEntry, ChangeSetEntry>();
|
||||
foreach (DbEntityEntry conflict in ex.Entries)
|
||||
{
|
||||
ChangeSetEntry entry = ChangeSet.ChangeSetEntries.SingleOrDefault(p => Object.ReferenceEquals(p.Entity, conflict.Entity));
|
||||
if (entry == null)
|
||||
{
|
||||
// If we're unable to find the object in our changeset, propagate
|
||||
// the original exception
|
||||
throw;
|
||||
}
|
||||
operationConflictMap.Add(conflict, entry);
|
||||
}
|
||||
|
||||
SetChangeSetConflicts(operationConflictMap);
|
||||
|
||||
// Call out to any user resolve code and resubmit if all conflicts
|
||||
// were resolved
|
||||
if (retryOnConflict && ResolveConflicts(ex.Entries))
|
||||
{
|
||||
// clear the conflics from the entries
|
||||
foreach (ChangeSetEntry entry in ChangeSet.ChangeSetEntries)
|
||||
{
|
||||
entry.StoreEntity = null;
|
||||
entry.ConflictMembers = null;
|
||||
entry.IsDeleteConflict = false;
|
||||
}
|
||||
|
||||
// If all conflicts were resolved attempt a resubmit
|
||||
return InvokeSaveChanges(retryOnConflict: false);
|
||||
}
|
||||
|
||||
// if there was a conflict but no conflict information was
|
||||
// extracted to the individual entries, we need to ensure the
|
||||
// error makes it back to the client
|
||||
if (!ChangeSet.HasError)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates each entry in the ChangeSet with its corresponding conflict info.
|
||||
/// </summary>
|
||||
/// <param name="operationConflictMap">Map of conflicts to their corresponding operations entries.</param>
|
||||
private void SetChangeSetConflicts(Dictionary<DbEntityEntry, ChangeSetEntry> operationConflictMap)
|
||||
{
|
||||
object storeValue;
|
||||
EntityKey refreshEntityKey;
|
||||
|
||||
ObjectContext objectContext = ((IObjectContextAdapter)DbContext).ObjectContext;
|
||||
ObjectStateManager objectStateManager = objectContext.ObjectStateManager;
|
||||
if (objectStateManager == null)
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.ObjectStateManagerNotFoundException, DbContext.GetType().Name);
|
||||
}
|
||||
|
||||
foreach (var conflictEntry in operationConflictMap)
|
||||
{
|
||||
DbEntityEntry entityEntry = conflictEntry.Key;
|
||||
ObjectStateEntry stateEntry = objectStateManager.GetObjectStateEntry(entityEntry.Entity);
|
||||
|
||||
if (stateEntry.State == EntityState.Unchanged)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Note: we cannot call Refresh StoreWins since this will overwrite Current entity and remove the optimistic concurrency ex.
|
||||
ChangeSetEntry operationInConflict = conflictEntry.Value;
|
||||
refreshEntityKey = RefreshContext.CreateEntityKey(stateEntry.EntitySet.Name, stateEntry.Entity);
|
||||
RefreshContext.TryGetObjectByKey(refreshEntityKey, out storeValue);
|
||||
operationInConflict.StoreEntity = storeValue;
|
||||
|
||||
// StoreEntity will be null if the entity has been deleted in the store (i.e. Delete/Delete conflict)
|
||||
bool isDeleted = (operationInConflict.StoreEntity == null);
|
||||
if (isDeleted)
|
||||
{
|
||||
operationInConflict.IsDeleteConflict = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Determine which members are in conflict by comparing original values to the current DB values
|
||||
PropertyDescriptorCollection propDescriptors = TypeDescriptor.GetProperties(operationInConflict.Entity.GetType());
|
||||
List<string> membersInConflict = new List<string>();
|
||||
object originalValue;
|
||||
PropertyDescriptor pd;
|
||||
for (int i = 0; i < stateEntry.OriginalValues.FieldCount; i++)
|
||||
{
|
||||
originalValue = stateEntry.OriginalValues.GetValue(i);
|
||||
if (originalValue is DBNull)
|
||||
{
|
||||
originalValue = null;
|
||||
}
|
||||
|
||||
string propertyName = stateEntry.OriginalValues.GetName(i);
|
||||
pd = propDescriptors[propertyName];
|
||||
if (pd == null)
|
||||
{
|
||||
// This might happen in the case of a private model
|
||||
// member that isn't mapped
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Object.Equals(originalValue, pd.GetValue(operationInConflict.StoreEntity)))
|
||||
{
|
||||
membersInConflict.Add(pd.Name);
|
||||
}
|
||||
}
|
||||
operationInConflict.ConflictMembers = membersInConflict;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert an entity into the <see cref="DbContext" />, ensuring its <see cref="EntityState" /> is <see cref="EntityState.Added" />
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to be inserted</param>
|
||||
protected virtual void InsertEntity(object entity)
|
||||
{
|
||||
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
|
||||
if (dbEntityEntry.State != EntityState.Detached)
|
||||
{
|
||||
dbEntityEntry.State = EntityState.Added;
|
||||
}
|
||||
else
|
||||
{
|
||||
DbContext.Set(entity.GetType()).Add(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update an entity in the <see cref="DbContext" />, ensuring it is treated as a modified entity
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to be updated</param>
|
||||
protected virtual void UpdateEntity(object entity)
|
||||
{
|
||||
object original = ChangeSet.GetOriginal(entity);
|
||||
DbSet dbSet = DbContext.Set(entity.GetType());
|
||||
if (original == null)
|
||||
{
|
||||
dbSet.AttachAsModified(entity, DbContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
dbSet.AttachAsModified(entity, original, DbContext);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete an entity from the <see cref="DbContext" />, ensuring that its <see cref="EntityState" /> is <see cref="EntityState.Deleted" />
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to be deleted</param>
|
||||
protected virtual void DeleteEntity(object entity)
|
||||
{
|
||||
DbEntityEntry entityEntry = DbContext.Entry(entity);
|
||||
if (entityEntry.State != EntityState.Deleted)
|
||||
{
|
||||
entityEntry.State = EntityState.Deleted;
|
||||
}
|
||||
else
|
||||
{
|
||||
DbContext.Set(entity.GetType()).Attach(entity);
|
||||
DbContext.Set(entity.GetType()).Remove(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
//
|
||||
// To add a suppression to this file, right-click the message in the
|
||||
// Error List, point to "Suppress Message(s)", and click
|
||||
// "In Project Suppression File".
|
||||
// You do not need to add suppressions to this file manually.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Assembly is delay signed")]
|
||||
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Web.Http.Data.EntityFramework.Metadata", Justification = "These types are in their own namespace to match folder structure.")]
|
|
@ -1,312 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Data.Objects;
|
||||
using System.Linq;
|
||||
using System.Web.Http.Controllers;
|
||||
using Microsoft.Web.Http.Data.EntityFramework.Metadata;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for DataControllers operating on LINQ To Entities data models
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">The Type of the LINQ To Entities ObjectContext</typeparam>
|
||||
[LinqToEntitiesMetadataProvider]
|
||||
public abstract class LinqToEntitiesDataController<TContext> : DataController where TContext : ObjectContext, new()
|
||||
{
|
||||
private TContext _objectContext;
|
||||
private TContext _refreshContext;
|
||||
|
||||
/// <summary>
|
||||
/// Protected constructor because this is an abstract class
|
||||
/// </summary>
|
||||
protected LinqToEntitiesDataController()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ObjectContext"/>
|
||||
/// </summary>
|
||||
protected internal TContext ObjectContext
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_objectContext == null)
|
||||
{
|
||||
_objectContext = CreateObjectContext();
|
||||
}
|
||||
return _objectContext;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ObjectContext"/> used by retrieving store values
|
||||
/// </summary>
|
||||
private ObjectContext RefreshContext
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_refreshContext == null)
|
||||
{
|
||||
_refreshContext = CreateObjectContext();
|
||||
}
|
||||
return _refreshContext;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this <see cref="DataController"/>.
|
||||
/// </summary>
|
||||
/// <param name="controllerContext">The <see cref="HttpControllerContext"/> for this <see cref="DataController"/>
|
||||
/// instance. Overrides must call the base method.</param>
|
||||
protected override void Initialize(HttpControllerContext controllerContext)
|
||||
{
|
||||
base.Initialize(controllerContext);
|
||||
|
||||
// TODO: should we be turning this off categorically? Can we do this only
|
||||
// for queries?
|
||||
ObjectContext.ContextOptions.LazyLoadingEnabled = false;
|
||||
|
||||
// We turn this off, since our deserializer isn't going to create
|
||||
// the EF proxy types anyways. Proxies only really work if the entities
|
||||
// are queried on the server.
|
||||
ObjectContext.ContextOptions.ProxyCreationEnabled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns the <see cref="ObjectContext"/> instance that will
|
||||
/// be used by this provider.
|
||||
/// </summary>
|
||||
/// <returns>The ObjectContext</returns>
|
||||
protected virtual TContext CreateObjectContext()
|
||||
{
|
||||
return new TContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="IDisposable"/>.
|
||||
/// </summary>
|
||||
/// <param name="disposing">A <see cref="Boolean"/> indicating whether or not the instance is currently disposing.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (_objectContext != null)
|
||||
{
|
||||
_objectContext.Dispose();
|
||||
}
|
||||
if (_refreshContext != null)
|
||||
{
|
||||
_refreshContext.Dispose();
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is called to finalize changes after all the operations in the specified changeset
|
||||
/// have been invoked. All changes are committed to the ObjectContext, and any resulting optimistic
|
||||
/// concurrency errors are processed.
|
||||
/// </summary>
|
||||
/// <returns>True if the <see cref="ChangeSet"/> was persisted successfully, false otherwise.</returns>
|
||||
protected override bool PersistChangeSet()
|
||||
{
|
||||
return InvokeSaveChanges(true);
|
||||
}
|
||||
|
||||
private bool InvokeSaveChanges(bool retryOnConflict)
|
||||
{
|
||||
try
|
||||
{
|
||||
ObjectContext.SaveChanges();
|
||||
}
|
||||
catch (OptimisticConcurrencyException ex)
|
||||
{
|
||||
// Map the operations that could have caused a conflict to an entity.
|
||||
Dictionary<ObjectStateEntry, ChangeSetEntry> operationConflictMap = new Dictionary<ObjectStateEntry, ChangeSetEntry>();
|
||||
foreach (ObjectStateEntry conflict in ex.StateEntries)
|
||||
{
|
||||
ChangeSetEntry entry = ChangeSet.ChangeSetEntries.SingleOrDefault(p => Object.ReferenceEquals(p.Entity, conflict.Entity));
|
||||
if (entry == null)
|
||||
{
|
||||
// If we're unable to find the object in our changeset, propagate
|
||||
// the original exception
|
||||
throw;
|
||||
}
|
||||
operationConflictMap.Add(conflict, entry);
|
||||
}
|
||||
|
||||
SetChangeSetConflicts(operationConflictMap);
|
||||
|
||||
// Call out to any user resolve code and resubmit if all conflicts
|
||||
// were resolved
|
||||
if (retryOnConflict && ResolveConflicts(ex.StateEntries))
|
||||
{
|
||||
// clear the conflics from the entries
|
||||
foreach (ChangeSetEntry entry in ChangeSet.ChangeSetEntries)
|
||||
{
|
||||
entry.StoreEntity = null;
|
||||
entry.ConflictMembers = null;
|
||||
entry.IsDeleteConflict = false;
|
||||
}
|
||||
|
||||
// If all conflicts were resolved attempt a resubmit
|
||||
return InvokeSaveChanges(retryOnConflict: false);
|
||||
}
|
||||
|
||||
// if there was a conflict but no conflict information was
|
||||
// extracted to the individual entries, we need to ensure the
|
||||
// error makes it back to the client
|
||||
if (!ChangeSet.HasError)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is called to finalize changes after all the operations in the specified changeset
|
||||
/// have been invoked. All changes are committed to the ObjectContext.
|
||||
/// <remarks>If the submit fails due to concurrency conflicts <see cref="ResolveConflicts"/> will be called.
|
||||
/// If <see cref="ResolveConflicts"/> returns true a single resubmit will be attempted.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="conflicts">The list of concurrency conflicts that occurred</param>
|
||||
/// <returns>Returns <c>true</c> if the <see cref="ChangeSet"/> was persisted successfully, <c>false</c> otherwise.</returns>
|
||||
protected virtual bool ResolveConflicts(IEnumerable<ObjectStateEntry> conflicts)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert an entity into the <see cref="ObjectContext" />, ensuring its <see cref="EntityState" /> is <see cref="EntityState.Added" />
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">The entity type</typeparam>
|
||||
/// <param name="entity">The entity to be inserted</param>
|
||||
protected virtual void InsertEntity<TEntity>(TEntity entity) where TEntity : class
|
||||
{
|
||||
ObjectStateEntry stateEntry;
|
||||
if (ObjectContext.ObjectStateManager.TryGetObjectStateEntry(entity, out stateEntry) &&
|
||||
stateEntry.State != EntityState.Added)
|
||||
{
|
||||
ObjectContext.ObjectStateManager.ChangeObjectState(entity, EntityState.Added);
|
||||
}
|
||||
else
|
||||
{
|
||||
ObjectContext.CreateObjectSet<TEntity>().AddObject(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update an entity in the <see cref="ObjectContext" />, ensuring it is treated as a modified entity
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">The entity type</typeparam>
|
||||
/// <param name="entity">The entity to be updated</param>
|
||||
protected virtual void UpdateEntity<TEntity>(TEntity entity) where TEntity : class
|
||||
{
|
||||
TEntity original = ChangeSet.GetOriginal(entity);
|
||||
ObjectSet<TEntity> objectSet = ObjectContext.CreateObjectSet<TEntity>();
|
||||
if (original == null)
|
||||
{
|
||||
objectSet.AttachAsModified(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
objectSet.AttachAsModified(entity, original);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete an entity from the <see cref="ObjectContext" />, ensuring that its <see cref="EntityState" /> is <see cref="EntityState.Deleted" />
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">The entity type</typeparam>
|
||||
/// <param name="entity">The entity to be deleted</param>
|
||||
protected virtual void DeleteEntity<TEntity>(TEntity entity) where TEntity : class
|
||||
{
|
||||
ObjectStateEntry stateEntry;
|
||||
if (ObjectContext.ObjectStateManager.TryGetObjectStateEntry(entity, out stateEntry) &&
|
||||
stateEntry.State != EntityState.Deleted)
|
||||
{
|
||||
ObjectContext.ObjectStateManager.ChangeObjectState(entity, EntityState.Deleted);
|
||||
}
|
||||
else
|
||||
{
|
||||
ObjectSet<TEntity> objectSet = ObjectContext.CreateObjectSet<TEntity>();
|
||||
objectSet.Attach(entity);
|
||||
objectSet.DeleteObject(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates each entry in the ChangeSet with its corresponding conflict info.
|
||||
/// </summary>
|
||||
/// <param name="operationConflictMap">Map of conflicts to their corresponding operations entries.</param>
|
||||
private void SetChangeSetConflicts(Dictionary<ObjectStateEntry, ChangeSetEntry> operationConflictMap)
|
||||
{
|
||||
object storeValue;
|
||||
EntityKey refreshEntityKey;
|
||||
|
||||
foreach (var conflictEntry in operationConflictMap)
|
||||
{
|
||||
ObjectStateEntry stateEntry = conflictEntry.Key;
|
||||
|
||||
if (stateEntry.State == EntityState.Unchanged)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Note: we cannot call Refresh StoreWins since this will overwrite Current entity and remove the optimistic concurrency ex.
|
||||
ChangeSetEntry operationInConflict = conflictEntry.Value;
|
||||
refreshEntityKey = RefreshContext.CreateEntityKey(stateEntry.EntitySet.Name, stateEntry.Entity);
|
||||
RefreshContext.TryGetObjectByKey(refreshEntityKey, out storeValue);
|
||||
operationInConflict.StoreEntity = storeValue;
|
||||
|
||||
// StoreEntity will be null if the entity has been deleted in the store (i.e. Delete/Delete conflict)
|
||||
bool isDeleted = (operationInConflict.StoreEntity == null);
|
||||
if (isDeleted)
|
||||
{
|
||||
operationInConflict.IsDeleteConflict = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Determine which members are in conflict by comparing original values to the current DB values
|
||||
PropertyDescriptorCollection propDescriptors = TypeDescriptor.GetProperties(operationInConflict.Entity.GetType());
|
||||
List<string> membersInConflict = new List<string>();
|
||||
object originalValue;
|
||||
PropertyDescriptor pd;
|
||||
for (int i = 0; i < stateEntry.OriginalValues.FieldCount; i++)
|
||||
{
|
||||
originalValue = stateEntry.OriginalValues.GetValue(i);
|
||||
if (originalValue is DBNull)
|
||||
{
|
||||
originalValue = null;
|
||||
}
|
||||
|
||||
string propertyName = stateEntry.OriginalValues.GetName(i);
|
||||
pd = propDescriptors[propertyName];
|
||||
if (pd == null)
|
||||
{
|
||||
// This might happen in the case of a private model
|
||||
// member that isn't mapped
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Object.Equals(originalValue, pd.GetValue(operationInConflict.StoreEntity)))
|
||||
{
|
||||
membersInConflict.Add(pd.Name);
|
||||
}
|
||||
}
|
||||
operationInConflict.ConflictMembers = membersInConflict;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about an Association
|
||||
/// </summary>
|
||||
internal sealed class AssociationInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the association
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The key members on the FK side of the association
|
||||
/// </summary>
|
||||
public string[] ThisKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The key members on the non-FK side of the association
|
||||
/// </summary>
|
||||
public string[] OtherKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The foreign key role name for this association
|
||||
/// </summary>
|
||||
public string FKRole { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this association can have a
|
||||
/// multiplicity of zero
|
||||
/// </summary>
|
||||
public bool IsRequired { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Data.Entity;
|
||||
using System.Web.Http;
|
||||
using Microsoft.Web.Http.Data.Metadata;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute applied to a <see cref="DbDataController{DbContext}"/> that exposes LINQ to Entities mapped
|
||||
/// Types.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public sealed class DbMetadataProviderAttribute : MetadataProviderAttribute
|
||||
{
|
||||
private Type _dbContextType;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor. Using this constructor, the Type of the LINQ To Entities
|
||||
/// DbContext will be inferred from the <see cref="DataController"/> the
|
||||
/// attribute is applied to.
|
||||
/// </summary>
|
||||
public DbMetadataProviderAttribute()
|
||||
: base(typeof(LinqToEntitiesMetadataProvider))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an attribute for the specified LINQ To Entities
|
||||
/// DbContext Type.
|
||||
/// </summary>
|
||||
/// <param name="dbContextType">The LINQ To Entities ObjectContext Type.</param>
|
||||
public DbMetadataProviderAttribute(Type dbContextType)
|
||||
: base(typeof(LinqToEntitiesMetadataProvider))
|
||||
{
|
||||
_dbContextType = dbContextType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Linq To Entities DbContext Type.
|
||||
/// </summary>
|
||||
public Type DbContextType
|
||||
{
|
||||
get { return _dbContextType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method creates an instance of the <see cref="MetadataProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="controllerType">The <see cref="DataController"/> Type to create a metadata provider for.</param>
|
||||
/// <param name="parent">The existing parent metadata provider.</param>
|
||||
/// <returns>The metadata provider.</returns>
|
||||
public override MetadataProvider CreateProvider(Type controllerType, MetadataProvider parent)
|
||||
{
|
||||
if (controllerType == null)
|
||||
{
|
||||
throw Error.ArgumentNull("controllerType");
|
||||
}
|
||||
|
||||
if (_dbContextType == null)
|
||||
{
|
||||
_dbContextType = GetContextType(controllerType);
|
||||
}
|
||||
|
||||
if (!typeof(DbContext).IsAssignableFrom(_dbContextType))
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.InvalidDbMetadataProviderSpecification, _dbContextType);
|
||||
}
|
||||
|
||||
return new LinqToEntitiesMetadataProvider(_dbContextType, parent, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the context type from the specified <paramref name="dataControllerType"/>.
|
||||
/// </summary>
|
||||
/// <param name="dataControllerType">A LINQ to Entities data controller type.</param>
|
||||
/// <returns>The type of the object context.</returns>
|
||||
private static Type GetContextType(Type dataControllerType)
|
||||
{
|
||||
Type efDataControllerType = dataControllerType.BaseType;
|
||||
while (!efDataControllerType.IsGenericType || efDataControllerType.GetGenericTypeDefinition() != typeof(DbDataController<>))
|
||||
{
|
||||
if (efDataControllerType == typeof(object))
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.InvalidMetadataProviderSpecification, typeof(DbMetadataProviderAttribute).Name, dataControllerType.Name, typeof(DbDataController<>).Name);
|
||||
}
|
||||
efDataControllerType = efDataControllerType.BaseType;
|
||||
}
|
||||
|
||||
return efDataControllerType.GetGenericArguments()[0];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data.Metadata.Edm;
|
||||
using Microsoft.Web.Http.Data.Metadata;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
|
||||
{
|
||||
internal class LinqToEntitiesMetadataProvider : MetadataProvider
|
||||
{
|
||||
private static ConcurrentDictionary<Type, LinqToEntitiesTypeDescriptionContext> _tdpContextMap = new ConcurrentDictionary<Type, LinqToEntitiesTypeDescriptionContext>();
|
||||
private readonly LinqToEntitiesTypeDescriptionContext _typeDescriptionContext;
|
||||
private readonly bool _isDbContext;
|
||||
private Dictionary<Type, ICustomTypeDescriptor> _descriptors = new Dictionary<Type, ICustomTypeDescriptor>();
|
||||
|
||||
public LinqToEntitiesMetadataProvider(Type contextType, MetadataProvider parent, bool isDbContext)
|
||||
: base(parent)
|
||||
{
|
||||
_isDbContext = isDbContext;
|
||||
|
||||
_typeDescriptionContext = _tdpContextMap.GetOrAdd(contextType, type =>
|
||||
{
|
||||
// create and cache a context for this provider type
|
||||
return new LinqToEntitiesTypeDescriptionContext(contextType, _isDbContext);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a custom type descriptor for the specified type (either an entity or complex type).
|
||||
/// </summary>
|
||||
/// <param name="objectType">Type of object for which we need the descriptor</param>
|
||||
/// <param name="parent">The parent type descriptor</param>
|
||||
/// <returns>Custom type description for the specified type</returns>
|
||||
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, ICustomTypeDescriptor parent)
|
||||
{
|
||||
// No need to deal with concurrency... Worst case scenario we have multiple
|
||||
// instances of this thing.
|
||||
ICustomTypeDescriptor td = null;
|
||||
if (!_descriptors.TryGetValue(objectType, out td))
|
||||
{
|
||||
// call into base so the TDs are chained
|
||||
parent = base.GetTypeDescriptor(objectType, parent);
|
||||
|
||||
StructuralType edmType = _typeDescriptionContext.GetEdmType(objectType);
|
||||
if (edmType != null &&
|
||||
(edmType.BuiltInTypeKind == BuiltInTypeKind.EntityType || edmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType))
|
||||
{
|
||||
// only add an LTE TypeDescriptor if the type is an EF Entity or ComplexType
|
||||
td = new LinqToEntitiesTypeDescriptor(_typeDescriptionContext, edmType, parent);
|
||||
}
|
||||
else
|
||||
{
|
||||
td = parent;
|
||||
}
|
||||
|
||||
_descriptors[objectType] = td;
|
||||
}
|
||||
|
||||
return td;
|
||||
}
|
||||
|
||||
public override bool LookUpIsEntityType(Type type)
|
||||
{
|
||||
StructuralType edmType = _typeDescriptionContext.GetEdmType(type);
|
||||
if (edmType != null && edmType.BuiltInTypeKind == BuiltInTypeKind.EntityType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Data.Objects;
|
||||
using System.Web.Http;
|
||||
using Microsoft.Web.Http.Data.Metadata;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute applied to a <see cref="DataController"/> that exposes LINQ to Entities mapped
|
||||
/// Types.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public sealed class LinqToEntitiesMetadataProviderAttribute : MetadataProviderAttribute
|
||||
{
|
||||
private Type _objectContextType;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor. Using this constructor, the Type of the LINQ To Entities
|
||||
/// ObjectContext will be inferred from the <see cref="DataController"/> the
|
||||
/// attribute is applied to.
|
||||
/// </summary>
|
||||
public LinqToEntitiesMetadataProviderAttribute()
|
||||
: base(typeof(LinqToEntitiesMetadataProvider))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an attribute for the specified LINQ To Entities
|
||||
/// ObjectContext Type.
|
||||
/// </summary>
|
||||
/// <param name="objectContextType">The LINQ To Entities ObjectContext Type.</param>
|
||||
public LinqToEntitiesMetadataProviderAttribute(Type objectContextType)
|
||||
: base(typeof(LinqToEntitiesMetadataProvider))
|
||||
{
|
||||
_objectContextType = objectContextType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Linq To Entities ObjectContext Type.
|
||||
/// </summary>
|
||||
public Type ObjectContextType
|
||||
{
|
||||
get { return _objectContextType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method creates an instance of the <see cref="MetadataProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="controllerType">The <see cref="DataController"/> Type to create a metadata provider for.</param>
|
||||
/// <param name="parent">The existing parent metadata provider.</param>
|
||||
/// <returns>The metadata provider.</returns>
|
||||
public override MetadataProvider CreateProvider(Type controllerType, MetadataProvider parent)
|
||||
{
|
||||
if (controllerType == null)
|
||||
{
|
||||
throw Error.ArgumentNull("controllerType");
|
||||
}
|
||||
|
||||
if (_objectContextType == null)
|
||||
{
|
||||
_objectContextType = GetContextType(controllerType);
|
||||
}
|
||||
|
||||
if (!typeof(ObjectContext).IsAssignableFrom(_objectContextType))
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.InvalidLinqToEntitiesMetadataProviderSpecification, _objectContextType);
|
||||
}
|
||||
|
||||
return new LinqToEntitiesMetadataProvider(_objectContextType, parent, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the context type from the specified <paramref name="dataControllerType"/>.
|
||||
/// </summary>
|
||||
/// <param name="dataControllerType">A LINQ to Entities data controller type.</param>
|
||||
/// <returns>The type of the object context.</returns>
|
||||
private static Type GetContextType(Type dataControllerType)
|
||||
{
|
||||
Type efDataControllerType = dataControllerType.BaseType;
|
||||
while (!efDataControllerType.IsGenericType || efDataControllerType.GetGenericTypeDefinition() != typeof(LinqToEntitiesDataController<>))
|
||||
{
|
||||
if (efDataControllerType == typeof(object))
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.InvalidMetadataProviderSpecification, typeof(LinqToEntitiesMetadataProviderAttribute).Name, dataControllerType.Name, typeof(LinqToEntitiesDataController<>).Name);
|
||||
}
|
||||
efDataControllerType = efDataControllerType.BaseType;
|
||||
}
|
||||
|
||||
return efDataControllerType.GetGenericArguments()[0];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Data.Metadata.Edm;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Web.Http;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Metadata context for LINQ To Entities controllers
|
||||
/// </summary>
|
||||
internal class LinqToEntitiesTypeDescriptionContext : TypeDescriptionContextBase
|
||||
{
|
||||
private readonly Type _contextType;
|
||||
private readonly bool _isDbContext;
|
||||
private ConcurrentDictionary<string, AssociationInfo> _associationMap = new ConcurrentDictionary<string, AssociationInfo>();
|
||||
private MetadataWorkspace _metadataWorkspace;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that accepts a LINQ To Entities context type
|
||||
/// </summary>
|
||||
/// <param name="contextType">The ObjectContext Type</param>
|
||||
/// <param name="isDbContext">Set to <c>true</c> if context is a database context.</param>
|
||||
public LinqToEntitiesTypeDescriptionContext(Type contextType, bool isDbContext)
|
||||
{
|
||||
if (contextType == null)
|
||||
{
|
||||
throw Error.ArgumentNull("contextType");
|
||||
}
|
||||
_contextType = contextType;
|
||||
_isDbContext = isDbContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the MetadataWorkspace for the context
|
||||
/// </summary>
|
||||
public MetadataWorkspace MetadataWorkspace
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_metadataWorkspace == null)
|
||||
{
|
||||
// we only support embedded mappings
|
||||
_metadataWorkspace = MetadataWorkspaceUtilities.CreateMetadataWorkspace(_contextType, _isDbContext);
|
||||
}
|
||||
return _metadataWorkspace;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="StructuralType"/> that corresponds to the given CLR type
|
||||
/// </summary>
|
||||
/// <param name="clrType">The CLR type</param>
|
||||
/// <returns>The StructuralType that corresponds to the given CLR type</returns>
|
||||
public StructuralType GetEdmType(Type clrType)
|
||||
{
|
||||
return ObjectContextUtilities.GetEdmType(MetadataWorkspace, clrType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the association information for the specified navigation property.
|
||||
/// </summary>
|
||||
/// <param name="navigationProperty">The navigation property to return association information for</param>
|
||||
/// <returns>The association info</returns>
|
||||
internal AssociationInfo GetAssociationInfo(NavigationProperty navigationProperty)
|
||||
{
|
||||
return _associationMap.GetOrAdd(navigationProperty.RelationshipType.FullName, associationName =>
|
||||
{
|
||||
AssociationType associationType = (AssociationType)navigationProperty.RelationshipType;
|
||||
|
||||
if (!associationType.ReferentialConstraints.Any())
|
||||
{
|
||||
// We only support EF models where FK info is part of the model.
|
||||
throw Error.NotSupported(Resource.LinqToEntitiesProvider_UnableToRetrieveAssociationInfo, associationName);
|
||||
}
|
||||
|
||||
string toRoleName = associationType.ReferentialConstraints[0].ToRole.Name;
|
||||
AssociationInfo associationInfo = new AssociationInfo()
|
||||
{
|
||||
FKRole = toRoleName,
|
||||
Name = GetAssociationName(navigationProperty, toRoleName),
|
||||
ThisKey = associationType.ReferentialConstraints[0].ToProperties.Select(p => p.Name).ToArray(),
|
||||
OtherKey = associationType.ReferentialConstraints[0].FromProperties.Select(p => p.Name).ToArray(),
|
||||
IsRequired = associationType.RelationshipEndMembers[0].RelationshipMultiplicity == RelationshipMultiplicity.One
|
||||
};
|
||||
|
||||
return associationInfo;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an AssociationAttribute for the specified navigation property
|
||||
/// </summary>
|
||||
/// <param name="navigationProperty">The navigation property that corresponds to the association (it identifies the end points)</param>
|
||||
/// <returns>A new AssociationAttribute that describes the given navigation property association</returns>
|
||||
internal AssociationAttribute CreateAssociationAttribute(NavigationProperty navigationProperty)
|
||||
{
|
||||
AssociationInfo assocInfo = GetAssociationInfo(navigationProperty);
|
||||
bool isForeignKey = navigationProperty.FromEndMember.Name == assocInfo.FKRole;
|
||||
string thisKey;
|
||||
string otherKey;
|
||||
if (isForeignKey)
|
||||
{
|
||||
thisKey = String.Join(",", assocInfo.ThisKey);
|
||||
otherKey = String.Join(",", assocInfo.OtherKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
otherKey = String.Join(",", assocInfo.ThisKey);
|
||||
thisKey = String.Join(",", assocInfo.OtherKey);
|
||||
}
|
||||
|
||||
AssociationAttribute assocAttrib = new AssociationAttribute(assocInfo.Name, thisKey, otherKey);
|
||||
assocAttrib.IsForeignKey = isForeignKey;
|
||||
return assocAttrib;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a unique association name for the specified navigation property.
|
||||
/// </summary>
|
||||
/// <param name="navigationProperty">The navigation property</param>
|
||||
/// <param name="foreignKeyRoleName">The foreign key role name for the property's association</param>
|
||||
/// <returns>A unique association name for the specified navigation property.</returns>
|
||||
private string GetAssociationName(NavigationProperty navigationProperty, string foreignKeyRoleName)
|
||||
{
|
||||
RelationshipEndMember fromMember = navigationProperty.FromEndMember;
|
||||
RelationshipEndMember toMember = navigationProperty.ToEndMember;
|
||||
|
||||
RefType toRefType = toMember.TypeUsage.EdmType as RefType;
|
||||
EntityType toEntityType = toRefType.ElementType as EntityType;
|
||||
|
||||
RefType fromRefType = fromMember.TypeUsage.EdmType as RefType;
|
||||
EntityType fromEntityType = fromRefType.ElementType as EntityType;
|
||||
|
||||
bool isForeignKey = navigationProperty.FromEndMember.Name == foreignKeyRoleName;
|
||||
string fromTypeName = isForeignKey ? fromEntityType.Name : toEntityType.Name;
|
||||
string toTypeName = isForeignKey ? toEntityType.Name : fromEntityType.Name;
|
||||
|
||||
// names are always formatted non-FK side type name followed by FK side type name
|
||||
string associationName = String.Format(CultureInfo.InvariantCulture, "{0}_{1}", toTypeName, fromTypeName);
|
||||
associationName = MakeUniqueName(associationName, _associationMap.Values.Select(p => p.Name));
|
||||
|
||||
return associationName;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,274 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Data;
|
||||
using System.Data.Metadata.Edm;
|
||||
using System.Data.Objects.DataClasses;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// CustomTypeDescriptor for LINQ To Entities
|
||||
/// </summary>
|
||||
internal class LinqToEntitiesTypeDescriptor : TypeDescriptorBase
|
||||
{
|
||||
private readonly LinqToEntitiesTypeDescriptionContext _typeDescriptionContext;
|
||||
private readonly StructuralType _edmType;
|
||||
private readonly EdmMember _timestampMember;
|
||||
private readonly HashSet<EdmMember> _foreignKeyMembers;
|
||||
private readonly bool _keyIsEditable;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor taking a metadata context, an structural type, and a parent custom type descriptor
|
||||
/// </summary>
|
||||
/// <param name="typeDescriptionContext">The <see cref="LinqToEntitiesTypeDescriptionContext"/> context.</param>
|
||||
/// <param name="edmType">The <see cref="StructuralType"/> type (can be an entity or complex type).</param>
|
||||
/// <param name="parent">The parent custom type descriptor.</param>
|
||||
public LinqToEntitiesTypeDescriptor(LinqToEntitiesTypeDescriptionContext typeDescriptionContext, StructuralType edmType, ICustomTypeDescriptor parent)
|
||||
: base(parent)
|
||||
{
|
||||
_typeDescriptionContext = typeDescriptionContext;
|
||||
_edmType = edmType;
|
||||
|
||||
EdmMember[] timestampMembers = _edmType.Members.Where(p => ObjectContextUtilities.IsConcurrencyTimestamp(p)).ToArray();
|
||||
if (timestampMembers.Length == 1)
|
||||
{
|
||||
_timestampMember = timestampMembers[0];
|
||||
}
|
||||
|
||||
if (edmType.BuiltInTypeKind == BuiltInTypeKind.EntityType)
|
||||
{
|
||||
// if any FK member of any association is also part of the primary key, then the key cannot be marked
|
||||
// Editable(false)
|
||||
EntityType entityType = (EntityType)edmType;
|
||||
_foreignKeyMembers = new HashSet<EdmMember>(entityType.NavigationProperties.SelectMany(p => p.GetDependentProperties()));
|
||||
foreach (EdmProperty foreignKeyMember in _foreignKeyMembers)
|
||||
{
|
||||
if (entityType.KeyMembers.Contains(foreignKeyMember))
|
||||
{
|
||||
_keyIsEditable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the metadata context
|
||||
/// </summary>
|
||||
public LinqToEntitiesTypeDescriptionContext TypeDescriptionContext
|
||||
{
|
||||
get { return _typeDescriptionContext; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Edm type
|
||||
/// </summary>
|
||||
private StructuralType EdmType
|
||||
{
|
||||
get { return _edmType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a collection of all the <see cref="Attribute"/>s we infer from the metadata associated
|
||||
/// with the metadata member corresponding to the given property descriptor
|
||||
/// </summary>
|
||||
/// <param name="pd">A <see cref="PropertyDescriptor"/> to examine</param>
|
||||
/// <returns>A collection of attributes inferred from the metadata in the given descriptor.</returns>
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "TODO refactor")]
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "TODO refactor")]
|
||||
protected override IEnumerable<Attribute> GetMemberAttributes(PropertyDescriptor pd)
|
||||
{
|
||||
List<Attribute> attributes = new List<Attribute>();
|
||||
|
||||
// Exclude any EntityState, EntityReference, etc. members
|
||||
if (ShouldExcludeEntityMember(pd))
|
||||
{
|
||||
// for these members, we don't want to do any attribute inference
|
||||
return attributes.ToArray();
|
||||
}
|
||||
|
||||
EditableAttribute editableAttribute = null;
|
||||
bool inferRoundtripOriginalAttribute = false;
|
||||
|
||||
bool hasKeyAttribute = (pd.Attributes[typeof(KeyAttribute)] != null);
|
||||
bool isEntity = EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType;
|
||||
if (isEntity)
|
||||
{
|
||||
EntityType entityType = (EntityType)EdmType;
|
||||
EdmMember keyMember = entityType.KeyMembers.SingleOrDefault(k => k.Name == pd.Name);
|
||||
if (keyMember != null && !hasKeyAttribute)
|
||||
{
|
||||
attributes.Add(new KeyAttribute());
|
||||
hasKeyAttribute = true;
|
||||
}
|
||||
}
|
||||
|
||||
EdmProperty member = EdmType.Members.SingleOrDefault(p => p.Name == pd.Name) as EdmProperty;
|
||||
if (member != null)
|
||||
{
|
||||
if (hasKeyAttribute)
|
||||
{
|
||||
// key members must always be roundtripped
|
||||
inferRoundtripOriginalAttribute = true;
|
||||
|
||||
// key members that aren't also FK members are non-editable (but allow an initial value)
|
||||
if (!_keyIsEditable)
|
||||
{
|
||||
editableAttribute = new EditableAttribute(false) { AllowInitialValue = true };
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the member is DB generated and add the DatabaseGeneratedAttribute to it if not already present.
|
||||
if (pd.Attributes[typeof(DatabaseGeneratedAttribute)] == null)
|
||||
{
|
||||
MetadataProperty md = ObjectContextUtilities.GetStoreGeneratedPattern(member);
|
||||
if (md != null)
|
||||
{
|
||||
if ((string)md.Value == "Computed")
|
||||
{
|
||||
attributes.Add(new DatabaseGeneratedAttribute(DatabaseGeneratedOption.Computed));
|
||||
}
|
||||
else if ((string)md.Value == "Identity")
|
||||
{
|
||||
attributes.Add(new DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add implicit ConcurrencyCheck attribute to metadata if ConcurrencyMode is anything other than ConcurrencyMode.None
|
||||
Facet facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "ConcurrencyMode");
|
||||
if (facet != null && facet.Value != null && (ConcurrencyMode)facet.Value != ConcurrencyMode.None &&
|
||||
pd.Attributes[typeof(ConcurrencyCheckAttribute)] == null)
|
||||
{
|
||||
attributes.Add(new ConcurrencyCheckAttribute());
|
||||
inferRoundtripOriginalAttribute = true;
|
||||
}
|
||||
|
||||
bool isStringType = pd.PropertyType == typeof(string) || pd.PropertyType == typeof(char[]);
|
||||
|
||||
// Add Required attribute to metdata if the member cannot be null and it is either a reference type or a Nullable<T>
|
||||
if (!member.Nullable && (!pd.PropertyType.IsValueType || IsNullableType(pd.PropertyType)) &&
|
||||
pd.Attributes[typeof(RequiredAttribute)] == null)
|
||||
{
|
||||
attributes.Add(new RequiredAttribute());
|
||||
}
|
||||
|
||||
if (isStringType &&
|
||||
pd.Attributes[typeof(StringLengthAttribute)] == null)
|
||||
{
|
||||
facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "MaxLength");
|
||||
if (facet != null && facet.Value != null && facet.Value.GetType() == typeof(int))
|
||||
{
|
||||
// need to test for Type int, since the value can also be of type
|
||||
// System.Data.Metadata.Edm.EdmConstants.Unbounded
|
||||
int maxLength = (int)facet.Value;
|
||||
attributes.Add(new StringLengthAttribute(maxLength));
|
||||
}
|
||||
}
|
||||
|
||||
bool hasTimestampAttribute = (pd.Attributes[typeof(TimestampAttribute)] != null);
|
||||
|
||||
if (_timestampMember == member && !hasTimestampAttribute)
|
||||
{
|
||||
attributes.Add(new TimestampAttribute());
|
||||
hasTimestampAttribute = true;
|
||||
}
|
||||
|
||||
// All members marked with TimestampAttribute (inferred or explicit) need to
|
||||
// have [Editable(false)] and [RoundtripOriginal] applied
|
||||
if (hasTimestampAttribute)
|
||||
{
|
||||
inferRoundtripOriginalAttribute = true;
|
||||
|
||||
if (editableAttribute == null)
|
||||
{
|
||||
editableAttribute = new EditableAttribute(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Add RTO to this member if required. If this type has a timestamp
|
||||
// member that member should be the ONLY member we apply RTO to.
|
||||
// Dont apply RTO if it is an association member.
|
||||
bool isForeignKeyMember = _foreignKeyMembers != null && _foreignKeyMembers.Contains(member);
|
||||
if ((_timestampMember == null || _timestampMember == member) &&
|
||||
(inferRoundtripOriginalAttribute || isForeignKeyMember) &&
|
||||
pd.Attributes[typeof(AssociationAttribute)] == null)
|
||||
{
|
||||
if (pd.Attributes[typeof(RoundtripOriginalAttribute)] == null)
|
||||
{
|
||||
attributes.Add(new RoundtripOriginalAttribute());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the Editable attribute if required
|
||||
if (editableAttribute != null && pd.Attributes[typeof(EditableAttribute)] == null)
|
||||
{
|
||||
attributes.Add(editableAttribute);
|
||||
}
|
||||
|
||||
if (isEntity)
|
||||
{
|
||||
AddAssociationAttributes(pd, attributes);
|
||||
}
|
||||
|
||||
return attributes.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified property is an Entity member that
|
||||
/// should be excluded.
|
||||
/// </summary>
|
||||
/// <param name="pd">The property to check.</param>
|
||||
/// <returns>True if the property should be excluded, false otherwise.</returns>
|
||||
internal static bool ShouldExcludeEntityMember(PropertyDescriptor pd)
|
||||
{
|
||||
// exclude EntityState members
|
||||
if (pd.PropertyType == typeof(EntityState) &&
|
||||
(pd.ComponentType == typeof(EntityObject) || typeof(IEntityChangeTracker).IsAssignableFrom(pd.ComponentType)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// exclude entity reference properties
|
||||
if (typeof(EntityReference).IsAssignableFrom(pd.PropertyType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add AssociationAttribute if required for the specified property
|
||||
/// </summary>
|
||||
/// <param name="pd">The property</param>
|
||||
/// <param name="attributes">The list of attributes to append to</param>
|
||||
private void AddAssociationAttributes(PropertyDescriptor pd, List<Attribute> attributes)
|
||||
{
|
||||
EntityType entityType = (EntityType)EdmType;
|
||||
NavigationProperty navProperty = entityType.NavigationProperties.Where(n => n.Name == pd.Name).SingleOrDefault();
|
||||
if (navProperty != null)
|
||||
{
|
||||
bool isManyToMany = navProperty.RelationshipType.RelationshipEndMembers[0].RelationshipMultiplicity == RelationshipMultiplicity.Many &&
|
||||
navProperty.RelationshipType.RelationshipEndMembers[1].RelationshipMultiplicity == RelationshipMultiplicity.Many;
|
||||
if (!isManyToMany)
|
||||
{
|
||||
AssociationAttribute assocAttrib = (AssociationAttribute)pd.Attributes[typeof(System.ComponentModel.DataAnnotations.AssociationAttribute)];
|
||||
if (assocAttrib == null)
|
||||
{
|
||||
assocAttrib = TypeDescriptionContext.CreateAssociationAttribute(navProperty);
|
||||
attributes.Add(assocAttrib);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
|
||||
{
|
||||
internal class MetadataPropertyDescriptorWrapper : PropertyDescriptor
|
||||
{
|
||||
private readonly PropertyDescriptor _descriptor;
|
||||
|
||||
public MetadataPropertyDescriptorWrapper(PropertyDescriptor descriptor, Attribute[] attrs)
|
||||
: base(descriptor, attrs)
|
||||
{
|
||||
_descriptor = descriptor;
|
||||
}
|
||||
|
||||
public override Type ComponentType
|
||||
{
|
||||
get { return _descriptor.ComponentType; }
|
||||
}
|
||||
|
||||
public override bool IsReadOnly
|
||||
{
|
||||
get { return _descriptor.IsReadOnly; }
|
||||
}
|
||||
|
||||
public override Type PropertyType
|
||||
{
|
||||
get { return _descriptor.PropertyType; }
|
||||
}
|
||||
|
||||
public override bool SupportsChangeEvents
|
||||
{
|
||||
get { return _descriptor.SupportsChangeEvents; }
|
||||
}
|
||||
|
||||
public override void AddValueChanged(object component, EventHandler handler)
|
||||
{
|
||||
_descriptor.AddValueChanged(component, handler);
|
||||
}
|
||||
|
||||
public override bool CanResetValue(object component)
|
||||
{
|
||||
return _descriptor.CanResetValue(component);
|
||||
}
|
||||
|
||||
public override object GetValue(object component)
|
||||
{
|
||||
return _descriptor.GetValue(component);
|
||||
}
|
||||
|
||||
public override void RemoveValueChanged(object component, EventHandler handler)
|
||||
{
|
||||
_descriptor.RemoveValueChanged(component, handler);
|
||||
}
|
||||
|
||||
public override void ResetValue(object component)
|
||||
{
|
||||
_descriptor.ResetValue(component);
|
||||
}
|
||||
|
||||
public override void SetValue(object component, object value)
|
||||
{
|
||||
_descriptor.SetValue(component, value);
|
||||
}
|
||||
|
||||
public override bool ShouldSerializeValue(object component)
|
||||
{
|
||||
return _descriptor.ShouldSerializeValue(component);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,210 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Mapping;
|
||||
using System.Data.Metadata.Edm;
|
||||
using System.Data.Objects;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Web.Http;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// EF metadata utilities class.
|
||||
/// </summary>
|
||||
internal static class MetadataWorkspaceUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a metadata workspace for the specified context.
|
||||
/// </summary>
|
||||
/// <param name="contextType">The type of the object context.</param>
|
||||
/// <param name="isDbContext">Set to <c>true</c> if context is a database context.</param>
|
||||
/// <returns>The metadata workspace.</returns>
|
||||
public static MetadataWorkspace CreateMetadataWorkspace(Type contextType, bool isDbContext)
|
||||
{
|
||||
MetadataWorkspace metadataWorkspace = null;
|
||||
|
||||
if (!isDbContext)
|
||||
{
|
||||
metadataWorkspace = MetadataWorkspaceUtilities.CreateMetadataWorkspaceFromResources(contextType, typeof(ObjectContext));
|
||||
}
|
||||
else
|
||||
{
|
||||
metadataWorkspace = MetadataWorkspaceUtilities.CreateMetadataWorkspaceFromResources(contextType, typeof(System.Data.Entity.DbContext));
|
||||
if (metadataWorkspace == null && typeof(System.Data.Entity.DbContext).IsAssignableFrom(contextType))
|
||||
{
|
||||
if (contextType.GetConstructor(Type.EmptyTypes) == null)
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.DefaultCtorNotFound, contextType.FullName);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
System.Data.Entity.DbContext dbContext = Activator.CreateInstance(contextType) as System.Data.Entity.DbContext;
|
||||
ObjectContext objectContext = (dbContext as System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext;
|
||||
metadataWorkspace = objectContext.MetadataWorkspace;
|
||||
}
|
||||
catch (Exception efException)
|
||||
{
|
||||
throw Error.InvalidOperation(efException, Resource.MetadataWorkspaceNotFound, contextType.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (metadataWorkspace == null)
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.LinqToEntitiesProvider_UnableToRetrieveMetadata, contextType.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return metadataWorkspace;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the MetadataWorkspace for the given context type and base context type.
|
||||
/// </summary>
|
||||
/// <param name="contextType">The type of the context.</param>
|
||||
/// <param name="baseContextType">The base context type (DbContext or ObjectContext).</param>
|
||||
/// <returns>The generated <see cref="MetadataWorkspace"/></returns>
|
||||
public static MetadataWorkspace CreateMetadataWorkspaceFromResources(Type contextType, Type baseContextType)
|
||||
{
|
||||
// get the set of embedded mapping resources for the target assembly and create
|
||||
// a metadata workspace info for each group
|
||||
IEnumerable<string> metadataResourcePaths = FindMetadataResources(contextType.Assembly);
|
||||
IEnumerable<MetadataWorkspaceInfo> workspaceInfos = GetMetadataWorkspaceInfos(metadataResourcePaths);
|
||||
|
||||
// Search for the correct EntityContainer by name and if found, create
|
||||
// a comlete MetadataWorkspace and return it
|
||||
foreach (var workspaceInfo in workspaceInfos)
|
||||
{
|
||||
EdmItemCollection edmItemCollection = new EdmItemCollection(workspaceInfo.Csdl);
|
||||
|
||||
Type currentType = contextType;
|
||||
while (currentType != baseContextType && currentType != typeof(object))
|
||||
{
|
||||
EntityContainer container;
|
||||
if (edmItemCollection.TryGetEntityContainer(currentType.Name, out container))
|
||||
{
|
||||
StoreItemCollection store = new StoreItemCollection(workspaceInfo.Ssdl);
|
||||
StorageMappingItemCollection mapping = new StorageMappingItemCollection(edmItemCollection, store, workspaceInfo.Msl);
|
||||
MetadataWorkspace workspace = new MetadataWorkspace();
|
||||
workspace.RegisterItemCollection(edmItemCollection);
|
||||
workspace.RegisterItemCollection(store);
|
||||
workspace.RegisterItemCollection(mapping);
|
||||
workspace.RegisterItemCollection(new ObjectItemCollection());
|
||||
return workspace;
|
||||
}
|
||||
|
||||
currentType = currentType.BaseType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified resource paths as metadata workspace info objects.
|
||||
/// </summary>
|
||||
/// <param name="resourcePaths">The metadata resource paths.</param>
|
||||
/// <returns>The metadata workspace info objects.</returns>
|
||||
private static IEnumerable<MetadataWorkspaceInfo> GetMetadataWorkspaceInfos(IEnumerable<string> resourcePaths)
|
||||
{
|
||||
// for file paths, you would want to group without the path or the extension like Path.GetFileNameWithoutExtension, but resource names can contain
|
||||
// forbidden path chars, so don't use it on resource names
|
||||
foreach (var group in resourcePaths.GroupBy(p => p.Substring(0, p.LastIndexOf('.')), StringComparer.InvariantCultureIgnoreCase))
|
||||
{
|
||||
yield return MetadataWorkspaceInfo.Create(group);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all the EF metadata resources.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly to find the metadata resources in.</param>
|
||||
/// <returns>The metadata paths that were found.</returns>
|
||||
private static IEnumerable<string> FindMetadataResources(Assembly assembly)
|
||||
{
|
||||
List<string> result = new List<string>();
|
||||
foreach (string name in assembly.GetManifestResourceNames())
|
||||
{
|
||||
if (MetadataWorkspaceInfo.IsMetadata(name))
|
||||
{
|
||||
result.Add(String.Format(CultureInfo.InvariantCulture, "res://{0}/{1}", assembly.FullName, name));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the paths for a single metadata workspace.
|
||||
/// </summary>
|
||||
private class MetadataWorkspaceInfo
|
||||
{
|
||||
private const string CsdlExtension = ".csdl";
|
||||
private const string MslExtension = ".msl";
|
||||
private const string SsdlExtension = ".ssdl";
|
||||
|
||||
public MetadataWorkspaceInfo(string csdlPath, string mslPath, string ssdlPath)
|
||||
{
|
||||
if (csdlPath == null)
|
||||
{
|
||||
throw Error.ArgumentNull("csdlPath");
|
||||
}
|
||||
|
||||
if (mslPath == null)
|
||||
{
|
||||
throw Error.ArgumentNull("mslPath");
|
||||
}
|
||||
|
||||
if (ssdlPath == null)
|
||||
{
|
||||
throw Error.ArgumentNull("ssdlPath");
|
||||
}
|
||||
|
||||
Csdl = csdlPath;
|
||||
Msl = mslPath;
|
||||
Ssdl = ssdlPath;
|
||||
}
|
||||
|
||||
public string Csdl { get; private set; }
|
||||
|
||||
public string Msl { get; private set; }
|
||||
|
||||
public string Ssdl { get; private set; }
|
||||
|
||||
public static MetadataWorkspaceInfo Create(IEnumerable<string> paths)
|
||||
{
|
||||
string csdlPath = null;
|
||||
string mslPath = null;
|
||||
string ssdlPath = null;
|
||||
foreach (string path in paths)
|
||||
{
|
||||
if (path.EndsWith(CsdlExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
csdlPath = path;
|
||||
}
|
||||
else if (path.EndsWith(MslExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mslPath = path;
|
||||
}
|
||||
else if (path.EndsWith(SsdlExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ssdlPath = path;
|
||||
}
|
||||
}
|
||||
|
||||
return new MetadataWorkspaceInfo(csdlPath, mslPath, ssdlPath);
|
||||
}
|
||||
|
||||
public static bool IsMetadata(string path)
|
||||
{
|
||||
return path.EndsWith(CsdlExtension, StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith(MslExtension, StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith(SsdlExtension, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for LTS and EF type description contexts
|
||||
/// </summary>
|
||||
internal abstract class TypeDescriptionContextBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Given a suggested name and a collection of existing names, this method
|
||||
/// creates a unique name by appending a numerix suffix as required.
|
||||
/// </summary>
|
||||
/// <param name="suggested">The desired name</param>
|
||||
/// <param name="existing">Collection of existing names</param>
|
||||
/// <returns>The unique name</returns>
|
||||
protected static string MakeUniqueName(string suggested, IEnumerable<string> existing)
|
||||
{
|
||||
int i = 1;
|
||||
string currSuggestion = suggested;
|
||||
while (existing.Contains(currSuggestion))
|
||||
{
|
||||
currSuggestion = suggested + (i++).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return currSuggestion;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// CustomTypeDescriptor base type shared by LINQ To SQL and LINQ To Entities
|
||||
/// </summary>
|
||||
internal abstract class TypeDescriptorBase : CustomTypeDescriptor
|
||||
{
|
||||
private PropertyDescriptorCollection _properties;
|
||||
|
||||
/// <summary>
|
||||
/// Main constructor that accepts the parent custom type descriptor
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent custom type descriptor.</param>
|
||||
public TypeDescriptorBase(ICustomTypeDescriptor parent)
|
||||
: base(parent)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override of the <see cref="CustomTypeDescriptor.GetProperties()"/> to obtain the list
|
||||
/// of properties for this type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is overridden so that it can merge this class's parent attributes with those
|
||||
/// it infers from the DAL-specific attributes.
|
||||
/// </remarks>
|
||||
/// <returns>A list of properties for this type</returns>
|
||||
public sealed override PropertyDescriptorCollection GetProperties()
|
||||
{
|
||||
// No need to lock anything... Worst case scenario we create the properties multiple times.
|
||||
if (_properties == null)
|
||||
{
|
||||
// Get properties from our parent
|
||||
PropertyDescriptorCollection originalCollection = base.GetProperties();
|
||||
|
||||
bool customDescriptorsCreated = false;
|
||||
List<PropertyDescriptor> tempPropertyDescriptors = new List<PropertyDescriptor>();
|
||||
|
||||
// for every property exposed by our parent, see if we have additional metadata to add
|
||||
foreach (PropertyDescriptor propDescriptor in originalCollection)
|
||||
{
|
||||
Attribute[] newMetadata = GetMemberAttributes(propDescriptor).ToArray();
|
||||
if (newMetadata.Length > 0)
|
||||
{
|
||||
tempPropertyDescriptors.Add(new MetadataPropertyDescriptorWrapper(propDescriptor, newMetadata));
|
||||
customDescriptorsCreated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
tempPropertyDescriptors.Add(propDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
if (customDescriptorsCreated)
|
||||
{
|
||||
_properties = new PropertyDescriptorCollection(tempPropertyDescriptors.ToArray(), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_properties = originalCollection;
|
||||
}
|
||||
}
|
||||
|
||||
return _properties;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract method specific DAL implementations must override to return the
|
||||
/// list of RIA <see cref="Attribute"/>s implied by their DAL-specific attributes
|
||||
/// </summary>
|
||||
/// <param name="pd">A <see cref="PropertyDescriptor"/> to examine.</param>
|
||||
/// <returns>A list of RIA attributes implied by the DAL specific attributes</returns>
|
||||
protected abstract IEnumerable<Attribute> GetMemberAttributes(PropertyDescriptor pd);
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> if the given type is a <see cref="Nullable"/>
|
||||
/// </summary>
|
||||
/// <param name="type">The type to test</param>
|
||||
/// <returns><c>true</c> if the given type is a nullable type</returns>
|
||||
public static bool IsNullableType(Type type)
|
||||
{
|
||||
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{653F3946-541C-42D3-BBC1-CE89B392BDA9}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Microsoft.Web.Http.Data.EntityFramework</RootNamespace>
|
||||
<AssemblyName>Microsoft.Web.Http.Data.EntityFramework</AssemblyName>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\..\bin\Debug\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG;CODE_ANALYSIS;ASPNETMVC</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<DocumentationFile>$(OutputPath)$(AssemblyName).xml</DocumentationFile>
|
||||
<RunCodeAnalysis>$(CodeAnalysis)</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\..\bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;ASPNETMVC</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<DocumentationFile>$(OutputPath)$(AssemblyName).xml</DocumentationFile>
|
||||
<RunCodeAnalysis>$(CodeAnalysis)</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeAnalysis|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\..\bin\CodeAnalysis\</OutputPath>
|
||||
<DefineConstants>TRACE;CODE_ANALYSIS;ASPNETMVC</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<DocumentationFile>$(OutputPath)$(AssemblyName).xml</DocumentationFile>
|
||||
<RunCodeAnalysis>$(CodeAnalysis)</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="EntityFramework">
|
||||
<HintPath>..\..\packages\EntityFramework.5.0.0\lib\net40\EntityFramework.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.Entity" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http.WebRequest, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\CommonAssemblyInfo.cs">
|
||||
<Link>Properties\CommonAssemblyInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Common\DictionaryExtensions.cs">
|
||||
<Link>Common\DictionaryExtensions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Common\Error.cs">
|
||||
<Link>Common\Error.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="DbContextExtensions.cs" />
|
||||
<Compile Include="DbDataController.cs" />
|
||||
<Compile Include="GlobalSuppressions.cs" />
|
||||
<Compile Include="LinqToEntitiesDataController.cs" />
|
||||
<Compile Include="Metadata\AssociationInfo.cs" />
|
||||
<Compile Include="Metadata\DbMetadataProviderAttribute.cs" />
|
||||
<Compile Include="Metadata\LinqToEntitiesMetadataProvider.cs" />
|
||||
<Compile Include="Metadata\LinqToEntitiesMetadataProviderAttribute.cs" />
|
||||
<Compile Include="Metadata\LinqToEntitiesTypeDescriptionContext.cs" />
|
||||
<Compile Include="Metadata\LinqToEntitiesTypeDescriptor.cs" />
|
||||
<Compile Include="Metadata\MetadataPropertyDescriptorWrapper.cs" />
|
||||
<Compile Include="Metadata\MetadataWorkspaceUtilities.cs" />
|
||||
<Compile Include="Metadata\TypeDescriptionContextBase.cs" />
|
||||
<Compile Include="Metadata\TypeDescriptorBase.cs" />
|
||||
<Compile Include="ObjectContextExtensions.cs" />
|
||||
<Compile Include="ObjectContextUtilities.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="..\AptcaCommonAssemblyInfo.cs">
|
||||
<Link>Properties\AptcaCommonAssemblyInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Resource.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resource.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CodeAnalysisDictionary Include="..\CodeAnalysisDictionary.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\System.Net.Http.Formatting\System.Net.Http.Formatting.csproj">
|
||||
<Project>{668E9021-CE84-49D9-98FB-DF125A9FCDB0}</Project>
|
||||
<Name>System.Net.Http.Formatting</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Microsoft.Web.Http.Data\Microsoft.Web.Http.Data.csproj">
|
||||
<Project>{ACE91549-D86E-4EB6-8C2A-5FF51386BB68}</Project>
|
||||
<Name>Microsoft.Web.Http.Data</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\System.Web.Http\System.Web.Http.csproj">
|
||||
<Project>{DDC1CE0C-486E-4E35-BB3B-EAB61F8F9440}</Project>
|
||||
<Name>System.Web.Http</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\CommonWebApiResources.Designer.cs">
|
||||
<Link>Properties\CommonWebApiResources.Designer.cs</Link>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>CommonWebApiResources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\Common\CommonWebApiResources.resx">
|
||||
<Link>Properties\CommonWebApiResources.resx</Link>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>CommonWebApiResources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resource.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
|
||||
</Project>
|
|
@ -1,92 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Data.Objects;
|
||||
using System.Web.Http;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// ObjectContext extension methods
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static class ObjectContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension method used to attach the specified entity as modified,
|
||||
/// with the specified original state.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">The entity Type</typeparam>
|
||||
/// <param name="objectSet">The ObjectSet to attach to</param>
|
||||
/// <param name="current">The current entity state</param>
|
||||
/// <param name="original">The original entity state</param>
|
||||
public static void AttachAsModified<TEntity>(this ObjectSet<TEntity> objectSet, TEntity current, TEntity original) where TEntity : class
|
||||
{
|
||||
if (objectSet == null)
|
||||
{
|
||||
throw Error.ArgumentNull("objectSet");
|
||||
}
|
||||
if (current == null)
|
||||
{
|
||||
throw Error.ArgumentNull("current");
|
||||
}
|
||||
if (original == null)
|
||||
{
|
||||
throw Error.ArgumentNull("original");
|
||||
}
|
||||
|
||||
// Attach the entity if it is not already attached, or if it is already
|
||||
// attached, transition to Modified
|
||||
EntityState currState = ObjectContextUtilities.GetEntityState(objectSet.Context, current);
|
||||
if (currState == EntityState.Detached)
|
||||
{
|
||||
objectSet.Attach(current);
|
||||
}
|
||||
else
|
||||
{
|
||||
objectSet.Context.ObjectStateManager.ChangeObjectState(current, EntityState.Modified);
|
||||
}
|
||||
|
||||
ObjectStateEntry stateEntry = ObjectContextUtilities.AttachAsModifiedInternal<TEntity>(current, original, objectSet.Context);
|
||||
|
||||
if (stateEntry.State != EntityState.Modified)
|
||||
{
|
||||
// Ensure that when we leave this method, the entity is in a
|
||||
// Modified state. For example, if current and original are the
|
||||
// same, we still need to force the state transition
|
||||
objectSet.Context.ObjectStateManager.ChangeObjectState(current, EntityState.Modified);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method used to attach the specified entity as modified. This overload
|
||||
/// can be used in cases where the entity has a Timestamp member.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">The entity Type</typeparam>
|
||||
/// <param name="objectSet">The ObjectSet to attach to</param>
|
||||
/// <param name="entity">The current entity state</param>
|
||||
public static void AttachAsModified<TEntity>(this ObjectSet<TEntity> objectSet, TEntity entity) where TEntity : class
|
||||
{
|
||||
if (objectSet == null)
|
||||
{
|
||||
throw Error.ArgumentNull("objectSet");
|
||||
}
|
||||
if (entity == null)
|
||||
{
|
||||
throw Error.ArgumentNull("entity");
|
||||
}
|
||||
|
||||
ObjectContext context = objectSet.Context;
|
||||
EntityState currState = ObjectContextUtilities.GetEntityState(context, entity);
|
||||
if (currState == EntityState.Detached)
|
||||
{
|
||||
// attach the entity
|
||||
objectSet.Attach(entity);
|
||||
}
|
||||
|
||||
// transition the entity to the modified state
|
||||
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,180 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Data;
|
||||
using System.Data.Metadata.Edm;
|
||||
using System.Data.Objects;
|
||||
using System.Linq;
|
||||
using System.Web.Http;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal utility functions for dealing with EF types and metadata
|
||||
/// </summary>
|
||||
internal static class ObjectContextUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves the <see cref="StructuralType"/> corresponding to the given CLR type (where the
|
||||
/// type is an entity or complex type).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If no mapping exists for <paramref name="clrType"/>, but one does exist for one of its base
|
||||
/// types, we will return the mapping for the base type.
|
||||
/// </remarks>
|
||||
/// <param name="workspace">The <see cref="MetadataWorkspace"/></param>
|
||||
/// <param name="clrType">The CLR type</param>
|
||||
/// <returns>The <see cref="StructuralType"/> corresponding to that CLR type, or <c>null</c> if the Type
|
||||
/// is not mapped.</returns>
|
||||
public static StructuralType GetEdmType(MetadataWorkspace workspace, Type clrType)
|
||||
{
|
||||
if (workspace == null)
|
||||
{
|
||||
throw Error.ArgumentNull("workspace");
|
||||
}
|
||||
if (clrType == null)
|
||||
{
|
||||
throw Error.ArgumentNull("clrType");
|
||||
}
|
||||
|
||||
if (clrType.IsPrimitive || clrType == typeof(object))
|
||||
{
|
||||
// want to avoid loading searching system assemblies for
|
||||
// types we know aren't entity or complex types
|
||||
return null;
|
||||
}
|
||||
|
||||
// We first locate the EdmType in "OSpace", which matches the name and namespace of the CLR type
|
||||
EdmType edmType = null;
|
||||
do
|
||||
{
|
||||
if (!workspace.TryGetType(clrType.Name, clrType.Namespace, DataSpace.OSpace, out edmType))
|
||||
{
|
||||
// If EF could not find this type, it could be because it is not loaded into
|
||||
// its current workspace. In this case, we explicitly load the assembly containing
|
||||
// the CLR type and try again.
|
||||
workspace.LoadFromAssembly(clrType.Assembly);
|
||||
workspace.TryGetType(clrType.Name, clrType.Namespace, DataSpace.OSpace, out edmType);
|
||||
}
|
||||
}
|
||||
while (edmType == null && (clrType = clrType.BaseType) != typeof(object) && clrType != null);
|
||||
|
||||
// Next we locate the StructuralType from the EdmType.
|
||||
// This 2-step process is necessary when the types CLR namespace does not match Edm namespace.
|
||||
// Look at the EdmEntityTypeAttribute on the generated entity classes to see this Edm namespace.
|
||||
StructuralType structuralType = null;
|
||||
if (edmType != null &&
|
||||
(edmType.BuiltInTypeKind == BuiltInTypeKind.EntityType || edmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType))
|
||||
{
|
||||
workspace.TryGetEdmSpaceType((StructuralType)edmType, out structuralType);
|
||||
}
|
||||
|
||||
return structuralType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method used to return the current <see cref="EntityState"/> of the specified
|
||||
/// entity.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="ObjectContext"/></param>
|
||||
/// <param name="entity">The entity to return the <see cref="EntityState"/> for</param>
|
||||
/// <returns>The current <see cref="EntityState"/> of the specified entity</returns>
|
||||
public static EntityState GetEntityState(ObjectContext context, object entity)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw Error.ArgumentNull("context");
|
||||
}
|
||||
if (entity == null)
|
||||
{
|
||||
throw Error.ArgumentNull("entity");
|
||||
}
|
||||
|
||||
ObjectStateEntry stateEntry = null;
|
||||
if (!context.ObjectStateManager.TryGetObjectStateEntry(entity, out stateEntry))
|
||||
{
|
||||
return EntityState.Detached;
|
||||
}
|
||||
return stateEntry.State;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the specified EdmMember is a concurrency timestamp.
|
||||
/// </summary>
|
||||
/// <remarks>Since EF doesn't expose "timestamp" as a first class
|
||||
/// concept, we use the below criteria to infer this for ourselves.
|
||||
/// </remarks>
|
||||
/// <param name="member">The member to check.</param>
|
||||
/// <returns>True or false.</returns>
|
||||
public static bool IsConcurrencyTimestamp(EdmMember member)
|
||||
{
|
||||
Facet facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "ConcurrencyMode");
|
||||
if (facet == null || facet.Value == null || (ConcurrencyMode)facet.Value != ConcurrencyMode.Fixed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "FixedLength");
|
||||
if (facet == null || facet.Value == null || !((bool)facet.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "MaxLength");
|
||||
if (facet == null || facet.Value == null || (int)facet.Value != 8)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MetadataProperty md = ObjectContextUtilities.GetStoreGeneratedPattern(member);
|
||||
if (md == null || facet.Value == null || (string)md.Value != "Computed")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="StoreGeneratedPattern"/> property value from the edm member.
|
||||
/// </summary>
|
||||
/// <param name="member">The EdmMember from which to get the StoreGeneratedPattern value.</param>
|
||||
/// <returns>The <see cref="StoreGeneratedPattern"/> value.</returns>
|
||||
public static MetadataProperty GetStoreGeneratedPattern(EdmMember member)
|
||||
{
|
||||
MetadataProperty md;
|
||||
member.MetadataProperties.TryGetValue("http://schemas.microsoft.com/ado/2009/02/edm/annotation:StoreGeneratedPattern", ignoreCase: true, item: out md);
|
||||
return md;
|
||||
}
|
||||
|
||||
public static ObjectStateEntry AttachAsModifiedInternal<TEntity>(TEntity current, TEntity original, ObjectContext objectContext)
|
||||
{
|
||||
ObjectStateEntry stateEntry = objectContext.ObjectStateManager.GetObjectStateEntry(current);
|
||||
stateEntry.ApplyOriginalValues(original);
|
||||
|
||||
// For any members that don't have RoundtripOriginal applied, EF can't determine modification
|
||||
// state by doing value comparisons. To avoid losing updates in these cases, we must explicitly
|
||||
// mark such members as modified.
|
||||
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(TEntity));
|
||||
AttributeCollection attributes = TypeDescriptor.GetAttributes(typeof(TEntity));
|
||||
bool isRoundtripType = attributes[typeof(RoundtripOriginalAttribute)] != null;
|
||||
foreach (var fieldMetadata in stateEntry.CurrentValues.DataRecordInfo.FieldMetadata)
|
||||
{
|
||||
string memberName = stateEntry.CurrentValues.GetName(fieldMetadata.Ordinal);
|
||||
PropertyDescriptor property = properties[memberName];
|
||||
// TODO: below we need to replace ExcludeAttribute logic with corresponding
|
||||
// DataContractMember/IgnoreDataMember logic
|
||||
if (property != null &&
|
||||
(property.Attributes[typeof(RoundtripOriginalAttribute)] == null && !isRoundtripType)
|
||||
/* && property.Attributes[typeof(ExcludeAttribute)] == null */)
|
||||
{
|
||||
stateEntry.SetModifiedProperty(memberName);
|
||||
}
|
||||
}
|
||||
|
||||
return stateEntry;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: AssemblyTitle("Microsoft.Web.Http.Data.EntityFramework")]
|
||||
[assembly: AssemblyDescription("Microsoft.Web.Http.Data.EntityFramework")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.Web.Http.Data.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
|
|
@ -1,135 +0,0 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.239
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.Web.Http.Data.EntityFramework {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resource {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resource() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Web.Http.Data.EntityFramework.Resource", typeof(Resource).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The DbContext type '{0}' does not contain a parameterless constructor. A parameterless constructor is required to use EntityFramework in the Code-First mode with a DataController..
|
||||
/// </summary>
|
||||
internal static string DefaultCtorNotFound {
|
||||
get {
|
||||
return ResourceManager.GetString("DefaultCtorNotFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Type '{0}' is not a valid DbMetadataProviderAttribute parameter because it does not derive from DbContext..
|
||||
/// </summary>
|
||||
internal static string InvalidDbMetadataProviderSpecification {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidDbMetadataProviderSpecification", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Type '{0}' is not a valid LinqToEntitiesMetadataProviderAttribute parameter because it does not derive from ObjectContext..
|
||||
/// </summary>
|
||||
internal static string InvalidLinqToEntitiesMetadataProviderSpecification {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidLinqToEntitiesMetadataProviderSpecification", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to '{0}' cannot be applied to DataController type '{1}' because '{1}' does not derive from '{2}'..
|
||||
/// </summary>
|
||||
internal static string InvalidMetadataProviderSpecification {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidMetadataProviderSpecification", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to retrieve association information for association '{0}'. Only models that include foreign key information are supported. See Entity Framework documentation for details on creating models that include foreign key information..
|
||||
/// </summary>
|
||||
internal static string LinqToEntitiesProvider_UnableToRetrieveAssociationInfo {
|
||||
get {
|
||||
return ResourceManager.GetString("LinqToEntitiesProvider_UnableToRetrieveAssociationInfo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to find metadata for '{0}'..
|
||||
/// </summary>
|
||||
internal static string LinqToEntitiesProvider_UnableToRetrieveMetadata {
|
||||
get {
|
||||
return ResourceManager.GetString("LinqToEntitiesProvider_UnableToRetrieveMetadata", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed to get the MetadataWorkspace for the DbContext type '{0}'..
|
||||
/// </summary>
|
||||
internal static string MetadataWorkspaceNotFound {
|
||||
get {
|
||||
return ResourceManager.GetString("MetadataWorkspaceNotFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to ObjectStateManager not initialized for the DbContext type '{0}'..
|
||||
/// </summary>
|
||||
internal static string ObjectStateManagerNotFoundException {
|
||||
get {
|
||||
return ResourceManager.GetString("ObjectStateManagerNotFoundException", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="DefaultCtorNotFound" xml:space="preserve">
|
||||
<value>The DbContext type '{0}' does not contain a parameterless constructor. A parameterless constructor is required to use EntityFramework in the Code-First mode with a DataController.</value>
|
||||
</data>
|
||||
<data name="InvalidDbMetadataProviderSpecification" xml:space="preserve">
|
||||
<value>Type '{0}' is not a valid DbMetadataProviderAttribute parameter because it does not derive from DbContext.</value>
|
||||
</data>
|
||||
<data name="InvalidLinqToEntitiesMetadataProviderSpecification" xml:space="preserve">
|
||||
<value>Type '{0}' is not a valid LinqToEntitiesMetadataProviderAttribute parameter because it does not derive from ObjectContext.</value>
|
||||
</data>
|
||||
<data name="InvalidMetadataProviderSpecification" xml:space="preserve">
|
||||
<value>'{0}' cannot be applied to DataController type '{1}' because '{1}' does not derive from '{2}'.</value>
|
||||
</data>
|
||||
<data name="LinqToEntitiesProvider_UnableToRetrieveAssociationInfo" xml:space="preserve">
|
||||
<value>Unable to retrieve association information for association '{0}'. Only models that include foreign key information are supported. See Entity Framework documentation for details on creating models that include foreign key information.</value>
|
||||
</data>
|
||||
<data name="LinqToEntitiesProvider_UnableToRetrieveMetadata" xml:space="preserve">
|
||||
<value>Unable to find metadata for '{0}'.</value>
|
||||
</data>
|
||||
<data name="MetadataWorkspaceNotFound" xml:space="preserve">
|
||||
<value>Failed to get the MetadataWorkspace for the DbContext type '{0}'.</value>
|
||||
</data>
|
||||
<data name="ObjectStateManagerNotFoundException" xml:space="preserve">
|
||||
<value>ObjectStateManager not initialized for the DbContext type '{0}'.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1,12 +0,0 @@
|
|||
<StyleCopSettings Version="105">
|
||||
<Analyzers>
|
||||
<Analyzer AnalyzerId="StyleCop.CSharp.NamingRules">
|
||||
<AnalyzerSettings>
|
||||
<CollectionProperty Name="Hungarian">
|
||||
<Value>db</Value>
|
||||
<Value>ef</Value>
|
||||
</CollectionProperty>
|
||||
</AnalyzerSettings>
|
||||
</Analyzer>
|
||||
</Analyzers>
|
||||
</StyleCopSettings>
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="EntityFramework" version="5.0.0" />
|
||||
<package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net40" />
|
||||
</packages>
|
|
@ -1,384 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.Helpers
|
||||
{
|
||||
internal static class DataControllerMetadataGenerator
|
||||
{
|
||||
private static readonly ConcurrentDictionary<DataControllerDescription, IEnumerable<TypeMetadata>> _metadataMap =
|
||||
new ConcurrentDictionary<DataControllerDescription, IEnumerable<TypeMetadata>>();
|
||||
|
||||
private static readonly IEnumerable<JProperty> _emptyJsonPropertyEnumerable = Enumerable.Empty<JProperty>();
|
||||
|
||||
public static IEnumerable<TypeMetadata> GetMetadata(DataControllerDescription description)
|
||||
{
|
||||
return _metadataMap.GetOrAdd(description, desc =>
|
||||
{
|
||||
return GenerateMetadata(desc);
|
||||
});
|
||||
}
|
||||
|
||||
private static IEnumerable<TypeMetadata> GenerateMetadata(DataControllerDescription description)
|
||||
{
|
||||
List<TypeMetadata> metadata = new List<TypeMetadata>();
|
||||
foreach (Type entityType in description.EntityTypes)
|
||||
{
|
||||
metadata.Add(new TypeMetadata(entityType));
|
||||
}
|
||||
// TODO: Complex types are NYI in DataControllerDescription
|
||||
// foreach (Type complexType in description.ComplexTypes)
|
||||
// {
|
||||
// metadata.Add(new TypeMetadata(complexType));
|
||||
// }
|
||||
return metadata;
|
||||
}
|
||||
|
||||
internal static string EncodeTypeName(Type type)
|
||||
{
|
||||
// This format matches that employed by Json.NET. We assume that DataController uses Json.NET for JSON serialization.
|
||||
return String.Format(CultureInfo.InvariantCulture, "{0}.{1},{2}", type.Namespace, type.Name, type.Assembly.GetName().Name);
|
||||
}
|
||||
|
||||
private static class MetadataStrings
|
||||
{
|
||||
public const string NamespaceMarker = ":#";
|
||||
public const string TypeString = "type";
|
||||
public const string ArrayString = "array";
|
||||
public const string AssociationString = "association";
|
||||
public const string FieldsString = "fields";
|
||||
public const string ThisKeyString = "thisKey";
|
||||
public const string IsForeignKey = "isForeignKey";
|
||||
public const string OtherKeyString = "otherKey";
|
||||
public const string NameString = "name";
|
||||
public const string ReadOnlyString = "readonly";
|
||||
public const string KeyString = "key";
|
||||
public const string RulesString = "rules";
|
||||
public const string MessagesString = "messages";
|
||||
}
|
||||
|
||||
public class TypeMetadata
|
||||
{
|
||||
private List<string> _key = new List<string>();
|
||||
private List<TypePropertyMetadata> _properties = new List<TypePropertyMetadata>();
|
||||
|
||||
public TypeMetadata(Type entityType)
|
||||
{
|
||||
Type type = TypeUtility.GetElementType(entityType);
|
||||
ClrType = type;
|
||||
|
||||
IEnumerable<PropertyDescriptor> properties =
|
||||
TypeDescriptor.GetProperties(type).Cast<PropertyDescriptor>().OrderBy(p => p.Name)
|
||||
.Where(p => TypeUtility.IsDataMember(p));
|
||||
|
||||
foreach (PropertyDescriptor pd in properties)
|
||||
{
|
||||
_properties.Add(new TypePropertyMetadata(pd));
|
||||
if (TypeDescriptorExtensions.ExplicitAttributes(pd)[typeof(KeyAttribute)] != null)
|
||||
{
|
||||
_key.Add(pd.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Type ClrType { get; private set; }
|
||||
|
||||
public string EncodedTypeName
|
||||
{
|
||||
get { return EncodeTypeName(ClrType); }
|
||||
}
|
||||
|
||||
public IEnumerable<string> Key
|
||||
{
|
||||
get { return _key; }
|
||||
}
|
||||
|
||||
public IEnumerable<TypePropertyMetadata> Properties
|
||||
{
|
||||
get { return _properties; }
|
||||
}
|
||||
|
||||
public JToken ToJToken()
|
||||
{
|
||||
JObject value = new JObject();
|
||||
|
||||
value[MetadataStrings.KeyString] = new JArray(Key.Select(k => (JToken)k));
|
||||
value[MetadataStrings.FieldsString] = new JObject(Properties.Select(p => new JProperty(p.Name, p.ToJToken())));
|
||||
|
||||
// TODO: Only include these properties when they'll have non-empty values. Need to update SPA T4 templates to tolerate null in scaffolded SPA JavaScript.
|
||||
//if (Properties.Any(p => p.ValidationRules.Count > 0))
|
||||
//{
|
||||
value[MetadataStrings.RulesString] = new JObject(
|
||||
Properties.SelectMany(
|
||||
p => p.ValidationRules.Count == 0
|
||||
? _emptyJsonPropertyEnumerable
|
||||
: new JProperty[]
|
||||
{
|
||||
new JProperty(
|
||||
p.Name,
|
||||
new JObject(p.ValidationRules.Select(
|
||||
r => new JProperty(r.Name, r.ToJToken()))))
|
||||
}));
|
||||
//}
|
||||
//if (Properties.Any(p => p.ValidationRules.Any(r => r.ErrorMessageString != null)))
|
||||
//{
|
||||
value[MetadataStrings.MessagesString] = new JObject(
|
||||
Properties.SelectMany(
|
||||
p => !p.ValidationRules.Any(r => r.ErrorMessageString != null)
|
||||
? _emptyJsonPropertyEnumerable
|
||||
: new JProperty[]
|
||||
{
|
||||
new JProperty(
|
||||
p.Name,
|
||||
new JObject(p.ValidationRules.SelectMany(r =>
|
||||
r.ErrorMessageString == null
|
||||
? _emptyJsonPropertyEnumerable
|
||||
: new JProperty[]
|
||||
{
|
||||
new JProperty(r.Name, r.ErrorMessageString)
|
||||
})))
|
||||
}));
|
||||
//}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public class TypePropertyAssociationMetadata
|
||||
{
|
||||
private List<string> _thisKeyMembers = new List<string>();
|
||||
private List<string> _otherKeyMembers = new List<string>();
|
||||
|
||||
public TypePropertyAssociationMetadata(AssociationAttribute associationAttr)
|
||||
{
|
||||
Name = associationAttr.Name;
|
||||
IsForeignKey = associationAttr.IsForeignKey;
|
||||
_otherKeyMembers = associationAttr.OtherKeyMembers.ToList<string>();
|
||||
_thisKeyMembers = associationAttr.ThisKeyMembers.ToList<string>();
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
public bool IsForeignKey { get; private set; }
|
||||
|
||||
public IEnumerable<string> ThisKeyMembers
|
||||
{
|
||||
get { return _thisKeyMembers; }
|
||||
}
|
||||
|
||||
public IEnumerable<string> OtherKeyMembers
|
||||
{
|
||||
get { return _otherKeyMembers; }
|
||||
}
|
||||
|
||||
public JToken ToJToken()
|
||||
{
|
||||
JObject value = new JObject();
|
||||
value[MetadataStrings.NameString] = Name;
|
||||
value[MetadataStrings.ThisKeyString] = new JArray(ThisKeyMembers.Select(k => (JToken)k));
|
||||
value[MetadataStrings.OtherKeyString] = new JArray(OtherKeyMembers.Select(k => (JToken)k));
|
||||
value[MetadataStrings.IsForeignKey] = IsForeignKey;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public class TypePropertyMetadata
|
||||
{
|
||||
private List<TypePropertyValidationRuleMetadata> _validationRules = new List<TypePropertyValidationRuleMetadata>();
|
||||
|
||||
public TypePropertyMetadata(PropertyDescriptor descriptor)
|
||||
{
|
||||
Name = descriptor.Name;
|
||||
|
||||
Type elementType = TypeUtility.GetElementType(descriptor.PropertyType);
|
||||
IsArray = !elementType.Equals(descriptor.PropertyType);
|
||||
// TODO: What should we do with nullable types here?
|
||||
ClrType = elementType;
|
||||
|
||||
AttributeCollection propertyAttributes = TypeDescriptorExtensions.ExplicitAttributes(descriptor);
|
||||
|
||||
// TODO, 336102, ReadOnlyAttribute for editability? RIA used EditableAttribute?
|
||||
ReadOnlyAttribute readonlyAttr = (ReadOnlyAttribute)propertyAttributes[typeof(ReadOnlyAttribute)];
|
||||
IsReadOnly = (readonlyAttr != null) ? readonlyAttr.IsReadOnly : false;
|
||||
|
||||
AssociationAttribute associationAttr = (AssociationAttribute)propertyAttributes[typeof(AssociationAttribute)];
|
||||
if (associationAttr != null)
|
||||
{
|
||||
Association = new TypePropertyAssociationMetadata(associationAttr);
|
||||
}
|
||||
|
||||
RequiredAttribute requiredAttribute = (RequiredAttribute)propertyAttributes[typeof(RequiredAttribute)];
|
||||
if (requiredAttribute != null)
|
||||
{
|
||||
_validationRules.Add(new TypePropertyValidationRuleMetadata(requiredAttribute));
|
||||
}
|
||||
|
||||
RangeAttribute rangeAttribute = (RangeAttribute)propertyAttributes[typeof(RangeAttribute)];
|
||||
if (rangeAttribute != null)
|
||||
{
|
||||
Type operandType = rangeAttribute.OperandType;
|
||||
operandType = Nullable.GetUnderlyingType(operandType) ?? operandType;
|
||||
if (operandType.Equals(typeof(Double))
|
||||
|| operandType.Equals(typeof(Int16))
|
||||
|| operandType.Equals(typeof(Int32))
|
||||
|| operandType.Equals(typeof(Int64))
|
||||
|| operandType.Equals(typeof(Single)))
|
||||
{
|
||||
_validationRules.Add(new TypePropertyValidationRuleMetadata(rangeAttribute));
|
||||
}
|
||||
}
|
||||
|
||||
StringLengthAttribute stringLengthAttribute = (StringLengthAttribute)propertyAttributes[typeof(StringLengthAttribute)];
|
||||
if (stringLengthAttribute != null)
|
||||
{
|
||||
_validationRules.Add(new TypePropertyValidationRuleMetadata(stringLengthAttribute));
|
||||
}
|
||||
|
||||
DataTypeAttribute dataTypeAttribute = (DataTypeAttribute)propertyAttributes[typeof(DataTypeAttribute)];
|
||||
if (dataTypeAttribute != null)
|
||||
{
|
||||
if (dataTypeAttribute.DataType.Equals(DataType.EmailAddress)
|
||||
|| dataTypeAttribute.DataType.Equals(DataType.Url))
|
||||
{
|
||||
_validationRules.Add(new TypePropertyValidationRuleMetadata(dataTypeAttribute));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
public Type ClrType { get; private set; }
|
||||
public bool IsReadOnly { get; private set; }
|
||||
public bool IsArray { get; private set; }
|
||||
public TypePropertyAssociationMetadata Association { get; private set; }
|
||||
|
||||
public IList<TypePropertyValidationRuleMetadata> ValidationRules
|
||||
{
|
||||
get { return _validationRules; }
|
||||
}
|
||||
|
||||
public JToken ToJToken()
|
||||
{
|
||||
JObject value = new JObject();
|
||||
|
||||
value[MetadataStrings.TypeString] = EncodeTypeName(ClrType);
|
||||
|
||||
if (IsReadOnly)
|
||||
{
|
||||
value[MetadataStrings.ReadOnlyString] = true;
|
||||
}
|
||||
|
||||
if (IsArray)
|
||||
{
|
||||
value[MetadataStrings.ArrayString] = true;
|
||||
}
|
||||
|
||||
if (Association != null)
|
||||
{
|
||||
value[MetadataStrings.AssociationString] = Association.ToJToken();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public class TypePropertyValidationRuleMetadata
|
||||
{
|
||||
private string _type;
|
||||
|
||||
public TypePropertyValidationRuleMetadata(RequiredAttribute attribute)
|
||||
: this((ValidationAttribute)attribute)
|
||||
{
|
||||
Name = "required";
|
||||
Value1 = true;
|
||||
_type = "boolean";
|
||||
}
|
||||
|
||||
public TypePropertyValidationRuleMetadata(RangeAttribute attribute)
|
||||
: this((ValidationAttribute)attribute)
|
||||
{
|
||||
Name = "range";
|
||||
Value1 = attribute.Minimum;
|
||||
Value2 = attribute.Maximum;
|
||||
_type = "array";
|
||||
}
|
||||
|
||||
public TypePropertyValidationRuleMetadata(StringLengthAttribute attribute)
|
||||
: this((ValidationAttribute)attribute)
|
||||
{
|
||||
if (attribute.MinimumLength != 0)
|
||||
{
|
||||
Name = "rangelength";
|
||||
Value1 = attribute.MinimumLength;
|
||||
Value2 = attribute.MaximumLength;
|
||||
_type = "array";
|
||||
}
|
||||
else
|
||||
{
|
||||
Name = "maxlength";
|
||||
Value1 = attribute.MaximumLength;
|
||||
_type = "number";
|
||||
}
|
||||
}
|
||||
|
||||
public TypePropertyValidationRuleMetadata(DataTypeAttribute attribute)
|
||||
: this((ValidationAttribute)attribute)
|
||||
{
|
||||
switch (attribute.DataType)
|
||||
{
|
||||
case DataType.EmailAddress:
|
||||
Name = "email";
|
||||
break;
|
||||
case DataType.Url:
|
||||
Name = "url";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
Value1 = true;
|
||||
_type = "boolean";
|
||||
}
|
||||
|
||||
public TypePropertyValidationRuleMetadata(ValidationAttribute attribute)
|
||||
{
|
||||
if (attribute.ErrorMessage != null)
|
||||
{
|
||||
ErrorMessageString = attribute.ErrorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
public object Value1 { get; private set; }
|
||||
public object Value2 { get; private set; }
|
||||
public string ErrorMessageString { get; private set; }
|
||||
|
||||
public JToken ToJToken()
|
||||
{
|
||||
// The output json is determined by the number of values. The object constructor takes care the value assignment.
|
||||
// When we have two values, we have two numbers that are written as an array.
|
||||
// When we have only one value, it is written as it's type only.
|
||||
if (_type == "array")
|
||||
{
|
||||
return new JArray() { new JValue(Value1), new JValue(Value2) };
|
||||
}
|
||||
else if (_type == "boolean")
|
||||
{
|
||||
return (bool)Value1;
|
||||
}
|
||||
else if (_type == "number")
|
||||
{
|
||||
return (int)Value1;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unexpected validation rule type.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
//
|
||||
// To add a suppression to this file, right-click the message in the
|
||||
// Error List, point to "Suppress Message(s)", and click
|
||||
// "In Project Suppression File".
|
||||
// You do not need to add suppressions to this file manually.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Assembly is delay signed")]
|
||||
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Web.Http.Data.Helpers", Justification = "There are just a few helpers for client generation.")]
|
||||
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Web.Http.Data.TypeDescriptorExtensions.#ContainsAttributeType`1(System.ComponentModel.AttributeCollection)", Justification = "Used in Microsoft.Web.Http.Data assembly")]
|
||||
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Web.Http.Data.TypeUtility.#GetKnownTypes(System.Type,System.Boolean)", Justification = "Used in Microsoft.Web.Http.Data assembly")]
|
||||
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Web.Http.Data.TypeUtility.#UnwrapTaskInnerType(System.Type)", Justification = "Used in Microsoft.Web.Http.Data assembly")]
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.Helpers
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static class MetadataExtensions
|
||||
{
|
||||
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Following established design pattern for HTML helpers.")]
|
||||
public static IHtmlString Metadata<TDataController>(this HtmlHelper htmlHelper) where TDataController : DataController
|
||||
{
|
||||
HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor
|
||||
{
|
||||
Configuration = GlobalConfiguration.Configuration, // This helper can't be run until after global app init.
|
||||
ControllerType = typeof(TDataController)
|
||||
};
|
||||
|
||||
DataControllerDescription description = DataControllerDescription.GetDescription(controllerDescriptor);
|
||||
IEnumerable<DataControllerMetadataGenerator.TypeMetadata> metadata =
|
||||
DataControllerMetadataGenerator.GetMetadata(description);
|
||||
|
||||
JToken metadataValue = new JObject(metadata.Select(
|
||||
m => new JProperty(m.EncodedTypeName, m.ToJToken())));
|
||||
|
||||
return htmlHelper.Raw(metadataValue);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{B6895A1B-382F-4A69-99EC-E965E19B0AB3}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Microsoft.Web.Http.Data.Helpers</RootNamespace>
|
||||
<AssemblyName>Microsoft.Web.Http.Data.Helpers</AssemblyName>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
<IsStyleCopEnabled>false</IsStyleCopEnabled>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\..\bin\Debug\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG;CODE_ANALYSIS;ASPNETMVC</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<DocumentationFile>$(OutputPath)$(AssemblyName).xml</DocumentationFile>
|
||||
<RunCodeAnalysis>$(CodeAnalysis)</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\..\bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;ASPNETMVC</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<DocumentationFile>$(OutputPath)$(AssemblyName).xml</DocumentationFile>
|
||||
<RunCodeAnalysis>$(CodeAnalysis)</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeAnalysis|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\..\bin\CodeAnalysis\</OutputPath>
|
||||
<DefineConstants>TRACE;CODE_ANALYSIS;ASPNETMVC</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<DocumentationFile>$(OutputPath)$(AssemblyName).xml</DocumentationFile>
|
||||
<RunCodeAnalysis>$(CodeAnalysis)</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.4.5.6\lib\net40\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.configuration" />
|
||||
<Reference Include="System.Net.Http, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http.WebRequest, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Web" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\AptcaCommonAssemblyInfo.cs">
|
||||
<Link>Properties\AptcaCommonAssemblyInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\CommonAssemblyInfo.cs">
|
||||
<Link>Properties\CommonAssemblyInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="GlobalSuppressions.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="DataControllerMetadataGenerator.cs" />
|
||||
<Compile Include="MetadataExtensions.cs" />
|
||||
<Compile Include="..\Microsoft.Web.Http.Data\TypeUtility.cs">
|
||||
<Link>TypeUtility.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.Web.Http.Data\TypeDescriptorExtensions.cs">
|
||||
<Link>TypeDescriptorExtensions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="UpshotExtensions.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Web.Http.Data\Microsoft.Web.Http.Data.csproj">
|
||||
<Project>{ACE91549-D86E-4EB6-8C2A-5FF51386BB68}</Project>
|
||||
<Name>Microsoft.Web.Http.Data</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\System.Web.Http.WebHost\System.Web.Http.WebHost.csproj">
|
||||
<Project>{A0187BC2-8325-4BB2-8697-7F955CF4173E}</Project>
|
||||
<Name>System.Web.Http.WebHost</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\System.Web.Http\System.Web.Http.csproj">
|
||||
<Project>{DDC1CE0C-486E-4E35-BB3B-EAB61F8F9440}</Project>
|
||||
<Name>System.Web.Http</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\System.Web.Mvc\System.Web.Mvc.csproj">
|
||||
<Project>{3D3FFD8A-624D-4E9B-954B-E1C105507975}</Project>
|
||||
<Name>System.Web.Mvc</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CodeAnalysisDictionary Include="..\CodeAnalysisDictionary.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
|
||||
</Project>
|
|
@ -1,8 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: AssemblyTitle("Microsoft.Web.Http.Data.Helpers")]
|
||||
[assembly: AssemblyDescription("Microsoft.Web.Http.Data.Helpers")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.Web.Http.Data.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
|
|
@ -1,319 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Mvc;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.Helpers
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static class UpshotExtensions
|
||||
{
|
||||
public static UpshotConfigBuilder UpshotContext(this HtmlHelper htmlHelper)
|
||||
{
|
||||
return UpshotContext(htmlHelper, false);
|
||||
}
|
||||
|
||||
public static UpshotConfigBuilder UpshotContext(this HtmlHelper htmlHelper, bool bufferChanges)
|
||||
{
|
||||
return new UpshotConfigBuilder(htmlHelper, bufferChanges);
|
||||
}
|
||||
}
|
||||
|
||||
public class UpshotConfigBuilder : IHtmlString
|
||||
{
|
||||
private readonly HtmlHelper htmlHelper;
|
||||
private readonly bool bufferChanges;
|
||||
private readonly IDictionary<string, IDataSourceConfig> dataSources = new Dictionary<string, IDataSourceConfig>();
|
||||
private readonly IDictionary<Type, string> clientMappings = new Dictionary<Type, string>();
|
||||
|
||||
public UpshotConfigBuilder(HtmlHelper htmlHelper, bool bufferChanges)
|
||||
{
|
||||
this.htmlHelper = htmlHelper;
|
||||
this.bufferChanges = bufferChanges;
|
||||
}
|
||||
|
||||
private interface IDataSourceConfig
|
||||
{
|
||||
string ClientName { get; }
|
||||
Type DataControllerType { get; }
|
||||
string SharedDataContextExpression { get; }
|
||||
string DataContextExpression { set; }
|
||||
string ClientMappingsJson { set; }
|
||||
string GetInitializationScript();
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Following established design pattern for HTML helpers.")]
|
||||
public UpshotConfigBuilder DataSource<TDataController>(Expression<Func<TDataController, object>> queryOperation) where TDataController : DataController
|
||||
{
|
||||
return this.DataSource<TDataController>(queryOperation, null, null);
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "Following established design pattern for HTML helpers."),
|
||||
System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Following established design pattern for HTML helpers.")]
|
||||
public UpshotConfigBuilder DataSource<TDataController>(Expression<Func<TDataController, object>> queryOperation, string serviceUrl, string clientName) where TDataController : DataController
|
||||
{
|
||||
IDataSourceConfig dataSourceConfig = new DataSourceConfig<TDataController>(htmlHelper, bufferChanges, queryOperation, serviceUrl, clientName);
|
||||
if (dataSources.ContainsKey(dataSourceConfig.ClientName))
|
||||
{
|
||||
throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "Cannot have multiple data sources with the same clientName. Found multiple data sources with the name '{0}'", dataSourceConfig.ClientName));
|
||||
}
|
||||
dataSources.Add(dataSourceConfig.ClientName, dataSourceConfig);
|
||||
return this;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Following established design pattern for HTML helpers.")]
|
||||
public UpshotConfigBuilder ClientMapping<TEntity>(string clientConstructor)
|
||||
{
|
||||
if (String.IsNullOrEmpty(clientConstructor))
|
||||
{
|
||||
throw new ArgumentException("clientConstructor cannot be null or empty", "clientConstructor");
|
||||
}
|
||||
if (clientMappings.ContainsKey(typeof(TEntity)))
|
||||
{
|
||||
throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "Cannot have multiple client mappings for the same entity type. Found multiple client mappings for '{0}'", typeof(TEntity).FullName));
|
||||
}
|
||||
clientMappings.Add(typeof(TEntity), clientConstructor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public string ToHtmlString()
|
||||
{
|
||||
StringBuilder js = new StringBuilder("upshot.dataSources = upshot.dataSources || {};\n");
|
||||
|
||||
// First emit metadata for each referenced DataController
|
||||
IEnumerable<Type> dataControllerTypes = dataSources.Select(x => x.Value.DataControllerType).Distinct();
|
||||
foreach (Type dataControllerType in dataControllerTypes)
|
||||
{
|
||||
js.AppendFormat("upshot.metadata({0});\n", GetMetadata(dataControllerType));
|
||||
}
|
||||
|
||||
// Let the first dataSource construct a dataContext, and all subsequent ones share it
|
||||
IEnumerable<IDataSourceConfig> allDataSources = dataSources.Values;
|
||||
IDataSourceConfig firstDataSource = allDataSources.FirstOrDefault();
|
||||
if (firstDataSource != null)
|
||||
{
|
||||
// All but the first data source share the DataContext implicitly instantiated by the first.
|
||||
foreach (IDataSourceConfig dataSource in allDataSources.Skip(1))
|
||||
{
|
||||
dataSource.DataContextExpression = firstDataSource.SharedDataContextExpression;
|
||||
}
|
||||
|
||||
// Let the first dataSource define the client mappings
|
||||
firstDataSource.ClientMappingsJson = GetClientMappingsObjectLiteral();
|
||||
}
|
||||
|
||||
// Now emit initialization code for each dataSource
|
||||
foreach (IDataSourceConfig dataSource in allDataSources)
|
||||
{
|
||||
js.AppendLine("\n" + dataSource.GetInitializationScript());
|
||||
}
|
||||
|
||||
// Also record the mapping functions in use
|
||||
foreach (var mapping in clientMappings)
|
||||
{
|
||||
js.AppendFormat("upshot.registerType(\"{0}\", function() {{ return {1} }});\n", EncodeServerTypeName(mapping.Key), mapping.Value);
|
||||
}
|
||||
|
||||
return String.Format(CultureInfo.InvariantCulture, "<script type='text/javascript'>\n{0}</script>", js);
|
||||
}
|
||||
|
||||
private string GetMetadata(Type dataControllerType)
|
||||
{
|
||||
var methodInfo = typeof(MetadataExtensions).GetMethod("Metadata");
|
||||
var result = (IHtmlString)methodInfo.MakeGenericMethod(dataControllerType).Invoke(null, new[] { htmlHelper });
|
||||
return result.ToHtmlString();
|
||||
}
|
||||
|
||||
private string GetClientMappingsObjectLiteral()
|
||||
{
|
||||
IEnumerable<string> clientMappingStrings =
|
||||
clientMappings.Select(
|
||||
clientMapping => String.Format(CultureInfo.InvariantCulture, "\"{0}\": function(data) {{ return new {1}(data) }}", EncodeServerTypeName(clientMapping.Key), clientMapping.Value));
|
||||
return String.Format(CultureInfo.InvariantCulture, "{{{0}}}", String.Join(",", clientMappingStrings));
|
||||
}
|
||||
|
||||
private static string EncodeServerTypeName(Type type)
|
||||
{
|
||||
return DataControllerMetadataGenerator.EncodeTypeName(type);
|
||||
}
|
||||
|
||||
private class DataSourceConfig<TDataController> : IDataSourceConfig where TDataController : DataController
|
||||
{
|
||||
private readonly HtmlHelper htmlHelper;
|
||||
private readonly bool bufferChanges;
|
||||
private readonly Expression<Func<TDataController, object>> queryOperation;
|
||||
private readonly string serviceUrlOverride;
|
||||
private readonly string clientName;
|
||||
|
||||
public DataSourceConfig(HtmlHelper htmlHelper, bool bufferChanges, Expression<Func<TDataController, object>> queryOperation, string serviceUrlOverride, string clientName)
|
||||
{
|
||||
this.htmlHelper = htmlHelper;
|
||||
this.bufferChanges = bufferChanges;
|
||||
this.queryOperation = queryOperation;
|
||||
this.serviceUrlOverride = serviceUrlOverride;
|
||||
this.clientName = String.IsNullOrEmpty(clientName) ? DefaultClientName : clientName;
|
||||
}
|
||||
|
||||
public string ClientName
|
||||
{
|
||||
get
|
||||
{
|
||||
return clientName;
|
||||
}
|
||||
}
|
||||
|
||||
public Type DataControllerType
|
||||
{
|
||||
get
|
||||
{
|
||||
return typeof(TDataController);
|
||||
}
|
||||
}
|
||||
|
||||
public string DataContextExpression { private get; set; }
|
||||
|
||||
public string ClientMappingsJson { private get; set; }
|
||||
|
||||
public string SharedDataContextExpression
|
||||
{
|
||||
get
|
||||
{
|
||||
return ClientExpression + ".getDataContext()";
|
||||
}
|
||||
}
|
||||
|
||||
private string ClientExpression
|
||||
{
|
||||
get
|
||||
{
|
||||
return "upshot.dataSources." + ClientName;
|
||||
}
|
||||
}
|
||||
|
||||
private Type EntityType
|
||||
{
|
||||
get
|
||||
{
|
||||
Type operationReturnType = OperationMethod.ReturnType;
|
||||
Type genericTypeDefinition = operationReturnType.IsGenericType ? operationReturnType.GetGenericTypeDefinition() : null;
|
||||
Type entityType;
|
||||
if (genericTypeDefinition != null && (genericTypeDefinition == typeof(IQueryable<>) || genericTypeDefinition == typeof(IEnumerable<>)))
|
||||
{
|
||||
// Permits IQueryable<TEntity> and IEnumerable<TEntity>.
|
||||
entityType = operationReturnType.GetGenericArguments().Single();
|
||||
}
|
||||
else
|
||||
{
|
||||
entityType = operationReturnType;
|
||||
}
|
||||
|
||||
if (!Description.EntityTypes.Any(type => type == entityType))
|
||||
{
|
||||
throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "queryOperation '{0}' must return an entity type or an IEnumerable/IQueryable of an entity type", OperationMethod.Name));
|
||||
}
|
||||
|
||||
return entityType;
|
||||
}
|
||||
}
|
||||
|
||||
private string ServiceUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!String.IsNullOrEmpty(serviceUrlOverride))
|
||||
{
|
||||
return serviceUrlOverride;
|
||||
}
|
||||
|
||||
UrlHelper urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
|
||||
string dataControllerName = typeof(TDataController).Name;
|
||||
if (!dataControllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new ArgumentException("DataController type name must end with 'Controller'");
|
||||
}
|
||||
string controllerRouteName = dataControllerName.Substring(0, dataControllerName.Length - "Controller".Length);
|
||||
return urlHelper.RouteUrl(new { controller = controllerRouteName, action = UrlParameter.Optional, httproute = true });
|
||||
}
|
||||
}
|
||||
|
||||
private string DefaultClientName
|
||||
{
|
||||
get
|
||||
{
|
||||
string operationName = OperationMethod.Name;
|
||||
// By convention, strip away any "Get" verb on the method. Clients can override by explictly specifying client name.
|
||||
return operationName.StartsWith("Get", StringComparison.OrdinalIgnoreCase) && operationName.Length > 3 && Char.IsLetter(operationName[3]) ? operationName.Substring(3) : operationName;
|
||||
}
|
||||
}
|
||||
|
||||
private MethodInfo OperationMethod
|
||||
{
|
||||
get
|
||||
{
|
||||
Expression body = queryOperation.Body;
|
||||
|
||||
// The VB compiler will inject a convert to object here.
|
||||
if (body.NodeType == ExpressionType.Convert)
|
||||
{
|
||||
UnaryExpression convert = (UnaryExpression)body;
|
||||
if (convert.Type == typeof(object))
|
||||
{
|
||||
body = convert.Operand;
|
||||
}
|
||||
}
|
||||
|
||||
MethodCallExpression methodCall = body as MethodCallExpression;
|
||||
if (methodCall == null)
|
||||
{
|
||||
throw new ArgumentException("queryOperation must be a method call");
|
||||
}
|
||||
|
||||
if (!methodCall.Method.DeclaringType.IsAssignableFrom(typeof(TDataController)))
|
||||
{
|
||||
throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "queryOperation must be a method on '{0}' or a base type", typeof(TDataController).Name));
|
||||
}
|
||||
|
||||
return methodCall.Method;
|
||||
}
|
||||
}
|
||||
|
||||
private static DataControllerDescription Description
|
||||
{
|
||||
get
|
||||
{
|
||||
HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor
|
||||
{
|
||||
Configuration = GlobalConfiguration.Configuration, // This helper can't be run until after global app init.
|
||||
ControllerType = typeof(TDataController)
|
||||
};
|
||||
|
||||
DataControllerDescription description = DataControllerDescription.GetDescription(controllerDescriptor);
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetInitializationScript()
|
||||
{
|
||||
return String.Format(CultureInfo.InvariantCulture, @"{0} = upshot.RemoteDataSource({{
|
||||
providerParameters: {{ url: ""{1}"", operationName: ""{2}"" }},
|
||||
entityType: ""{3}"",
|
||||
bufferChanges: {4},
|
||||
dataContext: {5},
|
||||
mapping: {6}
|
||||
}});",
|
||||
ClientExpression, ServiceUrl, OperationMethod.Name, EncodeServerTypeName(EntityType),
|
||||
bufferChanges ? "true" : "false", DataContextExpression ?? "undefined", ClientMappingsJson ?? "undefined");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net40" />
|
||||
<package id="Newtonsoft.Json" version="4.5.6" targetFramework="net40" />
|
||||
</packages>
|
|
@ -1,35 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of the types of operations a <see cref="DataController"/> can perform.
|
||||
/// </summary>
|
||||
public enum ChangeOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that no operation is to be performed
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates an operation that inserts new data
|
||||
/// </summary>
|
||||
Insert,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates an operation that updates existing data
|
||||
/// </summary>
|
||||
Update,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates an operation that deletes existing data
|
||||
/// </summary>
|
||||
Delete,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a custom update operation
|
||||
/// </summary>
|
||||
Custom
|
||||
}
|
||||
}
|
|
@ -1,337 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a set of changes to be processed by a <see cref="DataController"/>.
|
||||
/// </summary>
|
||||
public sealed class ChangeSet
|
||||
{
|
||||
private IEnumerable<ChangeSetEntry> _changeSetEntries;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ChangeSet class
|
||||
/// </summary>
|
||||
/// <param name="changeSetEntries">The set of <see cref="ChangeSetEntry"/> items this <see cref="ChangeSet"/> represents.</param>
|
||||
/// <exception cref="ArgumentNullException">if <paramref name="changeSetEntries"/> is null.</exception>
|
||||
public ChangeSet(IEnumerable<ChangeSetEntry> changeSetEntries)
|
||||
{
|
||||
if (changeSetEntries == null)
|
||||
{
|
||||
throw Error.ArgumentNull("changeSetEntries");
|
||||
}
|
||||
|
||||
// ensure the changeset is valid
|
||||
ValidateChangeSetEntries(changeSetEntries);
|
||||
|
||||
_changeSetEntries = changeSetEntries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of <see cref="ChangeSetEntry"/> items this <see cref="ChangeSet"/> represents.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<ChangeSetEntry> ChangeSetEntries
|
||||
{
|
||||
get { return _changeSetEntries.ToList().AsReadOnly(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether any of the <see cref="ChangeSetEntry"/> items has an error.
|
||||
/// </summary>
|
||||
public bool HasError
|
||||
{
|
||||
get { return _changeSetEntries.Any(op => op.HasConflict || (op.ValidationErrors != null && op.ValidationErrors.Any())); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the original unmodified entity for the provided <paramref name="clientEntity"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that only members marked with <see cref="RoundtripOriginalAttribute"/> will be set
|
||||
/// in the returned instance.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TEntity">The entity type.</typeparam>
|
||||
/// <param name="clientEntity">The client modified entity.</param>
|
||||
/// <returns>The original unmodified entity for the provided <paramref name="clientEntity"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException">if <paramref name="clientEntity"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">if <paramref name="clientEntity"/> is not in the change set.</exception>
|
||||
public TEntity GetOriginal<TEntity>(TEntity clientEntity) where TEntity : class
|
||||
{
|
||||
if (clientEntity == null)
|
||||
{
|
||||
throw Error.ArgumentNull("clientEntity");
|
||||
}
|
||||
|
||||
ChangeSetEntry entry = _changeSetEntries.FirstOrDefault(p => Object.ReferenceEquals(p.Entity, clientEntity));
|
||||
if (entry == null)
|
||||
{
|
||||
throw Error.Argument(Resource.ChangeSet_ChangeSetEntryNotFound);
|
||||
}
|
||||
|
||||
if (entry.Operation == ChangeOperation.Insert)
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.ChangeSet_OriginalNotValidForInsert);
|
||||
}
|
||||
|
||||
return (TEntity)entry.OriginalEntity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the specified entries are well formed.
|
||||
/// </summary>
|
||||
/// <param name="changeSetEntries">The changeset entries to validate.</param>
|
||||
private static void ValidateChangeSetEntries(IEnumerable<ChangeSetEntry> changeSetEntries)
|
||||
{
|
||||
HashSet<int> idSet = new HashSet<int>();
|
||||
HashSet<object> entitySet = new HashSet<object>();
|
||||
foreach (ChangeSetEntry entry in changeSetEntries)
|
||||
{
|
||||
// ensure Entity is not null
|
||||
if (entry.Entity == null)
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.InvalidChangeSet, Resource.InvalidChangeSet_NullEntity);
|
||||
}
|
||||
|
||||
// ensure unique client IDs
|
||||
if (idSet.Contains(entry.Id))
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.InvalidChangeSet, Resource.InvalidChangeSet_DuplicateId);
|
||||
}
|
||||
idSet.Add(entry.Id);
|
||||
|
||||
// ensure unique entity instances - there can only be a single entry
|
||||
// for a given entity instance
|
||||
if (entitySet.Contains(entry.Entity))
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.InvalidChangeSet, Resource.InvalidChangeSet_DuplicateEntity);
|
||||
}
|
||||
entitySet.Add(entry.Entity);
|
||||
|
||||
// entities must be of the same type
|
||||
if (entry.OriginalEntity != null && !(entry.Entity.GetType() == entry.OriginalEntity.GetType()))
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.InvalidChangeSet, Resource.InvalidChangeSet_MustBeSameType);
|
||||
}
|
||||
|
||||
if (entry.Operation == ChangeOperation.Insert && entry.OriginalEntity != null)
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.InvalidChangeSet, Resource.InvalidChangeSet_InsertsCantHaveOriginal);
|
||||
}
|
||||
}
|
||||
|
||||
// now that we have the full Id space, we can validate associations
|
||||
foreach (ChangeSetEntry entry in changeSetEntries)
|
||||
{
|
||||
if (entry.Associations != null)
|
||||
{
|
||||
ValidateAssociationMap(entry.Entity.GetType(), idSet, entry.Associations);
|
||||
}
|
||||
|
||||
if (entry.OriginalAssociations != null)
|
||||
{
|
||||
ValidateAssociationMap(entry.Entity.GetType(), idSet, entry.OriginalAssociations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the specified association map.
|
||||
/// </summary>
|
||||
/// <param name="entityType">The entity type the association is on.</param>
|
||||
/// <param name="idSet">The set of all unique Ids in the changeset.</param>
|
||||
/// <param name="associationMap">The association map to validate.</param>
|
||||
private static void ValidateAssociationMap(Type entityType, HashSet<int> idSet, IDictionary<string, int[]> associationMap)
|
||||
{
|
||||
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(entityType);
|
||||
|
||||
foreach (var associationItem in associationMap)
|
||||
{
|
||||
// ensure that the member is an association member
|
||||
string associationMemberName = associationItem.Key;
|
||||
PropertyDescriptor associationMember = properties[associationMemberName];
|
||||
if (associationMember == null || associationMember.Attributes[typeof(AssociationAttribute)] == null)
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.InvalidChangeSet,
|
||||
String.Format(CultureInfo.CurrentCulture, Resource.InvalidChangeSet_InvalidAssociationMember, entityType, associationMemberName));
|
||||
}
|
||||
|
||||
// ensure that the id collection is not null
|
||||
if (associationItem.Value == null)
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.InvalidChangeSet,
|
||||
String.Format(CultureInfo.CurrentCulture, Resource.InvalidChangeSet_AssociatedIdsCannotBeNull, entityType, associationMemberName));
|
||||
}
|
||||
// ensure that each Id specified is in the changeset
|
||||
foreach (int id in associationItem.Value)
|
||||
{
|
||||
if (!idSet.Contains(id))
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.InvalidChangeSet,
|
||||
String.Format(CultureInfo.CurrentCulture, Resource.InvalidChangeSet_AssociatedIdNotInChangeset, id, entityType, associationMemberName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reestablish associations based on Id lists by adding the referenced entities
|
||||
/// to their association members
|
||||
/// </summary>
|
||||
internal void SetEntityAssociations()
|
||||
{
|
||||
// create a unique map from Id to entity instances, and update operations
|
||||
// so Ids map to the same instances, since during deserialization reference
|
||||
// identity is not maintained.
|
||||
var entityIdMap = _changeSetEntries.ToDictionary(p => p.Id, p => new { Entity = p.Entity, OriginalEntity = p.OriginalEntity });
|
||||
foreach (ChangeSetEntry changeSetEntry in _changeSetEntries)
|
||||
{
|
||||
object entity = entityIdMap[changeSetEntry.Id].Entity;
|
||||
if (changeSetEntry.Entity != entity)
|
||||
{
|
||||
changeSetEntry.Entity = entity;
|
||||
}
|
||||
|
||||
object original = entityIdMap[changeSetEntry.Id].OriginalEntity;
|
||||
if (original != null && changeSetEntry.OriginalEntity != original)
|
||||
{
|
||||
changeSetEntry.OriginalEntity = original;
|
||||
}
|
||||
}
|
||||
|
||||
// for all entities with associations, reestablish the associations by mapping the Ids
|
||||
// to entity instances and adding them to the association members
|
||||
HashSet<int> visited = new HashSet<int>();
|
||||
foreach (var entityGroup in _changeSetEntries.Where(p => (p.Associations != null && p.Associations.Count > 0) || (p.OriginalAssociations != null && p.OriginalAssociations.Count > 0)).GroupBy(p => p.Entity.GetType()))
|
||||
{
|
||||
Dictionary<string, PropertyDescriptor> associationMemberMap = TypeDescriptor.GetProperties(entityGroup.Key).Cast<PropertyDescriptor>().Where(p => p.Attributes[typeof(AssociationAttribute)] != null).ToDictionary(p => p.Name);
|
||||
foreach (ChangeSetEntry changeSetEntry in entityGroup)
|
||||
{
|
||||
if (visited.Contains(changeSetEntry.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
visited.Add(changeSetEntry.Id);
|
||||
|
||||
// set current associations
|
||||
if (changeSetEntry.Associations != null)
|
||||
{
|
||||
foreach (var associationItem in changeSetEntry.Associations)
|
||||
{
|
||||
PropertyDescriptor assocMember = associationMemberMap[associationItem.Key];
|
||||
IEnumerable<object> children = associationItem.Value.Select(p => entityIdMap[p].Entity);
|
||||
SetAssociationMember(changeSetEntry.Entity, assocMember, children);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal bool Validate(HttpActionContext actionContext)
|
||||
{
|
||||
// Validate all entries except those with type None or Delete (since we don't want to validate
|
||||
// entites we're going to delete).
|
||||
bool success = true;
|
||||
IEnumerable<ChangeSetEntry> entriesToValidate = ChangeSetEntries.Where(
|
||||
p => (p.ActionDescriptor != null && p.Operation != ChangeOperation.None && p.Operation != ChangeOperation.Delete)
|
||||
|| (p.EntityActions != null && p.EntityActions.Any()));
|
||||
|
||||
foreach (ChangeSetEntry entry in entriesToValidate)
|
||||
{
|
||||
// TODO: optimize by determining whether a type actually requires any validation?
|
||||
// TODO: support for method level / parameter validation?
|
||||
|
||||
List<ValidationResultInfo> validationErrors = new List<ValidationResultInfo>();
|
||||
if (!DataControllerValidation.ValidateObject(entry.Entity, validationErrors, actionContext))
|
||||
{
|
||||
entry.ValidationErrors = validationErrors.Distinct(EqualityComparer<ValidationResultInfo>.Default).ToList();
|
||||
success = false;
|
||||
}
|
||||
|
||||
// clear after each validate call, since we've already
|
||||
// copied over the errors
|
||||
actionContext.ModelState.Clear();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified associated entities to the specified association member for the specified entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity</param>
|
||||
/// <param name="associationProperty">The association member (singleton or collection)</param>
|
||||
/// <param name="associatedEntities">Collection of associated entities</param>
|
||||
private static void SetAssociationMember(object entity, PropertyDescriptor associationProperty, IEnumerable<object> associatedEntities)
|
||||
{
|
||||
if (associatedEntities.Count() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
object associationValue = associationProperty.GetValue(entity);
|
||||
if (typeof(IEnumerable).IsAssignableFrom(associationProperty.PropertyType))
|
||||
{
|
||||
if (associationValue == null)
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.DataController_AssociationCollectionPropertyIsNull, associationProperty.ComponentType.Name, associationProperty.Name);
|
||||
}
|
||||
|
||||
IList list = associationValue as IList;
|
||||
IEnumerable<object> associationSequence = null;
|
||||
MethodInfo addMethod = null;
|
||||
if (list == null)
|
||||
{
|
||||
// not an IList, so we have to use reflection
|
||||
Type associatedEntityType = TypeUtility.GetElementType(associationValue.GetType());
|
||||
addMethod = associationValue.GetType().GetMethod("Add", BindingFlags.Public | BindingFlags.Instance, null, new Type[] { associatedEntityType }, null);
|
||||
if (addMethod == null)
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.DataController_InvalidCollectionMember, associationProperty.Name);
|
||||
}
|
||||
associationSequence = ((IEnumerable)associationValue).Cast<object>();
|
||||
}
|
||||
|
||||
foreach (object associatedEntity in associatedEntities)
|
||||
{
|
||||
// add the entity to the collection if it's not already there
|
||||
if (list != null)
|
||||
{
|
||||
if (!list.Contains(associatedEntity))
|
||||
{
|
||||
list.Add(associatedEntity);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!associationSequence.Contains(associatedEntity))
|
||||
{
|
||||
addMethod.Invoke(associationValue, new object[] { associatedEntity });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// set the reference if it's not already set
|
||||
object associatedEntity = associatedEntities.Single();
|
||||
object currentValue = associationProperty.GetValue(entity);
|
||||
if (!Object.Equals(currentValue, associatedEntity))
|
||||
{
|
||||
associationProperty.SetValue(entity, associatedEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a change operation to be performed on an entity.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[DebuggerDisplay("Operation = {Operation}, Type = {Entity.GetType().Name}")]
|
||||
public sealed class ChangeSetEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the client ID for the entity
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ChangeOperation"/> to be performed on the entity.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public ChangeOperation Operation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Entity"/> being operated on
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public object Entity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the original state of the entity being operated on
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public object OriginalEntity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the state of the entity in the data store
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public object StoreEntity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom methods invoked on the entity, as a set
|
||||
/// of method name / parameter set pairs.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public IDictionary<string, object[]> EntityActions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the validation errors encountered during the processing of the operation.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public IEnumerable<ValidationResultInfo> ValidationErrors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of members in conflict. The <see cref="StoreEntity"/> property
|
||||
/// contains the current store value for each member in conflict.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public IEnumerable<string> ConflictMembers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the conflict is a delete conflict, meaning the
|
||||
/// entity no longer exists in the store.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public bool IsDeleteConflict { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of IDs of the associated entities for
|
||||
/// each association of the Entity
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public IDictionary<string, int[]> Associations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of IDs for each association of the <see cref="OriginalEntity"/>
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public IDictionary<string, int[]> OriginalAssociations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the <see cref="ChangeSetEntry"/> contains conflicts.
|
||||
/// </summary>
|
||||
public bool HasConflict
|
||||
{
|
||||
get { return (IsDeleteConflict || (ConflictMembers != null && ConflictMembers.Any())); }
|
||||
}
|
||||
|
||||
public bool HasError
|
||||
{
|
||||
get { return HasConflict || (ValidationErrors != null && ValidationErrors.Any()); }
|
||||
}
|
||||
|
||||
internal UpdateActionDescriptor ActionDescriptor { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,318 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
[DataControllerConfiguration]
|
||||
public abstract class DataController : ApiController
|
||||
{
|
||||
private ChangeSet _changeSet;
|
||||
private DataControllerDescription _description;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current <see cref="ChangeSet"/>. Returns null if no change operations are being performed.
|
||||
/// </summary>
|
||||
protected ChangeSet ChangeSet
|
||||
{
|
||||
get { return _changeSet; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="DataControllerDescription"/> for this <see cref="DataController"/>.
|
||||
/// </summary>
|
||||
protected DataControllerDescription Description
|
||||
{
|
||||
get { return _description; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="HttpActionContext"/> for the currently executing action.
|
||||
/// </summary>
|
||||
protected internal HttpActionContext ActionContext { get; internal set; }
|
||||
|
||||
protected override void Initialize(HttpControllerContext controllerContext)
|
||||
{
|
||||
// ensure that the service is valid and all custom metadata providers
|
||||
// have been registered
|
||||
_description = DataControllerDescription.GetDescription(controllerContext.ControllerDescriptor);
|
||||
|
||||
base.Initialize(controllerContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the operations indicated by the specified <see cref="ChangeSet"/> by invoking
|
||||
/// the corresponding actions for each.
|
||||
/// </summary>
|
||||
/// <param name="changeSet">The changeset to submit</param>
|
||||
/// <returns>True if the submit was successful, false otherwise.</returns>
|
||||
public virtual bool Submit(ChangeSet changeSet)
|
||||
{
|
||||
if (changeSet == null)
|
||||
{
|
||||
throw Error.ArgumentNull("changeSet");
|
||||
}
|
||||
_changeSet = changeSet;
|
||||
|
||||
ResolveActions(_description, ChangeSet.ChangeSetEntries);
|
||||
|
||||
if (!AuthorizeChangeSet())
|
||||
{
|
||||
// Don't try to save if there were any errors.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Before invoking any operations, validate the entire changeset
|
||||
if (!ValidateChangeSet())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now that we're validated, proceed to invoke the actions.
|
||||
if (!ExecuteChangeSet())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// persist the changes
|
||||
if (!PersistChangeSetInternal())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For all operations in the current changeset, validate that the operation exists, and
|
||||
/// set the operation entry.
|
||||
/// </summary>
|
||||
internal static void ResolveActions(DataControllerDescription description, IEnumerable<ChangeSetEntry> changeSet)
|
||||
{
|
||||
// Resolve and set the action for each operation in the changeset
|
||||
foreach (ChangeSetEntry changeSetEntry in changeSet)
|
||||
{
|
||||
Type entityType = changeSetEntry.Entity.GetType();
|
||||
UpdateActionDescriptor actionDescriptor = null;
|
||||
if (changeSetEntry.Operation == ChangeOperation.Insert ||
|
||||
changeSetEntry.Operation == ChangeOperation.Update ||
|
||||
changeSetEntry.Operation == ChangeOperation.Delete)
|
||||
{
|
||||
actionDescriptor = description.GetUpdateAction(entityType, changeSetEntry.Operation);
|
||||
}
|
||||
|
||||
// if a custom method invocation is specified, validate that the method exists
|
||||
bool isCustomUpdate = false;
|
||||
if (changeSetEntry.EntityActions != null && changeSetEntry.EntityActions.Any())
|
||||
{
|
||||
var entityAction = changeSetEntry.EntityActions.Single();
|
||||
UpdateActionDescriptor customMethodOperation = description.GetCustomMethod(entityType, entityAction.Key);
|
||||
if (customMethodOperation == null)
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.DataController_InvalidAction, entityAction.Key, entityType.Name);
|
||||
}
|
||||
|
||||
// if the primary action for an update is null but the entry
|
||||
// contains a valid custom update action, its considered a "custom update"
|
||||
isCustomUpdate = actionDescriptor == null && customMethodOperation != null;
|
||||
}
|
||||
|
||||
if (actionDescriptor == null && !isCustomUpdate)
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.DataController_InvalidAction, changeSetEntry.Operation.ToString(), entityType.Name);
|
||||
}
|
||||
|
||||
changeSetEntry.ActionDescriptor = actionDescriptor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the user is authorized to submit the current <see cref="ChangeSet"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if the <see cref="ChangeSet"/> is authorized, false otherwise.</returns>
|
||||
protected virtual bool AuthorizeChangeSet()
|
||||
{
|
||||
foreach (ChangeSetEntry changeSetEntry in ChangeSet.ChangeSetEntries)
|
||||
{
|
||||
if (!changeSetEntry.ActionDescriptor.Authorize(ActionContext))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// if there are any custom method invocations for this operation
|
||||
// we need to authorize them as well
|
||||
if (changeSetEntry.EntityActions != null && changeSetEntry.EntityActions.Any())
|
||||
{
|
||||
Type entityType = changeSetEntry.Entity.GetType();
|
||||
foreach (var entityAction in changeSetEntry.EntityActions)
|
||||
{
|
||||
UpdateActionDescriptor customAction = Description.GetCustomMethod(entityType, entityAction.Key);
|
||||
if (!customAction.Authorize(ActionContext))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !ChangeSet.HasError;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the current <see cref="ChangeSet"/>. Any errors should be set on the individual <see cref="ChangeSetEntry"/>s
|
||||
/// in the <see cref="ChangeSet"/>.
|
||||
/// </summary>
|
||||
/// <returns><c>True</c> if all operations in the <see cref="ChangeSet"/> passed validation, <c>false</c> otherwise.</returns>
|
||||
protected virtual bool ValidateChangeSet()
|
||||
{
|
||||
return ChangeSet.Validate(ActionContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method invokes the action for each operation in the current <see cref="ChangeSet"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if the <see cref="ChangeSet"/> was processed successfully, false otherwise.</returns>
|
||||
protected virtual bool ExecuteChangeSet()
|
||||
{
|
||||
InvokeCUDOperations();
|
||||
InvokeCustomUpdateOperations();
|
||||
|
||||
return !ChangeSet.HasError;
|
||||
}
|
||||
|
||||
private void InvokeCUDOperations()
|
||||
{
|
||||
foreach (ChangeSetEntry changeSetEntry in ChangeSet.ChangeSetEntries
|
||||
.Where(op => op.Operation == ChangeOperation.Insert ||
|
||||
op.Operation == ChangeOperation.Update ||
|
||||
op.Operation == ChangeOperation.Delete))
|
||||
{
|
||||
if (changeSetEntry.ActionDescriptor == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
InvokeAction(changeSetEntry.ActionDescriptor, new object[] { changeSetEntry.Entity }, changeSetEntry);
|
||||
}
|
||||
}
|
||||
|
||||
private void InvokeCustomUpdateOperations()
|
||||
{
|
||||
foreach (ChangeSetEntry changeSetEntry in ChangeSet.ChangeSetEntries.Where(op => op.EntityActions != null && op.EntityActions.Any()))
|
||||
{
|
||||
Type entityType = changeSetEntry.Entity.GetType();
|
||||
foreach (var entityAction in changeSetEntry.EntityActions)
|
||||
{
|
||||
UpdateActionDescriptor customUpdateAction = Description.GetCustomMethod(entityType, entityAction.Key);
|
||||
|
||||
List<object> customMethodParams = new List<object>(entityAction.Value);
|
||||
customMethodParams.Insert(0, changeSetEntry.Entity);
|
||||
|
||||
InvokeAction(customUpdateAction, customMethodParams.ToArray(), changeSetEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InvokeAction(HttpActionDescriptor action, object[] parameters, ChangeSetEntry changeSetEntry)
|
||||
{
|
||||
try
|
||||
{
|
||||
Collection<HttpParameterDescriptor> pds = action.GetParameters();
|
||||
Dictionary<string, object> paramMap = new Dictionary<string, object>(pds.Count);
|
||||
for (int i = 0; i < pds.Count; i++)
|
||||
{
|
||||
paramMap.Add(pds[i].ParameterName, parameters[i]);
|
||||
}
|
||||
|
||||
// TODO - Issue #103
|
||||
// This method is not correctly observing the execution results, the catch block below is wrong.
|
||||
// Submit should be Task<bool>, not bool, and should model bind for the CancellationToken which would then
|
||||
// be propagated through to all the helper methods (one or more of which might also need to be made async,
|
||||
// once we start respecting the fact that the read/write actions should be allowed to be async).
|
||||
action.ExecuteAsync(ActionContext.ControllerContext, paramMap, CancellationToken.None);
|
||||
}
|
||||
catch (TargetInvocationException tie)
|
||||
{
|
||||
ValidationException vex = tie.GetBaseException() as ValidationException;
|
||||
if (vex != null)
|
||||
{
|
||||
ValidationResultInfo error = new ValidationResultInfo(vex.Message, 0, String.Empty, vex.ValidationResult.MemberNames);
|
||||
if (changeSetEntry.ValidationErrors != null)
|
||||
{
|
||||
changeSetEntry.ValidationErrors = changeSetEntry.ValidationErrors.Concat(new ValidationResultInfo[] { error }).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
changeSetEntry.ValidationErrors = new ValidationResultInfo[] { error };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is called to finalize changes after all the operations in the current <see cref="ChangeSet"/>
|
||||
/// have been invoked. This method should commit the changes as necessary to the data store.
|
||||
/// Any errors should be set on the individual <see cref="ChangeSetEntry"/>s in the <see cref="ChangeSet"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if the <see cref="ChangeSet"/> was persisted successfully, false otherwise.</returns>
|
||||
protected virtual bool PersistChangeSet()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method invokes the user overridable <see cref="PersistChangeSet"/> method wrapping the call
|
||||
/// with the appropriate exception handling logic. All framework calls to <see cref="PersistChangeSet"/>
|
||||
/// must go through this method. Some data sources have their own validation hook points,
|
||||
/// so if a <see cref="ValidationException"/> is thrown at that level, we want to capture it.
|
||||
/// </summary>
|
||||
/// <returns>True if the <see cref="ChangeSet"/> was persisted successfully, false otherwise.</returns>
|
||||
private bool PersistChangeSetInternal()
|
||||
{
|
||||
try
|
||||
{
|
||||
PersistChangeSet();
|
||||
}
|
||||
catch (ValidationException e)
|
||||
{
|
||||
// if a validation exception is thrown for one of the entities in the changeset
|
||||
// set the error on the corresponding ChangeSetEntry
|
||||
if (e.Value != null && e.ValidationResult != null)
|
||||
{
|
||||
IEnumerable<ChangeSetEntry> updateOperations =
|
||||
ChangeSet.ChangeSetEntries.Where(
|
||||
p => p.Operation == ChangeOperation.Insert ||
|
||||
p.Operation == ChangeOperation.Update ||
|
||||
p.Operation == ChangeOperation.Delete);
|
||||
|
||||
ChangeSetEntry operation = updateOperations.SingleOrDefault(p => Object.ReferenceEquals(p.Entity, e.Value));
|
||||
if (operation != null)
|
||||
{
|
||||
ValidationResultInfo error = new ValidationResultInfo(e.ValidationResult.ErrorMessage, e.ValidationResult.MemberNames);
|
||||
error.StackTrace = e.StackTrace;
|
||||
operation.ValidationErrors = new List<ValidationResultInfo>() { error };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return !ChangeSet.HasError;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http.Controllers;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
internal sealed class DataControllerActionInvoker : ApiControllerActionInvoker
|
||||
{
|
||||
public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
|
||||
{
|
||||
DataController controller = (DataController)actionContext.ControllerContext.Controller;
|
||||
controller.ActionContext = actionContext;
|
||||
return base.InvokeActionAsync(actionContext, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
internal sealed class DataControllerActionSelector : ApiControllerActionSelector
|
||||
{
|
||||
private const string ActionRouteKey = "action";
|
||||
private const string SubmitActionValue = "Submit";
|
||||
|
||||
public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
|
||||
{
|
||||
// first check to see if this is a call to Submit
|
||||
string actionName;
|
||||
if (controllerContext.RouteData.Values.TryGetValue(ActionRouteKey, out actionName) && actionName.Equals(SubmitActionValue, StringComparison.Ordinal))
|
||||
{
|
||||
return new SubmitActionDescriptor(controllerContext.ControllerDescriptor, controllerContext.Controller.GetType());
|
||||
}
|
||||
|
||||
// next check to see if this is a direct invocation of a CUD action
|
||||
DataControllerDescription description = DataControllerDescription.GetDescription(controllerContext.ControllerDescriptor);
|
||||
UpdateActionDescriptor action = description.GetUpdateAction(actionName);
|
||||
if (action != null)
|
||||
{
|
||||
return new SubmitProxyActionDescriptor(action);
|
||||
}
|
||||
|
||||
return base.SelectAction(controllerContext);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Validation;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
internal sealed class DataControllerConfigurationAttribute : Attribute, IControllerConfiguration
|
||||
{
|
||||
private static ConcurrentDictionary<Type, IEnumerable<SerializerInfo>> _serializerCache = new ConcurrentDictionary<Type, IEnumerable<SerializerInfo>>();
|
||||
|
||||
public void Initialize(HttpControllerSettings settings, HttpControllerDescriptor controllerDescriptor)
|
||||
{
|
||||
settings.Formatters.Clear();
|
||||
foreach (MediaTypeFormatter formatter in GetFormatters(controllerDescriptor))
|
||||
{
|
||||
settings.Formatters.Add(formatter);
|
||||
}
|
||||
|
||||
settings.Services.Replace(typeof(IHttpActionInvoker), new DataControllerActionInvoker());
|
||||
settings.Services.Replace(typeof(IHttpActionSelector), new DataControllerActionSelector());
|
||||
|
||||
// Clear the validator to disable validation.
|
||||
settings.Services.Replace(typeof(IBodyModelValidator), null);
|
||||
}
|
||||
|
||||
private static IEnumerable<MediaTypeFormatter> GetFormatters(HttpControllerDescriptor descr)
|
||||
{
|
||||
HttpConfiguration config = descr.Configuration;
|
||||
DataControllerDescription dataDesc = DataControllerDescription.GetDescription(descr);
|
||||
|
||||
List<MediaTypeFormatter> list = new List<MediaTypeFormatter>();
|
||||
AddFormattersFromConfig(list, config);
|
||||
AddDataControllerFormatters(list, dataDesc);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static void AddDataControllerFormatters(List<MediaTypeFormatter> formatters, DataControllerDescription description)
|
||||
{
|
||||
var cachedSerializers = _serializerCache.GetOrAdd(description.ControllerType, controllerType =>
|
||||
{
|
||||
// for the specified controller type, set the serializers for the built
|
||||
// in framework types
|
||||
List<SerializerInfo> serializers = new List<SerializerInfo>();
|
||||
|
||||
Type[] exposedTypes = description.EntityTypes.ToArray();
|
||||
serializers.Add(GetSerializerInfo(typeof(ChangeSetEntry[]), exposedTypes));
|
||||
|
||||
return serializers;
|
||||
});
|
||||
|
||||
JsonMediaTypeFormatter formatterJson = new JsonMediaTypeFormatter();
|
||||
formatterJson.SerializerSettings = new JsonSerializerSettings() { PreserveReferencesHandling = PreserveReferencesHandling.Objects, TypeNameHandling = TypeNameHandling.All };
|
||||
|
||||
XmlMediaTypeFormatter formatterXml = new XmlMediaTypeFormatter();
|
||||
|
||||
// apply the serializers to configuration
|
||||
foreach (var serializerInfo in cachedSerializers)
|
||||
{
|
||||
formatterXml.SetSerializer(serializerInfo.ObjectType, serializerInfo.XmlSerializer);
|
||||
}
|
||||
|
||||
formatters.Add(formatterJson);
|
||||
formatters.Add(formatterXml);
|
||||
}
|
||||
|
||||
// Get existing formatters from config, excluding Json/Xml formatters.
|
||||
private static void AddFormattersFromConfig(List<MediaTypeFormatter> formatters, HttpConfiguration config)
|
||||
{
|
||||
foreach (var formatter in config.Formatters)
|
||||
{
|
||||
if (formatter.GetType() == typeof(JsonMediaTypeFormatter) ||
|
||||
formatter.GetType() == typeof(XmlMediaTypeFormatter))
|
||||
{
|
||||
// skip copying the json/xml formatters since we're configuring those
|
||||
// specifically per controller type and can't share instances between
|
||||
// controllers
|
||||
continue;
|
||||
}
|
||||
formatters.Add(formatter);
|
||||
}
|
||||
}
|
||||
|
||||
private static SerializerInfo GetSerializerInfo(Type type, IEnumerable<Type> knownTypes)
|
||||
{
|
||||
SerializerInfo info = new SerializerInfo();
|
||||
info.ObjectType = type;
|
||||
|
||||
info.XmlSerializer = new DataContractSerializer(type, knownTypes);
|
||||
return info;
|
||||
}
|
||||
|
||||
private class SerializerInfo
|
||||
{
|
||||
public Type ObjectType { get; set; }
|
||||
public DataContractSerializer XmlSerializer { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,440 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using Microsoft.Web.Http.Data.Metadata;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
public class DataControllerDescription
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Type, DataControllerDescription> _descriptionMap = new ConcurrentDictionary<Type, DataControllerDescription>();
|
||||
private static ConcurrentDictionary<Type, HashSet<Type>> _typeDescriptionProviderMap = new ConcurrentDictionary<Type, HashSet<Type>>();
|
||||
|
||||
private static readonly string[] _deletePrefixes = { "Delete", "Remove" };
|
||||
private static readonly string[] _insertPrefixes = { "Insert", "Add", "Create" };
|
||||
private static readonly string[] _updatePrefixes = { "Update", "Change", "Modify" };
|
||||
private Type _dataControllerType;
|
||||
private ReadOnlyCollection<Type> _entityTypes;
|
||||
private List<UpdateActionDescriptor> _updateActions;
|
||||
|
||||
internal DataControllerDescription(Type dataControllerType, IEnumerable<Type> entityTypes, List<UpdateActionDescriptor> actions)
|
||||
{
|
||||
_dataControllerType = dataControllerType;
|
||||
_entityTypes = entityTypes.ToList().AsReadOnly();
|
||||
_updateActions = actions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Type of the <see cref="DataController"/>
|
||||
/// </summary>
|
||||
public Type ControllerType
|
||||
{
|
||||
get { return _dataControllerType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity types exposed by the <see cref="DataController"/>
|
||||
/// </summary>
|
||||
public IEnumerable<Type> EntityTypes
|
||||
{
|
||||
get { return _entityTypes; }
|
||||
}
|
||||
|
||||
public static DataControllerDescription GetDescription(HttpControllerDescriptor controllerDescriptor)
|
||||
{
|
||||
return _descriptionMap.GetOrAdd(controllerDescriptor.ControllerType, type =>
|
||||
{
|
||||
return CreateDescription(controllerDescriptor);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns the metadata provider for the specified DataController Type.
|
||||
/// </summary>
|
||||
/// <param name="dataControllerType">The DataController Type.</param>
|
||||
/// <returns>The metadata provider.</returns>
|
||||
internal static MetadataProvider CreateMetadataProvider(Type dataControllerType)
|
||||
{
|
||||
// construct a list of all types in the inheritance hierarchy for the controller
|
||||
List<Type> baseTypes = new List<Type>();
|
||||
Type currType = dataControllerType;
|
||||
while (currType != typeof(DataController))
|
||||
{
|
||||
baseTypes.Add(currType);
|
||||
currType = currType.BaseType;
|
||||
}
|
||||
|
||||
// create our base reflection provider
|
||||
List<MetadataProvider> providerList = new List<MetadataProvider>();
|
||||
ReflectionMetadataProvider reflectionProvider = new ReflectionMetadataProvider();
|
||||
|
||||
// Set the IsEntity function which consults the chain of providers.
|
||||
Func<Type, bool> isEntityTypeFunc = (t) => providerList.Any(p => p.LookUpIsEntityType(t));
|
||||
reflectionProvider.SetIsEntityTypeFunc(isEntityTypeFunc);
|
||||
|
||||
// Now from most derived to base, create any declared metadata providers,
|
||||
// chaining the instances as we progress. Note that ordering from derived to
|
||||
// base is important - we want to ensure that any providers the user has placed on
|
||||
// their DataController directly come before any DAL providers.
|
||||
MetadataProvider currProvider = reflectionProvider;
|
||||
providerList.Add(currProvider);
|
||||
for (int i = 0; i < baseTypes.Count; i++)
|
||||
{
|
||||
currType = baseTypes[i];
|
||||
|
||||
// Reflection rather than TD is used here so we only get explicit
|
||||
// Type attributes. TD inherits attributes by default, even if the
|
||||
// attributes aren't inheritable.
|
||||
foreach (MetadataProviderAttribute providerAttribute in
|
||||
currType.GetCustomAttributes(typeof(MetadataProviderAttribute), false))
|
||||
{
|
||||
currProvider = providerAttribute.CreateProvider(dataControllerType, currProvider);
|
||||
currProvider.SetIsEntityTypeFunc(isEntityTypeFunc);
|
||||
providerList.Add(currProvider);
|
||||
}
|
||||
}
|
||||
|
||||
return currProvider;
|
||||
}
|
||||
|
||||
private static DataControllerDescription CreateDescription(HttpControllerDescriptor controllerDescriptor)
|
||||
{
|
||||
Type dataControllerType = controllerDescriptor.ControllerType;
|
||||
MetadataProvider metadataProvider = CreateMetadataProvider(dataControllerType);
|
||||
|
||||
// get all public candidate methods and create the operations
|
||||
HashSet<Type> entityTypes = new HashSet<Type>();
|
||||
List<UpdateActionDescriptor> actions = new List<UpdateActionDescriptor>();
|
||||
IEnumerable<MethodInfo> methodsToInspect =
|
||||
dataControllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(p => (p.DeclaringType != typeof(DataController) && (p.DeclaringType != typeof(object))) && !p.IsSpecialName);
|
||||
|
||||
foreach (MethodInfo method in methodsToInspect)
|
||||
{
|
||||
if (method.GetCustomAttributes(typeof(NonActionAttribute), false).Length > 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (method.IsVirtual && method.GetBaseDefinition().DeclaringType == typeof(DataController))
|
||||
{
|
||||
// don't want to infer overrides of DataController virtual methods as
|
||||
// operations
|
||||
continue;
|
||||
}
|
||||
|
||||
// We need to ensure the buddy metadata provider is registered BEFORE we
|
||||
// attempt to do convention, since we rely on IsEntity which relies on
|
||||
// KeyAttributes being present (possibly from "buddy" classes)
|
||||
RegisterAssociatedMetadataProvider(method);
|
||||
|
||||
ChangeOperation operationType = ClassifyUpdateOperation(method, metadataProvider);
|
||||
if (operationType != ChangeOperation.None)
|
||||
{
|
||||
Type entityType = method.GetParameters()[0].ParameterType;
|
||||
UpdateActionDescriptor actionDescriptor = new UpdateActionDescriptor(controllerDescriptor, method, entityType, operationType);
|
||||
ValidateAction(actionDescriptor);
|
||||
actions.Add(actionDescriptor);
|
||||
|
||||
// TODO : currently considering entity types w/o any query methods
|
||||
// exposing them. Should we?
|
||||
if (metadataProvider.IsEntityType(entityType))
|
||||
{
|
||||
AddEntityType(entityType, entityTypes, metadataProvider);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if the method is a "query" operation returning an entity,
|
||||
// add to entity types
|
||||
if (method.ReturnType != typeof(void))
|
||||
{
|
||||
Type returnType = TypeUtility.UnwrapTaskInnerType(method.ReturnType);
|
||||
Type elementType = TypeUtility.GetElementType(returnType);
|
||||
if (metadataProvider.IsEntityType(elementType))
|
||||
{
|
||||
AddEntityType(elementType, entityTypes, metadataProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new DataControllerDescription(dataControllerType, entityTypes, actions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified entity type and any associated entity types recursively to the specified set.
|
||||
/// </summary>
|
||||
/// <param name="entityType">The entity Type to add.</param>
|
||||
/// <param name="entityTypes">The types set to accumulate in.</param>
|
||||
/// <param name="metadataProvider">The metadata provider.</param>
|
||||
private static void AddEntityType(Type entityType, HashSet<Type> entityTypes, MetadataProvider metadataProvider)
|
||||
{
|
||||
if (entityTypes.Contains(entityType))
|
||||
{
|
||||
// already added this type
|
||||
return;
|
||||
}
|
||||
|
||||
entityTypes.Add(entityType);
|
||||
RegisterDataControllerTypeDescriptionProvider(entityType, metadataProvider);
|
||||
|
||||
foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(entityType))
|
||||
{
|
||||
// for any "exposed" association members, recursively add the associated
|
||||
// entity type
|
||||
if (pd.Attributes[typeof(AssociationAttribute)] != null && TypeUtility.IsDataMember(pd))
|
||||
{
|
||||
Type includedEntityType = TypeUtility.GetElementType(pd.PropertyType);
|
||||
if (metadataProvider.IsEntityType(entityType))
|
||||
{
|
||||
AddEntityType(includedEntityType, entityTypes, metadataProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively add any derived entity types specified by [KnownType]
|
||||
// attributes
|
||||
IEnumerable<Type> knownTypes = TypeUtility.GetKnownTypes(entityType, true);
|
||||
foreach (Type knownType in knownTypes)
|
||||
{
|
||||
if (entityType.IsAssignableFrom(knownType))
|
||||
{
|
||||
AddEntityType(knownType, entityTypes, metadataProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateAction(UpdateActionDescriptor updateAction)
|
||||
{
|
||||
// Only authorization filters are supported on CUD actions. This will capture 99% of user errors.
|
||||
// There is the chance that someone might attempt to implement an attribute that implements both
|
||||
// IAuthorizationFilter AND another filter type, but we don't want to have a black-list of filter
|
||||
// types here.
|
||||
if (updateAction.GetFilters().Any(p => !typeof(AuthorizationFilterAttribute).IsAssignableFrom(p.GetType())))
|
||||
{
|
||||
throw Error.NotSupported(Resource.InvalidAction_UnsupportedFilterType, updateAction.ControllerDescriptor.ControllerType.Name, updateAction.ActionName);
|
||||
}
|
||||
}
|
||||
|
||||
private static ChangeOperation ClassifyUpdateOperation(MethodInfo method, MetadataProvider metadataProvider)
|
||||
{
|
||||
ChangeOperation operationType;
|
||||
|
||||
AttributeCollection methodAttributes = new AttributeCollection(method.GetCustomAttributes(false).Cast<Attribute>().ToArray());
|
||||
|
||||
// Check if explicit attributes exist.
|
||||
if (methodAttributes[typeof(InsertAttribute)] != null)
|
||||
{
|
||||
operationType = ChangeOperation.Insert;
|
||||
}
|
||||
else if (methodAttributes[typeof(UpdateAttribute)] != null)
|
||||
{
|
||||
UpdateAttribute updateAttribute = (UpdateAttribute)methodAttributes[typeof(UpdateAttribute)];
|
||||
if (updateAttribute.UsingCustomMethod)
|
||||
{
|
||||
operationType = ChangeOperation.Custom;
|
||||
}
|
||||
else
|
||||
{
|
||||
operationType = ChangeOperation.Update;
|
||||
}
|
||||
}
|
||||
else if (methodAttributes[typeof(DeleteAttribute)] != null)
|
||||
{
|
||||
operationType = ChangeOperation.Delete;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TryClassifyUpdateOperationImplicit(method, metadataProvider);
|
||||
}
|
||||
|
||||
return operationType;
|
||||
}
|
||||
|
||||
private static ChangeOperation TryClassifyUpdateOperationImplicit(MethodInfo method, MetadataProvider metadataProvider)
|
||||
{
|
||||
ChangeOperation operationType = ChangeOperation.None;
|
||||
if (method.ReturnType == typeof(void))
|
||||
{
|
||||
// Check if this looks like an insert, update or delete method.
|
||||
if (_insertPrefixes.Any(p => method.Name.StartsWith(p, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
operationType = ChangeOperation.Insert;
|
||||
}
|
||||
else if (_updatePrefixes.Any(p => method.Name.StartsWith(p, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
operationType = ChangeOperation.Update;
|
||||
}
|
||||
else if (_deletePrefixes.Any(p => method.Name.StartsWith(p, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
operationType = ChangeOperation.Delete;
|
||||
}
|
||||
else if (IsCustomUpdateMethod(method, metadataProvider))
|
||||
{
|
||||
operationType = ChangeOperation.Custom;
|
||||
}
|
||||
}
|
||||
|
||||
return operationType;
|
||||
}
|
||||
|
||||
private static bool IsCustomUpdateMethod(MethodInfo method, MetadataProvider metadataProvider)
|
||||
{
|
||||
ParameterInfo[] parameters = method.GetParameters();
|
||||
if (parameters.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (method.ReturnType != typeof(void))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return metadataProvider.IsEntityType(parameters[0].ParameterType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register the associated metadata provider for Types in the signature
|
||||
/// of the specified method as required.
|
||||
/// </summary>
|
||||
/// <param name="methodInfo">The method to register for.</param>
|
||||
private static void RegisterAssociatedMetadataProvider(MethodInfo methodInfo)
|
||||
{
|
||||
Type type = TypeUtility.GetElementType(methodInfo.ReturnType);
|
||||
if (type != typeof(void) && type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).Length != 0)
|
||||
{
|
||||
RegisterAssociatedMetadataTypeTypeDescriptor(type);
|
||||
}
|
||||
foreach (ParameterInfo parameter in methodInfo.GetParameters())
|
||||
{
|
||||
type = parameter.ParameterType;
|
||||
if (type != typeof(void) && type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).Length != 0)
|
||||
{
|
||||
RegisterAssociatedMetadataTypeTypeDescriptor(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the <see cref="MetadataTypeAttribute"/> reference does not contain a cyclic reference and
|
||||
/// registers the AssociatedMetadataTypeTypeDescriptionProvider in that case.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type with the MetadataType attribute.</param>
|
||||
private static void RegisterAssociatedMetadataTypeTypeDescriptor(Type type)
|
||||
{
|
||||
Type currentType = type;
|
||||
HashSet<Type> metadataTypeReferences = new HashSet<Type>();
|
||||
metadataTypeReferences.Add(currentType);
|
||||
while (true)
|
||||
{
|
||||
MetadataTypeAttribute attribute = (MetadataTypeAttribute)Attribute.GetCustomAttribute(currentType, typeof(MetadataTypeAttribute));
|
||||
if (attribute == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentType = attribute.MetadataClassType;
|
||||
// If we find a cyclic reference, throw an error.
|
||||
if (metadataTypeReferences.Contains(currentType))
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.CyclicMetadataTypeAttributesFound, type.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
metadataTypeReferences.Add(currentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the MetadataType reference chain doesn't contain a cycle, register the use of the AssociatedMetadataTypeTypeDescriptionProvider.
|
||||
RegisterCustomTypeDescriptor(new AssociatedMetadataTypeTypeDescriptionProvider(type), type);
|
||||
}
|
||||
|
||||
// The JITer enforces CAS. By creating a separate method we can avoid getting SecurityExceptions
|
||||
// when we weren't going to really call TypeDescriptor.AddProvider.
|
||||
internal static void RegisterCustomTypeDescriptor(TypeDescriptionProvider tdp, Type type)
|
||||
{
|
||||
// Check if we already registered provider with the specified type.
|
||||
HashSet<Type> existingProviders = _typeDescriptionProviderMap.GetOrAdd(type, t =>
|
||||
{
|
||||
return new HashSet<Type>();
|
||||
});
|
||||
|
||||
if (!existingProviders.Contains(tdp.GetType()))
|
||||
{
|
||||
TypeDescriptor.AddProviderTransparent(tdp, type);
|
||||
existingProviders.Add(tdp.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register our DataControllerTypeDescriptionProvider for the specified Type. This provider is responsible for surfacing the
|
||||
/// custom TDs returned by metadata providers.
|
||||
/// </summary>
|
||||
/// <param name="type">The Type that we should register for.</param>
|
||||
/// <param name="metadataProvider">The metadata provider.</param>
|
||||
private static void RegisterDataControllerTypeDescriptionProvider(Type type, MetadataProvider metadataProvider)
|
||||
{
|
||||
DataControllerTypeDescriptionProvider tdp = new DataControllerTypeDescriptionProvider(type, metadataProvider);
|
||||
RegisterCustomTypeDescriptor(tdp, type);
|
||||
}
|
||||
|
||||
public UpdateActionDescriptor GetUpdateAction(string name)
|
||||
{
|
||||
return _updateActions.FirstOrDefault(p => p.ActionName == name);
|
||||
}
|
||||
|
||||
public UpdateActionDescriptor GetUpdateAction(Type entityType, ChangeOperation operationType)
|
||||
{
|
||||
return _updateActions.FirstOrDefault(p => (p.EntityType == entityType) && (p.ChangeOperation == operationType));
|
||||
}
|
||||
|
||||
public UpdateActionDescriptor GetCustomMethod(Type entityType, string methodName)
|
||||
{
|
||||
if (entityType == null)
|
||||
{
|
||||
throw Error.ArgumentNull("entityType");
|
||||
}
|
||||
if (methodName == null)
|
||||
{
|
||||
throw Error.ArgumentNull("methodName");
|
||||
}
|
||||
|
||||
return _updateActions.FirstOrDefault(p => (p.EntityType == entityType) && (p.ChangeOperation == ChangeOperation.Custom) && (p.ActionName == methodName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the default provider in the metadata provider chain. It is based solely on
|
||||
/// attributes applied directly to types (either via CLR attributes, or via "buddy" metadata class).
|
||||
/// </summary>
|
||||
private class ReflectionMetadataProvider : MetadataProvider
|
||||
{
|
||||
public ReflectionMetadataProvider()
|
||||
: base(parent: null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the Type has at least one member marked with KeyAttribute.
|
||||
/// </summary>
|
||||
/// <param name="type">The Type to check.</param>
|
||||
/// <returns>True if the Type is an entity, false otherwise.</returns>
|
||||
public override bool LookUpIsEntityType(Type type)
|
||||
{
|
||||
return TypeDescriptor.GetProperties(type).Cast<PropertyDescriptor>().Any(p => p.Attributes[typeof(KeyAttribute)] != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Metadata;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using System.Web.Http.Validation;
|
||||
using System.Web.Http.ValueProviders;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
internal static class DataControllerValidation
|
||||
{
|
||||
internal static bool ValidateObject(object o, List<ValidationResultInfo> validationErrors, HttpActionContext actionContext)
|
||||
{
|
||||
// create a model validation node for the object
|
||||
ModelMetadataProvider metadataProvider = actionContext.GetMetadataProvider();
|
||||
string modelStateKey = String.Empty;
|
||||
ModelValidationNode validationNode = CreateModelValidationNode(o, metadataProvider, actionContext.ModelState, modelStateKey);
|
||||
validationNode.ValidateAllProperties = true;
|
||||
|
||||
// add the node to model state
|
||||
ModelState modelState = new ModelState();
|
||||
modelState.Value = new ValueProviderResult(o, String.Empty, CultureInfo.CurrentCulture);
|
||||
actionContext.ModelState.Add(modelStateKey, modelState);
|
||||
|
||||
// invoke validation
|
||||
validationNode.Validate(actionContext);
|
||||
|
||||
if (!actionContext.ModelState.IsValid)
|
||||
{
|
||||
foreach (var modelStateItem in actionContext.ModelState)
|
||||
{
|
||||
foreach (ModelError modelError in modelStateItem.Value.Errors)
|
||||
{
|
||||
validationErrors.Add(new ValidationResultInfo(modelError.ErrorMessage, new string[] { modelStateItem.Key }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actionContext.ModelState.IsValid;
|
||||
}
|
||||
|
||||
private static ModelValidationNode CreateModelValidationNode(object o, ModelMetadataProvider metadataProvider, ModelStateDictionary modelStateDictionary, string modelStateKey)
|
||||
{
|
||||
ModelMetadata metadata = metadataProvider.GetMetadataForType(() =>
|
||||
{
|
||||
return o;
|
||||
}, o.GetType());
|
||||
ModelValidationNode validationNode = new ModelValidationNode(metadata, modelStateKey);
|
||||
|
||||
// for this root node, recursively add all child nodes
|
||||
HashSet<object> visited = new HashSet<object>();
|
||||
CreateModelValidationNodeRecursive(o, validationNode, metadataProvider, metadata, modelStateDictionary, modelStateKey, visited);
|
||||
|
||||
return validationNode;
|
||||
}
|
||||
|
||||
private static void CreateModelValidationNodeRecursive(object o, ModelValidationNode parentNode, ModelMetadataProvider metadataProvider, ModelMetadata metadata, ModelStateDictionary modelStateDictionary, string modelStateKey, HashSet<object> visited)
|
||||
{
|
||||
if (visited.Contains(o))
|
||||
{
|
||||
return;
|
||||
}
|
||||
visited.Add(o);
|
||||
|
||||
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(o))
|
||||
{
|
||||
// append the current property name to the model state path
|
||||
string propertyKey = modelStateKey;
|
||||
if (propertyKey.Length > 0)
|
||||
{
|
||||
propertyKey += ".";
|
||||
}
|
||||
propertyKey += property.Name;
|
||||
|
||||
// create the node for this property and add to the parent node
|
||||
object propertyValue = property.GetValue(o);
|
||||
metadata = metadataProvider.GetMetadataForProperty(() =>
|
||||
{
|
||||
return propertyValue;
|
||||
}, o.GetType(), property.Name);
|
||||
ModelValidationNode childNode = new ModelValidationNode(metadata, propertyKey);
|
||||
parentNode.ChildNodes.Add(childNode);
|
||||
|
||||
// add the property node to model state
|
||||
ModelState modelState = new ModelState();
|
||||
modelState.Value = new ValueProviderResult(propertyValue, null, CultureInfo.CurrentCulture);
|
||||
modelStateDictionary.Add(propertyKey, modelState);
|
||||
|
||||
if (propertyValue != null)
|
||||
{
|
||||
CreateModelValidationNodeRecursive(propertyValue, childNode, metadataProvider, metadata, modelStateDictionary, propertyKey, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute applied to a <see cref="DataController"/> method to indicate that it is a delete method.
|
||||
/// </summary>
|
||||
[AttributeUsage(
|
||||
AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property,
|
||||
AllowMultiple = false, Inherited = true)]
|
||||
public sealed class DeleteAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
//
|
||||
// To add a suppression to this file, right-click the message in the
|
||||
// Error List, point to "Suppress Message(s)", and click
|
||||
// "In Project Suppression File".
|
||||
// You do not need to add suppressions to this file manually.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Web.Http.Data.ChangeSetEntry.#OriginalAssociations")]
|
||||
[assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Web.Http.Data.ChangeSetEntry.#Associations")]
|
||||
[assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Web.Http.Data.ChangeSetEntry.#EntityActions")]
|
||||
[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Assembly is delay signed")]
|
||||
[assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.Web.Http.Data.ChangeSet.#SetEntityAssociations()")]
|
||||
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.ComponentModel.DataAnnotations")]
|
||||
[assembly: SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Scope = "type", Target = "Microsoft.Web.Http.Data.Metadata.MetadataProviderAttribute", Justification = "We intend for people to derive from this attribute.")]
|
||||
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Web.Http.Data.Metadata", Justification = "These types are in their own namespace to match folder structure.")]
|
||||
[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "LookUp", Scope = "member", Target = "Microsoft.Web.Http.Data.Metadata.MetadataProvider.#LookUpIsEntityType(System.Type)", Justification = "This is intended to read as two words")]
|
||||
[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Web.Http.Data.SubmitActionDescriptor.#Execute(System.Web.Http.HttpControllerContext,System.Collections.Generic.IDictionary`2<System.String,System.Object>)", Justification = "This object is being returned - it can't be disposed.")]
|
||||
[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Web.Http.Data.DataControllerConfiguration.#CloneConfiguration(System.Web.Http.HttpConfiguration)", Justification = "This object cannot be disposed - it is being set on the execution context")]
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute applied to a <see cref="DataController"/> method to indicate that it is an insert method.
|
||||
/// </summary>
|
||||
[AttributeUsage(
|
||||
AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property,
|
||||
AllowMultiple = false, Inherited = true)]
|
||||
public sealed class InsertAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Web.Http;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom TypeDescriptionProvider conditionally registered for Types exposed by a <see cref="DataController"/>.
|
||||
/// </summary>
|
||||
internal class DataControllerTypeDescriptionProvider : TypeDescriptionProvider
|
||||
{
|
||||
private readonly MetadataProvider _metadataProvider;
|
||||
private readonly Type _type;
|
||||
private ICustomTypeDescriptor _customTypeDescriptor;
|
||||
|
||||
public DataControllerTypeDescriptionProvider(Type type, MetadataProvider metadataProvider)
|
||||
: base(TypeDescriptor.GetProvider(type))
|
||||
{
|
||||
if (metadataProvider == null)
|
||||
{
|
||||
throw Error.ArgumentNull("metadataProvider");
|
||||
}
|
||||
|
||||
_type = type;
|
||||
_metadataProvider = metadataProvider;
|
||||
}
|
||||
|
||||
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
|
||||
{
|
||||
if (objectType == null && instance != null)
|
||||
{
|
||||
objectType = instance.GetType();
|
||||
}
|
||||
|
||||
if (_type != objectType)
|
||||
{
|
||||
// In inheritance scenarios, we might be called to provide a descriptor
|
||||
// for a derived Type. In that case, we just return base.
|
||||
return base.GetTypeDescriptor(objectType, instance);
|
||||
}
|
||||
|
||||
if (_customTypeDescriptor == null)
|
||||
{
|
||||
// CLR, buddy class type descriptors
|
||||
_customTypeDescriptor = base.GetTypeDescriptor(objectType, instance);
|
||||
|
||||
// EF, any other custom type descriptors provided through MetadataProviders.
|
||||
_customTypeDescriptor = _metadataProvider.GetTypeDescriptor(objectType, _customTypeDescriptor);
|
||||
|
||||
// initialize FK members AFTER our type descriptors have chained
|
||||
HashSet<string> foreignKeyMembers = GetForeignKeyMembers();
|
||||
|
||||
// if any FK member of any association is also part of the primary key, then the key cannot be marked
|
||||
// Editable(false)
|
||||
bool keyIsEditable = false;
|
||||
foreach (PropertyDescriptor pd in _customTypeDescriptor.GetProperties())
|
||||
{
|
||||
if (pd.Attributes[typeof(KeyAttribute)] != null &&
|
||||
foreignKeyMembers.Contains(pd.Name))
|
||||
{
|
||||
keyIsEditable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (DataControllerTypeDescriptor.ShouldRegister(_customTypeDescriptor, keyIsEditable, foreignKeyMembers))
|
||||
{
|
||||
// Extend the chain with one more descriptor.
|
||||
_customTypeDescriptor = new DataControllerTypeDescriptor(_customTypeDescriptor, keyIsEditable, foreignKeyMembers);
|
||||
}
|
||||
}
|
||||
|
||||
return _customTypeDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the set of all foreign key members for the entity.
|
||||
/// </summary>
|
||||
/// <returns>The set of foreign keys.</returns>
|
||||
private HashSet<string> GetForeignKeyMembers()
|
||||
{
|
||||
HashSet<string> foreignKeyMembers = new HashSet<string>();
|
||||
foreach (PropertyDescriptor pd in _customTypeDescriptor.GetProperties())
|
||||
{
|
||||
AssociationAttribute assoc = (AssociationAttribute)pd.Attributes[typeof(AssociationAttribute)];
|
||||
if (assoc != null && assoc.IsForeignKey)
|
||||
{
|
||||
foreach (string foreignKeyMember in assoc.ThisKeyMembers)
|
||||
{
|
||||
foreignKeyMembers.Add(foreignKeyMember);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foreignKeyMembers;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,247 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom TypeDescriptor for Types exposed by a <see cref="DataController"/>.
|
||||
/// </summary>
|
||||
internal class DataControllerTypeDescriptor : CustomTypeDescriptor
|
||||
{
|
||||
private readonly HashSet<string> _foreignKeyMembers;
|
||||
private readonly bool _keyIsEditable;
|
||||
private PropertyDescriptorCollection _properties;
|
||||
|
||||
public DataControllerTypeDescriptor(ICustomTypeDescriptor parent, bool keyIsEditable, HashSet<string> foreignKeyMembers)
|
||||
: base(parent)
|
||||
{
|
||||
_keyIsEditable = keyIsEditable;
|
||||
_foreignKeyMembers = foreignKeyMembers;
|
||||
}
|
||||
|
||||
public override PropertyDescriptorCollection GetProperties()
|
||||
{
|
||||
if (_properties == null)
|
||||
{
|
||||
// Get properties from our parent
|
||||
PropertyDescriptorCollection originalCollection = base.GetProperties();
|
||||
|
||||
// Set _properties to avoid a stack overflow when CreateProjectionProperties
|
||||
// ends up recursively calling TypeDescriptor.GetProperties on a type.
|
||||
_properties = originalCollection;
|
||||
|
||||
bool customDescriptorsCreated = false;
|
||||
List<PropertyDescriptor> tempPropertyDescriptors = new List<PropertyDescriptor>();
|
||||
|
||||
// for every property exposed by our parent, see if we have additional metadata to add,
|
||||
// and if we do we need to add a wrapper PropertyDescriptor to add the new attributes
|
||||
foreach (PropertyDescriptor propDescriptor in _properties)
|
||||
{
|
||||
Attribute[] newMetadata = GetAdditionalAttributes(propDescriptor);
|
||||
if (newMetadata.Length > 0)
|
||||
{
|
||||
tempPropertyDescriptors.Add(new DataControllerPropertyDescriptor(propDescriptor, newMetadata));
|
||||
customDescriptorsCreated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
tempPropertyDescriptors.Add(propDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
if (customDescriptorsCreated)
|
||||
{
|
||||
_properties = new PropertyDescriptorCollection(tempPropertyDescriptors.ToArray(), true);
|
||||
}
|
||||
}
|
||||
|
||||
return _properties;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return an array of new attributes for the specified PropertyDescriptor. If no
|
||||
/// attributes need to be added, return an empty array.
|
||||
/// </summary>
|
||||
/// <param name="pd">The property to add attributes for.</param>
|
||||
/// <returns>The collection of new attributes.</returns>
|
||||
private Attribute[] GetAdditionalAttributes(PropertyDescriptor pd)
|
||||
{
|
||||
List<Attribute> additionalAttributes = new List<Attribute>();
|
||||
|
||||
if (ShouldAddRoundTripAttribute(pd, _foreignKeyMembers.Contains(pd.Name)))
|
||||
{
|
||||
additionalAttributes.Add(new RoundtripOriginalAttribute());
|
||||
}
|
||||
|
||||
bool allowInitialValue;
|
||||
if (ShouldAddEditableFalseAttribute(pd, _keyIsEditable, out allowInitialValue))
|
||||
{
|
||||
additionalAttributes.Add(new EditableAttribute(false) { AllowInitialValue = allowInitialValue });
|
||||
}
|
||||
|
||||
return additionalAttributes.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a type uses any features requiring the
|
||||
/// <see cref="DataControllerTypeDescriptor"/> to be registered. We do this
|
||||
/// check as an optimization so we're not adding additional TDPs to the
|
||||
/// chain when they're not necessary.
|
||||
/// </summary>
|
||||
/// <param name="descriptor">The descriptor for the type to check.</param>
|
||||
/// <param name="keyIsEditable">Indicates whether the key for this Type is editable.</param>
|
||||
/// <param name="foreignKeyMembers">The set of foreign key members for the Type.</param>
|
||||
/// <returns>Returns <c>true</c> if the type uses any features requiring the
|
||||
/// <see cref="DataControllerTypeDescriptionProvider"/> to be registered.</returns>
|
||||
internal static bool ShouldRegister(ICustomTypeDescriptor descriptor, bool keyIsEditable, HashSet<string> foreignKeyMembers)
|
||||
{
|
||||
foreach (PropertyDescriptor pd in descriptor.GetProperties())
|
||||
{
|
||||
// If there are any attributes that should be inferred for this member, then
|
||||
// we will register the descriptor
|
||||
if (ShouldInferAttributes(pd, keyIsEditable, foreignKeyMembers))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified member requires a RoundTripOriginalAttribute
|
||||
/// and one isn't already present.
|
||||
/// </summary>
|
||||
/// <param name="pd">The member to check.</param>
|
||||
/// <param name="isFkMember">True if the member is a foreign key, false otherwise.</param>
|
||||
/// <returns>True if RoundTripOriginalAttribute should be added, false otherwise.</returns>
|
||||
private static bool ShouldAddRoundTripAttribute(PropertyDescriptor pd, bool isFkMember)
|
||||
{
|
||||
if (pd.Attributes[typeof(RoundtripOriginalAttribute)] != null || pd.Attributes[typeof(AssociationAttribute)] != null)
|
||||
{
|
||||
// already has the attribute or is an association
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isFkMember || pd.Attributes[typeof(ConcurrencyCheckAttribute)] != null ||
|
||||
pd.Attributes[typeof(TimestampAttribute)] != null || pd.Attributes[typeof(KeyAttribute)] != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> if the specified member requires an <see cref="EditableAttribute"/>
|
||||
/// to make the member read-only and one isn't already present.
|
||||
/// </summary>
|
||||
/// <param name="pd">The member to check.</param>
|
||||
/// <param name="keyIsEditable">Indicates whether the key for this Type is editable.</param>
|
||||
/// <param name="allowInitialValue">
|
||||
/// The default that should be used for <see cref="EditableAttribute.AllowInitialValue"/> if the attribute
|
||||
/// should be added to the member.
|
||||
/// </param>
|
||||
/// <returns><c>true</c> if <see cref="EditableAttribute"/> should be added, <c>false</c> otherwise.</returns>
|
||||
private static bool ShouldAddEditableFalseAttribute(PropertyDescriptor pd, bool keyIsEditable, out bool allowInitialValue)
|
||||
{
|
||||
allowInitialValue = false;
|
||||
|
||||
if (pd.Attributes[typeof(EditableAttribute)] != null)
|
||||
{
|
||||
// already has the attribute
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasKeyAttribute = (pd.Attributes[typeof(KeyAttribute)] != null);
|
||||
if (hasKeyAttribute && keyIsEditable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasKeyAttribute || pd.Attributes[typeof(TimestampAttribute)] != null)
|
||||
{
|
||||
// If we're inferring EditableAttribute because of a KeyAttribute
|
||||
// we want to allow initial value for the member.
|
||||
allowInitialValue = hasKeyAttribute;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if there are any attributes that can be inferred for the specified member.
|
||||
/// </summary>
|
||||
/// <param name="pd">The member to check.</param>
|
||||
/// <param name="keyIsEditable">Indicates whether the key for this Type is editable.</param>
|
||||
/// <param name="foreignKeyMembers">Collection of foreign key members for the Type.</param>
|
||||
/// <returns><c>true</c> if there are attributes to be inferred, <c>false</c> otherwise.</returns>
|
||||
private static bool ShouldInferAttributes(PropertyDescriptor pd, bool keyIsEditable, IEnumerable<string> foreignKeyMembers)
|
||||
{
|
||||
bool allowInitialValue;
|
||||
|
||||
return ShouldAddEditableFalseAttribute(pd, keyIsEditable, out allowInitialValue) ||
|
||||
ShouldAddRoundTripAttribute(pd, foreignKeyMembers.Contains(pd.Name));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PropertyDescriptor wrapper.
|
||||
/// </summary>
|
||||
internal class DataControllerPropertyDescriptor : PropertyDescriptor
|
||||
{
|
||||
private PropertyDescriptor _base;
|
||||
|
||||
public DataControllerPropertyDescriptor(PropertyDescriptor pd, Attribute[] attribs)
|
||||
: base(pd, attribs)
|
||||
{
|
||||
_base = pd;
|
||||
}
|
||||
|
||||
public override Type ComponentType
|
||||
{
|
||||
get { return _base.ComponentType; }
|
||||
}
|
||||
|
||||
public override bool IsReadOnly
|
||||
{
|
||||
get { return _base.IsReadOnly; }
|
||||
}
|
||||
|
||||
public override Type PropertyType
|
||||
{
|
||||
get { return _base.PropertyType; }
|
||||
}
|
||||
|
||||
public override object GetValue(object component)
|
||||
{
|
||||
return _base.GetValue(component);
|
||||
}
|
||||
|
||||
public override void SetValue(object component, object value)
|
||||
{
|
||||
_base.SetValue(component, value);
|
||||
}
|
||||
|
||||
public override bool ShouldSerializeValue(object component)
|
||||
{
|
||||
return _base.ShouldSerializeValue(component);
|
||||
}
|
||||
|
||||
public override bool CanResetValue(object component)
|
||||
{
|
||||
return _base.CanResetValue(component);
|
||||
}
|
||||
|
||||
public override void ResetValue(object component)
|
||||
{
|
||||
_base.ResetValue(component);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Web.Http;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="MetadataProvider"/> is used to provide the metadata description for
|
||||
/// types exposed by a <see cref="DataController"/>.
|
||||
/// </summary>
|
||||
public abstract class MetadataProvider
|
||||
{
|
||||
private MetadataProvider _parentProvider;
|
||||
private Func<Type, bool> _isEntityTypeFunc;
|
||||
|
||||
/// <summary>
|
||||
/// Protected Constructor
|
||||
/// </summary>
|
||||
/// <param name="parent">The existing parent provider. May be null.</param>
|
||||
protected MetadataProvider(MetadataProvider parent)
|
||||
{
|
||||
_parentProvider = parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent provider.
|
||||
/// </summary>
|
||||
internal MetadataProvider ParentProvider
|
||||
{
|
||||
get { return _parentProvider; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="TypeDescriptor"/> for the specified Type, using the specified parent descriptor
|
||||
/// as the base. Overrides should call base to ensure the <see cref="TypeDescriptor"/>s are chained properly.
|
||||
/// </summary>
|
||||
/// <param name="type">The Type to return a descriptor for.</param>
|
||||
/// <param name="parent">The parent descriptor.</param>
|
||||
/// <returns>The <see cref="TypeDescriptor"/> for the specified Type.</returns>
|
||||
public virtual ICustomTypeDescriptor GetTypeDescriptor(Type type, ICustomTypeDescriptor parent)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
throw Error.ArgumentNull("type");
|
||||
}
|
||||
if (parent == null)
|
||||
{
|
||||
throw Error.ArgumentNull("parent");
|
||||
}
|
||||
|
||||
if (_parentProvider != null)
|
||||
{
|
||||
return _parentProvider.GetTypeDescriptor(type, parent);
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the specified <see cref="Type"/> should be considered an entity <see cref="Type"/>.
|
||||
/// The base implementation returns <c>false</c>.
|
||||
/// </summary>
|
||||
/// <remarks>Effectively, the return from this method is this provider's vote as to whether the specified
|
||||
/// Type is an entity. The votes from this provider and all other providers in the chain are used
|
||||
/// by <see cref="IsEntityType"/> to make it's determination.</remarks>
|
||||
/// <param name="type">The <see cref="Type"/> to check.</param>
|
||||
/// <returns>Returns <c>true</c> if the <see cref="Type"/> should be considered an entity,
|
||||
/// <c>false</c> otherwise.</returns>
|
||||
public virtual bool LookUpIsEntityType(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
throw Error.ArgumentNull("type");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the specified <see cref="Type"/> is an entity <see cref="Type"/> by consulting
|
||||
/// the <see cref="LookUpIsEntityType"/> method of all <see cref="MetadataProvider"/>s
|
||||
/// in the provider chain for the <see cref="DataController"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The <see cref="Type"/> to check.</param>
|
||||
/// <returns>Returns <c>true</c> if the <see cref="Type"/> is an entity, <c>false</c> otherwise.</returns>
|
||||
protected internal bool IsEntityType(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
throw Error.ArgumentNull("type");
|
||||
}
|
||||
|
||||
if (_isEntityTypeFunc != null)
|
||||
{
|
||||
return _isEntityTypeFunc(type);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the internal entity lookup function for this provider. The function consults
|
||||
/// the entire provider chain to make its determination.
|
||||
/// </summary>
|
||||
/// <param name="isEntityTypeFunc">The entity function.</param>
|
||||
internal void SetIsEntityTypeFunc(Func<Type, bool> isEntityTypeFunc)
|
||||
{
|
||||
_isEntityTypeFunc = isEntityTypeFunc;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Web.Http;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute applied to a <see cref="DataController"/> type to specify the <see cref="MetadataProvider"/>
|
||||
/// for the type.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public class MetadataProviderAttribute : Attribute
|
||||
{
|
||||
private Type _providerType;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the MetadataProviderAttribute class
|
||||
/// </summary>
|
||||
/// <param name="providerType">The <see cref="MetadataProvider"/> type</param>
|
||||
public MetadataProviderAttribute(Type providerType)
|
||||
{
|
||||
if (providerType == null)
|
||||
{
|
||||
throw Error.ArgumentNull("providerType");
|
||||
}
|
||||
|
||||
_providerType = providerType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="MetadataProvider"/> type
|
||||
/// </summary>
|
||||
public Type ProviderType
|
||||
{
|
||||
get { return _providerType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a unique identifier for this attribute.
|
||||
/// </summary>
|
||||
public override object TypeId
|
||||
{
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method creates an instance of the <see cref="MetadataProvider"/>. Subclasses can override this
|
||||
/// method to provide their own construction logic.
|
||||
/// </summary>
|
||||
/// <param name="controllerType">The <see cref="DataController"/> type to create a metadata provider for.</param>
|
||||
/// <param name="parent">The parent provider. May be null.</param>
|
||||
/// <returns>The metadata provider</returns>
|
||||
public virtual MetadataProvider CreateProvider(Type controllerType, MetadataProvider parent)
|
||||
{
|
||||
if (controllerType == null)
|
||||
{
|
||||
throw Error.ArgumentNull("controllerType");
|
||||
}
|
||||
|
||||
if (!typeof(DataController).IsAssignableFrom(controllerType))
|
||||
{
|
||||
throw Error.Argument("controllerType", Resource.InvalidType, controllerType.FullName, typeof(DataController).FullName);
|
||||
}
|
||||
|
||||
if (!typeof(MetadataProvider).IsAssignableFrom(_providerType))
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.InvalidType, _providerType.FullName, typeof(MetadataProvider).FullName);
|
||||
}
|
||||
|
||||
// Verify the type has a .ctor(MetadataProvider).
|
||||
if (_providerType.GetConstructor(new Type[] { typeof(MetadataProvider) }) == null)
|
||||
{
|
||||
throw Error.InvalidOperation(Resource.MetadataProviderAttribute_MissingConstructor, _providerType.FullName);
|
||||
}
|
||||
|
||||
return (MetadataProvider)Activator.CreateInstance(_providerType, parent);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{ACE91549-D86E-4EB6-8C2A-5FF51386BB68}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Microsoft.Web.Http.Data</RootNamespace>
|
||||
<AssemblyName>Microsoft.Web.Http.Data</AssemblyName>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
<IsStyleCopEnabled>false</IsStyleCopEnabled>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\..\bin\Debug\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG;CODE_ANALYSIS;ASPNETMVC</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<DocumentationFile>$(OutputPath)$(AssemblyName).xml</DocumentationFile>
|
||||
<RunCodeAnalysis>$(CodeAnalysis)</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\..\bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;ASPNETMVC</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<DocumentationFile>$(OutputPath)$(AssemblyName).xml</DocumentationFile>
|
||||
<RunCodeAnalysis>$(CodeAnalysis)</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeAnalysis|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\..\bin\CodeAnalysis\</OutputPath>
|
||||
<DefineConstants>TRACE;CODE_ANALYSIS;ASPNETMVC</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<DocumentationFile>$(OutputPath)$(AssemblyName).xml</DocumentationFile>
|
||||
<RunCodeAnalysis>$(CodeAnalysis)</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.4.5.6\lib\net40\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Net.Http, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http.WebRequest, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.XML" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\AptcaCommonAssemblyInfo.cs">
|
||||
<Link>Properties\AptcaCommonAssemblyInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\CommonAssemblyInfo.cs">
|
||||
<Link>Properties\CommonAssemblyInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Common\DictionaryExtensions.cs">
|
||||
<Link>Common\DictionaryExtensions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Common\Error.cs">
|
||||
<Link>Common\Error.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Common\TaskHelpers.cs">
|
||||
<Link>Common\TaskHelpers.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Common\TaskHelpersExtensions.cs">
|
||||
<Link>Common\TaskHelpersExtensions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="ChangeOperation.cs" />
|
||||
<Compile Include="ChangeSet.cs" />
|
||||
<Compile Include="ChangeSetEntry.cs" />
|
||||
<Compile Include="DataControllerActionInvoker.cs" />
|
||||
<Compile Include="DataController.cs" />
|
||||
<Compile Include="DataControllerActionSelector.cs" />
|
||||
<Compile Include="DataControllerConfigurationAttribute.cs" />
|
||||
<Compile Include="DataControllerDescription.cs" />
|
||||
<Compile Include="DataControllerValidation.cs" />
|
||||
<Compile Include="Metadata\DataControllerTypeDescriptor.cs" />
|
||||
<Compile Include="Metadata\DataControllerTypeDescriptionProvider.cs" />
|
||||
<Compile Include="DeleteAttribute.cs" />
|
||||
<Compile Include="GlobalSuppressions.cs" />
|
||||
<Compile Include="InsertAttribute.cs" />
|
||||
<Compile Include="Metadata\MetadataProvider.cs" />
|
||||
<Compile Include="Metadata\MetadataProviderAttribute.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Resource.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resource.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="RoundtripOriginalAttribute.cs" />
|
||||
<Compile Include="SubmitActionDescriptor.cs" />
|
||||
<Compile Include="SubmitProxyActionDescriptor.cs" />
|
||||
<Compile Include="TypeDescriptorExtensions.cs" />
|
||||
<Compile Include="TypeUtility.cs" />
|
||||
<Compile Include="UpdateActionDescriptor.cs" />
|
||||
<Compile Include="UpdateAttribute.cs" />
|
||||
<Compile Include="ValidationResultInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\System.Net.Http.Formatting\System.Net.Http.Formatting.csproj">
|
||||
<Project>{668E9021-CE84-49D9-98FB-DF125A9FCDB0}</Project>
|
||||
<Name>System.Net.Http.Formatting</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\System.Web.Http\System.Web.Http.csproj">
|
||||
<Project>{DDC1CE0C-486E-4E35-BB3B-EAB61F8F9440}</Project>
|
||||
<Name>System.Web.Http</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CodeAnalysisDictionary Include="..\CodeAnalysisDictionary.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\CommonWebApiResources.Designer.cs">
|
||||
<Link>Properties\CommonWebApiResources.Designer.cs</Link>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>CommonWebApiResources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\Common\CommonWebApiResources.resx">
|
||||
<Link>Properties\CommonWebApiResources.resx</Link>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>CommonWebApiResources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resource.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
|
||||
</Project>
|
|
@ -1,8 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: AssemblyTitle("Microsoft.Web.Http.Data")]
|
||||
[assembly: AssemblyDescription("Microsoft.Web.Http.Data")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.Web.Http.Data.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
|
|
@ -1,225 +0,0 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.239
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.Web.Http.Data {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resource {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resource() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Web.Http.Data.Resource", typeof(Resource).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The specified entity does not exist in the ChangeSet..
|
||||
/// </summary>
|
||||
internal static string ChangeSet_ChangeSetEntryNotFound {
|
||||
get {
|
||||
return ResourceManager.GetString("ChangeSet_ChangeSetEntryNotFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to GetOriginal cannot be called for a new entity being inserted..
|
||||
/// </summary>
|
||||
internal static string ChangeSet_OriginalNotValidForInsert {
|
||||
get {
|
||||
return ResourceManager.GetString("ChangeSet_OriginalNotValidForInsert", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The MetadataTypeAttribute on type '{0}' results in a cyclic metadata provider chain. Either remove the attribute or remove the cycle..
|
||||
/// </summary>
|
||||
internal static string CyclicMetadataTypeAttributesFound {
|
||||
get {
|
||||
return ResourceManager.GetString("CyclicMetadataTypeAttributesFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to One or more associated objects were passed for collection property '{1}' on type '{0}', but the target collection is null..
|
||||
/// </summary>
|
||||
internal static string DataController_AssociationCollectionPropertyIsNull {
|
||||
get {
|
||||
return ResourceManager.GetString("DataController_AssociationCollectionPropertyIsNull", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This DataController does not support operation '{0}' for entity '{1}'..
|
||||
/// </summary>
|
||||
internal static string DataController_InvalidAction {
|
||||
get {
|
||||
return ResourceManager.GetString("DataController_InvalidAction", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Association collection member '{0}' does not implement IList and does not have an Add method..
|
||||
/// </summary>
|
||||
internal static string DataController_InvalidCollectionMember {
|
||||
get {
|
||||
return ResourceManager.GetString("DataController_InvalidCollectionMember", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Action '{0}.{1}' has one or more filters applied that do not derive from AuthorizationFilterAttribute. Only authorization filters are supported on DataController Insert/Update/Delete actions..
|
||||
/// </summary>
|
||||
internal static string InvalidAction_UnsupportedFilterType {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidAction_UnsupportedFilterType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid ChangeSet: {0}.
|
||||
/// </summary>
|
||||
internal static string InvalidChangeSet {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidChangeSet", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Id '{0}' specified for association member '{0}.{1}' is invalid..
|
||||
/// </summary>
|
||||
internal static string InvalidChangeSet_AssociatedIdNotInChangeset {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidChangeSet_AssociatedIdNotInChangeset", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Associated Ids for member '{0}.{1}' cannot be null..
|
||||
/// </summary>
|
||||
internal static string InvalidChangeSet_AssociatedIdsCannotBeNull {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidChangeSet_AssociatedIdsCannotBeNull", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Only one entry for a given entity instance can exist in the ChangeSet..
|
||||
/// </summary>
|
||||
internal static string InvalidChangeSet_DuplicateEntity {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidChangeSet_DuplicateEntity", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Id must be unique for each entry..
|
||||
/// </summary>
|
||||
internal static string InvalidChangeSet_DuplicateId {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidChangeSet_DuplicateId", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to OriginalEntity cannot be specified for an Insert operation..
|
||||
/// </summary>
|
||||
internal static string InvalidChangeSet_InsertsCantHaveOriginal {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidChangeSet_InsertsCantHaveOriginal", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Association member '{0}.{1}' specified in the ChangeSet does not exist or is not marked with AssociationAttribute..
|
||||
/// </summary>
|
||||
internal static string InvalidChangeSet_InvalidAssociationMember {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidChangeSet_InvalidAssociationMember", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Entity and OriginalEntity must be of the same type..
|
||||
/// </summary>
|
||||
internal static string InvalidChangeSet_MustBeSameType {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidChangeSet_MustBeSameType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Entity cannot be null..
|
||||
/// </summary>
|
||||
internal static string InvalidChangeSet_NullEntity {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidChangeSet_NullEntity", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Type '{0}' must derive from '{1}'..
|
||||
/// </summary>
|
||||
internal static string InvalidType {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to MetadataProvider type '{0}' must have a constructor with a single parameter of type 'MetadataProvider'..
|
||||
/// </summary>
|
||||
internal static string MetadataProviderAttribute_MissingConstructor {
|
||||
get {
|
||||
return ResourceManager.GetString("MetadataProviderAttribute_MissingConstructor", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="ChangeSet_ChangeSetEntryNotFound" xml:space="preserve">
|
||||
<value>The specified entity does not exist in the ChangeSet.</value>
|
||||
</data>
|
||||
<data name="ChangeSet_OriginalNotValidForInsert" xml:space="preserve">
|
||||
<value>GetOriginal cannot be called for a new entity being inserted.</value>
|
||||
</data>
|
||||
<data name="CyclicMetadataTypeAttributesFound" xml:space="preserve">
|
||||
<value>The MetadataTypeAttribute on type '{0}' results in a cyclic metadata provider chain. Either remove the attribute or remove the cycle.</value>
|
||||
</data>
|
||||
<data name="DataController_AssociationCollectionPropertyIsNull" xml:space="preserve">
|
||||
<value>One or more associated objects were passed for collection property '{1}' on type '{0}', but the target collection is null.</value>
|
||||
</data>
|
||||
<data name="DataController_InvalidAction" xml:space="preserve">
|
||||
<value>This DataController does not support operation '{0}' for entity '{1}'.</value>
|
||||
</data>
|
||||
<data name="DataController_InvalidCollectionMember" xml:space="preserve">
|
||||
<value>Association collection member '{0}' does not implement IList and does not have an Add method.</value>
|
||||
</data>
|
||||
<data name="InvalidAction_UnsupportedFilterType" xml:space="preserve">
|
||||
<value>Action '{0}.{1}' has one or more filters applied that do not derive from AuthorizationFilterAttribute. Only authorization filters are supported on DataController Insert/Update/Delete actions.</value>
|
||||
</data>
|
||||
<data name="InvalidChangeSet" xml:space="preserve">
|
||||
<value>Invalid ChangeSet: {0}</value>
|
||||
</data>
|
||||
<data name="InvalidChangeSet_AssociatedIdNotInChangeset" xml:space="preserve">
|
||||
<value>Id '{0}' specified for association member '{0}.{1}' is invalid.</value>
|
||||
</data>
|
||||
<data name="InvalidChangeSet_AssociatedIdsCannotBeNull" xml:space="preserve">
|
||||
<value>Associated Ids for member '{0}.{1}' cannot be null.</value>
|
||||
</data>
|
||||
<data name="InvalidChangeSet_DuplicateEntity" xml:space="preserve">
|
||||
<value>Only one entry for a given entity instance can exist in the ChangeSet.</value>
|
||||
</data>
|
||||
<data name="InvalidChangeSet_DuplicateId" xml:space="preserve">
|
||||
<value>Id must be unique for each entry.</value>
|
||||
</data>
|
||||
<data name="InvalidChangeSet_InsertsCantHaveOriginal" xml:space="preserve">
|
||||
<value>OriginalEntity cannot be specified for an Insert operation.</value>
|
||||
</data>
|
||||
<data name="InvalidChangeSet_InvalidAssociationMember" xml:space="preserve">
|
||||
<value>Association member '{0}.{1}' specified in the ChangeSet does not exist or is not marked with AssociationAttribute.</value>
|
||||
</data>
|
||||
<data name="InvalidChangeSet_MustBeSameType" xml:space="preserve">
|
||||
<value>Entity and OriginalEntity must be of the same type.</value>
|
||||
</data>
|
||||
<data name="InvalidChangeSet_NullEntity" xml:space="preserve">
|
||||
<value>Entity cannot be null.</value>
|
||||
</data>
|
||||
<data name="InvalidType" xml:space="preserve">
|
||||
<value>Type '{0}' must derive from '{1}'.</value>
|
||||
</data>
|
||||
<data name="MetadataProviderAttribute_MissingConstructor" xml:space="preserve">
|
||||
<value>MetadataProvider type '{0}' must have a constructor with a single parameter of type 'MetadataProvider'.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
namespace System.ComponentModel.DataAnnotations
|
||||
{
|
||||
/// <summary>
|
||||
/// When applied to a member, this attribute indicates that the original value of
|
||||
/// the member should be sent back to the server when the object is updated. When applied
|
||||
/// to a class, the attribute gets applied to each member of that class. If this attribute is not
|
||||
/// present, the value of this member will be null in the original object sent back to the server.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Class, AllowMultiple = false)]
|
||||
public sealed class RoundtripOriginalAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http.Controllers;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// This descriptor translates between the wire format for Submit (an enumerable
|
||||
/// of ChangeSetEntry) and the actual Submit(ChangeSet) signature.
|
||||
/// </summary>
|
||||
internal sealed class SubmitActionDescriptor : ReflectedHttpActionDescriptor
|
||||
{
|
||||
private const string ChangeSetParameterName = "changeSet";
|
||||
private Collection<HttpParameterDescriptor> _parameters;
|
||||
|
||||
public SubmitActionDescriptor(HttpControllerDescriptor controllerDescriptor, Type controllerType)
|
||||
: base(controllerDescriptor, controllerType.GetMethod("Submit", BindingFlags.Instance | BindingFlags.Public))
|
||||
{
|
||||
_parameters = new Collection<HttpParameterDescriptor>(new List<HttpParameterDescriptor>() { new ChangeSetParameterDescriptor(this) });
|
||||
}
|
||||
|
||||
public override Type ReturnType
|
||||
{
|
||||
get { return typeof(HttpResponseMessage); }
|
||||
}
|
||||
|
||||
public override Collection<HttpParameterDescriptor> GetParameters()
|
||||
{
|
||||
return _parameters;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller owns HttpResponseMessage.")]
|
||||
public override Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments, CancellationToken cancellationToken)
|
||||
{
|
||||
return TaskHelpers.RunSynchronously<object>(() =>
|
||||
{
|
||||
// create a changeset from the entries
|
||||
ChangeSet changeSet = new ChangeSet((IEnumerable<ChangeSetEntry>)arguments[ChangeSetParameterName]);
|
||||
changeSet.SetEntityAssociations();
|
||||
|
||||
DataController controller = (DataController)controllerContext.Controller;
|
||||
if (!controller.Submit(changeSet) &&
|
||||
controller.ActionContext.Response != null)
|
||||
{
|
||||
// If the submit failed due to an authorization failure,
|
||||
// return the authorization response directly
|
||||
return controller.ActionContext.Response;
|
||||
}
|
||||
|
||||
// return the entries
|
||||
return controllerContext.Request.CreateResponse<ChangeSetEntry[]>(HttpStatusCode.OK, changeSet.ChangeSetEntries.ToArray());
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ParameterDescriptor representing the single Submit(ChangeSet) parameter,
|
||||
/// but with the enumerable of ChangeSetEntry ParameterType so the formatters
|
||||
/// deserialize the argument properly.
|
||||
/// </summary>
|
||||
private class ChangeSetParameterDescriptor : ReflectedHttpParameterDescriptor
|
||||
{
|
||||
public ChangeSetParameterDescriptor(HttpActionDescriptor actionDescriptor)
|
||||
: base(actionDescriptor, actionDescriptor.ControllerDescriptor.ControllerType.GetMethod("Submit", BindingFlags.Instance | BindingFlags.Public).GetParameters()[0])
|
||||
{
|
||||
}
|
||||
|
||||
public override Type ParameterType
|
||||
{
|
||||
get { return typeof(ChangeSetEntry[]); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http.Controllers;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// This descriptor translates a direct CUD action invocation into a call to
|
||||
/// Submit. This descriptor wraps the actual action, intercepting the execution
|
||||
/// to do the transformation.
|
||||
/// </summary>
|
||||
internal sealed class SubmitProxyActionDescriptor : ReflectedHttpActionDescriptor
|
||||
{
|
||||
private UpdateActionDescriptor _updateAction;
|
||||
|
||||
public SubmitProxyActionDescriptor(UpdateActionDescriptor updateAction)
|
||||
: base(updateAction.ControllerDescriptor, updateAction.ControllerDescriptor.ControllerType.GetMethod("Submit", BindingFlags.Instance | BindingFlags.Public))
|
||||
{
|
||||
_updateAction = updateAction;
|
||||
}
|
||||
|
||||
public override string ActionName
|
||||
{
|
||||
get { return _updateAction.ActionName; }
|
||||
}
|
||||
|
||||
public override Type ReturnType
|
||||
{
|
||||
get { return typeof(HttpResponseMessage); }
|
||||
}
|
||||
|
||||
public override Collection<HttpParameterDescriptor> GetParameters()
|
||||
{
|
||||
return _updateAction.GetParameters();
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for the lifetime of the object")]
|
||||
public override Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments, CancellationToken cancellationToken)
|
||||
{
|
||||
return TaskHelpers.RunSynchronously<object>(() =>
|
||||
{
|
||||
// create the changeset
|
||||
object entity = arguments.Single().Value; // there is only a single parameter - the entity being submitted
|
||||
ChangeSetEntry[] changeSetEntries = new ChangeSetEntry[]
|
||||
{
|
||||
new ChangeSetEntry
|
||||
{
|
||||
Id = 1,
|
||||
ActionDescriptor = _updateAction,
|
||||
Entity = entity,
|
||||
Operation = _updateAction.ChangeOperation
|
||||
}
|
||||
};
|
||||
ChangeSet changeSet = new ChangeSet(changeSetEntries);
|
||||
changeSet.SetEntityAssociations();
|
||||
|
||||
DataController controller = (DataController)controllerContext.Controller;
|
||||
if (!controller.Submit(changeSet) &&
|
||||
controller.ActionContext.Response != null)
|
||||
{
|
||||
// If the submit failed due to an authorization failure,
|
||||
// return the authorization response directly
|
||||
return controller.ActionContext.Response;
|
||||
}
|
||||
|
||||
// return the entity
|
||||
entity = changeSet.ChangeSetEntries[0].Entity;
|
||||
// REVIEW does JSON make sense here?
|
||||
return new HttpResponseMessage()
|
||||
{
|
||||
Content = new ObjectContent(_updateAction.EntityType, entity, new JsonMediaTypeFormatter())
|
||||
};
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for TypeDescriptors
|
||||
/// </summary>
|
||||
internal static class TypeDescriptorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension method to extract only the explicitly specified attributes from a <see cref="PropertyDescriptor"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Normal TypeDescriptor semantics are to inherit the attributes of a property's type. This method
|
||||
/// exists to suppress those inherited attributes.
|
||||
/// </remarks>
|
||||
/// <param name="propertyDescriptor">The property descriptor whose attributes are needed.</param>
|
||||
/// <returns>A new <see cref="AttributeCollection"/> stripped of any attributes from the property's type.</returns>
|
||||
public static AttributeCollection ExplicitAttributes(this PropertyDescriptor propertyDescriptor)
|
||||
{
|
||||
List<Attribute> attributes = new List<Attribute>(propertyDescriptor.Attributes.Cast<Attribute>());
|
||||
AttributeCollection typeAttributes = TypeDescriptor.GetAttributes(propertyDescriptor.PropertyType);
|
||||
bool removedAttribute = false;
|
||||
foreach (Attribute attr in typeAttributes)
|
||||
{
|
||||
for (int i = attributes.Count - 1; i >= 0; --i)
|
||||
{
|
||||
// We must use ReferenceEquals since attributes could Match if they are the same.
|
||||
// Only ReferenceEquals will catch actual duplications.
|
||||
if (Object.ReferenceEquals(attr, attributes[i]))
|
||||
{
|
||||
attributes.RemoveAt(i);
|
||||
removedAttribute = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return removedAttribute ? new AttributeCollection(attributes.ToArray()) : propertyDescriptor.Attributes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method to extract attributes from a type taking into account the inheritance type of attributes
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Normal TypeDescriptor semantics are to inherit the attributes of a type's base type, regardless of their
|
||||
/// inheritance type.
|
||||
/// </remarks>
|
||||
/// <param name="type">The type whose attributes are needed.</param>
|
||||
/// <returns>A new <see cref="AttributeCollection"/> stripped of any incorrectly inherited attributes from the type.</returns>
|
||||
public static AttributeCollection Attributes(this Type type)
|
||||
{
|
||||
AttributeCollection baseTypeAttributes = TypeDescriptor.GetAttributes(type.BaseType);
|
||||
List<Attribute> typeAttributes = new List<Attribute>(TypeDescriptor.GetAttributes(type).Cast<Attribute>());
|
||||
foreach (Attribute attr in baseTypeAttributes)
|
||||
{
|
||||
AttributeUsageAttribute attributeUsageAtt = (AttributeUsageAttribute)TypeDescriptor.GetAttributes(attr)[typeof(AttributeUsageAttribute)];
|
||||
if (attributeUsageAtt != null && !attributeUsageAtt.Inherited)
|
||||
{
|
||||
for (int i = typeAttributes.Count - 1; i >= 0; --i)
|
||||
{
|
||||
// We must use ReferenceEquals since attributes could Match if they are the same.
|
||||
// Only ReferenceEquals will catch actual duplications.
|
||||
if (Object.ReferenceEquals(attr, typeAttributes[i]))
|
||||
{
|
||||
typeAttributes.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new AttributeCollection(typeAttributes.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if an attribute collection contains any attributes of the provided type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TAttribute">The attribute type to check for</typeparam>
|
||||
/// <param name="attributes">The attribute collection to inspect</param>
|
||||
/// <returns><c>True</c> if an attribute of the provided type is contained in the attribute collection.</returns>
|
||||
public static bool ContainsAttributeType<TAttribute>(this AttributeCollection attributes) where TAttribute : Attribute
|
||||
{
|
||||
return attributes.Cast<Attribute>().Any(a => a.GetType() == typeof(TAttribute));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
internal static class TypeUtility
|
||||
{
|
||||
public static Type GetElementType(Type type)
|
||||
{
|
||||
// Array, pointers, etc.
|
||||
if (type.HasElementType)
|
||||
{
|
||||
return type.GetElementType();
|
||||
}
|
||||
|
||||
// IEnumerable<T> returns T
|
||||
Type ienum = FindIEnumerable(type);
|
||||
if (ienum != null)
|
||||
{
|
||||
Type genericArg = ienum.GetGenericArguments()[0];
|
||||
return genericArg;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
internal static Type FindIEnumerable(Type seqType)
|
||||
{
|
||||
if (seqType == null || seqType == typeof(string))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (seqType.IsArray)
|
||||
{
|
||||
return typeof(IEnumerable<>).MakeGenericType(seqType.GetElementType());
|
||||
}
|
||||
if (seqType.IsGenericType)
|
||||
{
|
||||
foreach (Type arg in seqType.GetGenericArguments())
|
||||
{
|
||||
Type ienum = typeof(IEnumerable<>).MakeGenericType(arg);
|
||||
if (ienum.IsAssignableFrom(seqType))
|
||||
{
|
||||
return ienum;
|
||||
}
|
||||
}
|
||||
}
|
||||
Type[] ifaces = seqType.GetInterfaces();
|
||||
if (ifaces != null && ifaces.Length > 0)
|
||||
{
|
||||
foreach (Type iface in ifaces)
|
||||
{
|
||||
Type ienum = FindIEnumerable(iface);
|
||||
if (ienum != null)
|
||||
{
|
||||
return ienum;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (seqType.BaseType != null && seqType.BaseType != typeof(object))
|
||||
{
|
||||
return FindIEnumerable(seqType.BaseType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified PropertyDescriptor is publically
|
||||
/// exposed, based on DataContract visibility rules.
|
||||
/// </summary>
|
||||
internal static bool IsDataMember(PropertyDescriptor pd)
|
||||
{
|
||||
AttributeCollection attrs = pd.ComponentType.Attributes();
|
||||
|
||||
if (attrs[typeof(DataContractAttribute)] != null)
|
||||
{
|
||||
if (pd.Attributes[typeof(DataMemberAttribute)] == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pd.Attributes[typeof(IgnoreDataMemberAttribute)] != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains the set of known types from the <see cref="KnownTypeAttribute"/> custom attributes
|
||||
/// attached to the specified <paramref name="type"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This utility function either retrieving the declared types or invokes the method declared in <see cref="KnownTypeAttribute.MethodName"/>.
|
||||
/// </remarks>
|
||||
/// <param name="type">The type to examine for <see cref="KnownTypeAttribute"/>s</param>
|
||||
/// <param name="inherit"><c>true</c> to allow inheritance of <see cref="KnownTypeAttribute"/> from the base.</param>
|
||||
/// <returns>The distinct set of types fould via the <see cref="KnownTypeAttribute"/>s</returns>
|
||||
internal static IEnumerable<Type> GetKnownTypes(Type type, bool inherit)
|
||||
{
|
||||
IDictionary<Type, Type> knownTypes = new Dictionary<Type, Type>();
|
||||
IEnumerable<KnownTypeAttribute> knownTypeAttributes = type.GetCustomAttributes(typeof(KnownTypeAttribute), inherit).Cast<KnownTypeAttribute>();
|
||||
|
||||
foreach (KnownTypeAttribute knownTypeAttribute in knownTypeAttributes)
|
||||
{
|
||||
Type knownType = knownTypeAttribute.Type;
|
||||
if (knownType != null)
|
||||
{
|
||||
knownTypes[knownType] = knownType;
|
||||
}
|
||||
|
||||
string methodName = knownTypeAttribute.MethodName;
|
||||
if (!String.IsNullOrEmpty(methodName))
|
||||
{
|
||||
Type typeOfIEnumerableOfType = typeof(IEnumerable<Type>);
|
||||
MethodInfo methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
||||
if (methodInfo != null && typeOfIEnumerableOfType.IsAssignableFrom(methodInfo.ReturnType))
|
||||
{
|
||||
IEnumerable<Type> enumerable = methodInfo.Invoke(null, null) as IEnumerable<Type>;
|
||||
if (enumerable != null)
|
||||
{
|
||||
foreach (Type t in enumerable)
|
||||
{
|
||||
knownTypes[t] = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return knownTypes.Keys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the specified type is a generic Task, this function returns the
|
||||
/// inner task type.
|
||||
/// </summary>
|
||||
internal static Type UnwrapTaskInnerType(Type t)
|
||||
{
|
||||
if (typeof(Task).IsAssignableFrom(t) && t.IsGenericType)
|
||||
{
|
||||
return t.GetGenericArguments()[0];
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
[DebuggerDisplay("Action = {ActionName}, Type = {EntityType.Name}, Operation = {ChangeOperation}")]
|
||||
public class UpdateActionDescriptor : ReflectedHttpActionDescriptor
|
||||
{
|
||||
private readonly ChangeOperation _changeOperation;
|
||||
private readonly Type _entityType;
|
||||
private readonly MethodInfo _method;
|
||||
|
||||
public UpdateActionDescriptor(HttpControllerDescriptor controllerDescriptor, MethodInfo method, Type entityType, ChangeOperation operationType)
|
||||
: base(controllerDescriptor, method)
|
||||
{
|
||||
_entityType = entityType;
|
||||
_changeOperation = operationType;
|
||||
_method = method;
|
||||
}
|
||||
|
||||
public Type EntityType
|
||||
{
|
||||
get { return _entityType; }
|
||||
}
|
||||
|
||||
public ChangeOperation ChangeOperation
|
||||
{
|
||||
get { return _changeOperation; }
|
||||
}
|
||||
|
||||
public bool Authorize(HttpActionContext context)
|
||||
{
|
||||
// We only select Action scope Authorization filters, since Global and Class level filters will already
|
||||
// be executed when Submit is invoked. We only look at AuthorizationFilterAttributes because we are only
|
||||
// interested in running synchronous (i.e., quick to run) attributes.
|
||||
IEnumerable<AuthorizationFilterAttribute> authFilters =
|
||||
GetFilterPipeline()
|
||||
.Where(p => p.Scope == FilterScope.Action)
|
||||
.Select(p => p.Instance)
|
||||
.OfType<AuthorizationFilterAttribute>();
|
||||
|
||||
foreach (AuthorizationFilterAttribute authFilter in authFilters)
|
||||
{
|
||||
authFilter.OnAuthorization(context);
|
||||
|
||||
if (context.Response != null && !context.Response.IsSuccessStatusCode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments, CancellationToken cancellationToken)
|
||||
{
|
||||
return TaskHelpers.RunSynchronously(() =>
|
||||
{
|
||||
DataController controller = (DataController)controllerContext.Controller;
|
||||
object[] paramValues = arguments.Select(p => p.Value).ToArray();
|
||||
|
||||
return _method.Invoke(controller, paramValues);
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute applied to a <see cref="DataController"/> method to indicate that it is an update method.
|
||||
/// </summary>
|
||||
[AttributeUsage(
|
||||
AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property,
|
||||
AllowMultiple = false, Inherited = true)]
|
||||
public sealed class UpdateAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the method is a custom update operation.
|
||||
/// </summary>
|
||||
public bool UsingCustomMethod { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Web.Http;
|
||||
|
||||
namespace Microsoft.Web.Http.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// The data contract of an error that has occurred
|
||||
/// during the execution of an operation on the server.
|
||||
/// This is sent back along with the action
|
||||
/// result(s) to the client.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public sealed class ValidationResultInfo : IEquatable<ValidationResultInfo>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor accepting a localized error message and and collection
|
||||
/// of the names of the members the error originated from.
|
||||
/// </summary>
|
||||
/// <param name="message">The localized message</param>
|
||||
/// <param name="sourceMemberNames">A collection of the names of the members the error originated from.</param>
|
||||
public ValidationResultInfo(string message, IEnumerable<string> sourceMemberNames)
|
||||
{
|
||||
if (message == null)
|
||||
{
|
||||
throw Error.ArgumentNull("message");
|
||||
}
|
||||
|
||||
if (sourceMemberNames == null)
|
||||
{
|
||||
throw Error.ArgumentNull("sourceMemberNames");
|
||||
}
|
||||
|
||||
Message = message;
|
||||
SourceMemberNames = sourceMemberNames;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor accepting a localized error message, error code, optional stack trace,
|
||||
/// and collection of the names of the members the error originated from.
|
||||
/// </summary>
|
||||
/// <param name="message">The localized error message</param>
|
||||
/// <param name="errorCode">The custom error code</param>
|
||||
/// <param name="stackTrace">The error stack trace</param>
|
||||
/// <param name="sourceMemberNames">A collection of the names of the members the error originated from.</param>
|
||||
public ValidationResultInfo(string message, int errorCode, string stackTrace, IEnumerable<string> sourceMemberNames)
|
||||
{
|
||||
if (message == null)
|
||||
{
|
||||
throw Error.ArgumentNull("message");
|
||||
}
|
||||
|
||||
if (sourceMemberNames == null)
|
||||
{
|
||||
throw Error.ArgumentNull("sourceMemberNames");
|
||||
}
|
||||
|
||||
Message = message;
|
||||
ErrorCode = errorCode;
|
||||
StackTrace = stackTrace;
|
||||
SourceMemberNames = sourceMemberNames;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the error message
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets custom error code
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public int ErrorCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the stack trace of the error
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string StackTrace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the names of the members the error originated from.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public IEnumerable<string> SourceMemberNames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash code for this object.
|
||||
/// </summary>
|
||||
/// <returns>The hash code for this object.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Message.GetHashCode();
|
||||
}
|
||||
|
||||
#region IEquatable<ValidationResultInfo> Members
|
||||
|
||||
/// <summary>
|
||||
/// Test the current instance against the specified instance for equality
|
||||
/// </summary>
|
||||
/// <param name="other">The ValidationResultInfo to compare to</param>
|
||||
/// <returns>True if the instances are equal, false otherwise</returns>
|
||||
bool IEquatable<ValidationResultInfo>.Equals(ValidationResultInfo other)
|
||||
{
|
||||
if (Object.ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Object.ReferenceEquals(null, other))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return ((Message == other.Message) &&
|
||||
(ErrorCode == other.ErrorCode) &&
|
||||
(StackTrace == other.StackTrace) &&
|
||||
(SourceMemberNames.SequenceEqual(other.SourceMemberNames)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net40" />
|
||||
<package id="Newtonsoft.Json" version="4.5.6" targetFramework="net40" />
|
||||
</packages>
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
|
||||
[assembly: AssemblyTitle("SPA")]
|
||||
[assembly: AssemblyDescription("This is a dummy assembly to satisfy the build process")]
|
||||
[assembly: Guid("6df72360-ebfc-4097-96fa-2ee418c04f7b")]
|
|
@ -1,165 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
|
||||
<PropertyGroup>
|
||||
<ProductVersion>1.0.0</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{1ACEF677-B6A0-4680-A076-7893DE176D6B}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<ScriptOutputPath>$(OutputPath)</ScriptOutputPath>
|
||||
<OutputPath>bin\$(Configuration)</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DefineConstants>DEBUG;TRACE;CODE_ANALYSIS</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DefineConstants>TRACE;CODE_ANALYSIS</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeAnalysis|AnyCPU' ">
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="upshot\AssociatedEntitiesView.js" />
|
||||
<Content Include="upshot\Core.js" />
|
||||
<Content Include="upshot\DataContext.js" />
|
||||
<Content Include="upshot\DataProvider.js" />
|
||||
<Content Include="upshot\DataProvider.OData.js" />
|
||||
<Content Include="upshot\DataProvider.ria.js" />
|
||||
<Content Include="upshot\DataSource.js" />
|
||||
<Content Include="upshot\EntitySet.js" />
|
||||
<Content Include="upshot\EntitySource.js" />
|
||||
<Content Include="upshot\EntityView.js" />
|
||||
<Content Include="upshot\LocalDataSource.js" />
|
||||
<Content Include="upshot\Metadata.js" />
|
||||
<Content Include="upshot\Observability.js" />
|
||||
<Content Include="upshot\RemoteDataSource.js" />
|
||||
<Content Include="upshot\Upshot.Compat.jQueryUI.js" />
|
||||
<Content Include="upshot\Upshot.Compat.JsViews.js" />
|
||||
<Content Include="upshot\Upshot.Compat.Knockout.js" />
|
||||
<Content Include="upshot\Upshot.Compat.WinJS.js" />
|
||||
<Content Include="upshot\upshot.dataview.js" />
|
||||
<Content Include="nav\nav.js">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>nav.coffee</DependentUpon>
|
||||
</Content>
|
||||
<Content Include="nav\nav.transitions.js">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>nav.transitions.coffee</DependentUpon>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<IntelliSense Include="upshot\IntelliSense\Dependencies.js" />
|
||||
<IntelliSense Include="upshot\IntelliSense\jquery-1.5.2-vsdoc.js" />
|
||||
<IntelliSense Include="upshot\IntelliSense\knockout-2.0.0.debug.js" />
|
||||
<IntelliSense Include="upshot\IntelliSense\References.js" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="nav\nav.coffee">
|
||||
<Generator>CoffeeScriptGenerator</Generator>
|
||||
<LastGenOutput>nav.js</LastGenOutput>
|
||||
</None>
|
||||
<None Include="nav\nav.transitions.coffee">
|
||||
<Generator>CoffeeScriptGenerator</Generator>
|
||||
<LastGenOutput>nav.transitions.js</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<JSKnownGlobalNames>jQuery,$,upshot,WinJS,OData,JSON,ko</JSKnownGlobalNames>
|
||||
</PropertyGroup>
|
||||
<!-- We don't use these targets, but VS will try to upgrade the project when they aren't present -->
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<ProjectExtensions />
|
||||
<!-- SPA build targets -->
|
||||
<Import Project="SPA.targets" />
|
||||
<Target Name="CompileJs">
|
||||
<!-- purpose of compile step here is to do some basic syntax checking on the files prior to packaging -->
|
||||
<ItemGroup>
|
||||
<!-- for validation, compile all js files in the project; this doesn't mean they will be included in the package -->
|
||||
<_CompileJavascript Include="@(Content->'%(FullPath)')" Condition="%(Extension) == '.js'" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
<Target Name="BuildUpshot" BeforeTargets="Build">
|
||||
<PropertyGroup>
|
||||
<UpshotPkgOutputFile>$(ScriptOutputPath)\upshot.js</UpshotPkgOutputFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PkgOutputFile Include="$(UpshotPkgOutputFile)" />
|
||||
<!-- order matters here -->
|
||||
<UpshotPkg Include="upshot\Core.js" />
|
||||
<UpshotPkg Include="upshot\Observability.js" />
|
||||
<UpshotPkg Include="upshot\Metadata.js" />
|
||||
<UpshotPkg Include="upshot\EntitySource.js" />
|
||||
<UpshotPkg Include="upshot\EntityView.js" />
|
||||
<UpshotPkg Include="upshot\DataSource.js" />
|
||||
<UpshotPkg Include="upshot\EntitySet.js" />
|
||||
<UpshotPkg Include="upshot\DataContext.js" />
|
||||
<UpshotPkg Include="upshot\DataProvider.js" />
|
||||
<UpshotPkg Include="upshot\RemoteDataSource.js" />
|
||||
<UpshotPkg Include="upshot\AssociatedEntitiesView.js" />
|
||||
<UpshotPkg Include="upshot\LocalDataSource.js" />
|
||||
<UpshotPkg Include="upshot\DataProvider.OData.js" />
|
||||
<UpshotPkg Include="upshot\DataProvider.ria.js" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<UpshotJQueryPkgOutputFile>$(ScriptOutputPath)\Upshot.Compat.jQueryUI.js</UpshotJQueryPkgOutputFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PkgOutputFile Include="$(UpshotJQueryPkgOutputFile)" />
|
||||
<UpshotJQueryPkg Include="upshot\Upshot.Compat.jQueryUI.js" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<UpshotJsViewsPkgOutputFile>$(ScriptOutputPath)\Upshot.Compat.JsViews.js</UpshotJsViewsPkgOutputFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PkgOutputFile Include="$(UpshotJsViewsPkgOutputFile)" />
|
||||
<UpshotJsViewsPkg Include="upshot\Upshot.Compat.JsViews.js" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<UpshotKnockoutPkgOutputFile>$(ScriptOutputPath)\Upshot.Compat.Knockout.js</UpshotKnockoutPkgOutputFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PkgOutputFile Include="$(UpshotKnockoutPkgOutputFile)" />
|
||||
<UpshotKnockoutPkg Include="upshot\Upshot.Compat.Knockout.js" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<UpshotDataViewPkgOutputFile>$(ScriptOutputPath)\upshot.dataview.js</UpshotDataViewPkgOutputFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PkgOutputFile Include="$(UpshotDataViewPkgOutputFile)" />
|
||||
<UpshotDataViewPkg Include="upshot\upshot.dataview.js" />
|
||||
</ItemGroup>
|
||||
<ProcessScriptFiles ScriptFiles="@(UpshotPkg)" OutputFile="$(UpshotPkgOutputFile)" />
|
||||
<ProcessScriptFiles ScriptFiles="@(UpshotJQueryPkg)" OutputFile="$(UpshotJQueryPkgOutputFile)" />
|
||||
<ProcessScriptFiles ScriptFiles="@(UpshotJsViewsPkg)" OutputFile="$(UpshotJsViewsPkgOutputFile)" />
|
||||
<ProcessScriptFiles ScriptFiles="@(UpshotKnockoutPkg)" OutputFile="$(UpshotKnockoutPkgOutputFile)" />
|
||||
<ProcessScriptFiles ScriptFiles="@(UpshotDataViewPkg)" OutputFile="$(UpshotDataViewPkgOutputFile)" />
|
||||
</Target>
|
||||
<Target Name="BuildNav" BeforeTargets="Build">
|
||||
<ItemGroup>
|
||||
<NavJsFiles Include="nav\nav.js;nav\nav.transitions.js" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(NavJsFiles)" DestinationFolder="$(ScriptOutputPath)" />
|
||||
<ItemGroup>
|
||||
<NavPkgOutputFile Include="$(ScriptOutputPath)\nav.js" />
|
||||
<NavPkgOutputFile Include="$(ScriptOutputPath)\nav.transitions.js" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
<Target Name="CleanJs" BeforeTargets="Clean">
|
||||
<ItemGroup>
|
||||
<CleanPkgOutputFile Include="$(ScriptOutputPath)\upshot.js" />
|
||||
<CleanPkgOutputFile Include="$(ScriptOutputPath)\Upshot.Compat.jQueryUI.js" />
|
||||
<CleanPkgOutputFile Include="$(ScriptOutputPath)\Upshot.Compat.JsViews.js" />
|
||||
<CleanPkgOutputFile Include="$(ScriptOutputPath)\Upshot.Compat.Knockout.js" />
|
||||
<CleanPkgOutputFile Include="$(ScriptOutputPath)\upshot.dataview.js" />
|
||||
<CleanPkgOutputFile Include="$(ScriptOutputPath)\nav.js" />
|
||||
<CleanPkgOutputFile Include="$(ScriptOutputPath)\nav.transitions.js" />
|
||||
</ItemGroup>
|
||||
<Delete Files="@(CleanPkgOutputFile->'%(RootDir)%(Directory)%(Filename)%(Extension)')" />
|
||||
</Target>
|
||||
</Project>
|
|
@ -1,68 +0,0 @@
|
|||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<UsingTask TaskName="ProcessScriptFiles" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
|
||||
<ParameterGroup>
|
||||
<ScriptFiles ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
|
||||
<OutputFile ParameterType="Microsoft.Build.Framework.ITaskItem" Required="true" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Using Namespace="System"/>
|
||||
<Using Namespace="System.IO"/>
|
||||
<Using Namespace="System.Text"/>
|
||||
<Using Namespace="System.Text.RegularExpressions"/>
|
||||
<Using Namespace="Microsoft.Build.Framework"/>
|
||||
<Using Namespace="Microsoft.Build.Utilities"/>
|
||||
<Code Type="Fragment" Language="cs">
|
||||
<![CDATA[
|
||||
try
|
||||
{
|
||||
string output = string.Empty;
|
||||
|
||||
// Adding copyright
|
||||
output +=
|
||||
"// Copyright (c) Microsoft. All rights reserved." + Environment.NewLine +
|
||||
"// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation" + Environment.NewLine +
|
||||
"// files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy," + Environment.NewLine +
|
||||
"// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the" + Environment.NewLine +
|
||||
"// Software is furnished to do so, subject to the following conditions:" + Environment.NewLine +
|
||||
"//" + Environment.NewLine +
|
||||
"// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software." + Environment.NewLine +
|
||||
"//" + Environment.NewLine +
|
||||
"// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE" + Environment.NewLine +
|
||||
"// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR" + Environment.NewLine +
|
||||
"// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE," + Environment.NewLine +
|
||||
"// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + Environment.NewLine;
|
||||
|
||||
// Removing /// <reference path="script.js" /> lines
|
||||
// Removing ///#RESTORE from lines
|
||||
Regex regex = new Regex("/// <reference path=\"[^\"]*\" />\r+\n|///#RESTORE ", RegexOptions.Multiline | RegexOptions.Compiled);
|
||||
|
||||
foreach (ITaskItem file in ScriptFiles)
|
||||
{
|
||||
string fullPath = Path.GetFullPath(file.ItemSpec);
|
||||
|
||||
// Adding the original file name
|
||||
output +=
|
||||
Environment.NewLine +
|
||||
"///" + Environment.NewLine +
|
||||
"/// " + Path.GetFileName(fullPath) + Environment.NewLine +
|
||||
"///" + Environment.NewLine +
|
||||
Environment.NewLine;
|
||||
|
||||
output += regex.Replace(File.ReadAllText(fullPath), "");
|
||||
}
|
||||
|
||||
string outputPath = Path.GetFullPath(OutputFile.ItemSpec);
|
||||
File.WriteAllText(outputPath, output, Encoding.UTF8);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.LogErrorFromException(ex);
|
||||
return false;
|
||||
}
|
||||
]]>
|
||||
</Code>
|
||||
</Task>
|
||||
</UsingTask>
|
||||
</Project>
|
|
@ -1,222 +0,0 @@
|
|||
###!
|
||||
nav.js v0.1 - (c) Microsoft Corporation
|
||||
###
|
||||
|
||||
# Small helper function for creating jQuery-style read/write function wrappers around an underlying value
|
||||
readWriteValue = (initialValue) ->
|
||||
currentValue = initialValue
|
||||
return () ->
|
||||
if arguments.length > 0 then currentValue = arguments[0]
|
||||
currentValue
|
||||
|
||||
class window.NavHistory
|
||||
constructor: (opts) ->
|
||||
@options = opts || {}
|
||||
@options.params = @_extend({}, @options.params, @_asString) # Ensure all defaults are strings
|
||||
@isLinkedToUrl = false
|
||||
|
||||
# If KO is referenced, @position and @entries will be observable. Otherwise, use plain JS objects.
|
||||
# You can override this via @options.ko if you want.
|
||||
useKo = if 'ko' of @options then @options.ko else ko?.observable?
|
||||
@position = if useKo then ko.observable(-1) else readWriteValue(-1)
|
||||
@entries = if useKo then ko.observableArray([]) else []
|
||||
|
||||
length: => @_entriesArray().length
|
||||
relative: (offset) => @_entriesArray()[@position() + offset] || {}
|
||||
current: => @relative(0)
|
||||
params: => @current().params || {}
|
||||
loadedData: => @current().loadedData
|
||||
back: => if @position() > 0 then @navigateAll(@relative(-1).params)
|
||||
forward: => if @position() < @length() - 1 then @navigateAll(@relative(1).params)
|
||||
_entriesArray: => if typeof @entries == 'function' then @entries() else @entries
|
||||
|
||||
initialize: (opts) ->
|
||||
if opts?.linkToUrl
|
||||
@_linkToUrl()
|
||||
else
|
||||
@navigateAll(opts?.params || {})
|
||||
return this
|
||||
|
||||
navigate: (newParams, opts) =>
|
||||
# Retain any params not specified
|
||||
newParamsPlusCurrent = @_extend(@_extend({}, @params()), newParams)
|
||||
@navigateAll(newParamsPlusCurrent, opts)
|
||||
|
||||
navigateAll: (newParams, opts) =>
|
||||
newParams = @_normalizeParams(newParams)
|
||||
isBack = false
|
||||
isForward = false
|
||||
isNoChange = false
|
||||
transition = opts?.transition
|
||||
navEntry = null
|
||||
|
||||
if @length() && @_propsAreEqual(newParams, @params())
|
||||
# We're already there. No need to create a new nav entry.
|
||||
if opts?.force
|
||||
isNoChange = true
|
||||
navEntry = @current()
|
||||
else
|
||||
# In the absence of a "force" directive, we don't even need to navigate at all
|
||||
return
|
||||
|
||||
else if @_propsAreEqual(newParams, @relative(-1)?.params)
|
||||
# It's a "back" - reuse existing transition if no new one was given
|
||||
isBack = true
|
||||
transition = transition || @current().savedTransition
|
||||
navEntry = @relative(-1)
|
||||
|
||||
else if @_propsAreEqual(newParams, @relative(1)?.params)
|
||||
# It's a "forward" - reuse existing transition if no new one was given
|
||||
isForward = true
|
||||
navEntry = @relative(1)
|
||||
transition = transition || @current().savedTransition
|
||||
|
||||
else
|
||||
# Entirely new navigation - create new entry
|
||||
navEntry = { params: newParams, navEntryId: "navEntry_" + @_getUniqueSequenceValue() }
|
||||
|
||||
# Extra param for beforeNavigate/onNavigate callbacks
|
||||
# Note that navInfo.transition is what will be used during the current navigation (regardless of direction)
|
||||
# whereas navEntry.forwardTransition is what we're storing in case we need to reuse transitions in the future
|
||||
navInfo = { isFirst: @length() == 0, isBack: isBack, isForward: isForward, transition: transition }
|
||||
|
||||
beforeNavigateCallback = () =>
|
||||
if isBack
|
||||
# Consider also doing a History.back() here. This feels more natural if there's only a single NavHistory instance,
|
||||
# because then programmatic back/forwards is the same as using the browser controls. But if you have multiple NavHistory
|
||||
# instances, you can't be sure that History.back() will take you to the same place that this instance has logged.
|
||||
@position(@position() - 1)
|
||||
else if isForward
|
||||
# As above comment, except with History.forwards
|
||||
@position(@position() + 1)
|
||||
else if !isNoChange
|
||||
# Clear "forward" items, and add new entry
|
||||
deleteCount = @length() - @position() - 1
|
||||
@entries().splice(@position() + 1, deleteCount, navEntry)
|
||||
|
||||
# Move to it, possibly by removing the first entry if we've exceeded capacity
|
||||
if @options.maxEntries && @length() > @options.maxEntries
|
||||
@entries.shift() # This will notify subscribers to @entries, if it's observable
|
||||
else
|
||||
@position(@position() + 1)
|
||||
# Notify subscribers to @entries, if it's observable
|
||||
if typeof @entries.valueHasMutated == 'function'
|
||||
@entries.valueHasMutated()
|
||||
|
||||
if !isBack && navInfo.transition
|
||||
@current().savedTransition = navInfo.transition
|
||||
|
||||
# Consider only using pushState for totally new navigations (not isBack and not isForwards), and using History.back()
|
||||
# and History.forwards() as in above comments.
|
||||
if @isLinkedToUrl && (opts?.updateUrl != false) && !isNoChange
|
||||
updatedQueryString = @_getUpdatedQueryString(@params())
|
||||
window.NavHistory.historyProvider.pushState({ url: updatedQueryString })
|
||||
|
||||
if @options.onNavigate
|
||||
@options.onNavigate.call(this, @current(), navInfo)
|
||||
|
||||
if !@options.beforeNavigate
|
||||
beforeNavigateCallback()
|
||||
else
|
||||
threadLoadToken = @objectLoadToken = {}
|
||||
@options.beforeNavigate.call(this, navEntry, navInfo, ((loadedData) =>
|
||||
# Ignore the callback unless threadLoadToken still matches. Avoids race conditions.
|
||||
if threadLoadToken == @objectLoadToken
|
||||
if loadedData != undefined
|
||||
navEntry.loadedData = loadedData
|
||||
beforeNavigateCallback()
|
||||
))
|
||||
this
|
||||
|
||||
_asString: (val) -> if val == null || val == undefined then "" else val.toString()
|
||||
|
||||
_extend: (target, source, mapFunction) ->
|
||||
for own key, value of source
|
||||
target[key] = if mapFunction then mapFunction(value) else value
|
||||
target
|
||||
|
||||
_normalizeParams: (params) ->
|
||||
# Normalized params are purely strings, and contain a value for every default
|
||||
defaults = @options.params || {}
|
||||
@_extend(@_extend({}, defaults), params || {}, @_asString)
|
||||
|
||||
_propsAreEqual: (obj1, obj2) ->
|
||||
if !(obj1 && obj2)
|
||||
return obj1 == obj2
|
||||
for own obj1key, obj1value of obj1
|
||||
if obj2[obj1key] != obj1value then return false
|
||||
for own obj2key, obj2value of obj2
|
||||
if obj1[obj2key] != obj2value then return false
|
||||
true
|
||||
|
||||
_parseQueryString: (url) ->
|
||||
if url.indexOf('?') < 0 then return {}
|
||||
query = url.substring(url.lastIndexOf('?') + 1)
|
||||
result = {}
|
||||
for pair in query.split("&")
|
||||
tokens = pair.split("=")
|
||||
if (tokens.length == 2)
|
||||
result[tokens[0]] = decodeURIComponent(tokens[1])
|
||||
result
|
||||
|
||||
_formatQueryString: (params) ->
|
||||
formattedUrl = '?'
|
||||
for own key, value of params
|
||||
if formattedUrl != '?'
|
||||
formattedUrl += '&'
|
||||
formattedUrl += key + '=' + encodeURIComponent(value)
|
||||
formattedUrl
|
||||
|
||||
_getUpdatedQueryString: (params) ->
|
||||
# Take the existing query string...
|
||||
allUrlParams = @_parseQueryString(window.NavHistory.historyProvider.getState().url)
|
||||
|
||||
# ... update the params based on the supplied arg (removing any that correspond to our default)
|
||||
for own key, defaultValue of @options.params
|
||||
suppliedValue = params[key]
|
||||
if suppliedValue == defaultValue
|
||||
delete allUrlParams[key]
|
||||
else
|
||||
allUrlParams[key] = suppliedValue
|
||||
|
||||
# ... and return the resulting querystring
|
||||
@_formatQueryString(allUrlParams)
|
||||
|
||||
_getUniqueSequenceValue: () ->
|
||||
NavHistory._sequence = NavHistory._sequence || 0
|
||||
(NavHistory._sequence++).toString()
|
||||
|
||||
_linkToUrl: ->
|
||||
@isLinkedToUrl = true
|
||||
onStateChange = =>
|
||||
# Get the subset of URL params that applies to this NavHistory instance
|
||||
applicableParams = {}
|
||||
allUrlParams = @_parseQueryString(window.NavHistory.historyProvider.getState().url)
|
||||
defaults = @options.params || {}
|
||||
for own key, value of allUrlParams when defaults.hasOwnProperty(key)
|
||||
applicableParams[key] = value
|
||||
|
||||
# ... and navigate to the new params
|
||||
@navigateAll(applicableParams, { updateUrl: false })
|
||||
|
||||
# Perform initial navigation (loads state from the requested URL)
|
||||
onStateChange()
|
||||
|
||||
# Respond to future URL changes too
|
||||
window.NavHistory.historyProvider.onStateChange(onStateChange)
|
||||
|
||||
# Default history provider is history.js
|
||||
window.NavHistory.historyProvider =
|
||||
onStateChange: (handler) -> History.Adapter.bind(window, 'statechange', handler)
|
||||
pushState: (data) -> History.pushState(null, null, data.url)
|
||||
getState: -> History.getState()
|
||||
back: -> History.back()
|
||||
|
||||
# Helper to display a specific element, simultaneously hiding its siblings. Useful for toggling panes, tabs, etc.
|
||||
# Does not require any DOM library.
|
||||
window.NavHistory.showPane = (elementId, navInfo) ->
|
||||
elemToShow = document.getElementById(elementId)
|
||||
if elemToShow
|
||||
for sibling in elemToShow.parentNode.childNodes when sibling.nodeType == 1
|
||||
sibling.style.display = 'none'
|
||||
elemToShow.style.display = 'block'
|
|
@ -1,317 +0,0 @@
|
|||
(function() {
|
||||
|
||||
/*!
|
||||
nav.js v0.1 - (c) Microsoft Corporation
|
||||
*/
|
||||
|
||||
var readWriteValue;
|
||||
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty;
|
||||
|
||||
readWriteValue = function(initialValue) {
|
||||
var currentValue;
|
||||
currentValue = initialValue;
|
||||
return function() {
|
||||
if (arguments.length > 0) currentValue = arguments[0];
|
||||
return currentValue;
|
||||
};
|
||||
};
|
||||
|
||||
window.NavHistory = (function() {
|
||||
|
||||
function NavHistory(opts) {
|
||||
this.navigateAll = __bind(this.navigateAll, this);
|
||||
this.navigate = __bind(this.navigate, this);
|
||||
this._entriesArray = __bind(this._entriesArray, this);
|
||||
this.forward = __bind(this.forward, this);
|
||||
this.back = __bind(this.back, this);
|
||||
this.loadedData = __bind(this.loadedData, this);
|
||||
this.params = __bind(this.params, this);
|
||||
this.current = __bind(this.current, this);
|
||||
this.relative = __bind(this.relative, this);
|
||||
this.length = __bind(this.length, this);
|
||||
var useKo;
|
||||
this.options = opts || {};
|
||||
this.options.params = this._extend({}, this.options.params, this._asString);
|
||||
this.isLinkedToUrl = false;
|
||||
useKo = 'ko' in this.options ? this.options.ko : (typeof ko !== "undefined" && ko !== null ? ko.observable : void 0) != null;
|
||||
this.position = useKo ? ko.observable(-1) : readWriteValue(-1);
|
||||
this.entries = useKo ? ko.observableArray([]) : [];
|
||||
}
|
||||
|
||||
NavHistory.prototype.length = function() {
|
||||
return this._entriesArray().length;
|
||||
};
|
||||
|
||||
NavHistory.prototype.relative = function(offset) {
|
||||
return this._entriesArray()[this.position() + offset] || {};
|
||||
};
|
||||
|
||||
NavHistory.prototype.current = function() {
|
||||
return this.relative(0);
|
||||
};
|
||||
|
||||
NavHistory.prototype.params = function() {
|
||||
return this.current().params || {};
|
||||
};
|
||||
|
||||
NavHistory.prototype.loadedData = function() {
|
||||
return this.current().loadedData;
|
||||
};
|
||||
|
||||
NavHistory.prototype.back = function() {
|
||||
if (this.position() > 0) return this.navigateAll(this.relative(-1).params);
|
||||
};
|
||||
|
||||
NavHistory.prototype.forward = function() {
|
||||
if (this.position() < this.length() - 1) {
|
||||
return this.navigateAll(this.relative(1).params);
|
||||
}
|
||||
};
|
||||
|
||||
NavHistory.prototype._entriesArray = function() {
|
||||
if (typeof this.entries === 'function') {
|
||||
return this.entries();
|
||||
} else {
|
||||
return this.entries;
|
||||
}
|
||||
};
|
||||
|
||||
NavHistory.prototype.initialize = function(opts) {
|
||||
if (opts != null ? opts.linkToUrl : void 0) {
|
||||
this._linkToUrl();
|
||||
} else {
|
||||
this.navigateAll((opts != null ? opts.params : void 0) || {});
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
NavHistory.prototype.navigate = function(newParams, opts) {
|
||||
var newParamsPlusCurrent;
|
||||
newParamsPlusCurrent = this._extend(this._extend({}, this.params()), newParams);
|
||||
return this.navigateAll(newParamsPlusCurrent, opts);
|
||||
};
|
||||
|
||||
NavHistory.prototype.navigateAll = function(newParams, opts) {
|
||||
var beforeNavigateCallback, isBack, isForward, isNoChange, navEntry, navInfo, threadLoadToken, transition, _ref, _ref2;
|
||||
var _this = this;
|
||||
newParams = this._normalizeParams(newParams);
|
||||
isBack = false;
|
||||
isForward = false;
|
||||
isNoChange = false;
|
||||
transition = opts != null ? opts.transition : void 0;
|
||||
navEntry = null;
|
||||
if (this.length() && this._propsAreEqual(newParams, this.params())) {
|
||||
if (opts != null ? opts.force : void 0) {
|
||||
isNoChange = true;
|
||||
navEntry = this.current();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (this._propsAreEqual(newParams, (_ref = this.relative(-1)) != null ? _ref.params : void 0)) {
|
||||
isBack = true;
|
||||
transition = transition || this.current().savedTransition;
|
||||
navEntry = this.relative(-1);
|
||||
} else if (this._propsAreEqual(newParams, (_ref2 = this.relative(1)) != null ? _ref2.params : void 0)) {
|
||||
isForward = true;
|
||||
navEntry = this.relative(1);
|
||||
transition = transition || this.current().savedTransition;
|
||||
} else {
|
||||
navEntry = {
|
||||
params: newParams,
|
||||
navEntryId: "navEntry_" + this._getUniqueSequenceValue()
|
||||
};
|
||||
}
|
||||
navInfo = {
|
||||
isFirst: this.length() === 0,
|
||||
isBack: isBack,
|
||||
isForward: isForward,
|
||||
transition: transition
|
||||
};
|
||||
beforeNavigateCallback = function() {
|
||||
var deleteCount, updatedQueryString;
|
||||
if (isBack) {
|
||||
_this.position(_this.position() - 1);
|
||||
} else if (isForward) {
|
||||
_this.position(_this.position() + 1);
|
||||
} else if (!isNoChange) {
|
||||
deleteCount = _this.length() - _this.position() - 1;
|
||||
_this.entries().splice(_this.position() + 1, deleteCount, navEntry);
|
||||
if (_this.options.maxEntries && _this.length() > _this.options.maxEntries) {
|
||||
_this.entries.shift();
|
||||
} else {
|
||||
_this.position(_this.position() + 1);
|
||||
if (typeof _this.entries.valueHasMutated === 'function') {
|
||||
_this.entries.valueHasMutated();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isBack && navInfo.transition) {
|
||||
_this.current().savedTransition = navInfo.transition;
|
||||
}
|
||||
if (_this.isLinkedToUrl && ((opts != null ? opts.updateUrl : void 0) !== false) && !isNoChange) {
|
||||
updatedQueryString = _this._getUpdatedQueryString(_this.params());
|
||||
window.NavHistory.historyProvider.pushState({
|
||||
url: updatedQueryString
|
||||
});
|
||||
}
|
||||
if (_this.options.onNavigate) {
|
||||
return _this.options.onNavigate.call(_this, _this.current(), navInfo);
|
||||
}
|
||||
};
|
||||
if (!this.options.beforeNavigate) {
|
||||
beforeNavigateCallback();
|
||||
} else {
|
||||
threadLoadToken = this.objectLoadToken = {};
|
||||
this.options.beforeNavigate.call(this, navEntry, navInfo, (function(loadedData) {
|
||||
if (threadLoadToken === _this.objectLoadToken) {
|
||||
if (loadedData !== void 0) navEntry.loadedData = loadedData;
|
||||
return beforeNavigateCallback();
|
||||
}
|
||||
}));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
NavHistory.prototype._asString = function(val) {
|
||||
if (val === null || val === void 0) {
|
||||
return "";
|
||||
} else {
|
||||
return val.toString();
|
||||
}
|
||||
};
|
||||
|
||||
NavHistory.prototype._extend = function(target, source, mapFunction) {
|
||||
var key, value;
|
||||
for (key in source) {
|
||||
if (!__hasProp.call(source, key)) continue;
|
||||
value = source[key];
|
||||
target[key] = mapFunction ? mapFunction(value) : value;
|
||||
}
|
||||
return target;
|
||||
};
|
||||
|
||||
NavHistory.prototype._normalizeParams = function(params) {
|
||||
var defaults;
|
||||
defaults = this.options.params || {};
|
||||
return this._extend(this._extend({}, defaults), params || {}, this._asString);
|
||||
};
|
||||
|
||||
NavHistory.prototype._propsAreEqual = function(obj1, obj2) {
|
||||
var obj1key, obj1value, obj2key, obj2value;
|
||||
if (!(obj1 && obj2)) return obj1 === obj2;
|
||||
for (obj1key in obj1) {
|
||||
if (!__hasProp.call(obj1, obj1key)) continue;
|
||||
obj1value = obj1[obj1key];
|
||||
if (obj2[obj1key] !== obj1value) return false;
|
||||
}
|
||||
for (obj2key in obj2) {
|
||||
if (!__hasProp.call(obj2, obj2key)) continue;
|
||||
obj2value = obj2[obj2key];
|
||||
if (obj1[obj2key] !== obj2value) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
NavHistory.prototype._parseQueryString = function(url) {
|
||||
var pair, query, result, tokens, _i, _len, _ref;
|
||||
if (url.indexOf('?') < 0) return {};
|
||||
query = url.substring(url.lastIndexOf('?') + 1);
|
||||
result = {};
|
||||
_ref = query.split("&");
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
pair = _ref[_i];
|
||||
tokens = pair.split("=");
|
||||
if (tokens.length === 2) result[tokens[0]] = decodeURIComponent(tokens[1]);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
NavHistory.prototype._formatQueryString = function(params) {
|
||||
var formattedUrl, key, value;
|
||||
formattedUrl = '?';
|
||||
for (key in params) {
|
||||
if (!__hasProp.call(params, key)) continue;
|
||||
value = params[key];
|
||||
if (formattedUrl !== '?') formattedUrl += '&';
|
||||
formattedUrl += key + '=' + encodeURIComponent(value);
|
||||
}
|
||||
return formattedUrl;
|
||||
};
|
||||
|
||||
NavHistory.prototype._getUpdatedQueryString = function(params) {
|
||||
var allUrlParams, defaultValue, key, suppliedValue, _ref;
|
||||
allUrlParams = this._parseQueryString(window.NavHistory.historyProvider.getState().url);
|
||||
_ref = this.options.params;
|
||||
for (key in _ref) {
|
||||
if (!__hasProp.call(_ref, key)) continue;
|
||||
defaultValue = _ref[key];
|
||||
suppliedValue = params[key];
|
||||
if (suppliedValue === defaultValue) {
|
||||
delete allUrlParams[key];
|
||||
} else {
|
||||
allUrlParams[key] = suppliedValue;
|
||||
}
|
||||
}
|
||||
return this._formatQueryString(allUrlParams);
|
||||
};
|
||||
|
||||
NavHistory.prototype._getUniqueSequenceValue = function() {
|
||||
NavHistory._sequence = NavHistory._sequence || 0;
|
||||
return (NavHistory._sequence++).toString();
|
||||
};
|
||||
|
||||
NavHistory.prototype._linkToUrl = function() {
|
||||
var onStateChange;
|
||||
var _this = this;
|
||||
this.isLinkedToUrl = true;
|
||||
onStateChange = function() {
|
||||
var allUrlParams, applicableParams, defaults, key, value;
|
||||
applicableParams = {};
|
||||
allUrlParams = _this._parseQueryString(window.NavHistory.historyProvider.getState().url);
|
||||
defaults = _this.options.params || {};
|
||||
for (key in allUrlParams) {
|
||||
if (!__hasProp.call(allUrlParams, key)) continue;
|
||||
value = allUrlParams[key];
|
||||
if (defaults.hasOwnProperty(key)) applicableParams[key] = value;
|
||||
}
|
||||
return _this.navigateAll(applicableParams, {
|
||||
updateUrl: false
|
||||
});
|
||||
};
|
||||
onStateChange();
|
||||
return window.NavHistory.historyProvider.onStateChange(onStateChange);
|
||||
};
|
||||
|
||||
return NavHistory;
|
||||
|
||||
})();
|
||||
|
||||
window.NavHistory.historyProvider = {
|
||||
onStateChange: function(handler) {
|
||||
return History.Adapter.bind(window, 'statechange', handler);
|
||||
},
|
||||
pushState: function(data) {
|
||||
return History.pushState(null, null, data.url);
|
||||
},
|
||||
getState: function() {
|
||||
return History.getState();
|
||||
},
|
||||
back: function() {
|
||||
return History.back();
|
||||
}
|
||||
};
|
||||
|
||||
window.NavHistory.showPane = function(elementId, navInfo) {
|
||||
var elemToShow, sibling, _i, _len, _ref;
|
||||
elemToShow = document.getElementById(elementId);
|
||||
if (elemToShow) {
|
||||
_ref = elemToShow.parentNode.childNodes;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
sibling = _ref[_i];
|
||||
if (sibling.nodeType === 1) sibling.style.display = 'none';
|
||||
}
|
||||
return elemToShow.style.display = 'block';
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
|
@ -1,231 +0,0 @@
|
|||
###!
|
||||
nav.transitions.js v0.1 - (c) Microsoft Corporation
|
||||
###
|
||||
|
||||
# Pane transitions. Currently assumes availability of XUI. Need to generalise for jQuery.
|
||||
$ = x$
|
||||
|
||||
# Feature detection
|
||||
features =
|
||||
vendor:
|
||||
if (/webkit/i).test(navigator.appVersion) then 'webkit'
|
||||
else if (/firefox/i).test(navigator.userAgent) then 'Moz'
|
||||
else if 'opera' of window then 'O'
|
||||
else ''
|
||||
isAndroid: (/android/gi).test(navigator.appVersion)
|
||||
|
||||
features.useCssTransform = (!features.isAndroid) && (features.vendor + 'Transform' of document.documentElement.style)
|
||||
features.cssTransformPrefix = "-" + features.vendor.toLowerCase() + "-"
|
||||
features.transitionEndEvent =
|
||||
if features.vendor == 'webkit' then 'webkitTransitionEnd'
|
||||
else if features.vendor == 'O' then 'oTransitionEnd'
|
||||
else 'transitionend'
|
||||
|
||||
$.isTouch = 'ontouchstart' of document.documentElement
|
||||
$.clickOrTouch = if $.isTouch then 'touchstart' else 'click'
|
||||
features.supportsCssTouchScroll = (typeof document.body.style.webkitOverflowScrolling != "undefined") # Currently only iOS 5 can do native touch scrolling
|
||||
features.supportsIScroll = (features.vendor == 'webkit' || features.vendor == "Moz")
|
||||
|
||||
# Utilities
|
||||
findFirstChildWithClass = (elem, className) ->
|
||||
child = elem.firstChild
|
||||
while child
|
||||
if $(child).hasClass(className) then return child
|
||||
child = child.nextSibling
|
||||
return null
|
||||
|
||||
oppositeDirection = { left: 'right', right: 'left', top: 'bottom', bottom: 'top' }
|
||||
oppositeTransition = (transition) ->
|
||||
if transition
|
||||
for own key of transition
|
||||
if $.paneTransitionInverters.hasOwnProperty(key)
|
||||
return $.paneTransitionInverters[key](transition[key])
|
||||
null
|
||||
|
||||
# Implement $.getJSON and $.map like jQuery does
|
||||
$.getJSON = (url, options) ->
|
||||
callback = if typeof options == "function" then options else options.callback
|
||||
$(null).xhr(url, {
|
||||
method: options.method,
|
||||
async: true,
|
||||
data: JSON.stringify(options.data),
|
||||
headers: options.headers,
|
||||
callback: ->
|
||||
callback(JSON.parse(this.responseText))
|
||||
})
|
||||
|
||||
$.map = (items, map) ->
|
||||
results = []
|
||||
for item in items
|
||||
mapped = map(item)
|
||||
if mapped != undefined
|
||||
results.push(mapped)
|
||||
results
|
||||
|
||||
# XUI extensions
|
||||
$.fn.togglePane = (show) ->
|
||||
@each ->
|
||||
# Can't just toggle display:block/none, as that resets any scroll positions within the pane
|
||||
# Can't just toggle visibility:visible/hidden either, as Safari iOS still sends events (e.g., taps) into the element
|
||||
# So, just move it very far away
|
||||
if show
|
||||
@style.display = 'block'
|
||||
if @isOffScreen
|
||||
@isOffScreen = false
|
||||
@style.top = ''
|
||||
@style.bottom = ''
|
||||
else
|
||||
@isOffScreen = true
|
||||
@style.top = '-10000px'
|
||||
@style.bottom = '10000px'
|
||||
this
|
||||
|
||||
$.fn.afterNextTransition = (callback) ->
|
||||
@each ->
|
||||
elem = this
|
||||
handlerWrapper = () ->
|
||||
callback.apply(this, arguments)
|
||||
elem.removeEventListener(features.transitionEndEvent, handlerWrapper)
|
||||
elem.addEventListener(features.transitionEndEvent, handlerWrapper);
|
||||
|
||||
$.fn.animateTranslation = (finalPos, transition, callback) ->
|
||||
callback = callback || () -> { }
|
||||
@each ->
|
||||
$this = $(this)
|
||||
if features.useCssTransform
|
||||
transform = {}
|
||||
transform[features.cssTransformPrefix + "transform"] = "translate(" + finalPos.left + ", " + finalPos.top + ")"
|
||||
transform[features.cssTransformPrefix + "transition"] = if transition then features.cssTransformPrefix + "transform 250ms ease-out" else null
|
||||
|
||||
if transition
|
||||
$this.afterNextTransition(callback)
|
||||
|
||||
$this.css(transform)
|
||||
if !transition
|
||||
callback()
|
||||
else
|
||||
if transition
|
||||
$this.tween(finalPos, callback)
|
||||
else
|
||||
$this.css(finalPos)
|
||||
callback()
|
||||
|
||||
$.fn.setPanePosition = (position, transition, callback) ->
|
||||
callback = callback || () -> { }
|
||||
|
||||
@each ->
|
||||
$this = $(this).togglePane(true)
|
||||
x = 0
|
||||
y = 0
|
||||
width = @parentNode.offsetWidth
|
||||
height = @parentNode.offsetHeight
|
||||
|
||||
switch position
|
||||
when 'right' then x = width
|
||||
when 'left' then x = -1 * width
|
||||
when 'top' then y = -1 * height
|
||||
when 'bottom' then y = height
|
||||
|
||||
finalPos = { left: x + 'px', right: (-1 * x) + 'px', top: y + 'px', bottom: (-1 * y) + 'px' }
|
||||
$this.animateTranslation(finalPos, transition, callback)
|
||||
|
||||
$.fn.slidePane = (options) ->
|
||||
@each ->
|
||||
$this = $(this)
|
||||
afterSlide = ->
|
||||
if options.to then $this.togglePane(false)
|
||||
if options.callback then options.callback()
|
||||
$this.setPanePosition(options.from, null)
|
||||
.setPanePosition(options.to, true, afterSlide)
|
||||
|
||||
$.fn.showPane = (options) ->
|
||||
options = options || {}
|
||||
@each ->
|
||||
activePane = findFirstChildWithClass(this.parentNode, "active")
|
||||
if activePane != this # Not already shown
|
||||
$(this).has(".scroll-y.autoscroll").touchScroll({ hScroll: false })
|
||||
$(this).has(".scroll-x.autoscroll").touchScroll({ yScroll: false })
|
||||
|
||||
# Find and invoke the requested transition
|
||||
transitionToUse = 'default'
|
||||
for own transitionKey of $.paneTransitions
|
||||
if options.hasOwnProperty(transitionKey)
|
||||
transitionToUse = transitionKey
|
||||
break
|
||||
$.paneTransitions[transitionKey](this, activePane, options[transitionKey])
|
||||
|
||||
# Keep track of which pane is active
|
||||
$(this).addClass("active")
|
||||
if activePane
|
||||
$(activePane).removeClass("active")
|
||||
|
||||
$.fn.showBySlidingParent = (options) ->
|
||||
@each ->
|
||||
targetPaneOffsetLeft = parseInt(@style.left) || 0
|
||||
targetPaneOffsetTop = parseInt(@style.top) || 0
|
||||
finalPos =
|
||||
left: (-1*targetPaneOffsetLeft) + '%'
|
||||
right: targetPaneOffsetLeft + '%'
|
||||
top: (-1*targetPaneOffsetTop) + '%'
|
||||
bottom: targetPaneOffsetTop + '%'
|
||||
$(this).css({ display: 'block' })
|
||||
$(@parentNode).css({ 'overflow': 'visible' }).animateTranslation(finalPos, options.animate != false)
|
||||
this
|
||||
|
||||
$.fn.touchScroll = (options) ->
|
||||
if (!features.supportsCssTouchScroll) && features.supportsIScroll
|
||||
@each ->
|
||||
if !@hasIScroll
|
||||
@hasIScroll = new iScroll(this, options)
|
||||
doRefresh = => @hasIScroll.refresh()
|
||||
setTimeout(doRefresh, 0)
|
||||
this
|
||||
this
|
||||
|
||||
$.fn.clickOrTouch = (handler) ->
|
||||
@on($.clickOrTouch, handler)
|
||||
|
||||
# Create missing event shortcuts for IE version of XUI
|
||||
for eventName in ['click']
|
||||
if (!$.fn[eventName])
|
||||
$.fn[eventName] = (handler) -> @on(eventName, handler)
|
||||
|
||||
# Transitions
|
||||
$.paneTransitions =
|
||||
slideFrom: (incomingPane, outgoingPane, options) ->
|
||||
$(incomingPane).slidePane({ from: options })
|
||||
if outgoingPane
|
||||
$(outgoingPane).slidePane({ to: oppositeDirection[options] })
|
||||
|
||||
coverFrom: (incomingPane, outgoingPane, options) ->
|
||||
outgoingZIndex = outgoingPane?.style.zIndex || 0
|
||||
$(incomingPane).css({ zIndex: outgoingZIndex + 1 })
|
||||
.slidePane({
|
||||
from: options,
|
||||
callback: () -> $(outgoingPane).togglePane(false)
|
||||
})
|
||||
|
||||
uncoverTo: (incomingPane, outgoingPane, options) ->
|
||||
incomingZIndex = incomingPane.style.zIndex || 0;
|
||||
$(incomingPane).togglePane(true).setPanePosition()
|
||||
$(outgoingPane).css({ zIndex: incomingZIndex + 1 }).slidePane({ to: options })
|
||||
|
||||
default: (incomingPane, outgoingPane, options) ->
|
||||
# No transition - just show instantly, and hide the previously active pane
|
||||
$(incomingPane).togglePane(true).setPanePosition()
|
||||
$(outgoingPane).togglePane(false)
|
||||
|
||||
$.paneTransitionInverters =
|
||||
slideFrom: (direction) -> { slideFrom: oppositeDirection[direction] }
|
||||
coverFrom: (direction) -> { uncoverTo: direction }
|
||||
uncoverTo: (direction) -> { coverFrom: direction }
|
||||
|
||||
# Hook into nav.js so you can easily animate transitions on navigation
|
||||
window.NavHistory.animatePane = (elementId, navInfo) ->
|
||||
transition = navInfo.transition || (if !navInfo.isFirst then { slideFrom: 'right' } else null)
|
||||
if navInfo.isBack
|
||||
transition = oppositeTransition(transition)
|
||||
x$('#' + elementId).showPane(transition)
|
||||
|
||||
window.NavHistory.slideParent = (elementId, navInfo) ->
|
||||
x$('#' + elementId).showBySlidingParent({ animate: !navInfo.isFirst })
|
|
@ -1,338 +0,0 @@
|
|||
(function() {
|
||||
|
||||
/*!
|
||||
nav.transitions.js v0.1 - (c) Microsoft Corporation
|
||||
*/
|
||||
|
||||
var $, eventName, features, findFirstChildWithClass, oppositeDirection, oppositeTransition, _i, _len, _ref;
|
||||
var __hasProp = Object.prototype.hasOwnProperty;
|
||||
|
||||
$ = x$;
|
||||
|
||||
features = {
|
||||
vendor: /webkit/i.test(navigator.appVersion) ? 'webkit' : /firefox/i.test(navigator.userAgent) ? 'Moz' : 'opera' in window ? 'O' : '',
|
||||
isAndroid: /android/gi.test(navigator.appVersion)
|
||||
};
|
||||
|
||||
features.useCssTransform = (!features.isAndroid) && (features.vendor + 'Transform' in document.documentElement.style);
|
||||
|
||||
features.cssTransformPrefix = "-" + features.vendor.toLowerCase() + "-";
|
||||
|
||||
features.transitionEndEvent = features.vendor === 'webkit' ? 'webkitTransitionEnd' : features.vendor === 'O' ? 'oTransitionEnd' : 'transitionend';
|
||||
|
||||
$.isTouch = 'ontouchstart' in document.documentElement;
|
||||
|
||||
$.clickOrTouch = $.isTouch ? 'touchstart' : 'click';
|
||||
|
||||
features.supportsCssTouchScroll = typeof document.body.style.webkitOverflowScrolling !== "undefined";
|
||||
|
||||
features.supportsIScroll = features.vendor === 'webkit' || features.vendor === "Moz";
|
||||
|
||||
findFirstChildWithClass = function(elem, className) {
|
||||
var child;
|
||||
child = elem.firstChild;
|
||||
while (child) {
|
||||
if ($(child).hasClass(className)) return child;
|
||||
child = child.nextSibling;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
oppositeDirection = {
|
||||
left: 'right',
|
||||
right: 'left',
|
||||
top: 'bottom',
|
||||
bottom: 'top'
|
||||
};
|
||||
|
||||
oppositeTransition = function(transition) {
|
||||
var key;
|
||||
if (transition) {
|
||||
for (key in transition) {
|
||||
if (!__hasProp.call(transition, key)) continue;
|
||||
if ($.paneTransitionInverters.hasOwnProperty(key)) {
|
||||
return $.paneTransitionInverters[key](transition[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
$.getJSON = function(url, options) {
|
||||
var callback;
|
||||
callback = typeof options === "function" ? options : options.callback;
|
||||
return $(null).xhr(url, {
|
||||
method: options.method,
|
||||
async: true,
|
||||
data: JSON.stringify(options.data),
|
||||
headers: options.headers,
|
||||
callback: function() {
|
||||
return callback(JSON.parse(this.responseText));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$.map = function(items, map) {
|
||||
var item, mapped, results, _i, _len;
|
||||
results = [];
|
||||
for (_i = 0, _len = items.length; _i < _len; _i++) {
|
||||
item = items[_i];
|
||||
mapped = map(item);
|
||||
if (mapped !== void 0) results.push(mapped);
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
$.fn.togglePane = function(show) {
|
||||
return this.each(function() {
|
||||
if (show) {
|
||||
this.style.display = 'block';
|
||||
if (this.isOffScreen) {
|
||||
this.isOffScreen = false;
|
||||
this.style.top = '';
|
||||
this.style.bottom = '';
|
||||
}
|
||||
} else {
|
||||
this.isOffScreen = true;
|
||||
this.style.top = '-10000px';
|
||||
this.style.bottom = '10000px';
|
||||
}
|
||||
return this;
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.afterNextTransition = function(callback) {
|
||||
return this.each(function() {
|
||||
var elem, handlerWrapper;
|
||||
elem = this;
|
||||
handlerWrapper = function() {
|
||||
callback.apply(this, arguments);
|
||||
return elem.removeEventListener(features.transitionEndEvent, handlerWrapper);
|
||||
};
|
||||
return elem.addEventListener(features.transitionEndEvent, handlerWrapper);
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.animateTranslation = function(finalPos, transition, callback) {
|
||||
callback = callback || function() {
|
||||
return {};
|
||||
};
|
||||
return this.each(function() {
|
||||
var $this, transform;
|
||||
$this = $(this);
|
||||
if (features.useCssTransform) {
|
||||
transform = {};
|
||||
transform[features.cssTransformPrefix + "transform"] = "translate(" + finalPos.left + ", " + finalPos.top + ")";
|
||||
transform[features.cssTransformPrefix + "transition"] = transition ? features.cssTransformPrefix + "transform 250ms ease-out" : null;
|
||||
if (transition) $this.afterNextTransition(callback);
|
||||
$this.css(transform);
|
||||
if (!transition) return callback();
|
||||
} else {
|
||||
if (transition) {
|
||||
return $this.tween(finalPos, callback);
|
||||
} else {
|
||||
$this.css(finalPos);
|
||||
return callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.setPanePosition = function(position, transition, callback) {
|
||||
callback = callback || function() {
|
||||
return {};
|
||||
};
|
||||
return this.each(function() {
|
||||
var $this, finalPos, height, width, x, y;
|
||||
$this = $(this).togglePane(true);
|
||||
x = 0;
|
||||
y = 0;
|
||||
width = this.parentNode.offsetWidth;
|
||||
height = this.parentNode.offsetHeight;
|
||||
switch (position) {
|
||||
case 'right':
|
||||
x = width;
|
||||
break;
|
||||
case 'left':
|
||||
x = -1 * width;
|
||||
break;
|
||||
case 'top':
|
||||
y = -1 * height;
|
||||
break;
|
||||
case 'bottom':
|
||||
y = height;
|
||||
}
|
||||
finalPos = {
|
||||
left: x + 'px',
|
||||
right: (-1 * x) + 'px',
|
||||
top: y + 'px',
|
||||
bottom: (-1 * y) + 'px'
|
||||
};
|
||||
return $this.animateTranslation(finalPos, transition, callback);
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.slidePane = function(options) {
|
||||
return this.each(function() {
|
||||
var $this, afterSlide;
|
||||
$this = $(this);
|
||||
afterSlide = function() {
|
||||
if (options.to) $this.togglePane(false);
|
||||
if (options.callback) return options.callback();
|
||||
};
|
||||
return $this.setPanePosition(options.from, null).setPanePosition(options.to, true, afterSlide);
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.showPane = function(options) {
|
||||
options = options || {};
|
||||
return this.each(function() {
|
||||
var activePane, transitionKey, transitionToUse, _ref;
|
||||
activePane = findFirstChildWithClass(this.parentNode, "active");
|
||||
if (activePane !== this) {
|
||||
$(this).has(".scroll-y.autoscroll").touchScroll({
|
||||
hScroll: false
|
||||
});
|
||||
$(this).has(".scroll-x.autoscroll").touchScroll({
|
||||
yScroll: false
|
||||
});
|
||||
transitionToUse = 'default';
|
||||
_ref = $.paneTransitions;
|
||||
for (transitionKey in _ref) {
|
||||
if (!__hasProp.call(_ref, transitionKey)) continue;
|
||||
if (options.hasOwnProperty(transitionKey)) {
|
||||
transitionToUse = transitionKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$.paneTransitions[transitionKey](this, activePane, options[transitionKey]);
|
||||
$(this).addClass("active");
|
||||
if (activePane) return $(activePane).removeClass("active");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.showBySlidingParent = function(options) {
|
||||
return this.each(function() {
|
||||
var finalPos, targetPaneOffsetLeft, targetPaneOffsetTop;
|
||||
targetPaneOffsetLeft = parseInt(this.style.left) || 0;
|
||||
targetPaneOffsetTop = parseInt(this.style.top) || 0;
|
||||
finalPos = {
|
||||
left: (-1 * targetPaneOffsetLeft) + '%',
|
||||
right: targetPaneOffsetLeft + '%',
|
||||
top: (-1 * targetPaneOffsetTop) + '%',
|
||||
bottom: targetPaneOffsetTop + '%'
|
||||
};
|
||||
$(this).css({
|
||||
display: 'block'
|
||||
});
|
||||
$(this.parentNode).css({
|
||||
'overflow': 'visible'
|
||||
}).animateTranslation(finalPos, options.animate !== false);
|
||||
return this;
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.touchScroll = function(options) {
|
||||
if ((!features.supportsCssTouchScroll) && features.supportsIScroll) {
|
||||
this.each(function() {
|
||||
var doRefresh;
|
||||
var _this = this;
|
||||
if (!this.hasIScroll) this.hasIScroll = new iScroll(this, options);
|
||||
doRefresh = function() {
|
||||
return _this.hasIScroll.refresh();
|
||||
};
|
||||
setTimeout(doRefresh, 0);
|
||||
return this;
|
||||
});
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
$.fn.clickOrTouch = function(handler) {
|
||||
return this.on($.clickOrTouch, handler);
|
||||
};
|
||||
|
||||
_ref = ['click'];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
eventName = _ref[_i];
|
||||
if (!$.fn[eventName]) {
|
||||
$.fn[eventName] = function(handler) {
|
||||
return this.on(eventName, handler);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
$.paneTransitions = {
|
||||
slideFrom: function(incomingPane, outgoingPane, options) {
|
||||
$(incomingPane).slidePane({
|
||||
from: options
|
||||
});
|
||||
if (outgoingPane) {
|
||||
return $(outgoingPane).slidePane({
|
||||
to: oppositeDirection[options]
|
||||
});
|
||||
}
|
||||
},
|
||||
coverFrom: function(incomingPane, outgoingPane, options) {
|
||||
var outgoingZIndex;
|
||||
outgoingZIndex = (outgoingPane != null ? outgoingPane.style.zIndex : void 0) || 0;
|
||||
return $(incomingPane).css({
|
||||
zIndex: outgoingZIndex + 1
|
||||
}).slidePane({
|
||||
from: options,
|
||||
callback: function() {
|
||||
return $(outgoingPane).togglePane(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
uncoverTo: function(incomingPane, outgoingPane, options) {
|
||||
var incomingZIndex;
|
||||
incomingZIndex = incomingPane.style.zIndex || 0;
|
||||
$(incomingPane).togglePane(true).setPanePosition();
|
||||
return $(outgoingPane).css({
|
||||
zIndex: incomingZIndex + 1
|
||||
}).slidePane({
|
||||
to: options
|
||||
});
|
||||
},
|
||||
"default": function(incomingPane, outgoingPane, options) {
|
||||
$(incomingPane).togglePane(true).setPanePosition();
|
||||
return $(outgoingPane).togglePane(false);
|
||||
}
|
||||
};
|
||||
|
||||
$.paneTransitionInverters = {
|
||||
slideFrom: function(direction) {
|
||||
return {
|
||||
slideFrom: oppositeDirection[direction]
|
||||
};
|
||||
},
|
||||
coverFrom: function(direction) {
|
||||
return {
|
||||
uncoverTo: direction
|
||||
};
|
||||
},
|
||||
uncoverTo: function(direction) {
|
||||
return {
|
||||
coverFrom: direction
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
window.NavHistory.animatePane = function(elementId, navInfo) {
|
||||
var transition;
|
||||
transition = navInfo.transition || (!navInfo.isFirst ? {
|
||||
slideFrom: 'right'
|
||||
} : null);
|
||||
if (navInfo.isBack) transition = oppositeTransition(transition);
|
||||
return x$('#' + elementId).showPane(transition);
|
||||
};
|
||||
|
||||
window.NavHistory.slideParent = function(elementId, navInfo) {
|
||||
return x$('#' + elementId).showBySlidingParent({
|
||||
animate: !navInfo.isFirst
|
||||
});
|
||||
};
|
||||
|
||||
}).call(this);
|
|
@ -1,221 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
var base = upshot.EntityView.prototype;
|
||||
|
||||
var obs = upshot.observability;
|
||||
|
||||
var ctor = function (entity, parentEntitySet, childEntitySet, associationMetadata, parentPropertySetter, result) {
|
||||
this._entity = entity;
|
||||
this._parentEntitySet = parentEntitySet;
|
||||
this._childEntitySet = childEntitySet;
|
||||
this._associationMetadata = associationMetadata;
|
||||
this._parentPropertySetter = parentPropertySetter;
|
||||
|
||||
// The EntityView base class observes its "source" option (which is the target entity set) for
|
||||
// array- and property-changes.
|
||||
// Additionally, we need to observe property changes on the source entity set to catch:
|
||||
// - FK property changes that would affect a parent association property
|
||||
// - PK (non-FK) property changes that would affect a child association property
|
||||
var self = this;
|
||||
this._sourceEntitySetObserver = function (entity, property, newValue) {
|
||||
if (!self._needRecompute &&
|
||||
$.inArray(property, associationMetadata.thisKey) >= 0) {
|
||||
self._setNeedRecompute();
|
||||
}
|
||||
};
|
||||
var sourceEntitySet = associationMetadata.isForeignKey ? childEntitySet : parentEntitySet;
|
||||
sourceEntitySet.bind("propertyChanged", this._sourceEntitySetObserver);
|
||||
|
||||
var entitySource = associationMetadata.isForeignKey ? parentEntitySet : childEntitySet;
|
||||
base.constructor.call(this, { source: entitySource, result: result });
|
||||
|
||||
// We only ever instantiate AssociatedEntitiesViews when adding entities to
|
||||
// an EntitySet, which always ends with a recompute.
|
||||
this._initialized = false;
|
||||
this._setNeedRecompute();
|
||||
};
|
||||
|
||||
var instanceMembers = {
|
||||
|
||||
// Internal methods
|
||||
|
||||
// This is called from EntitySet.js as it treats tracked changes to parent association
|
||||
// properties on child entities.
|
||||
__handleParentPropertySet: function (parentEntity) {
|
||||
this._handleRelationshipEdit(this._entity, parentEntity);
|
||||
},
|
||||
|
||||
// Private methods
|
||||
|
||||
_dispose: function () {
|
||||
var sourceEntitySet = this._associationMetadata.isForeignKey ? this._childEntitySet : this._parentEntitySet;
|
||||
sourceEntitySet.unbind("propertyChanged", this._sourceEntitySetObserver);
|
||||
base._dispose.apply(this, arguments);
|
||||
},
|
||||
|
||||
_handleEntityAdd: function (entity) {
|
||||
this._handleRelationshipEdit(entity, this._entity);
|
||||
},
|
||||
|
||||
// Do the appropriate EntitySet adds and FK property changes to reflect an editied relationship
|
||||
// between childEntity and parentEntity.
|
||||
_handleRelationshipEdit: function (childEntity, parentEntity) {
|
||||
var associationMetadata = this._associationMetadata,
|
||||
isForeignKey = associationMetadata.isForeignKey,
|
||||
parentKeyValue;
|
||||
if (!parentEntity) {
|
||||
parentKeyValue = null;
|
||||
} else {
|
||||
if ($.inArray(parentEntity, obs.asArray(this._parentEntitySet.getEntities())) < 0) {
|
||||
// TODO -- Should this implicitly add the parent entity? I doubt it.
|
||||
throw "Parent entity is not in the parent entity set for this association.";
|
||||
} else if ((this._parentEntitySet.getEntityState(parentEntity) || "").indexOf("Add") > 0) {
|
||||
// TODO -- Add support for added parent entities without an established key value, fix-up after commit.
|
||||
throw "NYI -- Cannot set foreign keys to key values computed from added entities. Commit the parent entity first.";
|
||||
}
|
||||
|
||||
var parentKey = isForeignKey ? associationMetadata.otherKey : associationMetadata.thisKey;
|
||||
parentKeyValue = obs.getProperty(parentEntity, parentKey[0]); // TODO -- Generalize to N fields.
|
||||
if (parentKeyValue === undefined) {
|
||||
throw "Parent entity has no value for its '" + parentKey[0] + "' key property.";
|
||||
}
|
||||
}
|
||||
|
||||
var childKey = isForeignKey ? associationMetadata.thisKey : associationMetadata.otherKey,
|
||||
childKeyValue = obs.getProperty(childEntity, childKey[0]), // TODO -- Generalize to N fields.
|
||||
setForeignKeyValue;
|
||||
if (!parentEntity) {
|
||||
if (childKeyValue !== null) {
|
||||
setForeignKeyValue = true;
|
||||
}
|
||||
} else if (childKeyValue === undefined || childKeyValue !== parentKeyValue) {
|
||||
setForeignKeyValue = true;
|
||||
}
|
||||
|
||||
var isAddToChildEntities = !isForeignKey;
|
||||
if (isAddToChildEntities && $.inArray(childEntity, obs.asArray(this._entitySource.getEntities())) < 0) {
|
||||
// Base class will translate add to child entities into an add on our input EntitySet.
|
||||
base._handleEntityAdd.call(this, childEntity);
|
||||
}
|
||||
|
||||
if (setForeignKeyValue) {
|
||||
// Do this after the entitySet add above. That way, the property change will be observable by clients
|
||||
// interested in childEntitiesCollection or the EntitySet.
|
||||
// Likewise, above, we will have done obs.track (as part of adding to the EntitySet) before
|
||||
// obs.setProperty, in case establishing observable proxies is done implicitly w/in setProperty
|
||||
// (as WinJS support does).
|
||||
this._childEntitySet.__setProperty(childEntity, childKey[0], parentKeyValue); // TODO -- Generalize to N fields.
|
||||
}
|
||||
},
|
||||
|
||||
_onPropertyChanged: function (entity, property, newValue) {
|
||||
if (!this._needRecompute &&
|
||||
$.inArray(property, this._associationMetadata.otherKey) >= 0) {
|
||||
this._setNeedRecompute();
|
||||
}
|
||||
base._onPropertyChanged.apply(this, arguments);
|
||||
},
|
||||
|
||||
_onArrayChanged: function (type, eventArgs) {
|
||||
if (this._needRecompute) {
|
||||
return;
|
||||
}
|
||||
|
||||
var needRecompute;
|
||||
switch (type) {
|
||||
case "insert":
|
||||
case "remove":
|
||||
var self = this;
|
||||
$.each(eventArgs.items, function (index, entity) {
|
||||
if (self._haveEntity(entity) ^ type === "insert") {
|
||||
needRecompute = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "replaceAll":
|
||||
needRecompute = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw "NYI -- Array operation '" + type + "' is not supported.";
|
||||
}
|
||||
|
||||
if (needRecompute) {
|
||||
this._setNeedRecompute();
|
||||
}
|
||||
},
|
||||
|
||||
_recompute: function () {
|
||||
var clientEntities = this._clientEntities,
|
||||
newEntities = this._computeAssociatedEntities();
|
||||
|
||||
if (!this._initialized) {
|
||||
this._initialized = true;
|
||||
|
||||
if (newEntities.length > 0) { // Don't event a replaceAll if we're not actually modifying the entities array.
|
||||
var oldEntities = obs.asArray(clientEntities).slice(); // Here, assume a live array. It will be for jQuery compat.
|
||||
obs.refresh(clientEntities, newEntities);
|
||||
this._trigger("arrayChanged", "replaceAll", { oldItems: oldEntities, newItems: obs.asArray(clientEntities) });
|
||||
}
|
||||
} else {
|
||||
// Perform adds/removes on clientEntities to have it reflect the same membership
|
||||
// as newEntities. Issue change events for the adds/removes.
|
||||
// Don't try to preserve ordering between clientEntities and newEntities.
|
||||
// Assume that obs.asArray returns a non-live array instance. It will be for Knockout compat.
|
||||
// Don't cache obs.asArray(clientEntities) below.
|
||||
var self = this;
|
||||
|
||||
var addedEntities = $.grep(newEntities, function (entity) {
|
||||
return $.inArray(entity, obs.asArray(clientEntities)) < 0;
|
||||
});
|
||||
$.each(addedEntities, function (unused, entity) {
|
||||
var index = obs.asArray(clientEntities).length,
|
||||
items = [entity];
|
||||
obs.insert(clientEntities, index, items);
|
||||
self._trigger("arrayChanged", "insert", { index: index, items: items });
|
||||
});
|
||||
|
||||
var removedEntities = $.grep(obs.asArray(clientEntities), function (entity) {
|
||||
return $.inArray(entity, newEntities) < 0;
|
||||
});
|
||||
$.each(removedEntities, function (unused, entity) {
|
||||
var indexRemove = $.inArray(entity, obs.asArray(clientEntities));
|
||||
obs.remove(clientEntities, indexRemove, 1);
|
||||
self._trigger("arrayChanged", "remove", { index: indexRemove, items: [entity] });
|
||||
});
|
||||
}
|
||||
|
||||
if (this._parentPropertySetter) {
|
||||
// EntitySet.js has supplied a handler with which to make observable changes
|
||||
// to a parent association property on a child entity.
|
||||
this._parentPropertySetter.apply(this);
|
||||
}
|
||||
},
|
||||
|
||||
_computeAssociatedEntities: function () {
|
||||
var entity = this._entity,
|
||||
associationMetadata = this._associationMetadata,
|
||||
sourceKeyValue = obs.getProperty(entity, associationMetadata.thisKey[0]), // TODO -- Generalize to N fields.
|
||||
targetEntitySet = associationMetadata.isForeignKey ? this._parentEntitySet : this._childEntitySet,
|
||||
targetEntities = obs.asArray(targetEntitySet.getEntities()),
|
||||
targetKey = associationMetadata.otherKey,
|
||||
associatedEntities = [];
|
||||
for (var i = 0; i < targetEntities.length; i++) {
|
||||
var targetEntity = targetEntities[i],
|
||||
targetKeyValue = obs.getProperty(targetEntity, targetKey[0]); // TODO -- Generalize to N fields.
|
||||
if (targetKeyValue !== undefined && targetKeyValue === sourceKeyValue) {
|
||||
associatedEntities.push(targetEntity);
|
||||
}
|
||||
}
|
||||
return associatedEntities;
|
||||
}
|
||||
|
||||
// TODO -- Make array removals from "_clientEntities" null out foreign key values.
|
||||
};
|
||||
|
||||
upshot.AssociatedEntitiesView = upshot.deriveClass(base, ctor, instanceMembers);
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
|
@ -1,276 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, undefined)
|
||||
{
|
||||
|
||||
function extend(target, members) {
|
||||
for (var member in members) {
|
||||
target[member] = members[member];
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
function defineNamespace(name) {
|
||||
var names = name.split(".");
|
||||
var current = global;
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var ns = current[names[i]];
|
||||
if (!ns || typeof ns !== "object") {
|
||||
current[names[i]] = ns = {};
|
||||
}
|
||||
current = ns;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
function defineClass(ctor, instanceMembers, classMembers) {
|
||||
ctor = ctor || function () { };
|
||||
if (instanceMembers) {
|
||||
extend(ctor.prototype, instanceMembers);
|
||||
}
|
||||
if (classMembers) {
|
||||
extend(ctor, classMembers);
|
||||
}
|
||||
return ctor;
|
||||
}
|
||||
|
||||
function deriveClass(basePrototype, ctor, instanceMembers) {
|
||||
var prototype = {};
|
||||
extend(prototype, basePrototype);
|
||||
extend(prototype, instanceMembers); // Will override like-named members on basePrototype.
|
||||
|
||||
ctor = ctor || function () { };
|
||||
ctor.prototype = prototype;
|
||||
ctor.prototype.constructor = ctor;
|
||||
return ctor;
|
||||
}
|
||||
|
||||
function classof(o) {
|
||||
if (o === null) {
|
||||
return "null";
|
||||
}
|
||||
if (o === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
return Object.prototype.toString.call(o).slice(8, -1).toLowerCase();
|
||||
}
|
||||
|
||||
function isArray(o) {
|
||||
return classof(o) === "array";
|
||||
}
|
||||
|
||||
function isObject(o) {
|
||||
return classof(o) === "object";
|
||||
}
|
||||
|
||||
function isValueArray(o) {
|
||||
return isArray(o) && (o.length === 0 || !(isArray(o[0]) || isObject(o[0])));
|
||||
}
|
||||
|
||||
function isDate(o) {
|
||||
return classof(o) === "date";
|
||||
}
|
||||
|
||||
function isFunction(o) {
|
||||
return classof(o) === "function";
|
||||
}
|
||||
|
||||
function isGuid(value) {
|
||||
return (typeof value === "string") && /[a-fA-F\d]{8}-(?:[a-fA-F\d]{4}-){3}[a-fA-F\d]{12}/.test(value);
|
||||
}
|
||||
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
function isEmpty(obj) {
|
||||
if (obj === null || obj === undefined) {
|
||||
return true;
|
||||
}
|
||||
for (var key in obj) {
|
||||
if (hasOwnProperty.call(obj, key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
var idCounter = 0;
|
||||
function uniqueId(prefix) {
|
||||
/// <summary>Generates a unique id (unique within the entire client session)</summary>
|
||||
/// <param name="prefix" type="String">Optional prefix to the id</param>
|
||||
/// <returns type="String" />
|
||||
prefix || (prefix = "");
|
||||
return prefix + idCounter++;
|
||||
}
|
||||
|
||||
function cache(object, key, value) {
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
if (arguments.length === 2) {
|
||||
// read
|
||||
var cacheName = upshot.cacheName;
|
||||
if (cacheName && object[cacheName]) {
|
||||
return object[cacheName][key];
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
// write
|
||||
if (object.nodeType !== undefined) {
|
||||
throw "upshot.cache cannot be used with DOM elements";
|
||||
}
|
||||
var cacheName = upshot.cacheName || (upshot.cacheName = uniqueId("__upshot__"));
|
||||
object[cacheName] || (object[cacheName] = function () { });
|
||||
return object[cacheName][key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function deleteCache(object, key) {
|
||||
var cacheName = upshot.cacheName;
|
||||
if (cacheName && object && object[cacheName]) {
|
||||
if (key) {
|
||||
delete object[cacheName][key];
|
||||
}
|
||||
if (!key || isEmpty(object[cacheName])) {
|
||||
delete object[cacheName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sameArrayContents(array1, array2) {
|
||||
if (array1.length !== array2.length) {
|
||||
return false;
|
||||
} else {
|
||||
for (var i = 0; i < array1.length; i++) {
|
||||
if (array1[i] !== array2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// This routine provides an equivalent of array.push(item) missing from JavaScript array.
|
||||
function arrayRemove(array, item) {
|
||||
var callback = upshot.isFunction(item) ? item : undefined;
|
||||
for (var index = 0; index < array.length; index++) {
|
||||
if (callback ? callback(array[index]) : (array[index] === item)) {
|
||||
array.splice(index, 1);
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// pre-defined ns
|
||||
///#RESTORE var upshot = defineNamespace("upshot");
|
||||
|
||||
// pre-defined routines
|
||||
upshot.extend = extend;
|
||||
upshot.defineNamespace = defineNamespace;
|
||||
upshot.defineClass = defineClass;
|
||||
upshot.deriveClass = deriveClass;
|
||||
upshot.classof = classof;
|
||||
upshot.isArray = isArray;
|
||||
upshot.isObject = isObject;
|
||||
upshot.isValueArray = isValueArray;
|
||||
upshot.isDate = isDate;
|
||||
upshot.isFunction = isFunction;
|
||||
upshot.isGuid = isGuid;
|
||||
upshot.isEmpty = isEmpty;
|
||||
upshot.uniqueId = uniqueId;
|
||||
upshot.cacheName = null;
|
||||
upshot.cache = cache;
|
||||
upshot.deleteCache = deleteCache;
|
||||
upshot.sameArrayContents = sameArrayContents;
|
||||
upshot.arrayRemove = arrayRemove;
|
||||
|
||||
upshot.EntityState = {
|
||||
Unmodified: "Unmodified",
|
||||
ClientUpdated: "ClientUpdated",
|
||||
ClientAdded: "ClientAdded",
|
||||
ClientDeleted: "ClientDeleted",
|
||||
ServerUpdating: "ServerUpdating",
|
||||
ServerAdding: "ServerAdding",
|
||||
ServerDeleting: "ServerDeleting",
|
||||
Deleted: "Deleted",
|
||||
|
||||
isClientModified: function (entityState) {
|
||||
return entityState && entityState.indexOf("Client") === 0;
|
||||
},
|
||||
isServerSyncing: function (entityState) {
|
||||
return entityState && entityState.indexOf("Server") === 0;
|
||||
},
|
||||
isUpdated: function (entityState) {
|
||||
return entityState && entityState.indexOf("Updat") > 0;
|
||||
},
|
||||
isDeleted: function (entityState) {
|
||||
return entityState && entityState.indexOf("Delet") > 0;
|
||||
},
|
||||
isAdded: function (entityState) {
|
||||
return entityState && entityState.indexOf("Add") > 0;
|
||||
}
|
||||
};
|
||||
|
||||
upshot.ChangeKind = {
|
||||
Add: "Add",
|
||||
Update: "Update",
|
||||
Delete: "Delete"
|
||||
};
|
||||
|
||||
///#DEBUG
|
||||
upshot.assert = function (cond, msg) {
|
||||
if (!cond) {
|
||||
alert(msg || "assert is encountered!");
|
||||
}
|
||||
}
|
||||
///#ENDDEBUG
|
||||
|
||||
var entitySources = [];
|
||||
function registerRootEntitySource (entitySource) {
|
||||
entitySources.push(entitySource);
|
||||
}
|
||||
|
||||
function deregisterRootEntitySource (entitySource) {
|
||||
entitySources.splice($.inArray(entitySource, entitySources), 1);
|
||||
}
|
||||
|
||||
var recomputeInProgress;
|
||||
function triggerRecompute () {
|
||||
if (recomputeInProgress) {
|
||||
throw "Cannot make observable edits from within an event callback.";
|
||||
}
|
||||
|
||||
try {
|
||||
recomputeInProgress = true;
|
||||
|
||||
var sources = entitySources.slice();
|
||||
$.each(sources, function (index, source) {
|
||||
source.__recomputeDependentViews();
|
||||
});
|
||||
|
||||
$.each(sources, function (index, source) {
|
||||
if (source.__flushEntityStateChangedEvents) {
|
||||
source.__flushEntityStateChangedEvents();
|
||||
}
|
||||
});
|
||||
}
|
||||
finally {
|
||||
recomputeInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
function beginChange () {
|
||||
if (recomputeInProgress) {
|
||||
throw "Cannot make observable edits from within an event callback.";
|
||||
}
|
||||
}
|
||||
|
||||
function endChange () {
|
||||
triggerRecompute();
|
||||
}
|
||||
|
||||
upshot.__registerRootEntitySource = registerRootEntitySource;
|
||||
upshot.__deregisterRootEntitySource = deregisterRootEntitySource;
|
||||
upshot.__triggerRecompute = triggerRecompute;
|
||||
upshot.__beginChange = beginChange;
|
||||
upshot.__endChange = endChange;
|
||||
}
|
||||
///#RESTORE )(this);
|
|
@ -1,511 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
var obs = upshot.observability;
|
||||
|
||||
var ctor = function (dataProvider, implicitCommitHandler, mappings) {
|
||||
|
||||
// support no new ctor
|
||||
if (this._trigger === undefined) {
|
||||
return new upshot.DataContext(dataProvider, implicitCommitHandler);
|
||||
}
|
||||
|
||||
this._dataProvider = dataProvider;
|
||||
this.__manageAssociations = true; // TODO: Make this configurable by the app. Fix unmanaged associations.
|
||||
this._implicitCommitHandler = implicitCommitHandler;
|
||||
|
||||
this._eventCallbacks = {};
|
||||
this._entitySets = {};
|
||||
|
||||
this._mappings = {};
|
||||
if (mappings) {
|
||||
this.addMapping(mappings);
|
||||
}
|
||||
};
|
||||
|
||||
function getProviderParameters(type, parameters) {
|
||||
var result;
|
||||
if (parameters) {
|
||||
// first include any explicit get/submit
|
||||
// param properties
|
||||
result = $.extend(result, parameters[type] || {});
|
||||
|
||||
// next, add any additional "outer" properties
|
||||
for (var prop in parameters) {
|
||||
if (prop !== "get" && prop !== "submit") {
|
||||
result[prop] = parameters[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
var instanceMembers = {
|
||||
|
||||
// Public methods
|
||||
|
||||
dispose: function () {
|
||||
/// <summary>
|
||||
/// Disposes the DataContext instance.
|
||||
/// </summary>
|
||||
|
||||
if (this._entitySets) { // Use _entitySets as an indicator as to whether we've been disposed.
|
||||
$.each(this._entitySets, function (index, entitySet) {
|
||||
entitySet.__dispose();
|
||||
});
|
||||
this._entitySets = null;
|
||||
}
|
||||
},
|
||||
|
||||
// TODO: bind/unbind/_trigger are duplicated in EntitySource and DataContext, consider common routine.
|
||||
bind: function (event, callback) {
|
||||
/// <summary>
|
||||
/// Registers the supplied callback to be called when an event is raised.
|
||||
/// </summary>
|
||||
/// <param name="event" type="String">
|
||||
/// The event name.
|
||||
/// </param>
|
||||
/// <param name="callback" type="Function">
|
||||
/// The callback function.
|
||||
/// </param>
|
||||
/// <returns type="upshot.DataContext"/>
|
||||
|
||||
if (typeof event === "string") {
|
||||
var list = this._eventCallbacks[event] || (this._eventCallbacks[event] = []);
|
||||
list.push(callback);
|
||||
} else {
|
||||
for (var key in event) {
|
||||
this.bind(key, event[key]);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
unbind: function (event, callback) {
|
||||
/// <summary>
|
||||
/// Deregisters the supplied callback for the supplied event.
|
||||
/// </summary>
|
||||
/// <param name="event" type="String">
|
||||
/// The event name.
|
||||
/// </param>
|
||||
/// <param name="callback" type="Function">
|
||||
/// The callback function to be deregistered.
|
||||
/// </param>
|
||||
/// <returns type="upshot.DataContext"/>
|
||||
|
||||
if (typeof event === "string") {
|
||||
var list = this._eventCallbacks[event];
|
||||
if (list) {
|
||||
for (var i = 0, l = list.length; i < l; i++) {
|
||||
if (list[i] === callback) {
|
||||
list.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var key in event) {
|
||||
this.unbind(key, event[key]);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
addMapping: function (entityType, mapping) { // TODO: Should we support CTs here too? Or take steps to disallow?
|
||||
// TODO: Need doc comments.
|
||||
|
||||
if (typeof entityType === "string") {
|
||||
var mappingT = mapping;
|
||||
mapping = {};
|
||||
mapping[entityType] = mappingT;
|
||||
} else {
|
||||
mapping = entityType;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
$.each(mapping, function (entityType, mapping) {
|
||||
if ($.isFunction(mapping)) {
|
||||
mapping = { map: mapping };
|
||||
}
|
||||
|
||||
var existingMapping = self._mappings[entityType];
|
||||
if (!existingMapping) {
|
||||
var entitySet = self._entitySets[entityType];
|
||||
if (entitySet && entitySet.getEntities().length > 0) {
|
||||
throw "Supply a mapping for a type before loading data of that type";
|
||||
}
|
||||
self._mappings[entityType] = { map: mapping.map, unmap: mapping.unmap };
|
||||
} else if (existingMapping.map !== mapping.map || existingMapping.unmap !== mapping.unmap) {
|
||||
throw "For a given type, DataContext.addMapping must be supplied the same map/unmap functions.";
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
getEntitySet: function (entityType) {
|
||||
/// <summary>
|
||||
/// Returns the EntitySet for the supplied type.
|
||||
/// </summary>
|
||||
/// <param name="entityType" type="String"/>
|
||||
/// <returns type="upshot.EntitySet"/>
|
||||
|
||||
var entitySet = this._entitySets[entityType];
|
||||
if (!entitySet) {
|
||||
entitySet = this._entitySets[entityType] = new upshot.EntitySet(this, entityType);
|
||||
}
|
||||
return entitySet;
|
||||
},
|
||||
|
||||
getEntityErrors: function () {
|
||||
/// <summary>
|
||||
/// Returns an array of server errors by entity, of the form [ { entity: <entity>, error: <object> }, ... ].
|
||||
/// </summary>
|
||||
/// <returns type="Array"/>
|
||||
|
||||
var errors = [];
|
||||
$.each(this._entitySets, function (type, entitySet) {
|
||||
var spliceArguments = [errors.length, 0].concat(entitySet.getEntityErrors());
|
||||
[ ].splice.apply(errors, spliceArguments);
|
||||
});
|
||||
return errors;
|
||||
},
|
||||
|
||||
getEntityError: function (entity) {
|
||||
/// <summary>
|
||||
/// Returns server errors for the supplied entity.
|
||||
/// </summary>
|
||||
/// <param name="entity" type="Object">
|
||||
/// The entity for which server errors are to be returned.
|
||||
/// </param>
|
||||
/// <returns type="Object"/>
|
||||
|
||||
var error;
|
||||
// TODO: we should get related-entitySet for an entity, then getEntityError.
|
||||
// this will need type rationalization across all providers.
|
||||
$.each(this._entitySets, function (unused, entitySet) {
|
||||
error = entitySet.getEntityError(entity);
|
||||
return !error;
|
||||
});
|
||||
return error;
|
||||
},
|
||||
|
||||
commitChanges: function (options, success, error) {
|
||||
/// <summary>
|
||||
/// Initiates an asynchronous commit of any model data changes collected by this DataContext.
|
||||
/// </summary>
|
||||
/// <param name="success" type="Function" optional="true">
|
||||
/// A success callback.
|
||||
/// </param>
|
||||
/// <param name="error" type="Function" optional="true">
|
||||
/// An error callback with signature function(httpStatus, errorText, context).
|
||||
/// </param>
|
||||
/// <returns type="upshot.DataContext"/>
|
||||
|
||||
if (this._implicitCommitHandler) {
|
||||
throw "Data context must be in change-tracking mode to explicitly commit changes.";
|
||||
}
|
||||
this._commitChanges(options, success, error);
|
||||
return this;
|
||||
},
|
||||
|
||||
revertChanges: function () {
|
||||
/// <summary>
|
||||
/// Reverts any changes to model data (to entities) back to original entity values.
|
||||
/// </summary>
|
||||
/// <returns type="upshot.DataContext"/>
|
||||
|
||||
$.each(this._entitySets, function (type, entitySet) {
|
||||
entitySet.__revertChanges();
|
||||
});
|
||||
upshot.__triggerRecompute();
|
||||
return this;
|
||||
},
|
||||
|
||||
merge: function (entities, type, includedEntities) {
|
||||
/// <summary>Merges data into the cache</summary>
|
||||
/// <param name="entities" type="Array">The array of entities to add or merge into the cache</param>
|
||||
/// <param name="type" type="String">The type of the entities to be merge into the cache. This parameter can be null/undefined when no entities are supplied</param>
|
||||
/// <param name="includedEntities" type="Array">An additional array of entities (possibly related) to add or merge into the cache. These entities will not be returned from this function. This parameter is optional</param>
|
||||
/// <returns type="Array">The array of entities with newly merged values</returns>
|
||||
|
||||
var self = this;
|
||||
includedEntities = includedEntities || {};
|
||||
|
||||
$.each(entities, function (unused, entity) {
|
||||
self.__flatten(entity, type, includedEntities);
|
||||
});
|
||||
|
||||
$.each(includedEntities, function (type, entities) {
|
||||
var entitySet = self.getEntitySet(type);
|
||||
entitySet.__loadEntities(entities);
|
||||
});
|
||||
|
||||
var entitySet = type && this.getEntitySet(type),
|
||||
mergedEntities = entitySet ? entitySet.__loadEntities(entities) : [];
|
||||
|
||||
upshot.__triggerRecompute();
|
||||
|
||||
return mergedEntities;
|
||||
},
|
||||
|
||||
// TODO -- We have no mechanism to similarly clear data sources.
|
||||
//// clear: function () {
|
||||
//// $.each(this._entitySets, function (type, entitySet) {
|
||||
//// entitySet.__clear();
|
||||
//// });
|
||||
//// },
|
||||
|
||||
// Internal methods
|
||||
|
||||
// recursively visit the specified entity and its associations, accumulating all
|
||||
// associated entities to the included entities collection
|
||||
__flatten: function (entity, entityType, includedEntities) {
|
||||
var self = this;
|
||||
|
||||
$.each(upshot.metadata.getProperties(entity, entityType, true), function (index, prop) {
|
||||
var value = obs.getProperty(entity, prop.name);
|
||||
if (value) {
|
||||
if (prop.association) {
|
||||
var associatedEntities = upshot.isArray(value) ? value : [value],
|
||||
associatedEntityType = prop.type,
|
||||
entities = includedEntities[associatedEntityType] || (includedEntities[associatedEntityType] = []);
|
||||
|
||||
$.each(associatedEntities, function (inner_index, associatedEntity) {
|
||||
// add the associated entity
|
||||
var identity = upshot.EntitySet.__getIdentity(associatedEntity, associatedEntityType);
|
||||
|
||||
if (!entities.identityMap) {
|
||||
entities.identityMap = {};
|
||||
}
|
||||
if (!entities.identityMap[identity]) {
|
||||
// add the entity and recursively flatten it
|
||||
entities.identityMap[identity] = true;
|
||||
entities.push(associatedEntity);
|
||||
self.__flatten(associatedEntity, associatedEntityType, includedEntities);
|
||||
}
|
||||
///#DEBUG
|
||||
// TODO: For unmanaged associations, where is it that we should fix up internal reference
|
||||
// refer only to the atomized entity for a given identity?
|
||||
upshot.assert(self.__manageAssociations);
|
||||
///#ENDDEBUG
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
__load: function (options, success, error) {
|
||||
|
||||
var dataProvider = this._dataProvider,
|
||||
self = this,
|
||||
onSuccess = function (result) {
|
||||
if (self._isDisposed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add metadata if specified
|
||||
if (result.metadata) {
|
||||
upshot.metadata(result.metadata);
|
||||
}
|
||||
|
||||
// determine the result type
|
||||
var entityType = result.type || options.entityType;
|
||||
if (!entityType) {
|
||||
throw "Unable to determine entity type.";
|
||||
}
|
||||
|
||||
var entities = $.map(result.entities, function (entity) {
|
||||
return self._mapEntity(entity, entityType);
|
||||
});
|
||||
var includedEntities;
|
||||
if (result.includedEntities) {
|
||||
includedEntities = {};
|
||||
$.each(result.includedEntities, function (type, entities) {
|
||||
includedEntities[type] = $.map(entities, function (entity) {
|
||||
return self._mapEntity(entity, type);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var mergedEntities = self.merge(entities, entityType, includedEntities);
|
||||
|
||||
success.call(self, self.getEntitySet(entityType), mergedEntities, result.totalCount);
|
||||
},
|
||||
onError = function (httpStatus, errorText, context) {
|
||||
if (!self._isDisposed()) {
|
||||
error.call(self, httpStatus, errorText, context);
|
||||
}
|
||||
};
|
||||
|
||||
var getParameters = getProviderParameters("get", options.providerParameters);
|
||||
|
||||
dataProvider.get(getParameters, options.queryParameters, onSuccess, onError);
|
||||
},
|
||||
|
||||
__queueImplicitCommit: function () {
|
||||
if (this._implicitCommitHandler) {
|
||||
// when in implicit commit mode, we group all implicit commits within
|
||||
// a single thread of execution by queueing a timer callback that expires
|
||||
// immediately.
|
||||
if (!this._implicitCommitQueued) {
|
||||
this._implicitCommitQueued = true;
|
||||
|
||||
var self = this;
|
||||
setTimeout(function () {
|
||||
if (!self._isDisposed()) {
|
||||
self._implicitCommitQueued = false;
|
||||
self._implicitCommitHandler();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Private methods
|
||||
|
||||
_isDisposed: function () {
|
||||
return this._entitySets === null;
|
||||
},
|
||||
|
||||
_trigger: function (eventType) {
|
||||
var list = this._eventCallbacks[eventType];
|
||||
if (list) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
// clone the list to be robust against bind/unbind during callback
|
||||
list = list.slice(0);
|
||||
for (var i = 0, l = list.length; i < l; i++) {
|
||||
list[i].apply(this, args);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
_submitChanges: function (options, changedEntities, success, error) {
|
||||
|
||||
this._trigger("commitStart");
|
||||
|
||||
var changes = $.map(changedEntities, function (changedEntity) {
|
||||
return changedEntity.entitySet.__getEntityChange(changedEntity.entity);
|
||||
});
|
||||
|
||||
$.each(changes, function (index, change) { change.updateEntityState(); });
|
||||
|
||||
var self = this;
|
||||
var mapChangeResult = function (result, changeKind, entityType) {
|
||||
if (changeKind !== upshot.ChangeKind.Delete) { // Only add/update operations require mapped entity.
|
||||
return $.extend({}, result, {
|
||||
entity: self._mapEntity(result.entity, entityType)
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
var unmapChange = function (change) {
|
||||
if (change.changeKind !== upshot.ChangeKind.Delete) { // Delete operations don't require unmapping
|
||||
var unmap = (self._mappings[change.entityType] || {}).unmap || obs.unmap;
|
||||
change.entity = unmap(change.entity, change.entityType);
|
||||
}
|
||||
};
|
||||
$.each(changes, function (unused, change) {
|
||||
return unmapChange(change.changeSetEntry);
|
||||
});
|
||||
|
||||
var onSuccess = function (submitResult) {
|
||||
if (self._isDisposed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// all updates in the changeset where successful
|
||||
$.each(changes, function (index, change) {
|
||||
change.succeeded(mapChangeResult(submitResult[index], change.changeSetEntry.changeKind, change.changeSetEntry.entityType));
|
||||
});
|
||||
upshot.__triggerRecompute();
|
||||
self._trigger("commitSuccess", submitResult);
|
||||
if (success) {
|
||||
success.call(self, submitResult);
|
||||
}
|
||||
},
|
||||
onError = function (httpStatus, errorText, context, submitResult) {
|
||||
if (self._isDisposed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// one or more updates in the changeset failed
|
||||
$.each(changes, function (index, change) {
|
||||
if (submitResult) {
|
||||
// if a submitResult was provided, we use that data in the
|
||||
// completion of the change
|
||||
var changeResult = submitResult[index];
|
||||
if (changeResult.error) {
|
||||
change.failed(changeResult.error);
|
||||
} else {
|
||||
// even though there were failures in the changeset,
|
||||
// this particular change is marked as completed, so
|
||||
// we need to accept changes for it
|
||||
change.succeeded(mapChangeResult(change.changeSetEntry.entityType, changeResult));
|
||||
}
|
||||
} else {
|
||||
// if we don't have a submitResult, we still need to state
|
||||
// transition the change properly
|
||||
change.failed(null);
|
||||
}
|
||||
});
|
||||
|
||||
upshot.__triggerRecompute();
|
||||
self._trigger("commitError", httpStatus, errorText, context, submitResult);
|
||||
if (error) {
|
||||
error.call(self, httpStatus, errorText, context, submitResult);
|
||||
}
|
||||
};
|
||||
|
||||
var submitParameters = getProviderParameters("submit", options.providerParameters),
|
||||
changeSet = $.map(changes, function (change) {
|
||||
return change.changeSetEntry;
|
||||
});
|
||||
|
||||
this._dataProvider.submit(submitParameters, changeSet, onSuccess, onError);
|
||||
},
|
||||
|
||||
_commitChanges: function (options, success, error) {
|
||||
var changedEntities = [];
|
||||
$.each(this._entitySets, function (type, entitySet) {
|
||||
var entities = $.map(entitySet.__getChangedEntities(), function (entity) {
|
||||
return { entitySet: entitySet, entity: entity };
|
||||
});
|
||||
[ ].push.apply(changedEntities, entities);
|
||||
});
|
||||
|
||||
this._submitChanges(options, changedEntities, success, error);
|
||||
upshot.__triggerRecompute();
|
||||
},
|
||||
|
||||
_mapEntity: function (data, entityType) {
|
||||
return this._map(data, entityType, true);
|
||||
},
|
||||
|
||||
_map: function (data, entityType, isObject) {
|
||||
if (isObject || upshot.isObject(data)) {
|
||||
var map = (this._mappings[entityType] || {}).map;
|
||||
if (map) {
|
||||
// Don't pass "entityType"/"mapNested" as we do below for obs.map.
|
||||
// This would pollute the signature for app-supplied map functions (especially
|
||||
// when ctors are supplied).
|
||||
return new map(data); // Use "new" here to allow ctors to be passed as map functions.
|
||||
}
|
||||
}
|
||||
|
||||
// The "map" function provided by the observability layer takes a function
|
||||
// to map nested objects, so we take advantage of app-supplied mapping functions.
|
||||
var self = this,
|
||||
mapNested = function (data, entityType) {
|
||||
return self._map(data, entityType);
|
||||
};
|
||||
return obs.map(data, entityType, mapNested);
|
||||
}
|
||||
};
|
||||
|
||||
upshot.DataContext = upshot.defineClass(ctor, instanceMembers);
|
||||
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
|
@ -1,172 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
function pad(count, value) {
|
||||
var str = "0000" + value;
|
||||
return str.slice(str.length - count);
|
||||
}
|
||||
|
||||
function formatDateTime(date) {
|
||||
return "datetime" +
|
||||
"'" + pad(4, date.getUTCFullYear()) +
|
||||
"-" + pad(2, date.getUTCMonth() + 1) +
|
||||
"-" + pad(2, date.getUTCDate()) +
|
||||
"T" + pad(2, date.getUTCHours()) +
|
||||
":" + pad(2, date.getUTCMinutes()) +
|
||||
":" + pad(2, date.getUTCSeconds()) + "'";
|
||||
}
|
||||
|
||||
function getQueryResult(getResult) {
|
||||
var entities = getResult.results,
|
||||
resultType = entities.length && entities[0].__metadata.type;
|
||||
|
||||
var metadata;
|
||||
if (resultType) {
|
||||
metadata = {};
|
||||
metadata[resultType] = {
|
||||
key: ["__metadata.uri"]
|
||||
};
|
||||
}
|
||||
|
||||
var count = getResult.__count,
|
||||
totalCount = count === undefined ? null : +count;
|
||||
|
||||
return {
|
||||
type: resultType,
|
||||
metadata: metadata,
|
||||
entities: entities,
|
||||
totalCount: totalCount
|
||||
};
|
||||
}
|
||||
|
||||
var instanceMembers = {
|
||||
|
||||
// Public methods
|
||||
|
||||
get: function (parameters, queryParameters, success, error) {
|
||||
/// <summary>
|
||||
/// Asynchronously gets data from the server using the specified parameters
|
||||
/// </summary>
|
||||
/// <param name="parameters" type="String">The get parameters</param>
|
||||
/// <param name="queryParameters" type="Object">An object where each property is a query to pass to the operation. This parameter is optional.</param>
|
||||
/// <param name="success" type="Function">Optional success callback</param>
|
||||
/// <param name="error" type="Function">Optional error callback</param>
|
||||
/// <returns type="Promise">A Promise representing the result of the load operation</returns>
|
||||
|
||||
var operation, operationParameters;
|
||||
if (parameters) {
|
||||
operation = parameters.operationName;
|
||||
operationParameters = parameters.operationParameters;
|
||||
}
|
||||
|
||||
if ($.isFunction(operationParameters)) {
|
||||
success = operationParameters;
|
||||
error = queryParameters;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
// $.map applied to objects is supported in jQuery >= 1.6. Our current baseline is jQuery 1.5
|
||||
var parameterStrings = [];
|
||||
$.each($.extend({}, operationParameters, upshot.ODataDataProvider.getODataQueryParameters(queryParameters)), function (key, value) {
|
||||
parameterStrings.push(key.toString() + "=" + value.toString());
|
||||
});
|
||||
var queryString = parameterStrings.length ? ("?" + parameterStrings.join("&")) : "";
|
||||
|
||||
// Invoke the query
|
||||
OData.read(upshot.DataProvider.normalizeUrl(parameters.url) + operation + queryString,
|
||||
function (result) {
|
||||
if (success) {
|
||||
arguments[0] = getQueryResult(arguments[0]);
|
||||
success.apply(self, arguments);
|
||||
}
|
||||
},
|
||||
function (reason) {
|
||||
if (error) {
|
||||
error.call(self, -1, reason.message, reason);
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
throw "Saving edits through the OData data provider is not supported.";
|
||||
}
|
||||
};
|
||||
|
||||
var classMembers = {
|
||||
getODataQueryParameters: function (query) {
|
||||
query = query || {};
|
||||
var queryParameters = {};
|
||||
|
||||
// filters -> $filter
|
||||
if (query.filters && query.filters.length) {
|
||||
var filterParameter = "",
|
||||
applyOperator = function (property, operator, value) {
|
||||
if (typeof value === "string") {
|
||||
if (upshot.isGuid(value)) {
|
||||
value = "guid'" + value + "'";
|
||||
} else {
|
||||
value = "'" + value + "'";
|
||||
}
|
||||
} else if (upshot.isDate(value)) {
|
||||
value = formatDateTime(value);
|
||||
}
|
||||
|
||||
switch (operator) {
|
||||
case "<": return property + " lt " + value;
|
||||
case "<=": return property + " le " + value;
|
||||
case "==": return property + " eq " + value;
|
||||
case "!=": return property + " ne " + value;
|
||||
case ">=": return property + " ge " + value;
|
||||
case ">": return property + " gt " + value;
|
||||
case "StartsWith": return "startswith(" + property + "," + value + ") eq true";
|
||||
case "EndsWith": return "endswith(" + property + "," + value + ") eq true";
|
||||
case "Contains": return "substringof(" + value + "," + property + ") eq true";
|
||||
default: throw "The operator '" + operator + "' is not supported.";
|
||||
}
|
||||
};
|
||||
|
||||
$.each(query.filters, function (index, filter) {
|
||||
if (filterParameter) {
|
||||
filterParameter += " and ";
|
||||
}
|
||||
filterParameter += applyOperator(filter.property, filter.operator, filter.value);
|
||||
});
|
||||
|
||||
queryParameters.$filter = filterParameter;
|
||||
}
|
||||
|
||||
// sort -> $orderby
|
||||
if (query.sort && query.sort.length) {
|
||||
var formatSort = function (sort) {
|
||||
return !!sort.descending ? (sort.property + " desc") : sort.property;
|
||||
};
|
||||
queryParameters.$orderby = $.map(query.sort, function (sort, index) {
|
||||
return formatSort(sort);
|
||||
}).join();
|
||||
}
|
||||
|
||||
// skip -> $skip
|
||||
if (query.skip) {
|
||||
queryParameters.$skip = query.skip;
|
||||
}
|
||||
|
||||
// take -> $top
|
||||
if (query.take) {
|
||||
queryParameters.$top = query.take;
|
||||
}
|
||||
|
||||
// includeTotalCount -> $inlinecount
|
||||
if (query.includeTotalCount) {
|
||||
queryParameters.$inlinecount = "allpages";
|
||||
}
|
||||
|
||||
return queryParameters;
|
||||
}
|
||||
}
|
||||
|
||||
upshot.ODataDataProvider = upshot.defineClass(null, instanceMembers, classMembers);
|
||||
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
|
@ -1,200 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
function getQueryResult(getResult, wrappedResult) {
|
||||
var entities, totalCount;
|
||||
|
||||
if (wrappedResult) {
|
||||
entities = getResult.Results;
|
||||
totalCount = getResult.TotalCount;
|
||||
}
|
||||
else {
|
||||
entities = getResult;
|
||||
}
|
||||
|
||||
entities = upshot.isArray(entities) ? entities : [entities];
|
||||
|
||||
$.each(entities, function (unused, entity) {
|
||||
// This is not strictly model data.
|
||||
delete entity["$type"];
|
||||
});
|
||||
|
||||
return {
|
||||
entities: entities,
|
||||
totalCount: totalCount
|
||||
};
|
||||
}
|
||||
|
||||
var operations = (function () {
|
||||
var operations = {};
|
||||
operations[upshot.ChangeKind.Add] = 1;
|
||||
operations[upshot.ChangeKind.Update] = 2;
|
||||
operations[upshot.ChangeKind.Delete] = 3;
|
||||
return operations;
|
||||
})();
|
||||
|
||||
function transformChangeSet(changeSet) {
|
||||
return $.map(changeSet, function (change, index) {
|
||||
var changeSetEntry = {
|
||||
Id: index.toString(),
|
||||
Operation: operations[change.changeKind]
|
||||
};
|
||||
$.each({ entity: "Entity", originalEntity: "OriginalEntity" }, function (key, value) {
|
||||
if (change[key]) {
|
||||
changeSetEntry[value] = $.extend(true, {}, { "$type": change.entityType }, change[key]);
|
||||
}
|
||||
});
|
||||
return changeSetEntry;
|
||||
});
|
||||
}
|
||||
|
||||
function transformSubmitResult(result) {
|
||||
return $.map(result, function (changeSetEntry) {
|
||||
var result = {};
|
||||
|
||||
if (changeSetEntry.Entity) {
|
||||
result.entity = changeSetEntry.Entity;
|
||||
}
|
||||
|
||||
// transform to Error property
|
||||
// even though upshot currently doesn't support reporting of concurrency conflicts,
|
||||
// we must still identify such failures
|
||||
$.each(["ConflictMembers", "ValidationErrors", "IsDeleteConflict"], function (index, property) {
|
||||
if (changeSetEntry.hasOwnProperty(property)) {
|
||||
result.error = result.error || {};
|
||||
result.error[property] = changeSetEntry[property];
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
var instanceMembers = {
|
||||
|
||||
// Public methods
|
||||
|
||||
get: function (parameters, queryParameters, success, error) {
|
||||
/// <summary>
|
||||
/// Asynchronously gets data from the server using the specified parameters
|
||||
/// </summary>
|
||||
/// <param name="parameters" type="String">The get parameters</param>
|
||||
/// <param name="queryParameters" type="Object">An object where each property is a query to pass to the operation. This parameter is optional.</param>
|
||||
/// <param name="success" type="Function">Optional success callback</param>
|
||||
/// <param name="error" type="Function">Optional error callback</param>
|
||||
/// <returns type="Promise">A Promise representing the result of the load operation</returns>
|
||||
|
||||
var operation, operationParameters;
|
||||
if (parameters) {
|
||||
operation = parameters.operationName;
|
||||
operationParameters = parameters.operationParameters;
|
||||
}
|
||||
|
||||
if ($.isFunction(operationParameters)) {
|
||||
success = operationParameters;
|
||||
error = queryParameters;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
// set up the request parameters
|
||||
var url = upshot.DataProvider.normalizeUrl(parameters.url) + operation;
|
||||
var oDataQueryParams = upshot.ODataDataProvider.getODataQueryParameters(queryParameters);
|
||||
var data = $.extend({}, operationParameters, oDataQueryParams);
|
||||
var wrappedResult = oDataQueryParams.$inlinecount == "allpages";
|
||||
|
||||
// invoke the query
|
||||
$.ajax({
|
||||
url: url,
|
||||
data: data,
|
||||
success: success && function () {
|
||||
arguments[0] = getQueryResult(arguments[0], wrappedResult);
|
||||
success.apply(self, arguments);
|
||||
},
|
||||
error: error && function (jqXHR, statusText, errorText) {
|
||||
error.call(self, jqXHR.status, self._parseErrorText(jqXHR.responseText) || errorText, jqXHR);
|
||||
},
|
||||
dataType: "json"
|
||||
});
|
||||
},
|
||||
|
||||
submit: function (parameters, changeSet, success, error) {
|
||||
/// <summary>
|
||||
/// Asynchronously submits the specified changeset
|
||||
/// </summary>
|
||||
/// <param name="parameters" type="String">The submit parameters</param>
|
||||
/// <param name="changeSet" type="Object">The changeset to submit</param>
|
||||
/// <param name="success" type="Function">Optional success callback</param>
|
||||
/// <param name="error" type="Function">Optional error callback</param>
|
||||
/// <returns type="Promise">A Promise representing the result of the post operation</returns>
|
||||
|
||||
var self = this,
|
||||
encodedChangeSet = JSON.stringify(transformChangeSet(changeSet));
|
||||
|
||||
$.ajax({
|
||||
url: upshot.DataProvider.normalizeUrl(parameters.url) + "Submit",
|
||||
contentType: "application/json",
|
||||
data: encodedChangeSet,
|
||||
dataType: "json",
|
||||
type: "POST",
|
||||
success: (success || error) && function (submitChangesResult, statusText, jqXHR) {
|
||||
var result = submitChangesResult ? transformSubmitResult(submitChangesResult) : [];
|
||||
var hasErrors = $.grep(result, function (subresult) {
|
||||
return subresult.hasOwnProperty("error");
|
||||
}).length > 0;
|
||||
|
||||
if (!hasErrors) {
|
||||
if (success) {
|
||||
success.call(self, result);
|
||||
}
|
||||
} else if (error) {
|
||||
var errorText = "Submit failed.";
|
||||
if (submitChangesResult) {
|
||||
for (var i = 0; i < submitChangesResult.length; ++i) {
|
||||
// TODO: Why does this only treat ValidationErrors? What about ConflictMembers and IsDeleteConflict?
|
||||
var validationError = (submitChangesResult[i].ValidationErrors && submitChangesResult[i].ValidationErrors[0] && submitChangesResult[i].ValidationErrors[0].Message);
|
||||
if (validationError) {
|
||||
errorText = validationError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
error.call(self, jqXHR.status, errorText, jqXHR, result);
|
||||
}
|
||||
},
|
||||
error: error && function (jqXHR, statusText, errorText) {
|
||||
error.call(self, jqXHR.status, self._parseErrorText(jqXHR.responseText) || errorText, jqXHR);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_parseErrorText: function (responseText) {
|
||||
var match = /Exception]: (.+)\r/g.exec(responseText);
|
||||
if (match && match[1]) {
|
||||
return match[1];
|
||||
}
|
||||
if (/^{.*}$/g.test(responseText)) {
|
||||
var error = JSON.parse(responseText);
|
||||
// TODO: error.Message returned by DataController
|
||||
// Does ErrorMessage check still necessary?
|
||||
if (error.ErrorMessage) {
|
||||
return error.ErrorMessage;
|
||||
} else if (error.Message) {
|
||||
return error.Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var classMembers = {
|
||||
normalizeUrl: function (url) {
|
||||
if (url && url.substring(url.length - 1) !== "/") {
|
||||
return url + "/";
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
upshot.DataProvider = upshot.defineClass(null, instanceMembers, classMembers);
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
|
@ -1,291 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
function transformQuery(query) {
|
||||
var queryParameters = {};
|
||||
|
||||
// filters -> $where
|
||||
if (query.filters && query.filters.length) {
|
||||
var whereParameter = "",
|
||||
applyOperator = function (property, operator, value) {
|
||||
if (typeof value === "string") {
|
||||
if (upshot.isGuid(value)) {
|
||||
value = "Guid(" + value + ")";
|
||||
} else {
|
||||
value = '"' + value + '"';
|
||||
}
|
||||
} else if (upshot.isDate(value)) {
|
||||
// DomainService expects ticks; js Date.getTime() gives ms since epoch
|
||||
value = "DateTime(" + (value.getTime() * 10000 + 621355968000000000) + ")";
|
||||
}
|
||||
|
||||
switch (operator) {
|
||||
case "<":
|
||||
case "<=":
|
||||
case "==":
|
||||
case "!=":
|
||||
case ">=":
|
||||
case ">": return property + operator + value;
|
||||
case "StartsWith":
|
||||
case "EndsWith":
|
||||
case "Contains": return property + "." + operator + "(" + value + ")";
|
||||
default: throw "The operator '" + operator + "' is not supported.";
|
||||
}
|
||||
};
|
||||
|
||||
$.each(query.filters, function (index, filter) {
|
||||
if (whereParameter) {
|
||||
whereParameter += " AND ";
|
||||
}
|
||||
whereParameter += applyOperator(filter.property, filter.operator, filter.value);
|
||||
});
|
||||
|
||||
queryParameters.$where = whereParameter;
|
||||
}
|
||||
|
||||
// sort -> $orderby
|
||||
if (query.sort && query.sort.length) {
|
||||
var formatSort = function (sort) {
|
||||
return !!sort.descending ? (sort.property + " desc") : sort.property;
|
||||
};
|
||||
queryParameters.$orderby = $.map(query.sort, function (sort, index) {
|
||||
return formatSort(sort);
|
||||
}).join();
|
||||
}
|
||||
|
||||
// skip -> $skip
|
||||
if (query.skip) {
|
||||
queryParameters.$skip = query.skip;
|
||||
}
|
||||
|
||||
// take -> $take
|
||||
if (query.take) {
|
||||
queryParameters.$take = query.take;
|
||||
}
|
||||
|
||||
// includeTotalCount -> $includeTotalCount
|
||||
if (query.includeTotalCount) {
|
||||
queryParameters.$includeTotalCount = query.includeTotalCount;
|
||||
}
|
||||
|
||||
return queryParameters;
|
||||
}
|
||||
|
||||
function transformParameters(parameters) {
|
||||
// perform any required transformations on the specified parameters
|
||||
// before invoking the service, for example json serializing arrays
|
||||
// and other complex parameters.
|
||||
if (parameters) {
|
||||
$.each(parameters || {}, function (key, value) {
|
||||
if ($.isArray(value)) {
|
||||
// json serialize arrays since this is the format the json
|
||||
// endpoint expects.
|
||||
parameters[key] = JSON.stringify(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
function getQueryResult(getResult) {
|
||||
var resultKey;
|
||||
$.each(getResult, function (key) {
|
||||
if (/Result$/.test(key)) {
|
||||
resultKey = key;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
var result = getResult[resultKey];
|
||||
|
||||
// process the metadata
|
||||
var metadata = {};
|
||||
$.each(result.Metadata, function (unused, metadataForType) {
|
||||
metadata[metadataForType.type] = {
|
||||
key: metadataForType.key,
|
||||
fields: metadataForType.fields,
|
||||
rules: metadataForType.rules,
|
||||
messages: metadataForType.messages
|
||||
};
|
||||
});
|
||||
|
||||
$.each(result.RootResults, function (unused, entity) {
|
||||
// This is not strictly model data.
|
||||
delete entity.__type;
|
||||
});
|
||||
|
||||
var includedEntities;
|
||||
if (result.IncludedResults) {
|
||||
// group included entities by type
|
||||
includedEntities = {};
|
||||
$.each(result.IncludedResults, function (unused, entity) {
|
||||
var entityType = entity.__type;
|
||||
var entities = includedEntities[entityType] || (includedEntities[entityType] = []);
|
||||
entities.push(entity);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
type: result.Metadata[0].type,
|
||||
metadata: metadata,
|
||||
entities: result.RootResults,
|
||||
includedEntities: includedEntities,
|
||||
totalCount: result.TotalCount || 0
|
||||
};
|
||||
}
|
||||
|
||||
var operations = (function () {
|
||||
var operations = {};
|
||||
operations[upshot.ChangeKind.Add] = 2;
|
||||
operations[upshot.ChangeKind.Update] = 3;
|
||||
operations[upshot.ChangeKind.Delete] = 4;
|
||||
return operations;
|
||||
})();
|
||||
|
||||
function transformChangeSet(changeSet) {
|
||||
return $.map(changeSet, function (change, index) {
|
||||
var changeSetEntry = {
|
||||
Id: index.toString(),
|
||||
Operation: operations[change.changeKind]
|
||||
};
|
||||
$.each({ entity: "Entity", originalEntity: "OriginalEntity" }, function (key, value) {
|
||||
if (change[key]) {
|
||||
changeSetEntry[value] = $.extend(true, {}, { __type: change.entityType }, change[key]);
|
||||
}
|
||||
});
|
||||
return changeSetEntry;
|
||||
});
|
||||
}
|
||||
|
||||
function transformSubmitResult(result) {
|
||||
return $.map(result, function (changeSetEntry) {
|
||||
var result = {};
|
||||
|
||||
if (changeSetEntry.Entity) {
|
||||
result.entity = changeSetEntry.Entity;
|
||||
}
|
||||
|
||||
// transform to Error property
|
||||
// even though upshot currently doesn't support reporting of concurrency conflicts,
|
||||
// we must still identify such failures
|
||||
$.each(["ConflictMembers", "ValidationErrors", "IsDeleteConflict"], function (index, property) {
|
||||
if (changeSetEntry.hasOwnProperty(property)) {
|
||||
result.error = result.error || {};
|
||||
result.error[property] = changeSetEntry[property];
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
var instanceMembers = {
|
||||
|
||||
// Public methods
|
||||
|
||||
get: function (parameters, queryParameters, success, error) {
|
||||
/// <summary>
|
||||
/// Asynchronously gets data from the server using the specified parameters
|
||||
/// </summary>
|
||||
/// <param name="parameters" type="String">The get parameters</param>
|
||||
/// <param name="queryParameters" type="Object">An object where each property is a query to pass to the operation. This parameter is optional.</param>
|
||||
/// <param name="success" type="Function">Optional success callback</param>
|
||||
/// <param name="error" type="Function">Optional error callback</param>
|
||||
/// <returns type="Promise">A Promise representing the result of the load operation</returns>
|
||||
|
||||
var operation, operationParameters;
|
||||
if (parameters) {
|
||||
operation = parameters.operationName;
|
||||
operationParameters = parameters.operationParameters;
|
||||
}
|
||||
|
||||
if ($.isFunction(operationParameters)) {
|
||||
success = operationParameters;
|
||||
error = queryParameters;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
// Invoke the query
|
||||
$.ajax({
|
||||
url: upshot.DataProvider.normalizeUrl(parameters.url) + "json/" + operation,
|
||||
data: $.extend({}, transformParameters(operationParameters), transformQuery(queryParameters || {})),
|
||||
success: success && function () {
|
||||
arguments[0] = getQueryResult(arguments[0]);
|
||||
success.apply(self, arguments);
|
||||
},
|
||||
error: error && function (jqXHR, statusText, errorText) {
|
||||
error.call(self, jqXHR.status, self._parseErrorText(jqXHR.responseText) || errorText, jqXHR);
|
||||
},
|
||||
dataType: "json"
|
||||
});
|
||||
},
|
||||
|
||||
submit: function (parameters, changeSet, success, error) {
|
||||
/// <summary>
|
||||
/// Asynchronously submits the specified changeset
|
||||
/// </summary>
|
||||
/// <param name="parameters" type="String">The submit parameters</param>
|
||||
/// <param name="changeSet" type="Object">The changeset to submit</param>
|
||||
/// <param name="success" type="Function">Optional success callback</param>
|
||||
/// <param name="error" type="Function">Optional error callback</param>
|
||||
/// <returns type="Promise">A Promise representing the result of the post operation</returns>
|
||||
|
||||
var self = this,
|
||||
encodedChangeSet = JSON.stringify({ changeSet: transformChangeSet(changeSet) });
|
||||
|
||||
$.ajax({
|
||||
url: upshot.DataProvider.normalizeUrl(parameters.url) + "json/SubmitChanges",
|
||||
contentType: "application/json",
|
||||
data: encodedChangeSet,
|
||||
dataType: "json",
|
||||
type: "POST",
|
||||
success: (success || error) && function (data, statusText, jqXHR) {
|
||||
var submitChangesResult = data.SubmitChangesResult,
|
||||
result = submitChangesResult ? transformSubmitResult(submitChangesResult) : [],
|
||||
hasErrors = $.grep(result, function (subresult) {
|
||||
return subresult.hasOwnProperty("error");
|
||||
}).length > 0;
|
||||
|
||||
if (!hasErrors) {
|
||||
if (success) {
|
||||
success.call(self, result);
|
||||
}
|
||||
} else if (error) {
|
||||
var errorText = "Submit failed.";
|
||||
if (submitChangesResult) {
|
||||
for (var i = 0; i < submitChangesResult.length; ++i) {
|
||||
// TODO: Why does this only treat ValidationErrors? What about ConflictMembers and IsDeleteConflict?
|
||||
var validationError = (submitChangesResult[i].ValidationErrors && submitChangesResult[i].ValidationErrors[0] && submitChangesResult[i].ValidationErrors[0].Message);
|
||||
if (validationError) {
|
||||
errorText = validationError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
error.call(self, jqXHR.status, errorText, jqXHR, result);
|
||||
}
|
||||
},
|
||||
error: error && function (jqXHR, statusText, errorText) {
|
||||
error.call(self, jqXHR.status, self._parseErrorText(jqXHR.responseText) || errorText, jqXHR);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_parseErrorText: function (responseText) {
|
||||
var match = /Exception]: (.+)\r/g.exec(responseText);
|
||||
if (match && match[1]) {
|
||||
return match[1];
|
||||
}
|
||||
if (/^{.*}$/g.test(responseText)) {
|
||||
var error = JSON.parse(responseText);
|
||||
if (error.ErrorMessage) {
|
||||
return error.ErrorMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
upshot.riaDataProvider = upshot.defineClass(null, instanceMembers);
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
|
@ -1,168 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
var base = upshot.EntityView.prototype;
|
||||
|
||||
var obs = upshot.observability;
|
||||
var queryOptions = { paging: "setPaging", sort: "setSort", filter: "setFilter" };
|
||||
|
||||
var ctor = function (options) {
|
||||
|
||||
if (options && options.result && options.result.length !== 0) {
|
||||
throw "NYI -- Currently, \"result\" array must be empty to bind to a data source.";
|
||||
}
|
||||
|
||||
this._skip = null;
|
||||
this._take = null;
|
||||
this._includeTotalCount = false;
|
||||
this._lastRefreshTotalEntityCount = 0;
|
||||
this._allowRefreshWithEdits = options && !!options.allowRefreshWithEdits;
|
||||
|
||||
if (options) {
|
||||
var self = this;
|
||||
$.each(options, function (key, value) {
|
||||
if (queryOptions[key]) {
|
||||
self[queryOptions[key]](value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
base.constructor.apply(this, arguments);
|
||||
|
||||
// Events specific to DataSource
|
||||
this._bindFromOptions(options, [ "refreshStart", "refreshSuccess", "refreshError" ]);
|
||||
};
|
||||
|
||||
var instanceMembers = {
|
||||
|
||||
// Public methods
|
||||
|
||||
// TODO -- These query set-* methods should be consolidated, passing a "settings" parameter.
|
||||
// That way, we can issue a single "needs refresh" event when the client updates the query settings.
|
||||
// TODO -- Changing query options should trigger "results stale".
|
||||
|
||||
setSort: function (sort) {
|
||||
throw "Unreachable"; // Abstract/pure virtual method.
|
||||
},
|
||||
|
||||
setFilter: function (filter) {
|
||||
throw "Unreachable"; // Abstract/pure virtual method.
|
||||
},
|
||||
|
||||
setPaging: function (paging) {
|
||||
/// <summary>
|
||||
/// Establishes the paging specification that is to be applied when loading model data.
|
||||
/// </summary>
|
||||
/// <param name="paging">
|
||||
/// The paging specification to be applied when loading model data.
|
||||
/// Should be supplied as an object of the form { skip: <number>, take: <number>, includeTotalCount: <bool> }. All properties on this object are optional.
|
||||
/// When supplied as null or undefined, the paging specification for this DataSource is cleared.
|
||||
/// </param>
|
||||
/// <returns type="upshot.DataSource"/>
|
||||
|
||||
paging = paging || {};
|
||||
this._skip = paging.skip;
|
||||
this._take = paging.take;
|
||||
this._includeTotalCount = !!paging.includeTotalCount;
|
||||
return this;
|
||||
},
|
||||
|
||||
getTotalEntityCount: function () {
|
||||
/// <summary>
|
||||
/// Returns the total entity count from the last refresh operation on this DataSource. This count will differ from DataSource.getEntities().length when a filter or paging specification is applied to this DataSource.
|
||||
/// </summary>
|
||||
/// <returns type="Number"/>
|
||||
|
||||
// NOTE: We had been updating this to reflect internal, client-only adds, but this doesn't
|
||||
// generalize nicely. For instance, a RemoteDataSource might have server-only logic that
|
||||
// determines whether an added entity should be included in a filtered query result.
|
||||
// TODO: Revisit this conclusion.
|
||||
return this._lastRefreshTotalEntityCount;
|
||||
},
|
||||
|
||||
refresh: function (options) {
|
||||
throw "Unreachable"; // Abstract/pure virtual method.
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
/// <summary>
|
||||
/// Empties the result array for this DataSource (that is, the array returned by DataSource.getEntities()). The result array can be repopulated using DataSource.refresh().
|
||||
/// </summary>
|
||||
/// <returns type="Number"/>
|
||||
|
||||
this._applyNewQueryResult([]);
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
// Private methods
|
||||
|
||||
// acceptable filter parameter
|
||||
// { property: "Id", operator: "==", value: 1 } // default operator is "=="
|
||||
// and an array of such
|
||||
_normalizeFilters: function (filter) {
|
||||
filter = upshot.isArray(filter) ? filter : [filter];
|
||||
var filters = [];
|
||||
for (var i = 0; i < filter.length; i++) {
|
||||
var filterPart = filter[i];
|
||||
if (filterPart) {
|
||||
if (!$.isFunction(filterPart)) {
|
||||
filterPart.operator = filterPart.operator || "==";
|
||||
}
|
||||
filters.push(filterPart);
|
||||
}
|
||||
}
|
||||
return filters;
|
||||
},
|
||||
|
||||
_verifyOkToRefresh: function () {
|
||||
if (!this._allowRefreshWithEdits) {
|
||||
var self = this;
|
||||
$.each(obs.asArray(this._clientEntities), function (unused, entity) {
|
||||
if (self.getEntityState(entity) !== upshot.EntityState.Unmodified) {
|
||||
throw "Refreshing this DataSource will potentially remove unsaved entities. Such entities might encounter errors during save, and your app should have UI to view such errors. Either disallow DataSource.refresh() with edits or build error UI and suppress this exception with the 'allowRefreshWithEdits' DataSource option.";
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_completeRefresh: function (entities, totalCount, success) {
|
||||
if (this._applyNewQueryResult(entities, totalCount)) {
|
||||
upshot.__triggerRecompute();
|
||||
}
|
||||
|
||||
var newClientEntities = obs.asArray(this._clientEntities),
|
||||
newTotalCount = this._lastRefreshTotalEntityCount;
|
||||
this._trigger("refreshSuccess", newClientEntities, newTotalCount);
|
||||
if ($.isFunction(success)) {
|
||||
success.call(this, newClientEntities, newTotalCount);
|
||||
}
|
||||
},
|
||||
|
||||
_failRefresh: function (httpStatus, errorText, context, fail) {
|
||||
this._trigger("refreshError", httpStatus, errorText, context);
|
||||
if ($.isFunction(fail)) {
|
||||
fail.call(this, httpStatus, errorText, context);
|
||||
}
|
||||
},
|
||||
|
||||
_applyNewQueryResult: function (entities, totalCount) {
|
||||
this._lastRefreshTotalEntityCount = totalCount;
|
||||
|
||||
var sameEntities = upshot.sameArrayContents(obs.asArray(this._clientEntities), entities);
|
||||
if (!sameEntities) {
|
||||
// Update our client entities.
|
||||
var oldEntities = obs.asArray(this._clientEntities).slice();
|
||||
obs.refresh(this._clientEntities, entities);
|
||||
this._trigger("arrayChanged", "replaceAll", { oldItems: oldEntities, newItems: obs.asArray(this._clientEntities) });
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
upshot.DataSource = upshot.deriveClass(base, ctor, instanceMembers);
|
||||
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,236 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
var obs = upshot.observability;
|
||||
|
||||
var ctor = function (options) {
|
||||
var result = options && options.result;
|
||||
if (result) {
|
||||
if (upshot.EntitySource.as(result)) {
|
||||
throw "Array is already bound to an EntitySource.";
|
||||
}
|
||||
}
|
||||
|
||||
this._viewsToRecompute = [];
|
||||
this._eventCallbacks = {};
|
||||
this._clientEntities = result || obs.createCollection();
|
||||
|
||||
// Events shared by subclasses
|
||||
this._bindFromOptions(options, [ "arrayChanged", "propertyChanged", "entityStateChanged" ]);
|
||||
|
||||
var self = this;
|
||||
obs.track(this._clientEntities, {
|
||||
afterChange: function (array, type, eventArguments) {
|
||||
upshot.__beginChange();
|
||||
self._handleArrayChange(type, eventArguments);
|
||||
},
|
||||
afterEvent: function () {
|
||||
upshot.__endChange();
|
||||
}
|
||||
});
|
||||
|
||||
upshot.cache(this._clientEntities, "entitySource", this);
|
||||
};
|
||||
|
||||
var instanceMembers = {
|
||||
|
||||
// Public methods
|
||||
|
||||
dispose: function () {
|
||||
/// <summary>
|
||||
/// Disposes the EntitySource instance.
|
||||
/// </summary>
|
||||
|
||||
if (this._eventCallbacks) { // Use _eventCallbacks as an indicator as to whether we've been disposed.
|
||||
obs.track(this._clientEntities, null);
|
||||
upshot.deleteCache(this._clientEntities, "entitySource");
|
||||
this._dispose(); // Give subclass code an opportunity to clean up.
|
||||
this._eventCallbacks = null;
|
||||
}
|
||||
},
|
||||
|
||||
// TODO: bind/unbind/_trigger are duplicated in EntitySource and DataContext, consider common routine.
|
||||
bind: function (event, callback) {
|
||||
/// <summary>
|
||||
/// Registers the supplied callback to be called when an event is raised.
|
||||
/// </summary>
|
||||
/// <param name="event" type="String">
|
||||
/// The event name.
|
||||
/// </param>
|
||||
/// <param name="callback" type="Function">
|
||||
/// The callback function.
|
||||
/// </param>
|
||||
/// <returns type="upshot.EntitySource"/>
|
||||
|
||||
if (typeof event === "string") {
|
||||
var list = this._eventCallbacks[event] || (this._eventCallbacks[event] = []);
|
||||
list.push(callback);
|
||||
} else {
|
||||
for (var key in event) {
|
||||
this.bind(key, event[key]);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
unbind: function (event, callback) {
|
||||
/// <summary>
|
||||
/// Deregisters the supplied callback for the supplied event.
|
||||
/// </summary>
|
||||
/// <param name="event" type="String">
|
||||
/// The event name.
|
||||
/// </param>
|
||||
/// <param name="callback" type="Function">
|
||||
/// The callback function to be deregistered.
|
||||
/// </param>
|
||||
/// <returns type="upshot.EntitySource"/>
|
||||
|
||||
if (typeof event === "string") {
|
||||
var list = this._eventCallbacks && this._eventCallbacks[event];
|
||||
if (list) {
|
||||
for (var i = 0, l = list.length; i < l; i++) {
|
||||
if (list[i] === callback) {
|
||||
list.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var key in event) {
|
||||
this.unbind(key, event[key]);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
getEntities: function () {
|
||||
/// <summary>
|
||||
/// Returns the stable, observable array of model data.
|
||||
/// </summary>
|
||||
/// <returns type="Array"/>
|
||||
|
||||
return this._clientEntities;
|
||||
},
|
||||
|
||||
|
||||
// Internal methods
|
||||
|
||||
__registerForRecompute: function (entityView) {
|
||||
if ($.inArray(entityView, this._viewsToRecompute) <= 0) {
|
||||
this._viewsToRecompute.push(entityView);
|
||||
}
|
||||
},
|
||||
|
||||
__recomputeDependentViews: function () {
|
||||
while (this._viewsToRecompute.length > 0) { // Downstream entity views might be dirtied due to recompute.
|
||||
var viewsToRecompute = this._viewsToRecompute.slice();
|
||||
this._viewsToRecompute.splice(0, this._viewsToRecompute.length);
|
||||
$.each(viewsToRecompute, function (index, entityView) {
|
||||
entityView.__recompute();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Used to translate entity inserts through EntityViews (and onto their input EntitySource).
|
||||
__addEntity: function (entity) {
|
||||
var index = obs.asArray(this._clientEntities).length;
|
||||
obs.insert(this._clientEntities, index, [entity]);
|
||||
|
||||
this._handleArrayChange("insert", { index: index, items: [entity] });
|
||||
},
|
||||
|
||||
// Used to translate entity removes through EntityViews (and onto their input EntitySource).
|
||||
__deleteEntity: function (entity, index) {
|
||||
var index = $.inArray(entity, obs.asArray(this._clientEntities));
|
||||
///#DEBUG
|
||||
upshot.assert(index >= 0, "entity must exist!");
|
||||
///#ENDDEBUG
|
||||
obs.remove(this._clientEntities, index, 1);
|
||||
|
||||
this._handleArrayChange("remove", { index: index, items: [entity] });
|
||||
},
|
||||
|
||||
// Private methods
|
||||
|
||||
_bindFromOptions: function (options, events) {
|
||||
if (options) {
|
||||
var self = this;
|
||||
$.each(events, function (unused, event) {
|
||||
var callback = options && options[event];
|
||||
if (callback) {
|
||||
self.bind(event, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_dispose: function () {
|
||||
// Will be overridden by derived classes.
|
||||
},
|
||||
|
||||
_isDisposed: function () {
|
||||
return this._eventCallbacks === null;
|
||||
},
|
||||
|
||||
_handleArrayChange: function (type, eventArguments) {
|
||||
switch (type) {
|
||||
case "insert":
|
||||
var entitiesToAdd = eventArguments.items;
|
||||
if (entitiesToAdd.length > 1) {
|
||||
throw "NYI -- Can only add a single entity to/from an array in one operation.";
|
||||
}
|
||||
|
||||
var entityToAdd = entitiesToAdd[0];
|
||||
this._handleEntityAdd(entityToAdd);
|
||||
break;
|
||||
|
||||
case "remove":
|
||||
throw "Use 'deleteEntity' to delete entities from your array. Destructive delete is not yet implemented.";
|
||||
|
||||
case "replaceAll":
|
||||
if (!upshot.sameArrayContents(eventArguments.newItems, obs.asArray(this._clientEntities))) {
|
||||
throw "NYI -- Can only replaceAll with own entities.";
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw "NYI -- Array operation '" + type + "' is not supported.";
|
||||
}
|
||||
|
||||
this._trigger("arrayChanged", type, eventArguments);
|
||||
},
|
||||
|
||||
_handleEntityAdd: function (entity) {
|
||||
// Will be overridden by derived classes to do specific handling for an entity add.
|
||||
},
|
||||
|
||||
_purgeEntity: function (entity) {
|
||||
// TODO -- Should we try to handle duplicates here?
|
||||
var index = $.inArray(entity, obs.asArray(this._clientEntities));
|
||||
obs.remove(this._clientEntities, index, 1);
|
||||
this._trigger("arrayChanged", "remove", { index: index, items: [entity] });
|
||||
},
|
||||
|
||||
_trigger: function (eventType) {
|
||||
var list = this._eventCallbacks[eventType];
|
||||
if (list) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
// clone the list to be robust against bind/unbind during callback
|
||||
list = list.slice(0);
|
||||
for (var i = 0, l = list.length; i < l; i++) {
|
||||
list[i].apply(this, args);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
var classMembers = {
|
||||
as: function (array) {
|
||||
return upshot.cache(array, "entitySource");
|
||||
}
|
||||
};
|
||||
|
||||
upshot.EntitySource = upshot.defineClass(ctor, instanceMembers, classMembers);
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
|
@ -1,327 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
var base = upshot.EntitySource.prototype;
|
||||
|
||||
var obs = upshot.observability;
|
||||
|
||||
var ctor = function (options) {
|
||||
|
||||
this._needRecompute = false;
|
||||
|
||||
var self = this;
|
||||
this._observer = {
|
||||
propertyChanged: function (entity, property, newValue) { self._onPropertyChanged(entity, property, newValue); },
|
||||
arrayChanged: function (type, eventArgs) { self._onArrayChanged(type, eventArgs); },
|
||||
entityStateChanged: function (entity, state, error) { self._onEntityStateChanged(entity, state, error); },
|
||||
entityUpdated: function (entity, path, eventArgs) { self._onEntityUpdated(entity, path, eventArgs); }
|
||||
};
|
||||
|
||||
// RemoteDataSource may dynamically bind to its EntitySet as it refreshes.
|
||||
this._entitySource = null; // Make JS runtime type inference happy?
|
||||
|
||||
var entitySource = options && options.source;
|
||||
if (entitySource) {
|
||||
this._bindToEntitySource(entitySource);
|
||||
}
|
||||
|
||||
base.constructor.call(this, options);
|
||||
};
|
||||
|
||||
var dataContextMethodNames = [
|
||||
"getDataContext",
|
||||
"getEntityState",
|
||||
"getEntityValidationRules",
|
||||
"getEntityId",
|
||||
"revertChanges",
|
||||
"deleteEntity",
|
||||
"getEntityErrors",
|
||||
"getEntityError",
|
||||
"isUpdated",
|
||||
"revertUpdates"
|
||||
];
|
||||
|
||||
var instanceMembers = {
|
||||
|
||||
///#DEBUG
|
||||
getDataContext: function () {
|
||||
/// <summary>
|
||||
/// Returns the DataContext used as a cache for model data.
|
||||
/// </summary>
|
||||
/// <returns type="upshot.DataContext"/>
|
||||
|
||||
throw "Not reached"; // For Intellisense only.
|
||||
},
|
||||
|
||||
getEntityState: function (entity) {
|
||||
/// <summary>
|
||||
/// Returns the EntityState for the supplied entity.
|
||||
/// </summary>
|
||||
/// <param name="entity" type="Object">
|
||||
/// The entity for which EntityState will be returned.
|
||||
/// </param>
|
||||
/// <returns type="upshot.EntityState"/>
|
||||
|
||||
throw "Not reached"; // For Intellisense only.
|
||||
},
|
||||
|
||||
getEntityValidationRules: function () {
|
||||
/// <summary>
|
||||
/// Returns entity validation rules for the type of entity returned by this EntityView.
|
||||
/// </summary>
|
||||
/// <returns type="Object"/>
|
||||
|
||||
throw "Not reached"; // For Intellisense only.
|
||||
},
|
||||
|
||||
getEntityId: function (entity) {
|
||||
/// <summary>
|
||||
/// Returns an identifier for the supplied entity.
|
||||
/// </summary>
|
||||
/// <param name="entity" type="Object"/>
|
||||
/// <returns type="String"/>
|
||||
|
||||
throw "Not reached"; // For Intellisense only.
|
||||
},
|
||||
|
||||
revertChanges: function (all) {
|
||||
/// <summary>
|
||||
/// Reverts any edits to model data (to entities) back to original entity values.
|
||||
/// </summary>
|
||||
/// <param name="all" type="Boolean" optional="true">
|
||||
/// Revert all model edits in the underlying DataContext (to all types of entities). Otherwise, revert changes only to those entities of the type loaded by this EntityView.
|
||||
/// </param>
|
||||
/// <returns type="upshot.EntityView"/>
|
||||
|
||||
throw "Not reached"; // For Intellisense only.
|
||||
},
|
||||
|
||||
deleteEntity: function (entity) {
|
||||
/// <summary>
|
||||
/// Marks the supplied entity for deletion. This is a non-destructive operation, meaning that the entity will remain in the EntityView.getEntities() array until the server commits the delete.
|
||||
/// </summary>
|
||||
/// <param name="entity" type="Object">
|
||||
/// The entity to be marked for deletion.
|
||||
/// </param>
|
||||
/// <returns type="upshot.EntityView"/>
|
||||
|
||||
throw "Not reached"; // For Intellisense only.
|
||||
},
|
||||
|
||||
getEntityErrors: function () {
|
||||
/// <summary>
|
||||
/// Returns an array of server errors by entity, of the form [ { entity: <entity>, error: <object> }, ... ].
|
||||
/// </summary>
|
||||
/// <returns type="Array"/>
|
||||
|
||||
throw "Not reached"; // For Intellisense only.
|
||||
},
|
||||
|
||||
getEntityError: function (entity) {
|
||||
/// <summary>
|
||||
/// Returns server errors for the supplied entity.
|
||||
/// </summary>
|
||||
/// <param name="entity" type="Object">
|
||||
/// The entity for which server errors are to be returned.
|
||||
/// </param>
|
||||
/// <returns type="Object"/>
|
||||
|
||||
throw "Not reached"; // For Intellisense only.
|
||||
},
|
||||
|
||||
isUpdated: function (entity, path, ignoreChildren) {
|
||||
/// <summary>
|
||||
/// Returns whether the entity of any of the objects or arrays it contains are updated. When a path is specified,
|
||||
/// it returns whether the specified property of any of its children are updated. This function will never return
|
||||
/// 'true' for entities not in the 'ClientUpdated' state.
|
||||
/// </summary>
|
||||
/// <param name="entity" type="Object">
|
||||
/// The entity to check for updates
|
||||
/// </param>
|
||||
/// <param name="path" type="String" optional="true">
|
||||
/// The path to the property to check for updates. The path should be valid javascript; for example "Addresses[3].Street".
|
||||
/// </param>
|
||||
/// <param name="ignoreChildren" type="Boolean" optional="true">
|
||||
/// Whether or not updates to the children of the specified property should be considered in the result
|
||||
/// </param>
|
||||
/// <returns type="Boolean"/>
|
||||
|
||||
throw "Not reached"; // For Intellisense only.
|
||||
},
|
||||
|
||||
revertUpdates: function (entity, path, skipChildren) {
|
||||
/// <summary>
|
||||
/// Reverts updates to the entity and all the objects or arrays it contains. When a path is specified, it will
|
||||
/// revert only updates to the specified property and all of its children. This function is a no-op for entities
|
||||
/// not in the 'ClientUpdated' state.
|
||||
/// </summary>
|
||||
/// <param name="entity" type="Object">
|
||||
/// The entity to revert updates for
|
||||
/// </param>
|
||||
/// <param name="path" type="String" optional="true">
|
||||
/// The path to the property to revert updates for. The path should be valid javascript; for example "Addresses[3].Street".
|
||||
/// </param>
|
||||
/// <param name="skipChildren" type="Boolean" optional="true">
|
||||
/// Whether or not to revert updates to the children of the specified property
|
||||
/// </param>
|
||||
/// <returns type="upshot.EntityView"/>
|
||||
|
||||
throw "Not reached"; // For Intellisense only.
|
||||
},
|
||||
|
||||
///#ENDDEBUG
|
||||
|
||||
|
||||
// Internal methods
|
||||
|
||||
__registerForRecompute: function (entityView) {
|
||||
// Some EntityView that depends on us wants to recompute.
|
||||
base.__registerForRecompute.apply(this, arguments);
|
||||
|
||||
// Register on our input EntitySource transitively back to the root EntitySources, from which
|
||||
// our recompute wave originates.
|
||||
this._entitySource.__registerForRecompute(this);
|
||||
},
|
||||
|
||||
__recompute: function () {
|
||||
// Our input EntitySource is giving us an opportunity to recompute. Do so, if we've so-marked.
|
||||
if (this._needRecompute) {
|
||||
this._needRecompute = false;
|
||||
this._recompute();
|
||||
}
|
||||
|
||||
// Tell EntityViews that depend on us to recompute.
|
||||
this.__recomputeDependentViews();
|
||||
},
|
||||
|
||||
|
||||
// Private methods
|
||||
|
||||
_dispose: function () {
|
||||
if (this._entitySource) { // RemoteDataSource dynamically binds to its input EntitySource.
|
||||
this._entitySource.unbind(this._observer);
|
||||
}
|
||||
base._dispose.apply(this, arguments);
|
||||
},
|
||||
|
||||
_bindToEntitySource: function (entitySource) {
|
||||
|
||||
if (this._entitySource === entitySource) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
// Remove proxied DataContext-derived methods.
|
||||
if (this._entitySource) {
|
||||
$.each(dataContextMethodNames, function (index, name) {
|
||||
if (self[name]) {
|
||||
delete self[name];
|
||||
}
|
||||
});
|
||||
|
||||
this._entitySource.unbind(this._observer);
|
||||
}
|
||||
|
||||
this._entitySource = entitySource;
|
||||
|
||||
// Proxy these DataContext-derived methods, if they're available.
|
||||
if (entitySource.getDataContext) {
|
||||
$.each(dataContextMethodNames, function (index, name) {
|
||||
if (name !== "getEntityErrors") {
|
||||
// Don't use $.proxy here, as that will statically bind to entitySource[name] and
|
||||
// RemoteDataSource will dynamically change entitySource[name].
|
||||
self[name] = function () {
|
||||
var ret = entitySource[name].apply(entitySource, arguments);
|
||||
return (name === "deleteEntity") ? self : ret;
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.getEntityErrors = function () {
|
||||
return $.grep(entitySource.getEntityErrors(), function (error) {
|
||||
return self._haveEntity(error.entity);
|
||||
});
|
||||
};
|
||||
|
||||
entitySource.bind(this._observer);
|
||||
},
|
||||
|
||||
_setNeedRecompute: function () {
|
||||
// Sub-classes will call this method to mark themselves as being dirty, requiring recompute.
|
||||
this._needRecompute = true;
|
||||
this._entitySource.__registerForRecompute(this);
|
||||
},
|
||||
|
||||
_recompute: function () {
|
||||
// In response to a call to _setNeedRecompute, we're getting called to recompute as
|
||||
// part of the next recompute wave.
|
||||
throw "Unreachable"; // Abstract/pure virtual method.
|
||||
},
|
||||
|
||||
_handleEntityAdd: function (entity) {
|
||||
// Translate adds onto our input EntitySource.
|
||||
this._entitySource.__addEntity(entity);
|
||||
|
||||
base._handleEntityAdd.apply(this, arguments);
|
||||
},
|
||||
|
||||
_haveEntity: function (entity) {
|
||||
return $.inArray(entity, obs.asArray(this._clientEntities)) >= 0;
|
||||
},
|
||||
|
||||
_purgeEntity: function (entity) {
|
||||
base._purgeEntity.apply(this, arguments);
|
||||
},
|
||||
|
||||
_onPropertyChanged: function (entity, property, newValue) {
|
||||
// Translate property changes from our input EntitySource onto our result, if appropriate.
|
||||
// NOTE: _haveEntity will be with respect to our current, stable set of result entities.
|
||||
// Ignoring direct, observable inserts and removes, this result set will only change
|
||||
// as part of our separate recompute wave, which happens _after_ such data change events.
|
||||
if (this._haveEntity(entity)) {
|
||||
this._trigger("propertyChanged", entity, property, newValue);
|
||||
}
|
||||
},
|
||||
|
||||
_onArrayChanged: function (type, eventArgs) {
|
||||
// NOTE: These are not translated directly in the same way that property and entity state
|
||||
// change events are. Rather, subclasses have specific logic as to how changes to the
|
||||
// membership of their input EntitySource impacts their result entity membership.
|
||||
|
||||
// Will be overridden by derived classes.
|
||||
},
|
||||
|
||||
_onEntityStateChanged: function (entity, state, error) {
|
||||
if (this._haveEntity(entity)) {
|
||||
if (state === upshot.EntityState.Deleted) {
|
||||
// Entities deleted from our cache (due to an accepted server delete or due to a
|
||||
// reverted internal add) should disappear from all dependent EntityViews.
|
||||
this._purgeEntity(entity);
|
||||
}
|
||||
|
||||
// Translate entity state changes from our input EntitySource onto our result, if appropriate.
|
||||
// NOTE: _haveEntity will be with respect to our current, stable set of result entities.
|
||||
// Ignoring direct, observable inserts and removes, this result set will only change
|
||||
// as part of our separate recompute wave, which happens _after_ such change events.
|
||||
this._trigger("entityStateChanged", entity, state, error);
|
||||
}
|
||||
},
|
||||
|
||||
_onEntityUpdated: function (entity, path, eventArgs) {
|
||||
// Translate property changes from our input EntitySource onto our result, if appropriate.
|
||||
// NOTE: _haveEntity will be with respect to our current, stable set of result entities.
|
||||
// Ignoring direct, observable inserts and removes, this result set will only change
|
||||
// as part of our separate recompute wave, which happens _after_ such data change events.
|
||||
if (this._haveEntity(entity)) {
|
||||
this._trigger("entityUpdated", entity, path, eventArgs);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
upshot.EntityView = upshot.deriveClass(base, ctor, instanceMembers);
|
||||
|
||||
upshot.EntityView.__dataContextMethodNames = dataContextMethodNames;
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
|
@ -1,5 +0,0 @@
|
|||
/// <reference path="jquery-1.5.2-vsdoc.js" />
|
||||
/// <reference path="knockout-2.0.0.debug.js" />
|
||||
// top-level objects - these could possibly be transitioned to script dependencies
|
||||
var upshot = {}, WinJS = {};
|
||||
WinJS.Namespace = {};
|
|
@ -1,21 +0,0 @@
|
|||
/// <reference path="Dependencies.js" />
|
||||
/// <reference path="..\Core.js" />
|
||||
/// <reference path="..\AssociatedEntitiesView.js" />
|
||||
/// <reference path="..\Upshot.Compat.jQueryUI.js" />
|
||||
/// <reference path="..\Upshot.Compat.JsViews.js" />
|
||||
/// <reference path="..\Upshot.Compat.Knockout.js" />
|
||||
/// <reference path="..\Upshot.Compat.WinJS.js" />
|
||||
/// <reference path="..\DataContext.js" />
|
||||
/// <reference path="..\DataProvider.js" />
|
||||
/// <reference path="..\DataProvider.OData.js" />
|
||||
/// <reference path="..\DataProvider.datacontroller.js" />
|
||||
/// <reference path="..\DataProvider.json.js" />
|
||||
/// <reference path="..\DataSource.js" />
|
||||
/// <reference path="..\EntitySet.js" />
|
||||
/// <reference path="..\EntitySource.js" />
|
||||
/// <reference path="..\EntityView.js" />
|
||||
/// <reference path="..\LocalDataSource.js" />
|
||||
/// <reference path="..\Observability.js" />
|
||||
/// <reference path="..\RemoteDataSource.js" />
|
||||
|
||||
// This file enables VS IntelliSense in JavaScript, otherwise it can be removed
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,475 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
var base = upshot.DataSource.prototype;
|
||||
|
||||
var obs = upshot.observability;
|
||||
|
||||
var ctor = function (options) {
|
||||
/// <summary>
|
||||
/// LocalDataSource is used to load model data matching a query that is evaluated in-memory.
|
||||
/// </summary>
|
||||
/// <param name="options" optional="true">
|
||||
/// Options used in the construction of the LocalDataSource:
|
||||
/// source: The input data which is to be sorted, filtered and paged to produce the output for this LocalDataSource. Can be supplied as some instance deriving from EntitySource or as an array from some EntitySource.getEntities().
|
||||
/// autoRefresh: (Optional) Instructs the LocalDataSource to implicitly reevaluate its query in response to edits to input data. Otherwise, LocalDataSource.refresh() must be used to reevaluate the query against modified input data.
|
||||
/// </param>
|
||||
|
||||
// support no new ctor
|
||||
if (this._trigger === undefined) {
|
||||
return new upshot.LocalDataSource(options);
|
||||
}
|
||||
|
||||
var input = options && options.source;
|
||||
if (input && !input.__recomputeDependentViews) { // Test if "input" is an EntitySource.
|
||||
var entitySource = obs.isArray(input) && upshot.EntitySource.as(input);
|
||||
if (!entitySource) {
|
||||
throw "Input data for a LocalDataSource must be an EntitySource or the array returned by EntitySource.getEntities().";
|
||||
}
|
||||
|
||||
options.source = entitySource;
|
||||
|
||||
// TODO -- If this is an array that isn't already the output of an EntitySource,
|
||||
// engage the compatibility layer to wrap the raw array in an EntitySource.
|
||||
// Such an EntitySource would upshot.__registerRootEntitySource and would turn
|
||||
// observable CUD into Upshot "change" and "arrayChange" events.
|
||||
}
|
||||
|
||||
this._autoRefresh = options && options.autoRefresh; // TODO -- Should we make "auto refresh" a feature of RemoteDataSource too?
|
||||
|
||||
// Optional query options
|
||||
this._sort = null;
|
||||
this._filter = null;
|
||||
|
||||
// State
|
||||
this._refreshAllInProgress = false;
|
||||
|
||||
base.constructor.call(this, options);
|
||||
|
||||
// Events specific to LocalDataSource
|
||||
this._bindFromOptions(options, [ "refreshNeeded" ]);
|
||||
|
||||
if (this._autoRefresh) {
|
||||
this.refresh();
|
||||
}
|
||||
};
|
||||
|
||||
var instanceMembers = {
|
||||
|
||||
// override "sort" option setter
|
||||
setSort: function (sort) {
|
||||
/// <summary>
|
||||
/// Establishes the sort specification that is to be applied to the input data.
|
||||
/// </summary>
|
||||
/// <param name="sort">
|
||||
/// The sort specification to applied to the input data.
|
||||
/// Should be supplied as an object of the form { property: <propertyName> [, descending: <bool> ] } or an array of ordered objects of this form.
|
||||
/// When supplied as null or undefined, the sort specification for this LocalDataSource is cleared.
|
||||
/// </param>
|
||||
/// <returns type="upshot.LocalDataSource"/>
|
||||
|
||||
// TODO -- Should really raise "need refresh" event when changed (throughout).
|
||||
// TODO -- Validate sort specification?
|
||||
this._sort = sort;
|
||||
return this;
|
||||
},
|
||||
|
||||
// override "filter" option setter
|
||||
setFilter: function (filter) {
|
||||
/// <summary>
|
||||
/// Establishes the filter specification that is to be applied to the input data.
|
||||
/// </summary>
|
||||
/// <param name="filter">
|
||||
/// The filter specification to applied to the input data.
|
||||
/// Should be supplied as an object of the form { property: <propertyName>, value: <propertyValue> [, operator: <operator> ] } or a function(entity) returning Boolean or an ordered array of these forms.
|
||||
/// When supplied as null or undefined, the filter specification for this LocalDataSource is cleared.
|
||||
/// </param>
|
||||
/// <returns type="upshot.LocalDataSource"/>
|
||||
|
||||
// TODO -- Should really raise "need refresh" event when changed (throughout).
|
||||
this._filter = filter && this._createFilterFunction(filter);
|
||||
},
|
||||
|
||||
refresh: function (options, success, fail) {
|
||||
/// <summary>
|
||||
/// Initiates an asynchronous reevaluation of the query established with setSort, setFilter and setPaging.
|
||||
/// </summary>
|
||||
/// <param name="options" type="Object" optional="true">
|
||||
/// If supplied, an object with an "all" property, indicating that the input DataSource is to be refreshed prior to reevaluating this LocalDataSource query.
|
||||
/// </param>
|
||||
/// <param name="success" type="Function" optional="true">
|
||||
/// A success callback with signature function(entities, totalCount).
|
||||
/// </param>
|
||||
/// <param name="error" type="Function" optional="true">
|
||||
/// An error callback with signature function(httpStatus, errorText, context).
|
||||
/// </param>
|
||||
/// <returns type="upshot.LocalDataSource"/>
|
||||
|
||||
this._verifyOkToRefresh();
|
||||
|
||||
if ($.isFunction(options)) {
|
||||
fail = success;
|
||||
success = options;
|
||||
options = undefined;
|
||||
}
|
||||
|
||||
this._trigger("refreshStart");
|
||||
|
||||
var self = this,
|
||||
sourceIsDataSource = this._entitySource.refresh; // Only DataSources will have "refresh" (and not EntitySet and AssociatedEntitiesView).
|
||||
if (options && !!options.all && sourceIsDataSource) {
|
||||
// N.B. "all" is a helper, in the sense that it saves a client from doing a serverDataSource.refresh and then,
|
||||
// in response to serverDataSource.onRefresh, calling localDataSource.refresh. Also, it allows the app to listen
|
||||
// on refreshStart/refresh events from this LDS alone (and not the inner SDS as well).
|
||||
this._refreshAllInProgress = true;
|
||||
this._entitySource.refresh({ all: true }, function (entities) {
|
||||
completeRefresh(entities);
|
||||
self._refreshAllInProgress = false;
|
||||
}, function (httpStatus, errorText, context) {
|
||||
self._failRefresh(httpStatus, errorText, context, fail);
|
||||
});
|
||||
} else {
|
||||
// We do this refresh asynchronously so that, if this refresh was called during a callback,
|
||||
// the app receives remaining callbacks first, before the new batch of callbacks with respect to this refresh.
|
||||
// TODO -- We should only refresh once in response to N>1 "refresh" calls.
|
||||
setTimeout(function () {
|
||||
if (!self._isDisposed()) {
|
||||
completeRefresh(obs.asArray(self._entitySource.getEntities()));
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
function completeRefresh(entities) {
|
||||
self._needRecompute = false;
|
||||
|
||||
var results = self._applyQuery(entities);
|
||||
self._completeRefresh(results.entities, results.totalCount, success);
|
||||
};
|
||||
},
|
||||
|
||||
refreshNeeded: function () {
|
||||
/// <summary>
|
||||
/// Indicates whether the input data has been modified in such a way that LocalDataSource.getEntities() would change on the next LocalDataSource.refresh() call.
|
||||
/// </summary>
|
||||
/// <returns type="Boolean"/>
|
||||
|
||||
return this._needRecompute;
|
||||
},
|
||||
|
||||
// Private methods
|
||||
|
||||
_setNeedRecompute: function () {
|
||||
///#DEBUG
|
||||
upshot.assert(!this._needRecompute); // Callers should only determine dirtiness if we're not already dirty.
|
||||
///#ENDDEBUG
|
||||
|
||||
if (this._autoRefresh) {
|
||||
// Recompute our result entities according to our regular recompute cycle,
|
||||
// just as AssociatedEntitiesView does in response to changes in its
|
||||
// target EntitySet.
|
||||
base._setNeedRecompute.call(this);
|
||||
} else {
|
||||
// Explicit use of "refresh" was requested here. Reuse (or abuse) the _needRecompute
|
||||
// flag to track our dirtiness.
|
||||
this._needRecompute = true;
|
||||
this._trigger("refreshNeeded");
|
||||
}
|
||||
},
|
||||
|
||||
_recompute: function () {
|
||||
///#DEBUG
|
||||
upshot.assert(this._autoRefresh); // We should only get here if we scheduled a recompute, for auto-refresh.
|
||||
///#ENDDEBUG
|
||||
|
||||
this._trigger("refreshStart");
|
||||
|
||||
var results = this._applyQuery(obs.asArray(this._entitySource.getEntities()));
|
||||
// Don't __triggerRecompute here. Downstream listeners on this data source will recompute
|
||||
// as part of this wave.
|
||||
this._applyNewQueryResult(results.entities, results.totalCount);
|
||||
|
||||
this._trigger("refreshSuccess", obs.asArray(this._clientEntities), this._lastRefreshTotalEntityCount);
|
||||
},
|
||||
|
||||
_normalizePropertyValue: function (entity, property) {
|
||||
// TODO -- Should do this based on metadata and return default value of the correct scalar type.
|
||||
return obs.getProperty(entity, property) || "";
|
||||
},
|
||||
|
||||
_onPropertyChanged: function (entity, property, newValue) {
|
||||
base._onPropertyChanged.apply(this, arguments);
|
||||
|
||||
if (this._refreshAllInProgress) {
|
||||
// We don't want to event "need refresh" due to a "refresh all".
|
||||
// Rather, we want to issue "refresh completed".
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._needRecompute) {
|
||||
var needRecompute = false;
|
||||
if (this._haveEntity(entity)) {
|
||||
if (this._filter && !this._filter(entity)) {
|
||||
needRecompute = true;
|
||||
}
|
||||
} else if (this._filter && this._filter(entity)) {
|
||||
// This is overly pessimistic if we have paging options in place.
|
||||
// It could be that this entity is already on a preceding page (and will
|
||||
// stay there on recompute) or would be added to a following page (and
|
||||
// excluded from the current page on recompute).
|
||||
needRecompute = true;
|
||||
}
|
||||
if (this._haveEntity(entity) && this._sort) {
|
||||
if ($.isFunction(this._sort)) {
|
||||
needRecompute = true;
|
||||
} else if (upshot.isArray(this._sort)) {
|
||||
needRecompute = $.grep(this._sort, function (sortPart) {
|
||||
return sortPart.property === property;
|
||||
}).length > 0;
|
||||
} else {
|
||||
needRecompute = this._sort.property === property;
|
||||
}
|
||||
}
|
||||
|
||||
if (needRecompute) {
|
||||
this._setNeedRecompute();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// support the following filter formats
|
||||
// function, [functions], filterPart, [filterParts]
|
||||
// return: function
|
||||
_createFilterFunction: function (filter) {
|
||||
var self = this;
|
||||
if ($.isFunction(filter)) {
|
||||
return filter;
|
||||
}
|
||||
|
||||
var filters = this._normalizeFilters(filter);
|
||||
var comparisonFunctions = []
|
||||
for (var i = 0; i < filters.length; i++) {
|
||||
var filterPart = filters[i];
|
||||
if ($.isFunction(filterPart)) {
|
||||
comparisonFunctions.push(filterPart);
|
||||
} else {
|
||||
var func = createFunction(filterPart.property, filterPart.operator, filterPart.value);
|
||||
comparisonFunctions.push(func);
|
||||
}
|
||||
}
|
||||
return function (entity) {
|
||||
for (var i = 0; i < comparisonFunctions.length; i++) {
|
||||
if (!comparisonFunctions[i](entity)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
function createFunction(filterProperty, filterOperator, filterValue) {
|
||||
var comparer;
|
||||
switch (filterOperator) {
|
||||
case "<": comparer = function (propertyValue) { return propertyValue < filterValue; }; break;
|
||||
case "<=": comparer = function (propertyValue) { return propertyValue <= filterValue; }; break;
|
||||
case "==": comparer = function (propertyValue) { return propertyValue == filterValue; }; break;
|
||||
case "!=": comparer = function (propertyValue) { return propertyValue != filterValue; }; break;
|
||||
case ">=": comparer = function (propertyValue) { return propertyValue >= filterValue; }; break;
|
||||
case ">": comparer = function (propertyValue) { return propertyValue > filterValue; }; break;
|
||||
case "Contains":
|
||||
comparer = function (propertyValue) {
|
||||
if (typeof propertyValue === "string" && typeof filterValue === "string") {
|
||||
propertyValue = propertyValue.toLowerCase();
|
||||
filterValue = filterValue.toLowerCase();
|
||||
}
|
||||
return propertyValue.indexOf(filterValue) >= 0;
|
||||
};
|
||||
break;
|
||||
default: throw "Unrecognized filter operator.";
|
||||
};
|
||||
|
||||
return function (entity) {
|
||||
// Can't trust added entities, for instance, to have all required property values.
|
||||
var propertyValue = self._normalizePropertyValue(entity, filterProperty);
|
||||
return comparer(propertyValue);
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
_getSortFunction: function () {
|
||||
var self = this;
|
||||
if (!this._sort) {
|
||||
return null;
|
||||
} else if ($.isFunction(this._sort)) {
|
||||
return this._sort;
|
||||
} else if (upshot.isArray(this._sort)) {
|
||||
var sortFunction;
|
||||
$.each(this._sort, function (unused, sortPart) {
|
||||
var sortPartFunction = getSortPartFunction(sortPart);
|
||||
if (!sortFunction) {
|
||||
sortFunction = sortPartFunction;
|
||||
} else {
|
||||
sortFunction = function (sortPartFunction1, sortPartFunction2) {
|
||||
return function (entity1, entity2) {
|
||||
var result = sortPartFunction1(entity1, entity2);
|
||||
return result === 0 ? sortPartFunction2(entity1, entity2) : result;
|
||||
};
|
||||
} (sortFunction, sortPartFunction);
|
||||
}
|
||||
});
|
||||
return sortFunction;
|
||||
} else {
|
||||
return getSortPartFunction(this._sort);
|
||||
}
|
||||
|
||||
function getSortPartFunction(sortPart) {
|
||||
return function (entity1, entity2) {
|
||||
var isAscending = !sortPart.descending,
|
||||
propertyName = sortPart.property,
|
||||
propertyValue1 = self._normalizePropertyValue(entity1, propertyName),
|
||||
propertyValue2 = self._normalizePropertyValue(entity2, propertyName);
|
||||
if (propertyValue1 == propertyValue2) {
|
||||
return 0;
|
||||
} else if (propertyValue1 > propertyValue2) {
|
||||
return isAscending ? 1 : -1;
|
||||
} else {
|
||||
return isAscending ? -1 : 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
_applyQuery: function (entities) {
|
||||
var self = this;
|
||||
|
||||
var filteredEntities;
|
||||
if (this._filter) {
|
||||
filteredEntities = $.grep(entities, function (entity, index) {
|
||||
return self._filter(entity);
|
||||
});
|
||||
} else {
|
||||
filteredEntities = entities;
|
||||
}
|
||||
|
||||
var sortFunction = this._getSortFunction(),
|
||||
sortedEntities;
|
||||
if (sortFunction) {
|
||||
// "sort" modifies filtered entities, so we must be operating against a copy
|
||||
// by this point. Otherwise, we'll potentially update the LDS input array.
|
||||
if (filteredEntities === entities) {
|
||||
filteredEntities = filteredEntities.slice(0);
|
||||
}
|
||||
sortedEntities = filteredEntities.sort(sortFunction);
|
||||
} else {
|
||||
sortedEntities = filteredEntities;
|
||||
}
|
||||
|
||||
var skip = this._skip || 0,
|
||||
pagedEntities = skip > 0 ? sortedEntities.slice(skip) : sortedEntities;
|
||||
if (this._take) {
|
||||
pagedEntities = pagedEntities.slice(0, this._take);
|
||||
}
|
||||
|
||||
return { entities: pagedEntities, totalCount: sortedEntities.length };
|
||||
},
|
||||
|
||||
_onArrayChanged: function (type, eventArguments) {
|
||||
base._onArrayChanged.apply(this, arguments);
|
||||
|
||||
if (this._refreshAllInProgress) {
|
||||
// We don't want to event "need refresh" due to a "refresh all".
|
||||
// Rather, we want to issue "refresh completed".
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._needRecompute) {
|
||||
// See if the inner array change should cause us to raise the "need refresh" event.
|
||||
var self = this,
|
||||
needRecompute = false;
|
||||
|
||||
switch (type) {
|
||||
case "insert":
|
||||
var insertedEntities = eventArguments.items;
|
||||
if (insertedEntities.length > 0) {
|
||||
var anyExternallyInsertedEntitiesMatchFilter = !this._filter ||
|
||||
$.grep(insertedEntities, function (entity) {
|
||||
return self._filter(entity);
|
||||
}).length > 0;
|
||||
if (anyExternallyInsertedEntitiesMatchFilter) {
|
||||
needRecompute = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "remove":
|
||||
if (this._take > 0 || this._skip > 0) {
|
||||
// If we have paging options, we have to conservatively assume that the result will be shy
|
||||
// of the _limit due to this delete or the result should be shifted due to a delete from
|
||||
// those entities preceding the _skip.
|
||||
needRecompute = true;
|
||||
|
||||
// NOTE: This covers the case where an entity in our input reaches the upshot.EntityState.Deleted
|
||||
// state. We assume that this will cause the entity to be removed from the input EntitySource.
|
||||
} else {
|
||||
var nonDeletedResultEntitiesRemoved = $.grep(eventArguments.items, function (entity) {
|
||||
return self._haveEntity(entity) &&
|
||||
(self.getEntityState(entity) || upshot.EntityState.Deleted) !== upshot.EntityState.Deleted;
|
||||
});
|
||||
if (nonDeletedResultEntitiesRemoved.length > 0) {
|
||||
// If the input EntitySource happens to be an EntityView and entities leave that view
|
||||
// for some other reason than reaching the upshot.EntityState.Deleted state, we should
|
||||
// signal the need for a recompute to remove these entities from our results (but let
|
||||
// the client control this for the non-auto-refresh case).
|
||||
needRecompute = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "replaceAll":
|
||||
if (!this._refreshAllInProgress) {
|
||||
// We don't want to event "need refresh" due to a "refresh all".
|
||||
// Rather, we want to issue "refresh completed".
|
||||
|
||||
var results = this._applyQuery(eventArguments.newItems);
|
||||
if (this.totalCount !== results.totalCount) {
|
||||
needRecompute = true;
|
||||
} else {
|
||||
// Reference comparison is enough here. "property changed" catches deeper causes of "need refresh".
|
||||
needRecompute = !upshot.sameArrayContents(obs.asArray(this._clientEntities), results.entities);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw "Unknown array operation '" + type + "'.";
|
||||
}
|
||||
|
||||
if (needRecompute) {
|
||||
this._setNeedRecompute();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_handleEntityAdd: function (entity) {
|
||||
if (!this._needRecompute) {
|
||||
// We have no idea whether this add is in the right position relative to the input entities,
|
||||
// the current sort or paging specifications. Conservatively recompute.
|
||||
this._setNeedRecompute();
|
||||
}
|
||||
base._handleEntityAdd.apply(this, arguments);
|
||||
},
|
||||
|
||||
_handleEntityDelete: function (entity) {
|
||||
if (!this._needRecompute && this._take > 0) {
|
||||
// If we have a _take, we have to conservatively assume that the result will be one entity shy of
|
||||
// the take due to this delete.
|
||||
this._setNeedRecompute();
|
||||
}
|
||||
base._handleEntityDelete.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
upshot.LocalDataSource = upshot.deriveClass(base, ctor, instanceMembers);
|
||||
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
|
@ -1,124 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
var obs = upshot.observability;
|
||||
|
||||
var metadata = {};
|
||||
|
||||
upshot.metadata = function (entityType) {
|
||||
if (arguments.length === 0) {
|
||||
return $.extend({}, metadata);
|
||||
} else if (typeof entityType === "string") {
|
||||
if (arguments.length === 1) {
|
||||
return metadata[entityType];
|
||||
} else {
|
||||
if (!metadata[entityType]) {
|
||||
metadata[entityType] = arguments[1];
|
||||
}
|
||||
// ...else assume the new metadata is the same as that previously registered for entityType.
|
||||
}
|
||||
} else {
|
||||
$.each(entityType, function (entityType, metadata) {
|
||||
upshot.metadata(entityType, metadata);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
upshot.metadata.getProperties = function (entity, entityType, includeAssocations) {
|
||||
var props = [];
|
||||
if (entityType) {
|
||||
var metadata = upshot.metadata(entityType);
|
||||
if (metadata && metadata.fields) {
|
||||
// if metadata is present, we'll loop through the fields
|
||||
var fields = metadata.fields;
|
||||
for (var prop in fields) {
|
||||
if (includeAssocations || !fields[prop].association) {
|
||||
props.push({ name: prop, type: fields[prop].type, association: fields[prop].association });
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
||||
// otherwise we'll use the observability layer to infer the properties
|
||||
for (var prop in entity) {
|
||||
// TODO: determine if we want to allow the case where hasOwnProperty returns false (hierarchies, etc.)
|
||||
if (entity.hasOwnProperty(prop) && obs.isProperty(entity, prop) && (prop.indexOf("jQuery") !== 0)) {
|
||||
props.push({ name: prop });
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
upshot.metadata.getPropertyType = function (entityType, property) {
|
||||
if (entityType) {
|
||||
var metadata = upshot.metadata(entityType);
|
||||
if (metadata && metadata.fields && metadata.fields[property]) {
|
||||
return metadata.fields[property].type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
upshot.metadata.isEntityType = function (type) {
|
||||
if (type) {
|
||||
var metadata = upshot.metadata(type);
|
||||
if (metadata && metadata.key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var types = {};
|
||||
|
||||
upshot.registerType = function (type, keyFunction) {
|
||||
/// <summary>
|
||||
/// Registers a type string for later access with a key. This facility is convenient to avoid duplicate type string literals throughout your application scripts. The key is expected to be returned by 'keyFunction', allowing the call to 'registerType' to precede the line of JavaScript declaring the key. Typically, the returned key will be a constructor function for a JavaScript class corresponding to 'type'.
|
||||
/// </summary>
|
||||
/// <param name="keyFunction" type="Function">
|
||||
/// A function returning the key by which the type string will later be retrieved.
|
||||
/// </param>
|
||||
/// <returns type="String"/>
|
||||
|
||||
if (upshot.isObject(type)) {
|
||||
// Allow for registrations that cover multiple types like:
|
||||
// upshot.registerType({ "BarType": function () { return Bar; }, "FooType": function () { return Foo; } });
|
||||
$.each(type, function (type, key) {
|
||||
upshot.registerType(type, key);
|
||||
});
|
||||
} else {
|
||||
// Allow for single-type registrations:
|
||||
// upshot.registerType("BarType", function () { return Bar; });
|
||||
var keyFunctions = types[type] || (types[type] = []);
|
||||
if ($.inArray(keyFunction, keyFunctions) < 0) {
|
||||
keyFunctions.push(keyFunction);
|
||||
}
|
||||
}
|
||||
return upshot;
|
||||
}
|
||||
|
||||
upshot.type = function (key) {
|
||||
/// <summary>
|
||||
/// Returns the type string registered for a particular key.
|
||||
/// </summary>
|
||||
/// <param name="key">
|
||||
/// The key under which the desired type string is registered.
|
||||
/// </param>
|
||||
/// <returns type="String"/>
|
||||
|
||||
var result;
|
||||
for (var type in types) {
|
||||
if (types.hasOwnProperty(type)) {
|
||||
var keyFunctions = types[type];
|
||||
for (var i = 0; i < keyFunctions.length; i++) {
|
||||
if (keyFunctions[i]() === key) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw "No type string registered for key '" + key + "'.";
|
||||
}
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
|
@ -1,24 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
var observability = upshot.observability = upshot.observability || {};
|
||||
|
||||
$.each(["track", "insert", "remove", "refresh", "isProperty", "getProperty", "setProperty", "isArray", "createCollection", "asArray", "map", "unmap", "setContextProperty"], function (index, value) {
|
||||
observability[value] = function () {
|
||||
// NOTE: observability.configuration is expected to be established by a loaded Upshot.Compat.<platform>.js.
|
||||
// TODO: Support apps and UI libraries that have no observability design.
|
||||
var config = observability.configuration;
|
||||
return config[value].apply(config, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
upshot.map = function (data, entityType, target) {
|
||||
// Interestingly, we don't use a "mapNested" parameter here (as Upshot proper does).
|
||||
// As a consequence, apps that call upshot.map from a map function will not pick up custom
|
||||
// map functions for nested entities/objects. They'd need to hand-code calls to custom map
|
||||
// functions (ctors) for nested objects from the parent map function.
|
||||
return observability.configuration.map(data, entityType, null, target);
|
||||
};
|
||||
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
|
@ -1,263 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
var base = upshot.DataSource.prototype;
|
||||
|
||||
var commitEvents = ["commitStart", "commitSuccess", "commitError"];
|
||||
|
||||
var ctor = function (options) {
|
||||
/// <summary>
|
||||
/// RemoteDataSource is used to load model data matching a query that is evaluated on the server.
|
||||
/// </summary>
|
||||
/// <param name="options" optional="true">
|
||||
/// Options used in the construction of the RemoteDataSource:
|
||||
/// bufferChanges: (Optional) If 'true', edits to model data are buffered until RemoteDataSource.commitChanges. Otherwise, edits are committed to the server immediately.
|
||||
/// result: (Optional) The observable array into which the RemoteDataSource will load model data.
|
||||
/// dataContext: (Optional) A DataContext instance that acts as a shared cache for multiple DataSource instances. When not supplied, a DataContext instance is instantiated for this RemoteDataSource.
|
||||
/// entityType: The type of model data that will be loaded by this RemoteDataSource instance.
|
||||
/// provider: (Optional) Specifies the DataProvider that will be used to get model data and commit edits to the model data. Defaults to upshot.DataProvider which works with Microsoft.Web.Http.Data.DataController.
|
||||
/// providerParameters: (Optional) Parameters that are supplied to the DataProvider for this RemoteDataSource and used by the DataProvider when it gets model data from the server.
|
||||
/// mapping: (Optional) A function (typically a constructor) used to translate raw model data loaded via the DataProvider into model data that will be surfaced by this RemoteDataSource.
|
||||
/// </param>
|
||||
|
||||
// support no new ctor
|
||||
if (this._trigger === undefined) {
|
||||
return new upshot.RemoteDataSource(options);
|
||||
}
|
||||
|
||||
// Optional query options
|
||||
this._sort = null;
|
||||
this._filters = null;
|
||||
|
||||
var dataProvider, dataContext, mapping;
|
||||
if (options) {
|
||||
this._providerParameters = options.providerParameters;
|
||||
this._entityType = options.entityType;
|
||||
dataContext = options.dataContext;
|
||||
|
||||
// support both specification of a provider instance as well as
|
||||
// a provider function. In the latter case, we create the provider
|
||||
// for the user
|
||||
if (!options.provider && !options.dataContext) {
|
||||
// we're in a remote scenario but no context or provider has been specified.
|
||||
// use our default provider in this case.
|
||||
dataProvider = upshot.DataProvider;
|
||||
} else {
|
||||
dataProvider = options.provider;
|
||||
}
|
||||
|
||||
if ($.isFunction(dataProvider)) {
|
||||
dataProvider = new dataProvider();
|
||||
}
|
||||
|
||||
// Acceptable formats for "mapping":
|
||||
// { entityType: "Customer", mapping: Customer }
|
||||
// { entityType: "Customer", mapping: { map: Customer, unmap: unmapCustomer } }
|
||||
// { mapping: { "Customer": Customer } }
|
||||
// { mapping: { "Customer": { map: Customer, unmap: unmapCustomer } } }
|
||||
mapping = options.mapping;
|
||||
if (mapping &&
|
||||
($.isFunction(mapping) || // Mapping supplied as a "map" function.
|
||||
($.isFunction(mapping.map) || $.isFunction(mapping.unmap)))) { // Mapping supplied as map/unmap functions.
|
||||
|
||||
if (!this._entityType) {
|
||||
// TODO: Build out the no-type scenario where the DataSource supplies a mapping
|
||||
// function and our merge algorithm ignores any typing from the DataProvider.
|
||||
throw "Need 'entityType' option in order to supply " +
|
||||
($.isFunction(mapping) ? "a function" : "map/unmap functions") +
|
||||
" for 'mapping' option.";
|
||||
}
|
||||
|
||||
var mappingForType = mapping;
|
||||
mapping = {};
|
||||
mapping[this._entityType] = mappingForType;
|
||||
}
|
||||
}
|
||||
|
||||
var self = this;
|
||||
if (!dataContext) {
|
||||
var implicitCommitHandler;
|
||||
if (!options.bufferChanges) {
|
||||
// since we're not change tracking, define an implicit commit callback
|
||||
// and pass into the DC
|
||||
implicitCommitHandler = function () {
|
||||
self._dataContext._commitChanges({ providerParameters: self._providerParameters });
|
||||
}
|
||||
}
|
||||
|
||||
dataContext = new upshot.DataContext(dataProvider, implicitCommitHandler, mapping);
|
||||
// TODO -- If DS exclusively owns the DC, can we make it non-accumulating?
|
||||
} else if (mapping) {
|
||||
// This will throw if the app is supplying a different mapping for a given entityType.
|
||||
dataContext.addMapping(mapping);
|
||||
}
|
||||
|
||||
this._dataContext = dataContext;
|
||||
|
||||
// define commit[Start,Success,Error] observers
|
||||
var observer = {};
|
||||
$.each(commitEvents, function (unused, name) {
|
||||
observer[name] = function () {
|
||||
self._trigger.apply(self, [name].concat(Array.prototype.slice.call(arguments)));
|
||||
};
|
||||
});
|
||||
|
||||
this._dataContextObserver = observer;
|
||||
this._dataContext.bind(this._dataContextObserver);
|
||||
|
||||
var entitySource = options && options.entityType && this._dataContext.getEntitySet(options.entityType);
|
||||
if (entitySource) {
|
||||
options = $.extend({}, options, { source: entitySource });
|
||||
} else {
|
||||
// Until we can bindToEntitySource, fill in the DataContext-specific methods with some usable defaults.
|
||||
$.each(upshot.EntityView.__dataContextMethodNames, function (index, name) {
|
||||
if (name !== "getDataContext") {
|
||||
self[name] = function () {
|
||||
throw "DataContext-specific methods are not available on RemoteDataSource a result type can be determined. Consider supplying the \"entityType\" option when creating a RemoteDataSource or execute an initial query against your RemoteDatasource to determine the result type.";
|
||||
};
|
||||
}
|
||||
});
|
||||
this.getDataContext = function () {
|
||||
return this._dataContext;
|
||||
};
|
||||
}
|
||||
|
||||
base.constructor.call(this, options);
|
||||
|
||||
// Events specific to RemoteDataSource
|
||||
this._bindFromOptions(options, commitEvents);
|
||||
};
|
||||
|
||||
var instanceMembers = {
|
||||
|
||||
setSort: function (sort) {
|
||||
/// <summary>
|
||||
/// Establishes the sort specification that is to be applied as part of a server query when loading model data.
|
||||
/// </summary>
|
||||
/// <param name="sort">
|
||||
/// The sort specification to applied when loading model data.
|
||||
/// Should be supplied as an object of the form { property: <propertyName> [, descending: <bool> ] } or an array of ordered objects of this form.
|
||||
/// When supplied as null or undefined, the sort specification for this RemoteDataSource is cleared.
|
||||
/// </param>
|
||||
/// <returns type="upshot.RemoteDataSource"/>
|
||||
|
||||
// TODO -- Validate sort specification?
|
||||
this._sort = (sort && !upshot.isArray(sort)) ? [sort] : sort;
|
||||
return this;
|
||||
},
|
||||
|
||||
setFilter: function (filter) {
|
||||
/// <summary>
|
||||
/// Establishes the filter specification that is to be applied as part of a server query when loading model data.
|
||||
/// </summary>
|
||||
/// <param name="filter">
|
||||
/// The filter specification to applied when loading model data.
|
||||
/// Should be supplied as an object of the form { property: <propertyName>, value: <propertyValue> [, operator: <operator> ] } or an array of ordered objects of this form.
|
||||
/// When supplied as null or undefined, the filter specification for this RemoteDataSource is cleared.
|
||||
/// </param>
|
||||
/// <returns type="upshot.RemoteDataSource"/>
|
||||
|
||||
this._filters = filter && this._normalizeFilters(filter);
|
||||
return this;
|
||||
},
|
||||
|
||||
// TODO -- We should do a single setTimeout here instead, just in case N clients request a refresh
|
||||
// in response to callbacks.
|
||||
refresh: function (options, success, error) {
|
||||
/// <summary>
|
||||
/// Initiates an asynchronous get to load model data matching the query established with setSort, setFilter and setPaging.
|
||||
/// </summary>
|
||||
/// <param name="options" optional="true">
|
||||
/// There are no valid options recognized by RemoteDataSource.
|
||||
/// </param>
|
||||
/// <param name="success" type="Function" optional="true">
|
||||
/// A success callback with signature function(entities, totalCount).
|
||||
/// </param>
|
||||
/// <param name="error" type="Function" optional="true">
|
||||
/// An error callback with signature function(httpStatus, errorText, context).
|
||||
/// </param>
|
||||
/// <returns type="upshot.RemoteDataSource"/>
|
||||
|
||||
this._verifyOkToRefresh();
|
||||
|
||||
if ($.isFunction(options)) {
|
||||
error = success;
|
||||
success = options;
|
||||
options = undefined;
|
||||
}
|
||||
|
||||
this._trigger("refreshStart");
|
||||
|
||||
var self = this,
|
||||
onSuccess = function (entitySet, entities, totalCount) {
|
||||
if (!self._isDisposed()) {
|
||||
self._bindToEntitySource(entitySet);
|
||||
self._completeRefresh(entities, totalCount, success);
|
||||
}
|
||||
},
|
||||
onError = function (httpStatus, errorText, context) {
|
||||
if (!self._isDisposed()) {
|
||||
self._failRefresh(httpStatus, errorText, context, error);
|
||||
}
|
||||
};
|
||||
|
||||
this._dataContext.__load({
|
||||
entityType: this._entityType,
|
||||
providerParameters: this._providerParameters,
|
||||
|
||||
queryParameters: {
|
||||
filters: this._filters,
|
||||
sort: this._sort,
|
||||
skip: this._skip,
|
||||
take: this._take,
|
||||
includeTotalCount: this._includeTotalCount
|
||||
}
|
||||
}, onSuccess, onError);
|
||||
return this;
|
||||
},
|
||||
|
||||
commitChanges: function (success, error) {
|
||||
/// <summary>
|
||||
/// Initiates an asynchronous commit of any model data edits collected by the DataContext for this RemoteDataSource.
|
||||
/// </summary>
|
||||
/// <param name="success" type="Function" optional="true">
|
||||
/// A success callback.
|
||||
/// </param>
|
||||
/// <param name="error" type="Function" optional="true">
|
||||
/// An error callback with signature function(httpStatus, errorText, context).
|
||||
/// </param>
|
||||
/// <returns type="upshot.RemoteDataSource"/>
|
||||
|
||||
this._dataContext.commitChanges({
|
||||
providerParameters: this._providerParameters
|
||||
}, $.proxy(success, this), $.proxy(error, this));
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
// Private methods
|
||||
|
||||
_dispose: function () {
|
||||
this._dataContext.unbind(this._dataContextObserver);
|
||||
base._dispose.apply(this, arguments);
|
||||
},
|
||||
|
||||
_bindToEntitySource: function (entitySource) {
|
||||
|
||||
base._bindToEntitySource.call(this, entitySource);
|
||||
|
||||
// Reverting changes at this level with no "entities" arguments will revert all changes in the data context.
|
||||
// TODO -- should AssociatedEntitiesView do the same thing with respect to revertChanges
|
||||
this.revertChanges = function () {
|
||||
return arguments.length > 0
|
||||
? this._entitySource.revertChanges.apply(this._entitySource, arguments)
|
||||
: this._dataContext.revertChanges();
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
upshot.RemoteDataSource = upshot.deriveClass(base, ctor, instanceMembers);
|
||||
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
|
@ -1,135 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
function track(data, options) {
|
||||
if (!options) {
|
||||
$.observable.track(data, null);
|
||||
} else {
|
||||
$.observable.track(data, {
|
||||
beforeChange: options.beforeChange && wrapCallback(options.beforeChange),
|
||||
afterChange: options.afterChange && wrapCallback(options.afterChange),
|
||||
afterEvent: options.afterEvent && wrapCallback(options.afterEvent)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// transform JsViews to upshot eventArg style
|
||||
function wrapCallback(callback) {
|
||||
return function ($target, type, data) {
|
||||
if (type == "propertyChange") {
|
||||
var eventArg = { newValues: {}, oldValues: {} };
|
||||
eventArg.newValues[data.path] = data.value;
|
||||
eventArg.oldValues[data.path] = $target[data.path];
|
||||
return callback.call(this, $target, "change", eventArg);
|
||||
} else if (type == "arrayChange") {
|
||||
switch (data.change) {
|
||||
case "insert":
|
||||
return callback.call(this, $target, "insert", { index: data.index, items: data.items });
|
||||
case "remove":
|
||||
return callback.call(this, $target, "remove", { index: data.index, items: data.items });
|
||||
case "refresh":
|
||||
return callback.call(this, $target, "replaceAll", { newItems: data.newItems, oldItems: data.oldItems });
|
||||
}
|
||||
}
|
||||
throw "NYI - event '" + type + "' and '" + data.change + "' is not supported!";
|
||||
};
|
||||
}
|
||||
|
||||
function insert(array, index, items) {
|
||||
array.splice.apply(array, [index, 0].concat(items));
|
||||
var eventArguments = {
|
||||
change: "insert",
|
||||
index: index,
|
||||
items: items
|
||||
};
|
||||
$([array]).triggerHandler("arrayChange", eventArguments);
|
||||
}
|
||||
|
||||
function remove(array, index, numToRemove) {
|
||||
var itemsRemoved = array.slice(index, index + numToRemove);
|
||||
array.splice(index, numToRemove);
|
||||
var eventArguments = {
|
||||
change: "remove",
|
||||
index: index,
|
||||
items: itemsRemoved
|
||||
};
|
||||
$([array]).triggerHandler("arrayChange", eventArguments);
|
||||
}
|
||||
|
||||
function refresh(array, newItems) {
|
||||
var oldItems = array.slice(0);
|
||||
array.splice.apply(array, [0, array.length].concat(newItems));
|
||||
var eventArguments = {
|
||||
change: "refresh",
|
||||
oldItems: oldItems,
|
||||
newItems: newItems
|
||||
};
|
||||
$([array]).triggerHandler("arrayChange", eventArguments);
|
||||
}
|
||||
|
||||
function isProperty(item, name) {
|
||||
return !$.isFunction(item[name]);
|
||||
}
|
||||
|
||||
function getProperty(item, name) {
|
||||
return item[name];
|
||||
}
|
||||
|
||||
function setProperty(item, name, value) {
|
||||
var oldValue = item[name];
|
||||
item[name] = value;
|
||||
var eventArguments = {
|
||||
path: name,
|
||||
value: value
|
||||
};
|
||||
$(item).triggerHandler("propertyChange", eventArguments);
|
||||
}
|
||||
|
||||
function isArray(item) {
|
||||
return upshot.isArray(item);
|
||||
}
|
||||
|
||||
function createCollection(initialValues) {
|
||||
return initialValues || [];
|
||||
}
|
||||
|
||||
function asArray(collection) {
|
||||
return collection;
|
||||
}
|
||||
|
||||
function map(item) {
|
||||
return item;
|
||||
}
|
||||
|
||||
function unmap(item) {
|
||||
return item;
|
||||
}
|
||||
|
||||
var observability = upshot.defineNamespace("upshot.observability");
|
||||
|
||||
observability.jsviews = {
|
||||
track: track,
|
||||
|
||||
insert: insert,
|
||||
remove: remove,
|
||||
refresh: refresh,
|
||||
|
||||
isProperty: isProperty,
|
||||
getProperty: getProperty,
|
||||
setProperty: setProperty,
|
||||
|
||||
isArray: isArray,
|
||||
createCollection: createCollection,
|
||||
asArray: asArray,
|
||||
|
||||
map: map,
|
||||
unmap: unmap,
|
||||
|
||||
setContextProperty: $.noop
|
||||
};
|
||||
|
||||
observability.configuration = observability.jsviews;
|
||||
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
||||
|
|
@ -1,397 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, ko, upshot, undefined)
|
||||
{
|
||||
var cacheKey = "koConfig",
|
||||
stateProperty = "EntityState",
|
||||
errorProperty = "EntityError",
|
||||
updatedProperty = "IsUpdated",
|
||||
addedProperty = "IsAdded",
|
||||
deletedProperty = "IsDeleted",
|
||||
changedProperty = "IsChanged",
|
||||
canDeleteProperty = "CanDelete",
|
||||
validationErrorProperty = "ValidationError";
|
||||
|
||||
var splice = function (array, start, howmany, items) {
|
||||
array.splice.apply(array, items ? [start, howmany].concat(items) : [start, howmany]);
|
||||
};
|
||||
var copy = function (value) {
|
||||
var copy = upshot.isArray(value) ? value.slice(0) : value;
|
||||
if (value && (value[upshot.cacheName] !== undefined)) {
|
||||
// Handles in-place array edits
|
||||
copy[upshot.cacheName] = value[upshot.cacheName];
|
||||
}
|
||||
return copy;
|
||||
};
|
||||
var executeAndIgnoreChanges = function (observable, fn) {
|
||||
var tracker = upshot.cache(observable, cacheKey);
|
||||
try {
|
||||
tracker && (tracker.skip = true);
|
||||
fn();
|
||||
} finally {
|
||||
tracker && (tracker.skip = false);
|
||||
}
|
||||
}
|
||||
var punchKoMinified = function (obj, fn, _new) {
|
||||
// replaces all instance of a function with the new version
|
||||
for (var prop in obj) {
|
||||
if (obj[prop] === fn) {
|
||||
obj[prop] = _new;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Observability configuration
|
||||
function track(data, options, entityType) {
|
||||
if (!options) {
|
||||
if (upshot.isObject(data)) {
|
||||
for (var prop in data) {
|
||||
var tracker = upshot.cache(data[prop], cacheKey);
|
||||
tracker && tracker.dispose();
|
||||
upshot.deleteCache(data[prop], cacheKey);
|
||||
}
|
||||
} else if (ko.isObservable(data) && data.hasOwnProperty("push")) { // observableArray
|
||||
var tracker = upshot.cache(data[prop], cacheKey);
|
||||
tracker && tracker.dispose();
|
||||
upshot.deleteCache(data, cacheKey);
|
||||
}
|
||||
} else {
|
||||
if (upshot.isObject(data)) {
|
||||
trackObject(data, options, entityType);
|
||||
} else if (ko.isObservable(data) && data.hasOwnProperty("push")) { // observableArray
|
||||
trackArray(data, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function trackObject(data, options, entityType) {
|
||||
ko.utils.arrayForEach(upshot.metadata.getProperties(data, entityType, !!options.includeAssociations), function (property) {
|
||||
var observable = data[property.name];
|
||||
if (ko.isObservable(observable)) {
|
||||
var tracker = function () {
|
||||
var self = this,
|
||||
oldValue = null,
|
||||
createArgs = function (old, _new) {
|
||||
var oldValues = {},
|
||||
newValues = {};
|
||||
oldValues[property.name] = old;
|
||||
newValues[property.name] = _new;
|
||||
return { oldValues: oldValues, newValues: newValues };
|
||||
},
|
||||
realNotifySubscribers = observable.notifySubscribers,
|
||||
notifySubscribers = function (valueToNotify, event) {
|
||||
var before = (event === "beforeChange"),
|
||||
after = (event === undefined || event === "change");
|
||||
|
||||
if (before && options.beforeChange) {
|
||||
oldValue = copy(valueToNotify);
|
||||
}
|
||||
if (self.skip) {
|
||||
realNotifySubscribers.apply(this, arguments);
|
||||
} else {
|
||||
if (before) {
|
||||
if (options.beforeChange) {
|
||||
options.beforeChange(data, "change", createArgs(oldValue));
|
||||
}
|
||||
} else if (after && options.afterChange) {
|
||||
options.afterChange(data, "change", createArgs(oldValue, valueToNotify));
|
||||
}
|
||||
realNotifySubscribers.apply(this, arguments);
|
||||
if (after && options.afterEvent) {
|
||||
options.afterEvent(data, "change", createArgs(oldValue, valueToNotify));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.skip = false;
|
||||
this.dispose = function () {
|
||||
punchKoMinified(observable, notifySubscribers, realNotifySubscribers);
|
||||
};
|
||||
|
||||
punchKoMinified(observable, realNotifySubscribers, notifySubscribers);
|
||||
};
|
||||
upshot.cache(observable, cacheKey, new tracker());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function trackArray(data, options) {
|
||||
// Creates a tracker to raise before/after callbacks for the array
|
||||
var tracker = function (observable) {
|
||||
var self = this,
|
||||
oldValue = null,
|
||||
eventArgs = null,
|
||||
createArgs = function (old, _new) {
|
||||
var args = null,
|
||||
edits = ko.utils.compareArrays(old, _new);
|
||||
for (var i = 0, length = edits.length; i < length; i++) {
|
||||
var type = null;
|
||||
switch (edits[i].status) {
|
||||
case "added":
|
||||
type = "insert";
|
||||
break;
|
||||
case "deleted":
|
||||
type = "remove";
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
if (type !== null) {
|
||||
if (args === null) {
|
||||
args = { type: type, args: { index: i, items: [edits[i].value]} };
|
||||
} else {
|
||||
// We'll aggregate two separate edits into a single event
|
||||
args = { type: "replaceAll", args: { newItems: _new, oldItems: old} };
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return eventArgs = args;
|
||||
},
|
||||
realNotifySubscribers = observable.notifySubscribers,
|
||||
notifySubscribers = function (valueToNotify, event) {
|
||||
var before = (event === "beforeChange"),
|
||||
after = (event === undefined || event === "change");
|
||||
|
||||
if (before) {
|
||||
oldValue = copy(valueToNotify);
|
||||
}
|
||||
if (self.skip) {
|
||||
realNotifySubscribers.apply(this, arguments);
|
||||
} else {
|
||||
if (before) {
|
||||
// TODO, kylemc, What does this comment mean?
|
||||
// do nothing; there isn't a meaningful callback we can make with our data
|
||||
} else if (after && options.afterChange) {
|
||||
createArgs(oldValue, valueToNotify);
|
||||
options.afterChange(data, eventArgs.type, eventArgs.args);
|
||||
}
|
||||
realNotifySubscribers.apply(this, arguments);
|
||||
if (after && options.afterEvent) {
|
||||
options.afterEvent(data, eventArgs.type, eventArgs.args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.skip = false;
|
||||
this.dispose = function () {
|
||||
punchKoMinified(observable, notifySubscribers, realNotifySubscribers);
|
||||
};
|
||||
|
||||
punchKoMinified(observable, realNotifySubscribers, notifySubscribers);
|
||||
};
|
||||
upshot.cache(data, cacheKey, new tracker(data));
|
||||
}
|
||||
|
||||
function insert(array, index, items) {
|
||||
executeAndIgnoreChanges(array, function () {
|
||||
splice(array, index, 0, items);
|
||||
});
|
||||
}
|
||||
|
||||
function remove(array, index, numToRemove) {
|
||||
executeAndIgnoreChanges(array, function () {
|
||||
splice(array, index, numToRemove);
|
||||
});
|
||||
}
|
||||
|
||||
function refresh(array, newItems) {
|
||||
executeAndIgnoreChanges(array, function () {
|
||||
splice(array, 0, array().length, newItems);
|
||||
});
|
||||
}
|
||||
|
||||
function isProperty(item, name) {
|
||||
if (name === stateProperty || name === errorProperty) {
|
||||
return false;
|
||||
}
|
||||
var value = item[name];
|
||||
// filter out dependent observables
|
||||
if (ko.isObservable(value) && value.hasOwnProperty("getDependenciesCount")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function getProperty(item, name) {
|
||||
return ko.utils.unwrapObservable(item[name]);
|
||||
}
|
||||
|
||||
function setProperty(item, name, value) {
|
||||
executeAndIgnoreChanges(item[name], function () {
|
||||
ko.isObservable(item[name]) ? item[name](value) : item[name] = value;
|
||||
});
|
||||
}
|
||||
|
||||
function isArray(item) {
|
||||
return upshot.isArray(ko.utils.unwrapObservable(item));
|
||||
}
|
||||
|
||||
function createCollection(initialValues) {
|
||||
return ko.observableArray(initialValues || []);
|
||||
}
|
||||
|
||||
function asArray(collection) {
|
||||
return collection();
|
||||
}
|
||||
|
||||
function map(item, type, mapNested, context) {
|
||||
if (upshot.isArray(item)) {
|
||||
var array;
|
||||
if (upshot.isValueArray(item)) {
|
||||
// Primitive values don't get mapped. Avoid iteration over the potentially large array.
|
||||
// TODO: This precludes heterogeneous arrays. Should we test for primitive element type here instead?
|
||||
array = item;
|
||||
} else {
|
||||
array = ko.utils.arrayMap(item, function (value) {
|
||||
return (mapNested || map)(value, type);
|
||||
});
|
||||
}
|
||||
return ko.observableArray(array);
|
||||
} else if (upshot.isObject(item)) {
|
||||
var obj = context || {};
|
||||
// Often, server entities will not carry null-valued nullable-type properties.
|
||||
// Use getProperties here so that we'll map missing property value like these to observables.
|
||||
ko.utils.arrayForEach(upshot.metadata.getProperties(item, type, true), function (prop) {
|
||||
var value = (mapNested || map)(item[prop.name], prop.type);
|
||||
obj[prop.name] = ko.isObservable(value) ? value : ko.observable(value);
|
||||
});
|
||||
if (upshot.metadata.isEntityType(type)) {
|
||||
upshot.addEntityProperties(obj, type);
|
||||
}
|
||||
// addUpdatedProperties is applied shallowly. This allows map (which is applied deeply) to add the
|
||||
// properties at each level or for custom type mapping to decide whether the properties should be added
|
||||
upshot.addUpdatedProperties(obj, type);
|
||||
return obj;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
function unmap(item, entityType) {
|
||||
item = ko.utils.unwrapObservable(item);
|
||||
if (upshot.isArray(item)) {
|
||||
var array = ko.utils.arrayMap(item, function (value) {
|
||||
return unmap(value);
|
||||
});
|
||||
return array;
|
||||
} else if (upshot.isObject(item)) {
|
||||
var obj = {};
|
||||
ko.utils.arrayForEach(upshot.metadata.getProperties(item, entityType), function (prop) {
|
||||
// TODO: determine if there are scenarios where we want to support hasOwnProperty returning false
|
||||
if (item.hasOwnProperty(prop.name)) {
|
||||
obj[prop.name] = unmap(item[prop.name], prop.type);
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
function setContextProperty(item, kind, name, value) {
|
||||
if (kind === "entity") {
|
||||
// TODO -- do we want these 'reserved' properties to be configurable?
|
||||
var prop;
|
||||
if (name === "state") {
|
||||
// set item.EntityState
|
||||
prop = stateProperty;
|
||||
} else if (name === "error") {
|
||||
// set item.EntityError
|
||||
prop = errorProperty;
|
||||
}
|
||||
if (ko.isObservable(item[prop])) {
|
||||
setProperty(item, prop, value);
|
||||
}
|
||||
} else if (kind === "property") {
|
||||
// set item.name.IsUpdated
|
||||
if (ko.isObservable(item[name])) {
|
||||
var observable = item[name][updatedProperty];
|
||||
observable && observable(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var observability = upshot.defineNamespace("upshot.observability");
|
||||
|
||||
observability.knockout = {
|
||||
track: track,
|
||||
|
||||
insert: insert,
|
||||
remove: remove,
|
||||
refresh: refresh,
|
||||
|
||||
isProperty: isProperty,
|
||||
getProperty: getProperty,
|
||||
setProperty: setProperty,
|
||||
|
||||
isArray: isArray,
|
||||
createCollection: createCollection,
|
||||
asArray: asArray,
|
||||
|
||||
map: map,
|
||||
unmap: unmap,
|
||||
|
||||
setContextProperty: setContextProperty
|
||||
};
|
||||
|
||||
observability.configuration = observability.knockout;
|
||||
|
||||
// KO entity extensions
|
||||
function addValidationErrorProperty(entity, prop) {
|
||||
var observable = entity[prop];
|
||||
if (observable) { // Custom mappings might not include this property.
|
||||
observable[validationErrorProperty] = ko.computed(function () {
|
||||
var allErrors = entity[errorProperty]();
|
||||
if (allErrors && allErrors.ValidationErrors) {
|
||||
var matchingError = ko.utils.arrayFirst(allErrors.ValidationErrors, function (error) {
|
||||
return ko.utils.arrayIndexOf(error.SourceMemberNames, prop) >= 0;
|
||||
});
|
||||
if (matchingError) {
|
||||
return matchingError.Message;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
upshot.addEntityProperties = function (entity, entityType) {
|
||||
// Adds properties to an entity that will be managed by upshot
|
||||
entity[stateProperty] = ko.observable(upshot.EntityState.Unmodified);
|
||||
entity[errorProperty] = ko.observable();
|
||||
|
||||
// Minimize view boilerplate by exposing state flags
|
||||
var containsState = function (states) {
|
||||
return ko.utils.arrayIndexOf(states, entity[stateProperty]()) !== -1;
|
||||
};
|
||||
var es = upshot.EntityState;
|
||||
entity[updatedProperty] = ko.computed(function () {
|
||||
return containsState([es.ClientUpdated, es.ServerUpdating]);
|
||||
});
|
||||
entity[addedProperty] = ko.computed(function () {
|
||||
return containsState([es.ClientAdded, es.ServerAdding]);
|
||||
});
|
||||
entity[deletedProperty] = ko.computed(function () {
|
||||
return containsState([es.ClientDeleted, es.ServerDeleting, es.Deleted]);
|
||||
});
|
||||
entity[changedProperty] = ko.computed(function () {
|
||||
return !containsState([es.Unmodified, es.Deleted]);
|
||||
});
|
||||
entity[canDeleteProperty] = ko.computed(function () {
|
||||
return !(entity[addedProperty]() || entity[deletedProperty]());
|
||||
});
|
||||
|
||||
// TODO -- these are only applied a single level deep; see if there's a consistent way to apply CTs
|
||||
ko.utils.arrayForEach(upshot.metadata.getProperties(entity, entityType, true), function (prop) {
|
||||
addValidationErrorProperty(entity, prop.name);
|
||||
});
|
||||
}
|
||||
|
||||
upshot.addUpdatedProperties = function (obj, type) {
|
||||
ko.utils.arrayForEach(upshot.metadata.getProperties(obj, type, false), function (prop) {
|
||||
var observable = obj[prop.name];
|
||||
if (ko.isObservable(observable)) {
|
||||
observable[updatedProperty] = ko.observable(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
///#RESTORE )(this, ko, upshot);
|
|
@ -1,495 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, WinJS, upshot, undefined)
|
||||
{
|
||||
// Customizations to WinJS observability to interop with Upshot.
|
||||
|
||||
var ObservableProxy = WinJS.Binding.as({}).constructor;
|
||||
var base = ObservableProxy.prototype;
|
||||
var RiaObservableProxy = WinJS.Class.derive(base, function (data, beforeChange, afterChange, afterEvent) {
|
||||
base.constructor.call(this, data);
|
||||
this._beforeChange = beforeChange;
|
||||
this._afterChange = afterChange;
|
||||
this._afterEvent = afterEvent;
|
||||
}, {
|
||||
updateProperty: function (name, value) {
|
||||
var oldValue = this._backingData[name];
|
||||
var newValue = WinJS.Binding.unwrap(value);
|
||||
if (oldValue !== newValue) {
|
||||
var eventArguments = { path: name, value: value };
|
||||
|
||||
if (this._beforeChange) {
|
||||
this._beforeChange(this._backingData, "propertyChange", eventArguments);
|
||||
}
|
||||
|
||||
this._backingData[name] = newValue;
|
||||
|
||||
if (this._afterChange) {
|
||||
this._afterChange(this._backingData, "propertyChange", eventArguments);
|
||||
}
|
||||
|
||||
var notifyPromise = this._notifyListeners(name, newValue, oldValue),
|
||||
afterEvent = this._afterEvent;
|
||||
if (afterEvent) {
|
||||
return notifyPromise.then(function () {
|
||||
afterEvent(this._backingData, "propertyChange", eventArguments);
|
||||
}); // TODO: Implement "cancel" too, as that's what we'll get when events are coalesced.
|
||||
} else {
|
||||
return notifyPromise;
|
||||
}
|
||||
}
|
||||
return WinJS.Promise.as();
|
||||
}
|
||||
});
|
||||
|
||||
function track(data, options) {
|
||||
if (!options) {
|
||||
delete data._getObservable;
|
||||
} else {
|
||||
if ($.isArray(data)) {
|
||||
// TODO: Integrate WinJS array observability when it exists. We assume jQuery observability for arrays until then.
|
||||
$.observable.track(data, {
|
||||
beforeChange: options.beforeChange,
|
||||
afterChange: options.afterChange,
|
||||
afterEvent: options.afterEvent
|
||||
});
|
||||
} else {
|
||||
if ($.inArray("_getObservable", Object.getOwnPropertyNames(data)) >= 0) {
|
||||
throw "Entities added via Upshot/JS must not previously have been treated with WinJS.Binding.as";
|
||||
}
|
||||
|
||||
var observable = new RiaObservableProxy(data, options.beforeChange, options.afterChange, options.afterEvent);
|
||||
observable.backingData = data;
|
||||
Object.defineProperty(data, "_getObservable", {
|
||||
value: function () { return observable; },
|
||||
enumerable: false,
|
||||
writable: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Integrate WinJS array observability when it exists. We assume jQuery observability for arrays until then.
|
||||
function createInsertDeferredEvent(array, index, items) {
|
||||
return function () {
|
||||
var eventArguments = {
|
||||
change: "insert",
|
||||
index: index,
|
||||
items: items
|
||||
};
|
||||
$([array]).triggerHandler("arrayChange", eventArguments);
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Integrate WinJS array observability when it exists. We assume jQuery observability for arrays until then.
|
||||
function createRemoveDeferredEvent(array, index, itemsRemoved) {
|
||||
return function () {
|
||||
var eventArguments = {
|
||||
change: "remove",
|
||||
index: index,
|
||||
items: itemsRemoved
|
||||
};
|
||||
$([array]).triggerHandler("arrayChange", eventArguments);
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Integrate WinJS array observability when it exists. We assume jQuery observability for arrays until then.
|
||||
function createRefreshDeferredEvent(array, oldItems, newItems) {
|
||||
return function () {
|
||||
var eventArguments = {
|
||||
change: "refresh",
|
||||
oldItems: oldItems,
|
||||
newItems: newItems
|
||||
};
|
||||
$([array]).triggerHandler("arrayChange", eventArguments);
|
||||
};
|
||||
}
|
||||
|
||||
function createSetPropertyDeferredEvent(item, name, oldValue, newValue) {
|
||||
var observable = WinJS.Binding.as(item);
|
||||
return function () {
|
||||
observable._notifyListeners(name, newValue, oldValue);
|
||||
};
|
||||
}
|
||||
|
||||
var observability = upshot.defineNamespace("upshot.observability");
|
||||
|
||||
observability.winjs = {
|
||||
track: track,
|
||||
|
||||
createInsertDeferredEvent: createInsertDeferredEvent,
|
||||
createRemoveDeferredEvent: createRemoveDeferredEvent,
|
||||
createRefreshDeferredEvent: createRefreshDeferredEvent,
|
||||
createSetPropertyDeferredEvent: createSetPropertyDeferredEvent
|
||||
};
|
||||
|
||||
observability.configuration = observability.winjs;
|
||||
|
||||
// WinJS DataSource support follows.
|
||||
|
||||
var DataAdaptor = WinJS.Class.define(function (riaDataSource) {
|
||||
var dataSource;
|
||||
if (riaDataSource.applyLocalQuery) {
|
||||
dataSource = $.dataSource.unwrapHack(riaDataSource.getEntities());
|
||||
} else {
|
||||
dataSource = riaDataSource;
|
||||
}
|
||||
|
||||
this._dataSource = dataSource;
|
||||
this._dataSourceObserver = null;
|
||||
this._arrayChangeHandler = null;
|
||||
|
||||
this.compareByIdentity = true;
|
||||
}, {
|
||||
dispose: function () {
|
||||
if (this._dataSource) {
|
||||
this._dataSource.removeObserver(this._dataSourceObserver);
|
||||
$([this._dataSource.getEntities()]).unbind("arrayChange", this._arrayChangeHandler);
|
||||
this._dataSource = null;
|
||||
}
|
||||
},
|
||||
|
||||
setNotificationHandler: function (notificationHandler) {
|
||||
var that = this;
|
||||
|
||||
function handleArrayChange(event, eventArgs) {
|
||||
var index;
|
||||
notificationHandler.beginNotifications();
|
||||
switch (eventArgs.change) {
|
||||
case "refresh":
|
||||
notificationHandler.invalidateAll();
|
||||
break;
|
||||
|
||||
case "insert":
|
||||
var entities = that._dataSource.getEntities(),
|
||||
previousItem = index === 0 ? null : entities[index - 1],
|
||||
nextItem = index === entities.length - 1 ? null : entities[index + 1];
|
||||
|
||||
index = eventArgs.index;
|
||||
$.each(eventArgs.items, function () {
|
||||
var item = { key: that._getEntityKey(this), data: this };
|
||||
notificationHandler.inserted(item, previousItem && that._getEntityKey(previousItem), nextItem && that._getEntityKey(nextItem), index);
|
||||
previousItem = this;
|
||||
index++;
|
||||
});
|
||||
break;
|
||||
|
||||
case "remove":
|
||||
index = eventArgs.index;
|
||||
$.each(eventArgs.items, function () {
|
||||
notificationHandler.removed(that._getEntityKey(this), index);
|
||||
});
|
||||
break;
|
||||
|
||||
case "move": // TODO: Our data sources never issue move events presently, but this could still be implemented.
|
||||
default:
|
||||
throw "Unexpected array changed event.";
|
||||
}
|
||||
notificationHandler.endNotifications();
|
||||
}
|
||||
|
||||
this._dataSourceObserver = {
|
||||
propertyChanged: function (entity, property, value) {
|
||||
notificationHandler.changed({ key: that._getEntityKey(entity), data: entity });
|
||||
}
|
||||
};
|
||||
this._dataSource.addObserver(this._dataSourceObserver);
|
||||
|
||||
// TODO: We should have a platform-neutral way of communicating array changes, so we
|
||||
// don't have a hard dependency on a single observability pattern.
|
||||
this._arrayChangeHandler = handleArrayChange;
|
||||
$([this._dataSource.getEntities()]).bind("arrayChange", handleArrayChange);
|
||||
},
|
||||
|
||||
// compareByIdentity: set in constructor
|
||||
// itemsFromStart: not implemented
|
||||
// itemsFromEnd: not implemented
|
||||
// itemsFromKey: not implemented
|
||||
|
||||
itemsFromIndex: function (index, countBefore, countAfter) {
|
||||
var skip = index - countBefore,
|
||||
take = countBefore + 1 + countAfter,
|
||||
allEntities = this._dataSource.getEntities(),
|
||||
entities = allEntities.slice(skip, skip + take),
|
||||
that = this;
|
||||
return WinJS.Promise.wrap({
|
||||
items: $.map(entities, function (entity) {
|
||||
return { key: that._getEntityKey(entity), data: entity };
|
||||
}),
|
||||
offset: countBefore,
|
||||
totalCount: allEntities.length,
|
||||
absoluteIndex: index
|
||||
// TODO: atEnd?
|
||||
});
|
||||
},
|
||||
|
||||
// itemsFromDescription: not implemented
|
||||
|
||||
getCount: function () {
|
||||
return WinJS.Promise.wrap(this._dataSource.getEntities().length);
|
||||
},
|
||||
|
||||
// NOTE: We don't implement these, as there is an edit model inherent in the
|
||||
// Upshot data source passed into this adaptor. This adaptor component becomes much
|
||||
// more complex if we allow for editing over both WinJS's and Upshot's data sources.
|
||||
//
|
||||
// insertAtStart: function (key, data) {
|
||||
// insertBefore: function (key, data, nextKey, nextIndexHint) {
|
||||
// insertAfter: function (key, data, previousKey, previousIndexHint) {
|
||||
// insertAtEnd: function (key, data) {
|
||||
// change: function (key, newData, indexHint) {
|
||||
// moveToStart: not implemented
|
||||
// moveBefore: not implemented
|
||||
// moveAfter: not implemented
|
||||
// moveToEnd: not implemented
|
||||
// remove: not implemented
|
||||
|
||||
_getEntityKey: function (entity) {
|
||||
return this._dataSource.getEntityId(entity);
|
||||
}
|
||||
});
|
||||
|
||||
var VirtualizingDataAdaptor = WinJS.Class.define(function (options) {
|
||||
|
||||
this._dataContext = options.dataContext;
|
||||
if (!this._dataContext) {
|
||||
var implicitCommitHandler;
|
||||
if (!options.bufferChanges) {
|
||||
// since we're not change tracking, define an implicit commit callback
|
||||
// and pass into the DC
|
||||
var self = this;
|
||||
implicitCommitHandler = function () {
|
||||
self._dataContext._commitChanges({ providerParameters: self._providerParameters });
|
||||
}
|
||||
}
|
||||
this._dataContext = new upshot.DataContext(new upshot.riaDataProvider(), implicitCommitHandler);
|
||||
}
|
||||
this._providerParameters = options.providerParameters;
|
||||
this._entityType = options.entityType;
|
||||
|
||||
this._lastTotalCount = null;
|
||||
this._notificationHandler = null;
|
||||
this._entitySet = null;
|
||||
this._entitySetObserver = null;
|
||||
this._arrayChangeHandler = null;
|
||||
|
||||
this._sort = null;
|
||||
this._filters = null;
|
||||
|
||||
// TODO: Ick!
|
||||
this._setFilter = upshot.RemoteDataSource.prototype.setFilter;
|
||||
this._processFilter = upshot.DataSource.prototype._processFilter;
|
||||
|
||||
this.compareByIdentity = true; // TODO: How do this control list rerender vs. div surgery?
|
||||
}, {
|
||||
entitySet: {
|
||||
get: function () {
|
||||
return this._entitySet;
|
||||
}
|
||||
},
|
||||
|
||||
filter: {
|
||||
set: function (filter) {
|
||||
this._setFilter(filter);
|
||||
}
|
||||
},
|
||||
|
||||
sort: {
|
||||
set: function (sort) {
|
||||
this._sort = sort;
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function () {
|
||||
this._notificationHandler.invalidateAll();
|
||||
},
|
||||
|
||||
dispose: function () {
|
||||
if (this._entitySet) {
|
||||
this._entitySet.removeObserver(this._entitySetObserver);
|
||||
$([this._entitySet.getEntities()]).unbind("arrayChange", this._arrayChangeHandler);
|
||||
this._entitySet = null;
|
||||
}
|
||||
},
|
||||
|
||||
setNotificationHandler: function (notificationHandler) {
|
||||
this._notificationHandler = notificationHandler;
|
||||
},
|
||||
|
||||
// compareByIdentity: set in constructor
|
||||
// itemsFromStart: not implemented
|
||||
// itemsFromEnd: not implemented
|
||||
// itemsFromKey: not implemented
|
||||
|
||||
itemsFromIndex: function (index, countBefore, countAfter) {
|
||||
var that = this;
|
||||
return new WinJS.Promise(function (complete, error) {
|
||||
var skip = index - countBefore,
|
||||
take = countBefore + 1 + countAfter,
|
||||
success = function (entities) {
|
||||
var result = {
|
||||
items: $.map(entities, function (entity) {
|
||||
return { key: that._getEntityKey(entity), data: entity };
|
||||
}),
|
||||
offset: countBefore,
|
||||
totalCount: that._lastTotalCount,
|
||||
absoluteIndex: index
|
||||
// TODO: atEnd?
|
||||
};
|
||||
complete(result);
|
||||
};
|
||||
that._loadEntities(skip, take, success);
|
||||
});
|
||||
},
|
||||
|
||||
// itemsFromDescription: not implemented
|
||||
|
||||
getCount: function () {
|
||||
if (this._lastTotalCount !== null) {
|
||||
return WinJS.Promise.wrap(this._lastTotalCount);
|
||||
} else {
|
||||
var that = this;
|
||||
return new WinJS.Promise(function (complete, error) {
|
||||
// Ask for zero entities here. We merely want the total count from the server.
|
||||
that._loadEntities(null, 0, function () {
|
||||
complete(that._lastTotalCount);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// insertAtStart: function (key, data) {
|
||||
// insertBefore: function (key, data, nextKey, nextIndexHint) {
|
||||
// insertAfter: function (key, data, previousKey, previousIndexHint) {
|
||||
// insertAtEnd: function (key, data) {
|
||||
// change: function (key, newData, indexHint) {
|
||||
// moveToStart: not implemented
|
||||
// moveBefore: not implemented
|
||||
// moveAfter: not implemented
|
||||
// moveToEnd: not implemented
|
||||
// remove: not implemented
|
||||
|
||||
_loadEntities: function (skip, take, success, fail) {
|
||||
var that = this,
|
||||
loadSucceeded = function (entitySet, entities, totalCount) {
|
||||
that._bindToEntitySet(entitySet);
|
||||
that._lastTotalCount = totalCount; // TODO: Is there a way to event to the ListDataSource that the count has changed? Is it invalidateAll?
|
||||
if (success) {
|
||||
success.call(that, entities);
|
||||
}
|
||||
},
|
||||
loadFailed = function (statusText, error) {
|
||||
if (fail) {
|
||||
fail.call(that, statusText, error);
|
||||
}
|
||||
};
|
||||
|
||||
this._dataContext.__load({
|
||||
providerParameters: this._providerParameters,
|
||||
entityType: this._entityType,
|
||||
|
||||
queryParameters: {
|
||||
filters: this._filters,
|
||||
sort: this._sort,
|
||||
skip: skip,
|
||||
take: take,
|
||||
includeTotalCount: true
|
||||
}
|
||||
}, loadSucceeded, loadFailed);
|
||||
},
|
||||
|
||||
_bindToEntitySet: function (entitySet) {
|
||||
if (this._entitySet) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._entitySet = entitySet;
|
||||
|
||||
var that = this;
|
||||
|
||||
function handleArrayChange(event, eventArgs) {
|
||||
that._notificationHandler.beginNotifications();
|
||||
switch (eventArgs.change) {
|
||||
case "insert":
|
||||
// Such inserts are from other queries returning entities of this type
|
||||
// or they are internal inserts that might not even be committed yet.
|
||||
// The app should explicitly refresh their ListView here.
|
||||
break;
|
||||
|
||||
case "remove":
|
||||
$.each(eventArgs.items, function () {
|
||||
that._notificationHandler.removed(that._getEntityKey(this));
|
||||
});
|
||||
break;
|
||||
|
||||
case "move": // TODO: Our entity sets don't issue move events presently.
|
||||
case "refresh": // TODO: Our entity sets don't issue refresh events presently.
|
||||
default:
|
||||
throw "Unexpected array changed event.";
|
||||
break;
|
||||
}
|
||||
that._notificationHandler.endNotifications();
|
||||
}
|
||||
|
||||
this._entitySetObserver = {
|
||||
propertyChanged: function (entity, property, value) {
|
||||
that._notificationHandler.changed({ key: that._getEntityKey(entity), data: entity });
|
||||
}
|
||||
};
|
||||
entitySet.addObserver(this._entitySetObserver);
|
||||
|
||||
// TODO: We should have a platform-neutral way of communicating array changes, so we
|
||||
// don't have a hard dependency on a single observability pattern.
|
||||
this._arrayChangeHandler = handleArrayChange;
|
||||
$([entitySet.getEntities()]).bind("arrayChange", handleArrayChange);
|
||||
},
|
||||
|
||||
_getEntityKey: function (entity) {
|
||||
return this._entitySet.getEntityId(entity);
|
||||
}
|
||||
|
||||
// TODO: Investigate why the ListView stops loading if you drag to the right too aggressive.
|
||||
});
|
||||
|
||||
WinJS.Namespace.define("upshot.WinJS", {
|
||||
DataSource: function (riaDataSource) {
|
||||
var dataAdaptor = new DataAdaptor(riaDataSource),
|
||||
dataSource = new WinJS.UI.ListDataSource(dataAdaptor);
|
||||
|
||||
dataSource.dispose = function () {
|
||||
dataAdaptor.dispose();
|
||||
};
|
||||
|
||||
return dataSource;
|
||||
},
|
||||
VirtualizingDataSource: function (options) {
|
||||
var dataAdaptor = new VirtualizingDataAdaptor(options),
|
||||
dataSource = new WinJS.UI.ListDataSource(dataAdaptor);
|
||||
|
||||
Object.defineProperty(dataSource, "filter", {
|
||||
set: function (filter) {
|
||||
dataAdaptor.filter = filter;
|
||||
}
|
||||
});
|
||||
Object.defineProperty(dataSource, "sort", {
|
||||
set: function (sort) {
|
||||
dataAdaptor.sort = sort;
|
||||
}
|
||||
});
|
||||
Object.defineProperty(dataSource, "entitySet", {
|
||||
get: function () {
|
||||
return dataAdaptor.entitySet;
|
||||
}
|
||||
});
|
||||
dataSource.refresh = function () {
|
||||
dataAdaptor.refresh();
|
||||
};
|
||||
dataSource.dispose = function () {
|
||||
dataAdaptor.dispose();
|
||||
};
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
///#RESTORE )(this, jQuery, WinJS, upshot);
|
|
@ -1,175 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
|
||||
var cachedObservableKey = "__cachedObservable__",
|
||||
observable = $.observable.Observable;
|
||||
|
||||
$.observable = function (data) {
|
||||
return upshot.cache(data, cachedObservableKey) || new observable(data);
|
||||
};
|
||||
|
||||
var base = observable.prototype;
|
||||
|
||||
var UpshotObservable = upshot.deriveClass(base, function (data, beforeChange, afterChange, afterEvent) {
|
||||
observable.call(this, data);
|
||||
this.beforeChange = beforeChange;
|
||||
this.afterChange = afterChange;
|
||||
this.afterEvent = afterEvent;
|
||||
}, {
|
||||
_property: function (oldValues, newValues) {
|
||||
if (this.beforeChange) {
|
||||
this.beforeChange(this.data, "change", { oldValues: oldValues, newValues: newValues });
|
||||
}
|
||||
|
||||
return base._property.apply(this, arguments);
|
||||
},
|
||||
|
||||
_insert: function (index, items) {
|
||||
if (this.beforeChange) {
|
||||
this.beforeChange(this.data, "insert", { index: index, items: items });
|
||||
}
|
||||
|
||||
base._insert.apply(this, arguments);
|
||||
},
|
||||
|
||||
_remove: function (index, numToRemove) {
|
||||
if (this.beforeChange) {
|
||||
var items = this.data.slice(index, index + numToRemove);
|
||||
this.beforeChange(this.data, "remove", { index: index, items: items });
|
||||
}
|
||||
|
||||
base._remove.apply(this, arguments);
|
||||
},
|
||||
|
||||
replaceAll: function (newItems) {
|
||||
if (this.beforeChange) {
|
||||
this.beforeChange(this.data, "replaceAll", { oldItems: this.data.slice(0), newItems: newItems });
|
||||
}
|
||||
|
||||
base.replaceAll.apply(this, arguments);
|
||||
},
|
||||
|
||||
_trigger: function (type, eventData) {
|
||||
if (this.afterChange) {
|
||||
this.afterChange(this.data, type, eventData);
|
||||
}
|
||||
|
||||
base._trigger.apply(this, arguments);
|
||||
|
||||
if (this.afterEvent) {
|
||||
this.afterEvent(this.data, type, eventData);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// TODO, kylemc, Knockout compatibility uses an "entityType" parameter here and tracks only metadata-specified properties.
|
||||
function track(data, options) {
|
||||
if (!options) {
|
||||
upshot.deleteCache(data, cachedObservableKey);
|
||||
} else {
|
||||
upshot.cache(data, cachedObservableKey, new UpshotObservable(data, options.beforeChange, options.afterChange, options.afterEvent));
|
||||
}
|
||||
}
|
||||
|
||||
function insert(array, index, items) {
|
||||
array.splice.apply(array, [index, 0].concat(items));
|
||||
var eventArguments = {
|
||||
index: index,
|
||||
items: items
|
||||
};
|
||||
$([array]).triggerHandler("insert", eventArguments);
|
||||
}
|
||||
|
||||
function remove(array, index, numToRemove) {
|
||||
var itemsRemoved = array.slice(index, index + numToRemove);
|
||||
array.splice(index, numToRemove);
|
||||
var eventArguments = {
|
||||
index: index,
|
||||
items: itemsRemoved
|
||||
};
|
||||
$([array]).triggerHandler("remove", eventArguments);
|
||||
}
|
||||
|
||||
function refresh(array, newItems) {
|
||||
var oldItems = array.slice(0);
|
||||
array.splice.apply(array, [0, array.length].concat(newItems));
|
||||
var eventArguments = {
|
||||
oldItems: oldItems,
|
||||
newItems: newItems
|
||||
};
|
||||
$([array]).triggerHandler("replaceAll", eventArguments);
|
||||
}
|
||||
|
||||
function isProperty(item, name) {
|
||||
return !$.isFunction(item[name]);
|
||||
}
|
||||
|
||||
function getProperty(item, name) {
|
||||
return item[name];
|
||||
}
|
||||
|
||||
function setProperty(item, name, value) {
|
||||
var oldValue = item[name];
|
||||
item[name] = value;
|
||||
|
||||
var oldValues = {},
|
||||
newValues = {};
|
||||
oldValues[name] = oldValue;
|
||||
newValues[name] = value;
|
||||
var eventArguments = {
|
||||
oldValues: oldValues,
|
||||
newValues: newValues
|
||||
};
|
||||
$(item).triggerHandler("change", eventArguments);
|
||||
}
|
||||
|
||||
function isArray(item) {
|
||||
return upshot.isArray(item);
|
||||
}
|
||||
|
||||
function createCollection(initialValues) {
|
||||
return initialValues || [];
|
||||
}
|
||||
|
||||
function asArray(collection) {
|
||||
return collection;
|
||||
}
|
||||
|
||||
function map(item) {
|
||||
return item;
|
||||
}
|
||||
|
||||
function unmap(item) {
|
||||
return item;
|
||||
}
|
||||
|
||||
var observability = upshot.defineNamespace("upshot.observability");
|
||||
|
||||
observability.jquery = {
|
||||
track: track,
|
||||
|
||||
insert: insert,
|
||||
remove: remove,
|
||||
refresh: refresh,
|
||||
|
||||
isProperty: isProperty,
|
||||
getProperty: getProperty,
|
||||
setProperty: setProperty,
|
||||
|
||||
isArray: isArray,
|
||||
createCollection: createCollection,
|
||||
asArray: asArray,
|
||||
|
||||
map: map,
|
||||
unmap: unmap,
|
||||
|
||||
setContextProperty: $.noop
|
||||
};
|
||||
|
||||
observability.configuration = observability.jquery;
|
||||
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
/// <reference path="IntelliSense\References.js" />
|
||||
///#RESTORE (function (global, $, upshot, undefined)
|
||||
{
|
||||
var dataSourceEvents = ["refreshStart", "refreshSuccess", "refreshError", "commitStart", "commitSuccess", "commitError", "entityStateChanged", "refreshNeeded"];
|
||||
|
||||
function normalizeSort(sort) {
|
||||
if (!sort || $.isFunction(sort)) {
|
||||
return sort;
|
||||
}
|
||||
|
||||
if (upshot.isArray(sort)) {
|
||||
return $.map(sort, function (item) {
|
||||
var descending = item.charAt(0) === "-";
|
||||
var property = descending ? item.substr(1) : item;
|
||||
return { property: property, descending: descending };
|
||||
});
|
||||
}
|
||||
|
||||
var descending = sort.charAt(0) === "-";
|
||||
var property = descending ? sort.substr(1) : sort;
|
||||
return { property: property, descending: descending };
|
||||
}
|
||||
|
||||
function normalizeFilter(filter) {
|
||||
if (!filter) {
|
||||
return filter;
|
||||
}
|
||||
|
||||
filter = upshot.isArray(filter) ? filter : [filter];
|
||||
var filters = [];
|
||||
for (var i = 0; i < filter.length; i++) {
|
||||
var filterPart = filter[i];
|
||||
if (!$.isFunction(filterPart)) {
|
||||
for (var filterProperty in filterPart) {
|
||||
if (filterPart.hasOwnProperty(filterProperty)) {
|
||||
var filterValue = filterPart[filterProperty];
|
||||
if (filterValue && filterValue.hasOwnProperty("value")) {
|
||||
filters.push({ property: filterProperty, operator: filterValue.operator, value: filterValue.value });
|
||||
} else {
|
||||
filters.push({ property: filterProperty, value: filterValue });
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filters.push(filterPart);
|
||||
}
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
function normalizePaging(paging) {
|
||||
return paging && { skip: paging.offset, take: paging.limit, includeTotalCount: !!paging.includeTotalCount };
|
||||
}
|
||||
|
||||
var queryOptions = {
|
||||
paging: { setter: "setPaging", normalize: normalizePaging },
|
||||
filter: { setter: "setFilter", normalize: normalizeFilter },
|
||||
sort: { setter: "setSort", normalize: normalizeSort }
|
||||
};
|
||||
|
||||
$.widget("upshot.dataview", $.ui.dataview, {
|
||||
|
||||
_create: function () {
|
||||
var that = this;
|
||||
this.widgetEventPrefix = "dataview";
|
||||
this.options.source = function (request, success, error) {
|
||||
that.dataSource.refresh(request.refreshOptions, success, error);
|
||||
};
|
||||
},
|
||||
|
||||
_init: function () {
|
||||
this._super("_init");
|
||||
this.dataSource = this._createDataSource();
|
||||
this.result = this.dataSource.getEntities();
|
||||
|
||||
var that = this;
|
||||
var observer = {};
|
||||
$.each(dataSourceEvents, function (unused, name) {
|
||||
observer[name] = function () {
|
||||
$(that).trigger(name, arguments);
|
||||
};
|
||||
});
|
||||
this._observer = observer;
|
||||
this.dataSource.bind(observer);
|
||||
|
||||
var slice = Array.prototype.slice;
|
||||
$.each(["commitChanges", "revertUpdates", "revertChanges"], function (unused, key) {
|
||||
if (that.dataSource[key]) {
|
||||
that[key] = function () {
|
||||
return that.dataSource[key].apply(that.dataSource, $.map(slice.call(arguments), function (arg) {
|
||||
return $.proxy(arg, that) || arg;
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_destroy: function () {
|
||||
if (this._observer) {
|
||||
this.dataSource.unbind(this._observer);
|
||||
this._observer = null;
|
||||
}
|
||||
this._super("_destroy");
|
||||
},
|
||||
|
||||
_setOption: function (key, value) {
|
||||
var query = queryOptions[key];
|
||||
if (query) {
|
||||
this.dataSource[query.setter](query.normalize(value));
|
||||
} else {
|
||||
this.dataSource.option(key, value && $.inArray(key, dataSourceEvents) >= 0 ? $.proxy(value, this) : value);
|
||||
}
|
||||
this._super("_setOption", key, value);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
$.widget("upshot.remoteDataview", $.upshot.dataview, {
|
||||
|
||||
_createDataSource: function () {
|
||||
var options = {};
|
||||
for (var key in this.options) {
|
||||
var query = queryOptions[key];
|
||||
if (query) {
|
||||
options[key] = query.normalize(this.options[key]);
|
||||
} else if (key !== "source") {
|
||||
options[key] = $.inArray(key, dataSourceEvents) >= 0 ? $.proxy(this.options[key], this) : this.options[key];
|
||||
}
|
||||
}
|
||||
options.result = options.result || this.result;
|
||||
return new upshot.RemoteDataSource(options);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
$.widget("upshot.localDataview", $.upshot.dataview, {
|
||||
|
||||
_createDataSource: function () {
|
||||
var options = {};
|
||||
for (var key in this.options) {
|
||||
var query = queryOptions[key];
|
||||
if (query) {
|
||||
options[key] = query.normalize(this.options[key]);
|
||||
} else if (key === "input") {
|
||||
if ($.isArray(this.options.input)) {
|
||||
options.source = this.options.input;
|
||||
} else {
|
||||
options.source = this.options.input.dataSource;
|
||||
}
|
||||
} else if (key !== "source") {
|
||||
options[key] = $.inArray(key, dataSourceEvents) >= 0 ? $.proxy(this.options[key], this) : this.options[key];
|
||||
}
|
||||
}
|
||||
options.result = options.result || this.result;
|
||||
return new upshot.LocalDataSource(options);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
///#RESTORE )(this, jQuery, upshot);
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>9.0.30729</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{F6C0671C-B832-4807-BA9A-9206BD35A650}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Microsoft.Web.Http.Data.Helpers.Test</RootNamespace>
|
||||
<AssemblyName>Microsoft.Web.Http.Data.Helpers.Test</AssemblyName>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\..\bin\Debug\Test\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG;CODE_ANALYSIS</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\..\bin\Release\Test\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeAnalysis|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\..\bin\CodeAnalysis\Test\</OutputPath>
|
||||
<DefineConstants>TRACE;CODE_ANALYSIS</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="EntityFramework">
|
||||
<HintPath>..\..\packages\EntityFramework.5.0.0\lib\net40\EntityFramework.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Moq">
|
||||
<HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.4.5.6\lib\net40\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.Core">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Data.Entity" />
|
||||
<Reference Include="System.Net.Http, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http.WebRequest, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Web.Routing" />
|
||||
<Reference Include="System.XML" />
|
||||
<Reference Include="xunit">
|
||||
<HintPath>..\..\packages\xunit.1.9.1\lib\net20\xunit.dll</HintPath>
|
||||
<Aliases>unused</Aliases>
|
||||
</Reference>
|
||||
<Reference Include="xunit.extensions">
|
||||
<HintPath>..\..\packages\xunit.extensions.1.9.1\lib\net20\xunit.extensions.dll</HintPath>
|
||||
<Aliases>unused</Aliases>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
|
||||
<Visible>False</Visible>
|
||||
</CodeAnalysisDependentAssemblyPaths>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Microsoft.Web.Http.Data.Test\Controllers\CatalogController.cs">
|
||||
<Link>Controllers\CatalogController.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.Web.Http.Data.Test\Controllers\NorthwindEFController.cs">
|
||||
<Link>Controllers\NorthwindEFController.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.Web.Http.Data.Test\Models\CatalogEntities.cs">
|
||||
<Link>Models\CatalogEntities.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.Web.Http.Data.Test\Models\Cities.cs">
|
||||
<Link>Models\Cities.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.Web.Http.Data.Test\TestHelpers.cs">
|
||||
<Link>TestHelpers.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="UpshotExtensionsTests.cs" />
|
||||
<Compile Include="Models\Northwind.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Northwind.edmx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\System.Net.Http.Formatting\System.Net.Http.Formatting.csproj">
|
||||
<Project>{668E9021-CE84-49D9-98FB-DF125A9FCDB0}</Project>
|
||||
<Name>System.Net.Http.Formatting</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Web.Http.Data.Helpers\Microsoft.Web.Http.Data.Helpers.csproj">
|
||||
<Project>{B6895A1B-382F-4A69-99EC-E965E19B0AB3}</Project>
|
||||
<Name>Microsoft.Web.Http.Data.Helpers</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\System.Web.Http\System.Web.Http.csproj">
|
||||
<Project>{DDC1CE0C-486E-4E35-BB3B-EAB61F8F9440}</Project>
|
||||
<Name>System.Web.Http</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Web.Http.Data.EntityFramework\Microsoft.Web.Http.Data.EntityFramework.csproj">
|
||||
<Project>{653F3946-541C-42D3-BBC1-CE89B392BDA9}</Project>
|
||||
<Name>Microsoft.Web.Http.Data.EntityFramework</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Web.Http.Data\Microsoft.Web.Http.Data.csproj">
|
||||
<Project>{ACE91549-D86E-4EB6-8C2A-5FF51386BB68}</Project>
|
||||
<Name>Microsoft.Web.Http.Data</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Web.Http.Data.Helpers\Microsoft.Web.Http.Data.Helpers.csproj">
|
||||
<Project>{B6895A1B-382F-4A69-99EC-E965E19B0AB3}</Project>
|
||||
<Name>Microsoft.Web.Http.Data.Helpers</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\System.Web.Mvc\System.Web.Mvc.csproj">
|
||||
<Project>{3D3FFD8A-624D-4E9B-954B-E1C105507975}</Project>
|
||||
<Name>System.Web.Mvc</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
|
||||
<Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
|
||||
<Name>Microsoft.TestCommon</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\System.Web.Mvc.Test\System.Web.Mvc.Test.csproj">
|
||||
<Project>{8AC2A2E4-2F11-4D40-A887-62E2583A65E6}</Project>
|
||||
<Name>System.Web.Mvc.Test</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EntityDeploy Include="Models\Northwind.edmx">
|
||||
<Generator>EntityModelCodeGenerator</Generator>
|
||||
<LastGenOutput>Northwind.Designer.cs</LastGenOutput>
|
||||
<CustomToolNamespace>System.Web.Http.Data.Test.Models.EF</CustomToolNamespace>
|
||||
</EntityDeploy>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
|
||||
</Project>
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,933 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<edmx:Edmx Version="2.0" xmlns:edmx="http://schemas.microsoft.com/ado/2008/10/edmx">
|
||||
<!-- EF Runtime content -->
|
||||
<edmx:Runtime>
|
||||
<!-- SSDL content -->
|
||||
<edmx:StorageModels>
|
||||
<Schema Namespace="northwindModel.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2005" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl">
|
||||
<EntityContainer Name="northwindModelStoreContainer">
|
||||
<EntitySet Name="Categories" EntityType="northwindModel.Store.Categories" store:Type="Tables" Schema="dbo" />
|
||||
<EntitySet Name="CustomerCustomerDemo" EntityType="northwindModel.Store.CustomerCustomerDemo" store:Type="Tables" Schema="dbo" />
|
||||
<EntitySet Name="CustomerDemographics" EntityType="northwindModel.Store.CustomerDemographics" store:Type="Tables" Schema="dbo" />
|
||||
<EntitySet Name="Customers" EntityType="northwindModel.Store.Customers" store:Type="Tables" Schema="dbo" />
|
||||
<EntitySet Name="Employees" EntityType="northwindModel.Store.Employees" store:Type="Tables" Schema="dbo" />
|
||||
<EntitySet Name="EmployeeTerritories" EntityType="northwindModel.Store.EmployeeTerritories" store:Type="Tables" Schema="dbo" />
|
||||
<EntitySet Name="Order Details" EntityType="northwindModel.Store.Order Details" store:Type="Tables" Schema="dbo" />
|
||||
<EntitySet Name="Orders" EntityType="northwindModel.Store.Orders" store:Type="Tables" Schema="dbo" />
|
||||
<EntitySet Name="Products" EntityType="northwindModel.Store.Products" store:Type="Tables" Schema="dbo" />
|
||||
<EntitySet Name="Region" EntityType="northwindModel.Store.Region" store:Type="Tables" Schema="dbo" />
|
||||
<EntitySet Name="Shippers" EntityType="northwindModel.Store.Shippers" store:Type="Tables" Schema="dbo" />
|
||||
<EntitySet Name="Suppliers" EntityType="northwindModel.Store.Suppliers" store:Type="Tables" Schema="dbo" />
|
||||
<EntitySet Name="Territories" EntityType="northwindModel.Store.Territories" store:Type="Tables" Schema="dbo" />
|
||||
<AssociationSet Name="FK_CustomerCustomerDemo" Association="northwindModel.Store.FK_CustomerCustomerDemo">
|
||||
<End Role="CustomerDemographics" EntitySet="CustomerDemographics" />
|
||||
<End Role="CustomerCustomerDemo" EntitySet="CustomerCustomerDemo" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_CustomerCustomerDemo_Customers" Association="northwindModel.Store.FK_CustomerCustomerDemo_Customers">
|
||||
<End Role="Customers" EntitySet="Customers" />
|
||||
<End Role="CustomerCustomerDemo" EntitySet="CustomerCustomerDemo" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Employees_Employees" Association="northwindModel.Store.FK_Employees_Employees">
|
||||
<End Role="Employees" EntitySet="Employees" />
|
||||
<End Role="Employees1" EntitySet="Employees" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_EmployeeTerritories_Employees" Association="northwindModel.Store.FK_EmployeeTerritories_Employees">
|
||||
<End Role="Employees" EntitySet="Employees" />
|
||||
<End Role="EmployeeTerritories" EntitySet="EmployeeTerritories" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_EmployeeTerritories_Territories" Association="northwindModel.Store.FK_EmployeeTerritories_Territories">
|
||||
<End Role="Territories" EntitySet="Territories" />
|
||||
<End Role="EmployeeTerritories" EntitySet="EmployeeTerritories" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Order_Details_Orders" Association="northwindModel.Store.FK_Order_Details_Orders">
|
||||
<End Role="Orders" EntitySet="Orders" />
|
||||
<End Role="Order Details" EntitySet="Order Details" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Order_Details_Products" Association="northwindModel.Store.FK_Order_Details_Products">
|
||||
<End Role="Products" EntitySet="Products" />
|
||||
<End Role="Order Details" EntitySet="Order Details" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Orders_Customers" Association="northwindModel.Store.FK_Orders_Customers">
|
||||
<End Role="Customers" EntitySet="Customers" />
|
||||
<End Role="Orders" EntitySet="Orders" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Orders_Employees" Association="northwindModel.Store.FK_Orders_Employees">
|
||||
<End Role="Employees" EntitySet="Employees" />
|
||||
<End Role="Orders" EntitySet="Orders" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Orders_Shippers" Association="northwindModel.Store.FK_Orders_Shippers">
|
||||
<End Role="Shippers" EntitySet="Shippers" />
|
||||
<End Role="Orders" EntitySet="Orders" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Products_Categories" Association="northwindModel.Store.FK_Products_Categories">
|
||||
<End Role="Categories" EntitySet="Categories" />
|
||||
<End Role="Products" EntitySet="Products" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Products_Suppliers" Association="northwindModel.Store.FK_Products_Suppliers">
|
||||
<End Role="Suppliers" EntitySet="Suppliers" />
|
||||
<End Role="Products" EntitySet="Products" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Territories_Region" Association="northwindModel.Store.FK_Territories_Region">
|
||||
<End Role="Region" EntitySet="Region" />
|
||||
<End Role="Territories" EntitySet="Territories" />
|
||||
</AssociationSet>
|
||||
</EntityContainer>
|
||||
<EntityType Name="Categories">
|
||||
<Key>
|
||||
<PropertyRef Name="CategoryID" />
|
||||
</Key>
|
||||
<Property Name="CategoryID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
|
||||
<Property Name="CategoryName" Type="nvarchar" Nullable="false" MaxLength="15" />
|
||||
<Property Name="Description" Type="ntext" />
|
||||
<Property Name="Picture" Type="image" />
|
||||
</EntityType>
|
||||
<EntityType Name="CustomerCustomerDemo">
|
||||
<Key>
|
||||
<PropertyRef Name="CustomerID" />
|
||||
<PropertyRef Name="CustomerTypeID" />
|
||||
</Key>
|
||||
<Property Name="CustomerID" Type="nchar" Nullable="false" MaxLength="5" />
|
||||
<Property Name="CustomerTypeID" Type="nchar" Nullable="false" MaxLength="10" />
|
||||
</EntityType>
|
||||
<EntityType Name="CustomerDemographics">
|
||||
<Key>
|
||||
<PropertyRef Name="CustomerTypeID" />
|
||||
</Key>
|
||||
<Property Name="CustomerTypeID" Type="nchar" Nullable="false" MaxLength="10" />
|
||||
<Property Name="CustomerDesc" Type="ntext" />
|
||||
</EntityType>
|
||||
<EntityType Name="Customers">
|
||||
<Key>
|
||||
<PropertyRef Name="CustomerID" />
|
||||
</Key>
|
||||
<Property Name="CustomerID" Type="nchar" Nullable="false" MaxLength="5" />
|
||||
<Property Name="CompanyName" Type="nvarchar" Nullable="false" MaxLength="40" />
|
||||
<Property Name="ContactName" Type="nvarchar" MaxLength="30" />
|
||||
<Property Name="ContactTitle" Type="nvarchar" MaxLength="30" />
|
||||
<Property Name="Address" Type="nvarchar" MaxLength="60" />
|
||||
<Property Name="City" Type="nvarchar" MaxLength="15" />
|
||||
<Property Name="Region" Type="nvarchar" MaxLength="15" />
|
||||
<Property Name="PostalCode" Type="nvarchar" MaxLength="10" />
|
||||
<Property Name="Country" Type="nvarchar" MaxLength="15" />
|
||||
<Property Name="Phone" Type="nvarchar" MaxLength="24" />
|
||||
<Property Name="Fax" Type="nvarchar" MaxLength="24" />
|
||||
</EntityType>
|
||||
<EntityType Name="Employees">
|
||||
<Key>
|
||||
<PropertyRef Name="EmployeeID" />
|
||||
</Key>
|
||||
<Property Name="EmployeeID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
|
||||
<Property Name="LastName" Type="nvarchar" Nullable="false" MaxLength="20" />
|
||||
<Property Name="FirstName" Type="nvarchar" Nullable="false" MaxLength="10" />
|
||||
<Property Name="Title" Type="nvarchar" MaxLength="30" />
|
||||
<Property Name="TitleOfCourtesy" Type="nvarchar" MaxLength="25" />
|
||||
<Property Name="BirthDate" Type="datetime" />
|
||||
<Property Name="HireDate" Type="datetime" />
|
||||
<Property Name="Address" Type="nvarchar" MaxLength="60" />
|
||||
<Property Name="City" Type="nvarchar" MaxLength="15" />
|
||||
<Property Name="Region" Type="nvarchar" MaxLength="15" />
|
||||
<Property Name="PostalCode" Type="nvarchar" MaxLength="10" />
|
||||
<Property Name="Country" Type="nvarchar" MaxLength="15" />
|
||||
<Property Name="HomePhone" Type="nvarchar" MaxLength="24" />
|
||||
<Property Name="Extension" Type="nvarchar" MaxLength="4" />
|
||||
<Property Name="Photo" Type="image" />
|
||||
<Property Name="Notes" Type="ntext" />
|
||||
<Property Name="ReportsTo" Type="int" />
|
||||
<Property Name="PhotoPath" Type="nvarchar" MaxLength="255" />
|
||||
</EntityType>
|
||||
<EntityType Name="EmployeeTerritories">
|
||||
<Key>
|
||||
<PropertyRef Name="EmployeeID" />
|
||||
<PropertyRef Name="TerritoryID" />
|
||||
</Key>
|
||||
<Property Name="EmployeeID" Type="int" Nullable="false" />
|
||||
<Property Name="TerritoryID" Type="nvarchar" Nullable="false" MaxLength="20" />
|
||||
</EntityType>
|
||||
<EntityType Name="Order Details">
|
||||
<Key>
|
||||
<PropertyRef Name="OrderID" />
|
||||
<PropertyRef Name="ProductID" />
|
||||
</Key>
|
||||
<Property Name="OrderID" Type="int" Nullable="false" />
|
||||
<Property Name="ProductID" Type="int" Nullable="false" />
|
||||
<Property Name="UnitPrice" Type="money" Nullable="false" />
|
||||
<Property Name="Quantity" Type="smallint" Nullable="false" />
|
||||
<Property Name="Discount" Type="real" Nullable="false" />
|
||||
</EntityType>
|
||||
<EntityType Name="Orders">
|
||||
<Key>
|
||||
<PropertyRef Name="OrderID" />
|
||||
</Key>
|
||||
<Property Name="OrderID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
|
||||
<Property Name="CustomerID" Type="nchar" MaxLength="5" />
|
||||
<Property Name="EmployeeID" Type="int" />
|
||||
<Property Name="OrderDate" Type="datetime" />
|
||||
<Property Name="RequiredDate" Type="datetime" />
|
||||
<Property Name="ShippedDate" Type="datetime" />
|
||||
<Property Name="ShipVia" Type="int" />
|
||||
<Property Name="Freight" Type="money" />
|
||||
<Property Name="ShipName" Type="nvarchar" MaxLength="40" />
|
||||
<Property Name="ShipAddress" Type="nvarchar" MaxLength="60" />
|
||||
<Property Name="ShipCity" Type="nvarchar" MaxLength="15" />
|
||||
<Property Name="ShipRegion" Type="nvarchar" MaxLength="15" />
|
||||
<Property Name="ShipPostalCode" Type="nvarchar" MaxLength="10" />
|
||||
<Property Name="ShipCountry" Type="nvarchar" MaxLength="15" />
|
||||
</EntityType>
|
||||
<EntityType Name="Products">
|
||||
<Key>
|
||||
<PropertyRef Name="ProductID" />
|
||||
</Key>
|
||||
<Property Name="ProductID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
|
||||
<Property Name="ProductName" Type="nvarchar" Nullable="false" MaxLength="40" />
|
||||
<Property Name="SupplierID" Type="int" />
|
||||
<Property Name="CategoryID" Type="int" />
|
||||
<Property Name="QuantityPerUnit" Type="nvarchar" MaxLength="20" />
|
||||
<Property Name="UnitPrice" Type="money" />
|
||||
<Property Name="UnitsInStock" Type="smallint" />
|
||||
<Property Name="UnitsOnOrder" Type="smallint" />
|
||||
<Property Name="ReorderLevel" Type="smallint" />
|
||||
<Property Name="Discontinued" Type="bit" Nullable="false" />
|
||||
</EntityType>
|
||||
<EntityType Name="Region">
|
||||
<Key>
|
||||
<PropertyRef Name="RegionID" />
|
||||
</Key>
|
||||
<Property Name="RegionID" Type="int" Nullable="false" />
|
||||
<Property Name="RegionDescription" Type="nchar" Nullable="false" MaxLength="50" />
|
||||
</EntityType>
|
||||
<EntityType Name="Shippers">
|
||||
<Key>
|
||||
<PropertyRef Name="ShipperID" />
|
||||
</Key>
|
||||
<Property Name="ShipperID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
|
||||
<Property Name="CompanyName" Type="nvarchar" Nullable="false" MaxLength="40" />
|
||||
<Property Name="Phone" Type="nvarchar" MaxLength="24" />
|
||||
</EntityType>
|
||||
<EntityType Name="Suppliers">
|
||||
<Key>
|
||||
<PropertyRef Name="SupplierID" />
|
||||
</Key>
|
||||
<Property Name="SupplierID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
|
||||
<Property Name="CompanyName" Type="nvarchar" Nullable="false" MaxLength="40" />
|
||||
<Property Name="ContactName" Type="nvarchar" MaxLength="30" />
|
||||
<Property Name="ContactTitle" Type="nvarchar" MaxLength="30" />
|
||||
<Property Name="Address" Type="nvarchar" MaxLength="60" />
|
||||
<Property Name="City" Type="nvarchar" MaxLength="15" />
|
||||
<Property Name="Region" Type="nvarchar" MaxLength="15" />
|
||||
<Property Name="PostalCode" Type="nvarchar" MaxLength="10" />
|
||||
<Property Name="Country" Type="nvarchar" MaxLength="15" />
|
||||
<Property Name="Phone" Type="nvarchar" MaxLength="24" />
|
||||
<Property Name="Fax" Type="nvarchar" MaxLength="24" />
|
||||
<Property Name="HomePage" Type="ntext" />
|
||||
</EntityType>
|
||||
<EntityType Name="Territories">
|
||||
<Key>
|
||||
<PropertyRef Name="TerritoryID" />
|
||||
</Key>
|
||||
<Property Name="TerritoryID" Type="nvarchar" Nullable="false" MaxLength="20" />
|
||||
<Property Name="TerritoryDescription" Type="nchar" Nullable="false" MaxLength="50" />
|
||||
<Property Name="RegionID" Type="int" Nullable="false" />
|
||||
</EntityType>
|
||||
<Association Name="FK_CustomerCustomerDemo">
|
||||
<End Role="CustomerDemographics" Type="northwindModel.Store.CustomerDemographics" Multiplicity="1" />
|
||||
<End Role="CustomerCustomerDemo" Type="northwindModel.Store.CustomerCustomerDemo" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="CustomerDemographics">
|
||||
<PropertyRef Name="CustomerTypeID" />
|
||||
</Principal>
|
||||
<Dependent Role="CustomerCustomerDemo">
|
||||
<PropertyRef Name="CustomerTypeID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_CustomerCustomerDemo_Customers">
|
||||
<End Role="Customers" Type="northwindModel.Store.Customers" Multiplicity="1" />
|
||||
<End Role="CustomerCustomerDemo" Type="northwindModel.Store.CustomerCustomerDemo" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Customers">
|
||||
<PropertyRef Name="CustomerID" />
|
||||
</Principal>
|
||||
<Dependent Role="CustomerCustomerDemo">
|
||||
<PropertyRef Name="CustomerID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Employees_Employees">
|
||||
<End Role="Employees" Type="northwindModel.Store.Employees" Multiplicity="0..1" />
|
||||
<End Role="Employees1" Type="northwindModel.Store.Employees" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Employees">
|
||||
<PropertyRef Name="EmployeeID" />
|
||||
</Principal>
|
||||
<Dependent Role="Employees1">
|
||||
<PropertyRef Name="ReportsTo" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_EmployeeTerritories_Employees">
|
||||
<End Role="Employees" Type="northwindModel.Store.Employees" Multiplicity="1" />
|
||||
<End Role="EmployeeTerritories" Type="northwindModel.Store.EmployeeTerritories" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Employees">
|
||||
<PropertyRef Name="EmployeeID" />
|
||||
</Principal>
|
||||
<Dependent Role="EmployeeTerritories">
|
||||
<PropertyRef Name="EmployeeID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_EmployeeTerritories_Territories">
|
||||
<End Role="Territories" Type="northwindModel.Store.Territories" Multiplicity="1" />
|
||||
<End Role="EmployeeTerritories" Type="northwindModel.Store.EmployeeTerritories" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Territories">
|
||||
<PropertyRef Name="TerritoryID" />
|
||||
</Principal>
|
||||
<Dependent Role="EmployeeTerritories">
|
||||
<PropertyRef Name="TerritoryID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Order_Details_Orders">
|
||||
<End Role="Orders" Type="northwindModel.Store.Orders" Multiplicity="1" />
|
||||
<End Role="Order Details" Type="northwindModel.Store.Order Details" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Orders">
|
||||
<PropertyRef Name="OrderID" />
|
||||
</Principal>
|
||||
<Dependent Role="Order Details">
|
||||
<PropertyRef Name="OrderID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Order_Details_Products">
|
||||
<End Role="Products" Type="northwindModel.Store.Products" Multiplicity="1" />
|
||||
<End Role="Order Details" Type="northwindModel.Store.Order Details" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Products">
|
||||
<PropertyRef Name="ProductID" />
|
||||
</Principal>
|
||||
<Dependent Role="Order Details">
|
||||
<PropertyRef Name="ProductID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Orders_Customers">
|
||||
<End Role="Customers" Type="northwindModel.Store.Customers" Multiplicity="0..1" />
|
||||
<End Role="Orders" Type="northwindModel.Store.Orders" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Customers">
|
||||
<PropertyRef Name="CustomerID" />
|
||||
</Principal>
|
||||
<Dependent Role="Orders">
|
||||
<PropertyRef Name="CustomerID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Orders_Employees">
|
||||
<End Role="Employees" Type="northwindModel.Store.Employees" Multiplicity="0..1" />
|
||||
<End Role="Orders" Type="northwindModel.Store.Orders" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Employees">
|
||||
<PropertyRef Name="EmployeeID" />
|
||||
</Principal>
|
||||
<Dependent Role="Orders">
|
||||
<PropertyRef Name="EmployeeID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Orders_Shippers">
|
||||
<End Role="Shippers" Type="northwindModel.Store.Shippers" Multiplicity="0..1" />
|
||||
<End Role="Orders" Type="northwindModel.Store.Orders" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Shippers">
|
||||
<PropertyRef Name="ShipperID" />
|
||||
</Principal>
|
||||
<Dependent Role="Orders">
|
||||
<PropertyRef Name="ShipVia" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Products_Categories">
|
||||
<End Role="Categories" Type="northwindModel.Store.Categories" Multiplicity="0..1" />
|
||||
<End Role="Products" Type="northwindModel.Store.Products" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Categories">
|
||||
<PropertyRef Name="CategoryID" />
|
||||
</Principal>
|
||||
<Dependent Role="Products">
|
||||
<PropertyRef Name="CategoryID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Products_Suppliers">
|
||||
<End Role="Suppliers" Type="northwindModel.Store.Suppliers" Multiplicity="0..1" />
|
||||
<End Role="Products" Type="northwindModel.Store.Products" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Suppliers">
|
||||
<PropertyRef Name="SupplierID" />
|
||||
</Principal>
|
||||
<Dependent Role="Products">
|
||||
<PropertyRef Name="SupplierID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Territories_Region">
|
||||
<End Role="Region" Type="northwindModel.Store.Region" Multiplicity="1" />
|
||||
<End Role="Territories" Type="northwindModel.Store.Territories" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Region">
|
||||
<PropertyRef Name="RegionID" />
|
||||
</Principal>
|
||||
<Dependent Role="Territories">
|
||||
<PropertyRef Name="RegionID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
</Schema>
|
||||
</edmx:StorageModels>
|
||||
<!-- CSDL content -->
|
||||
<edmx:ConceptualModels>
|
||||
<Schema Namespace="northwindModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
|
||||
<EntityContainer Name="NorthwindEntities" annotation:LazyLoadingEnabled="true">
|
||||
<EntitySet Name="Categories" EntityType="northwindModel.Category" />
|
||||
<EntitySet Name="CustomerDemographics" EntityType="northwindModel.CustomerDemographic" />
|
||||
<EntitySet Name="Customers" EntityType="northwindModel.Customer" />
|
||||
<EntitySet Name="Employees" EntityType="northwindModel.Employee" />
|
||||
<EntitySet Name="Order_Details" EntityType="northwindModel.Order_Detail" />
|
||||
<EntitySet Name="Orders" EntityType="northwindModel.Order" />
|
||||
<EntitySet Name="Products" EntityType="northwindModel.Product" />
|
||||
<EntitySet Name="Regions" EntityType="northwindModel.Region" />
|
||||
<EntitySet Name="Shippers" EntityType="northwindModel.Shipper" />
|
||||
<EntitySet Name="Suppliers" EntityType="northwindModel.Supplier" />
|
||||
<EntitySet Name="Territories" EntityType="northwindModel.Territory" />
|
||||
<AssociationSet Name="FK_Products_Categories" Association="northwindModel.FK_Products_Categories">
|
||||
<End Role="Categories" EntitySet="Categories" />
|
||||
<End Role="Products" EntitySet="Products" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Orders_Customers" Association="northwindModel.FK_Orders_Customers">
|
||||
<End Role="Customers" EntitySet="Customers" />
|
||||
<End Role="Orders" EntitySet="Orders" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Employees_Employees" Association="northwindModel.FK_Employees_Employees">
|
||||
<End Role="Employees" EntitySet="Employees" />
|
||||
<End Role="Employees1" EntitySet="Employees" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Orders_Employees" Association="northwindModel.FK_Orders_Employees">
|
||||
<End Role="Employees" EntitySet="Employees" />
|
||||
<End Role="Orders" EntitySet="Orders" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Order_Details_Orders" Association="northwindModel.FK_Order_Details_Orders">
|
||||
<End Role="Orders" EntitySet="Orders" />
|
||||
<End Role="Order_Details" EntitySet="Order_Details" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Order_Details_Products" Association="northwindModel.FK_Order_Details_Products">
|
||||
<End Role="Products" EntitySet="Products" />
|
||||
<End Role="Order_Details" EntitySet="Order_Details" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Orders_Shippers" Association="northwindModel.FK_Orders_Shippers">
|
||||
<End Role="Shippers" EntitySet="Shippers" />
|
||||
<End Role="Orders" EntitySet="Orders" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Products_Suppliers" Association="northwindModel.FK_Products_Suppliers">
|
||||
<End Role="Suppliers" EntitySet="Suppliers" />
|
||||
<End Role="Products" EntitySet="Products" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="FK_Territories_Region" Association="northwindModel.FK_Territories_Region">
|
||||
<End Role="Region" EntitySet="Regions" />
|
||||
<End Role="Territories" EntitySet="Territories" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="CustomerCustomerDemo" Association="northwindModel.CustomerCustomerDemo">
|
||||
<End Role="CustomerDemographics" EntitySet="CustomerDemographics" />
|
||||
<End Role="Customers" EntitySet="Customers" />
|
||||
</AssociationSet>
|
||||
<AssociationSet Name="EmployeeTerritories" Association="northwindModel.EmployeeTerritories">
|
||||
<End Role="Employees" EntitySet="Employees" />
|
||||
<End Role="Territories" EntitySet="Territories" />
|
||||
</AssociationSet>
|
||||
</EntityContainer>
|
||||
<EntityType Name="Category">
|
||||
<Key>
|
||||
<PropertyRef Name="CategoryID" />
|
||||
</Key>
|
||||
<Property Name="CategoryID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" ConcurrencyMode="Fixed" />
|
||||
<Property Name="CategoryName" Type="String" Nullable="false" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="None" />
|
||||
<Property Name="Description" Type="String" MaxLength="Max" Unicode="true" FixedLength="false" ConcurrencyMode="None" />
|
||||
<Property Name="Picture" Type="Binary" MaxLength="Max" FixedLength="false" />
|
||||
<NavigationProperty Name="Products" Relationship="northwindModel.FK_Products_Categories" FromRole="Categories" ToRole="Products" />
|
||||
</EntityType>
|
||||
<EntityType Name="CustomerDemographic">
|
||||
<Key>
|
||||
<PropertyRef Name="CustomerTypeID" />
|
||||
</Key>
|
||||
<Property Name="CustomerTypeID" Type="String" Nullable="false" MaxLength="10" Unicode="true" FixedLength="true" />
|
||||
<Property Name="CustomerDesc" Type="String" MaxLength="Max" Unicode="true" FixedLength="false" />
|
||||
<NavigationProperty Name="Customers" Relationship="northwindModel.CustomerCustomerDemo" FromRole="CustomerDemographics" ToRole="Customers" />
|
||||
</EntityType>
|
||||
<EntityType Name="Customer">
|
||||
<Key>
|
||||
<PropertyRef Name="CustomerID" />
|
||||
</Key>
|
||||
<Property Name="CustomerID" Type="String" Nullable="false" MaxLength="5" Unicode="true" FixedLength="true" ConcurrencyMode="Fixed" />
|
||||
<Property Name="CompanyName" Type="String" Nullable="false" MaxLength="40" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="ContactName" Type="String" MaxLength="30" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="ContactTitle" Type="String" MaxLength="30" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Address" Type="String" MaxLength="60" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="City" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Region" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="PostalCode" Type="String" MaxLength="10" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Country" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Phone" Type="String" MaxLength="24" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Fax" Type="String" MaxLength="24" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<NavigationProperty Name="Orders" Relationship="northwindModel.FK_Orders_Customers" FromRole="Customers" ToRole="Orders" />
|
||||
<NavigationProperty Name="CustomerDemographics" Relationship="northwindModel.CustomerCustomerDemo" FromRole="Customers" ToRole="CustomerDemographics" />
|
||||
</EntityType>
|
||||
<EntityType Name="Employee">
|
||||
<Key>
|
||||
<PropertyRef Name="EmployeeID" />
|
||||
</Key>
|
||||
<Property Name="EmployeeID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" ConcurrencyMode="Fixed" />
|
||||
<Property Name="LastName" Type="String" Nullable="false" MaxLength="20" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="FirstName" Type="String" Nullable="false" MaxLength="10" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Title" Type="String" MaxLength="30" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="TitleOfCourtesy" Type="String" MaxLength="25" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="BirthDate" Type="DateTime" ConcurrencyMode="Fixed" />
|
||||
<Property Name="HireDate" Type="DateTime" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Address" Type="String" MaxLength="60" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="City" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Region" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="PostalCode" Type="String" MaxLength="10" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Country" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="HomePhone" Type="String" MaxLength="24" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Extension" Type="String" MaxLength="4" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Photo" Type="Binary" MaxLength="Max" FixedLength="false" />
|
||||
<Property Name="Notes" Type="String" MaxLength="Max" Unicode="true" FixedLength="false" />
|
||||
<Property Name="ReportsTo" Type="Int32" ConcurrencyMode="Fixed" />
|
||||
<Property Name="PhotoPath" Type="String" MaxLength="255" Unicode="true" FixedLength="false" />
|
||||
<NavigationProperty Name="Employees1" Relationship="northwindModel.FK_Employees_Employees" FromRole="Employees" ToRole="Employees1" />
|
||||
<NavigationProperty Name="Employee1" Relationship="northwindModel.FK_Employees_Employees" FromRole="Employees1" ToRole="Employees" />
|
||||
<NavigationProperty Name="Orders" Relationship="northwindModel.FK_Orders_Employees" FromRole="Employees" ToRole="Orders" />
|
||||
<NavigationProperty Name="Territories" Relationship="northwindModel.EmployeeTerritories" FromRole="Employees" ToRole="Territories" />
|
||||
</EntityType>
|
||||
<EntityType Name="Order_Detail">
|
||||
<Key>
|
||||
<PropertyRef Name="OrderID" />
|
||||
<PropertyRef Name="ProductID" />
|
||||
</Key>
|
||||
<Property Name="OrderID" Type="Int32" Nullable="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="ProductID" Type="Int32" Nullable="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="UnitPrice" Type="Decimal" Nullable="false" Precision="19" Scale="4" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Quantity" Type="Int16" Nullable="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Discount" Type="Single" Nullable="false" ConcurrencyMode="Fixed" />
|
||||
<NavigationProperty Name="Order" Relationship="northwindModel.FK_Order_Details_Orders" FromRole="Order_Details" ToRole="Orders" />
|
||||
<NavigationProperty Name="Product" Relationship="northwindModel.FK_Order_Details_Products" FromRole="Order_Details" ToRole="Products" />
|
||||
</EntityType>
|
||||
<EntityType Name="Order">
|
||||
<Key>
|
||||
<PropertyRef Name="OrderID" />
|
||||
</Key>
|
||||
<Property Name="OrderID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" ConcurrencyMode="Fixed" />
|
||||
<Property Name="CustomerID" Type="String" MaxLength="5" Unicode="true" FixedLength="true" ConcurrencyMode="Fixed" />
|
||||
<Property Name="EmployeeID" Type="Int32" ConcurrencyMode="Fixed" />
|
||||
<Property Name="OrderDate" Type="DateTime" ConcurrencyMode="Fixed" />
|
||||
<Property Name="RequiredDate" Type="DateTime" ConcurrencyMode="Fixed" />
|
||||
<Property Name="ShippedDate" Type="DateTime" ConcurrencyMode="Fixed" />
|
||||
<Property Name="ShipVia" Type="Int32" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Freight" Type="Decimal" Precision="19" Scale="4" ConcurrencyMode="Fixed" />
|
||||
<Property Name="ShipName" Type="String" MaxLength="40" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="ShipAddress" Type="String" MaxLength="60" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="ShipCity" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="ShipRegion" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="ShipPostalCode" Type="String" MaxLength="10" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="ShipCountry" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<NavigationProperty Name="Customer" Relationship="northwindModel.FK_Orders_Customers" FromRole="Orders" ToRole="Customers" />
|
||||
<NavigationProperty Name="Employee" Relationship="northwindModel.FK_Orders_Employees" FromRole="Orders" ToRole="Employees" />
|
||||
<NavigationProperty Name="Order_Details" Relationship="northwindModel.FK_Order_Details_Orders" FromRole="Orders" ToRole="Order_Details" />
|
||||
<NavigationProperty Name="Shipper" Relationship="northwindModel.FK_Orders_Shippers" FromRole="Orders" ToRole="Shippers" />
|
||||
</EntityType>
|
||||
<EntityType Name="Product">
|
||||
<Key>
|
||||
<PropertyRef Name="ProductID" />
|
||||
</Key>
|
||||
<Property Name="ProductID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" ConcurrencyMode="None" />
|
||||
<Property Name="ProductName" Type="String" Nullable="false" MaxLength="40" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="SupplierID" Type="Int32" ConcurrencyMode="Fixed" />
|
||||
<Property Name="CategoryID" Type="Int32" ConcurrencyMode="Fixed" />
|
||||
<Property Name="QuantityPerUnit" Type="String" MaxLength="20" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
|
||||
<Property Name="UnitPrice" Type="Decimal" Precision="19" Scale="4" ConcurrencyMode="Fixed" />
|
||||
<Property Name="UnitsInStock" Type="Int16" ConcurrencyMode="Fixed" />
|
||||
<Property Name="UnitsOnOrder" Type="Int16" ConcurrencyMode="Fixed" />
|
||||
<Property Name="ReorderLevel" Type="Int16" ConcurrencyMode="Fixed" />
|
||||
<Property Name="Discontinued" Type="Boolean" Nullable="false" ConcurrencyMode="Fixed" />
|
||||
<NavigationProperty Name="Category" Relationship="northwindModel.FK_Products_Categories" FromRole="Products" ToRole="Categories" />
|
||||
<NavigationProperty Name="Order_Details" Relationship="northwindModel.FK_Order_Details_Products" FromRole="Products" ToRole="Order_Details" />
|
||||
<NavigationProperty Name="Supplier" Relationship="northwindModel.FK_Products_Suppliers" FromRole="Products" ToRole="Suppliers" />
|
||||
</EntityType>
|
||||
<EntityType Name="Region">
|
||||
<Key>
|
||||
<PropertyRef Name="RegionID" />
|
||||
</Key>
|
||||
<Property Name="RegionID" Type="Int32" Nullable="false" />
|
||||
<Property Name="RegionDescription" Type="String" Nullable="false" MaxLength="50" Unicode="true" FixedLength="true" />
|
||||
<NavigationProperty Name="Territories" Relationship="northwindModel.FK_Territories_Region" FromRole="Region" ToRole="Territories" />
|
||||
</EntityType>
|
||||
<EntityType Name="Shipper">
|
||||
<Key>
|
||||
<PropertyRef Name="ShipperID" />
|
||||
</Key>
|
||||
<Property Name="ShipperID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
|
||||
<Property Name="CompanyName" Type="String" Nullable="false" MaxLength="40" Unicode="true" FixedLength="false" />
|
||||
<Property Name="Phone" Type="String" MaxLength="24" Unicode="true" FixedLength="false" />
|
||||
<NavigationProperty Name="Orders" Relationship="northwindModel.FK_Orders_Shippers" FromRole="Shippers" ToRole="Orders" />
|
||||
</EntityType>
|
||||
<EntityType Name="Supplier">
|
||||
<Key>
|
||||
<PropertyRef Name="SupplierID" />
|
||||
</Key>
|
||||
<Property Name="SupplierID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
|
||||
<Property Name="CompanyName" Type="String" Nullable="false" MaxLength="40" Unicode="true" FixedLength="false" />
|
||||
<Property Name="ContactName" Type="String" MaxLength="30" Unicode="true" FixedLength="false" />
|
||||
<Property Name="ContactTitle" Type="String" MaxLength="30" Unicode="true" FixedLength="false" />
|
||||
<Property Name="Address" Type="String" MaxLength="60" Unicode="true" FixedLength="false" />
|
||||
<Property Name="City" Type="String" MaxLength="15" Unicode="true" FixedLength="false" />
|
||||
<Property Name="Region" Type="String" MaxLength="15" Unicode="true" FixedLength="false" />
|
||||
<Property Name="PostalCode" Type="String" MaxLength="10" Unicode="true" FixedLength="false" />
|
||||
<Property Name="Country" Type="String" MaxLength="15" Unicode="true" FixedLength="false" />
|
||||
<Property Name="Phone" Type="String" MaxLength="24" Unicode="true" FixedLength="false" />
|
||||
<Property Name="Fax" Type="String" MaxLength="24" Unicode="true" FixedLength="false" />
|
||||
<Property Name="HomePage" Type="String" MaxLength="Max" Unicode="true" FixedLength="false" />
|
||||
<NavigationProperty Name="Products" Relationship="northwindModel.FK_Products_Suppliers" FromRole="Suppliers" ToRole="Products" />
|
||||
</EntityType>
|
||||
<EntityType Name="Territory">
|
||||
<Key>
|
||||
<PropertyRef Name="TerritoryID" />
|
||||
</Key>
|
||||
<Property Name="TerritoryID" Type="String" Nullable="false" MaxLength="20" Unicode="true" FixedLength="false" />
|
||||
<Property Name="TerritoryDescription" Type="String" Nullable="false" MaxLength="50" Unicode="true" FixedLength="true" />
|
||||
<Property Name="RegionID" Type="Int32" Nullable="false" />
|
||||
<NavigationProperty Name="Region" Relationship="northwindModel.FK_Territories_Region" FromRole="Territories" ToRole="Region" />
|
||||
<NavigationProperty Name="Employees" Relationship="northwindModel.EmployeeTerritories" FromRole="Territories" ToRole="Employees" />
|
||||
</EntityType>
|
||||
<Association Name="FK_Products_Categories">
|
||||
<End Role="Categories" Type="northwindModel.Category" Multiplicity="0..1" />
|
||||
<End Role="Products" Type="northwindModel.Product" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Categories">
|
||||
<PropertyRef Name="CategoryID" />
|
||||
</Principal>
|
||||
<Dependent Role="Products">
|
||||
<PropertyRef Name="CategoryID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Orders_Customers">
|
||||
<End Role="Customers" Type="northwindModel.Customer" Multiplicity="0..1" />
|
||||
<End Role="Orders" Type="northwindModel.Order" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Customers">
|
||||
<PropertyRef Name="CustomerID" />
|
||||
</Principal>
|
||||
<Dependent Role="Orders">
|
||||
<PropertyRef Name="CustomerID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Employees_Employees">
|
||||
<End Role="Employees" Type="northwindModel.Employee" Multiplicity="0..1" />
|
||||
<End Role="Employees1" Type="northwindModel.Employee" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Employees">
|
||||
<PropertyRef Name="EmployeeID" />
|
||||
</Principal>
|
||||
<Dependent Role="Employees1">
|
||||
<PropertyRef Name="ReportsTo" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Orders_Employees">
|
||||
<End Role="Employees" Type="northwindModel.Employee" Multiplicity="0..1" />
|
||||
<End Role="Orders" Type="northwindModel.Order" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Employees">
|
||||
<PropertyRef Name="EmployeeID" />
|
||||
</Principal>
|
||||
<Dependent Role="Orders">
|
||||
<PropertyRef Name="EmployeeID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Order_Details_Orders">
|
||||
<End Role="Orders" Type="northwindModel.Order" Multiplicity="1" />
|
||||
<End Role="Order_Details" Type="northwindModel.Order_Detail" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Orders">
|
||||
<PropertyRef Name="OrderID" />
|
||||
</Principal>
|
||||
<Dependent Role="Order_Details">
|
||||
<PropertyRef Name="OrderID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Order_Details_Products">
|
||||
<End Role="Products" Type="northwindModel.Product" Multiplicity="1" />
|
||||
<End Role="Order_Details" Type="northwindModel.Order_Detail" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Products">
|
||||
<PropertyRef Name="ProductID" />
|
||||
</Principal>
|
||||
<Dependent Role="Order_Details">
|
||||
<PropertyRef Name="ProductID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Orders_Shippers">
|
||||
<End Role="Shippers" Type="northwindModel.Shipper" Multiplicity="0..1" />
|
||||
<End Role="Orders" Type="northwindModel.Order" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Shippers">
|
||||
<PropertyRef Name="ShipperID" />
|
||||
</Principal>
|
||||
<Dependent Role="Orders">
|
||||
<PropertyRef Name="ShipVia" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Products_Suppliers">
|
||||
<End Role="Suppliers" Type="northwindModel.Supplier" Multiplicity="0..1" />
|
||||
<End Role="Products" Type="northwindModel.Product" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Suppliers">
|
||||
<PropertyRef Name="SupplierID" />
|
||||
</Principal>
|
||||
<Dependent Role="Products">
|
||||
<PropertyRef Name="SupplierID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="FK_Territories_Region">
|
||||
<End Role="Region" Type="northwindModel.Region" Multiplicity="1" />
|
||||
<End Role="Territories" Type="northwindModel.Territory" Multiplicity="*" />
|
||||
<ReferentialConstraint>
|
||||
<Principal Role="Region">
|
||||
<PropertyRef Name="RegionID" />
|
||||
</Principal>
|
||||
<Dependent Role="Territories">
|
||||
<PropertyRef Name="RegionID" />
|
||||
</Dependent>
|
||||
</ReferentialConstraint>
|
||||
</Association>
|
||||
<Association Name="CustomerCustomerDemo">
|
||||
<End Role="CustomerDemographics" Type="northwindModel.CustomerDemographic" Multiplicity="*" />
|
||||
<End Role="Customers" Type="northwindModel.Customer" Multiplicity="*" />
|
||||
</Association>
|
||||
<Association Name="EmployeeTerritories">
|
||||
<End Role="Employees" Type="northwindModel.Employee" Multiplicity="*" />
|
||||
<End Role="Territories" Type="northwindModel.Territory" Multiplicity="*" />
|
||||
</Association>
|
||||
</Schema>
|
||||
</edmx:ConceptualModels>
|
||||
<!-- C-S mapping content -->
|
||||
<edmx:Mappings>
|
||||
<Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs">
|
||||
<EntityContainerMapping StorageEntityContainer="northwindModelStoreContainer" CdmEntityContainer="NorthwindEntities">
|
||||
<EntitySetMapping Name="Categories"><EntityTypeMapping TypeName="northwindModel.Category"><MappingFragment StoreEntitySet="Categories">
|
||||
<ScalarProperty Name="CategoryID" ColumnName="CategoryID" />
|
||||
<ScalarProperty Name="CategoryName" ColumnName="CategoryName" />
|
||||
<ScalarProperty Name="Description" ColumnName="Description" />
|
||||
<ScalarProperty Name="Picture" ColumnName="Picture" />
|
||||
</MappingFragment></EntityTypeMapping></EntitySetMapping>
|
||||
<EntitySetMapping Name="CustomerDemographics"><EntityTypeMapping TypeName="northwindModel.CustomerDemographic"><MappingFragment StoreEntitySet="CustomerDemographics">
|
||||
<ScalarProperty Name="CustomerTypeID" ColumnName="CustomerTypeID" />
|
||||
<ScalarProperty Name="CustomerDesc" ColumnName="CustomerDesc" />
|
||||
</MappingFragment></EntityTypeMapping></EntitySetMapping>
|
||||
<EntitySetMapping Name="Customers"><EntityTypeMapping TypeName="northwindModel.Customer"><MappingFragment StoreEntitySet="Customers">
|
||||
<ScalarProperty Name="CustomerID" ColumnName="CustomerID" />
|
||||
<ScalarProperty Name="CompanyName" ColumnName="CompanyName" />
|
||||
<ScalarProperty Name="ContactName" ColumnName="ContactName" />
|
||||
<ScalarProperty Name="ContactTitle" ColumnName="ContactTitle" />
|
||||
<ScalarProperty Name="Address" ColumnName="Address" />
|
||||
<ScalarProperty Name="City" ColumnName="City" />
|
||||
<ScalarProperty Name="Region" ColumnName="Region" />
|
||||
<ScalarProperty Name="PostalCode" ColumnName="PostalCode" />
|
||||
<ScalarProperty Name="Country" ColumnName="Country" />
|
||||
<ScalarProperty Name="Phone" ColumnName="Phone" />
|
||||
<ScalarProperty Name="Fax" ColumnName="Fax" />
|
||||
</MappingFragment></EntityTypeMapping></EntitySetMapping>
|
||||
<EntitySetMapping Name="Employees"><EntityTypeMapping TypeName="northwindModel.Employee"><MappingFragment StoreEntitySet="Employees">
|
||||
<ScalarProperty Name="EmployeeID" ColumnName="EmployeeID" />
|
||||
<ScalarProperty Name="LastName" ColumnName="LastName" />
|
||||
<ScalarProperty Name="FirstName" ColumnName="FirstName" />
|
||||
<ScalarProperty Name="Title" ColumnName="Title" />
|
||||
<ScalarProperty Name="TitleOfCourtesy" ColumnName="TitleOfCourtesy" />
|
||||
<ScalarProperty Name="BirthDate" ColumnName="BirthDate" />
|
||||
<ScalarProperty Name="HireDate" ColumnName="HireDate" />
|
||||
<ScalarProperty Name="Address" ColumnName="Address" />
|
||||
<ScalarProperty Name="City" ColumnName="City" />
|
||||
<ScalarProperty Name="Region" ColumnName="Region" />
|
||||
<ScalarProperty Name="PostalCode" ColumnName="PostalCode" />
|
||||
<ScalarProperty Name="Country" ColumnName="Country" />
|
||||
<ScalarProperty Name="HomePhone" ColumnName="HomePhone" />
|
||||
<ScalarProperty Name="Extension" ColumnName="Extension" />
|
||||
<ScalarProperty Name="Photo" ColumnName="Photo" />
|
||||
<ScalarProperty Name="Notes" ColumnName="Notes" />
|
||||
<ScalarProperty Name="ReportsTo" ColumnName="ReportsTo" />
|
||||
<ScalarProperty Name="PhotoPath" ColumnName="PhotoPath" />
|
||||
</MappingFragment></EntityTypeMapping></EntitySetMapping>
|
||||
<EntitySetMapping Name="Order_Details"><EntityTypeMapping TypeName="northwindModel.Order_Detail"><MappingFragment StoreEntitySet="Order Details">
|
||||
<ScalarProperty Name="OrderID" ColumnName="OrderID" />
|
||||
<ScalarProperty Name="ProductID" ColumnName="ProductID" />
|
||||
<ScalarProperty Name="UnitPrice" ColumnName="UnitPrice" />
|
||||
<ScalarProperty Name="Quantity" ColumnName="Quantity" />
|
||||
<ScalarProperty Name="Discount" ColumnName="Discount" />
|
||||
</MappingFragment></EntityTypeMapping></EntitySetMapping>
|
||||
<EntitySetMapping Name="Orders"><EntityTypeMapping TypeName="northwindModel.Order"><MappingFragment StoreEntitySet="Orders">
|
||||
<ScalarProperty Name="OrderID" ColumnName="OrderID" />
|
||||
<ScalarProperty Name="CustomerID" ColumnName="CustomerID" />
|
||||
<ScalarProperty Name="EmployeeID" ColumnName="EmployeeID" />
|
||||
<ScalarProperty Name="OrderDate" ColumnName="OrderDate" />
|
||||
<ScalarProperty Name="RequiredDate" ColumnName="RequiredDate" />
|
||||
<ScalarProperty Name="ShippedDate" ColumnName="ShippedDate" />
|
||||
<ScalarProperty Name="ShipVia" ColumnName="ShipVia" />
|
||||
<ScalarProperty Name="Freight" ColumnName="Freight" />
|
||||
<ScalarProperty Name="ShipName" ColumnName="ShipName" />
|
||||
<ScalarProperty Name="ShipAddress" ColumnName="ShipAddress" />
|
||||
<ScalarProperty Name="ShipCity" ColumnName="ShipCity" />
|
||||
<ScalarProperty Name="ShipRegion" ColumnName="ShipRegion" />
|
||||
<ScalarProperty Name="ShipPostalCode" ColumnName="ShipPostalCode" />
|
||||
<ScalarProperty Name="ShipCountry" ColumnName="ShipCountry" />
|
||||
</MappingFragment></EntityTypeMapping></EntitySetMapping>
|
||||
<EntitySetMapping Name="Products"><EntityTypeMapping TypeName="northwindModel.Product"><MappingFragment StoreEntitySet="Products">
|
||||
<ScalarProperty Name="ProductID" ColumnName="ProductID" />
|
||||
<ScalarProperty Name="ProductName" ColumnName="ProductName" />
|
||||
<ScalarProperty Name="SupplierID" ColumnName="SupplierID" />
|
||||
<ScalarProperty Name="CategoryID" ColumnName="CategoryID" />
|
||||
<ScalarProperty Name="QuantityPerUnit" ColumnName="QuantityPerUnit" />
|
||||
<ScalarProperty Name="UnitPrice" ColumnName="UnitPrice" />
|
||||
<ScalarProperty Name="UnitsInStock" ColumnName="UnitsInStock" />
|
||||
<ScalarProperty Name="UnitsOnOrder" ColumnName="UnitsOnOrder" />
|
||||
<ScalarProperty Name="ReorderLevel" ColumnName="ReorderLevel" />
|
||||
<ScalarProperty Name="Discontinued" ColumnName="Discontinued" />
|
||||
</MappingFragment></EntityTypeMapping></EntitySetMapping>
|
||||
<EntitySetMapping Name="Regions"><EntityTypeMapping TypeName="northwindModel.Region"><MappingFragment StoreEntitySet="Region">
|
||||
<ScalarProperty Name="RegionID" ColumnName="RegionID" />
|
||||
<ScalarProperty Name="RegionDescription" ColumnName="RegionDescription" />
|
||||
</MappingFragment></EntityTypeMapping></EntitySetMapping>
|
||||
<EntitySetMapping Name="Shippers"><EntityTypeMapping TypeName="northwindModel.Shipper"><MappingFragment StoreEntitySet="Shippers">
|
||||
<ScalarProperty Name="ShipperID" ColumnName="ShipperID" />
|
||||
<ScalarProperty Name="CompanyName" ColumnName="CompanyName" />
|
||||
<ScalarProperty Name="Phone" ColumnName="Phone" />
|
||||
</MappingFragment></EntityTypeMapping></EntitySetMapping>
|
||||
<EntitySetMapping Name="Suppliers"><EntityTypeMapping TypeName="northwindModel.Supplier"><MappingFragment StoreEntitySet="Suppliers">
|
||||
<ScalarProperty Name="SupplierID" ColumnName="SupplierID" />
|
||||
<ScalarProperty Name="CompanyName" ColumnName="CompanyName" />
|
||||
<ScalarProperty Name="ContactName" ColumnName="ContactName" />
|
||||
<ScalarProperty Name="ContactTitle" ColumnName="ContactTitle" />
|
||||
<ScalarProperty Name="Address" ColumnName="Address" />
|
||||
<ScalarProperty Name="City" ColumnName="City" />
|
||||
<ScalarProperty Name="Region" ColumnName="Region" />
|
||||
<ScalarProperty Name="PostalCode" ColumnName="PostalCode" />
|
||||
<ScalarProperty Name="Country" ColumnName="Country" />
|
||||
<ScalarProperty Name="Phone" ColumnName="Phone" />
|
||||
<ScalarProperty Name="Fax" ColumnName="Fax" />
|
||||
<ScalarProperty Name="HomePage" ColumnName="HomePage" />
|
||||
</MappingFragment></EntityTypeMapping></EntitySetMapping>
|
||||
<EntitySetMapping Name="Territories"><EntityTypeMapping TypeName="northwindModel.Territory"><MappingFragment StoreEntitySet="Territories">
|
||||
<ScalarProperty Name="TerritoryID" ColumnName="TerritoryID" />
|
||||
<ScalarProperty Name="TerritoryDescription" ColumnName="TerritoryDescription" />
|
||||
<ScalarProperty Name="RegionID" ColumnName="RegionID" />
|
||||
</MappingFragment></EntityTypeMapping></EntitySetMapping>
|
||||
<AssociationSetMapping Name="CustomerCustomerDemo" TypeName="northwindModel.CustomerCustomerDemo" StoreEntitySet="CustomerCustomerDemo">
|
||||
<EndProperty Name="CustomerDemographics">
|
||||
<ScalarProperty Name="CustomerTypeID" ColumnName="CustomerTypeID" />
|
||||
</EndProperty>
|
||||
<EndProperty Name="Customers">
|
||||
<ScalarProperty Name="CustomerID" ColumnName="CustomerID" />
|
||||
</EndProperty>
|
||||
</AssociationSetMapping>
|
||||
<AssociationSetMapping Name="EmployeeTerritories" TypeName="northwindModel.EmployeeTerritories" StoreEntitySet="EmployeeTerritories">
|
||||
<EndProperty Name="Employees">
|
||||
<ScalarProperty Name="EmployeeID" ColumnName="EmployeeID" />
|
||||
</EndProperty>
|
||||
<EndProperty Name="Territories">
|
||||
<ScalarProperty Name="TerritoryID" ColumnName="TerritoryID" />
|
||||
</EndProperty>
|
||||
</AssociationSetMapping>
|
||||
</EntityContainerMapping>
|
||||
</Mapping>
|
||||
</edmx:Mappings>
|
||||
</edmx:Runtime>
|
||||
<!-- EF Designer content (DO NOT EDIT MANUALLY BELOW HERE) -->
|
||||
<Designer xmlns="http://schemas.microsoft.com/ado/2008/10/edmx">
|
||||
<Connection>
|
||||
<DesignerInfoPropertySet>
|
||||
<DesignerProperty Name="MetadataArtifactProcessing" Value="EmbedInOutputAssembly" />
|
||||
</DesignerInfoPropertySet>
|
||||
</Connection>
|
||||
<Options>
|
||||
<DesignerInfoPropertySet>
|
||||
<DesignerProperty Name="ValidateOnBuild" Value="true" />
|
||||
<DesignerProperty Name="EnablePluralization" Value="True" />
|
||||
<DesignerProperty Name="IncludeForeignKeysInModel" Value="True" />
|
||||
</DesignerInfoPropertySet>
|
||||
</Options>
|
||||
<!-- Diagram content (shape and connector positions) -->
|
||||
<Diagrams>
|
||||
<Diagram Name="Northwind">
|
||||
<EntityTypeShape EntityType="northwindModel.Category" Width="1.5" PointX="3" PointY="14.5" Height="1.9802864583333317" IsExpanded="true" />
|
||||
<EntityTypeShape EntityType="northwindModel.CustomerDemographic" Width="1.5" PointX="0.75" PointY="2.5" Height="1.5956835937500005" IsExpanded="true" />
|
||||
<EntityTypeShape EntityType="northwindModel.Customer" Width="1.5" PointX="3" PointY="1.5" Height="3.5186979166666661" IsExpanded="true" />
|
||||
<EntityTypeShape EntityType="northwindModel.Employee" Width="1.5" PointX="3" PointY="8.375" Height="5.2494108072916674" IsExpanded="true" />
|
||||
<EntityTypeShape EntityType="northwindModel.Order_Detail" Width="1.5" PointX="7.5" PointY="2.125" Height="2.3648893229166656" IsExpanded="true" />
|
||||
<EntityTypeShape EntityType="northwindModel.Order" Width="1.5" PointX="5.25" PointY="1" Height="4.480205078125" IsExpanded="true" />
|
||||
<EntityTypeShape EntityType="northwindModel.Product" Width="1.5" PointX="5.25" PointY="15.875" Height="3.5186979166666674" IsExpanded="true" />
|
||||
<EntityTypeShape EntityType="northwindModel.Region" Width="1.5" PointX="5.75" PointY="7.125" Height="1.5956835937499996" IsExpanded="true" />
|
||||
<EntityTypeShape EntityType="northwindModel.Shipper" Width="1.5" PointX="3" PointY="5.875" Height="1.7879850260416674" IsExpanded="true" />
|
||||
<EntityTypeShape EntityType="northwindModel.Supplier" Width="1.5" PointX="3" PointY="17.25" Height="3.5186979166666674" IsExpanded="true" />
|
||||
<EntityTypeShape EntityType="northwindModel.Territory" Width="1.5" PointX="8" PointY="9.375" Height="1.9802864583333353" IsExpanded="true" />
|
||||
<AssociationConnector Association="northwindModel.FK_Products_Categories" ManuallyRouted="false">
|
||||
<ConnectorPoint PointX="4.5" PointY="16.177643229166666" />
|
||||
<ConnectorPoint PointX="5.25" PointY="16.177643229166666" /></AssociationConnector>
|
||||
<AssociationConnector Association="northwindModel.FK_Orders_Customers" ManuallyRouted="false">
|
||||
<ConnectorPoint PointX="4.5" PointY="3.2593489583333328" />
|
||||
<ConnectorPoint PointX="5.25" PointY="3.2593489583333328" /></AssociationConnector>
|
||||
<AssociationConnector Association="northwindModel.FK_Employees_Employees" ManuallyRouted="false">
|
||||
<ConnectorPoint PointX="3.5319230769230767" PointY="13.624410807291667" />
|
||||
<ConnectorPoint PointX="3.5319230769230767" PointY="13.874410807291667" />
|
||||
<ConnectorPoint PointX="3.9784615384615383" PointY="13.874410807291667" />
|
||||
<ConnectorPoint PointX="3.9784615384615383" PointY="13.624410807291667" /></AssociationConnector>
|
||||
<AssociationConnector Association="northwindModel.FK_Orders_Employees" ManuallyRouted="false">
|
||||
<ConnectorPoint PointX="4.5" PointY="11.203797200520834" />
|
||||
<ConnectorPoint PointX="5.46875" PointY="11.203797200520834" />
|
||||
<ConnectorPoint PointX="5.46875" PointY="5.480205078125" /></AssociationConnector>
|
||||
<AssociationConnector Association="northwindModel.FK_Order_Details_Orders" ManuallyRouted="false">
|
||||
<ConnectorPoint PointX="6.75" PointY="3.3074446614583328" />
|
||||
<ConnectorPoint PointX="7.5" PointY="3.3074446614583328" /></AssociationConnector>
|
||||
<AssociationConnector Association="northwindModel.FK_Order_Details_Products" ManuallyRouted="false">
|
||||
<ConnectorPoint PointX="6.75" PointY="17.634348958333334" />
|
||||
<ConnectorPoint PointX="7.71875" PointY="17.634348958333334" />
|
||||
<ConnectorPoint PointX="7.71875" PointY="4.4898893229166656" /></AssociationConnector>
|
||||
<AssociationConnector Association="northwindModel.FK_Orders_Shippers" ManuallyRouted="false">
|
||||
<ConnectorPoint PointX="4.5" PointY="6.46875" />
|
||||
<ConnectorPoint PointX="5.3281225" PointY="6.46875" />
|
||||
<ConnectorPoint PointX="5.3281225" PointY="5.480205078125" /></AssociationConnector>
|
||||
<AssociationConnector Association="northwindModel.FK_Products_Suppliers" ManuallyRouted="false">
|
||||
<ConnectorPoint PointX="4.5" PointY="18.321848958333334" />
|
||||
<ConnectorPoint PointX="5.25" PointY="18.321848958333334" /></AssociationConnector>
|
||||
<AssociationConnector Association="northwindModel.FK_Territories_Region" ManuallyRouted="false">
|
||||
<ConnectorPoint PointX="7.25" PointY="7.922841796875" />
|
||||
<ConnectorPoint PointX="7.635416666666667" PointY="7.9228417968749989" />
|
||||
<ConnectorPoint PointX="7.802083333333333" PointY="7.922841796875" />
|
||||
<ConnectorPoint PointX="8.75" PointY="7.922841796875" />
|
||||
<ConnectorPoint PointX="8.75" PointY="9.375" /></AssociationConnector>
|
||||
<AssociationConnector Association="northwindModel.CustomerCustomerDemo" ManuallyRouted="false">
|
||||
<ConnectorPoint PointX="2.25" PointY="3.2978417968750002" />
|
||||
<ConnectorPoint PointX="3" PointY="3.2978417968750002" /></AssociationConnector>
|
||||
<AssociationConnector Association="northwindModel.EmployeeTerritories" ManuallyRouted="false">
|
||||
<ConnectorPoint PointX="4.5" PointY="10.226898600260416" />
|
||||
<ConnectorPoint PointX="5.385416666666667" PointY="10.226898600260416" />
|
||||
<ConnectorPoint PointX="5.552083333333333" PointY="10.226898600260416" />
|
||||
<ConnectorPoint PointX="7.635416666666667" PointY="10.226898600260416" />
|
||||
<ConnectorPoint PointX="7.802083333333333" PointY="10.226898600260416" />
|
||||
<ConnectorPoint PointX="8" PointY="10.226898600260416" /></AssociationConnector></Diagram></Diagrams>
|
||||
</Designer>
|
||||
</edmx:Edmx>
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web.Mvc;
|
||||
using Microsoft.TestCommon;
|
||||
using Microsoft.Web.Http.Data.Test;
|
||||
using Microsoft.Web.UnitTestUtil;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.Helpers.Test
|
||||
{
|
||||
public class UpshotExtensionsTests
|
||||
{
|
||||
private static readonly int _northwindProductsContextHash = -453854022;
|
||||
|
||||
[Fact]
|
||||
public void VerifyUpshotContextHelperOutput()
|
||||
{
|
||||
HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
|
||||
Regex rgx = new Regex("\\s+");
|
||||
|
||||
string northwindProductsContext = UpshotExtensions.UpshotContext(html, true).DataSource<NorthwindEFTestController>(x => x.GetProducts(), "myUrl", "GetProducts").ToHtmlString();
|
||||
int northwindProductsContextHash = rgx.Replace(northwindProductsContext, "").GetHashCode();
|
||||
Assert.Equal(northwindProductsContextHash, _northwindProductsContextHash);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="EntityFramework" version="5.0.0" />
|
||||
<package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net40" />
|
||||
<package id="Moq" version="4.0.10827" />
|
||||
<package id="Newtonsoft.Json" version="4.5.6" targetFramework="net40" />
|
||||
<package id="xunit" version="1.9.1" targetFramework="net40" />
|
||||
<package id="xunit.extensions" version="1.9.1" targetFramework="net40" />
|
||||
</packages>
|
|
@ -1,165 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.TestCommon;
|
||||
using Microsoft.Web.Http.Data;
|
||||
using Microsoft.Web.Http.Data.Test.Models;
|
||||
|
||||
namespace System.ServiceModel.DomainServices.Server.Test
|
||||
{
|
||||
public class ChangeSetTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Verify ChangeSet validation when specifying/requesting original for Insert operations.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Changeset_OriginalInvalidForInserts()
|
||||
{
|
||||
// can't specify an original for an insert operation
|
||||
Product curr = new Product { ProductID = 1 };
|
||||
Product orig = new Product { ProductID = 1 };
|
||||
ChangeSetEntry entry = new ChangeSetEntry { Id = 1, Entity = curr, OriginalEntity = orig, Operation = ChangeOperation.Insert };
|
||||
ChangeSet cs = null;
|
||||
Assert.Throws<InvalidOperationException>(delegate
|
||||
{
|
||||
cs = new ChangeSet(new ChangeSetEntry[] { entry });
|
||||
},
|
||||
String.Format(Resource.InvalidChangeSet, Resource.InvalidChangeSet_InsertsCantHaveOriginal));
|
||||
|
||||
// get original should throw for insert operations
|
||||
entry = new ChangeSetEntry { Id = 1, Entity = curr, OriginalEntity = null, Operation = ChangeOperation.Insert };
|
||||
cs = new ChangeSet(new ChangeSetEntry[] { entry });
|
||||
Assert.Throws<InvalidOperationException>(delegate
|
||||
{
|
||||
cs.GetOriginal(curr);
|
||||
},
|
||||
String.Format(Resource.ChangeSet_OriginalNotValidForInsert));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_HasErrors()
|
||||
{
|
||||
ChangeSet changeSet = this.GenerateChangeSet();
|
||||
Assert.False(changeSet.HasError);
|
||||
|
||||
changeSet = this.GenerateChangeSet();
|
||||
changeSet.ChangeSetEntries.First().ValidationErrors = new List<ValidationResultInfo>() { new ValidationResultInfo("Error", new[] { "Error" }) };
|
||||
Assert.True(changeSet.HasError, "Expected ChangeSet to have errors");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOriginal()
|
||||
{
|
||||
ChangeSet changeSet = this.GenerateChangeSet();
|
||||
ChangeSetEntry op = changeSet.ChangeSetEntries.First();
|
||||
|
||||
Product currentEntity = new Product();
|
||||
Product originalEntity = new Product();
|
||||
|
||||
op.Entity = currentEntity;
|
||||
op.OriginalEntity = originalEntity;
|
||||
|
||||
Product changeSetOriginalEntity = changeSet.GetOriginal(currentEntity);
|
||||
|
||||
// Verify we returned the original
|
||||
Assert.Same(originalEntity, changeSetOriginalEntity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOriginal_EntityExistsMoreThanOnce()
|
||||
{
|
||||
ChangeSet changeSet = this.GenerateChangeSet();
|
||||
ChangeSetEntry op1 = changeSet.ChangeSetEntries.Skip(0).First();
|
||||
ChangeSetEntry op2 = changeSet.ChangeSetEntries.Skip(1).First();
|
||||
ChangeSetEntry op3 = changeSet.ChangeSetEntries.Skip(2).First();
|
||||
|
||||
Product currentEntity = new Product(), originalEntity = new Product();
|
||||
|
||||
op1.Entity = currentEntity;
|
||||
op1.OriginalEntity = originalEntity;
|
||||
|
||||
op2.Entity = currentEntity;
|
||||
op2.OriginalEntity = originalEntity;
|
||||
|
||||
op3.Entity = currentEntity;
|
||||
op3.OriginalEntity = null;
|
||||
|
||||
Product changeSetOriginalEntity = changeSet.GetOriginal(currentEntity);
|
||||
|
||||
// Verify we returned the original
|
||||
Assert.Same(originalEntity, changeSetOriginalEntity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOriginal_InvalidArgs()
|
||||
{
|
||||
ChangeSet changeSet = this.GenerateChangeSet();
|
||||
|
||||
Assert.ThrowsArgumentNull(
|
||||
() => changeSet.GetOriginal<Product>(null),
|
||||
"clientEntity");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOriginal_EntityOperationNotFound()
|
||||
{
|
||||
ChangeSet changeSet = this.GenerateChangeSet();
|
||||
Assert.Throws<ArgumentException>(
|
||||
() => changeSet.GetOriginal(new Product()),
|
||||
Resource.ChangeSet_ChangeSetEntryNotFound);
|
||||
}
|
||||
|
||||
private ChangeSet GenerateChangeSet()
|
||||
{
|
||||
return new ChangeSet(this.GenerateEntityOperations(false));
|
||||
}
|
||||
|
||||
private IEnumerable<ChangeSetEntry> GenerateEntityOperations(bool alternateTypes)
|
||||
{
|
||||
List<ChangeSetEntry> ops = new List<ChangeSetEntry>(10);
|
||||
|
||||
int id = 1;
|
||||
for (int i = 0; i < ops.Capacity; ++i)
|
||||
{
|
||||
object entity, originalEntity;
|
||||
|
||||
if (!alternateTypes || i % 2 == 0)
|
||||
{
|
||||
entity = new MockEntity1() { FullName = String.Format("FName{0} LName{0}", i) };
|
||||
originalEntity = new MockEntity1() { FullName = String.Format("OriginalFName{0} OriginalLName{0}", i) };
|
||||
}
|
||||
else
|
||||
{
|
||||
entity = new MockEntity2() { FullNameAndID = String.Format("FName{0} LName{0} ID{0}", i) };
|
||||
originalEntity = new MockEntity2() { FullNameAndID = String.Format("OriginalFName{0} OriginalLName{0} OriginalID{0}", i) };
|
||||
}
|
||||
|
||||
ops.Add(new ChangeSetEntry { Id = id++, Entity = entity, OriginalEntity = originalEntity, Operation = ChangeOperation.Update });
|
||||
}
|
||||
|
||||
return ops;
|
||||
}
|
||||
|
||||
public class MockStoreEntity
|
||||
{
|
||||
public int ID { get; set; }
|
||||
public string FirstName { get; set; }
|
||||
public string LastName { get; set; }
|
||||
}
|
||||
|
||||
public class MockEntity1
|
||||
{
|
||||
public string FullName { get; set; }
|
||||
}
|
||||
|
||||
public class MockEntity2
|
||||
{
|
||||
public string FullNameAndID { get; set; }
|
||||
}
|
||||
|
||||
public class MockDerivedEntity : MockEntity1
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Web.Http.Data.Test.Models;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.Test
|
||||
{
|
||||
public class CatalogController : DataController
|
||||
{
|
||||
private Product[] products;
|
||||
|
||||
public CatalogController()
|
||||
{
|
||||
this.products = new Product[] {
|
||||
new Product { ProductID = 1, ProductName = "Frish Gnarbles", UnitPrice = 12.33M, UnitsInStock = 55 },
|
||||
new Product { ProductID = 2, ProductName = "Crispy Snarfs", UnitPrice = 4.22M, UnitsInStock = 11 },
|
||||
new Product { ProductID = 1, ProductName = "Cheezy Snax", UnitPrice = 2.99M, UnitsInStock = 21 },
|
||||
new Product { ProductID = 1, ProductName = "Fruit Yummies", UnitPrice = 5.55M, UnitsInStock = 88 },
|
||||
new Product { ProductID = 1, ProductName = "Choco Wafers", UnitPrice = 1.87M, UnitsInStock = 109 },
|
||||
new Product { ProductID = 1, ProductName = "Fritter Flaps", UnitPrice = 2.45M, UnitsInStock = 444 },
|
||||
new Product { ProductID = 1, ProductName = "Yummy Bears", UnitPrice = 2.00M, UnitsInStock = 27 },
|
||||
new Product { ProductID = 1, ProductName = "Cheddar Gnomes", UnitPrice = 3.99M, UnitsInStock = 975 },
|
||||
new Product { ProductID = 1, ProductName = "Beefcicles", UnitPrice = 0.99M, UnitsInStock = 634 },
|
||||
new Product { ProductID = 1, ProductName = "Butterscotchies", UnitPrice = 1.00M, UnitsInStock = 789 }
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerable<Order_Detail> GetDetails(int orderId)
|
||||
{
|
||||
return Enumerable.Empty<Order_Detail>();
|
||||
}
|
||||
|
||||
public void InsertOrder(Order order)
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdateProduct(Product product)
|
||||
{
|
||||
// demonstrate that the current ActionContext can be accessed by
|
||||
// controller actions
|
||||
string host = this.ActionContext.ControllerContext.Request.Headers.Host;
|
||||
}
|
||||
|
||||
public void InsertOrderDetail(Order_Detail detail)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Microsoft.Web.Http.Data.EntityFramework;
|
||||
using Microsoft.Web.Http.Data.Test.Models.EF;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.Test
|
||||
{
|
||||
public class NorthwindEFTestController : LinqToEntitiesDataController<NorthwindEntities>
|
||||
{
|
||||
public IQueryable<Product> GetProducts()
|
||||
{
|
||||
return this.ObjectContext.Products;
|
||||
}
|
||||
|
||||
public void InsertProduct(Product product)
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdateProduct(Product product)
|
||||
{
|
||||
}
|
||||
|
||||
protected override NorthwindEntities CreateObjectContext()
|
||||
{
|
||||
return new NorthwindEntities(TestHelpers.GetTestEFConnectionString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Microsoft.Web.Http.Data.Test.Models.EF
|
||||
{
|
||||
[MetadataType(typeof(ProductMetadata))]
|
||||
public partial class Product
|
||||
{
|
||||
internal sealed class ProductMetadata
|
||||
{
|
||||
[Editable(false, AllowInitialValue = true)]
|
||||
[StringLength(777, MinimumLength = 2)]
|
||||
public string QuantityPerUnit { get; set; }
|
||||
|
||||
[Range(0, 1000000)]
|
||||
public string UnitPrice { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,193 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using Microsoft.TestCommon;
|
||||
using Microsoft.Web.Http.Data.EntityFramework;
|
||||
using Microsoft.Web.Http.Data.EntityFramework.Metadata;
|
||||
using Microsoft.Web.Http.Data.Test.Models;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.Test
|
||||
{
|
||||
public class DataControllerDescriptionTest
|
||||
{
|
||||
// verify that the LinqToEntitiesMetadataProvider is registered by default for
|
||||
// LinqToEntitiesDataController<T> derived types
|
||||
[Fact]
|
||||
public void EFMetadataProvider_AttributeInference()
|
||||
{
|
||||
HttpConfiguration configuration = new HttpConfiguration();
|
||||
HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor
|
||||
{
|
||||
Configuration = configuration,
|
||||
ControllerType = typeof(NorthwindEFTestController),
|
||||
};
|
||||
DataControllerDescription description = GetDataControllerDescription(typeof(NorthwindEFTestController));
|
||||
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product));
|
||||
|
||||
// verify key attribute
|
||||
Assert.NotNull(properties["ProductID"].Attributes[typeof(KeyAttribute)]);
|
||||
Assert.Null(properties["ProductName"].Attributes[typeof(KeyAttribute)]);
|
||||
|
||||
// verify StringLengthAttribute
|
||||
StringLengthAttribute sla = (StringLengthAttribute)properties["ProductName"].Attributes[typeof(StringLengthAttribute)];
|
||||
Assert.NotNull(sla);
|
||||
Assert.Equal(40, sla.MaximumLength);
|
||||
|
||||
// verify RequiredAttribute
|
||||
RequiredAttribute ra = (RequiredAttribute)properties["ProductName"].Attributes[typeof(RequiredAttribute)];
|
||||
Assert.NotNull(ra);
|
||||
Assert.False(ra.AllowEmptyStrings);
|
||||
|
||||
// verify association attribute
|
||||
AssociationAttribute aa = (AssociationAttribute)properties["Category"].Attributes[typeof(AssociationAttribute)];
|
||||
Assert.NotNull(aa);
|
||||
Assert.Equal("Category_Product", aa.Name);
|
||||
Assert.True(aa.IsForeignKey);
|
||||
Assert.Equal("CategoryID", aa.ThisKey);
|
||||
Assert.Equal("CategoryID", aa.OtherKey);
|
||||
|
||||
// verify metadata from "buddy class"
|
||||
PropertyDescriptor pd = properties["QuantityPerUnit"];
|
||||
sla = (StringLengthAttribute)pd.Attributes[typeof(StringLengthAttribute)];
|
||||
Assert.NotNull(sla);
|
||||
Assert.Equal(777, sla.MaximumLength);
|
||||
EditableAttribute ea = (EditableAttribute)pd.Attributes[typeof(EditableAttribute)];
|
||||
Assert.False(ea.AllowEdit);
|
||||
Assert.True(ea.AllowInitialValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EFTypeDescriptor_ExcludedEntityMembers()
|
||||
{
|
||||
PropertyDescriptor pd = TypeDescriptor.GetProperties(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product))["EntityState"];
|
||||
Assert.True(LinqToEntitiesTypeDescriptor.ShouldExcludeEntityMember(pd));
|
||||
|
||||
pd = TypeDescriptor.GetProperties(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product))["EntityState"];
|
||||
Assert.True(LinqToEntitiesTypeDescriptor.ShouldExcludeEntityMember(pd));
|
||||
|
||||
pd = TypeDescriptor.GetProperties(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product))["SupplierReference"];
|
||||
Assert.True(LinqToEntitiesTypeDescriptor.ShouldExcludeEntityMember(pd));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DescriptionValidation_NonAuthorizationFilter()
|
||||
{
|
||||
Assert.Throws<NotSupportedException>(
|
||||
() => GetDataControllerDescription(typeof(InvalidController_NonAuthMethodFilter)),
|
||||
String.Format(String.Format(Resource.InvalidAction_UnsupportedFilterType, "InvalidController_NonAuthMethodFilter", "UpdateProduct")));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that associated entities are correctly registered in the description when
|
||||
/// using explicit data contracts
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AssociatedEntityTypeDiscovery_ExplicitDataContract()
|
||||
{
|
||||
DataControllerDescription description = GetDataControllerDescription(typeof(IncludedAssociationTestController_ExplicitDataContract));
|
||||
List<Type> entityTypes = description.EntityTypes.ToList();
|
||||
Assert.Equal(8, entityTypes.Count);
|
||||
Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Order)));
|
||||
Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Order_Detail)));
|
||||
Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Customer)));
|
||||
Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Employee)));
|
||||
Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product)));
|
||||
Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Category)));
|
||||
Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Supplier)));
|
||||
Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Shipper)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that associated entities are correctly registered in the description when
|
||||
/// using implicit data contracts
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AssociatedEntityTypeDiscovery_ImplicitDataContract()
|
||||
{
|
||||
DataControllerDescription description = GetDataControllerDescription(typeof(IncludedAssociationTestController_ImplicitDataContract));
|
||||
List<Type> entityTypes = description.EntityTypes.ToList();
|
||||
Assert.Equal(3, entityTypes.Count);
|
||||
Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.Customer)));
|
||||
Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.Order)));
|
||||
Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.Order_Detail)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that DataControllerDescription correctly handles Task returning actions and discovers
|
||||
/// entity types from those as well (unwrapping the task type).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TaskReturningGetActions()
|
||||
{
|
||||
DataControllerDescription desc = GetDataControllerDescription(typeof(TaskReturningGetActionsController));
|
||||
Assert.Equal(4, desc.EntityTypes.Count());
|
||||
Assert.True(desc.EntityTypes.Contains(typeof(City)));
|
||||
Assert.True(desc.EntityTypes.Contains(typeof(CityWithInfo)));
|
||||
Assert.True(desc.EntityTypes.Contains(typeof(CityWithEditHistory)));
|
||||
Assert.True(desc.EntityTypes.Contains(typeof(State)));
|
||||
}
|
||||
|
||||
internal static DataControllerDescription GetDataControllerDescription(Type controllerType)
|
||||
{
|
||||
HttpConfiguration configuration = new HttpConfiguration();
|
||||
HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor
|
||||
{
|
||||
Configuration = configuration,
|
||||
ControllerType = controllerType
|
||||
};
|
||||
return DataControllerDescription.GetDescription(controllerDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
internal class InvalidController_NonAuthMethodFilter : DataController
|
||||
{
|
||||
// attempt to apply a non-auth filter
|
||||
[TestActionFilter]
|
||||
public void UpdateProduct(Microsoft.Web.Http.Data.Test.Models.EF.Product product)
|
||||
{
|
||||
}
|
||||
|
||||
// the restriction doesn't apply for non CUD actions
|
||||
[TestActionFilter]
|
||||
public IEnumerable<Microsoft.Web.Http.Data.Test.Models.EF.Product> GetProducts()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal class TaskReturningGetActionsController : DataController
|
||||
{
|
||||
public Task<IEnumerable<City>> GetCities()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task<State> GetState(string name)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
|
||||
public class TestActionFilterAttribute : ActionFilterAttribute
|
||||
{
|
||||
}
|
||||
|
||||
internal class IncludedAssociationTestController_ExplicitDataContract : LinqToEntitiesDataController<Microsoft.Web.Http.Data.Test.Models.EF.NorthwindEntities>
|
||||
{
|
||||
public IQueryable<Microsoft.Web.Http.Data.Test.Models.EF.Order> GetOrders() { return null; }
|
||||
}
|
||||
|
||||
internal class IncludedAssociationTestController_ImplicitDataContract : DataController
|
||||
{
|
||||
public IQueryable<Microsoft.Web.Http.Data.Test.Models.Customer> GetCustomers() { return null; }
|
||||
}
|
||||
}
|
|
@ -1,374 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Dispatcher;
|
||||
using System.Web.Http.Filters;
|
||||
using System.Web.Http.Routing;
|
||||
using Microsoft.TestCommon;
|
||||
using Microsoft.Web.Http.Data.Test.Models;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.Test
|
||||
{
|
||||
public class DataControllerSubmitTests
|
||||
{
|
||||
// Verify that POSTs directly to CUD actions still go through the submit pipeline
|
||||
//[Fact] -- disabled by bradwils on 13 July 2012 because it's flaky
|
||||
public void Submit_Proxy_Insert()
|
||||
{
|
||||
Order order = new Order { OrderID = 1, OrderDate = DateTime.Now };
|
||||
|
||||
HttpResponseMessage response = this.ExecuteSelfHostRequest(TestConstants.CatalogUrl + "InsertOrder", "Catalog", order);
|
||||
Order resultOrder = response.Content.ReadAsAsync<Order>().Result;
|
||||
Assert.NotNull(resultOrder);
|
||||
}
|
||||
|
||||
// Submit a changeset with multiple entries
|
||||
[Fact]
|
||||
public void Submit_Multiple_Success()
|
||||
{
|
||||
Order order = new Order { OrderID = 1, OrderDate = DateTime.Now };
|
||||
Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" };
|
||||
ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
|
||||
new ChangeSetEntry { Id = 1, Entity = order, Operation = ChangeOperation.Insert },
|
||||
new ChangeSetEntry { Id = 2, Entity = product, Operation = ChangeOperation.Update }
|
||||
};
|
||||
|
||||
ChangeSetEntry[] resultChangeSet = this.ExecuteSubmit(TestConstants.CatalogUrl + "Submit", "Catalog", changeSet);
|
||||
Assert.Equal(2, resultChangeSet.Length);
|
||||
Assert.True(resultChangeSet.All(p => !p.HasError));
|
||||
}
|
||||
|
||||
// Submit a changeset with one parent object and multiple dependent children
|
||||
[Fact]
|
||||
public void Submit_Tree_Success()
|
||||
{
|
||||
Order order = new Order { OrderID = 1, OrderDate = DateTime.Now };
|
||||
Order_Detail d1 = new Order_Detail { ProductID = 1 };
|
||||
Order_Detail d2 = new Order_Detail { ProductID = 2 };
|
||||
Dictionary<string, int[]> detailsAssociation = new Dictionary<string, int[]>();
|
||||
detailsAssociation.Add("Order_Details", new int[] { 2, 3 });
|
||||
ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
|
||||
new ChangeSetEntry { Id = 1, Entity = order, Operation = ChangeOperation.Insert, Associations = detailsAssociation },
|
||||
new ChangeSetEntry { Id = 2, Entity = d1, Operation = ChangeOperation.Insert },
|
||||
new ChangeSetEntry { Id = 3, Entity = d2, Operation = ChangeOperation.Insert }
|
||||
};
|
||||
|
||||
ChangeSetEntry[] resultChangeSet = this.ExecuteSubmit(TestConstants.CatalogUrl + "Submit", "Catalog", changeSet);
|
||||
Assert.Equal(3, resultChangeSet.Length);
|
||||
Assert.True(resultChangeSet.All(p => !p.HasError));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End to end validation scenario showing changeset validation. DataAnnotations validation attributes are applied to
|
||||
/// the model by DataController metadata providers (metadata coming all the way from the EF model, as well as "buddy
|
||||
/// class" metadata), and these are validated during changeset validation. The validation results per entity/member are
|
||||
/// returned via the changeset and verified.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Submit_Validation_Failure()
|
||||
{
|
||||
Microsoft.Web.Http.Data.Test.Models.EF.Product newProduct = new Microsoft.Web.Http.Data.Test.Models.EF.Product { ProductID = 1, ProductName = String.Empty, UnitPrice = -1 };
|
||||
Microsoft.Web.Http.Data.Test.Models.EF.Product updateProduct = new Microsoft.Web.Http.Data.Test.Models.EF.Product { ProductID = 1, ProductName = new string('x', 50), UnitPrice = 55.77M };
|
||||
ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
|
||||
new ChangeSetEntry { Id = 1, Entity = newProduct, Operation = ChangeOperation.Insert },
|
||||
new ChangeSetEntry { Id = 2, Entity = updateProduct, Operation = ChangeOperation.Update }
|
||||
};
|
||||
|
||||
HttpResponseMessage response = this.ExecuteSelfHostRequest("http://testhost/NorthwindEFTest/Submit", "NorthwindEFTest", changeSet);
|
||||
changeSet = response.Content.ReadAsAsync<ChangeSetEntry[]>().Result;
|
||||
|
||||
// errors for the new product
|
||||
ValidationResultInfo[] errors = changeSet[0].ValidationErrors.ToArray();
|
||||
Assert.Equal(2, errors.Length);
|
||||
Assert.True(changeSet[0].HasError);
|
||||
|
||||
// validation rule inferred from EF model
|
||||
Assert.Equal("ProductName", errors[0].SourceMemberNames.Single());
|
||||
Assert.Equal("The ProductName field is required.", errors[0].Message);
|
||||
|
||||
// validation rule coming from buddy class
|
||||
Assert.Equal("UnitPrice", errors[1].SourceMemberNames.Single());
|
||||
Assert.Equal("The field UnitPrice must be between 0 and 1000000.", errors[1].Message);
|
||||
|
||||
// errors for the updated product
|
||||
errors = changeSet[1].ValidationErrors.ToArray();
|
||||
Assert.Equal(1, errors.Length);
|
||||
Assert.True(changeSet[1].HasError);
|
||||
|
||||
// validation rule inferred from EF model
|
||||
Assert.Equal("ProductName", errors[0].SourceMemberNames.Single());
|
||||
Assert.Equal("The field ProductName must be a string with a maximum length of 40.", errors[0].Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Submit_Authorization_Success()
|
||||
{
|
||||
TestAuthAttribute.Reset();
|
||||
|
||||
Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" };
|
||||
ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
|
||||
new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Update }
|
||||
};
|
||||
|
||||
ChangeSetEntry[] resultChangeSet = this.ExecuteSubmit("http://testhost/TestAuth/Submit", "TestAuth", changeSet);
|
||||
Assert.Equal(1, resultChangeSet.Length);
|
||||
Assert.True(TestAuthAttribute.Log.SequenceEqual(new string[] { "Global", "Class", "SubmitMethod", "UserMethod" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Submit_Authorization_Fail_UserMethod()
|
||||
{
|
||||
TestAuthAttribute.Reset();
|
||||
|
||||
Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" };
|
||||
ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
|
||||
new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Update }
|
||||
};
|
||||
|
||||
TestAuthAttribute.FailLevel = "UserMethod";
|
||||
HttpResponseMessage response = this.ExecuteSelfHostRequest("http://testhost/TestAuth/Submit", "TestAuth", changeSet);
|
||||
|
||||
Assert.True(TestAuthAttribute.Log.SequenceEqual(new string[] { "Global", "Class", "SubmitMethod", "UserMethod" }));
|
||||
Assert.Equal("Not Authorized", response.ReasonPhrase);
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Submit_Authorization_Fail_SubmitMethod()
|
||||
{
|
||||
TestAuthAttribute.Reset();
|
||||
|
||||
Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" };
|
||||
ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
|
||||
new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Update }
|
||||
};
|
||||
|
||||
TestAuthAttribute.FailLevel = "SubmitMethod";
|
||||
HttpResponseMessage response = this.ExecuteSelfHostRequest("http://testhost/TestAuth/Submit", "TestAuth", changeSet);
|
||||
|
||||
Assert.True(TestAuthAttribute.Log.SequenceEqual(new string[] { "Global", "Class", "SubmitMethod" }));
|
||||
Assert.Equal("Not Authorized", response.ReasonPhrase);
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Submit_Authorization_Fail_Class()
|
||||
{
|
||||
TestAuthAttribute.Reset();
|
||||
|
||||
Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" };
|
||||
ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
|
||||
new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Update }
|
||||
};
|
||||
|
||||
TestAuthAttribute.FailLevel = "Class";
|
||||
HttpResponseMessage response = this.ExecuteSelfHostRequest("http://testhost/TestAuth/Submit", "TestAuth", changeSet);
|
||||
|
||||
Assert.True(TestAuthAttribute.Log.SequenceEqual(new string[] { "Global", "Class" }));
|
||||
Assert.Equal("Not Authorized", response.ReasonPhrase);
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Submit_Authorization_Fail_Global()
|
||||
{
|
||||
TestAuthAttribute.Reset();
|
||||
|
||||
Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" };
|
||||
ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
|
||||
new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Update }
|
||||
};
|
||||
|
||||
TestAuthAttribute.FailLevel = "Global";
|
||||
HttpResponseMessage response = this.ExecuteSelfHostRequest("http://testhost/TestAuth/Submit", "TestAuth", changeSet);
|
||||
|
||||
Assert.True(TestAuthAttribute.Log.SequenceEqual(new string[] { "Global" }));
|
||||
Assert.Equal("Not Authorized", response.ReasonPhrase);
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
// Verify that a CUD operation that isn't supported for a given entity type
|
||||
// results in a server error
|
||||
[Fact]
|
||||
public void Submit_ResolveActions_UnsupportedAction()
|
||||
{
|
||||
Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" };
|
||||
ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
|
||||
new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Delete }
|
||||
};
|
||||
|
||||
HttpConfiguration configuration = new HttpConfiguration();
|
||||
HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor(configuration, "NorthwindEFTestController", typeof(NorthwindEFTestController));
|
||||
DataControllerDescription description = DataControllerDescription.GetDescription(controllerDescriptor);
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => DataController.ResolveActions(description, changeSet),
|
||||
String.Format(Resource.DataController_InvalidAction, "Delete", "Product"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a full roundtrip Submit request for the specified changeset, going through
|
||||
/// the full serialization pipeline.
|
||||
/// </summary>
|
||||
private ChangeSetEntry[] ExecuteSubmit(string url, string controllerName, ChangeSetEntry[] changeSet)
|
||||
{
|
||||
HttpResponseMessage response = this.ExecuteSelfHostRequest(url, controllerName, changeSet);
|
||||
ChangeSetEntry[] resultChangeSet = GetChangesetResponse(response);
|
||||
return changeSet;
|
||||
}
|
||||
|
||||
private HttpResponseMessage ExecuteSelfHostRequest(string url, string controller, object data)
|
||||
{
|
||||
return ExecuteSelfHostRequest(url, controller, data, "application/json");
|
||||
}
|
||||
|
||||
private HttpResponseMessage ExecuteSelfHostRequest(string url, string controller, object data, string mediaType)
|
||||
{
|
||||
HttpConfiguration config = new HttpConfiguration();
|
||||
IHttpRoute routeData;
|
||||
if (!config.Routes.TryGetValue(controller, out routeData))
|
||||
{
|
||||
HttpRoute route = new HttpRoute("{controller}/{action}", new HttpRouteValueDictionary(controller));
|
||||
config.Routes.Add(controller, route);
|
||||
}
|
||||
|
||||
HttpControllerDispatcher dispatcher = new HttpControllerDispatcher(config);
|
||||
HttpServer server = new HttpServer(config, dispatcher);
|
||||
HttpMessageInvoker invoker = new HttpMessageInvoker(server);
|
||||
|
||||
string serializedChangeSet = String.Empty;
|
||||
if (mediaType == "application/json")
|
||||
{
|
||||
JsonSerializer serializer = new JsonSerializer() { PreserveReferencesHandling = PreserveReferencesHandling.Objects, TypeNameHandling = TypeNameHandling.All };
|
||||
MemoryStream ms = new MemoryStream();
|
||||
JsonWriter writer = new JsonTextWriter(new StreamWriter(ms));
|
||||
serializer.Serialize(writer, data);
|
||||
writer.Flush();
|
||||
ms.Seek(0, 0);
|
||||
serializedChangeSet = Encoding.UTF8.GetString(ms.GetBuffer()).TrimEnd('\0');
|
||||
}
|
||||
else
|
||||
{
|
||||
DataContractSerializer ser = new DataContractSerializer(data.GetType(), GetTestKnownTypes());
|
||||
MemoryStream ms = new MemoryStream();
|
||||
ser.WriteObject(ms, data);
|
||||
ms.Flush();
|
||||
ms.Seek(0, 0);
|
||||
serializedChangeSet = Encoding.UTF8.GetString(ms.GetBuffer()).TrimEnd('\0');
|
||||
}
|
||||
|
||||
HttpRequestMessage request = TestHelpers.CreateTestMessage(url, HttpMethod.Post, config);
|
||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
|
||||
request.Content = new StringContent(serializedChangeSet, Encoding.UTF8, mediaType);
|
||||
|
||||
return invoker.SendAsync(request, CancellationToken.None).Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For the given Submit response, serialize and deserialize the content. This forces the
|
||||
/// formatter pipeline to run so we can verify that registered serializers are being used
|
||||
/// properly.
|
||||
/// </summary>
|
||||
private ChangeSetEntry[] GetChangesetResponse(HttpResponseMessage responseMessage)
|
||||
{
|
||||
// serialize the content to a stream
|
||||
ObjectContent content = (ObjectContent)responseMessage.Content;
|
||||
MemoryStream ms = new MemoryStream();
|
||||
content.CopyToAsync(ms).Wait();
|
||||
ms.Flush();
|
||||
ms.Seek(0, 0);
|
||||
|
||||
// deserialize based on content type
|
||||
ChangeSetEntry[] changeSet = null;
|
||||
string mediaType = responseMessage.RequestMessage.Content.Headers.ContentType.MediaType;
|
||||
if (mediaType == "application/json")
|
||||
{
|
||||
JsonSerializer ser = new JsonSerializer() { PreserveReferencesHandling = PreserveReferencesHandling.Objects, TypeNameHandling = TypeNameHandling.All };
|
||||
changeSet = (ChangeSetEntry[])ser.Deserialize(new JsonTextReader(new StreamReader(ms)), content.ObjectType);
|
||||
}
|
||||
else
|
||||
{
|
||||
DataContractSerializer ser = new DataContractSerializer(content.ObjectType, GetTestKnownTypes());
|
||||
changeSet = (ChangeSetEntry[])ser.ReadObject(ms);
|
||||
}
|
||||
|
||||
return changeSet;
|
||||
}
|
||||
|
||||
private IEnumerable<Type> GetTestKnownTypes()
|
||||
{
|
||||
List<Type> knownTypes = new List<Type>(new Type[] { typeof(Order), typeof(Product), typeof(Order_Detail) });
|
||||
knownTypes.AddRange(new Type[] { typeof(Microsoft.Web.Http.Data.Test.Models.EF.Order), typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product), typeof(Microsoft.Web.Http.Data.Test.Models.EF.Order_Detail) });
|
||||
return knownTypes;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test controller used for multi-level authorization testing
|
||||
/// </summary>
|
||||
[TestAuth(Level = "Class")]
|
||||
public class TestAuthController : DataController
|
||||
{
|
||||
[TestAuth(Level = "UserMethod")]
|
||||
public void UpdateProduct(Product product)
|
||||
{
|
||||
}
|
||||
|
||||
[TestAuth(Level = "SubmitMethod")]
|
||||
public override bool Submit(ChangeSet changeSet)
|
||||
{
|
||||
return base.Submit(changeSet);
|
||||
}
|
||||
|
||||
protected override void Initialize(HttpControllerContext controllerContext)
|
||||
{
|
||||
controllerContext.Configuration.Filters.Add(new TestAuthAttribute() { Level = "Global" });
|
||||
|
||||
base.Initialize(controllerContext);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test authorization attribute used to verify authorization behavior.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
|
||||
public class TestAuthAttribute : AuthorizationFilterAttribute
|
||||
{
|
||||
public string Level;
|
||||
|
||||
public static string FailLevel;
|
||||
|
||||
public static List<string> Log = new List<string>();
|
||||
|
||||
public override void OnAuthorization(HttpActionContext context)
|
||||
{
|
||||
TestAuthAttribute.Log.Add(Level);
|
||||
|
||||
if (FailLevel != null && FailLevel == Level)
|
||||
{
|
||||
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
|
||||
response.ReasonPhrase = "Not Authorized";
|
||||
context.Response = response;
|
||||
}
|
||||
|
||||
base.OnAuthorization(context);
|
||||
}
|
||||
|
||||
public static void Reset()
|
||||
{
|
||||
FailLevel = null;
|
||||
Log.Clear();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.TestCommon;
|
||||
using Microsoft.Web.Http.Data.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Web.Http.Data.Test
|
||||
{
|
||||
public class MetadataExtensionsTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Serialize metadata for a test controller exposing types with various
|
||||
/// metadata annotations.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TestMetadataSerialization()
|
||||
{
|
||||
JToken metadata = GenerateMetadata(typeof(TestController));
|
||||
string s = metadata.ToString(Formatting.None);
|
||||
Assert.True(s.Contains("{\"range\":[-10.0,20.5]}"));
|
||||
}
|
||||
|
||||
private static JToken GenerateMetadata(Type dataControllerType)
|
||||
{
|
||||
DataControllerDescription desc = DataControllerDescriptionTest.GetDataControllerDescription(dataControllerType);
|
||||
var metadata = DataControllerMetadataGenerator.GetMetadata(desc);
|
||||
|
||||
JObject metadataValue = new JObject();
|
||||
foreach (var m in metadata)
|
||||
{
|
||||
metadataValue.Add(m.EncodedTypeName, m.ToJToken());
|
||||
}
|
||||
|
||||
return metadataValue;
|
||||
}
|
||||
}
|
||||
|
||||
public class TestClass
|
||||
{
|
||||
[Key]
|
||||
public int ID { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Range(-10.0, 20.5)]
|
||||
public double Number { get; set; }
|
||||
|
||||
[StringLength(5)]
|
||||
public string Address { get; set; }
|
||||
}
|
||||
|
||||
public class TestController : DataController
|
||||
{
|
||||
public TestClass GetTestClass(int id)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче