diff --git a/RESTier.sln b/RESTier.sln index 696fc3f4..6f3fa65e 100644 --- a/RESTier.sln +++ b/RESTier.sln @@ -39,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Restier.Publisher EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Restier.Providers.EntityFramework", "src\Microsoft.Restier.Providers.EntityFramework\Microsoft.Restier.Providers.EntityFramework.csproj", "{F7EC910E-17CE-4579-84C5-36D3777B3218}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Service.Library", "test\ODataEndToEnd\Microsoft.OData.Service.Library\Microsoft.OData.Service.Library.csproj", "{1CA9B17F-D3F8-4FC3-A992-5135DCCAB9DE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -93,6 +95,10 @@ Global {F7EC910E-17CE-4579-84C5-36D3777B3218}.Debug|Any CPU.Build.0 = Debug|Any CPU {F7EC910E-17CE-4579-84C5-36D3777B3218}.Release|Any CPU.ActiveCfg = Release|Any CPU {F7EC910E-17CE-4579-84C5-36D3777B3218}.Release|Any CPU.Build.0 = Release|Any CPU + {1CA9B17F-D3F8-4FC3-A992-5135DCCAB9DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CA9B17F-D3F8-4FC3-A992-5135DCCAB9DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CA9B17F-D3F8-4FC3-A992-5135DCCAB9DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CA9B17F-D3F8-4FC3-A992-5135DCCAB9DE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -114,5 +120,6 @@ Global {31FE1F5B-7CD8-48F1-9CE3-4E57A0066440} = {552DD8A7-2F3A-4D0F-B623-B7D832C6C62B} {186F667E-54E5-4B57-9998-21D74CB77C24} = {432208D4-54DF-453E-96AE-CB7721461030} {F7EC910E-17CE-4579-84C5-36D3777B3218} = {0355FEC8-17CF-44B4-9D24-685266A349FB} + {1CA9B17F-D3F8-4FC3-A992-5135DCCAB9DE} = {4AC28EC2-FBCF-44CA-A922-0B257F55DE0D} EndGlobalSection EndGlobal diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Library/DataStoreManager/DefaultDataStoreManager.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Library/DataStoreManager/DefaultDataStoreManager.cs new file mode 100644 index 00000000..14249bb3 --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Library/DataStoreManager/DefaultDataStoreManager.cs @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Timers; + +namespace Microsoft.OData.Service.Library.DataStoreManager +{ + /// + /// Default resource management class to manage resources. + /// Use a dictionary to easily access the resource by and make a constraint on the total number of resources. + /// Use a timer for each reasource, when the resource live longer than , it will be destroyed automatically. + /// + public class DefaultDataStoreManager :IDataStoreManager where TDataStoreType : class, new() + { + /// + /// The max capacity of the resource container, this is a constraint for memory cost. + /// + public int MaxDataStoreInstanceCapacity { get; set; } = 1000; + + /// + /// The max life time of each resource. When the resource lives longer than that, it will be destroyed automatically. + /// Besides, when the resource container is full, the resource live longest will be destroyed. + /// + public TimeSpan MaxDataStoreInstanceLifeTime { get; set; } = new TimeSpan(0, 15, 0); + + private Dictionary _dataStoreDict = new Dictionary(); + + public TDataStoreType ResetDataStoreInstance(TKey key) + { + if (_dataStoreDict.ContainsKey(key)) + { + _dataStoreDict[key] = new DataStoreUnit(key, MaxDataStoreInstanceLifeTime.TotalMilliseconds, ResouceTimeoutHandler); + } + else + { + AddDataStoreInstance(key); + } + + return _dataStoreDict[key].DataStore; + } + + public TDataStoreType GetDataStoreInstance(TKey key) + { + if (_dataStoreDict.ContainsKey(key)) + { + _dataStoreDict[key].UpdateLastUsedDateTime(); + } + else + { + AddDataStoreInstance(key); + } + + return _dataStoreDict[key].DataStore; + } + + private TDataStoreType AddDataStoreInstance(TKey key) + { + if (_dataStoreDict.Count >= MaxDataStoreInstanceCapacity) + { + // No resource lives longer than maxLifeTime, find the one lives longest and remove it. + var minLastUsedTime = DateTime.Now; + TKey minKey = default(TKey); + + foreach (var val in _dataStoreDict) + { + var resourceLastUsedTime = val.Value.DataStoreLastUsedDateTime; + if (resourceLastUsedTime < minLastUsedTime) + { + minLastUsedTime = resourceLastUsedTime; + minKey = val.Key; + } + } + + DeleteDataStoreInstance(minKey); + } + + System.Diagnostics.Trace.TraceInformation("The resouce dictionary size right now is {0}", _dataStoreDict.Count); + _dataStoreDict.Add(key, new DataStoreUnit(key, MaxDataStoreInstanceLifeTime.TotalMilliseconds, ResouceTimeoutHandler)); + return _dataStoreDict[key].DataStore; + } + + private DefaultDataStoreManager DeleteDataStoreInstance(TKey key) + { + if (_dataStoreDict.ContainsKey(key)) + { + _dataStoreDict[key].StopTimer(); + _dataStoreDict.Remove(key); + } + + return this; + } + + private void ResouceTimeoutHandler(object source, EventArgs e) + { + var resouceUnit = source as DataStoreUnit; + if (resouceUnit != null) + { + System.Diagnostics.Trace.TraceInformation(resouceUnit.DatastoreKey + " timeout occured, now destroy it!"); + DeleteDataStoreInstance(resouceUnit.DatastoreKey); + } + } + + private class DataStoreUnit + { + public TKey DatastoreKey { get; } + + public TDataStoreType DataStore { get; } + + public DateTime DataStoreLastUsedDateTime { get; private set; } + + private Timer DataStoreTimer { get; set; } + + private double _dataStoreLifeTime; + + private EventHandler _timerTimeoutHandler; + + public DataStoreUnit(TKey key, double dataStoreLifeTime, EventHandler dataStoreTimeoutHandler) + { + DatastoreKey = key; + DataStore = new TDataStoreType(); + DataStoreLastUsedDateTime = DateTime.Now; + _dataStoreLifeTime = dataStoreLifeTime; + _timerTimeoutHandler += dataStoreTimeoutHandler; + InitTimer(); + } + + public DataStoreUnit UpdateLastUsedDateTime() + { + UpdateTimer(); + DataStoreLastUsedDateTime = DateTime.Now; + return this; + } + + public void StopTimer() + { + DataStoreTimer.Stop(); + } + + private Timer InitTimer() + { + DataStoreTimer = new Timer(_dataStoreLifeTime); + DataStoreTimer.Elapsed += (sender, args) => { _timerTimeoutHandler?.Invoke(this, args); }; + DataStoreTimer.Start(); + return DataStoreTimer; + } + + private void UpdateTimer() + { + DataStoreTimer.Stop(); + DataStoreTimer = InitTimer(); + } + }; + } +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Library/DataStoreManager/IDataStoreManager.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Library/DataStoreManager/IDataStoreManager.cs new file mode 100644 index 00000000..6e604cd5 --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Library/DataStoreManager/IDataStoreManager.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.OData.Service.Library.DataStoreManager +{ + /// + /// Resource management interface. + /// + public interface IDataStoreManager + { + TDataStoreType GetDataStoreInstance(TKey key); + TDataStoreType ResetDataStoreInstance(TKey key); + } +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Library/Microsoft.OData.Service.Library.csproj b/test/ODataEndToEnd/Microsoft.OData.Service.Library/Microsoft.OData.Service.Library.csproj new file mode 100644 index 00000000..768fcd45 --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Library/Microsoft.OData.Service.Library.csproj @@ -0,0 +1,65 @@ + + + + + Debug + AnyCPU + {1CA9B17F-D3F8-4FC3-A992-5135DCCAB9DE} + Library + Properties + Microsoft.OData.Service.Library + Microsoft.OData.Service.Library + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Library/Properties/AssemblyInfo.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Library/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..829ed9c6 --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Library/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +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.OData.Service.Library")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.OData.Service.Library")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[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("1ca9b17f-d3f8-4fc3-a992-5135dccab9de")] + +// 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.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Library/Utils/LibraryUtils.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Library/Utils/LibraryUtils.cs new file mode 100644 index 00000000..1b6f8736 --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Library/Utils/LibraryUtils.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.OData.Service.Library.Utils +{ + public static class LibraryUtils + { + static public string GetSessionId() + { + var session = System.Web.HttpContext.Current.Session; + return session?.SessionID; + } + } +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Library/Utils/ODataSessionIdManager.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Library/Utils/ODataSessionIdManager.cs new file mode 100644 index 00000000..8bcfbcb3 --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Library/Utils/ODataSessionIdManager.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Text.RegularExpressions; +using System.Web; +using System.Web.SessionState; + +namespace Microsoft.OData.Service.Library.Utils +{ + /// + /// The default SessionIdManager in Azure will cause to loop 302, use custom SessionIdManager to avoid this. + /// + public class ODataSessionIdManager : ISessionIDManager + { + private static InternalSessionIdManager _internalManager = new InternalSessionIdManager(); + + public string CreateSessionID(HttpContext context) + { + return _internalManager.CreateSessionID(context); + } + + public string GetSessionID(HttpContext context) + { + var id = HttpContext.Current.Items["AspCookielessSession"] as string; + + // Azure web site does not support header "AspFilterSessionId", so we cannot get context.Items["AspCookielessSession"] + // for azure web site use, Headers["X-Original-URL"] format: /(S(xxx))/odata/path. + var originalUrl = HttpContext.Current.Request.Headers["X-Original-URL"]; + + if (!string.IsNullOrEmpty(originalUrl)) + { + var match = Regex.Match(HttpContext.Current.Request.Headers["X-Original-URL"], @"/\(S\((\w+)\)\)"); + if (match.Success) + { + id = match.Groups[1].Value; + } + } + + return id; + } + + public void Initialize() + { + _internalManager.Initialize(); + } + + public bool InitializeRequest(HttpContext context, bool suppressAutoDetectRedirect, out bool supportSessionIdReissue) + { + return _internalManager.InitializeRequest(context, suppressAutoDetectRedirect, out supportSessionIdReissue); + } + + public void RemoveSessionID(HttpContext context) + { + _internalManager.RemoveSessionID(context); + } + + public void SaveSessionID(HttpContext context, string id, out bool redirected, out bool cookieAdded) + { + _internalManager.SaveSessionID(context, id, out redirected, out cookieAdded); + } + + public bool Validate(string id) + { + return _internalManager.Validate(id); + } + + private class InternalSessionIdManager : SessionIDManager + { + public override bool Validate(string id) + { + return !string.IsNullOrEmpty(id); + } + } + } +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Library/packages.config b/test/ODataEndToEnd/Microsoft.OData.Service.Library/packages.config new file mode 100644 index 00000000..365d26fe --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Library/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Api/TrippinApi.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Api/TrippinApi.cs new file mode 100644 index 00000000..2e80b5b2 --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Api/TrippinApi.cs @@ -0,0 +1,502 @@ +// // Copyright (c) Microsoft Corporation. All rights reserved. +// // Licensed under the MIT License. See License.txt in the project root for license information. + +#region + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using System.Web.OData.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.OData.Service.Library.DataStoreManager; +using Microsoft.OData.Service.Library.Utils; +using Microsoft.OData.Service.Sample.TrippinInMemory.Models; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Core.Submit; +using Microsoft.Restier.Publishers.OData.Model; +using Microsoft.Spatial; + +#endregion + +namespace Microsoft.OData.Service.Sample.TrippinInMemory.Api +{ + public class TrippinApi : ApiBase + { + private static IDataStoreManager _dataStoreManager + = new DefaultDataStoreManager() + { + MaxDataStoreInstanceCapacity = 1000, + MaxDataStoreInstanceLifeTime = new TimeSpan(0, 30, 0) + }; + + private string Key + { + get { return LibraryUtils.GetSessionId(); } + } + + #region Entity Set + + public IQueryable People + { + get + { + var datasource = _dataStoreManager.GetDataStoreInstance(Key); + return datasource?.People.AsQueryable(); + } + } + + public Person Me + { + get + { + var datasource = _dataStoreManager.GetDataStoreInstance(Key); + return datasource?.Me; + } + } + + public IQueryable Airlines + { + get + { + var datasource = _dataStoreManager.GetDataStoreInstance(Key); + return datasource?.Airlines.AsQueryable(); + } + } + + public IQueryable Airports + { + get + { + var datasource = _dataStoreManager.GetDataStoreInstance(Key); + return datasource?.Airports.AsQueryable(); + } + } + + #endregion + + #region function/action + + /// + /// Unbound function, Get Person with most friends. + /// + /// + /// + /// + [Operation(EntitySet = "People")] + public Person GetPersonWithMostFriends() + { + Person result = null; + foreach (var person in People) + { + if (person.Friends == null) + { + continue; + } + + if (result == null) + { + result = person; + } + + if (person.Friends.Count > result.Friends.Count) + { + result = person; + } + } + + return result; + } + + /// + /// Unbound function, get nearest aireport to GeographyPoint(lat, lon). + /// + /// Latitude + /// Longitude + /// + /// + /// + [Operation(EntitySet = "Airports")] + public Airport GetNearestAirport(double lat, double lon) + { + var startPoint = GeographyPoint.Create(lat, lon); + double minDistance = 2; + Airport nearestAirport = null; + + foreach (var airport in Airports) + { + var distance = CalculateDistance(startPoint, airport.Location.Loc); + if (distance < minDistance) + { + nearestAirport = airport; + minDistance = distance; + } + } + + return nearestAirport; + } + + [Operation(IsBound = true)] + public Airline GetFavoriteAirline(Person person) + { + var countDict = new Dictionary(); + foreach (var a in Airlines) + { + countDict.Add(a.AirlineCode, 0); + } + + foreach (var t in person.Trips) + { + foreach (var p in t.PlanItems) + { + var f = p as Flight; + if (f != null) + { + countDict[f.Airline.AirlineCode]++; + } + } + } + + var max = -1; + string favoriteAirlineCode = null; + foreach (var record in countDict) + { + if (max < record.Value) + { + favoriteAirlineCode = record.Key; + max = record.Value; + } + } + + return Airlines.Single(a => a.AirlineCode.Equals(favoriteAirlineCode)); + } + + + /// + /// Bound Function, get the trips of one friend with userName + /// + [Operation(IsBound = true)] + public ICollection GetFriendsTrips(Person person, string userName) + { + var friends = person.Friends.Where(p => p.UserName.Equals(userName)).ToArray(); + if (friends.Count() == 0) + { + //todo: in this case it should throw a 404 not found error. + return new Collection(); + } + else + { + return friends[0].Trips; + } + } + + [Operation(IsBound = true)] + public ICollection GetInvolvedPeople(Trip trip) + { + var shareID = trip.ShareId; + ICollection sharingPersons = new Collection(); + + foreach (var person in People) + { + if (person.Trips != null) + { + foreach (var t in person.Trips) + { + if (shareID.Equals(t.ShareId)) + { + sharingPersons.Add(person); + break; + } + } + } + } + + return sharingPersons; + } + + /// + /// Unbound action, reset datasource. + /// + [Operation(HasSideEffects = true)] + public void ResetDataSource() + { + _dataStoreManager.ResetDataStoreInstance(Key); + } + + /// + /// Bound Action, update the last name of one person. + /// + /// The person to be updated. + /// The value of last name to be updated. + /// True if update successfully. + [Operation(IsBound = true)] + public bool UpdatePersonLastName(Person person, string lastName) + { + if (person != null) + { + person.LastName = lastName; + return true; + } + else + { + return false; + } + } + + [Operation(IsBound = true, HasSideEffects = true)] + public void ShareTrip(Person personInstance, string userName, int tripId) + { + if (personInstance == null) + { + throw new ArgumentNullException("personInstance"); + } + if (string.IsNullOrEmpty(userName)) + { + throw new ArgumentNullException("userName"); + } + if (tripId < 0) + { + throw new ArgumentNullException("tripId"); + } + + var tripInstance = personInstance.Trips.FirstOrDefault(item => item.TripId == tripId); + + if (tripInstance == null) + { + throw new Exception(string.Format("Can't get trip with ID '{0}' in person '{1}'", tripId, + personInstance.UserName)); + } + + var friendInstance = personInstance.Friends.FirstOrDefault(item => item.UserName == userName); + + if (friendInstance == null) + { + throw new Exception(string.Format("Can't get friend with userName '{0}' in person '{1}'", userName, + personInstance.UserName)); + } + + if (friendInstance.Trips != null && friendInstance.Trips.All(item => item.TripId != tripId)) + { + //TODO, should return 201 if we add new entity, those behavior should be update in handler. + var newTrip = tripInstance.Clone() as Trip; + var maxTripId = friendInstance.Trips.Select(item => item.TripId).Max(); + newTrip.TripId = maxTripId + 1; + friendInstance.Trips.Add(newTrip); + } + } + + private static double CalculateDistance(GeographyPoint p1, GeographyPoint p2) + { + // using Haversine formula + // refer to http://en.wikipedia.org/wiki/Haversine_formula. + var lat1 = Math.PI*p1.Latitude/180; + var lat2 = Math.PI*p2.Latitude/180; + var lon1 = Math.PI*p1.Longitude/180; + var lon2 = Math.PI*p2.Longitude/180; + var item1 = Math.Sin((lat1 - lat2)/2)*Math.Sin((lat1 - lat2)/2); + var item2 = Math.Cos(lat1)*Math.Cos(lat2)*Math.Sin((lon1 - lon2)/2)*Math.Sin((lon1 - lon2)/2); + return Math.Asin(Math.Sqrt(item1 + item2)); + } + + #endregion + + protected override IServiceCollection ConfigureApi(IServiceCollection services) + { + services.AddService((sp, next) => new ModelBuilder()); + services.AddService((sp, next) => new CustomerizedChangeSetInitializer()); + services.AddService((sp, next) => new CustomerizedSubmitExecutor()); + return base.ConfigureApi(services); + } + + private class ModelBuilder : IModelBuilder + { + public Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + var modelBuilder = new ODataConventionModelBuilder(); + modelBuilder.EntityType(); + return Task.FromResult(modelBuilder.GetEdmModel()); + } + } + + #region Services + + private class CustomerizedSubmitExecutor : ISubmitExecutor + { + public Task ExecuteSubmitAsync(SubmitContext context, CancellationToken cancellationToken) + { + return Task.FromResult(new SubmitResult(context.ChangeSet)); + } + } + + /// + /// ChangeSetInitializer class. + /// Since our datasource is in memory, + /// we just confirm the data change here, not in SubmitExecutor + /// + private class CustomerizedChangeSetInitializer : IChangeSetInitializer + { + public async Task InitializeAsync(SubmitContext context, CancellationToken cancellationToken) + { + var key = LibraryUtils.GetSessionId(); + var dataSource = _dataStoreManager.GetDataStoreInstance(key); + foreach (var dataModificationItem in context.ChangeSet.Entries.OfType()) + { + var expectedEntiType = dataModificationItem.ExpectedResourceType; + var operation = dataModificationItem.DataModificationItemAction; + object entity; + switch (operation) + { + case DataModificationItemAction.Insert: + { + // Here we create a instance of entity, parameters are from the request. + // Known issues: 1) not support odata.id + // 2) not support nested entity. + entity = Activator.CreateInstance(expectedEntiType); + SetValues(entity, expectedEntiType, dataModificationItem.LocalValues); + dataModificationItem.Resource = entity; + + // insert new entity into entity set + var entitySetProp = GetEntitySetPropertyInfoFromDataModificationItem(dataSource, + dataModificationItem); + + if (entitySetProp != null && entitySetProp.CanWrite) + { + var originSet = entitySetProp.GetValue(dataSource); + entitySetProp.PropertyType.GetMethod("Add").Invoke(originSet, new[] {entity}); + } + } + break; + case DataModificationItemAction.Update: + { + entity = FindEntity(dataSource, context, dataModificationItem, cancellationToken); + dataModificationItem.Resource = entity; + + // update the entity + if (entity != null) + { + SetValues(entity, expectedEntiType, dataModificationItem.LocalValues); + } + } + break; + case DataModificationItemAction.Remove: + { + entity = FindEntity(dataSource, context, dataModificationItem, cancellationToken); + dataModificationItem.Resource = entity; + + // remove the entity + if (entity != null) + { + var entitySetProp = GetEntitySetPropertyInfoFromDataModificationItem(dataSource, + dataModificationItem); + + if (entitySetProp != null && entitySetProp.CanWrite) + { + var originSet = entitySetProp.GetValue(dataSource); + entitySetProp.PropertyType.GetMethod("Remove").Invoke(originSet, new[] {entity}); + } + } + } + break; + case DataModificationItemAction.Undefined: + { + throw new NotImplementedException(); + } + } + } + } + + private static void SetValues(object instance, Type type, IReadOnlyDictionary values) + { + foreach (KeyValuePair propertyPair in values) + { + object value = propertyPair.Value; + PropertyInfo propertyInfo = type.GetProperty(propertyPair.Key); + if (value == null) + { + // If the property value is null, we set null in the object too. + propertyInfo.SetValue(instance, null); + continue; + } + + if (!propertyInfo.PropertyType.IsInstanceOfType(value)) + { + var dic = value as IReadOnlyDictionary; + if (dic == null) + { + throw new NotSupportedException(string.Format( + CultureInfo.InvariantCulture, + propertyPair.Key)); + } + + value = Activator.CreateInstance(propertyInfo.PropertyType); + SetValues(value, propertyInfo.PropertyType, dic); + } + + propertyInfo.SetValue(instance, value); + } + } + + private static object FindEntity( + object instance, + SubmitContext context, + DataModificationItem item, + CancellationToken cancellationToken) + { + var entitySetPropertyInfo = GetEntitySetPropertyInfoFromDataModificationItem(instance, item); + var originSet = entitySetPropertyInfo.GetValue(instance); + + object entity = null; + var enumerableSet = originSet as IEnumerable; + if (enumerableSet != null) + { + foreach (var o in enumerableSet) + { + var foundFlag = true; + foreach (var keyVal in item.ResourceKey) + { + var entityProp = o.GetType().GetProperty(keyVal.Key); + if (entityProp != null) + { + foundFlag &= entityProp.GetValue(o).Equals(keyVal.Value); + } + else + { + foundFlag = false; + } + + if (!foundFlag) + { + break; + } + } + + if (foundFlag) + { + entity = o; + break; + } + } + } + + return entity; + } + + private static PropertyInfo GetEntitySetPropertyInfoFromDataModificationItem(object instance, + DataModificationItem dataModificationItem) + { + var entitySetName = dataModificationItem.ResourceSetName; + var entitySetProp = instance.GetType() + .GetProperty(entitySetName, BindingFlags.Public | BindingFlags.Instance); + return entitySetProp; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/App_Start/WebApiConfig.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/App_Start/WebApiConfig.cs index d5f07e84..8c7c9f47 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/App_Start/WebApiConfig.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/App_Start/WebApiConfig.cs @@ -1,5 +1,9 @@ -using System.Web.Http; -using Microsoft.OData.Service.Sample.TrippinInMemory.Models; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Web.Http; +using System.Web.OData; +using Microsoft.OData.Service.Sample.TrippinInMemory.Api; using Microsoft.Restier.Publishers.OData; using Microsoft.Restier.Publishers.OData.Batch; @@ -9,10 +13,17 @@ public static class WebApiConfig { public static void Register(HttpConfiguration config) { - config.MapRestierRoute( - "TrippinApi", - "api/Trippin", - new RestierBatchHandler(GlobalConfiguration.DefaultServer)).Wait(); + RegisterTrippin(config, GlobalConfiguration.DefaultServer); + config.MessageHandlers.Add(new ETagMessageHandler()); + } + + public static async void RegisterTrippin( + HttpConfiguration config, HttpServer server) + { + await config.MapRestierRoute( + "TrippinApi", + null, //"api/Trippin", + new RestierBatchHandler(server)); } } -} +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Controllers/TrippinController.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Controllers/TrippinController.cs new file mode 100644 index 00000000..d4cb4976 --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Controllers/TrippinController.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Web.Http; +using System.Web.OData; +using System.Web.OData.Routing; +using Microsoft.OData.Service.Sample.TrippinInMemory.Api; + +namespace Microsoft.OData.Service.Sample.TrippinInMemory.Controllers +{ + public class TrippinController : ODataController + { + private TrippinApi _api; + private TrippinApi Api + { + get + { + if (_api == null) + { + _api = new TrippinApi(); + } + + return _api; + } + } + + /// + /// Restier only supports put and post entity set. + /// Use property name to simulate the bound action. + /// + /// Key of people entity set, parsed from uri. + /// The value of last name to be updated. + /// + [HttpPut] + [ODataRoute("People({key})/LastName")] + public IHttpActionResult UpdatePersonLastName([FromODataUri]string key, [FromBody] string name) + { + var person = Api.People.Single(p => p.UserName == key); + if (Api.UpdatePersonLastName(person, name)) + { + return Ok(); + } + else + { + return NotFound(); + } + } + } +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Global.asax.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Global.asax.cs index c2bff236..693880a7 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Global.asax.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Global.asax.cs @@ -1,4 +1,8 @@ -using System.Web.Http; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Web; +using System.Web.Http; namespace Microsoft.OData.Service.Sample.TrippinInMemory { @@ -8,5 +12,10 @@ protected void Application_Start() { GlobalConfiguration.Configure(WebApiConfig.Register); } + + protected void Application_PostAuthorizeRequest() + { + HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required); + } } } diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Microsoft.OData.Service.Sample.TrippinInMemory.csproj b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Microsoft.OData.Service.Sample.TrippinInMemory.csproj index 38694f3f..976dea75 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Microsoft.OData.Service.Sample.TrippinInMemory.csproj +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Microsoft.OData.Service.Sample.TrippinInMemory.csproj @@ -13,7 +13,7 @@ Properties Microsoft.OData.Service.Sample.TrippinInMemory Microsoft.OData.Service.Sample.TrippinInMemory - v4.5 + v4.5.2 true @@ -63,6 +63,7 @@ ..\..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll True + @@ -99,14 +100,22 @@ + Global.asax - + + + + + + + - + + @@ -120,7 +129,6 @@ - @@ -131,6 +139,10 @@ {186f667e-54e5-4b57-9998-21d74cb77c24} Microsoft.Restier.Publishers.OData + + {1ca9b17f-d3f8-4fc3-a992-5135dccab9de} + Microsoft.OData.Service.Library + 10.0 diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Airline.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Airline.cs new file mode 100644 index 00000000..491f1fca --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Airline.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.ComponentModel.DataAnnotations; + +namespace Microsoft.OData.Service.Sample.TrippinInMemory.Models +{ + public class Airline + { + [Key] + public string AirlineCode { get; set; } + + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Airport.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Airport.cs new file mode 100644 index 00000000..0badcc29 --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Airport.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.ComponentModel.DataAnnotations; + +namespace Microsoft.OData.Service.Sample.TrippinInMemory.Models +{ + public class Airport + { + public string Name { get; set; } + + [Key] + public string IcaoCode { get; set; } + + public string IataCode { get; set; } + + public AirportLocation Location { get; set; } + } +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Feature.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/City.cs similarity index 60% rename from test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Feature.cs rename to test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/City.cs index ecf36ecd..27155144 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Feature.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/City.cs @@ -3,11 +3,12 @@ namespace Microsoft.OData.Service.Sample.TrippinInMemory.Models { - public enum Feature + public class City { - Feature1, - Feature2, - Feature3, - Feature4 + public string Name { get; set; } + + public string CountryRegion { get; set; } + + public string Region { get; set; } } } \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Event.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Event.cs new file mode 100644 index 00000000..df26490d --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Event.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.OData.Service.Sample.TrippinInMemory.Models +{ + public class Event : PlanItem + { + public EventLocation OccursAt { get; set; } + + public string Description { get; set; } + + public override object Clone() + { + var newPlan = new Event() + { + ConfirmationCode = this.ConfirmationCode, + Duration = this.Duration, + EndsAt = this.EndsAt, + PlanItemId = this.PlanItemId, + StartsAt = this.StartsAt, + Description = this.Description, + OccursAt = new EventLocation() + { + Address = this.OccursAt.Address, + BuildingInfo = this.OccursAt.BuildingInfo, + City = new City() + { + CountryRegion = this.OccursAt.City.CountryRegion, + Name = this.OccursAt.City.Name, + Region = this.OccursAt.City.Region, + } + }, + }; + + return newPlan; + } + } +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Flight.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Flight.cs new file mode 100644 index 00000000..231aa3f5 --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Flight.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Microsoft.OData.Service.Sample.TrippinInMemory.Models +{ + public class Flight : PublicTransportation + { + public string FlightNumber { get; set; } + + public Airline Airline { get; set; } + + public virtual Airport From { get; set; } + + public virtual Airport To { get; set; } + + public override object Clone() + { + var newPlan = new Flight() + { + ConfirmationCode = this.ConfirmationCode, + Duration = this.Duration, + EndsAt = this.EndsAt, + PlanItemId = this.PlanItemId, + StartsAt = this.StartsAt, + FlightNumber = this.FlightNumber, + Airline = this.Airline, + From = this.From, + SeatNumber = this.SeatNumber, + To = this.To, + }; + + return newPlan; + } + } +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Location.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Location.cs index 0fd9762a..134df496 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Location.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Location.cs @@ -1,10 +1,25 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using Microsoft.Spatial; + namespace Microsoft.OData.Service.Sample.TrippinInMemory.Models { public class Location { public string Address { get; set; } + + public City City { get; set; } + } + + public class EventLocation : Location + { + public string BuildingInfo { get; set; } + } + + public class AirportLocation : Location + { + // TODO: the type of field does not support serialization + public GeographyPoint Loc { get; set; } } } \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Person.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Person.cs index 22745f2f..1e501b45 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Person.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Person.cs @@ -1,26 +1,22 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 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; -using System.Web.OData.Builder; namespace Microsoft.OData.Service.Sample.TrippinInMemory.Models { - public class Person + public enum PersonGender { - public Person BestFriend { get; set; } - - // Way 1: enable auto-expand through attribute. - [AutoExpand] - public virtual ICollection Friends { get; set; } - - public virtual ICollection Trips { get; set; } - - public int PersonId { get; set; } + Male, + Female, + Unknow + } + public class Person + { + [Key] + [ConcurrencyCheck] public string UserName { get; set; } [Required] @@ -28,22 +24,20 @@ public class Person [MaxLength(26), MinLength(1)] public string LastName { get; set; } - - public string MiddleName { get; set; } - public long Concurrency { get; set; } + public PersonGender Gender { get; set; } - [Column("BirthDate", TypeName = "Date")] - public DateTime BirthDate { get; set; } + public long? Age { get; set; } - public Feature FavoriteFeature { get; set; } + public ICollection Emails { get; set; } - public virtual ICollection Emails { get; set; } - - public Location HomeAddress { get; set; } + public ICollection AddressInfo { get; set; } - public virtual ICollection Locations { get; set; } + public virtual ICollection Friends { get; set; } - public virtual ICollection Features { get; set; } + public virtual ICollection Trips { get; set; } + + [ConcurrencyCheck] + public long Concurrency { get; set; } } -} +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/PlanItem.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/PlanItem.cs new file mode 100644 index 00000000..44fe69f2 --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/PlanItem.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.OData.Service.Sample.TrippinInMemory.Models +{ + public class PlanItem + { + public int PlanItemId { get; set; } + + public string ConfirmationCode { get; set; } + + public DateTimeOffset StartsAt { get; set; } + + public DateTimeOffset EndsAt { get; set; } + + public TimeSpan Duration { get; set; } + + public virtual object Clone() + { + var newPlan = new PlanItem() + { + ConfirmationCode = this.ConfirmationCode, + Duration = this.Duration, + EndsAt = this.EndsAt, + PlanItemId = this.PlanItemId, + StartsAt = this.StartsAt + }; + + return newPlan; + } + } +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/PublicTransportation.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/PublicTransportation.cs new file mode 100644 index 00000000..9a3a21a9 --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/PublicTransportation.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.OData.Service.Sample.TrippinInMemory.Models +{ + public class PublicTransportation : PlanItem + { + public string SeatNumber { get; set; } + + public override object Clone() + { + var newPlan = new PublicTransportation() + { + ConfirmationCode = this.ConfirmationCode, + Duration = this.Duration, + EndsAt = this.EndsAt, + PlanItemId = this.PlanItemId, + StartsAt = this.StartsAt, + SeatNumber = this.SeatNumber, + }; + + return newPlan; + } + } +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Trip.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Trip.cs index 3a8a4089..6ccbc3f4 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Trip.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/Trip.cs @@ -1,4 +1,9 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; namespace Microsoft.OData.Service.Sample.TrippinInMemory.Models { @@ -6,18 +11,52 @@ public class Trip { public int TripId { get; set; } - public int? PersonId { get; set; } - public Guid ShareId { get; set; } public string Name { get; set; } - + public float Budget { get; set; } public string Description { get; set; } + public ICollection Tags { get; set; } + public DateTimeOffset StartsAt { get; set; } public DateTimeOffset EndsAt { get; set; } + + public virtual ICollection PlanItems { get; set; } + + public object Clone() + { + Trip newTrip = new Trip() + { + TripId = 0,//Should reset the trip id value. + ShareId = this.ShareId, + Name = this.Name, + Budget = this.Budget, + Description = this.Description, + StartsAt = this.StartsAt, + EndsAt = this.EndsAt, + Tags = null, + }; + + if (this.Tags != null) + { + newTrip.Tags = new Collection(); + foreach (var tag in this.Tags) + { + newTrip.Tags.Add(tag); + } + } + + newTrip.PlanItems = new List(); + foreach (var planItem in this.PlanItems) + { + newTrip.PlanItems.Add(planItem.Clone() as PlanItem); + } + + return newTrip; + } } -} \ No newline at end of file +}; diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/TripPinDataSource.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/TripPinDataSource.cs new file mode 100644 index 00000000..30410646 --- /dev/null +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/TripPinDataSource.cs @@ -0,0 +1,1400 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Web.OData.Builder; +using Microsoft.Spatial; + +namespace Microsoft.OData.Service.Sample.TrippinInMemory.Models +{ + public class TripPinDataSource + { + public List People { get; set; } + + public List Airports { get; private set; } + + public List Airlines { get; private set; } + + public Person Me { get; set; } + + public TripPinDataSource() + { + this.Reset(); + this.Initialize(); + } + + private void Reset() + { + this.People = new List(); + this.Airports = new List(); + this.Airlines = new List(); + } + + private void Initialize() + { + #region Airports + this.Airports.AddRange(new List() + { + new Airport() + { + Name = "San Francisco International Airport", + Location = new AirportLocation() + { + Address = "South McDonnell Road, San Francisco, CA 94128", + City = new City() + { + Name = "San Francisco", + CountryRegion = "United States", + Region = "California" + }, + Loc = GeographyPoint.Create(37.6188888888889, -122.374722222222) + }, + IataCode = "SFO", + IcaoCode = "KSFO" + }, + new Airport() + { + Name = "Los Angeles International Airport", + Location = new AirportLocation() + { + Address = "1 World Way, Los Angeles, CA, 90045", + City = new City() + { + Name = "Los Angeles", + CountryRegion = "United States", + Region = "California" + }, + Loc = GeographyPoint.Create(33.9425, -118.408055555556) + }, + IataCode = "LAX", + IcaoCode = "KLAX" + }, + new Airport() + { + Name = "Shanghai Hongqiao International Airport", + Location = new AirportLocation() + { + Address = "Hongqiao Road 2550, Changning District", + City = new City() + { + Name = "Shanghai", + CountryRegion = "China", + Region = "Shanghai" + }, + Loc = GeographyPoint.Create(31.1977777777778, 121.336111111111) + }, + IataCode = "SHA", + IcaoCode = "ZSSS" + }, + new Airport() + { + Name = "Beijing Capital International Airport", + Location = new AirportLocation() + { + Address = "Airport Road, Chaoyang District, Beijing, 100621", + City = new City() + { + Name = "Beijing", + CountryRegion = "China", + Region = "Beijing" + }, + Loc = GeographyPoint.Create(40.08, 116.584444444444) + }, + IataCode = "PEK", + IcaoCode = "ZBAA" + }, + new Airport() + { + Name = "John F. Kennedy International Airport", + Location = new AirportLocation() + { + Address = "Jamaica, New York, NY 11430", + City = new City() + { + Name = "New York City", + CountryRegion = "United States", + Region = "New York" + }, + Loc = GeographyPoint.Create(40.6397222222222, -73.7788888888889) + }, + IataCode = "JFK", + IcaoCode = "KJFK" + } + }); + #endregion + + #region Airlines + this.Airlines.AddRange(new List() + { + new Airline() + { + Name = "American Airlines", + AirlineCode = "AA" + }, + + new Airline() + { + Name = "Shanghai Airline", + AirlineCode = "FM" + }, + + new Airline() + { + Name = "China Eastern Airlines", + AirlineCode = "MU" + } + }); + #endregion + + #region People + this.People.AddRange(new List() + { + new Person() + { + FirstName = "Russell", + LastName = "Whyte", + UserName = "russellwhyte", + Gender = PersonGender.Male, + Emails = new List { "Russell@example.com", "Russell@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "187 Suffolk Ln.", + City = new City() + { + CountryRegion = "United States", + Name = "Boise", + Region = "ID" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 0, + ShareId = new Guid("9d9b2fa0-efbf-490e-a5e3-bac8f7d47354"), + Name = "Trip in US", + Budget = 3000.0f, + Description = "Trip from San Francisco to New York City", + Tags = new List + { + "business", + "New York meeting" + }, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 4)), + PlanItems = new List + { + new Flight() + { + PlanItemId = 11, + ConfirmationCode = "JH58493", + FlightNumber = "VA1930", + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1, 8, 0, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 1, 9, 20, 0)), + Airline = Airlines[0], + From = Airports[0], + To = Airports[4] + }, + new Event() + { + PlanItemId = 12, + Description = "Client Meeting", + ConfirmationCode = "4372899DD", + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 2, 13, 0, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 6, 13, 0, 0)), + Duration = new TimeSpan(3, 0, 0), + OccursAt = new EventLocation() + { + BuildingInfo = "Regus Business Center", + City = new City() + { + Name = "New York City", + CountryRegion = "United States", + Region = "New York" + }, + Address = "100 Church Street, 8th Floor, Manhattan, 10007" + } + }, + new Flight() + { + PlanItemId = 13, + ConfirmationCode = "JH58493", + FlightNumber = "VA1930", + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 4, 13, 0, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 4, 14, 20, 0)), + Airline = Airlines[0], + From = Airports[4], + To = Airports[0] + }, + } + }, + new Trip() + { + TripId = 1, + Name = "Trip in Beijing", + Budget = 2000.0f, + ShareId = new Guid("f94e9116-8bdd-4dac-ab61-08438d0d9a71"), + Description = "Trip from Shanghai to Beijing", + Tags = new List{"Travel", "Beijing"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 2, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 2, 4)), + PlanItems = new List + { + new Flight() + { + PlanItemId = 21, + ConfirmationCode = "JH58494", + FlightNumber = "FM1930", + StartsAt = new DateTimeOffset(new DateTime(2014, 2, 1, 8, 0, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 2, 1, 9, 20, 0)), + Airline = Airlines[1], + SeatNumber = "B11", + From = Airports[2], + To = Airports[3] + }, + new Flight() + { + PlanItemId = 32, + ConfirmationCode = "JH58495", + FlightNumber = "MU1930", + StartsAt = new DateTimeOffset(new DateTime(2014, 2, 10, 15, 30, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 2, 10, 16, 30, 0)), + Airline = Airlines[2], + SeatNumber = "A32", + From = Airports[3], + To = Airports[2] + }, + new Event() + { + PlanItemId = 5, + Description = "Dinner", + StartsAt = new DateTimeOffset(new DateTime(2014, 2, 2, 18, 0, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 2, 2, 21, 0, 0)), + Duration = new TimeSpan(3, 0, 0), + OccursAt = new EventLocation() + { + BuildingInfo = "Beijing Restaurant", + City = new City() + { + Name = "Beijing", + CountryRegion = "China", + Region = "Beijing" + }, + Address = "10 Beijing Street, 100000" + } + } + } + }, + new Trip() + { + TripId = 2, + ShareId = new Guid("9ce142c3-5fd6-4a71-848e-5220ebf1e9f3"), + Name = "Honeymoon", + Budget = 2650.0f, + Description = "Happy honeymoon trip", + Tags = new List{"Travel", "honeymoon"}, + StartsAt = new DateTime(2014, 2, 1), + EndsAt = new DateTime(2014, 2, 4) + } + } + }, + new Person() + { + FirstName = "Scott", + LastName = "Ketchum", + UserName = "scottketchum", + Gender = PersonGender.Male, + Emails = new List { "Scott@example.com" }, + AddressInfo = new List + { + new Location() + { + Address = "2817 Milton Dr.", + City = new City() + { + CountryRegion = "United States", + Name = "Albuquerque", + Region = "NM" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 3, + ShareId = new Guid("9d9b2fa0-efbf-490e-a5e3-bac8f7d47354"), + Name = "Trip in US", + Budget = 5000.0f, + Description = "Trip from San Francisco to New York City", + Tags = new List{"business","New York meeting"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 4)), + PlanItems = new List + { + new Flight() + { + PlanItemId = 11, + ConfirmationCode = "JH58493", + FlightNumber = "VA1930", + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1, 8, 0, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 1, 9, 20, 0)), + Airline = Airlines[0], + SeatNumber = "A12", + From = Airports[0], + To = Airports[4] + }, + new Event() + { + PlanItemId = 12, + Description = "Client Meeting", + ConfirmationCode = "4372899DD", + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 2, 13, 0, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 2, 16, 0, 0)), + Duration = new TimeSpan(3, 0, 0), + OccursAt = new EventLocation() + { + BuildingInfo = "Regus Business Center", + City = new City() + { + Name = "New York City", + CountryRegion = "United States", + Region = "New York" + }, + Address = "100 Church Street, 8th Floor, Manhattan, 10007" + } + }, + new Flight() + { + PlanItemId = 13, + ConfirmationCode = "JH58493", + FlightNumber = "VA1930", + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 4, 13, 0, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 4, 14, 20, 0)), + Airline = Airlines[0], + From = Airports[4], + To = Airports[0] + } + } + }, + new Trip() + { + TripId = 4, + ShareId = new Guid("f94e9116-8bdd-4dac-ab61-08438d0d9a71"), + Name = "Trip in Beijing", + Budget = 11000.0f, + Description = "Trip from Shanghai to Beijing", + Tags = new List{"Travel", "Beijing"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 2, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 2, 4)), + PlanItems = new List + { + new Flight() + { + PlanItemId = 21, + ConfirmationCode = "JH58494", + FlightNumber = "FM1930", + StartsAt = new DateTimeOffset(new DateTime(2014, 2, 1, 8, 0, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 2, 1, 9, 20, 0)), + Airline = Airlines[1], + SeatNumber = "B12", + From = Airports[2], + To = Airports[3] + }, + new Flight() + { + PlanItemId = 32, + ConfirmationCode = "JH58495", + FlightNumber = "MU1930", + StartsAt = new DateTimeOffset(new DateTime(2014, 2, 10, 16, 30, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 2, 10, 16, 30, 0)), + Airline = Airlines[2], + SeatNumber = "A33", + From = Airports[3], + To = Airports[2] + }, + new Event() + { + PlanItemId = 5, + Description = "Dinner", + StartsAt = new DateTimeOffset(new DateTime(2014, 2, 2, 18, 0, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 2, 2, 21, 0, 0)), + Duration = new TimeSpan(3, 0, 0), + OccursAt = new EventLocation() + { + BuildingInfo = "Beijing Restaurant", + City = new City() + { + Name = "Beijing", + CountryRegion = "China", + Region = "Beijing" + }, + Address = "10 Beijing Street, 100000" + } + } + } + } + } + }, + new Person() + { + FirstName = "Ronald", + LastName = "Mundy", + UserName = "ronaldmundy", + Gender = PersonGender.Male, + Emails = new List { "Ronald@example.com", "Ronald@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "187 Suffolk Ln.", + City = new City() + { + CountryRegion = "United States", + Name = "Boise", + Region = "ID" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 5, + ShareId = new Guid("dd6a09c0-e59b-4745-8612-f4499b676c47"), + Name = "Gradutaion trip", + Budget = 6000.0f, + Description = "Gradution trip with friends", + Tags = new List{"Travel"}, + StartsAt = new DateTimeOffset(new DateTime(2013, 5, 1)), + EndsAt = new DateTimeOffset(new DateTime(2013, 5, 8)) + } + } + }, + new Person() + { + FirstName = "Javier", + LastName = "Alfred", + UserName = "javieralfred", + Gender = PersonGender.Male, + Emails = new List { "Javier@example.com", "Javier@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "89 Jefferson Way Suite 2", + City = new City() + { + CountryRegion = "United States", + Name = "Portland", + Region = "WA" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 6, + ShareId = new Guid("f94e9116-8bdd-4dac-ab61-08438d0d9a71"), + Name = "Trip in Beijing", + Budget = 800.0f, + Description = "Trip from Shanghai to Beijing", + Tags = new List{"Travel", "Beijing"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 2, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 2, 4)) + } + } + }, + new Person() + { + FirstName = "Willie", + LastName = "Ashmore", + UserName = "willieashmore", + Gender = PersonGender.Male, + Emails = new List { "Willie@example.com", "Willie@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "89 Jefferson Way Suite 2", + City = new City() + { + CountryRegion = "United States", + Name = "Portland", + Region = "WA" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 7, + ShareId = new Guid("5ae142c3-5ad6-4a71-768e-5220ebf1e9f3"), + Name = "Business Trip", + Budget = 3800.5f, + Description = "This is my first business trip", + Tags = new List{"business", "first"}, + StartsAt = new DateTime(2014, 2, 1), + EndsAt = new DateTime(2014, 2, 4) + }, + new Trip() + { + TripId = 8, + ShareId = new Guid("9ce32ac3-5fd6-4a72-848e-2250ebf1e9f3"), + Name = "Trip in Europe", + Budget = 2000.0f, + Description = "The trip is currently in plan.", + Tags = new List{"Travel", "plan"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 2, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 2, 4)) + } + } + }, + new Person() + { + FirstName = "Vincent", + LastName = "Calabrese", + UserName = "vincentcalabrese", + Gender = PersonGender.Male, + Emails = new List { "Vincent@example.com", "Vincent@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "55 Grizzly Peak Rd.", + City = new City() + { + CountryRegion = "United States", + Name = "Butte", + Region = "MT" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 9, + ShareId = new Guid("dd6a09c0-e59b-4745-8612-f4499b676c47"), + Name = "Gradutaion trip", + Budget = 1000.0f, + Description = "Gradution trip with friends", + Tags = new List{"Travel"}, + StartsAt = new DateTimeOffset(new DateTime(2013, 5, 1)), + EndsAt = new DateTimeOffset(new DateTime(2013, 5, 8)) + } + } + }, + new Person() + { + FirstName = "Clyde", + LastName = "Guess", + UserName = "clydeguess", + Gender = PersonGender.Male, + Emails = new List { "Clyde@example.com" }, + AddressInfo = new List + { + new Location() + { + Address = "55 Grizzly Peak Rd.", + City = new City() + { + CountryRegion = "United States", + Name = "Butte", + Region = "MT" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 10, + ShareId = new Guid("a88f675d-9199-4392-9656-b08e3b46df8a"), + Name = "Study trip", + Budget = 1550.3f, + Description = "This is a 2 weeks study trip", + Tags = new List{"study"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 14)) + } + } + }, + new Person() + { + FirstName = "Keith", + LastName = "Pinckney", + UserName = "keithpinckney", + Gender = PersonGender.Male, + Emails = new List { "Keith@example.com", "Keith@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "55 Grizzly Peak Rd.", + City = new City() + { + CountryRegion = "United States", + Name = "Butte", + Region = "MT" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 11, + ShareId = new Guid("a88f675d-9199-4392-9656-b08e3b46df8a"), + Name = "Study trip", + Budget = 1550.3f, + Description = "This is a 2 weeks study trip", + Tags = new List{"study"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 14)) + } + } + }, + new Person() + { + FirstName = "Marshall", + LastName = "Garay", + UserName = "marshallgaray", + Gender = PersonGender.Male, + Emails = new List { "Marshall@example.com", "Marshall@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "55 Grizzly Peak Rd.", + City = new City() + { + CountryRegion = "United States", + Name = "Butte", + Region = "MT" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 12, + ShareId = new Guid("a88f675d-9199-4392-9656-b08e3b46df8a"), + Name = "Study trip", + Budget = 1550.3f, + Description = "This is a 2 weeks study trip", + Tags = new List{"study"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 14)) + } + } + }, + new Person() + { + FirstName = "Ryan", + LastName = "Theriault", + UserName = "ryantheriault", + Gender = PersonGender.Male, + Emails = new List { "Ryan@example.com", "Ryan@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "55 Grizzly Peak Rd.", + City = new City() + { + CountryRegion = "United States", + Name = "Butte", + Region = "MT" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 13, + ShareId = new Guid("a88f675d-9199-4392-9656-b08e3b46df8a"), + Name = "Study trip", + Budget = 1550.3f, + Description = "This is a 2 weeks study trip", + Tags = new List{"study"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 14)) + } + } + }, + new Person() + { + FirstName = "Elaine", + LastName = "Stewart", + UserName = "elainestewart", + Gender = PersonGender.Female, + Emails = new List { "Elaine@example.com", "Elaine@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "55 Grizzly Peak Rd.", + City = new City() + { + CountryRegion = "United States", + Name = "Butte", + Region = "MT" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 14, + ShareId = new Guid("a88f675d-9199-4392-9656-b08e3b46df8a"), + Name = "Study trip", + Budget = 1550.3f, + Description = "This is a 2 weeks study trip", + Tags = new List{"study"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 14)) + } + } + }, + new Person() + { + FirstName = "Sallie", + LastName = "Sampson", + UserName = "salliesampson", + Gender = PersonGender.Female, + Emails = new List { "Sallie@example.com", "Sallie@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "87 Polk St. Suite 5", + City = new City() + { + CountryRegion = "United States", + Name = "San Francisco", + Region = "CA" + } + }, + new Location() + { + Address = "89 Chiaroscuro Rd.", + City = new City() + { + CountryRegion = "United States", + Name = "Portland", + Region = "OR" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 15, + ShareId = new Guid("a88f675d-9199-4392-9656-b08e3b46df8a"), + Name = "Study trip", + Budget = 600.0f, + Description = "This is a 2 weeks study trip", + Tags = new List{"study"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 14)) + } + } + }, + new Person() + { + FirstName = "Joni", + LastName = "Rosales", + UserName = "jonirosales", + Gender = PersonGender.Female, + Emails = new List { "Joni@example.com", "Joni@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "55 Grizzly Peak Rd.", + City = new City() + { + CountryRegion = "United States", + Name = "Butte", + Region = "MT" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 16, + ShareId = new Guid("a88f675d-9199-4392-9656-b08e3b46df8a"), + Name = "Study trip", + Budget = 2000.0f, + Description = "This is a 2 weeks study trip", + Tags = new List{"study"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 14)) + } + } + }, + new Person() + { + FirstName = "Georgina", + LastName = "Barlow", + UserName = "georginabarlow", + Gender = PersonGender.Female, + Emails = new List { "Georgina@example.com", "Georgina@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "55 Grizzly Peak Rd.", + City = new City() + { + CountryRegion = "United States", + Name = "Butte", + Region = "MT" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 17, + ShareId = new Guid("a88f675d-9199-4392-9656-b08e3b46df8a"), + Name = "Study trip", + Budget = 1550.3f, + Description = "This is a 2 weeks study trip", + Tags = new List{"study"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 14)) + } + } + }, + new Person() + { + FirstName = "Angel", + LastName = "Huffman", + UserName = "angelhuffman", Gender = PersonGender.Female, + Emails = new List { "Angel@example.com" }, + AddressInfo = new List + { + new Location() + { + Address = "55 Grizzly Peak Rd.", + City = new City() + { + CountryRegion = "United States", + Name = "Butte", + Region = "MT" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 18, + ShareId = new Guid("cb0b8acb-79cb-4127-8316-772bc4302824"), + Name = "DIY Trip", + Budget = 1500.3f, + Description = "This is a DIY trip", + Tags = new List{"Travel", "DIY"}, + StartsAt = new DateTimeOffset(new DateTime(2011, 2, 11)), + EndsAt = new DateTimeOffset(new DateTime(2011, 2, 14)) + } + } + }, + new Person() + { + FirstName = "Laurel", + LastName = "Osborn", + UserName = "laurelosborn", + Gender = PersonGender.Female, + Emails = new List { "Laurel@example.com", "Laurel@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "87 Polk St. Suite 5", + City = new City() + { + CountryRegion = "United States", + Name = "San Francisco", + Region = "CA" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 19, + ShareId = new Guid("a88f675d-9199-4392-9656-b08e3b46df8a"), + Name = "Study trip", + Budget = 1550.3f, + Description = "This is a 2 weeks study trip", + Tags = new List{"study"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 14)) + } + } + }, + new Person() + { + FirstName = "Sandy", + LastName = "Osborn", + UserName = "sandyosborn", + Gender = PersonGender.Female, + Emails = new List { "Sandy@example.com", "Sandy@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "87 Polk St. Suite 5", + City = new City() + { + CountryRegion = "United States", + Name = "San Francisco", + Region = "CA" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 20, + ShareId = new Guid("a88f675d-9199-4392-9656-b08e3b46df8a"), + Name = "Study trip", + Budget = 1550.3f, + Description = "This is a 2 weeks study trip", + Tags = new List{"study"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 14)) + } + } + }, + new Person() + { + FirstName = "Ursula", + LastName = "Bright", + UserName = "ursulabright", + Gender = PersonGender.Female, + Emails = new List { "Ursula@example.com", "Ursula@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "87 Polk St. Suite 5", + City = new City() + { + CountryRegion = "United States", + Name = "San Francisco", + Region = "CA" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 21, + ShareId = new Guid("a88f675d-9199-4392-9656-b08e3b46df8a"), + Name = "Study trip", + Budget = 1550.3f, + Description = "This is a 2 weeks study trip", + Tags = new List{"study"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 14)) + } + } + }, + new Person() + { + FirstName = "Genevieve", + LastName = "Reeves", + UserName = "genevievereeves", + Gender = PersonGender.Female, + Emails = new List { "Genevieve@example.com", "Genevieve@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "87 Polk St. Suite 5", + City = new City() + { + CountryRegion = "United States", + Name = "San Francisco", + Region = "CA" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 22, + ShareId = new Guid("a88f675d-9199-4392-9656-b08e3b46df8a"), + Name = "Study trip", + Budget = 1550.3f, + Description = "This is a 2 weeks study trip", + Tags = new List{"study"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 14)) + } + } + }, + new Person() + { + FirstName = "Krista", + LastName = "Kemp", + UserName = "kristakemp", + Gender = PersonGender.Female, + Emails = new List { "Krista@example.com" }, + AddressInfo = new List + { + new Location() + { + Address = "87 Polk St. Suite 5", + City = new City() + { + CountryRegion = "United States", + Name = "San Francisco", + Region = "CA" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 234, + ShareId = new Guid("a88f675d-9199-4392-9656-b08e3b46df8a"), + Name = "Study trip", + Budget = 1550.3f, + Description = "This is a 2 weeks study trip", + Tags = new List{"study"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 14)) + } + } + } + }); + + People.Single(p => p.UserName == "russellwhyte").Friends = new Collection() + { + People.Single(p => p.UserName == "scottketchum"), + People.Single(p => p.UserName == "ronaldmundy"), + People.Single(p => p.UserName == "javieralfred") + }; + People.Single(p => p.UserName == "scottketchum").Friends = new Collection() + { + People.Single(p => p.UserName == "russellwhyte"), + People.Single(p => p.UserName == "ronaldmundy") + }; + People.Single(p => p.UserName == "ronaldmundy").Friends = new Collection() + { + People.Single(p => p.UserName == "russellwhyte"), + People.Single(p => p.UserName == "scottketchum") + }; + People.Single(p => p.UserName == "javieralfred").Friends = new Collection() + { + People.Single(p => p.UserName == "willieashmore"), + People.Single(p => p.UserName == "vincentcalabrese") + }; + People.Single(p => p.UserName == "willieashmore").Friends = new Collection() + { + People.Single(p => p.UserName == "javieralfred"), + People.Single(p => p.UserName == "vincentcalabrese") + }; + People.Single(p => p.UserName == "vincentcalabrese").Friends = new Collection() + { + People.Single(p => p.UserName == "javieralfred"), + People.Single(p => p.UserName == "willieashmore") + }; + People.Single(p => p.UserName == "clydeguess").Friends = new Collection() + { + People.Single(p => p.UserName == "keithpinckney") + }; + People.Single(p => p.UserName == "keithpinckney").Friends = new Collection() + { + People.Single(p => p.UserName == "clydeguess"), + People.Single(p => p.UserName == "marshallgaray") + }; + People.Single(p => p.UserName == "marshallgaray").Friends = new Collection() + { + People.Single(p => p.UserName == "keithpinckney") + }; + People.Single(p => p.UserName == "ryantheriault").Friends = new Collection() + { + People.Single(p=>p.UserName == "elainestewart") + }; + People.Single(p => p.UserName == "elainestewart").Friends = new Collection() + { + People.Single(p => p.UserName == "ryantheriault") + }; + People.Single(p => p.UserName == "salliesampson").Friends = new Collection() + { + People.Single(p => p.UserName == "jonirosales") + }; + People.Single(p => p.UserName == "jonirosales").Friends = new Collection() + { + People.Single(p => p.UserName == "salliesampson") + }; + People.Single(p => p.UserName == "georginabarlow").Friends = new Collection() + { + People.Single(p => p.UserName == "angelhuffman") + }; + People.Single(p => p.UserName == "angelhuffman").Friends = new Collection() + { + People.Single(p => p.UserName == "georginabarlow") + }; + People.Single(p => p.UserName == "laurelosborn").Friends = new Collection() + { + People.Single(p => p.UserName == "sandyosborn") + }; + People.Single(p => p.UserName == "sandyosborn").Friends = new Collection() + { + People.Single(p => p.UserName == "laurelosborn") + }; + People.Single(p => p.UserName == "ursulabright").Friends = new Collection() + { + People.Single(p => p.UserName == "genevievereeves"), + People.Single(p => p.UserName == "kristakemp") + }; + People.Single(p => p.UserName == "genevievereeves").Friends = new Collection() + { + People.Single(p => p.UserName == "ursulabright") + }; + People.Single(p => p.UserName == "kristakemp").Friends = new Collection() + { + People.Single(p => p.UserName == "ursulabright") + }; + #endregion + + #region Me + this.Me = new Person() + { + FirstName = "April", + LastName = "Cline", + UserName = "aprilcline", + Gender = PersonGender.Female, + Emails = new List { "April@example.com", "April@contoso.com" }, + AddressInfo = new List + { + new Location() + { + Address = "P.O. Box 555", + City = new City() + { + CountryRegion = "United States", + Name = "Lander", + Region = "WY" + } + } + }, + Trips = new List + { + new Trip() + { + TripId = 101, + ShareId = new Guid("9d9b2fa0-efbf-490e-a5e3-bac8f7d47354"), + Name = "Trip in US", + Budget = 1000.0f, + Description = "Trip in US", + Tags = new List + { + "business", + "US" + }, + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 4)), + PlanItems = new List + { + new Flight() + { + PlanItemId = 11, + ConfirmationCode = "JH58493", + FlightNumber = "VA1930", + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1, 8, 0, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 1, 9, 20, 0)), + Airline = Airlines[0], + From = Airports[0], + To = Airports[1] + }, + new Event() + { + PlanItemId = 12, + Description = "Client Meeting", + ConfirmationCode = "4372899DD", + StartsAt = new DateTimeOffset(new DateTime(2014, 1, 2, 13, 0, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 1, 2, 16, 0, 0)), + Duration = new TimeSpan(3, 0, 0), + OccursAt = new EventLocation() + { + Address = "100 Church Street, 8th Floor, Manhattan, 10007", + BuildingInfo = "Regus Business Center", + City = new City() + { + Name = "New York City", + CountryRegion = "United States", + Region = "New York" + } + } + } + } + }, + new Trip() + { + TripId = 102, + Name = "Trip in Beijing", + Budget = 3000.0f, + ShareId = new Guid("f94e9116-8bdd-4dac-ab61-08438d0d9a71"), + Description = "Trip from Shanghai to Beijing", + Tags = new List{"Travel", "Beijing"}, + StartsAt = new DateTimeOffset(new DateTime(2014, 2, 1)), + EndsAt = new DateTimeOffset(new DateTime(2014, 2, 4)), + PlanItems = new List + { + new Flight() + { + PlanItemId = 21, + ConfirmationCode = "JH58494", + FlightNumber = "FM1930", + StartsAt = new DateTimeOffset(new DateTime(2014, 2, 1, 8, 0, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 2, 1, 9, 20, 0)), + Airline = Airlines[1], + SeatNumber = "B11", + From = Airports[2], + To = Airports[3] + }, + new Flight() + { + PlanItemId = 32, + ConfirmationCode = "JH58495", + FlightNumber = "MU1930", + StartsAt = new DateTimeOffset(new DateTime(2014, 2, 10, 15, 00, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 2, 10, 16, 30, 0)), + Airline = Airlines[2], + SeatNumber = "A32", + From = Airports[3], + To = Airports[2] + }, + new Event() + { + PlanItemId = 5, + Description = "Dinner", + StartsAt = new DateTimeOffset(new DateTime(2014, 2, 2, 18, 0, 0)), + EndsAt = new DateTimeOffset(new DateTime(2014, 2, 2, 21, 0, 0)), + Duration = new TimeSpan(3, 0, 0), + OccursAt = new EventLocation() + { + Address = "10 Beijing Street, 100000", + City = new City(){ + Name = "Beijing", + CountryRegion = "China", + Region = "Beijing" + }, + BuildingInfo = "Beijing Restaurant" + } + } + } + }, + new Trip() + { + TripId = 103, + ShareId = new Guid("9ce142c3-5fd6-4a71-848e-5220ebf1e9f3"), + Name = "Honeymoon", + Budget = 800.0f, + Description = "Happy honeymoon trip", + Tags = new List{"Travel", "honeymoon"}, + StartsAt = new DateTime(2014, 2, 1), + EndsAt = new DateTime(2014, 2, 4) + }, + new Trip() + { + TripId = 104, + ShareId = new Guid("4CCFB043-C79C-44EF-8CFE-CD493CED6654"), + Name = "Business trip to OData", + Budget = 324.6f, + Description = "Business trip to OData", + Tags = new List{"business", "odata"}, + StartsAt = new DateTime(2013, 1, 1), + EndsAt = new DateTime(2013, 1, 4) + }, + new Trip() + { + TripId = 105, + ShareId = new Guid("4546F419-0070-45F7-BA2C-19E4BC3647E1"), + Name = "Travel trip in US", + Budget = 1250.0f, + Description = "Travel trip in US", + Tags = new List{"travel", "overseas"}, + StartsAt = new DateTime(2013, 1, 19), + EndsAt = new DateTime(2013, 1, 28) + }, + new Trip() + { + TripId = 106, + ShareId = new Guid("26F0E8F6-657A-4561-BF3B-719366EF04FA"), + Name = "Study music in Europe", + Budget = 3200.0f, + Description = "Study music in Europe", + Tags = new List{"study", "overseas"}, + StartsAt = new DateTime(2013, 3, 1), + EndsAt = new DateTime(2013, 5, 4) + }, + new Trip() + { + TripId = 107, + ShareId = new Guid("2E77BF06-A354-454B-8BCA-5F004C1AFB59"), + Name = "Conference talk about OData", + Budget = 2120.55f, + Description = "Conference talk about ODatan", + Tags = new List{"odata", "overseas"}, + StartsAt = new DateTime(2013, 7, 2), + EndsAt = new DateTime(2013, 7, 5) + }, + new Trip() + { + TripId = 108, + ShareId = new Guid("E6E23FB2-C428-439E-BDAB-9283482F49F0"), + Name = "Vocation at hometown", + Budget = 1500.0f, + Description = "Vocation at hometown", + Tags = new List{"voaction"}, + StartsAt = new DateTime(2013, 10, 1), + EndsAt = new DateTime(2013, 10, 5) + }, + new Trip() + { + TripId = 109, + ShareId = new Guid("FAE31279-35CE-4119-9BDC-53F6E19DD1C5"), + Name = "Business trip for tech training", + Budget = 100.0f, + Description = "Business trip for tech training", + Tags = new List{"business"}, + StartsAt = new DateTime(2013, 9, 1), + EndsAt = new DateTime(2013, 9, 4) + } + } + }; + + Me.Friends = People; + #endregion Me + } + } +} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/TrippinApi.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/TrippinApi.cs deleted file mode 100644 index 10dcfdfb..00000000 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Models/TrippinApi.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Web.OData.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OData.Edm; -using Microsoft.Restier.Core; -using Microsoft.Restier.Core.Model; - -namespace Microsoft.OData.Service.Sample.TrippinInMemory.Models -{ - public class TrippinApi : ApiBase - { - private static readonly List people = new List - { - new Person - { - PersonId = 1, - FirstName = "u1", - FavoriteFeature = Feature.Feature1, - Emails = new Collection - { - "u1@trippin.com", - "u1@odata.org" - }, - HomeAddress = new Location { Address = "ccc1" }, - Locations = new Collection - { - new Location { Address = "a1" }, - new Location { Address = "b1" } - }, - Features = new Collection - { - Feature.Feature1, - Feature.Feature3 - } - }, - new Person - { - PersonId = 2, - FirstName = "u2", - FavoriteFeature = Feature.Feature3, - Emails = new Collection - { - "u2@trippin.com", - "u2@odata.org" - }, - Locations = new Collection - { - new Location { Address = "a2" }, - new Location { Address = "b2" } - }, - Features = new Collection - { - Feature.Feature3, - Feature.Feature2 - } - }, - new Person - { - PersonId = 3, - FirstName = "u3", - FavoriteFeature = Feature.Feature2, - Emails = new Collection - { - "u3@trippin.com", - "u3@odata.org" - }, - Locations = new Collection - { - new Location { Address = "a3" }, - new Location { Address = "b3" } - }, - Features = new Collection - { - Feature.Feature2, - Feature.Feature4 - } - }, - new Person - { - PersonId = 4, - FirstName = "u4", - FavoriteFeature = Feature.Feature4, - Emails = new Collection - { - "u4@trippin.com", - "u4@odata.org" - }, - Locations = new Collection - { - new Location { Address = "a4" }, - new Location { Address = "b4" } - }, - Features = new Collection - { - Feature.Feature4, - Feature.Feature1 - } - }, - new Person - { - PersonId = 5, - FirstName = "u4", - FavoriteFeature = Feature.Feature4, - Emails = new Collection(), - Features = new Collection(), - Locations = new Collection(), - Trips = new Collection() - { - new Trip() - { - TripId = 0, - Name = "Team Building", - Description = "Trip from Shanghai To Chongqing" - }, - new Trip() - { - TripId = 0, - Name = "Team Building", - Description = "Trip from Chongqing To Shanghai" - } - } - }, - new Person - { - PersonId = 6, - FirstName = "u4", - FavoriteFeature = Feature.Feature4, - Emails = new Collection - { - "u4@trippin.com", - "u4@odata.org" - }, - HomeAddress = new Location(), - Locations = new Collection - { - new Location { Address = "a4" }, - new Location { Address = "b4" } - }, - Features = new Collection - { - Feature.Feature4, - Feature.Feature1 - }, - Trips = new Collection() - }, - new Person - { - PersonId = 7, - FirstName = "u4", - FavoriteFeature = Feature.Feature4, - HomeAddress = new Location() - } - }; - - static TrippinApi() - { - people[0].Friends = new Collection { people[1], people[2] }; - people[1].Friends = new Collection { people[2], people[3] }; - people[2].Friends = new Collection { people[3], people[0] }; - people[3].Friends = new Collection { people[0], people[1] }; - people[4].Friends = new Collection(); - people[5].Friends = new Collection(); - people[6].Friends = new Collection(); - - people[5].BestFriend = people[4]; - } - - public IQueryable People - { - get { return people.AsQueryable(); } - } - - public IQueryable NewComePeople - { - get { return this.GetQueryableSource("People").Where(p => p.PersonId >= 2); } - } - - protected override IServiceCollection ConfigureApi(IServiceCollection services) - { - services.AddService((sp, next) => new ModelBuilder()); - return base.ConfigureApi(services); - } - - private class ModelBuilder : IModelBuilder - { - public Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) - { - var services = new ODataConventionModelBuilder(); - services.EntityType(); - return Task.FromResult(services.GetEdmModel()); - } - } - } -} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Web.config b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Web.config index fe9dac19..25051b71 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Web.config +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Web.config @@ -14,17 +14,29 @@ --> - + + + + + - + + + + + + + + +