Imported Upstream version 3.6.0

Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
This commit is contained in:
Jo Shields
2014-08-13 10:39:27 +01:00
commit a575963da9
50588 changed files with 8155799 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
// Copyright (c) Microsoft Corporation. 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;
using Microsoft.Web.Http.Data.Test.Models;
using Xunit;
using Assert = Microsoft.TestCommon.AssertEx;
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
{
}
}
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
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 }
};
}
[Queryable(ResultLimit = 9)]
public IQueryable<Product> GetProducts()
{
return this.products.AsQueryable();
}
[Queryable]
public IQueryable<Order> GetOrders()
{
return new Order[] {
new Order { OrderID = 1, CustomerID = "ALFKI" },
new Order { OrderID = 2, CustomerID = "CHOPS" }
}.AsQueryable();
}
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)
{
}
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Linq;
using Microsoft.Web.Http.Data.Test.Models;
namespace Microsoft.Web.Http.Data.Test
{
public class CitiesController : DataController
{
private CityData cityData = new CityData();
public IQueryable<City> GetCities()
{
return this.cityData.Cities.AsQueryable();
}
}
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. 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; }
}
}
}

View File

@@ -0,0 +1,194 @@
// Copyright (c) Microsoft Corporation. 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.Web.Http.Data.EntityFramework;
using Microsoft.Web.Http.Data.EntityFramework.Metadata;
using Microsoft.Web.Http.Data.Test.Models;
using Xunit;
using Assert = Microsoft.TestCommon.AssertEx;
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; }
}
}

View File

@@ -0,0 +1,226 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Dispatcher;
using System.Web.Http.Routing;
using Microsoft.Web.Http.Data.Test.Models;
using Xunit;
namespace Microsoft.Web.Http.Data.Test
{
public class DataControllerQueryTests
{
/// <summary>
/// Execute a simple query with limited results
/// </summary>
[Fact]
public void GetProducts()
{
HttpConfiguration config = GetTestConfiguration();
HttpServer server = GetTestCatalogServer(config);
HttpMessageInvoker invoker = new HttpMessageInvoker(server);
HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProducts", HttpMethod.Get, config);
HttpResponseMessage response = invoker.SendAsync(request, CancellationToken.None).Result;
Product[] products = response.Content.ReadAsAsync<IQueryable<Product>>().Result.ToArray();
Assert.Equal(9, products.Length);
}
/// <summary>
/// Execute a query with an OData filter specified
/// </summary>
[Fact]
public void Query_Filter()
{
HttpConfiguration config = GetTestConfiguration();
HttpServer server = GetTestCatalogServer(config);
HttpMessageInvoker invoker = new HttpMessageInvoker(server);
string query = "?$filter=UnitPrice lt 5.0";
HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProducts" + query, HttpMethod.Get, config);
HttpResponseMessage response = invoker.SendAsync(request, CancellationToken.None).Result;
Product[] products = response.Content.ReadAsAsync<IQueryable<Product>>().Result.ToArray();
Assert.Equal(8, products.Length);
}
/// <summary>
/// Verify that the json/xml formatter instances are not shared between controllers, since
/// their serializers are configured per controller.
/// </summary>
[Fact(Skip = "Need to verify if this test still makes sense given changed ObjectContent design")]
public void Query_VerifyFormatterConfiguration()
{
HttpConfiguration config = GetTestConfiguration();
HttpServer catalogServer = GetTestCatalogServer(config);
HttpMessageInvoker catalogInvoker = new HttpMessageInvoker(catalogServer);
HttpServer citiesServer = GetTestCitiesServer(config);
HttpMessageInvoker citiesInvoker = new HttpMessageInvoker(citiesServer);
// verify products query
HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProducts", HttpMethod.Get, config);
HttpResponseMessage response = catalogInvoker.SendAsync(request, CancellationToken.None).Result;
Product[] products = response.Content.ReadAsAsync<IQueryable<Product>>().Result.ToArray();
Assert.Equal(9, products.Length);
// verify serialization
QueryResult qr = new QueryResult(products, products.Length);
ObjectContent oc = (ObjectContent)response.Content;
MemoryStream ms = new MemoryStream();
Task task = new JsonMediaTypeFormatter().WriteToStreamAsync(typeof(QueryResult), qr, ms, oc.Headers, null);
task.Wait();
Assert.True(ms.Length > 0);
// verify cities query
request = TestHelpers.CreateTestMessage(TestConstants.CitiesUrl + "GetCities", HttpMethod.Get, config);
response = citiesInvoker.SendAsync(request, CancellationToken.None).Result;
City[] cities = response.Content.ReadAsAsync<IQueryable<City>>().Result.ToArray();
Assert.Equal(11, cities.Length);
// verify serialization
qr = new QueryResult(cities, cities.Length);
oc = (ObjectContent)response.Content;
ms = new MemoryStream();
task = new JsonMediaTypeFormatter().WriteToStreamAsync(typeof(QueryResult), qr, ms, oc.Headers, null);
task.Wait();
Assert.True(ms.Length > 0);
}
/// <summary>
/// Execute a query that requests an inline count with a paging query applied.
/// </summary>
[Fact]
public void Query_InlineCount_SkipTop()
{
HttpConfiguration config = GetTestConfiguration();
HttpServer server = GetTestCatalogServer(config);
HttpMessageInvoker invoker = new HttpMessageInvoker(server);
string query = "?$filter=UnitPrice lt 5.0&$skip=2&$top=5&$inlinecount=allpages";
HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProducts" + query, HttpMethod.Get, config);
HttpResponseMessage response = invoker.SendAsync(request, CancellationToken.None).Result;
QueryResult queryResult = response.Content.ReadAsAsync<QueryResult>().Result;
Assert.Equal(5, queryResult.Results.Cast<object>().Count());
Assert.Equal(8, queryResult.TotalCount);
}
/// <summary>
/// Execute a query that requests an inline count with only a top operation applied in the query.
/// Expect the total count to not inlcude the take operation.
/// </summary>
[Fact]
public void Query_IncludeTotalCount_Top()
{
HttpConfiguration config = GetTestConfiguration();
HttpServer server = GetTestCatalogServer(config);
HttpMessageInvoker invoker = new HttpMessageInvoker(server);
string query = "?$filter=UnitPrice lt 5.0&$top=5&$inlinecount=allpages";
HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProducts" + query, HttpMethod.Get, config);
HttpResponseMessage response = invoker.SendAsync(request, CancellationToken.None).Result;
QueryResult queryResult = response.Content.ReadAsAsync<QueryResult>().Result;
Assert.Equal(5, queryResult.Results.Cast<object>().Count());
Assert.Equal(8, queryResult.TotalCount);
}
/// <summary>
/// Execute a query that requests an inline count with no paging operations specified in the
/// user query. There is however still a server specified limit.
/// </summary>
[Fact]
public void Query_IncludeTotalCount_NoPaging()
{
HttpConfiguration config = GetTestConfiguration();
HttpServer server = GetTestCatalogServer(config);
HttpMessageInvoker invoker = new HttpMessageInvoker(server);
string query = "?$filter=UnitPrice lt 5.0&$inlinecount=allpages";
HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProducts" + query, HttpMethod.Get, config);
HttpResponseMessage response = invoker.SendAsync(request, CancellationToken.None).Result;
QueryResult queryResult = response.Content.ReadAsAsync<QueryResult>().Result;
Assert.Equal(8, queryResult.Results.Cast<object>().Count());
Assert.Equal(8, queryResult.TotalCount);
}
/// <summary>
/// Execute a query that sets the inlinecount option explicitly to 'none', and verify count is not returned.
/// </summary>
[Fact]
public void Query_IncludeTotalCount_False()
{
HttpConfiguration config = GetTestConfiguration();
HttpServer server = GetTestCatalogServer(config);
HttpMessageInvoker invoker = new HttpMessageInvoker(server);
string query = "?$filter=UnitPrice lt 5.0&$inlinecount=none";
HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProducts" + query, HttpMethod.Get, config);
HttpResponseMessage response = invoker.SendAsync(request, CancellationToken.None).Result;
Product[] products = response.Content.ReadAsAsync<IQueryable<Product>>().Result.ToArray();
Assert.Equal(8, products.Length);
}
/// <summary>
/// Verify that when no skip/top query operations are performed (and no result limits are active
/// on the action server side), the total count returned is -1, indicating that the total count
/// equals the result count. This avoids the fx having to double enumerate the query results to
/// set the count server side.
/// </summary>
[Fact]
public void Query_TotalCount_Equals_ResultCount()
{
HttpConfiguration config = GetTestConfiguration();
HttpServer server = GetTestCatalogServer(config);
HttpMessageInvoker invoker = new HttpMessageInvoker(server);
string query = "?$inlinecount=allpages";
HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetOrders" + query, HttpMethod.Get, config);
HttpResponseMessage response = invoker.SendAsync(request, CancellationToken.None).Result;
QueryResult result = response.Content.ReadAsAsync<QueryResult>().Result;
Assert.Equal(2, result.Results.Cast<object>().Count());
Assert.Equal(-1, result.TotalCount);
}
private HttpConfiguration GetTestConfiguration()
{
HttpConfiguration config = new HttpConfiguration();
return config;
}
private HttpServer GetTestCatalogServer(HttpConfiguration config)
{
HttpControllerDispatcher dispatcher = new HttpControllerDispatcher(config);
HttpRoute route = new HttpRoute("{controller}/{action}", new HttpRouteValueDictionary("Catalog"));
config.Routes.Add("catalog", route);
HttpServer server = new HttpServer(config, dispatcher);
return server;
}
private HttpServer GetTestCitiesServer(HttpConfiguration config)
{
HttpControllerDispatcher dispatcher = new HttpControllerDispatcher(config);
HttpRoute route = new HttpRoute("{controller}/{action}", new HttpRouteValueDictionary("Cities"));
config.Routes.Add("cities", route);
HttpServer server = new HttpServer(config, dispatcher);
return server;
}
}
}

View File

@@ -0,0 +1,375 @@
// Copyright (c) Microsoft Corporation. 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.Web.Http.Data.Test.Models;
using Newtonsoft.Json;
using Xunit;
using Assert = Microsoft.TestCommon.AssertEx;
namespace Microsoft.Web.Http.Data.Test
{
public class DataControllerSubmitTests
{
// Verify that POSTs directly to CUD actions still go through the submit pipeline
[Fact]
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();
}
}
}

View File

@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.Web.Http.Data.Helpers;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
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;
}
}
}

View File

@@ -0,0 +1,143 @@
<?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>{81876811-6C36-492A-9609-F0E85990FBC9}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Microsoft.Web.Http.Data.Test</RootNamespace>
<AssemblyName>Microsoft.Web.Http.Data.Test</AssemblyName>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="EntityFramework">
<HintPath>..\..\packages\EntityFramework.5.0.0-beta2\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">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\Newtonsoft.Json.4.5.1\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.20326.1\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.20326.1\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.0.1566\lib\xunit.dll</HintPath>
</Reference>
<Reference Include="xunit.extensions">
<HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
<Visible>False</Visible>
</CodeAnalysisDependentAssemblyPaths>
</ItemGroup>
<ItemGroup>
<Compile Include="ChangeSetTests.cs" />
<Compile Include="Controllers\CitiesController.cs" />
<Compile Include="Controllers\NorthwindEFController.cs" />
<Compile Include="DataControllerDescriptionTest.cs" />
<Compile Include="DataControllerQueryTests.cs" />
<Compile Include="DataControllerSubmitTests.cs" />
<Compile Include="MetadataExtensionsTests.cs" />
<Compile Include="Models\CatalogEntities.cs" />
<Compile Include="Models\Cities.cs" />
<Compile Include="Models\Northwind.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Northwind.edmx</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Controllers\CatalogController.cs" />
<Compile Include="TestHelpers.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\System.Json\System.Json.csproj">
<Project>{F0441BE9-BDC0-4629-BE5A-8765FFAA2481}</Project>
<Name>System.Json</Name>
</ProjectReference>
<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="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
<Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
<Name>Microsoft.TestCommon</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" />
</Project>

View File

@@ -0,0 +1,172 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Microsoft.Web.Http.Data.Test.Models
{
public partial class Category
{
public Category()
{
this.Products = new HashSet<Product>();
}
[Key]
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
public byte[] Picture { get; set; }
public ICollection<Product> Products { get; set; }
}
public partial class Customer
{
public Customer()
{
this.Orders = new HashSet<Order>();
}
[Key]
public string CustomerID { get; set; }
public string CompanyName { get; set; }
public string ContactName { get; set; }
public string ContactTitle { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
[Association("Customer_Orders", "CustomerID", "CustomerID")]
public ICollection<Order> Orders { get; set; }
}
public partial class Order
{
private List<Order_Detail> _details;
[Key]
public int OrderID { get; set; }
public string CustomerID { get; set; }
public Nullable<int> EmployeeID { get; set; }
public Nullable<System.DateTime> OrderDate { get; set; }
public Nullable<System.DateTime> RequiredDate { get; set; }
public Nullable<System.DateTime> ShippedDate { get; set; }
public Nullable<int> ShipVia { get; set; }
public Nullable<decimal> Freight { get; set; }
[StringLength(50, MinimumLength = 0)]
public string ShipName { get; set; }
public string ShipAddress { get; set; }
public string ShipCity { get; set; }
public string ShipRegion { get; set; }
public string ShipPostalCode { get; set; }
public string ShipCountry { get; set; }
[Association("Customer_Orders", "CustomerID", "CustomerID", IsForeignKey = true)]
public Customer Customer { get; set; }
[Association("Order_Details", "OrderID", "OrderID")]
public List<Order_Detail> Order_Details
{
get
{
if (this._details == null)
{
this._details = new List<Order_Detail>();
}
return this._details;
}
set
{
this._details = value;
}
}
public Shipper Shipper { get; set; }
}
public partial class Order_Detail
{
[Key]
[Column(Order = 1)]
public int OrderID { get; set; }
[Key]
[Column(Order = 2)]
public int ProductID { get; set; }
public decimal UnitPrice { get; set; }
public short Quantity { get; set; }
public float Discount { get; set; }
public Order Order { get; set; }
public Product Product { get; set; }
}
public partial class Shipper
{
public Shipper()
{
this.Orders = new HashSet<Order>();
}
[Key]
public int ShipperID { get; set; }
public string CompanyName { get; set; }
public string Phone { get; set; }
public ICollection<Order> Orders { get; set; }
}
public partial class Product
{
public Product()
{
this.Order_Details = new HashSet<Order_Detail>();
}
[Key]
public int ProductID { get; set; }
public string ProductName { get; set; }
public Nullable<int> SupplierID { get; set; }
public Nullable<int> CategoryID { get; set; }
public string QuantityPerUnit { get; set; }
public Nullable<decimal> UnitPrice { get; set; }
public Nullable<short> UnitsInStock { get; set; }
public Nullable<short> UnitsOnOrder { get; set; }
public Nullable<short> ReorderLevel { get; set; }
public bool Discontinued { get; set; }
public Category Category { get; set; }
public ICollection<Order_Detail> Order_Details { get; set; }
public Supplier Supplier { get; set; }
}
public partial class Supplier
{
public Supplier()
{
this.Products = new HashSet<Product>();
}
[Key]
public int SupplierID { get; set; }
public string CompanyName { get; set; }
public string ContactName { get; set; }
public string ContactTitle { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
public string HomePage { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
}

View File

@@ -0,0 +1,304 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.Serialization;
namespace Microsoft.Web.Http.Data.Test.Models
{
/// <summary>
/// Sample data class
/// </summary>
/// <remarks>
/// This class exposes several data types (City, County, State and Zip) and some sample
/// data for each.
/// </remarks>
public partial class CityData
{
private List<State> _states;
private List<County> _counties;
private List<City> _cities;
private List<Zip> _zips;
private List<ZipWithInfo> _zipsWithInfo;
private List<CityWithInfo> _citiesWithInfo;
public CityData()
{
_states = new List<State>()
{
new State() { Name="WA", FullName="Washington", TimeZone = TimeZone.Pacific },
new State() { Name="OR", FullName="Oregon", TimeZone = TimeZone.Pacific },
new State() { Name="CA", FullName="California", TimeZone = TimeZone.Pacific },
new State() { Name="OH", FullName="Ohio", TimeZone = TimeZone.Eastern, ShippingZone=ShippingZone.Eastern }
};
_counties = new List<County>()
{
new County() { Name="King", StateName="WA" },
new County() { Name="Pierce", StateName="WA" },
new County() { Name="Snohomish", StateName="WA" },
new County() { Name="Tillamook", StateName="OR" },
new County() { Name="Wallowa", StateName="OR" },
new County() { Name="Jackson", StateName="OR" },
new County() { Name="Orange", StateName="CA" },
new County() { Name="Santa Barbara",StateName="CA" },
new County() { Name="Lucas", StateName="OH" }
};
foreach (State state in _states)
{
foreach (County county in _counties.Where(p => p.StateName == state.Name))
{
state.Counties.Add(county);
county.State = state;
}
}
_cities = new List<City>()
{
new CityWithInfo() {Name="Redmond", CountyName="King", StateName="WA", Info="Has Microsoft campus", LastUpdated=DateTime.Now},
new CityWithInfo() {Name="Bellevue", CountyName="King", StateName="WA", Info="Means beautiful view", LastUpdated=DateTime.Now},
new City() {Name="Duvall", CountyName="King", StateName="WA"},
new City() {Name="Carnation", CountyName="King", StateName="WA"},
new City() {Name="Everett", CountyName="King", StateName="WA"},
new City() {Name="Tacoma", CountyName="Pierce", StateName="WA"},
new City() {Name="Ashland", CountyName="Jackson", StateName="OR"},
new City() {Name="Santa Barbara", CountyName="Santa Barbara", StateName="CA"},
new City() {Name="Orange", CountyName="Orange", StateName="CA"},
new City() {Name="Oregon", CountyName="Lucas", StateName="OH"},
new City() {Name="Toledo", CountyName="Lucas", StateName="OH"}
};
_citiesWithInfo = new List<CityWithInfo>(this._cities.OfType<CityWithInfo>());
foreach (County county in _counties)
{
foreach (City city in _cities.Where(p => p.CountyName == county.Name && p.StateName == county.StateName))
{
county.Cities.Add(city);
city.County = county;
}
}
_zips = new List<Zip>()
{
new Zip() { Code=98053, FourDigit=8625, CityName="Redmond", CountyName="King", StateName="WA" },
new ZipWithInfo() { Code=98052, FourDigit=8300, CityName="Redmond", CountyName="King", StateName="WA", Info="Microsoft" },
new Zip() { Code=98052, FourDigit=6399, CityName="Redmond", CountyName="King", StateName="WA" },
};
_zipsWithInfo = new List<ZipWithInfo>(this._zips.OfType<ZipWithInfo>());
foreach (City city in _cities)
{
foreach (Zip zip in _zips.Where(p => p.CityName == city.Name && p.CountyName == city.CountyName && p.StateName == city.StateName))
{
city.ZipCodes.Add(zip);
zip.City = city;
}
}
foreach (CityWithInfo city in _citiesWithInfo)
{
foreach (ZipWithInfo zip in _zipsWithInfo.Where(p => p.CityName == city.Name && p.CountyName == city.CountyName && p.StateName == city.StateName))
{
city.ZipCodesWithInfo.Add(zip);
zip.City = city;
}
}
}
public List<State> States { get { return this._states; } }
public List<County> Counties { get { return this._counties; } }
public List<City> Cities { get { return this._cities; } }
public List<CityWithInfo> CitiesWithInfo { get { return this._citiesWithInfo; } }
public List<Zip> Zips { get { return this._zips; } }
public List<ZipWithInfo> ZipsWithInfo { get { return this._zipsWithInfo; } }
}
/// <summary>
/// These types are simple data types that can be used to build
/// mocks and simple data stores.
/// </summary>
public partial class State
{
private readonly List<County> _counties = new List<County>();
[Key]
public string Name { get; set; }
[Key]
public string FullName { get; set; }
public TimeZone TimeZone { get; set; }
public ShippingZone ShippingZone { get; set; }
public List<County> Counties { get { return this._counties; } }
}
[DataContract(Name = "CityName", Namespace = "CityNamespace")]
public enum ShippingZone
{
[EnumMember(Value = "P")]
Pacific = 0, // default
[EnumMember(Value = "C")]
Central,
[EnumMember(Value = "E")]
Eastern
}
public enum TimeZone
{
Central,
Mountain,
Eastern,
Pacific
}
public partial class County
{
public County()
{
Cities = new List<City>();
}
[Key]
public string Name { get; set; }
[Key]
public string StateName { get; set; }
[IgnoreDataMember]
public State State { get; set; }
public List<City> Cities { get; set; }
}
[KnownType(typeof(CityWithEditHistory))]
[KnownType(typeof(CityWithInfo))]
public partial class City
{
public City()
{
ZipCodes = new List<Zip>();
}
[Key]
public string Name { get; set; }
[Key]
public string CountyName { get; set; }
[Key]
public string StateName { get; set; }
[IgnoreDataMember]
public County County { get; set; }
public string ZoneName { get; set; }
public string CalculatedCounty { get { return this.CountyName; } set { } }
public int ZoneID { get; set; }
public List<Zip> ZipCodes { get; set; }
public override string ToString()
{
return this.GetType().Name + " Name=" + this.Name + ", State=" + this.StateName + ", County=" + this.CountyName;
}
public int this[int index]
{
get
{
return index;
}
set
{
}
}
}
public abstract partial class CityWithEditHistory : City
{
private string _editHistory;
public CityWithEditHistory()
{
this.EditHistory = "new";
}
// Edit history always appends, never overwrites
public string EditHistory
{
get
{
return this._editHistory;
}
set
{
this._editHistory = this._editHistory == null ? value : (this._editHistory + "," + value);
this.LastUpdated = DateTime.Now;
}
}
public DateTime LastUpdated
{
get;
set;
}
public override string ToString()
{
return base.ToString() + ", History=" + this.EditHistory + ", Updated=" + this.LastUpdated;
}
}
public partial class CityWithInfo : CityWithEditHistory
{
public CityWithInfo()
{
ZipCodesWithInfo = new List<ZipWithInfo>();
}
public string Info
{
get;
set;
}
public List<ZipWithInfo> ZipCodesWithInfo { get; set; }
public override string ToString()
{
return base.ToString() + ", Info=" + this.Info;
}
}
[KnownType(typeof(ZipWithInfo))]
public partial class Zip
{
[Key]
public int Code { get; set; }
[Key]
public int FourDigit { get; set; }
public string CityName { get; set; }
public string CountyName { get; set; }
public string StateName { get; set; }
[IgnoreDataMember]
public City City { get; set; }
}
public partial class ZipWithInfo : Zip
{
public string Info
{
get;
set;
}
}
}

View File

@@ -0,0 +1 @@
6d749e42e8a7a95d663dc8a38970833f1a820101

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. 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("Microsoft.Web.Http.Data.Test")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyProduct("Microsoft.Web.Http.Data.Test")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("8e9662e7-dc7b-4ffa-8cb0-1002491aae66")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,111 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Data.EntityClient;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using System.Web.Http.Hosting;
using System.Web.Http.Routing;
namespace Microsoft.Web.Http.Data.Test
{
internal static class TestHelpers
{
internal static HttpRequestMessage CreateTestMessage(string url, HttpMethod httpMethod, HttpConfiguration config)
{
HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, url);
IHttpRouteData rd = config.Routes[0].GetRouteData("/", requestMessage);
requestMessage.Properties.Add(HttpPropertyKeys.HttpRouteDataKey, rd);
return requestMessage;
}
// Return a non-functional connection string for an EF context. This will
// allow a context to be instantiated, but not used.
internal static string GetTestEFConnectionString()
{
string connectionString = new EntityConnectionStringBuilder
{
Metadata = "res://*",
Provider = "System.Data.SqlClient",
ProviderConnectionString = new System.Data.SqlClient.SqlConnectionStringBuilder
{
InitialCatalog = "Northwind",
DataSource = "xyz",
IntegratedSecurity = false,
UserID = "xyz",
Password = "xyz",
}.ConnectionString
}.ConnectionString;
return connectionString;
}
}
internal static class TestConstants
{
public static string BaseUrl = "http://testhost/";
public static string CatalogUrl = "http://testhost/Catalog/";
public static string CitiesUrl = "http://testhost/Cities/";
}
internal class HttpContextStub : HttpContextBase
{
private HttpRequestStub request;
public HttpContextStub(Uri baseAddress, HttpRequestMessage request)
{
this.request = new HttpRequestStub(baseAddress, request);
}
public override HttpRequestBase Request
{
get
{
return this.request;
}
}
}
internal class HttpRequestStub : HttpRequestBase
{
private const string AppRelativePrefix = "~/";
private string appRelativeCurrentExecutionFilePath;
public HttpRequestStub(Uri baseAddress, HttpRequestMessage request)
{
this.appRelativeCurrentExecutionFilePath = GetAppRelativeCurrentExecutionFilePath(baseAddress.AbsoluteUri, request.RequestUri.AbsoluteUri);
}
public override string AppRelativeCurrentExecutionFilePath
{
get
{
return this.appRelativeCurrentExecutionFilePath;
}
}
public override string PathInfo
{
get
{
return String.Empty;
}
}
private static string GetAppRelativeCurrentExecutionFilePath(string baseAddress, string requestUri)
{
int queryPos = requestUri.IndexOf('?');
string requestUriNoQuery = queryPos < 0 ? requestUri : requestUri.Substring(0, queryPos);
if (baseAddress.Length >= requestUriNoQuery.Length)
{
return AppRelativePrefix;
}
else
{
return AppRelativePrefix + requestUriNoQuery.Substring(baseAddress.Length);
}
}
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EntityFramework" version="5.0.0-beta2" />
<package id="Microsoft.Net.Http" version="2.0.20326.1" />
<package id="Moq" version="4.0.10827" />
<package id="Newtonsoft.Json" version="4.5.1" />
<package id="xunit" version="1.9.0.1566" />
<package id="xunit.extensions" version="1.9.0.1566" />
</packages>