Create webapi.odata projects for v4 OData

1. Copy System.Web.Http.OData to System.Web.OData
2. Copy System.Web.Http.OData.Test to System.Web.OData.Test
3. Generate new project guids
4. Set new src project InternalsVisibleTo to new test project
5. Update with latest changes from System.Web.Http.OData and the test project
6. Add 2 new projects to SkipStrongNames.xml
7. AssemblyTitle change
8. Move SuppressMessage from AssemblyInfo.cs to GlobalSuppressions.cs
9. Not change test project System.Web.OData.Test's RootNamespace (System.Web.Http) for now, since test resource files depend on it.
10. Suppress CA1704:IdentifiersShouldBeSpelledCorrectly for Unsortable.
This commit is contained in:
Congyong Su 2013-12-13 21:36:13 +08:00
Родитель 1fe1c1a57a
Коммит de22e048cd
637 изменённых файлов: 81101 добавлений и 0 удалений

Просмотреть файл

@ -115,6 +115,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Cors.Test", "tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Http.Cors.Test", "test\System.Web.Http.Cors.Test\System.Web.Http.Cors.Test.csproj", "{1E89A3E9-0A7F-418F-B4BE-6E38A6315373}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.OData", "src\System.Web.OData\System.Web.OData.csproj", "{D23E28F1-CCD0-43E0-8C0D-36731EC91318}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.OData.Test", "test\System.Web.OData.Test\System.Web.OData.Test.csproj", "{66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
CodeAnalysis|Any CPU = CodeAnalysis|Any CPU
@ -434,6 +438,18 @@ Global
{1E89A3E9-0A7F-418F-B4BE-6E38A6315373}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E89A3E9-0A7F-418F-B4BE-6E38A6315373}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E89A3E9-0A7F-418F-B4BE-6E38A6315373}.Release|Any CPU.Build.0 = Release|Any CPU
{D23E28F1-CCD0-43E0-8C0D-36731EC91318}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU
{D23E28F1-CCD0-43E0-8C0D-36731EC91318}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU
{D23E28F1-CCD0-43E0-8C0D-36731EC91318}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D23E28F1-CCD0-43E0-8C0D-36731EC91318}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D23E28F1-CCD0-43E0-8C0D-36731EC91318}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D23E28F1-CCD0-43E0-8C0D-36731EC91318}.Release|Any CPU.Build.0 = Release|Any CPU
{66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU
{66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU
{66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -464,6 +480,7 @@ Global
{66DD7CD7-C68F-4D0E-9F3D-3B58C49D1467} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}
{43C1B979-D593-4A32-BB3A-4316F1C66D66} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}
{25DEF6F6-7F99-4EB7-91ED-5181A346AFE1} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}
{D23E28F1-CCD0-43E0-8C0D-36731EC91318} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}
{0BB62A1D-E6B5-49FA-9E3C-6AF679A66DFE} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
{268DEE9D-F323-4A00-8ED8-3784388C3E3A} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
{0F4870DB-A799-4DBA-99DF-0D74BB52FEC2} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
@ -491,5 +508,6 @@ Global
{C19267DD-3984-430C-AE18-4034F85DE4E5} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
{BF07E947-120D-4E93-93DA-A4BF121753EA} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
{1E89A3E9-0A7F-418F-B4BE-6E38A6315373} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
{66EFD03D-95B7-4C7E-83AC-1A8BD6C12612} = {C40883CD-366D-4534-8B58-3EA0D13136DF}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Net.Http", Justification = "Follows System.Net.Http naming")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Http", Justification = "Follows System.Web.Http naming")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Http.OData.Formatter", Justification = "Follows System.Web.Http naming")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Http.OData.Formatter.Serialization", Justification = "Follows System.Web.Http naming")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Http.OData.Builder.Conventions.Attributes", Justification = "Follows System.Web.Http naming")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Http.OData.Results", Justification = "Follows System.Web.Http naming")]
[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "These assemblies are delay-signed.")]
[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "member", Target = "System.Web.Http.OData.Formatter.EdmLibHelpers.#.cctor()", Justification = "Class coupling necessary in this class")]
[assembly: SuppressMessage("Microsoft.Web.FxCop", "MW1000:UnusedResourceUsageRule", MessageId = "172567", Justification = "Resource used by framework")]
[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "inlinecount", Scope = "resource", Target = "System.Web.Http.OData.Properties.SRResources.resources", Justification = "spelled correctly")]
[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "orderby", Scope = "resource", Target = "System.Web.Http.OData.Properties.SRResources.resources", Justification = "$orderby is an odata keyword")]
[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "it", Scope = "resource", Target = "System.Web.Http.OData.Properties.SRResources.resources", Justification = "$it is an odata keyword")]
[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "type", Target = "System.Web.Http.OData.Builder.EdmModelHelperMethods", Justification = "Static helper class. Class coupling acceptable here.")]
[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "type", Target = "System.Web.Http.OData.Formatter.EdmLibHelpers", Justification = "Static helper class. Class coupling acceptable here.")]
[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unsortable", Scope = "member", Target = "System.Web.Http.OData.Builder.PropertyConfiguration.#Unsortable", Justification = "spelled correctly")]
[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unsortable", Scope = "member", Target = "System.Web.Http.OData.Builder.PropertyConfiguration.#IsUnsortable()", Justification = "spelled correctly")]
[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unsortable", Scope = "member", Target = "System.Web.Http.OData.QueryableRestrictions.#Unsortable", Justification = "spelled correctly")]
[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unsortable", Scope = "type", Target = "System.Web.Http.OData.Query.UnsortableAttribute", Justification = "spelled correctly")]

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Diagnostics.Contracts;
using System.Web.Http.Controllers;
using System.Web.Http.OData.Builder;
using Microsoft.Data.Edm;
namespace System.Web.Http
{
internal static class HttpActionDescriptorExtensions
{
internal const string EdmModelKey = "MS_EdmModel";
internal static IEdmModel GetEdmModel(this HttpActionDescriptor actionDescriptor, Type entityClrType)
{
if (actionDescriptor == null)
{
throw Error.ArgumentNull("actionDescriptor");
}
if (entityClrType == null)
{
throw Error.ArgumentNull("entityClrType");
}
// save the EdmModel to the action descriptor
return actionDescriptor.Properties.GetOrAdd(EdmModelKey + entityClrType.FullName, _ =>
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder(actionDescriptor.Configuration, isQueryCompositionMode: true);
EntityTypeConfiguration entityTypeConfiguration = builder.AddEntity(entityClrType);
builder.AddEntitySet(entityClrType.Name, entityTypeConfiguration);
IEdmModel edmModel = builder.GetEdmModel();
Contract.Assert(edmModel != null);
return edmModel;
}) as IEdmModel;
}
}
}

Просмотреть файл

@ -0,0 +1,222 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http.Description;
using System.Web.Http.OData.Properties;
using System.Web.Http.OData.Query;
using System.Web.Http.OData.Routing;
using Microsoft.Data.OData;
using Microsoft.Data.OData.Query;
namespace System.Web.Http.OData
{
/// <summary>
/// Provides a convenient starting point for a controller that exposes an OData entity set. This is the asynchronous version of <see cref="EntitySetController{TEntity, TKey}"/>.
/// </summary>
/// <typeparam name="TEntity">The type associated with the exposed entity set's entity type.</typeparam>
/// <typeparam name="TKey">The type associated with the entity key of the exposed entity set's entity type.</typeparam>
[CLSCompliant(false)]
[ODataNullValue]
public abstract class AsyncEntitySetController<TEntity, TKey> : ODataController where TEntity : class
{
/// <summary>
/// Gets the OData path of the current request.
/// </summary>
public ODataPath ODataPath
{
get
{
return EntitySetControllerHelpers.GetODataPath(this);
}
}
/// <summary>
/// Gets the OData query options of the current request.
/// </summary>
public ODataQueryOptions<TEntity> QueryOptions
{
get
{
return EntitySetControllerHelpers.CreateQueryOptions<TEntity>(this);
}
}
/// <summary>
/// This method should be overridden to handle GET requests that attempt to retrieve entities from the entity set. This method should asynchronously compute the
/// matching entities by applying the request's query options.
/// </summary>
/// <returns>A <see cref="Task"/> that contains the matching entities from the entity set when it completes.</returns>
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get", Justification = "Needs to be this name to follow routing conventions.")]
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Using tasks")]
public virtual Task<IEnumerable<TEntity>> Get()
{
throw EntitySetControllerHelpers.GetNotImplementedResponse(Request);
}
/// <summary>
/// Handles GET requests that attempt to retrieve an individual entity by key from the entity set.
/// </summary>
/// <param name="key">The entity key of the entity to retrieve.</param>
/// <returns>A <see cref="Task"/> that contains the response message to send back to the client when it completes.</returns>
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get", Justification = "Needs to be this name to follow routing conventions.")]
public virtual async Task<HttpResponseMessage> Get([FromODataUri] TKey key)
{
TEntity entity = await GetEntityByKeyAsync(key);
return EntitySetControllerHelpers.GetByKeyResponse<TEntity>(Request, entity);
}
/// <summary>
/// Handles POST requests that create new entities in the entity set.
/// </summary>
/// <param name="entity">The entity to insert into the entity set.</param>
/// <returns>A <see cref="Task"/> that contains the response message to send back to the client when it completes.</returns>
public virtual async Task<HttpResponseMessage> Post([FromBody] TEntity entity)
{
TEntity createdEntity = await CreateEntityAsync(entity);
return EntitySetControllerHelpers.PostResponse<TEntity, TKey>(this, createdEntity, GetKey(createdEntity));
}
/// <summary>
/// Handles PUT requests that attempt to replace a single entity in the entity set.
/// </summary>
/// <param name="key">The entity key of the entity to replace.</param>
/// <param name="update">The updated entity.</param>
/// <returns>A <see cref="Task"/> that contains the response message to send back to the client when it completes.</returns>
public virtual async Task<HttpResponseMessage> Put([FromODataUri] TKey key, [FromBody] TEntity update)
{
TEntity updatedEntity = await UpdateEntityAsync(key, update);
return EntitySetControllerHelpers.PutResponse<TEntity>(Request, updatedEntity);
}
/// <summary>
/// Handles PATCH and MERGE requests to partially update a single entity in the entity set.
/// </summary>
/// <param name="key">The entity key of the entity to update.</param>
/// <param name="patch">The patch representing the partial update.</param>
/// <returns>A <see cref="Task"/> that contains the response message to send back to the client when it completes.</returns>
[AcceptVerbs("PATCH", "MERGE")]
[SuppressMessage("Microsoft.Naming", "CA1719:ParameterNamesShouldNotMatchMemberNames", MessageId = "1#", Justification = "Patch is the action name by WebAPI convention.")]
public virtual async Task<HttpResponseMessage> Patch([FromODataUri] TKey key, Delta<TEntity> patch)
{
TEntity patchedEntity = await PatchEntityAsync(key, patch);
return EntitySetControllerHelpers.PatchResponse<TEntity>(Request, patchedEntity);
}
/// <summary>
/// This method should be overridden to handles DELETE requests for deleting existing entities from the entity set.
/// </summary>
/// <param name="key">The entity key of the entity to delete.</param>
/// <returns>A <see cref="Task"/> that completes when the entity has been successfully deleted.</returns>
public virtual Task Delete([FromODataUri] TKey key)
{
throw EntitySetControllerHelpers.DeleteEntityNotImplementedResponse(Request);
}
/// <summary>
/// This method should be overridden to handle POST and PUT requests that attempt to create a link between two entities.
/// </summary>
/// <param name="key">The key of the entity with the navigation property.</param>
/// <param name="navigationProperty">The name of the navigation property.</param>
/// <param name="link">The URI of the entity to link.</param>
/// <returns>A <see cref="Task"/> that completes when the link has been successfully created.</returns>
[AcceptVerbs("POST", "PUT")]
public virtual Task CreateLink([FromODataUri] TKey key, string navigationProperty, [FromBody] Uri link)
{
throw EntitySetControllerHelpers.CreateLinkNotImplementedResponse(Request, navigationProperty);
}
/// <summary>
/// This method should be overridden to handle DELETE requests that attempt to break a relationship between two entities.
/// </summary>
/// <param name="key">The key of the entity with the navigation property.</param>
/// <param name="navigationProperty">The name of the navigation property.</param>
/// <param name="link">The URI of the entity to remove from the navigation property.</param>
/// <returns>A <see cref="Task"/> that completes when the link has been successfully deleted.</returns>
public virtual Task DeleteLink([FromODataUri] TKey key, string navigationProperty, [FromBody] Uri link)
{
throw EntitySetControllerHelpers.DeleteLinkNotImplementedResponse(Request, navigationProperty);
}
/// <summary>
/// This method should be overridden to handle DELETE requests that attempt to break a relationship between two entities.
/// </summary>
/// <param name="key">The key of the entity with the navigation property.</param>
/// <param name="relatedKey">The key of the related entity.</param>
/// <param name="navigationProperty">The name of the navigation property.</param>
/// <returns>A <see cref="Task"/> that completes when the link has been successfully deleted.</returns>
public virtual Task DeleteLink([FromODataUri] TKey key, string relatedKey, string navigationProperty)
{
throw EntitySetControllerHelpers.DeleteLinkNotImplementedResponse(Request, navigationProperty);
}
/// <summary>
/// This method should be overridden to handle all unmapped OData requests.
/// </summary>
/// <param name="odataPath">The OData path of the request.</param>
/// <returns>A <see cref="Task"/> that contains the response message to send back to the client when it completes.</returns>
[AcceptVerbs("GET", "POST", "PUT", "PATCH", "MERGE", "DELETE")]
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "odata", Justification = "odata is spelled correctly.")]
public virtual Task<HttpResponseMessage> HandleUnmappedRequest(ODataPath odataPath)
{
throw EntitySetControllerHelpers.UnmappedRequestResponse(Request, odataPath);
}
/// <summary>
/// This method should be overridden to get the entity key of the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns>The entity key value</returns>
protected internal virtual TKey GetKey(TEntity entity)
{
throw EntitySetControllerHelpers.GetKeyNotImplementedResponse(Request);
}
/// <summary>
/// This method should be overridden to retrieve an entity by key from the entity set.
/// </summary>
/// <param name="key">The entity key of the entity to retrieve.</param>
/// <returns>A <see cref="Task"/> that contains the retrieved entity when it completes, or <c>null</c> if an entity with the specified entity key cannot be found in the entity set.</returns>
protected internal virtual Task<TEntity> GetEntityByKeyAsync(TKey key)
{
throw EntitySetControllerHelpers.GetEntityByKeyNotImplementedResponse(Request);
}
/// <summary>
/// This method should be overridden to create a new entity in the entity set.
/// </summary>
/// <remarks>When overriding this method, the GetKey method should also be overridden so that the location header can be generated.</remarks>
/// <param name="entity">The entity to add to the entity set.</param>
/// <returns>A <see cref="Task"/> that contains the created entity when it completes.</returns>
protected internal virtual Task<TEntity> CreateEntityAsync(TEntity entity)
{
throw EntitySetControllerHelpers.CreateEntityNotImplementedResponse(Request);
}
/// <summary>
/// This method should be overridden to update an existing entity in the entity set.
/// </summary>
/// <param name="key">The entity key of the entity to update.</param>
/// <param name="update">The updated entity.</param>
/// <returns>A <see cref="Task"/> that contains the updated entity when it completes.</returns>
protected internal virtual Task<TEntity> UpdateEntityAsync(TKey key, TEntity update)
{
throw EntitySetControllerHelpers.UpdateEntityNotImplementedResponse(Request);
}
/// <summary>
/// This method should be overridden to apply a partial update to an existing entity in the entity set.
/// </summary>
/// <param name="key">The entity key of the entity to update.</param>
/// <param name="patch">The patch representing the partial update.</param>
/// <returns>A <see cref="Task"/> that contains the updated entity when it completes.</returns>
protected internal virtual Task<TEntity> PatchEntityAsync(TKey key, Delta<TEntity> patch)
{
throw EntitySetControllerHelpers.PatchEntityNotImplementedResponse(Request);
}
}
}

Просмотреть файл

@ -0,0 +1,120 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace System.Web.Http.OData.Batch
{
/// <summary>
/// Represents a ChangeSet request.
/// </summary>
public class ChangeSetRequestItem : ODataBatchRequestItem
{
/// <summary>
/// Initializes a new instance of the <see cref="ChangeSetRequestItem"/> class.
/// </summary>
/// <param name="requests">The request messages in the ChangeSet.</param>
public ChangeSetRequestItem(IEnumerable<HttpRequestMessage> requests)
{
if (requests == null)
{
throw Error.ArgumentNull("requests");
}
Requests = requests;
}
/// <summary>
/// Gets the request messages in the ChangeSet.
/// </summary>
public IEnumerable<HttpRequestMessage> Requests { get; private set; }
/// <summary>
/// Sends the ChangeSet request.
/// </summary>
/// <param name="invoker">The invoker.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="ChangeSetResponseItem"/>.</returns>
public override async Task<ODataBatchResponseItem> SendRequestAsync(HttpMessageInvoker invoker, CancellationToken cancellationToken)
{
if (invoker == null)
{
throw Error.ArgumentNull("invoker");
}
Dictionary<string, string> contentIdToLocationMapping = new Dictionary<string, string>();
List<HttpResponseMessage> responses = new List<HttpResponseMessage>();
try
{
foreach (HttpRequestMessage request in Requests)
{
HttpResponseMessage response = await SendMessageAsync(invoker, request, cancellationToken, contentIdToLocationMapping);
if (response.IsSuccessStatusCode)
{
responses.Add(response);
}
else
{
DisposeResponses(responses);
responses.Clear();
responses.Add(response);
return new ChangeSetResponseItem(responses);
}
}
}
catch
{
DisposeResponses(responses);
throw;
}
return new ChangeSetResponseItem(responses);
}
/// <summary>
/// Gets the resources registered for dispose on each request messages of the ChangeSet.
/// </summary>
/// <returns>A collection of resources registered for dispose.</returns>
public override IEnumerable<IDisposable> GetResourcesForDisposal()
{
List<IDisposable> resources = new List<IDisposable>();
foreach (HttpRequestMessage request in Requests)
{
if (request != null)
{
resources.AddRange(request.GetResourcesForDisposal());
}
}
return resources;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (disposing)
{
foreach (HttpRequestMessage request in Requests)
{
if (request != null)
{
request.Dispose();
}
}
}
}
internal static void DisposeResponses(List<HttpResponseMessage> responses)
{
foreach (HttpResponseMessage response in responses)
{
if (response != null)
{
response.Dispose();
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,72 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.OData;
namespace System.Web.Http.OData.Batch
{
/// <summary>
/// Represents a ChangeSet response.
/// </summary>
public class ChangeSetResponseItem : ODataBatchResponseItem
{
/// <summary>
/// Initializes a new instance of the <see cref="ChangeSetResponseItem"/> class.
/// </summary>
/// <param name="responses">The response messages for the ChangeSet requests.</param>
public ChangeSetResponseItem(IEnumerable<HttpResponseMessage> responses)
{
if (responses == null)
{
throw Error.ArgumentNull("responses");
}
Responses = responses;
}
/// <summary>
/// Gets the response messages for the ChangeSet.
/// </summary>
public IEnumerable<HttpResponseMessage> Responses { get; private set; }
/// <summary>
/// Writes the responses as a ChangeSet.
/// </summary>
/// <param name="writer">The <see cref="ODataBatchWriter"/>.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
public override async Task WriteResponseAsync(ODataBatchWriter writer, CancellationToken cancellationToken)
{
if (writer == null)
{
throw Error.ArgumentNull("writer");
}
writer.WriteStartChangeset();
foreach (HttpResponseMessage responseMessage in Responses)
{
await WriteMessageAsync(writer, responseMessage, cancellationToken);
}
writer.WriteEndChangeset();
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (disposing)
{
foreach (HttpResponseMessage response in Responses)
{
if (response != null)
{
response.Dispose();
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,193 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Batch;
using Microsoft.Data.OData;
namespace System.Web.Http.OData.Batch
{
/// <summary>
/// Default implementation of <see cref="ODataBatchHandler"/> for handling OData batch request.
/// </summary>
/// <remarks>
/// By default, it buffers the request content stream.
/// </remarks>
public class DefaultODataBatchHandler : ODataBatchHandler
{
/// <summary>
/// Initializes a new instance of the <see cref="DefaultODataBatchHandler"/> class.
/// </summary>
/// <param name="httpServer">The <see cref="HttpServer"/> for handling the individual batch requests.</param>
public DefaultODataBatchHandler(HttpServer httpServer)
: base(httpServer)
{
}
/// <inheritdoc/>
public override async Task<HttpResponseMessage> ProcessBatchAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
ValidateRequest(request);
IList<ODataBatchRequestItem> subRequests = await ParseBatchRequestsAsync(request, cancellationToken);
try
{
IList<ODataBatchResponseItem> responses = await ExecuteRequestMessagesAsync(subRequests, cancellationToken);
return await CreateResponseMessageAsync(responses, request, cancellationToken);
}
finally
{
foreach (ODataBatchRequestItem subRequest in subRequests)
{
request.RegisterForDispose(subRequest.GetResourcesForDisposal());
request.RegisterForDispose(subRequest);
}
}
}
/// <summary>
/// Executes the OData batch requests.
/// </summary>
/// <param name="requests">The collection of OData batch requests.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A collection of <see cref="ODataBatchResponseItem"/> for the batch requests.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We need to return a collection of response messages asynchronously.")]
public virtual async Task<IList<ODataBatchResponseItem>> ExecuteRequestMessagesAsync(IEnumerable<ODataBatchRequestItem> requests, CancellationToken cancellationToken)
{
if (requests == null)
{
throw Error.ArgumentNull("requests");
}
IList<ODataBatchResponseItem> responses = new List<ODataBatchResponseItem>();
try
{
foreach (ODataBatchRequestItem request in requests)
{
responses.Add(await request.SendRequestAsync(Invoker, cancellationToken));
}
}
catch
{
foreach (ODataBatchResponseItem response in responses)
{
if (response != null)
{
response.Dispose();
}
}
throw;
}
return responses;
}
/// <summary>
/// Converts the incoming OData batch request into a collection of request messages.
/// </summary>
/// <param name="request">The request containing the batch request messages.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A collection of <see cref="ODataBatchRequestItem"/>.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We need to return a collection of request messages asynchronously.")]
public virtual async Task<IList<ODataBatchRequestItem>> ParseBatchRequestsAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
ODataMessageReaderSettings oDataReaderSettings = new ODataMessageReaderSettings
{
DisableMessageStreamDisposal = true,
MessageQuotas = MessageQuotas,
BaseUri = GetBaseUri(request)
};
ODataMessageReader reader = await request.Content.GetODataMessageReaderAsync(oDataReaderSettings, cancellationToken);
request.RegisterForDispose(reader);
List<ODataBatchRequestItem> requests = new List<ODataBatchRequestItem>();
ODataBatchReader batchReader = reader.CreateODataBatchReader();
Guid batchId = Guid.NewGuid();
while (batchReader.Read())
{
if (batchReader.State == ODataBatchReaderState.ChangesetStart)
{
IList<HttpRequestMessage> changeSetRequests = await batchReader.ReadChangeSetRequestAsync(batchId, cancellationToken);
foreach (HttpRequestMessage changeSetRequest in changeSetRequests)
{
changeSetRequest.CopyBatchRequestProperties(request);
}
requests.Add(new ChangeSetRequestItem(changeSetRequests));
}
else if (batchReader.State == ODataBatchReaderState.Operation)
{
HttpRequestMessage operationRequest = await batchReader.ReadOperationRequestAsync(batchId, bufferContentStream: true, cancellationToken: cancellationToken);
operationRequest.CopyBatchRequestProperties(request);
requests.Add(new OperationRequestItem(operationRequest));
}
}
return requests;
}
/// <summary>
/// Creates the batch response message.
/// </summary>
/// <param name="responses">The responses for the batch requests.</param>
/// <param name="request">The original request containing all the batch requests.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The batch response message.</returns>
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing the object.")]
public virtual Task<HttpResponseMessage> CreateResponseMessageAsync(
IEnumerable<ODataBatchResponseItem> responses, HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
return request.CreateODataBatchResponseAsync(responses, MessageQuotas);
}
/// <summary>
/// Validates the incoming request that contains the batch request messages.
/// </summary>
/// <param name="request">The request containing the batch request messages.</param>
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing the object.")]
public virtual void ValidateRequest(HttpRequestMessage request)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
request.ValidateODataBatchRequest();
}
/// <summary>
/// Gets the base URI for the batched requests.
/// </summary>
/// <param name="request">The original request containing all the batch requests.</param>
/// <returns>The base URI.</returns>
public virtual Uri GetBaseUri(HttpRequestMessage request)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
return request.GetODataBatchBaseUri(ODataRouteName);
}
}
}

Просмотреть файл

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace System.Web.Http.OData.Batch
{
internal class LazyStreamContent : HttpContent
{
private Func<Stream> _getStream;
private StreamContent _streamContent;
public LazyStreamContent(Func<Stream> getStream)
{
_getStream = getStream;
}
private StreamContent StreamContent
{
get
{
if (_streamContent == null)
{
_streamContent = new StreamContent(_getStream());
}
return _streamContent;
}
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
return StreamContent.CopyToAsync(stream, context);
}
protected override bool TryComputeLength(out long length)
{
length = -1;
return false;
}
}
}

Просмотреть файл

@ -0,0 +1,100 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.OData.Formatter;
using Microsoft.Data.OData;
namespace System.Web.Http.OData.Batch
{
/// <summary>
/// Encapsulates a collection of OData batch responses.
/// </summary>
public class ODataBatchContent : HttpContent
{
private ODataMessageWriterSettings _writerSettings;
/// <summary>
/// Initializes a new instance of the <see cref="ODataBatchContent"/> class.
/// </summary>
/// <param name="responses">The batch responses.</param>
public ODataBatchContent(IEnumerable<ODataBatchResponseItem> responses)
: this(responses, new ODataMessageWriterSettings())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ODataBatchContent"/> class.
/// </summary>
/// <param name="responses">The batch responses.</param>
/// <param name="writerSettings">The <see cref="ODataMessageWriterSettings"/>.</param>
public ODataBatchContent(IEnumerable<ODataBatchResponseItem> responses, ODataMessageWriterSettings writerSettings)
{
if (responses == null)
{
throw Error.ArgumentNull("responses");
}
if (writerSettings == null)
{
throw Error.ArgumentNull("writerSettings");
}
Responses = responses;
_writerSettings = writerSettings;
Headers.ContentType = MediaTypeHeaderValue.Parse(String.Format(CultureInfo.InvariantCulture, "multipart/mixed;boundary=batchresponse_{0}", Guid.NewGuid()));
ODataVersion version = _writerSettings.Version.HasValue ? _writerSettings.Version.Value : ODataMediaTypeFormatter.DefaultODataVersion;
Headers.TryAddWithoutValidation(ODataMediaTypeFormatter.ODataServiceVersion, ODataUtils.ODataVersionToString(version));
}
/// <summary>
/// Gets the batch responses.
/// </summary>
public IEnumerable<ODataBatchResponseItem> Responses { get; private set; }
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (disposing)
{
foreach (ODataBatchResponseItem response in Responses)
{
if (response != null)
{
response.Dispose();
}
}
}
base.Dispose(disposing);
}
/// <inheritdoc/>
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
IODataResponseMessage responseMessage = new ODataMessageWrapper(stream, Headers);
ODataMessageWriter messageWriter = new ODataMessageWriter(responseMessage, _writerSettings);
ODataBatchWriter writer = messageWriter.CreateODataBatchWriter();
writer.WriteStartBatch();
foreach (ODataBatchResponseItem response in Responses)
{
await response.WriteResponseAsync(writer, CancellationToken.None);
}
writer.WriteEndBatch();
}
/// <inheritdoc/>
protected override bool TryComputeLength(out long length)
{
length = -1;
return false;
}
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Web.Http.Batch;
using Microsoft.Data.OData;
namespace System.Web.Http.OData.Batch
{
/// <summary>
/// Defines the abstraction for handling OData batch requests.
/// </summary>
public abstract class ODataBatchHandler : HttpBatchHandler
{
// Maxing out the received message size as we depend on the hosting layer to enforce this limit.
private ODataMessageQuotas _messageQuotas = new ODataMessageQuotas { MaxReceivedMessageSize = Int64.MaxValue };
/// <summary>
/// Initializes a new instance of the <see cref="ODataBatchHandler"/> class.
/// </summary>
/// <param name="httpServer">The <see cref="HttpServer"/> for handling the individual batch requests.</param>
protected ODataBatchHandler(HttpServer httpServer)
: base(httpServer)
{
}
/// <summary>
/// Gets the <see cref="ODataMessageQuotas"/> used for reading/writing the batch request/response.
/// </summary>
public ODataMessageQuotas MessageQuotas
{
get { return _messageQuotas; }
}
/// <summary>
/// Gets or sets the name of the OData route associated with this batch handler.
/// </summary>
public string ODataRouteName { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,212 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Web.Http.OData.Properties;
using System.Web.Http.OData.Routing;
using System.Web.Http.Routing;
using Microsoft.Data.OData;
namespace System.Web.Http.OData.Batch
{
/// <summary>
/// Provides extension methods for the <see cref="HttpRequestMessage"/> class.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class ODataBatchHttpRequestMessageExtensions
{
private const string BatchIdKey = "BatchId";
private const string ChangeSetIdKey = "ChangesetId";
private const string ContentIdMappingKey = "ContentIdMapping";
private const string ODataMaxServiceVersion = "MaxDataServiceVersion";
private const string BatchMediaType = "multipart/mixed";
private const string Boundary = "boundary";
/// <summary>
/// Retrieves the Batch ID associated with the request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>The Batch ID associated with this request, or <c>null</c> if there isn't one.</returns>
public static Guid? GetODataBatchId(this HttpRequestMessage request)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
object batchId;
if (request.Properties.TryGetValue(BatchIdKey, out batchId))
{
return (Guid)batchId;
}
return null;
}
/// <summary>
/// Associates a given Batch ID with the request.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="batchId">The Batch ID.</param>
public static void SetODataBatchId(this HttpRequestMessage request, Guid batchId)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
request.Properties[BatchIdKey] = batchId;
}
/// <summary>
/// Retrieves the ChangeSet ID associated with the request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>The ChangeSet ID associated with this request, or <c>null</c> if there isn't one.</returns>
public static Guid? GetODataChangeSetId(this HttpRequestMessage request)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
object changeSetId;
if (request.Properties.TryGetValue(ChangeSetIdKey, out changeSetId))
{
return (Guid)changeSetId;
}
return null;
}
/// <summary>
/// Associates a given ChangeSet ID with the request.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="changeSetId">The ChangeSet ID.</param>
public static void SetODataChangeSetId(this HttpRequestMessage request, Guid changeSetId)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
request.Properties[ChangeSetIdKey] = changeSetId;
}
/// <summary>
/// Retrieves the Content-ID to Location mapping associated with the request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>The Content-ID to Location mapping associated with this request, or <c>null</c> if there isn't one.</returns>
public static IDictionary<string, string> GetODataContentIdMapping(this HttpRequestMessage request)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
object contentIdMapping;
if (request.Properties.TryGetValue(ContentIdMappingKey, out contentIdMapping))
{
return contentIdMapping as IDictionary<string, string>;
}
return null;
}
/// <summary>
/// Associates a given Content-ID to Location mapping with the request.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="contentIdMapping">The Content-ID to Location mapping.</param>
public static void SetODataContentIdMapping(this HttpRequestMessage request, IDictionary<string, string> contentIdMapping)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
request.Properties[ContentIdMappingKey] = contentIdMapping;
}
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing the object.")]
internal static Task<HttpResponseMessage> CreateODataBatchResponseAsync(this HttpRequestMessage request, IEnumerable<ODataBatchResponseItem> responses, ODataMessageQuotas messageQuotas)
{
Contract.Assert(request != null);
ODataVersion odataVersion = request.GetODataVersion();
ODataMessageWriterSettings writerSettings = new ODataMessageWriterSettings()
{
Version = odataVersion,
Indent = true,
DisableMessageStreamDisposal = true,
MessageQuotas = messageQuotas
};
HttpResponseMessage response = request.CreateResponse(HttpStatusCode.Accepted);
response.Content = new ODataBatchContent(responses, writerSettings);
return Task.FromResult(response);
}
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing the object.")]
internal static void ValidateODataBatchRequest(this HttpRequestMessage request)
{
Contract.Assert(request != null);
if (request.Content == null)
{
throw new HttpResponseException(request.CreateErrorResponse(
HttpStatusCode.BadRequest,
SRResources.BatchRequestMissingContent));
}
MediaTypeHeaderValue contentType = request.Content.Headers.ContentType;
if (contentType == null)
{
throw new HttpResponseException(request.CreateErrorResponse(
HttpStatusCode.BadRequest,
SRResources.BatchRequestMissingContentType));
}
if (!String.Equals(contentType.MediaType, BatchMediaType, StringComparison.OrdinalIgnoreCase))
{
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.BadRequest,
Error.Format(SRResources.BatchRequestInvalidMediaType, BatchMediaType)));
}
NameValueHeaderValue boundary = contentType.Parameters.FirstOrDefault(p => String.Equals(p.Name, Boundary, StringComparison.OrdinalIgnoreCase));
if (boundary == null || String.IsNullOrEmpty(boundary.Value))
{
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.BadRequest,
SRResources.BatchRequestMissingBoundary));
}
}
internal static Uri GetODataBatchBaseUri(this HttpRequestMessage request, string oDataRouteName)
{
Contract.Assert(request != null);
if (oDataRouteName == null)
{
// Return request's base address.
return new Uri(request.RequestUri, new Uri("/", UriKind.Relative));
}
else
{
UrlHelper helper = request.GetUrlHelper() ?? new UrlHelper(request);
string baseAddress = helper.Link(oDataRouteName, new HttpRouteValueDictionary() { { ODataRouteConstants.ODataPath, String.Empty } });
if (baseAddress == null)
{
throw new InvalidOperationException(SRResources.UnableToDetermineBaseUrl);
}
return new Uri(baseAddress);
}
}
}
}

Просмотреть файл

@ -0,0 +1,189 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.OData.Properties;
using Microsoft.Data.OData;
namespace System.Web.Http.OData.Batch
{
/// <summary>
/// Provides extension methods for the <see cref="ODataBatchReader"/> class.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class ODataBatchReaderExtensions
{
/// <summary>
/// Reads a ChangeSet request.
/// </summary>
/// <param name="reader">The <see cref="ODataBatchReader"/>.</param>
/// <param name="batchId">The Batch Id.</param>
/// <returns>A collection of <see cref="HttpRequestMessage"/> in the ChangeSet.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We need to return a collection of request messages asynchronously.")]
public static Task<IList<HttpRequestMessage>> ReadChangeSetRequestAsync(this ODataBatchReader reader, Guid batchId)
{
return reader.ReadChangeSetRequestAsync(batchId, CancellationToken.None);
}
/// <summary>
/// Reads a ChangeSet request.
/// </summary>
/// <param name="reader">The <see cref="ODataBatchReader"/>.</param>
/// <param name="batchId">The Batch Id.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A collection of <see cref="HttpRequestMessage"/> in the ChangeSet.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We need to return a collection of request messages asynchronously.")]
public static async Task<IList<HttpRequestMessage>> ReadChangeSetRequestAsync(this ODataBatchReader reader, Guid batchId, CancellationToken cancellationToken)
{
if (reader == null)
{
throw Error.ArgumentNull("reader");
}
if (reader.State != ODataBatchReaderState.ChangesetStart)
{
throw Error.InvalidOperation(
SRResources.InvalidBatchReaderState,
reader.State.ToString(),
ODataBatchReaderState.ChangesetStart.ToString());
}
Guid changeSetId = Guid.NewGuid();
List<HttpRequestMessage> requests = new List<HttpRequestMessage>();
while (reader.Read() && reader.State != ODataBatchReaderState.ChangesetEnd)
{
if (reader.State == ODataBatchReaderState.Operation)
{
requests.Add(await ReadOperationInternalAsync(reader, batchId, changeSetId, cancellationToken));
}
}
return requests;
}
/// <summary>
/// Reads an Operation request.
/// </summary>
/// <param name="reader">The <see cref="ODataBatchReader"/>.</param>
/// <param name="batchId">The Batch ID.</param>
/// <param name="bufferContentStream">if set to <c>true</c> then the request content stream will be buffered.</param>
/// <returns>A <see cref="HttpRequestMessage"/> representing the operation.</returns>
public static Task<HttpRequestMessage> ReadOperationRequestAsync(this ODataBatchReader reader, Guid batchId, bool bufferContentStream)
{
return reader.ReadOperationRequestAsync(batchId, bufferContentStream, CancellationToken.None);
}
/// <summary>
/// Reads an Operation request.
/// </summary>
/// <param name="reader">The <see cref="ODataBatchReader"/>.</param>
/// <param name="batchId">The Batch ID.</param>
/// <param name="bufferContentStream">if set to <c>true</c> then the request content stream will be buffered.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="HttpRequestMessage"/> representing the operation.</returns>
public static Task<HttpRequestMessage> ReadOperationRequestAsync(this ODataBatchReader reader, Guid batchId, bool bufferContentStream, CancellationToken cancellationToken)
{
if (reader == null)
{
throw Error.ArgumentNull("reader");
}
if (reader.State != ODataBatchReaderState.Operation)
{
throw Error.InvalidOperation(
SRResources.InvalidBatchReaderState,
reader.State.ToString(),
ODataBatchReaderState.Operation.ToString());
}
return ReadOperationInternalAsync(reader, batchId, changeSetId: null, cancellationToken: cancellationToken, bufferContentStream: bufferContentStream);
}
/// <summary>
/// Reads an Operation request in a ChangeSet.
/// </summary>
/// <param name="reader">The <see cref="ODataBatchReader"/>.</param>
/// <param name="batchId">The Batch ID.</param>
/// <param name="changeSetId">The ChangeSet ID.</param>
/// <param name="bufferContentStream">if set to <c>true</c> then the request content stream will be buffered.</param>
/// <returns>A <see cref="HttpRequestMessage"/> representing a ChangeSet operation</returns>
public static Task<HttpRequestMessage> ReadChangeSetOperationRequestAsync(this ODataBatchReader reader, Guid batchId, Guid changeSetId, bool bufferContentStream)
{
return reader.ReadChangeSetOperationRequestAsync(batchId, changeSetId, bufferContentStream, CancellationToken.None);
}
/// <summary>
/// Reads an Operation request in a ChangeSet.
/// </summary>
/// <param name="reader">The <see cref="ODataBatchReader"/>.</param>
/// <param name="batchId">The Batch ID.</param>
/// <param name="changeSetId">The ChangeSet ID.</param>
/// <param name="bufferContentStream">if set to <c>true</c> then the request content stream will be buffered.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="HttpRequestMessage"/> representing a ChangeSet operation</returns>
public static Task<HttpRequestMessage> ReadChangeSetOperationRequestAsync(
this ODataBatchReader reader, Guid batchId, Guid changeSetId, bool bufferContentStream, CancellationToken cancellationToken)
{
if (reader == null)
{
throw Error.ArgumentNull("reader");
}
if (reader.State != ODataBatchReaderState.Operation)
{
throw Error.InvalidOperation(
SRResources.InvalidBatchReaderState,
reader.State.ToString(),
ODataBatchReaderState.Operation.ToString());
}
return ReadOperationInternalAsync(reader, batchId, changeSetId, cancellationToken, bufferContentStream);
}
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing the object.")]
private static async Task<HttpRequestMessage> ReadOperationInternalAsync(
ODataBatchReader reader, Guid batchId, Guid? changeSetId, CancellationToken cancellationToken, bool bufferContentStream = true)
{
ODataBatchOperationRequestMessage batchRequest = reader.CreateOperationRequestMessage();
HttpRequestMessage request = new HttpRequestMessage();
request.Method = new HttpMethod(batchRequest.Method);
request.RequestUri = batchRequest.Url;
if (bufferContentStream)
{
using (Stream stream = batchRequest.GetStream())
{
MemoryStream bufferedStream = new MemoryStream();
// Passing in the default buffer size of 81920 so that we can also pass in a cancellation token
await stream.CopyToAsync(bufferedStream, bufferSize: 81920, cancellationToken: cancellationToken);
bufferedStream.Position = 0;
request.Content = new StreamContent(bufferedStream);
}
}
else
{
request.Content = new LazyStreamContent(() => batchRequest.GetStream());
}
foreach (var header in batchRequest.Headers)
{
string headerName = header.Key;
string headerValue = header.Value;
if (!request.Headers.TryAddWithoutValidation(headerName, headerValue))
{
request.Content.Headers.TryAddWithoutValidation(headerName, headerValue);
}
}
request.SetODataBatchId(batchId);
if (changeSetId != null && changeSetId.HasValue)
{
request.SetODataChangeSetId(changeSetId.Value);
}
return request;
}
}
}

Просмотреть файл

@ -0,0 +1,82 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace System.Web.Http.OData.Batch
{
/// <summary>
/// Represents an OData batch request.
/// </summary>
public abstract class ODataBatchRequestItem : IDisposable
{
/// <summary>
/// Sends a single OData batch request.
/// </summary>
/// <param name="invoker">The invoker.</param>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <param name="contentIdToLocationMapping">The Content-ID to Location mapping.</param>
/// <returns></returns>
public static async Task<HttpResponseMessage> SendMessageAsync(HttpMessageInvoker invoker, HttpRequestMessage request, CancellationToken cancellationToken, Dictionary<string, string> contentIdToLocationMapping)
{
if (invoker == null)
{
throw Error.ArgumentNull("invoker");
}
if (request == null)
{
throw Error.ArgumentNull("request");
}
if (contentIdToLocationMapping != null)
{
string resolvedRequestUrl = ContentIdHelpers.ResolveContentId(request.RequestUri.OriginalString, contentIdToLocationMapping);
request.RequestUri = new Uri(resolvedRequestUrl);
request.SetODataContentIdMapping(contentIdToLocationMapping);
}
HttpResponseMessage response = await invoker.SendAsync(request, cancellationToken);
ContentIdHelpers.CopyContentIdToResponse(request, response);
if (contentIdToLocationMapping != null)
{
ContentIdHelpers.AddLocationHeaderToMapping(response, contentIdToLocationMapping);
}
return response;
}
/// <inheritdoc/>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Gets the resources for disposal.
/// </summary>
/// <returns>A collection of resources for disposal.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "The order of execution matters. The result can be different after calling SendMessageAsync.")]
public abstract IEnumerable<IDisposable> GetResourcesForDisposal();
/// <summary>
/// Sends the request.
/// </summary>
/// <param name="invoker">The invoker.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="ODataBatchResponseItem"/>.</returns>
public abstract Task<ODataBatchResponseItem> SendRequestAsync(HttpMessageInvoker invoker, CancellationToken cancellationToken);
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected abstract void Dispose(bool disposing);
}
}

Просмотреть файл

@ -0,0 +1,91 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.OData;
namespace System.Web.Http.OData.Batch
{
/// <summary>
/// Represents an OData batch response.
/// </summary>
public abstract class ODataBatchResponseItem : IDisposable
{
/// <summary>
/// Writes a single OData batch response.
/// </summary>
/// <param name="writer">The <see cref="ODataBatchWriter"/>.</param>
/// <param name="response">The response message.</param>
/// <returns>A task object representing writing the given batch response using the given writer.</returns>
public static Task WriteMessageAsync(ODataBatchWriter writer, HttpResponseMessage response)
{
return WriteMessageAsync(writer, response, CancellationToken.None);
}
/// <summary>
/// Writes a single OData batch response.
/// </summary>
/// <param name="writer">The <see cref="ODataBatchWriter"/>.</param>
/// <param name="response">The response message.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task object representing writing the given batch response using the given writer.</returns>
public static async Task WriteMessageAsync(ODataBatchWriter writer, HttpResponseMessage response,
CancellationToken cancellationToken)
{
if (writer == null)
{
throw Error.ArgumentNull("writer");
}
if (response == null)
{
throw Error.ArgumentNull("response");
}
ODataBatchOperationResponseMessage batchResponse = writer.CreateOperationResponseMessage();
batchResponse.StatusCode = (int)response.StatusCode;
foreach (KeyValuePair<string, IEnumerable<string>> header in response.Headers)
{
batchResponse.SetHeader(header.Key, String.Join(",", header.Value));
}
if (response.Content != null)
{
foreach (KeyValuePair<string, IEnumerable<string>> header in response.Content.Headers)
{
batchResponse.SetHeader(header.Key, String.Join(",", header.Value));
}
using (Stream stream = batchResponse.GetStream())
{
cancellationToken.ThrowIfCancellationRequested();
await response.Content.CopyToAsync(stream);
}
}
}
/// <inheritdoc/>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Writes the response.
/// </summary>
/// <param name="writer">The <see cref="ODataBatchWriter"/>.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
public abstract Task WriteResponseAsync(ODataBatchWriter writer, CancellationToken cancellationToken);
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected abstract void Dispose(bool disposing);
}
}

Просмотреть файл

@ -0,0 +1,54 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.ComponentModel;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.OData.Formatter;
using Microsoft.Data.OData;
namespace System.Web.Http.OData.Batch
{
/// <summary>
/// Provides extension methods for the <see cref="HttpContent"/> class.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class ODataHttpContentExtensions
{
/// <summary>
/// Gets the <see cref="ODataMessageReader"/> for the <see cref="HttpContent"/> stream.
/// </summary>
/// <param name="content">The <see cref="HttpContent"/>.</param>
/// <param name="settings">The <see cref="ODataMessageReaderSettings"/>.</param>
/// <returns>A task object that produces an <see cref="ODataMessageReader"/> when completed.</returns>
public static Task<ODataMessageReader> GetODataMessageReaderAsync(this HttpContent content,
ODataMessageReaderSettings settings)
{
return GetODataMessageReaderAsync(content, settings, CancellationToken.None);
}
/// <summary>
/// Gets the <see cref="ODataMessageReader"/> for the <see cref="HttpContent"/> stream.
/// </summary>
/// <param name="content">The <see cref="HttpContent"/>.</param>
/// <param name="settings">The <see cref="ODataMessageReaderSettings"/>.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task object that produces an <see cref="ODataMessageReader"/> when completed.</returns>
public static async Task<ODataMessageReader> GetODataMessageReaderAsync(this HttpContent content,
ODataMessageReaderSettings settings, CancellationToken cancellationToken)
{
if (content == null)
{
throw Error.ArgumentNull("content");
}
cancellationToken.ThrowIfCancellationRequested();
Stream contentStream = await content.ReadAsStreamAsync();
IODataRequestMessage oDataRequestMessage = new ODataMessageWrapper(contentStream, content.Headers);
ODataMessageReader oDataMessageReader = new ODataMessageReader(oDataRequestMessage, settings);
return oDataMessageReader;
}
}
}

Просмотреть файл

@ -0,0 +1,69 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace System.Web.Http.OData.Batch
{
/// <summary>
/// Represents an Operation request.
/// </summary>
public class OperationRequestItem : ODataBatchRequestItem
{
/// <summary>
/// Initializes a new instance of the <see cref="OperationRequestItem"/> class.
/// </summary>
/// <param name="request">The Operation request.</param>
public OperationRequestItem(HttpRequestMessage request)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
Request = request;
}
/// <summary>
/// Gets the Operation request.
/// </summary>
public HttpRequestMessage Request { get; private set; }
/// <summary>
/// Sends the Operation request.
/// </summary>
/// <param name="invoker">The invoker.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="OperationResponseItem"/>.</returns>
public override async Task<ODataBatchResponseItem> SendRequestAsync(HttpMessageInvoker invoker, CancellationToken cancellationToken)
{
if (invoker == null)
{
throw Error.ArgumentNull("invoker");
}
HttpResponseMessage response = await SendMessageAsync(invoker, Request, cancellationToken, null);
return new OperationResponseItem(response);
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (disposing)
{
Request.Dispose();
}
}
/// <summary>
/// Gets the resources registered for dispose on the Operation request message.
/// </summary>
/// <returns>A collection of resources registered for dispose.</returns>
public override IEnumerable<IDisposable> GetResourcesForDisposal()
{
return Request.GetResourcesForDisposal();
}
}
}

Просмотреть файл

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.OData;
namespace System.Web.Http.OData.Batch
{
/// <summary>
/// Represents an Operation response.
/// </summary>
public class OperationResponseItem : ODataBatchResponseItem
{
/// <summary>
/// Initializes a new instance of the <see cref="OperationResponseItem"/> class.
/// </summary>
/// <param name="response">The response messages for the Operation request.</param>
public OperationResponseItem(HttpResponseMessage response)
{
if (response == null)
{
throw Error.ArgumentNull("response");
}
Response = response;
}
/// <summary>
/// Gets the response messages for the Operation.
/// </summary>
public HttpResponseMessage Response { get; private set; }
/// <summary>
/// Writes the response as an Operation.
/// </summary>
/// <param name="writer">The <see cref="ODataBatchWriter"/>.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
public override Task WriteResponseAsync(ODataBatchWriter writer, CancellationToken cancellationToken)
{
if (writer == null)
{
throw Error.ArgumentNull("writer");
}
return WriteMessageAsync(writer, Response, cancellationToken);
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (disposing)
{
Response.Dispose();
}
}
}
}

Просмотреть файл

@ -0,0 +1,232 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Batch;
using Microsoft.Data.OData;
namespace System.Web.Http.OData.Batch
{
/// <summary>
/// An implementation of <see cref="ODataBatchHandler"/> that doesn't buffer the request content stream.
/// </summary>
public class UnbufferedODataBatchHandler : ODataBatchHandler
{
/// <summary>
/// Initializes a new instance of the <see cref="UnbufferedODataBatchHandler"/> class.
/// </summary>
/// <param name="httpServer">The <see cref="HttpServer"/> for handling the individual batch requests.</param>
public UnbufferedODataBatchHandler(HttpServer httpServer)
: base(httpServer)
{
}
/// <inheritdoc/>
public override async Task<HttpResponseMessage> ProcessBatchAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
ValidateRequest(request);
ODataMessageReaderSettings oDataReaderSettings = new ODataMessageReaderSettings
{
DisableMessageStreamDisposal = true,
MessageQuotas = MessageQuotas,
BaseUri = GetBaseUri(request)
};
ODataMessageReader reader = await request.Content.GetODataMessageReaderAsync(oDataReaderSettings, cancellationToken);
request.RegisterForDispose(reader);
ODataBatchReader batchReader = reader.CreateODataBatchReader();
List<ODataBatchResponseItem> responses = new List<ODataBatchResponseItem>();
Guid batchId = Guid.NewGuid();
List<IDisposable> resourcesToDispose = new List<IDisposable>();
try
{
while (batchReader.Read())
{
ODataBatchResponseItem responseItem = null;
if (batchReader.State == ODataBatchReaderState.ChangesetStart)
{
responseItem = await ExecuteChangeSetAsync(batchReader, batchId, request, cancellationToken);
}
else if (batchReader.State == ODataBatchReaderState.Operation)
{
responseItem = await ExecuteOperationAsync(batchReader, batchId, request, cancellationToken);
}
if (responseItem != null)
{
responses.Add(responseItem);
}
}
}
catch
{
foreach (ODataBatchResponseItem response in responses)
{
if (response != null)
{
response.Dispose();
}
}
throw;
}
return await CreateResponseMessageAsync(responses, request, cancellationToken);
}
/// <summary>
/// Executes the operation.
/// </summary>
/// <param name="batchReader">The batch reader.</param>
/// <param name="batchId">The batch id.</param>
/// <param name="originalRequest">The original request containing all the batch requests.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The response for the operation.</returns>
public virtual async Task<ODataBatchResponseItem> ExecuteOperationAsync(ODataBatchReader batchReader, Guid batchId, HttpRequestMessage originalRequest, CancellationToken cancellationToken)
{
if (batchReader == null)
{
throw Error.ArgumentNull("batchReader");
}
if (originalRequest == null)
{
throw Error.ArgumentNull("originalRequest");
}
cancellationToken.ThrowIfCancellationRequested();
HttpRequestMessage operationRequest = await batchReader.ReadOperationRequestAsync(batchId, bufferContentStream: false);
operationRequest.CopyBatchRequestProperties(originalRequest);
OperationRequestItem operation = new OperationRequestItem(operationRequest);
try
{
ODataBatchResponseItem response = await operation.SendRequestAsync(Invoker, cancellationToken);
return response;
}
finally
{
originalRequest.RegisterForDispose(operation.GetResourcesForDisposal());
originalRequest.RegisterForDispose(operation);
}
}
/// <summary>
/// Executes the ChangeSet.
/// </summary>
/// <param name="batchReader">The batch reader.</param>
/// <param name="batchId">The batch id.</param>
/// <param name="originalRequest">The original request containing all the batch requests.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The response for the ChangeSet.</returns>
public virtual async Task<ODataBatchResponseItem> ExecuteChangeSetAsync(ODataBatchReader batchReader, Guid batchId, HttpRequestMessage originalRequest, CancellationToken cancellationToken)
{
if (batchReader == null)
{
throw Error.ArgumentNull("batchReader");
}
if (originalRequest == null)
{
throw Error.ArgumentNull("originalRequest");
}
Guid changeSetId = Guid.NewGuid();
List<HttpResponseMessage> changeSetResponse = new List<HttpResponseMessage>();
Dictionary<string, string> contentIdToLocationMapping = new Dictionary<string, string>();
try
{
while (batchReader.Read() && batchReader.State != ODataBatchReaderState.ChangesetEnd)
{
if (batchReader.State == ODataBatchReaderState.Operation)
{
HttpRequestMessage changeSetOperationRequest = await batchReader.ReadChangeSetOperationRequestAsync(batchId, changeSetId, bufferContentStream: false);
changeSetOperationRequest.CopyBatchRequestProperties(originalRequest);
try
{
HttpResponseMessage response = await ODataBatchRequestItem.SendMessageAsync(Invoker, changeSetOperationRequest, cancellationToken, contentIdToLocationMapping);
if (response.IsSuccessStatusCode)
{
changeSetResponse.Add(response);
}
else
{
ChangeSetRequestItem.DisposeResponses(changeSetResponse);
changeSetResponse.Clear();
changeSetResponse.Add(response);
return new ChangeSetResponseItem(changeSetResponse);
}
}
finally
{
originalRequest.RegisterForDispose(changeSetOperationRequest.GetResourcesForDisposal());
originalRequest.RegisterForDispose(changeSetOperationRequest);
}
}
}
}
catch
{
ChangeSetRequestItem.DisposeResponses(changeSetResponse);
throw;
}
return new ChangeSetResponseItem(changeSetResponse);
}
/// <summary>
/// Creates the batch response message.
/// </summary>
/// <param name="responses">The responses for the batch requests.</param>
/// <param name="request">The original request containing all the batch requests.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The batch response message.</returns>
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing the object.")]
public virtual Task<HttpResponseMessage> CreateResponseMessageAsync(
IEnumerable<ODataBatchResponseItem> responses, HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
return request.CreateODataBatchResponseAsync(responses, MessageQuotas);
}
/// <summary>
/// Validates the incoming request that contains the batch request messages.
/// </summary>
/// <param name="request">The request containing the batch request messages.</param>
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing the object.")]
public virtual void ValidateRequest(HttpRequestMessage request)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
request.ValidateODataBatchRequest();
}
/// <summary>
/// Gets the base URI for the batched requests.
/// </summary>
/// <param name="request">The original request containing all the batch requests.</param>
/// <returns>The base URI.</returns>
public virtual Uri GetBaseUri(HttpRequestMessage request)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
return request.GetODataBatchBaseUri(ODataRouteName);
}
}
}

Просмотреть файл

@ -0,0 +1,250 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// ActionConfiguration represents an OData action that you wish to expose via your service.
/// <remarks>
/// ActionConfigurations are exposed via $metadata as a <FunctionImport/> element.
/// </remarks>
/// </summary>
public class ActionConfiguration : ProcedureConfiguration
{
/// <summary>
/// Initializes a new instance of <see cref="ActionConfiguration" /> class.
/// </summary>
/// <param name="builder">The ODataModelBuilder to which this ActionConfiguration should be added.</param>
/// <param name="name">The name of this ActionConfiguration.</param>
internal ActionConfiguration(ODataModelBuilder builder, string name)
: base(builder, name)
{
}
/// <inheritdoc />
public override ProcedureKind Kind
{
get { return ProcedureKind.Action; }
}
/// <inheritdoc />
public override bool IsSideEffecting
{
get { return true; }
}
/// <summary>
/// Register a factory that creates actions links.
/// </summary>
public ActionConfiguration HasActionLink(Func<EntityInstanceContext, Uri> actionLinkFactory, bool followsConventions)
{
if (actionLinkFactory == null)
{
throw new ArgumentNullException("actionLinkFactory");
}
if (!IsBindable || BindingParameter.TypeConfiguration.Kind != EdmTypeKind.Entity)
{
throw Error.InvalidOperation(SRResources.HasActionLinkRequiresBindToEntity, Name);
}
LinkFactory = actionLinkFactory;
FollowsConventions = followsConventions;
return this;
}
/// <summary>
/// Retrieves the currently registered action link factory.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Consistent with EF Has/Get pattern")]
public Func<EntityInstanceContext, Uri> GetActionLink()
{
return LinkFactory;
}
/// <summary>
/// Sets the return type to a single EntityType instance.
/// </summary>
/// <typeparam name="TEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetName">The name of the entity set which contains the returned entity.</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public ActionConfiguration ReturnsFromEntitySet<TEntityType>(string entitySetName) where TEntityType : class
{
ReturnsFromEntitySetImplementation<TEntityType>(entitySetName);
return this;
}
/// <summary>
/// Sets the return type to a single EntityType instance.
/// </summary>
/// <typeparam name="TEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetConfiguration">The entity set which contains the returned entity.</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public ActionConfiguration ReturnsFromEntitySet<TEntityType>(EntitySetConfiguration<TEntityType> entitySetConfiguration) where TEntityType : class
{
if (entitySetConfiguration == null)
{
throw Error.ArgumentNull("entitySetConfiguration");
}
EntitySet = entitySetConfiguration.EntitySet;
ReturnType = ModelBuilder.GetTypeConfigurationOrNull(typeof(TEntityType));
return this;
}
/// <summary>
/// Sets the return type to a collection of entities.
/// </summary>
/// <typeparam name="TElementEntityType">The entity type.</typeparam>
/// <param name="entitySetName">The name of the entity set which contains the returned entities.</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public ActionConfiguration ReturnsCollectionFromEntitySet<TElementEntityType>(string entitySetName) where TElementEntityType : class
{
ReturnsCollectionFromEntitySetImplementation<TElementEntityType>(entitySetName);
return this;
}
/// <summary>
/// Sets the return type to a collection of entities.
/// </summary>
/// <typeparam name="TElementEntityType">The entity type.</typeparam>
/// <param name="entitySetConfiguration">The entity set which contains the returned entities.</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public ActionConfiguration ReturnsCollectionFromEntitySet<TElementEntityType>(
EntitySetConfiguration<TElementEntityType> entitySetConfiguration) where TElementEntityType : class
{
if (entitySetConfiguration == null)
{
throw Error.ArgumentNull("entitySetConfiguration");
}
Type clrCollectionType = typeof(IEnumerable<TElementEntityType>);
EntitySet = entitySetConfiguration.EntitySet;
IEdmTypeConfiguration elementType = ModelBuilder.GetTypeConfigurationOrNull(typeof(TElementEntityType));
ReturnType = new CollectionTypeConfiguration(elementType, clrCollectionType);
return this;
}
/// <summary>
/// Established the return type of the Action.
/// <remarks>Used when the return type is a single Primitive or ComplexType.</remarks>
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public ActionConfiguration Returns<TReturnType>()
{
Type returnType = typeof(TReturnType);
IEdmTypeConfiguration configuration = ModelBuilder.GetTypeConfigurationOrNull(returnType);
if (configuration is EntityTypeConfiguration)
{
throw Error.InvalidOperation(SRResources.ReturnEntityWithoutEntitySet, configuration.FullName);
}
if (configuration == null)
{
ModelBuilder.AddComplexType(returnType);
configuration = ModelBuilder.GetTypeConfigurationOrNull(typeof(TReturnType));
}
ReturnType = configuration;
ReturnsImplementation<TReturnType>();
return this;
}
/// <summary>
/// Establishes the return type of the Action
/// <remarks>Used when the return type is a collection of either Primitive or ComplexTypes.</remarks>
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public ActionConfiguration ReturnsCollection<TReturnElementType>()
{
// TODO: I don't like this temporary solution that says the CLR type of the collection is IEnumerable<T>.
// It basically has no meaning. That said the CLR type is meaningful for IEdmTypeConfiguration
// because I still think it is useful for IEdmPrimitiveTypes too.
// You can imagine the override of this that takes a delegate using the correct CLR type for the return type.
Type clrCollectionType = typeof(IEnumerable<TReturnElementType>);
Type clrElementType = typeof(TReturnElementType);
IEdmTypeConfiguration edmElementType = ModelBuilder.GetTypeConfigurationOrNull(clrElementType);
if (edmElementType is EntityTypeConfiguration)
{
throw Error.InvalidOperation(SRResources.ReturnEntityCollectionWithoutEntitySet, edmElementType.FullName);
}
if (edmElementType == null)
{
ModelBuilder.AddComplexType(clrElementType);
edmElementType = ModelBuilder.GetTypeConfigurationOrNull(clrElementType);
}
ReturnType = new CollectionTypeConfiguration(edmElementType, clrCollectionType);
ReturnsCollectionImplementation<TReturnElementType>();
return this;
}
/// <summary>
/// Specifies the bindingParameter name, type and whether it is alwaysBindable, use only if the Action "isBindable".
/// </summary>
public ActionConfiguration SetBindingParameter(string name, IEdmTypeConfiguration bindingParameterType, bool alwaysBindable)
{
SetBindingParameterImplementation(name, bindingParameterType, alwaysBindable);
return this;
}
/// <summary>
/// Sets the return type to a single EntityType instance.
/// </summary>
/// <typeparam name="TEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetPath">The entitySetPath which contains the return EntityType instance</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public ActionConfiguration ReturnsEntityViaEntitySetPath<TEntityType>(string entitySetPath) where TEntityType : class
{
if (String.IsNullOrEmpty(entitySetPath))
{
throw new ArgumentNullException("entitySetPath");
}
ReturnsEntityViaEntitySetPathImplementation<TEntityType>(entitySetPath.Split('/'));
return this;
}
/// <summary>
/// Sets the return type to a single EntityType instance.
/// </summary>
/// <typeparam name="TEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetPath">The entitySetPath which contains the return EntityType instance</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public ActionConfiguration ReturnsEntityViaEntitySetPath<TEntityType>(params string[] entitySetPath) where TEntityType : class
{
ReturnsEntityViaEntitySetPathImplementation<TEntityType>(entitySetPath);
return this;
}
/// <summary>
/// Sets the return type to a collection of EntityType instances.
/// </summary>
/// <typeparam name="TElementEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetPath">The entitySetPath which contains the returned EntityType instances</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public ActionConfiguration ReturnsCollectionViaEntitySetPath<TElementEntityType>(string entitySetPath) where TElementEntityType : class
{
if (String.IsNullOrEmpty(entitySetPath))
{
throw new ArgumentNullException("entitySetPath");
}
ReturnsCollectionViaEntitySetPathImplementation<TElementEntityType>(entitySetPath.Split('/'));
return this;
}
/// <summary>
/// Sets the return type to a collection of EntityType instances.
/// </summary>
/// <typeparam name="TElementEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetPath">The entitySetPath which contains the returned EntityType instances</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public ActionConfiguration ReturnsCollectionViaEntitySetPath<TElementEntityType>(params string[] entitySetPath) where TElementEntityType : class
{
ReturnsCollectionViaEntitySetPathImplementation<TElementEntityType>(entitySetPath);
return this;
}
}
}

Просмотреть файл

@ -0,0 +1,76 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// ActionLinkBuilder can be used to annotate an Action.
/// This is how formatters create links to invoke bound actions.
/// </summary>
public class ActionLinkBuilder
{
private Func<EntityInstanceContext, Uri> _actionLinkFactory;
/// <summary>
/// Create a new ActionLinkBuilder based on an actionLinkFactory.
/// <remarks>
/// If the action is not available the actionLinkFactory delegate should return NULL.
/// </remarks>
/// </summary>
/// <param name="actionLinkFactory">The actionLinkFactory this ActionLinkBuilder should use when building links.</param>
/// <param name="followsConventions">
/// A value indicating whether the action link factory generates links that follow OData conventions.
/// </param>
public ActionLinkBuilder(Func<EntityInstanceContext, Uri> actionLinkFactory, bool followsConventions)
{
if (actionLinkFactory == null)
{
throw Error.ArgumentNull("actionLinkFactory");
}
_actionLinkFactory = actionLinkFactory;
FollowsConventions = followsConventions;
}
/// <summary>
/// Gets a boolean indicating whether the link factory follows OData conventions or not.
/// </summary>
public bool FollowsConventions { get; private set; }
/// <summary>
/// Builds the action link for the given entity.
/// </summary>
/// <param name="context">An instance context wrapping the entity instance.</param>
/// <returns>The generated action link.</returns>
public virtual Uri BuildActionLink(EntityInstanceContext context)
{
return _actionLinkFactory(context);
}
/// <summary>
/// Creates an action link factory that builds an action link, but only when appropriate based on the expensiveAvailabilityCheck, and whether expensive checks should be made,
/// which is deduced by looking at the EntityInstanceContext.SkipExpensiveActionAvailabilityChecks property.
/// </summary>
/// <param name="baseFactory">The action link factory that actually builds links if all checks pass.</param>
/// <param name="expensiveAvailabilityCheck">The availability check function that is expensive but when called returns whether the action is available.</param>
/// <returns>The new action link factory.</returns>
public static Func<EntityInstanceContext, Uri> CreateActionLinkFactory(Func<EntityInstanceContext, Uri> baseFactory, Func<EntityInstanceContext, bool> expensiveAvailabilityCheck)
{
return (EntityInstanceContext ctx) =>
{
if (ctx.SkipExpensiveAvailabilityChecks)
{
// OData says that if it is too expensive to check availability you should advertize actions
return baseFactory(ctx);
}
else if (expensiveAvailabilityCheck(ctx))
{
return baseFactory(ctx);
}
else
{
return null;
}
};
}
}
}

Просмотреть файл

@ -0,0 +1,69 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// This class builds a cache that allows for efficient look up of bindable procedure by EntityType.
/// </summary>
internal class BindableProcedureFinder
{
private Dictionary<IEdmEntityType, List<IEdmFunctionImport>> _map = new Dictionary<IEdmEntityType, List<IEdmFunctionImport>>();
/// <summary>
/// Constructs a concurrent cache for looking up bindable procedures for any EntityType in the provided model.
/// </summary>
public BindableProcedureFinder(IEdmModel model)
{
var query =
from ec in model.EntityContainers()
from fi in ec.FunctionImports()
where fi.IsBindable && fi.Parameters.First().Type.TypeKind() == EdmTypeKind.Entity
group fi by fi.Parameters.First().Type.Definition into fgroup
select new { EntityType = fgroup.Key as IEdmEntityType, BindableFunctions = fgroup.ToList() };
foreach (var match in query)
{
_map[match.EntityType] = match.BindableFunctions;
}
}
/// <summary>
/// Finds procedures that can be invoked on the given entity type. This would include all the procedures that are bound
/// to the given type and its base types.
/// </summary>
/// <param name="entityType">The EDM entity type.</param>
/// <returns>A collection of procedures bound to the entity type.</returns>
public virtual IEnumerable<IEdmFunctionImport> FindProcedures(IEdmEntityType entityType)
{
return GetTypeHierarchy(entityType).SelectMany(e => FindDeclaredProcedures(e));
}
private IEnumerable<IEdmEntityType> GetTypeHierarchy(IEdmEntityType entityType)
{
IEdmEntityType current = entityType;
while (current != null)
{
yield return current;
current = current.BaseEntityType();
}
}
private IEnumerable<IEdmFunctionImport> FindDeclaredProcedures(IEdmEntityType entityType)
{
List<IEdmFunctionImport> results = null;
if (_map.TryGetValue(entityType, out results))
{
return results;
}
else
{
return Enumerable.Empty<IEdmFunctionImport>();
}
}
}
}

Просмотреть файл

@ -0,0 +1,62 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Represents a BindingParameter.
/// <remarks>
/// Actions/Functions can have at most one BindingParameter.
/// This parameter has similar semantics to the 'this' keyword in C# extensions methods.
/// <example>
/// For example given a url that identifies a Movie, if there is an action that has a bindingParameter that is a Movie,
/// you can bind the Action to the url.
///
/// i.e. if ~/Movies(1) identifies a Movie, and there exists a Checkout action that has a Movie BindingParameter,
/// you can invoke that Action at this url ~/Movies(1)/Checkout
/// </example>
/// The BindingParameter type must either be an EntityType or a Collection of EntityTypes.
/// </remarks>
/// </summary>
public class BindingParameterConfiguration : ParameterConfiguration
{
/// <summary>
/// The default parameter name for an action's binding parameter.
/// </summary>
public const string DefaultBindingParameterName = "bindingParameter";
private bool _alwaysBindable;
/// <summary>
/// Create a BindingParameterConfiguration
/// </summary>
/// <param name="name">The name of the Binding Parameter</param>
/// <param name="parameterType">The type of the Binding Parameter</param>
/// <param name="alwaysBindable">Whether the action can always be bound to instances of the binding parameter.</param>
public BindingParameterConfiguration(string name, IEdmTypeConfiguration parameterType, bool alwaysBindable)
: base(name, parameterType)
{
EdmTypeKind kind = parameterType.Kind;
if (kind == EdmTypeKind.Collection)
{
kind = (parameterType as CollectionTypeConfiguration).ElementType.Kind;
}
if (kind != EdmTypeKind.Entity)
{
throw Error.Argument("parameterType", SRResources.InvalidBindingParameterType, parameterType.FullName);
}
_alwaysBindable = alwaysBindable;
}
/// <summary>
/// Indicates whether the BindingParameter is always bindable or not.
/// For example some actions are always available some are only available at certain times or in certain states.
/// </summary>
public bool AlwaysBindable
{
get { return _alwaysBindable; }
}
}
}

Просмотреть файл

@ -0,0 +1,65 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Reflection;
using System.Web.Http.OData.Properties;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// CollectionPropertyConfiguration represents a CollectionProperty on either an EntityType or ComplexType.
/// </summary>
public class CollectionPropertyConfiguration : StructuralPropertyConfiguration
{
private Type _elementType;
/// <summary>
/// Constructs a CollectionPropertyConfiguration using the <paramref name="property">property</paramref> provided.
/// </summary>
public CollectionPropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType)
: base(property, declaringType)
{
if (!property.PropertyType.IsCollection(out _elementType))
{
throw Error.Argument("property", SRResources.CollectionPropertiesMustReturnIEnumerable, property.Name, property.DeclaringType.FullName);
}
}
/// <inheritdoc />
public override PropertyKind Kind
{
get { return PropertyKind.Collection; }
}
/// <inheritdoc />
public override Type RelatedClrType
{
get { return ElementType; }
}
/// <summary>
/// Returns the type of Elements in the Collection
/// </summary>
public Type ElementType
{
get { return _elementType; }
}
/// <summary>
/// Sets the CollectionProperty to optional (i.e. nullable).
/// </summary>
public CollectionPropertyConfiguration IsOptional()
{
OptionalProperty = true;
return this;
}
/// <summary>
/// Sets the CollectionProperty to required (i.e. non-nullable).
/// </summary>
public CollectionPropertyConfiguration IsRequired()
{
OptionalProperty = false;
return this;
}
}
}

Просмотреть файл

@ -0,0 +1,99 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Globalization;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Represents a Collection of some named type.
/// <example>
/// Collection(Namespace.Customer) or Collection(Namespace.Address).
/// </example>
/// </summary>
public class CollectionTypeConfiguration : IEdmTypeConfiguration
{
private IEdmTypeConfiguration _elementType;
private Type _clrType;
/// <summary>
/// Constructs a collection that contains elements of the specified ElementType
/// and that is represented in CLR using the specified clrType.
/// </summary>
/// <param name="elementType">The EdmTypeConfiguration of the elements in the collection</param>
/// <param name="clrType">The type of this collection when manifested in CLR.</param>
public CollectionTypeConfiguration(IEdmTypeConfiguration elementType, Type clrType)
{
if (elementType == null)
{
throw Error.ArgumentNull("elementType");
}
if (clrType == null)
{
throw Error.ArgumentNull("clrType");
}
_elementType = elementType;
_clrType = clrType;
}
/// <summary>
/// Gets the <see cref="IEdmTypeConfiguration" /> of elements in this collection.
/// </summary>
public IEdmTypeConfiguration ElementType
{
get { return _elementType; }
}
/// <summary>
/// Gets the CLR type associated with this collection type.
/// </summary>
public Type ClrType
{
get { return _clrType; }
}
/// <summary>
/// Gets the fullname (including namespace) of this collection type.
/// </summary>
public string FullName
{
get
{
// There is no need to include the Namespace when it comes from the Edm Namespace.
return Name;
}
}
/// <summary>
/// Gets the namespace of this collection type.
/// </summary>
public string Namespace
{
get { return "Edm"; }
}
/// <summary>
/// Gets the name of this collection type.
/// </summary>
public string Name
{
get { return String.Format(CultureInfo.InvariantCulture, "Collection({0})", ElementType.FullName); }
}
/// <summary>
/// Gets the kind of the <see cref="IEdmType" />. In this case, it is <see cref="EdmTypeKind.Collection" />.
/// </summary>
public EdmTypeKind Kind
{
get { return EdmTypeKind.Collection; }
}
/// <summary>
/// Gets the <see cref="ODataModelBuilder"/> used to create this configuration.
/// </summary>
public ODataModelBuilder ModelBuilder
{
get { return _elementType.ModelBuilder; }
}
}
}

Просмотреть файл

@ -0,0 +1,54 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Reflection;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Represents the configuration for a complex property of a structural type (an entity type or a complex type).
/// </summary>
public class ComplexPropertyConfiguration : StructuralPropertyConfiguration
{
/// <summary>
/// Instantiates a new instance of the <see cref="ComplexPropertyConfiguration"/> class.
/// </summary>
/// <param name="property">The property of the configuration.</param>
/// <param name="declaringType">The declaring type of the property.</param>
public ComplexPropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType)
: base(property, declaringType)
{
}
/// <inheritdoc />
public override PropertyKind Kind
{
get { return PropertyKind.Complex; }
}
/// <inheritdoc />
public override Type RelatedClrType
{
get { return PropertyInfo.PropertyType; }
}
/// <summary>
/// Marks the current complex property as optional.
/// </summary>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public ComplexPropertyConfiguration IsOptional()
{
OptionalProperty = true;
return this;
}
/// <summary>
/// Marks the current complex property as required.
/// </summary>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public ComplexPropertyConfiguration IsRequired()
{
OptionalProperty = false;
return this;
}
}
}

Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Allows configuration to be performed for a complex type in a model. A ComplexTypeConfiguration can be obtained by using the method <see cref="ODataModelBuilder.ComplexType"/>.
/// </summary>
public class ComplexTypeConfiguration : StructuralTypeConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="ComplexTypeConfiguration"/> class.
/// </summary>
/// <remarks>The default constructor is intended for use by unit testing only.</remarks>
public ComplexTypeConfiguration()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ComplexTypeConfiguration"/> class.
/// <param name="modelBuilder">The <see cref="ODataModelBuilder"/> being used.</param>
/// <param name="clrType">The backing CLR type for this entity type.</param>
/// </summary>
public ComplexTypeConfiguration(ODataModelBuilder modelBuilder, Type clrType)
: base(modelBuilder, clrType)
{
}
/// <inheritdoc />
public override EdmTypeKind Kind
{
get
{
return EdmTypeKind.Complex;
}
}
}
}

Просмотреть файл

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Represents an <see cref="IEdmComplexType"/> that can be built using <see cref="ODataModelBuilder"/>.
/// </summary>
public class ComplexTypeConfiguration<TComplexType> : StructuralTypeConfiguration<TComplexType> where TComplexType : class
{
internal ComplexTypeConfiguration(ComplexTypeConfiguration configuration)
: base(configuration)
{
}
internal ComplexTypeConfiguration(ODataModelBuilder modelBuilder)
: base(new ComplexTypeConfiguration(modelBuilder, typeof(TComplexType)))
{
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder.Conventions
{
/// <summary>
/// <see cref="EntityTypeConvention"/> to figure out if an entity is abstract or not.
/// <remarks>This convention configures all entity types backed by an abstract CLR type as abstract entities.</remarks>
/// </summary>
internal class AbstractEntityTypeDiscoveryConvention : EntityTypeConvention
{
public override void Apply(EntityTypeConfiguration entity, ODataModelBuilder model)
{
if (entity.IsAbstract == null)
{
entity.IsAbstract = entity.ClrType.IsAbstract;
}
}
}
}

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Web.Http.OData.Routing;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder.Conventions
{
/// <summary>
/// The ActionLinkGenerationConvention calls action.HasActionLink(..) if the action binds to a single entity and has not previously been configured.
/// </summary>
internal class ActionLinkGenerationConvention : IProcedureConvention
{
public void Apply(ProcedureConfiguration configuration, ODataModelBuilder model)
{
ActionConfiguration action = configuration as ActionConfiguration;
// You only need to create links for bindable actions that bind to a single entity.
if (action != null && action.IsBindable && action.BindingParameter.TypeConfiguration.Kind == EdmTypeKind.Entity && action.GetActionLink() == null)
{
string bindingParamterType = action.BindingParameter.TypeConfiguration.FullName;
action.HasActionLink(entityContext => entityContext.GenerateActionLink(bindingParamterType, action.Name), followsConventions: true);
}
}
}
}

Просмотреть файл

@ -0,0 +1,75 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Web.Http.OData.Properties;
namespace System.Web.Http.OData.Builder.Conventions
{
/// <summary>
/// <see cref="IEntitySetConvention"/> to configure the EDM association sets for the given entity set.
/// <remarks>This convention adds an association set for each EDM navigation property defined in this type, its base types and all its derived types.
/// The target entity set chosen is the default entity set for the navigation property's target entity type.
/// The default entity set for an entity type is the entity set that contains entries of that entity type. If more than one entity sets match, the default entity set is none.
/// If no entity sets match the default entity set is the default entity set of the base type.</remarks>
/// </summary>
internal class AssociationSetDiscoveryConvention : IEntitySetConvention
{
public void Apply(EntitySetConfiguration configuration, ODataModelBuilder model)
{
foreach (EntityTypeConfiguration entity in model.ThisAndBaseAndDerivedTypes(configuration.EntityType))
{
foreach (NavigationPropertyConfiguration navigationProperty in entity.NavigationProperties)
{
EntitySetConfiguration targetEntitySet = GetTargetEntitySet(navigationProperty, model);
if (targetEntitySet != null)
{
configuration.AddBinding(navigationProperty, targetEntitySet);
}
}
}
}
// Get the default target entity set for this navigation property.
internal static EntitySetConfiguration GetTargetEntitySet(NavigationPropertyConfiguration navigationProperty, ODataModelBuilder model)
{
EntityTypeConfiguration targetEntityType =
model
.StructuralTypes
.OfType<EntityTypeConfiguration>()
.Where(e => e.ClrType == navigationProperty.RelatedClrType).SingleOrDefault();
if (targetEntityType == null)
{
throw Error.InvalidOperation(SRResources.TargetEntityTypeMissing, navigationProperty.Name, navigationProperty.PropertyInfo.ReflectedType.FullName);
}
return GetDefaultEntitySet(targetEntityType, model);
}
private static EntitySetConfiguration GetDefaultEntitySet(EntityTypeConfiguration targetEntityType, ODataModelBuilder model)
{
if (targetEntityType == null)
{
return null;
}
IEnumerable<EntitySetConfiguration> matchingEntitySets = model.EntitySets.Where(e => e.EntityType == targetEntityType);
if (matchingEntitySets.Count() > 1)
{
// no default entity set if more than one entity set match.
return null;
}
else if (matchingEntitySets.Count() == 1)
{
return matchingEntitySets.Single();
}
else
{
// default entity set is the same as the default entity set for the base type.
return GetDefaultEntitySet(targetEntityType.BaseType, model);
}
}
}
}

Просмотреть файл

@ -0,0 +1,72 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Linq;
using System.Reflection;
using System.Web.Http.OData.Properties;
namespace System.Web.Http.OData.Builder.Conventions.Attributes
{
/// <summary>
/// Base class for all attribute based conventions.
/// </summary>
internal abstract class AttributeConvention : IConvention
{
/// <summary>
/// Initializes a new instance of the <see cref="AttributeConvention"/> class.
/// </summary>
/// <param name="attributeFilter">A function to test whether this convention applies to an attribute or not.</param>
/// <param name="allowMultiple"><see langword="true"/> if the convention allows multiple attribues; otherwise, <see langword="false"/>.</param>
protected AttributeConvention(Func<Attribute, bool> attributeFilter, bool allowMultiple)
{
if (attributeFilter == null)
{
throw Error.ArgumentNull("attributeFilter");
}
AllowMultiple = allowMultiple;
AttributeFilter = attributeFilter;
}
/// <summary>
/// Gets the filter that finds the attributes that this convention applies to.
/// </summary>
public Func<Attribute, bool> AttributeFilter { get; private set; }
/// <summary>
/// Gets whether this convention allows multiple instances of the attribute.
/// </summary>
public bool AllowMultiple { get; private set; }
/// <summary>
/// Returns the attributes on <paramref name="member"/> that this convention applies to.
/// </summary>
/// <param name="member"></param>
/// <returns></returns>
public Attribute[] GetAttributes(MemberInfo member)
{
if (member == null)
{
throw Error.ArgumentNull("member");
}
Attribute[] attributes =
member
.GetCustomAttributes(inherit: true)
.OfType<Attribute>()
.Where(AttributeFilter)
.ToArray();
if (!AllowMultiple && attributes.Length > 1)
{
throw Error.Argument(
"member",
SRResources.MultipleAttributesFound,
member.Name,
member.ReflectedType.Name,
attributes.First().GetType().Name);
}
return attributes;
}
}
}

Просмотреть файл

@ -0,0 +1,77 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder.Conventions.Attributes
{
/// <summary>
/// Base class for all attribute based <see cref="IEdmPropertyConvention"/>'s.
/// </summary>
/// <typeparam name="TPropertyConfiguration">The type of the property this configuration applies to.</typeparam>
internal abstract class AttributeEdmPropertyConvention<TPropertyConfiguration> : AttributeConvention, IEdmPropertyConvention<TPropertyConfiguration>
where TPropertyConfiguration : PropertyConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="AttributeEdmPropertyConvention{TPropertyConfiguration}"/> class.
/// </summary>
/// <param name="attributeFilter">A function to test whether this convention applies to an attribute or not.</param>
/// <param name="allowMultiple"><see langword="true"/> if the convention allows multiple attributes; otherwise, <see langword="false"/>.</param>
protected AttributeEdmPropertyConvention(Func<Attribute, bool> attributeFilter, bool allowMultiple)
: base(attributeFilter, allowMultiple)
{
}
/// <summary>
/// Applies the convention.
/// </summary>
/// <param name="edmProperty">The property being configured.</param>
/// <param name="structuralTypeConfiguration">The type being configured.</param>
public void Apply(PropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration)
{
if (edmProperty == null)
{
throw Error.ArgumentNull("edmProperty");
}
if (structuralTypeConfiguration == null)
{
throw Error.ArgumentNull("structuralTypeConfiguration");
}
TPropertyConfiguration property = edmProperty as TPropertyConfiguration;
if (property != null)
{
Apply(property, structuralTypeConfiguration);
}
}
/// <summary>
/// Applies the convention.
/// </summary>
/// <param name="edmProperty">The property being configured.</param>
/// <param name="structuralTypeConfiguration">The type being configured.</param>
public void Apply(TPropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration)
{
if (edmProperty == null)
{
throw Error.ArgumentNull("edmProperty");
}
if (structuralTypeConfiguration == null)
{
throw Error.ArgumentNull("structuralTypeConfiguration");
}
foreach (Attribute attribute in GetAttributes(edmProperty.PropertyInfo))
{
Apply(edmProperty, structuralTypeConfiguration, attribute);
}
}
/// <summary>
/// Applies the convention.
/// </summary>
/// <param name="edmProperty">The property being configured.</param>
/// <param name="structuralTypeConfiguration">The type being configured.</param>
/// <param name="attribute">The attribute to be used during configuration.</param>
public abstract void Apply(TPropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, Attribute attribute);
}
}

Просмотреть файл

@ -0,0 +1,62 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder.Conventions.Attributes
{
/// <summary>
/// Base class for all <see cref="IEdmTypeConvention"/>'s based on a attribute on the type.
/// </summary>
/// <typeparam name="TEdmTypeConfiguration">The kind of Edm type that this convention must be applied to.</typeparam>
internal abstract class AttributeEdmTypeConvention<TEdmTypeConfiguration> : AttributeConvention, IEdmTypeConvention
where TEdmTypeConfiguration : StructuralTypeConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="AttributeEdmTypeConvention{TEdmTypeConfiguration}"/> class.
/// </summary>
/// <param name="attributeFilter">A function to test whether this convention applies to an attribute or not.</param>
/// <param name="allowMultiple"><see langword="true"/> if the convention allows multiple attributes; otherwise, <see langword="false"/>.</param>
protected AttributeEdmTypeConvention(Func<Attribute, bool> attributeFilter, bool allowMultiple)
: base(attributeFilter, allowMultiple)
{
}
/// <summary>
/// Applies the convention.
/// </summary>
/// <param name="edmTypeConfiguration">The edm type to apply the convention to.</param>
/// <param name="model">The model that this edm type belongs to.</param>
public void Apply(IEdmTypeConfiguration edmTypeConfiguration, ODataModelBuilder model)
{
TEdmTypeConfiguration type = edmTypeConfiguration as TEdmTypeConfiguration;
if (type != null)
{
Apply(type, model);
}
}
/// <summary>
/// Applies the convention.
/// </summary>
/// <param name="edmTypeConfiguration">The edm type to apply the convention to.</param>
/// <param name="model">The model that this edm type belongs to.</param>
public void Apply(TEdmTypeConfiguration edmTypeConfiguration, ODataModelBuilder model)
{
if (edmTypeConfiguration == null)
{
throw Error.ArgumentNull("edmTypeConfiguration");
}
foreach (Attribute attribute in GetAttributes(edmTypeConfiguration.ClrType))
{
Apply(edmTypeConfiguration, model, attribute);
}
}
/// <summary>
/// Applies the convention.
/// </summary>
/// <param name="edmTypeConfiguration">The edm type to apply the convention to.</param>
/// <param name="model">The model that this edm type belongs to.</param>
/// <param name="attribute">The attribute found on this edm type.</param>
public abstract void Apply(TEdmTypeConfiguration edmTypeConfiguration, ODataModelBuilder model, Attribute attribute);
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.ComponentModel.DataAnnotations;
namespace System.Web.Http.OData.Builder.Conventions.Attributes
{
/// <summary>
/// Marks properties that have <see cref="ConcurrencyCheckAttribute"/> as non-optional on their EDM type.
/// </summary>
internal class ConcurrencyCheckAttributeEdmPropertyConvention : AttributeEdmPropertyConvention<PropertyConfiguration>
{
public ConcurrencyCheckAttributeEdmPropertyConvention()
: base(attribute => attribute.GetType() == typeof(ConcurrencyCheckAttribute), allowMultiple: false)
{
}
/// <summary>
/// Marks the property with concurrency token on the EDM type.
/// </summary>
/// <param name="edmProperty">The EDM property.</param>
/// <param name="structuralTypeConfiguration">The EDM type being configured.</param>
/// <param name="attribute">The <see cref="Attribute"/> found.</param>
public override void Apply(PropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, Attribute attribute)
{
if (edmProperty == null)
{
throw Error.ArgumentNull("edmProperty");
}
EntityTypeConfiguration entityType = structuralTypeConfiguration as EntityTypeConfiguration;
PrimitivePropertyConfiguration primitiveProperty = edmProperty as PrimitivePropertyConfiguration;
if (entityType != null && primitiveProperty != null)
{
primitiveProperty.ConcurrencyToken = true;
}
}
}
}

Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
namespace System.Web.Http.OData.Builder.Conventions.Attributes
{
/// <summary>
/// Configures classes that have the <see cref="DataContractAttribute"/> to follow DataContract serialization/deserialization rules.
/// </summary>
internal class DataContractAttributeEdmTypeConvention : AttributeEdmTypeConvention<StructuralTypeConfiguration>
{
public DataContractAttributeEdmTypeConvention()
: base(attribute => attribute.GetType() == typeof(DataContractAttribute), allowMultiple: false)
{
}
/// <summary>
/// Removes properties that do not have the <see cref="DataMemberAttribute"/> attribute from the edm type.
/// </summary>
/// <param name="edmTypeConfiguration">The edm type to configure.</param>
/// <param name="model">The edm model that this type belongs to.</param>
/// <param name="attribute">The <see cref="Attribute"/> found on this type.</param>
public override void Apply(StructuralTypeConfiguration edmTypeConfiguration, ODataModelBuilder model, Attribute attribute)
{
if (edmTypeConfiguration == null)
{
throw Error.ArgumentNull("edmTypeConfiguration");
}
if (!edmTypeConfiguration.AddedExplicitly &&
edmTypeConfiguration.ModelBuilder != null &&
edmTypeConfiguration.ModelBuilder.ModelAliasingEnabled)
{
// set the name, and namespace, if not null
DataContractAttribute dataContractAttribute = attribute as DataContractAttribute;
if (dataContractAttribute != null)
{
if (dataContractAttribute.Name != null)
{
edmTypeConfiguration.Name = dataContractAttribute.Name;
}
if (dataContractAttribute.Namespace != null)
{
edmTypeConfiguration.Namespace = dataContractAttribute.Namespace;
}
}
edmTypeConfiguration.AddedExplicitly = false;
}
IEnumerable<PropertyConfiguration> allProperties = edmTypeConfiguration.Properties.ToArray();
foreach (PropertyConfiguration property in allProperties)
{
if (!property.PropertyInfo.GetCustomAttributes(typeof(DataMemberAttribute), inherit: true).Any())
{
if (!property.AddedExplicitly)
{
edmTypeConfiguration.RemoveProperty(property.PropertyInfo);
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,65 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Linq;
using System.Runtime.Serialization;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder.Conventions.Attributes
{
/// <summary>
/// Configures properties that have <see cref="DataMemberAttribute"/> as optional or required on their edm type.
/// </summary>
internal class DataMemberAttributeEdmPropertyConvention : AttributeEdmPropertyConvention<PropertyConfiguration>
{
public DataMemberAttributeEdmPropertyConvention()
: base(attribute => attribute.GetType() == typeof(DataMemberAttribute), allowMultiple: false)
{
}
public override void Apply(PropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, Attribute attribute)
{
if (structuralTypeConfiguration == null)
{
throw Error.ArgumentNull("structuralTypeConfiguration");
}
if (edmProperty == null)
{
throw Error.ArgumentNull("edmProperty");
}
bool isTypeDataContract = structuralTypeConfiguration.ClrType.GetCustomAttributes(typeof(DataContractAttribute), inherit: true).Any();
DataMemberAttribute dataMember = attribute as DataMemberAttribute;
if (isTypeDataContract && dataMember != null && !edmProperty.AddedExplicitly)
{
// set the name alias
if (structuralTypeConfiguration.ModelBuilder != null &&
structuralTypeConfiguration.ModelBuilder.ModelAliasingEnabled &&
!String.IsNullOrWhiteSpace(dataMember.Name))
{
edmProperty.Name = dataMember.Name;
}
StructuralPropertyConfiguration structuralProperty = edmProperty as StructuralPropertyConfiguration;
if (structuralProperty != null)
{
structuralProperty.OptionalProperty = !dataMember.IsRequired;
}
NavigationPropertyConfiguration navigationProperty = edmProperty as NavigationPropertyConfiguration;
if (navigationProperty != null && navigationProperty.Multiplicity != EdmMultiplicity.Many)
{
if (dataMember.IsRequired)
{
navigationProperty.Required();
}
else
{
navigationProperty.Optional();
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Linq;
using System.Runtime.Serialization;
namespace System.Web.Http.OData.Builder.Conventions.Attributes
{
/// <summary>
/// Removes properties that have <see cref="IgnoreDataMemberAttribute"/> from their edm type.
/// </summary>
internal class IgnoreDataMemberAttributeEdmPropertyConvention : AttributeEdmPropertyConvention<PropertyConfiguration>
{
public IgnoreDataMemberAttributeEdmPropertyConvention()
: base(attribute => attribute.GetType() == typeof(IgnoreDataMemberAttribute), allowMultiple: false)
{
}
/// <summary>
/// Removes the property from the edm type.
/// </summary>
/// <param name="edmProperty">The property being removed.</param>
/// <param name="structuralTypeConfiguration">The edm type from which the property is being removed.</param>
/// <param name="attribute">The <see cref="Attribute"/> found on this type.</param>
public override void Apply(PropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, Attribute attribute)
{
if (structuralTypeConfiguration == null)
{
throw Error.ArgumentNull("structuralTypeConfiguration");
}
if (edmProperty == null)
{
throw Error.ArgumentNull("edmProperty");
}
if (!edmProperty.AddedExplicitly)
{
bool isTypeDataContract = structuralTypeConfiguration.ClrType.GetCustomAttributes(typeof(DataContractAttribute), inherit: true).Any();
bool isPropertyDataMember = edmProperty.PropertyInfo.GetCustomAttributes(typeof(DataMemberAttribute), inherit: true).Any();
if (isTypeDataContract && isPropertyDataMember)
{
// both Datamember and IgnoreDataMember. DataMember wins as this a DataContract
return;
}
else
{
structuralTypeConfiguration.RemoveProperty(edmProperty.PropertyInfo);
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.ComponentModel.DataAnnotations;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder.Conventions.Attributes
{
/// <summary>
/// Configures properties that have the <see cref="KeyAttribute"/> as keys in the <see cref="IEdmEntityType"/>.
/// </summary>
internal class KeyAttributeEdmPropertyConvention : AttributeEdmPropertyConvention<PrimitivePropertyConfiguration>
{
public KeyAttributeEdmPropertyConvention()
: base(attribute => attribute.GetType() == typeof(KeyAttribute), allowMultiple: false)
{
}
/// <summary>
/// Configures the property as a key on the edm type.
/// </summary>
/// <param name="edmProperty">The key property.</param>
/// <param name="structuralTypeConfiguration">The edm type being configured.</param>
/// <param name="attribute">The <see cref="Attribute"/> found on the property.</param>
public override void Apply(PrimitivePropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, Attribute attribute)
{
if (edmProperty == null)
{
throw Error.ArgumentNull("edmProperty");
}
EntityTypeConfiguration entity = structuralTypeConfiguration as EntityTypeConfiguration;
if (entity != null)
{
entity.HasKey(edmProperty.PropertyInfo);
}
}
}
}

Просмотреть файл

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.ComponentModel.DataAnnotations;
using System.Web.Http.OData.Query;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder.Conventions.Attributes
{
internal class NonFilterableAttributeEdmPropertyConvention : AttributeEdmPropertyConvention<PropertyConfiguration>
{
public NonFilterableAttributeEdmPropertyConvention()
: base(attribute => attribute.GetType() == typeof(NonFilterableAttribute), allowMultiple: false)
{
}
public override void Apply(PropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, Attribute attribute)
{
if (edmProperty == null)
{
throw Error.ArgumentNull("edmProperty");
}
if (!edmProperty.AddedExplicitly)
{
edmProperty.IsNonFilterable();
}
}
}
}

Просмотреть файл

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.ComponentModel.DataAnnotations;
using System.Web.Http.OData.Query;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder.Conventions.Attributes
{
internal class NotExpandableAttributeEdmPropertyConvention : AttributeEdmPropertyConvention<NavigationPropertyConfiguration>
{
public NotExpandableAttributeEdmPropertyConvention()
: base(attribute => attribute.GetType() == typeof(NotExpandableAttribute), allowMultiple: false)
{
}
public override void Apply(NavigationPropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, Attribute attribute)
{
if (edmProperty == null)
{
throw Error.ArgumentNull("edmProperty");
}
if (!edmProperty.AddedExplicitly)
{
edmProperty.IsNotExpandable();
}
}
}
}

Просмотреть файл

@ -0,0 +1,43 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder.Conventions.Attributes
{
/// <summary>
/// Ignores properties with the NotMappedAttribute from <see cref="IEdmStructuredType"/>.
/// </summary>
internal class NotMappedAttributeConvention : AttributeEdmPropertyConvention<PropertyConfiguration>
{
// .net 4.5 NotMappedAttribute has the same name.
private const string EntityFrameworkNotMappedAttributeTypeName = "System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute";
private static Func<Attribute, bool> _filter = attribute =>
{
return attribute.GetType().FullName.Equals(EntityFrameworkNotMappedAttributeTypeName, StringComparison.Ordinal);
};
public NotMappedAttributeConvention()
: base(_filter, allowMultiple: false)
{
}
public override void Apply(PropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, Attribute attribute)
{
if (edmProperty == null)
{
throw Error.ArgumentNull("edmProperty");
}
if (structuralTypeConfiguration == null)
{
throw Error.ArgumentNull("structuralTypeConfiguration");
}
if (!edmProperty.AddedExplicitly)
{
structuralTypeConfiguration.RemoveProperty(edmProperty.PropertyInfo);
}
}
}
}

Просмотреть файл

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.ComponentModel.DataAnnotations;
using System.Web.Http.OData.Query;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder.Conventions.Attributes
{
internal class NotNavigableAttributeEdmPropertyConvention : AttributeEdmPropertyConvention<NavigationPropertyConfiguration>
{
public NotNavigableAttributeEdmPropertyConvention()
: base(attribute => attribute.GetType() == typeof(NotNavigableAttribute), allowMultiple: false)
{
}
public override void Apply(NavigationPropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, Attribute attribute)
{
if (edmProperty == null)
{
throw Error.ArgumentNull("edmProperty");
}
if (!edmProperty.AddedExplicitly)
{
edmProperty.IsNotNavigable();
}
}
}
}

Просмотреть файл

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.ComponentModel.DataAnnotations;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder.Conventions.Attributes
{
/// <summary>
/// Marks properties that have <see cref="RequiredAttribute"/> as non-optional on their edm type.
/// </summary>
internal class RequiredAttributeEdmPropertyConvention : AttributeEdmPropertyConvention<PropertyConfiguration>
{
public RequiredAttributeEdmPropertyConvention()
: base(attribute => attribute.GetType() == typeof(RequiredAttribute), allowMultiple: false)
{
}
/// <summary>
/// Marks the property non-optional on the edm type.
/// </summary>
/// <param name="edmProperty">The edm property.</param>
/// <param name="structuralTypeConfiguration">The edm type being configured.</param>
/// <param name="attribute">The <see cref="Attribute"/> found.</param>
public override void Apply(PropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, Attribute attribute)
{
if (edmProperty == null)
{
throw Error.ArgumentNull("edmProperty");
}
if (!edmProperty.AddedExplicitly)
{
StructuralPropertyConfiguration structuralProperty = edmProperty as StructuralPropertyConfiguration;
if (structuralProperty != null)
{
structuralProperty.OptionalProperty = false;
}
NavigationPropertyConfiguration navigationProperty = edmProperty as NavigationPropertyConfiguration;
if (navigationProperty != null && navigationProperty.Multiplicity != EdmMultiplicity.Many)
{
navigationProperty.Required();
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.ComponentModel.DataAnnotations;
using System.Web.Http.OData.Query;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder.Conventions.Attributes
{
internal class UnsortableAttributeEdmPropertyConvention : AttributeEdmPropertyConvention<PropertyConfiguration>
{
public UnsortableAttributeEdmPropertyConvention()
: base(attribute => attribute.GetType() == typeof(UnsortableAttribute), allowMultiple: false)
{
}
public override void Apply(PropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, Attribute attribute)
{
if (edmProperty == null)
{
throw Error.ArgumentNull("edmProperty");
}
if (!edmProperty.AddedExplicitly)
{
edmProperty.IsUnsortable();
}
}
}
}

Просмотреть файл

@ -0,0 +1,176 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Web.Http.OData.Formatter;
using System.Web.Http.OData.Formatter.Serialization;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
using Microsoft.Data.OData;
using Microsoft.Data.OData.Query;
namespace System.Web.Http.OData.Builder.Conventions
{
internal static class ConventionsHelpers
{
public static string GetEntityKeyValue(EntityInstanceContext entityContext)
{
Contract.Assert(entityContext != null);
Contract.Assert(entityContext.EntityType != null);
Contract.Assert(entityContext.EdmObject != null);
IEnumerable<IEdmProperty> keys = entityContext.EntityType.Key();
// TODO: BUG 453795: reflection cleanup
if (keys.Count() == 1)
{
return GetUriRepresentationForKeyValue(keys.First(), entityContext);
}
else
{
IEnumerable<string> keyValues =
keys.Select(key => String.Format(
CultureInfo.InvariantCulture, "{0}={1}", key.Name, GetUriRepresentationForKeyValue(key, entityContext)));
return String.Join(",", keyValues);
}
}
// Get properties of this entity type that are not already declared in the base entity type and are not already ignored.
public static IEnumerable<PropertyInfo> GetProperties(EntityTypeConfiguration entity, bool includeReadOnly)
{
IEnumerable<PropertyInfo> allProperties = GetAllProperties(entity as StructuralTypeConfiguration, includeReadOnly);
if (entity.BaseType != null)
{
IEnumerable<PropertyInfo> baseTypeProperties = GetAllProperties(entity.BaseType as StructuralTypeConfiguration, includeReadOnly);
return allProperties.Except(baseTypeProperties, PropertyEqualityComparer.Instance);
}
else
{
return allProperties;
}
}
// Get all properties of this type (that are not already ignored).
public static IEnumerable<PropertyInfo> GetAllProperties(StructuralTypeConfiguration type, bool includeReadOnly)
{
if (type == null)
{
throw Error.ArgumentNull("type");
}
return type
.ClrType
.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(p => p.IsValidStructuralProperty() && !type.IgnoredProperties().Any(p1 => p1.Name == p.Name))
.Where(p => includeReadOnly || (p.GetSetMethod() != null || p.PropertyType.IsCollection()))
.ToArray();
}
public static bool IsValidStructuralProperty(this PropertyInfo propertyInfo)
{
if (propertyInfo == null)
{
throw Error.ArgumentNull("propertyInfo");
}
// ignore any indexer properties.
if (propertyInfo.GetIndexParameters().Any())
{
return false;
}
if (propertyInfo.CanRead)
{
// non-public getters are not valid properties
MethodInfo publicGetter = propertyInfo.GetGetMethod();
if (publicGetter != null && propertyInfo.PropertyType.IsValidStructuralPropertyType())
{
return true;
}
}
return false;
}
// Gets the ignored properties from this type and the base types.
public static IEnumerable<PropertyInfo> IgnoredProperties(this StructuralTypeConfiguration structuralType)
{
if (structuralType == null)
{
return Enumerable.Empty<PropertyInfo>();
}
EntityTypeConfiguration entityType = structuralType as EntityTypeConfiguration;
if (entityType != null)
{
return entityType.IgnoredProperties.Concat(entityType.BaseType.IgnoredProperties());
}
else
{
return structuralType.IgnoredProperties;
}
}
public static bool IsValidStructuralPropertyType(this Type type)
{
if (type == null)
{
throw Error.ArgumentNull("type");
}
Type elementType;
return !(type.IsGenericTypeDefinition
|| type.IsPointer
|| type == typeof(object)
|| (type.IsCollection(out elementType) && elementType == typeof(object)));
}
// gets the primitive odata uri representation.
public static string GetUriRepresentationForValue(object value)
{
Contract.Assert(value != null);
Contract.Assert(EdmLibHelpers.GetEdmPrimitiveTypeOrNull(value.GetType()) != null);
value = ODataPrimitiveSerializer.ConvertUnsupportedPrimitives(value);
return ODataUriUtils.ConvertToUriLiteral(value, ODataVersion.V3);
}
private static string GetUriRepresentationForKeyValue(IEdmProperty key, EntityInstanceContext entityInstanceContext)
{
Contract.Assert(key != null);
Contract.Assert(entityInstanceContext != null);
object value = entityInstanceContext.GetPropertyValue(key.Name);
if (value == null)
{
IEdmTypeReference edmType = entityInstanceContext.EdmObject.GetEdmType();
throw Error.InvalidOperation(SRResources.KeyValueCannotBeNull, key.Name, edmType.Definition);
}
return GetUriRepresentationForValue(value);
}
private class PropertyEqualityComparer : IEqualityComparer<PropertyInfo>
{
public static PropertyEqualityComparer Instance = new PropertyEqualityComparer();
public bool Equals(PropertyInfo x, PropertyInfo y)
{
Contract.Assert(x != null);
Contract.Assert(y != null);
return x.Name == y.Name;
}
public int GetHashCode(PropertyInfo obj)
{
Contract.Assert(obj != null);
return obj.Name.GetHashCode();
}
}
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Web.Http.OData.Formatter;
namespace System.Web.Http.OData.Builder.Conventions
{
/// <summary>
/// <see cref="EntityTypeConvention"/> for figuring out the entity keys.
/// <remarks>This convention configures properties that are named 'ID' (case-insensitive) or {EntityName}+ID (case-insensitive) as the key.</remarks>
/// </summary>
internal class EntityKeyConvention : EntityTypeConvention
{
/// <summary>
/// Figures out the key properties and marks them as Keys in the EDM model.
/// </summary>
/// <param name="entity">The entity type being configured.</param>
/// <param name="model">The <see cref="ODataModelBuilder"/>.</param>
public override void Apply(EntityTypeConfiguration entity, ODataModelBuilder model)
{
if (entity == null)
{
throw Error.ArgumentNull("entity");
}
// Try to figure out keys only if there is no base type.
if (entity.BaseType == null)
{
PropertyConfiguration key = GetKeyProperty(entity);
if (key != null)
{
entity.HasKey(key.PropertyInfo);
}
}
}
private static PropertyConfiguration GetKeyProperty(EntityTypeConfiguration entityType)
{
IEnumerable<PropertyConfiguration> keys =
entityType.Properties
.Where(p => (p.Name.Equals(entityType.Name + "Id", StringComparison.OrdinalIgnoreCase) || p.Name.Equals("Id", StringComparison.OrdinalIgnoreCase))
&& EdmLibHelpers.GetEdmPrimitiveTypeOrNull(p.PropertyInfo.PropertyType) != null);
if (keys.Count() == 1)
{
return keys.Single();
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,31 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder.Conventions
{
/// <summary>
/// An <see cref="EntityTypeConvention"/> is used to configure an <see cref="EntityTypeConfiguration"/> in the
/// <see cref="ODataConventionModelBuilder"/>.
/// </summary>
internal abstract class EntityTypeConvention : IEdmTypeConvention
{
protected EntityTypeConvention()
{
}
public void Apply(IEdmTypeConfiguration edmTypeConfiguration, ODataModelBuilder model)
{
EntityTypeConfiguration entity = edmTypeConfiguration as EntityTypeConfiguration;
if (entity != null)
{
Apply(entity, model);
}
}
/// <summary>
/// Applies the convention.
/// </summary>
/// <param name="entity">The <see cref="EntityTypeConfiguration"/> to apply the convention on.</param>
/// <param name="model">The <see cref="ODataModelBuilder"/> instance.</param>
public abstract void Apply(EntityTypeConfiguration entity, ODataModelBuilder model);
}
}

Просмотреть файл

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Web.Http.OData.Routing;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder.Conventions
{
/// <summary>
/// The FunctionLinkGenerationConvention calls function.HasFunctionLink(..) if the function binds to a single entity and has not previously been configured.
/// </summary>
internal class FunctionLinkGenerationConvention : IProcedureConvention
{
public void Apply(ProcedureConfiguration configuration, ODataModelBuilder model)
{
FunctionConfiguration function = configuration as FunctionConfiguration;
// You only need to create links for bindable functions that bind to a single entity.
if (function != null && function.IsBindable && function.BindingParameter.TypeConfiguration.Kind == EdmTypeKind.Entity && function.GetFunctionLink() == null)
{
string bindingParamterType = function.BindingParameter.TypeConfiguration.FullName;
function.HasFunctionLink(entityContext =>
entityContext.GenerateFunctionLink(bindingParamterType, function.Name, function.Parameters.Select(p => p.Name)),
followsConventions: true);
}
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Diagnostics.CodeAnalysis;
namespace System.Web.Http.OData.Builder.Conventions
{
[SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Justification = "Marker interface acceptable here for derivation")]
internal interface IConvention
{
}
}

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder.Conventions
{
/// <summary>
/// Convention to process properties of <see cref="StructuralTypeConfiguration"/>.
/// </summary>
internal interface IEdmPropertyConvention : IConvention
{
/// <summary>
/// Applies the convention.
/// </summary>
/// <param name="edmProperty">The property the convention is applied on.</param>
/// <param name="structuralTypeConfiguration">The <see cref="StructuralTypeConfiguration"/> the edmProperty belongs to.</param>
void Apply(PropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration);
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder.Conventions
{
/// <summary>
/// Convention to process properties of <see cref="StructuralTypeConfiguration"/>.
/// </summary>
/// <typeparam name="TPropertyConfiguration"></typeparam>
internal interface IEdmPropertyConvention<TPropertyConfiguration> : IEdmPropertyConvention where TPropertyConfiguration : PropertyConfiguration
{
/// <summary>
/// Applies the convention.
/// </summary>
/// <param name="edmProperty">The property the convention is applied on.</param>
/// <param name="structuralTypeConfiguration">The <see cref="StructuralTypeConfiguration"/> the edmProperty belongs to.</param>
void Apply(TPropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration);
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder.Conventions
{
internal interface IEdmTypeConvention : IConvention
{
void Apply(IEdmTypeConfiguration edmTypeConfiguration, ODataModelBuilder model);
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder.Conventions
{
internal interface IEntitySetConvention : IConvention
{
void Apply(EntitySetConfiguration configuration, ODataModelBuilder model);
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder.Conventions
{
/// <summary>
/// Convention to apply to <see cref="ProcedureConfiguration"/> instances in the model
/// </summary>
internal interface IProcedureConvention : IConvention
{
void Apply(ProcedureConfiguration configuration, ODataModelBuilder model);
}
}

Просмотреть файл

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder.Conventions
{
internal class NavigationLinksGenerationConvention : IEntitySetConvention
{
public void Apply(EntitySetConfiguration configuration, ODataModelBuilder model)
{
if (configuration == null)
{
throw Error.ArgumentNull("configuration");
}
// generate links without cast for declared and inherited navigation properties
foreach (EntityTypeConfiguration entity in configuration.EntityType.ThisAndBaseTypes())
{
foreach (NavigationPropertyConfiguration property in entity.NavigationProperties)
{
if (configuration.GetNavigationPropertyLink(property) == null)
{
configuration.HasNavigationPropertyLink(
property,
new NavigationLinkBuilder(
(entityContext, navigationProperty) =>
entityContext.GenerateNavigationPropertyLink(navigationProperty, includeCast: false), followsConventions: true));
}
}
}
// generate links with cast for navigation properties in derived types.
foreach (EntityTypeConfiguration entity in model.DerivedTypes(configuration.EntityType))
{
foreach (NavigationPropertyConfiguration property in entity.NavigationProperties)
{
if (configuration.GetNavigationPropertyLink(property) == null)
{
configuration.HasNavigationPropertyLink(
property,
new NavigationLinkBuilder(
(entityContext, navigationProperty) =>
entityContext.GenerateNavigationPropertyLink(navigationProperty, includeCast: true), followsConventions: true));
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Linq;
using System.Web.Http.OData.Routing;
namespace System.Web.Http.OData.Builder.Conventions
{
internal class SelfLinksGenerationConvention : IEntitySetConvention
{
public void Apply(EntitySetConfiguration configuration, ODataModelBuilder model)
{
if (configuration == null)
{
throw Error.ArgumentNull("configuration");
}
// Configure the self link for the feed
if (configuration.GetFeedSelfLink() == null)
{
configuration.HasFeedSelfLink(entitySetContext =>
{
string selfLink = entitySetContext.Url.ODataLink(new EntitySetPathSegment(entitySetContext.EntitySet));
if (selfLink == null)
{
return null;
}
return new Uri(selfLink);
});
}
// We only need to configure the IdLink by convention, ReadLink and EditLink both delegate to IdLink
if (configuration.GetIdLink() == null)
{
bool derivedTypesDefineNavigationProperty = model.DerivedTypes(configuration.EntityType).Any(e => e.NavigationProperties.Any());
// generate links with cast if any of the derived types define a navigation property
if (derivedTypesDefineNavigationProperty)
{
configuration.HasIdLink(new SelfLinkBuilder<string>((entityContext) => entityContext.GenerateSelfLink(includeCast: true), followsConventions: true));
}
else
{
configuration.HasIdLink(new SelfLinkBuilder<string>((entityContext) => entityContext.GenerateSelfLink(includeCast: false), followsConventions: true));
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,378 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Web.Http.OData.Builder;
using System.Web.Http.OData.Formatter;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
using Microsoft.Data.Edm.Csdl;
using Microsoft.Data.Edm.Expressions;
using Microsoft.Data.Edm.Library;
using Microsoft.Data.Edm.Library.Expressions;
using Microsoft.Data.OData;
namespace System.Web.Http.OData.Builder
{
internal static class EdmModelHelperMethods
{
public static IEdmModel BuildEdmModel(ODataModelBuilder builder)
{
if (builder == null)
{
throw Error.ArgumentNull("builder");
}
EdmModel model = new EdmModel();
EdmEntityContainer container = new EdmEntityContainer(builder.Namespace, builder.ContainerName);
// add types and sets, building an index on the way.
Dictionary<Type, IEdmStructuredType> edmTypeMap = model.AddTypes(builder.StructuralTypes);
Dictionary<string, EdmEntitySet> edmEntitySetMap = model.AddEntitySets(builder, container, edmTypeMap);
// add procedures
model.AddProcedures(builder.Procedures, container, edmTypeMap, edmEntitySetMap);
// finish up
model.AddElement(container);
model.SetIsDefaultEntityContainer(container, isDefaultContainer: true);
// build the map from IEdmEntityType to IEdmFunctionImport
model.SetAnnotationValue<BindableProcedureFinder>(model, new BindableProcedureFinder(model));
// set the data service version annotations.
model.SetDataServiceVersion(builder.DataServiceVersion);
model.SetMaxDataServiceVersion(builder.MaxDataServiceVersion);
return model;
}
private static void AddTypes(this EdmModel model, Dictionary<Type, IEdmStructuredType> structuredTypes)
{
Contract.Assert(model != null);
Contract.Assert(structuredTypes != null);
foreach (IEdmStructuredType type in structuredTypes.Values)
{
model.AddType(type);
}
}
private static Dictionary<string, EdmEntitySet> AddEntitySets(this EdmModel model, ODataModelBuilder builder, EdmEntityContainer container, Dictionary<Type, IEdmStructuredType> edmTypeMap)
{
IEnumerable<EntitySetConfiguration> configurations = builder.EntitySets;
// build the entitysets and their annotations
IEnumerable<Tuple<EdmEntitySet, EntitySetConfiguration>> entitySets = AddEntitySets(configurations, container, edmTypeMap);
var entitySetAndAnnotations = entitySets.Select(e => new
{
EntitySet = e.Item1,
Configuration = e.Item2,
Annotations = new
{
LinkBuilder = new EntitySetLinkBuilderAnnotation(e.Item2),
Url = new EntitySetUrlAnnotation { Url = e.Item2.GetUrl() }
}
}).ToArray();
// index the entitySets by name
Dictionary<string, EdmEntitySet> edmEntitySetMap = entitySetAndAnnotations.ToDictionary(e => e.EntitySet.Name, e => e.EntitySet);
// apply the annotations
foreach (var iter in entitySetAndAnnotations)
{
EdmEntitySet entitySet = iter.EntitySet;
model.SetAnnotationValue<EntitySetUrlAnnotation>(entitySet, iter.Annotations.Url);
model.SetEntitySetLinkBuilder(entitySet, iter.Annotations.LinkBuilder);
AddNavigationBindings(iter.Configuration, iter.EntitySet, iter.Annotations.LinkBuilder, builder, edmTypeMap, edmEntitySetMap);
}
return edmEntitySetMap;
}
private static void AddNavigationBindings(EntitySetConfiguration configuration, EdmEntitySet entitySet, EntitySetLinkBuilderAnnotation linkBuilder, ODataModelBuilder builder,
Dictionary<Type, IEdmStructuredType> edmTypeMap, Dictionary<string, EdmEntitySet> edmEntitySetMap)
{
foreach (EntityTypeConfiguration entity in builder.ThisAndBaseAndDerivedTypes(configuration.EntityType))
{
foreach (NavigationPropertyConfiguration navigation in entity.NavigationProperties)
{
NavigationPropertyBindingConfiguration binding = configuration.FindBinding(navigation);
if (binding != null)
{
EdmEntityType edmEntityType = edmTypeMap[entity.ClrType] as EdmEntityType;
IEdmNavigationProperty edmNavigationProperty = edmEntityType.NavigationProperties().Single(np => np.Name == navigation.Name);
entitySet.AddNavigationTarget(edmNavigationProperty, edmEntitySetMap[binding.EntitySet.Name]);
NavigationLinkBuilder linkBuilderFunc = configuration.GetNavigationPropertyLink(navigation);
if (linkBuilderFunc != null)
{
linkBuilder.AddNavigationPropertyLinkBuilder(edmNavigationProperty, linkBuilderFunc);
}
}
}
}
}
private static void AddProcedureParameters(EdmFunctionImport functionImport, ProcedureConfiguration procedure, Dictionary<Type, IEdmStructuredType> edmTypeMap)
{
foreach (ParameterConfiguration parameter in procedure.Parameters)
{
// TODO: http://aspnetwebstack.codeplex.com/workitem/417
bool isParameterOptional = EdmLibHelpers.IsNullable(parameter.TypeConfiguration.ClrType);
IEdmTypeReference parameterTypeReference = GetEdmTypeReference(edmTypeMap, parameter.TypeConfiguration, nullable: isParameterOptional);
EdmFunctionParameter functionParameter = new EdmFunctionParameter(functionImport, parameter.Name, parameterTypeReference, EdmFunctionParameterMode.In);
functionImport.AddParameter(functionParameter);
}
}
private static void AddProcedureLinkBuilder(IEdmModel model, EdmFunctionImport functionImport, ProcedureConfiguration procedure)
{
if (procedure.BindingParameter.TypeConfiguration.Kind == EdmTypeKind.Entity)
{
ActionConfiguration action = procedure as ActionConfiguration;
FunctionConfiguration function = procedure as FunctionConfiguration;
if (action != null && action.GetActionLink() != null)
{
model.SetActionLinkBuilder(functionImport, new ActionLinkBuilder(action.GetActionLink(), action.FollowsConventions));
}
else if (function != null && function.GetFunctionLink() != null)
{
model.SetFunctionLinkBuilder(functionImport, new FunctionLinkBuilder(function.GetFunctionLink(), function.FollowsConventions));
}
}
}
private static void ValidateProcedureEntitySetPath(IEdmModel model, EdmFunctionImport functionImport, ProcedureConfiguration procedure)
{
IEdmFunctionParameter procedureParameter;
IEnumerable<IEdmNavigationProperty> navPath;
if (procedure.EntitySetPath != null && !functionImport.TryGetRelativeEntitySetPath(model, out procedureParameter, out navPath))
{
throw Error.InvalidOperation(SRResources.ProcedureHasInvalidEntitySetPath, String.Join("/", procedure.EntitySetPath.ToArray()), procedure.FullName);
}
}
private static void AddProcedures(this IEdmModel model, IEnumerable<ProcedureConfiguration> configurations, EdmEntityContainer container, Dictionary<Type, IEdmStructuredType> edmTypeMap, Dictionary<string, EdmEntitySet> edmEntitySetMap)
{
foreach (ProcedureConfiguration procedure in configurations)
{
switch (procedure.Kind)
{
case ProcedureKind.Action:
case ProcedureKind.Function:
IEdmTypeReference returnReference = GetEdmTypeReference(edmTypeMap, procedure.ReturnType, nullable: true);
IEdmExpression expression = GetEdmEntitySetExpression(edmEntitySetMap, procedure);
EdmFunctionImport functionImport = new EdmFunctionImport(container, procedure.Name, returnReference, expression, procedure.IsSideEffecting, procedure.IsComposable, procedure.IsBindable);
AddProcedureParameters(functionImport, procedure, edmTypeMap);
if (procedure.IsBindable)
{
model.SetIsAlwaysBindable(functionImport, procedure.IsAlwaysBindable);
AddProcedureLinkBuilder(model, functionImport, procedure);
ValidateProcedureEntitySetPath(model, functionImport, procedure);
}
container.AddElement(functionImport);
break;
case ProcedureKind.ServiceOperation:
Contract.Assert(false, "ServiceOperations are not supported.");
break;
}
}
}
private static Dictionary<Type, IEdmStructuredType> AddTypes(this EdmModel model, IEnumerable<StructuralTypeConfiguration> types)
{
StructuralTypeConfiguration[] configTypes = types.ToArray();
// build types
EdmTypeMap edmTypeMap = EdmTypeBuilder.GetTypesAndProperties(configTypes);
Dictionary<Type, IEdmStructuredType> edmTypes = edmTypeMap.EdmTypes;
// Add an annotate types
model.AddTypes(edmTypes);
model.AddClrTypeAnnotations(edmTypes);
// add annotation for properties
Dictionary<PropertyInfo, IEdmProperty> edmProperties = edmTypeMap.EdmProperties;
model.AddClrPropertyInfoAnnotations(edmProperties);
model.AddPropertyRestrictionsAnnotations(edmTypeMap.EdmPropertiesRestrictions);
return edmTypes;
}
private static void AddType(this EdmModel model, IEdmStructuredType type)
{
if (type.TypeKind == EdmTypeKind.Complex)
{
model.AddElement(type as IEdmComplexType);
}
else if (type.TypeKind == EdmTypeKind.Entity)
{
model.AddElement(type as IEdmEntityType);
}
else
{
Contract.Assert(false, "Only ComplexTypes and EntityTypes are supported.");
}
}
private static EdmEntitySet AddEntitySet(this EdmEntityContainer container, EntitySetConfiguration entitySet, IDictionary<Type, IEdmStructuredType> edmTypeMap)
{
return container.AddEntitySet(entitySet.Name, (IEdmEntityType)edmTypeMap[entitySet.EntityType.ClrType]);
}
private static IEnumerable<Tuple<EdmEntitySet, EntitySetConfiguration>> AddEntitySets(IEnumerable<EntitySetConfiguration> entitySets, EdmEntityContainer container, Dictionary<Type, IEdmStructuredType> edmTypeMap)
{
return entitySets.Select(es => Tuple.Create(container.AddEntitySet(es, edmTypeMap), es));
}
private static void AddClrTypeAnnotations(this EdmModel model, Dictionary<Type, IEdmStructuredType> edmTypes)
{
foreach (KeyValuePair<Type, IEdmStructuredType> map in edmTypes)
{
// pre-populate the model with clr-type annotations so that we dont have to scan
// all loaded assemblies to find the clr type for an edm type that we build.
IEdmStructuredType edmType = map.Value;
Type clrType = map.Key;
model.SetAnnotationValue<ClrTypeAnnotation>(edmType, new ClrTypeAnnotation(clrType));
}
}
private static void AddClrPropertyInfoAnnotations(this EdmModel model, Dictionary<PropertyInfo, IEdmProperty> edmProperties)
{
foreach (KeyValuePair<PropertyInfo, IEdmProperty> edmPropertyMap in edmProperties)
{
IEdmProperty edmProperty = edmPropertyMap.Value;
PropertyInfo clrProperty = edmPropertyMap.Key;
if (edmProperty.Name != clrProperty.Name)
{
model.SetAnnotationValue(edmProperty, new ClrPropertyInfoAnnotation(clrProperty));
}
}
}
private static void AddPropertyRestrictionsAnnotations(this EdmModel model, Dictionary<IEdmProperty, QueryableRestrictions> edmPropertiesRestrictions)
{
foreach (KeyValuePair<IEdmProperty, QueryableRestrictions> edmPropertyRestriction in edmPropertiesRestrictions)
{
IEdmProperty edmProperty = edmPropertyRestriction.Key;
QueryableRestrictions restrictions = edmPropertyRestriction.Value;
model.SetAnnotationValue(edmProperty, new QueryableRestrictionsAnnotation(restrictions));
}
}
private static IEdmExpression GetEdmEntitySetExpression(Dictionary<string, EdmEntitySet> entitySets, ProcedureConfiguration procedure)
{
if (procedure.EntitySet != null)
{
if (entitySets.ContainsKey(procedure.EntitySet.Name))
{
EdmEntitySet entitySet = entitySets[procedure.EntitySet.Name];
return new EdmEntitySetReferenceExpression(entitySet);
}
else
{
throw Error.InvalidOperation(SRResources.EntitySetNotFoundForName, procedure.EntitySet.Name);
}
}
else if (procedure.EntitySetPath != null)
{
return new EdmPathExpression(procedure.EntitySetPath);
}
return null;
}
private static IEdmTypeReference GetEdmTypeReference(Dictionary<Type, IEdmStructuredType> availableTypes, IEdmTypeConfiguration configuration, bool nullable)
{
Contract.Assert(availableTypes != null);
if (configuration == null)
{
return null;
}
EdmTypeKind kind = configuration.Kind;
if (kind == EdmTypeKind.Collection)
{
CollectionTypeConfiguration collectionType = configuration as CollectionTypeConfiguration;
EdmCollectionType edmCollectionType = new EdmCollectionType(GetEdmTypeReference(availableTypes, collectionType.ElementType, false));
return new EdmCollectionTypeReference(edmCollectionType, nullable);
}
else if (availableTypes.ContainsKey(configuration.ClrType))
{
IEdmStructuredType structuralType = availableTypes[configuration.ClrType];
if (kind == EdmTypeKind.Complex)
{
return new EdmComplexTypeReference(structuralType as IEdmComplexType, nullable);
}
else if (kind == EdmTypeKind.Entity)
{
return new EdmEntityTypeReference(structuralType as IEdmEntityType, nullable);
}
else
{
throw Error.InvalidOperation(SRResources.UnsupportedEdmTypeKind, kind.ToString());
}
}
else if (configuration.Kind == EdmTypeKind.Primitive)
{
PrimitiveTypeConfiguration primitiveTypeConfiguration = configuration as PrimitiveTypeConfiguration;
return new EdmPrimitiveTypeReference(primitiveTypeConfiguration.EdmPrimitiveType, nullable);
}
else
{
throw Error.InvalidOperation(SRResources.NoMatchingIEdmTypeFound, configuration.FullName);
}
}
internal static string GetEntitySetUrl(this IEdmModel model, IEdmEntitySet entitySet)
{
if (model == null)
{
throw Error.ArgumentNull("model");
}
if (entitySet == null)
{
throw Error.ArgumentNull("entitySet");
}
EntitySetUrlAnnotation annotation = model.GetAnnotationValue<EntitySetUrlAnnotation>(entitySet);
if (annotation == null)
{
return entitySet.Name;
}
else
{
return annotation.Url;
}
}
internal static IEnumerable<IEdmFunctionImport> GetAvailableProcedures(this IEdmModel model, IEdmEntityType entityType)
{
if (model == null)
{
throw Error.ArgumentNull("model");
}
if (entityType == null)
{
throw Error.ArgumentNull("entityType");
}
BindableProcedureFinder annotation = model.GetAnnotationValue<BindableProcedureFinder>(model);
if (annotation == null)
{
annotation = new BindableProcedureFinder(model);
model.SetAnnotationValue(model, annotation);
}
return annotation.FindProcedures(entityType);
}
}
}

Просмотреть файл

@ -0,0 +1,231 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Web.Http.OData.Formatter;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
using Microsoft.Data.Edm.Library;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// <see cref="EdmTypeBuilder"/> builds <see cref="IEdmType"/>'s from <see cref=" StructuralTypeConfiguration"/>'s.
/// </summary>
internal class EdmTypeBuilder
{
private readonly List<StructuralTypeConfiguration> _configurations;
private readonly Dictionary<Type, IEdmStructuredType> _types = new Dictionary<Type, IEdmStructuredType>();
private readonly Dictionary<PropertyInfo, IEdmProperty> _properties = new Dictionary<PropertyInfo, IEdmProperty>();
private readonly Dictionary<IEdmProperty, QueryableRestrictions> _propertiesRestrictions = new Dictionary<IEdmProperty, QueryableRestrictions>();
internal EdmTypeBuilder(IEnumerable<StructuralTypeConfiguration> configurations)
{
_configurations = configurations.ToList();
}
private Dictionary<Type, IEdmStructuredType> GetEdmTypes()
{
// Reset
_types.Clear();
_properties.Clear();
// Create headers to allow CreateEdmTypeBody to blindly references other things.
foreach (StructuralTypeConfiguration config in _configurations)
{
CreateEdmTypeHeader(config);
}
foreach (StructuralTypeConfiguration config in _configurations)
{
CreateEdmTypeBody(config);
}
return _types;
}
private void CreateEdmTypeHeader(StructuralTypeConfiguration config)
{
if (!_types.ContainsKey(config.ClrType))
{
if (config.Kind == EdmTypeKind.Complex)
{
_types.Add(config.ClrType, new EdmComplexType(config.Namespace, config.Name));
}
else
{
EntityTypeConfiguration entity = config as EntityTypeConfiguration;
Contract.Assert(entity != null);
IEdmEntityType baseType = null;
if (entity.BaseType != null)
{
CreateEdmTypeHeader(entity.BaseType);
baseType = _types[entity.BaseType.ClrType] as IEdmEntityType;
Contract.Assert(baseType != null);
}
_types.Add(config.ClrType, new EdmEntityType(config.Namespace, config.Name, baseType, entity.IsAbstract ?? false, isOpen: false));
}
}
}
private void CreateEdmTypeBody(StructuralTypeConfiguration config)
{
IEdmType edmType = _types[config.ClrType];
if (edmType.TypeKind == EdmTypeKind.Complex)
{
CreateComplexTypeBody(edmType as EdmComplexType, config as ComplexTypeConfiguration);
}
else
{
if (edmType.TypeKind == EdmTypeKind.Entity)
{
CreateEntityTypeBody(edmType as EdmEntityType, config as EntityTypeConfiguration);
}
}
}
private void CreateStructuralTypeBody(EdmStructuredType type, StructuralTypeConfiguration config)
{
foreach (PropertyConfiguration property in config.Properties)
{
IEdmProperty edmProperty = null;
switch (property.Kind)
{
case PropertyKind.Primitive:
PrimitivePropertyConfiguration primitiveProperty = property as PrimitivePropertyConfiguration;
EdmPrimitiveTypeKind typeKind = GetTypeKind(primitiveProperty.PropertyInfo.PropertyType);
IEdmTypeReference primitiveTypeReference = EdmCoreModel.Instance.GetPrimitive(
typeKind,
primitiveProperty.OptionalProperty);
// Set concurrency token if is entity type, and concurrency token is true
EdmConcurrencyMode concurrencyMode = EdmConcurrencyMode.None;
if (config.Kind == EdmTypeKind.Entity && primitiveProperty.ConcurrencyToken)
{
concurrencyMode = EdmConcurrencyMode.Fixed;
}
edmProperty = type.AddStructuralProperty(
primitiveProperty.Name,
primitiveTypeReference,
defaultValue: null,
concurrencyMode: concurrencyMode);
break;
case PropertyKind.Complex:
ComplexPropertyConfiguration complexProperty = property as ComplexPropertyConfiguration;
IEdmComplexType complexType = _types[complexProperty.RelatedClrType] as IEdmComplexType;
edmProperty = type.AddStructuralProperty(
complexProperty.Name,
new EdmComplexTypeReference(complexType, complexProperty.OptionalProperty));
break;
case PropertyKind.Collection:
CollectionPropertyConfiguration collectionProperty = property as CollectionPropertyConfiguration;
IEdmTypeReference elementTypeReference = null;
if (_types.ContainsKey(collectionProperty.ElementType))
{
IEdmComplexType elementType = _types[collectionProperty.ElementType] as IEdmComplexType;
elementTypeReference = new EdmComplexTypeReference(elementType, false);
}
else
{
elementTypeReference = EdmLibHelpers.GetEdmPrimitiveTypeReferenceOrNull(collectionProperty.ElementType);
}
edmProperty = type.AddStructuralProperty(
collectionProperty.Name,
new EdmCollectionTypeReference(
new EdmCollectionType(elementTypeReference),
collectionProperty.OptionalProperty));
break;
default:
break;
}
if (property.PropertyInfo != null && edmProperty != null)
{
_properties[property.PropertyInfo] = edmProperty;
}
if (edmProperty != null && property.IsRestricted)
{
_propertiesRestrictions[edmProperty] = new QueryableRestrictions(property);
}
}
}
private void CreateComplexTypeBody(EdmComplexType type, ComplexTypeConfiguration config)
{
CreateStructuralTypeBody(type, config);
}
private void CreateEntityTypeBody(EdmEntityType type, EntityTypeConfiguration config)
{
CreateStructuralTypeBody(type, config);
IEdmStructuralProperty[] keys = config.Keys.Select(p => type.DeclaredProperties.OfType<IEdmStructuralProperty>().First(dp => dp.Name == p.Name)).ToArray();
type.AddKeys(keys);
foreach (NavigationPropertyConfiguration navProp in config.NavigationProperties)
{
EdmNavigationPropertyInfo info = new EdmNavigationPropertyInfo();
info.Name = navProp.Name;
info.TargetMultiplicity = navProp.Multiplicity;
info.Target = _types[navProp.RelatedClrType] as IEdmEntityType;
//TODO: If target end has a multiplity of 1 this assumes the source end is 0..1.
// I think a better default multiplicity is *
IEdmProperty edmProperty = type.AddUnidirectionalNavigation(info);
if (navProp.PropertyInfo != null && edmProperty != null)
{
_properties[navProp.PropertyInfo] = edmProperty;
}
if (edmProperty != null && navProp.IsRestricted)
{
_propertiesRestrictions[edmProperty] = new QueryableRestrictions(navProp);
}
}
}
/// <summary>
/// Builds <see cref="IEdmType"/> and <see cref="IEdmProperty"/>'s from <paramref name="configurations"/>
/// </summary>
/// <param name="configurations">A collection of <see cref="StructuralTypeConfiguration"/>'s</param>
/// <returns>The built dictionary of <see cref="StructuralTypeConfiguration"/>'s indexed by their backing CLR type,
/// and dictionary of <see cref="StructuralTypeConfiguration"/>'s indexed by their backing CLR property info</returns>
public static EdmTypeMap GetTypesAndProperties(IEnumerable<StructuralTypeConfiguration> configurations)
{
if (configurations == null)
{
throw Error.ArgumentNull("configurations");
}
EdmTypeBuilder builder = new EdmTypeBuilder(configurations);
return new EdmTypeMap(builder.GetEdmTypes(), builder._properties, builder._propertiesRestrictions);
}
/// <summary>
/// Gets the <see cref="EdmPrimitiveTypeKind"/> that maps to the <see cref="Type"/>
/// </summary>
/// <param name="clrType">The clr type</param>
/// <returns>The corresponding Edm primitive kind.</returns>
public static EdmPrimitiveTypeKind GetTypeKind(Type clrType)
{
IEdmPrimitiveType primitiveType = EdmLibHelpers.GetEdmPrimitiveTypeOrNull(clrType);
if (primitiveType == null)
{
throw Error.Argument("clrType", SRResources.MustBePrimitiveType, clrType.FullName);
}
return primitiveType.PrimitiveKind;
}
}
}

Просмотреть файл

@ -0,0 +1,109 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
namespace System.Web.Http.OData.Builder
{
internal static class EdmTypeConfigurationExtensions
{
// returns all the properties declared in the base types of this type.
public static IEnumerable<PropertyConfiguration> DerivedProperties(this EntityTypeConfiguration entity)
{
if (entity == null)
{
throw Error.ArgumentNull("entity");
}
EntityTypeConfiguration baseType = entity.BaseType;
while (baseType != null)
{
foreach (PropertyConfiguration property in baseType.Properties)
{
yield return property;
}
baseType = baseType.BaseType;
}
}
// returns the keys declared or inherited for this entity
public static IEnumerable<PropertyConfiguration> Keys(this EntityTypeConfiguration entity)
{
Contract.Assert(entity != null);
return entity.BaseType == null ? entity.Keys : Keys(entity.BaseType);
}
// Returns the base types, this type.
public static IEnumerable<EntityTypeConfiguration> ThisAndBaseTypes(this EntityTypeConfiguration entity)
{
Contract.Assert(entity != null);
return entity.BaseTypes().Concat(new[] { entity });
}
// Returns the base types, this type and all the derived types of this type.
public static IEnumerable<EntityTypeConfiguration> ThisAndBaseAndDerivedTypes(this ODataModelBuilder modelBuilder, EntityTypeConfiguration entity)
{
Contract.Assert(modelBuilder != null);
Contract.Assert(entity != null);
return entity.BaseTypes()
.Concat(new[] { entity })
.Concat(modelBuilder.DerivedTypes(entity));
}
// Returns the base types for this type.
public static IEnumerable<EntityTypeConfiguration> BaseTypes(this EntityTypeConfiguration entity)
{
Contract.Assert(entity != null);
entity = entity.BaseType;
while (entity != null)
{
yield return entity;
entity = entity.BaseType;
}
}
// Returns all the derived types of this type.
public static IEnumerable<EntityTypeConfiguration> DerivedTypes(this ODataModelBuilder modelBuilder, EntityTypeConfiguration entity)
{
if (modelBuilder == null)
{
throw Error.ArgumentNull("modelBuilder");
}
if (entity == null)
{
throw Error.ArgumentNull("entity");
}
IEnumerable<EntityTypeConfiguration> derivedEntities = modelBuilder.StructuralTypes.OfType<EntityTypeConfiguration>().Where(e => e.BaseType == entity);
foreach (EntityTypeConfiguration derivedEntity in derivedEntities)
{
yield return derivedEntity;
foreach (EntityTypeConfiguration derivedDerivedEntity in modelBuilder.DerivedTypes(derivedEntity))
{
yield return derivedDerivedEntity;
}
}
}
public static bool IsAssignableFrom(this EntityTypeConfiguration baseEntity, EntityTypeConfiguration entity)
{
while (entity != null)
{
if (baseEntity == entity)
{
return true;
}
entity = entity.BaseType;
}
return false;
}
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
internal class EdmTypeMap
{
public EdmTypeMap(
Dictionary<Type, IEdmStructuredType> edmTypes,
Dictionary<PropertyInfo, IEdmProperty> edmProperties,
Dictionary<IEdmProperty, QueryableRestrictions> edmPropertiesRestrictions)
{
EdmTypes = edmTypes;
EdmProperties = edmProperties;
EdmPropertiesRestrictions = edmPropertiesRestrictions;
}
public Dictionary<Type, IEdmStructuredType> EdmTypes { get; private set; }
public Dictionary<PropertyInfo, IEdmProperty> EdmProperties { get; private set; }
public Dictionary<IEdmProperty, QueryableRestrictions> EdmPropertiesRestrictions { get; private set; }
}
}

Просмотреть файл

@ -0,0 +1,72 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.Contracts;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// EntityCollectionConfiguration represents a Collection of Entities.
/// This class can be used to configure things that get bound to entities, like Actions bound to a collection.
/// </summary>
/// <typeparam name="TEntityType">The EntityType that is the ElementType of the EntityCollection</typeparam>
public class EntityCollectionConfiguration<TEntityType> : CollectionTypeConfiguration
{
internal EntityCollectionConfiguration(EntityTypeConfiguration elementType)
: base(elementType, typeof(IEnumerable<TEntityType>))
{
}
/// <summary>
/// Creates a new Action that binds to Collection(EntityType).
/// </summary>
/// <param name="name">The name of the Action</param>
/// <returns>An <see cref="ActionConfiguration"/> to allow further configuration of the Action.</returns>
public ActionConfiguration Action(string name)
{
Contract.Assert(ModelBuilder != null);
ActionConfiguration configuration = ModelBuilder.Action(name);
configuration.SetBindingParameter(BindingParameterConfiguration.DefaultBindingParameterName, this, alwaysBindable: true);
return configuration;
}
/// <summary>
/// Creates a new Function that binds to Collection(EntityType).
/// </summary>
/// <param name="name">The name of the Function</param>
/// <returns>A <see cref="FunctionConfiguration"/> to allow further configuration of the Function.</returns>
public FunctionConfiguration Function(string name)
{
Contract.Assert(ModelBuilder != null);
FunctionConfiguration configuration = ModelBuilder.Function(name);
configuration.SetBindingParameter(BindingParameterConfiguration.DefaultBindingParameterName, this, alwaysBindable: true);
return configuration;
}
/// <summary>
/// Creates a new Action that sometimes binds to Collection(EntityType).
/// </summary>
/// <param name="name">The name of the Action</param>
/// <returns>An <see cref="ActionConfiguration"/> to allow further configuration of the Action.</returns>
public ActionConfiguration TransientAction(string name)
{
Contract.Assert(ModelBuilder != null);
ActionConfiguration configuration = ModelBuilder.Action(name);
configuration.SetBindingParameter(BindingParameterConfiguration.DefaultBindingParameterName, this, alwaysBindable: false);
return configuration;
}
/// <summary>
/// Creates a new Function that sometimes binds to Collection(EntityType).
/// </summary>
/// <param name="name">The name of the Function</param>
/// <returns>A <see cref="FunctionConfiguration"/> to allow further configuration of the Function.</returns>
public FunctionConfiguration TransientFunction(string name)
{
Contract.Assert(ModelBuilder != null);
FunctionConfiguration configuration = ModelBuilder.Function(name);
configuration.SetBindingParameter(BindingParameterConfiguration.DefaultBindingParameterName, this, alwaysBindable: false);
return configuration;
}
}
}

Просмотреть файл

@ -0,0 +1,419 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Web.Http.OData.Properties;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Allows configuration to be performed for a entity set in a model.
/// A <see cref="EntitySetConfiguration"/> can be obtained by using the method <see cref="ODataModelBuilder.EntitySet"/>.
/// </summary>
public class EntitySetConfiguration
{
private readonly ODataModelBuilder _modelBuilder;
private readonly Dictionary<NavigationPropertyConfiguration, NavigationPropertyBindingConfiguration> _entitySetBindings;
private string _url;
private Func<FeedContext, Uri> _feedSelfLinkFactory;
private SelfLinkBuilder<Uri> _editLinkBuilder;
private SelfLinkBuilder<Uri> _readLinkBuilder;
private SelfLinkBuilder<string> _idLinkBuilder;
private readonly Dictionary<NavigationPropertyConfiguration, NavigationLinkBuilder> _navigationPropertyLinkBuilders;
/// <summary>
/// Initializes a new instance of the <see cref="EntitySetConfiguration"/> class.
/// </summary>
/// <remarks>The default constructor is intended for use by unit testing only.</remarks>
public EntitySetConfiguration()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EntitySetConfiguration"/> class.
/// <param name="modelBuilder">The <see cref="ODataModelBuilder"/>.</param>
/// <param name="entityType">The CLR <see cref="Type"/> of the entity type contained in this entity set.</param>
/// <param name="name">The name of the entity set.</param>
/// </summary>
public EntitySetConfiguration(ODataModelBuilder modelBuilder, Type entityType, string name)
: this(modelBuilder, new EntityTypeConfiguration(modelBuilder, entityType), name)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EntitySetConfiguration"/> class.
/// </summary>
/// <param name="modelBuilder">The <see cref="ODataModelBuilder"/>.</param>
/// <param name="entityType">The entity type contained in this entity set.</param>
/// <param name="name">The name of the entity set.</param>
[SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "The property being set, ClrType, is on a different object.")]
public EntitySetConfiguration(ODataModelBuilder modelBuilder, EntityTypeConfiguration entityType, string name)
{
if (modelBuilder == null)
{
throw Error.ArgumentNull("modelBuilder");
}
if (entityType == null)
{
throw Error.ArgumentNull("entityType");
}
if (name == null)
{
throw Error.ArgumentNull("name");
}
_modelBuilder = modelBuilder;
Name = name;
EntityType = entityType;
ClrType = entityType.ClrType;
_url = Name;
_editLinkBuilder = null;
_readLinkBuilder = null;
_navigationPropertyLinkBuilders = new Dictionary<NavigationPropertyConfiguration, NavigationLinkBuilder>();
_entitySetBindings = new Dictionary<NavigationPropertyConfiguration, NavigationPropertyBindingConfiguration>();
}
/// <summary>
/// Gets the navigation targets of this entity set.
/// </summary>
public virtual IEnumerable<NavigationPropertyBindingConfiguration> Bindings
{
get
{
return _entitySetBindings.Values;
}
}
/// <summary>
/// Gets the entity type contained in this entity set.
/// </summary>
public virtual EntityTypeConfiguration EntityType { get; private set; }
/// <summary>
/// Gets the backing clr type for the entity type contained in this entity set.
/// </summary>
public virtual Type ClrType { get; private set; }
/// <summary>
/// Gets the name of this entity set.
/// </summary>
public virtual string Name { get; private set; }
/// <summary>
/// Configures the entity set URL.
/// </summary>
/// <param name="url">The entity set URL.</param>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "This Url property is not required to be a valid Uri")]
public virtual EntitySetConfiguration HasUrl(string url)
{
_url = url;
return this;
}
/// <summary>
/// Adds a self link to the feed.
/// </summary>
/// <param name="feedSelfLinkFactory">The builder used to generate the link URL.</param>
/// <returns>The entity set configuration currently being configured.</returns>
public virtual EntitySetConfiguration HasFeedSelfLink(Func<FeedContext, Uri> feedSelfLinkFactory)
{
_feedSelfLinkFactory = feedSelfLinkFactory;
return this;
}
/// <summary>
/// Configures the edit link for the entities from this entity set.
/// </summary>
/// <param name="editLinkBuilder">The builder used to generate the edit link.</param>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public virtual EntitySetConfiguration HasEditLink(SelfLinkBuilder<Uri> editLinkBuilder)
{
if (editLinkBuilder == null)
{
throw Error.ArgumentNull("editLinkBuilder");
}
_editLinkBuilder = editLinkBuilder;
return this;
}
/// <summary>
/// Configures the read link for the entities from this entity set.
/// </summary>
/// <param name="readLinkBuilder">The builder used to generate the read link.</param>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public virtual EntitySetConfiguration HasReadLink(SelfLinkBuilder<Uri> readLinkBuilder)
{
if (readLinkBuilder == null)
{
throw Error.ArgumentNull("readLinkBuilder");
}
_readLinkBuilder = readLinkBuilder;
return this;
}
/// <summary>
/// Configures the ID for the entities from this entity set.
/// </summary>
/// <param name="idLinkBuilder">The builder used to generate the ID.</param>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public virtual EntitySetConfiguration HasIdLink(SelfLinkBuilder<string> idLinkBuilder)
{
if (idLinkBuilder == null)
{
throw Error.ArgumentNull("idLinkBuilder");
}
_idLinkBuilder = idLinkBuilder;
return this;
}
/// <summary>
/// Configures the navigation link for the given navigation property for entities from this entity set.
/// </summary>
/// <param name="navigationProperty">The navigation property for which the navigation link is being generated.</param>
/// <param name="navigationLinkBuilder">The builder used to generate the navigation link.</param>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public virtual EntitySetConfiguration HasNavigationPropertyLink(NavigationPropertyConfiguration navigationProperty, NavigationLinkBuilder navigationLinkBuilder)
{
if (navigationProperty == null)
{
throw Error.ArgumentNull("navigationProperty");
}
if (navigationLinkBuilder == null)
{
throw Error.ArgumentNull("navigationLinkBuilder");
}
EntityTypeConfiguration declaringEntityType = navigationProperty.DeclaringEntityType;
if (!(declaringEntityType.IsAssignableFrom(EntityType) || EntityType.IsAssignableFrom(declaringEntityType)))
{
throw Error.Argument("navigationProperty", SRResources.NavigationPropertyNotInHierarchy, declaringEntityType.FullName, EntityType.FullName, Name);
}
_navigationPropertyLinkBuilders[navigationProperty] = navigationLinkBuilder;
return this;
}
/// <summary>
/// Configures the navigation link for the given navigation properties for entities from this entity set.
/// </summary>
/// <param name="navigationProperties">The navigation properties for which the navigation link is being generated.</param>
/// <param name="navigationLinkBuilder">The builder used to generate the navigation link.</param>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public virtual EntitySetConfiguration HasNavigationPropertiesLink(IEnumerable<NavigationPropertyConfiguration> navigationProperties, NavigationLinkBuilder navigationLinkBuilder)
{
if (navigationProperties == null)
{
throw Error.ArgumentNull("navigationProperties");
}
if (navigationLinkBuilder == null)
{
throw Error.ArgumentNull("navigationLinkBuilder");
}
foreach (NavigationPropertyConfiguration navigationProperty in navigationProperties)
{
HasNavigationPropertyLink(navigationProperty, navigationLinkBuilder);
}
return this;
}
/// <summary>
/// Binds the given navigation property to the target entity set.
/// </summary>
/// <param name="navigationConfiguration">The navigation property.</param>
/// <param name="targetEntitySet">The target entity set.</param>
/// <returns>The <see cref="NavigationPropertyBindingConfiguration"/> so that it can be further configured.</returns>
public virtual NavigationPropertyBindingConfiguration AddBinding(NavigationPropertyConfiguration navigationConfiguration, EntitySetConfiguration targetEntitySet)
{
if (navigationConfiguration == null)
{
throw Error.ArgumentNull("navigationConfiguration");
}
if (targetEntitySet == null)
{
throw Error.ArgumentNull("targetEntitySet");
}
EntityTypeConfiguration declaringEntityType = navigationConfiguration.DeclaringEntityType;
if (!(declaringEntityType.IsAssignableFrom(EntityType) || EntityType.IsAssignableFrom(declaringEntityType)))
{
throw Error.Argument("navigationConfiguration", SRResources.NavigationPropertyNotInHierarchy, declaringEntityType.FullName, EntityType.FullName, Name);
}
NavigationPropertyBindingConfiguration navigationPropertyBinding = null;
if (_entitySetBindings.ContainsKey(navigationConfiguration))
{
navigationPropertyBinding = _entitySetBindings[navigationConfiguration];
if (navigationPropertyBinding.EntitySet != targetEntitySet)
{
throw Error.NotSupported(SRResources.RebindingNotSupported);
}
}
else
{
navigationPropertyBinding = new NavigationPropertyBindingConfiguration(navigationConfiguration, targetEntitySet);
_entitySetBindings[navigationConfiguration] = navigationPropertyBinding;
}
return navigationPropertyBinding;
}
/// <summary>
/// Removes the binding for the given navigation property.
/// </summary>
/// <param name="navigationConfiguration">The navigation property</param>
public virtual void RemoveBinding(NavigationPropertyConfiguration navigationConfiguration)
{
if (_entitySetBindings.ContainsKey(navigationConfiguration))
{
_entitySetBindings.Remove(navigationConfiguration);
}
}
/// <summary>
/// Finds the binding for the given navigation property and tries to create it if it doesnot exist.
/// </summary>
/// <param name="navigationConfiguration">The navigation property.</param>
/// <returns>The <see cref="NavigationPropertyBindingConfiguration"/> so that it can be further configured.</returns>
public virtual NavigationPropertyBindingConfiguration FindBinding(NavigationPropertyConfiguration navigationConfiguration)
{
return FindBinding(navigationConfiguration, autoCreate: true);
}
/// <summary>
/// Finds the binding for the given navigation property.
/// </summary>
/// <param name="autoCreate">Tells whether the binding should be auto created if it does not exist.</param>
/// <param name="navigationConfiguration">The navigation property.</param>
/// <returns>The <see cref="NavigationPropertyBindingConfiguration"/> so that it can be further configured.</returns>
public virtual NavigationPropertyBindingConfiguration FindBinding(NavigationPropertyConfiguration navigationConfiguration, bool autoCreate)
{
if (navigationConfiguration == null)
{
throw Error.ArgumentNull("navigationConfiguration");
}
if (_entitySetBindings.ContainsKey(navigationConfiguration))
{
return _entitySetBindings[navigationConfiguration];
}
if (!autoCreate)
{
return null;
}
Type entityType = navigationConfiguration.RelatedClrType;
EntitySetConfiguration[] matchingSets = _modelBuilder.EntitySets.Where(es => es.EntityType.ClrType == entityType).ToArray();
if (matchingSets.Count() == 1)
{
return AddBinding(navigationConfiguration, matchingSets[0]);
}
else if (!matchingSets.Any())
{
return null;
}
else
{
throw Error.NotSupported(
SRResources.CannotAutoCreateMultipleCandidates,
navigationConfiguration.Name,
navigationConfiguration.DeclaringEntityType.FullName,
Name,
String.Join(", ", matchingSets.Select(entitySet => entitySet.Name)));
}
}
/// <summary>
/// Gets the entity set URL.
/// </summary>
/// <returns>The entity set URL.</returns>
[SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "This Url property is not required to be a valid Uri")]
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Consistent with EF Has/Get pattern")]
public virtual string GetUrl()
{
return _url;
}
/// <summary>
/// Gets the builder used to generate self links for feeds for this entity set.
/// </summary>
/// <returns>The link builder.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Consistent with EF Has/Get pattern")]
public virtual Func<FeedContext, Uri> GetFeedSelfLink()
{
return _feedSelfLinkFactory;
}
/// <summary>
/// Gets the builder used to generate edit links for entries from this entity set.
/// </summary>
/// <returns>The link builder.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Consistent with EF Has/Get pattern")]
public virtual SelfLinkBuilder<Uri> GetEditLink()
{
return _editLinkBuilder;
}
/// <summary>
/// Gets the builder used to generate read links for entries from this entity set.
/// </summary>
/// <returns>The link builder.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Consistent with EF Has/Get pattern")]
public virtual SelfLinkBuilder<Uri> GetReadLink()
{
return _readLinkBuilder;
}
/// <summary>
/// Gets the builder used to generate ID for entries from this entity set.
/// </summary>
/// <returns>The builder.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Consistent with EF Has/Get pattern")]
public virtual SelfLinkBuilder<string> GetIdLink()
{
return _idLinkBuilder;
}
/// <summary>
/// Gets the builder used to generate navigation link for the given navigation property for entries from this entity set.
/// </summary>
/// <param name="navigationProperty">The navigation property.</param>
/// <returns>The link builder.</returns>
public virtual NavigationLinkBuilder GetNavigationPropertyLink(NavigationPropertyConfiguration navigationProperty)
{
if (navigationProperty == null)
{
throw Error.ArgumentNull("navigationProperty");
}
NavigationLinkBuilder navigationPropertyLinkBuilder;
_navigationPropertyLinkBuilders.TryGetValue(navigationProperty, out navigationPropertyLinkBuilder);
return navigationPropertyLinkBuilder;
}
/// <summary>
/// Gets the <see cref="NavigationPropertyBindingConfiguration"/> for the navigation property with the given name.
/// </summary>
/// <param name="propertyName">The name of the navigation property.</param>
/// <returns>The <see cref="NavigationPropertyBindingConfiguration" />.</returns>
public virtual NavigationPropertyBindingConfiguration FindBinding(string propertyName)
{
return Bindings.Single(b => b.NavigationProperty.Name == propertyName);
}
}
}

Просмотреть файл

@ -0,0 +1,566 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Represents an <see cref="IEdmEntitySet"/> that can be built using <see cref="ODataModelBuilder"/>.
/// <typeparam name="TEntityType">The element type of the entity set.</typeparam>
/// </summary>
public class EntitySetConfiguration<TEntityType> where TEntityType : class
{
private EntitySetConfiguration _configuration;
private EntityTypeConfiguration<TEntityType> _entityType;
private ODataModelBuilder _modelBuilder;
internal EntitySetConfiguration(ODataModelBuilder modelBuilder, string name)
: this(modelBuilder, new EntitySetConfiguration(modelBuilder, typeof(TEntityType), name))
{
}
internal EntitySetConfiguration(ODataModelBuilder modelBuilder, EntitySetConfiguration configuration)
{
if (modelBuilder == null)
{
throw Error.ArgumentNull("modelBuilder");
}
if (configuration == null)
{
throw Error.ArgumentNull("configuration");
}
_configuration = configuration;
_modelBuilder = modelBuilder;
_entityType = new EntityTypeConfiguration<TEntityType>(modelBuilder, _configuration.EntityType);
}
internal EntitySetConfiguration EntitySet
{
get { return _configuration; }
}
/// <summary>
/// Gets the entity type contained in this entity set.
/// </summary>
public EntityTypeConfiguration<TEntityType> EntityType
{
get
{
return _entityType;
}
}
/// <summary>
/// Configures a many relationship from this entity type and binds the corresponding navigation property to the given entity set.
/// </summary>
/// <typeparam name="TTargetType">The target entity set type.</typeparam>
/// <typeparam name="TDerivedEntityType">The target entity type.</typeparam>
/// <param name="navigationExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <param name="entitySetName">The target entity set name for the binding. It will be created if it does not exist.</param>
/// <returns>A configuration object that can be used to further configure the relationship further.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public NavigationPropertyBindingConfiguration HasManyBinding<TTargetType, TDerivedEntityType>(
Expression<Func<TDerivedEntityType, IEnumerable<TTargetType>>> navigationExpression, string entitySetName)
where TTargetType : class
where TDerivedEntityType : class, TEntityType
{
if (navigationExpression == null)
{
throw Error.ArgumentNull("navigationExpression");
}
EntityTypeConfiguration<TDerivedEntityType> derivedEntityType =
_modelBuilder.Entity<TDerivedEntityType>().DerivesFrom<TEntityType>();
return _configuration.AddBinding(derivedEntityType.HasMany(navigationExpression), _modelBuilder.EntitySet<TTargetType>(entitySetName)._configuration);
}
/// <summary>
/// Configures a many relationship from this entity type and binds the corresponding navigation property to the given entity set.
/// </summary>
/// <typeparam name="TTargetType">The target entity set type.</typeparam>
/// <param name="navigationExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <param name="entitySetName">The target entity set name for the binding. It will be created if it does not exist.</param>
/// <returns>A configuration object that can be used to further configure the relationship.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public NavigationPropertyBindingConfiguration HasManyBinding<TTargetType>(
Expression<Func<TEntityType, IEnumerable<TTargetType>>> navigationExpression, string entitySetName)
where TTargetType : class
{
if (navigationExpression == null)
{
throw Error.ArgumentNull("navigationExpression");
}
return _configuration.AddBinding(EntityType.HasMany(navigationExpression), _modelBuilder.EntitySet<TTargetType>(entitySetName)._configuration);
}
/// <summary>
/// Configures a many relationship from this entity type and binds the corresponding navigation property to the given entity set.
/// </summary>
/// <typeparam name="TTargetType">The target entity set type.</typeparam>
/// <param name="navigationExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <param name="targetSet">The target entity set for the binding.</param>
/// <returns>A configuration object that can be used to further configure the relationship further.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public NavigationPropertyBindingConfiguration HasManyBinding<TTargetType>(
Expression<Func<TEntityType, IEnumerable<TTargetType>>> navigationExpression,
EntitySetConfiguration<TTargetType> targetSet) where TTargetType : class
{
if (navigationExpression == null)
{
throw Error.ArgumentNull("navigationExpression");
}
if (targetSet == null)
{
throw Error.ArgumentNull("targetSet");
}
return _configuration.AddBinding(EntityType.HasMany(navigationExpression), targetSet._configuration);
}
/// <summary>
/// Configures a many relationship from this entity type and binds the corresponding navigation property to the given entity set.
/// </summary>
/// <typeparam name="TTargetType">The target entity set type.</typeparam>
/// <typeparam name="TDerivedEntityType">The target entity type.</typeparam>
/// <param name="navigationExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <param name="targetSet">The target entity set for the binding.</param>
/// <returns>A configuration object that can be used to further configure the relationship further.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public NavigationPropertyBindingConfiguration HasManyBinding<TTargetType, TDerivedEntityType>(
Expression<Func<TDerivedEntityType, IEnumerable<TTargetType>>> navigationExpression,
EntitySetConfiguration<TTargetType> targetSet)
where TTargetType : class
where TDerivedEntityType : class, TEntityType
{
if (navigationExpression == null)
{
throw Error.ArgumentNull("navigationExpression");
}
if (targetSet == null)
{
throw Error.ArgumentNull("targetSet");
}
EntityTypeConfiguration<TDerivedEntityType> derivedEntityType =
_modelBuilder.Entity<TDerivedEntityType>().DerivesFrom<TEntityType>();
return _configuration.AddBinding(derivedEntityType.HasMany(navigationExpression), targetSet._configuration);
}
/// <summary>
/// Configures a required relationship from this entity type and binds the corresponding navigation property to the given entity set.
/// </summary>
/// <typeparam name="TTargetType">The target entity set type.</typeparam>
/// <param name="navigationExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <param name="entitySetName">The target entity set name for the binding.</param>
/// <returns>A configuration object that can be used to further configure the relationship further.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public NavigationPropertyBindingConfiguration HasRequiredBinding<TTargetType>(
Expression<Func<TEntityType, TTargetType>> navigationExpression, string entitySetName)
where TTargetType : class
{
if (navigationExpression == null)
{
throw Error.ArgumentNull("navigationExpression");
}
return _configuration.AddBinding(EntityType.HasRequired(navigationExpression), _modelBuilder.EntitySet<TTargetType>(entitySetName)._configuration);
}
/// <summary>
/// Configures a required relationship from this entity type and binds the corresponding navigation property to the given entity set.
/// </summary>
/// <typeparam name="TTargetType">The target entity set type.</typeparam>
/// <typeparam name="TDerivedEntityType">The target entity type.</typeparam>
/// <param name="navigationExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <param name="entitySetName">The target entity set name for the binding.</param>
/// <returns>A configuration object that can be used to further configure the relationship further.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public NavigationPropertyBindingConfiguration HasRequiredBinding<TTargetType, TDerivedEntityType>(
Expression<Func<TDerivedEntityType, TTargetType>> navigationExpression, string entitySetName)
where TTargetType : class
where TDerivedEntityType : class, TEntityType
{
if (navigationExpression == null)
{
throw Error.ArgumentNull("navigationExpression");
}
EntityTypeConfiguration<TDerivedEntityType> derivedEntityType =
_modelBuilder.Entity<TDerivedEntityType>().DerivesFrom<TEntityType>();
return _configuration.AddBinding(derivedEntityType.HasRequired(navigationExpression), _modelBuilder.EntitySet<TTargetType>(entitySetName)._configuration);
}
/// <summary>
/// Configures a required relationship from this entity type and binds the corresponding navigation property to the given entity set.
/// </summary>
/// <typeparam name="TTargetType">The target entity set type.</typeparam>
/// <param name="navigationExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <param name="targetSet">The target entity set for the binding.</param>
/// <returns>A configuration object that can be used to further configure the relationship further.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public NavigationPropertyBindingConfiguration HasRequiredBinding<TTargetType>(
Expression<Func<TEntityType, TTargetType>> navigationExpression,
EntitySetConfiguration<TTargetType> targetSet) where TTargetType : class
{
if (navigationExpression == null)
{
throw Error.ArgumentNull("navigationExpression");
}
if (targetSet == null)
{
throw Error.ArgumentNull("targetSet");
}
return _configuration.AddBinding(EntityType.HasRequired(navigationExpression), targetSet._configuration);
}
/// <summary>
/// Configures a required relationship from this entity type and binds the corresponding navigation property to the given entity set.
/// </summary>
/// <typeparam name="TTargetType">The target entity set type.</typeparam>
/// <typeparam name="TDerivedEntityType">The target entity type.</typeparam>
/// <param name="navigationExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <param name="targetSet">The target entity set for the binding.</param>
/// <returns>A configuration object that can be used to further configure the relationship further.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public NavigationPropertyBindingConfiguration HasRequiredBinding<TTargetType, TDerivedEntityType>(
Expression<Func<TDerivedEntityType, TTargetType>> navigationExpression,
EntitySetConfiguration<TTargetType> targetSet)
where TTargetType : class
where TDerivedEntityType : class, TEntityType
{
if (navigationExpression == null)
{
throw Error.ArgumentNull("navigationExpression");
}
if (targetSet == null)
{
throw Error.ArgumentNull("targetSet");
}
EntityTypeConfiguration<TDerivedEntityType> derivedEntityType =
_modelBuilder.Entity<TDerivedEntityType>().DerivesFrom<TEntityType>();
return _configuration.AddBinding(derivedEntityType.HasRequired(navigationExpression), targetSet._configuration);
}
/// <summary>
/// Configures an optional relationship from this entity type and binds the corresponding navigation property to the given entity set.
/// </summary>
/// <typeparam name="TTargetType">The target entity set type.</typeparam>
/// <param name="navigationExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <param name="entitySetName">The target entity set name for the binding.</param>
/// <returns>A configuration object that can be used to further configure the relationship further.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public NavigationPropertyBindingConfiguration HasOptionalBinding<TTargetType>(
Expression<Func<TEntityType, TTargetType>> navigationExpression, string entitySetName)
where TTargetType : class
{
if (navigationExpression == null)
{
throw Error.ArgumentNull("navigationExpression");
}
return _configuration.AddBinding(EntityType.HasOptional(navigationExpression), _modelBuilder.EntitySet<TTargetType>(entitySetName)._configuration);
}
/// <summary>
/// Configures an optional relationship from this entity type and binds the corresponding navigation property to the given entity set.
/// </summary>
/// <typeparam name="TTargetType">The target entity set type.</typeparam>
/// <typeparam name="TDerivedEntityType">The target entity type.</typeparam>
/// <param name="navigationExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <param name="entitySetName">The target entity set name for the binding. It will be created if it does not exist.</param>
/// <returns>A configuration object that can be used to further configure the relationship further.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public NavigationPropertyBindingConfiguration HasOptionalBinding<TTargetType, TDerivedEntityType>(
Expression<Func<TDerivedEntityType, TTargetType>> navigationExpression, string entitySetName)
where TTargetType : class
where TDerivedEntityType : class, TEntityType
{
if (navigationExpression == null)
{
throw Error.ArgumentNull("navigationExpression");
}
EntityTypeConfiguration<TDerivedEntityType> derivedEntityType =
_modelBuilder.Entity<TDerivedEntityType>().DerivesFrom<TEntityType>();
return _configuration.AddBinding(derivedEntityType.HasOptional(navigationExpression), _modelBuilder.EntitySet<TTargetType>(entitySetName)._configuration);
}
/// <summary>
/// Configures an optional relationship from this entity type and binds the corresponding navigation property to the given entity set.
/// </summary>
/// <typeparam name="TTargetType">The target entity set type.</typeparam>
/// <param name="navigationExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <param name="targetSet">The target entity set for the binding.</param>
/// <returns>A configuration object that can be used to further configure the relationship further.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public NavigationPropertyBindingConfiguration HasOptionalBinding<TTargetType>(
Expression<Func<TEntityType, TTargetType>> navigationExpression,
EntitySetConfiguration<TTargetType> targetSet) where TTargetType : class
{
if (navigationExpression == null)
{
throw Error.ArgumentNull("navigationExpression");
}
if (targetSet == null)
{
throw Error.ArgumentNull("targetSet");
}
return _configuration.AddBinding(EntityType.HasOptional(navigationExpression), targetSet._configuration);
}
/// <summary>
/// Configures an optional relationship from this entity type and binds the corresponding navigation property to the given entity set.
/// </summary>
/// <typeparam name="TTargetType">The target entity set type.</typeparam>
/// <typeparam name="TDerivedEntityType">The target entity type.</typeparam>
/// <param name="navigationExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <param name="targetSet">The target entity set for the binding.</param>
/// <returns>A configuration object that can be used to further configure the relationship further.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public NavigationPropertyBindingConfiguration HasOptionalBinding<TTargetType, TDerivedEntityType>(
Expression<Func<TDerivedEntityType, TTargetType>> navigationExpression,
EntitySetConfiguration<TTargetType> targetSet)
where TTargetType : class
where TDerivedEntityType : class, TEntityType
{
if (navigationExpression == null)
{
throw Error.ArgumentNull("navigationExpression");
}
if (targetSet == null)
{
throw Error.ArgumentNull("targetSet");
}
EntityTypeConfiguration<TDerivedEntityType> derivedEntityType =
_modelBuilder.Entity<TDerivedEntityType>().DerivesFrom<TEntityType>();
return _configuration.AddBinding(derivedEntityType.HasOptional(navigationExpression), targetSet._configuration);
}
/// <summary>
/// Adds a self link to the feed.
/// </summary>
/// <param name="feedSelfLinkFactory">The builder used to generate the link URL.</param>
public void HasFeedSelfLink(Func<FeedContext, string> feedSelfLinkFactory)
{
if (feedSelfLinkFactory == null)
{
throw Error.ArgumentNull("feedSelfLinkFactory");
}
_configuration.HasFeedSelfLink(feedContext => new Uri(feedSelfLinkFactory(feedContext)));
}
/// <summary>
/// Adds a self link to the feed.
/// </summary>
/// <param name="feedSelfLinkFactory">The builder used to generate the link URL.</param>
public void HasFeedSelfLink(Func<FeedContext, Uri> feedSelfLinkFactory)
{
if (feedSelfLinkFactory == null)
{
throw Error.ArgumentNull("feedSelfLinkFactory");
}
_configuration.HasFeedSelfLink(feedSelfLinkFactory);
}
/// <summary>
/// Configures the edit link for the entities from this entity set.
/// </summary>
/// <param name="editLinkFactory">The factory used to generate the edit link.</param>
/// <param name="followsConventions"><see langword="true"/> if the factory follows OData edit link conventions; otherwise, <see langword="false"/>.</param>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public void HasEditLink(Func<EntityInstanceContext<TEntityType>, string> editLinkFactory, bool followsConventions)
{
if (editLinkFactory == null)
{
throw Error.ArgumentNull("editLinkFactory");
}
HasEditLink(entityInstanceContext => new Uri(editLinkFactory(entityInstanceContext)), followsConventions);
}
/// <summary>
/// Configures the edit link for the entities from this entity set.
/// </summary>
/// <param name="editLinkFactory">The factory used to generate the edit link.</param>
/// <param name="followsConventions"><see langword="true"/> if the factory follows OData edit link conventions; otherwise, <see langword="false"/>.</param>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public void HasEditLink(Func<EntityInstanceContext<TEntityType>, Uri> editLinkFactory, bool followsConventions)
{
if (editLinkFactory == null)
{
throw Error.ArgumentNull("editLinkFactory");
}
_configuration.HasEditLink(new SelfLinkBuilder<Uri>((entity) => editLinkFactory(UpCastEntityInstanceContext(entity)), followsConventions));
}
/// <summary>
/// Configures the read link for the entities from this entity set.
/// </summary>
/// <param name="readLinkFactory">The factory used to generate the read link.</param>
/// <param name="followsConventions"><see langword="true"/> if the factory follows OData read link conventions; otherwise, <see langword="false"/>.</param>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public void HasReadLink(Func<EntityInstanceContext<TEntityType>, string> readLinkFactory, bool followsConventions)
{
if (readLinkFactory == null)
{
throw Error.ArgumentNull("readLinkFactory");
}
HasReadLink(entityInstanceContext => new Uri(readLinkFactory(entityInstanceContext)), followsConventions);
}
/// <summary>
/// Configures the read link for the entities from this entity set.
/// </summary>
/// <param name="readLinkFactory">The factory used to generate the read link.</param>
/// <param name="followsConventions"><see langword="true"/> if the factory follows OData read link conventions; otherwise, <see langword="false"/>.</param>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public void HasReadLink(Func<EntityInstanceContext<TEntityType>, Uri> readLinkFactory, bool followsConventions)
{
if (readLinkFactory == null)
{
throw Error.ArgumentNull("readLinkFactory");
}
_configuration.HasReadLink(new SelfLinkBuilder<Uri>((entity) => readLinkFactory(UpCastEntityInstanceContext(entity)), followsConventions));
}
/// <summary>
/// Configures the ID link for the entities from this entity set.
/// </summary>
/// <param name="idLinkFactory">The factory used to generate the ID link.</param>
/// <param name="followsConventions"><see langword="true"/> if the factory follows OData ID link conventions; otherwise, <see langword="false"/>.</param>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public void HasIdLink(Func<EntityInstanceContext<TEntityType>, string> idLinkFactory, bool followsConventions)
{
if (idLinkFactory == null)
{
throw Error.ArgumentNull("idLinkFactory");
}
_configuration.HasIdLink(new SelfLinkBuilder<string>((entity) => idLinkFactory(UpCastEntityInstanceContext(entity)), followsConventions));
}
/// <summary>
/// Configures the navigation link for the given navigation property for entities from this entity set.
/// </summary>
/// <param name="navigationProperty">The navigation property for which the navigation link is being generated.</param>
/// <param name="navigationLinkFactory">The factory used to generate the navigation link.</param>
/// <param name="followsConventions"><see langword="true"/> if the factory follows OData navigation link conventions; otherwise, <see langword="false"/>.</param>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public void HasNavigationPropertyLink(NavigationPropertyConfiguration navigationProperty, Func<EntityInstanceContext<TEntityType>, IEdmNavigationProperty, Uri> navigationLinkFactory, bool followsConventions)
{
if (navigationProperty == null)
{
throw Error.ArgumentNull("navigationProperty");
}
if (navigationLinkFactory == null)
{
throw Error.ArgumentNull("navigationLinkFactory");
}
_configuration.HasNavigationPropertyLink(navigationProperty, new NavigationLinkBuilder((entity, property) => navigationLinkFactory(UpCastEntityInstanceContext(entity), property), followsConventions));
}
/// <summary>
/// Configures the navigation link for the given navigation properties for entities from this entity set.
/// </summary>
/// <param name="navigationProperties">The navigation properties for which the navigation link is being generated.</param>
/// <param name="navigationLinkFactory">The factory used to generate the navigation link.</param>
/// <param name="followsConventions"><see langword="true"/> if the factory follows OData navigation link conventions; otherwise, <see langword="false"/>.</param>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
public void HasNavigationPropertiesLink(IEnumerable<NavigationPropertyConfiguration> navigationProperties, Func<EntityInstanceContext<TEntityType>, IEdmNavigationProperty, Uri> navigationLinkFactory, bool followsConventions)
{
if (navigationProperties == null)
{
throw Error.ArgumentNull("navigationProperties");
}
if (navigationLinkFactory == null)
{
throw Error.ArgumentNull("navigationLinkFactory");
}
_configuration.HasNavigationPropertiesLink(navigationProperties, new NavigationLinkBuilder((entity, property) => navigationLinkFactory(UpCastEntityInstanceContext(entity), property), followsConventions));
}
/// <summary>
/// Finds the <see cref="NavigationPropertyBindingConfiguration"/> for the navigation property with the given name.
/// </summary>
/// <param name="propertyName">The name of the navigation property.</param>
/// <returns>The binding, if found; otherwise, <see langword="null"/>.</returns>
public NavigationPropertyBindingConfiguration FindBinding(string propertyName)
{
return _configuration.FindBinding(propertyName);
}
/// <summary>
/// Finds the <see cref="NavigationPropertyBindingConfiguration"/> for the given navigation property and creates it if it does not exist.
/// </summary>
/// <param name="navigationConfiguration">The navigation property.</param>
/// <returns>The binding if found else the created binding.</returns>
public NavigationPropertyBindingConfiguration FindBinding(NavigationPropertyConfiguration navigationConfiguration)
{
return _configuration.FindBinding(navigationConfiguration, autoCreate: true);
}
/// <summary>
/// Finds the <see cref="NavigationPropertyBindingConfiguration"/> for the given navigation property.
/// </summary>
/// <param name="navigationConfiguration">The navigation property.</param>
/// <param name="autoCreate">Represents a value specifying if the binding should be created if it is not found.</param>
/// <returns>The binding if found.</returns>
public NavigationPropertyBindingConfiguration FindBinding(NavigationPropertyConfiguration navigationConfiguration, bool autoCreate)
{
return _configuration.FindBinding(navigationConfiguration, autoCreate);
}
private static EntityInstanceContext<TEntityType> UpCastEntityInstanceContext(EntityInstanceContext context)
{
return new EntityInstanceContext<TEntityType>
{
SerializerContext = context.SerializerContext,
EdmObject = context.EdmObject,
EntityType = context.EntityType
};
}
}
}

Просмотреть файл

@ -0,0 +1,293 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Web.Http.OData.Formatter;
using System.Web.Http.OData.Formatter.Serialization;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// <see cref="EntitySetLinkBuilderAnnotation" /> is a class used to annotate an <see cref="IEdmEntitySet" /> inside an <see cref="IEdmModel" />
/// with information about how to build links related to that entity set.
/// </summary>
public class EntitySetLinkBuilderAnnotation
{
private readonly Func<FeedContext, Uri> _feedSelfLinkBuilder;
private readonly SelfLinkBuilder<string> _idLinkBuilder;
private readonly SelfLinkBuilder<Uri> _editLinkBuilder;
private readonly SelfLinkBuilder<Uri> _readLinkBuilder;
private readonly Dictionary<IEdmNavigationProperty, NavigationLinkBuilder> _navigationPropertyLinkBuilderLookup = new Dictionary<IEdmNavigationProperty, NavigationLinkBuilder>();
private readonly string _entitySetName;
/// <summary>
/// Initializes a new instance of the <see cref="EntitySetLinkBuilderAnnotation" /> class.
/// </summary>
/// <remarks>The default constructor is intended for use by unit testing only.</remarks>
public EntitySetLinkBuilderAnnotation()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EntitySetLinkBuilderAnnotation"/> class.
/// </summary>
/// <param name="entitySet">The entity set for which the link builder is being constructed.</param>
/// <param name="model">The EDM model that this entity set belongs to.</param>
/// <remarks>This constructor creates a link builder that generates URL's that follow OData conventions for the given entity set.</remarks>
public EntitySetLinkBuilderAnnotation(IEdmEntitySet entitySet, IEdmModel model)
{
if (entitySet == null)
{
throw Error.ArgumentNull("entitySet");
}
if (model == null)
{
throw Error.ArgumentNull("model");
}
IEdmEntityType elementType = entitySet.ElementType;
IEnumerable<IEdmEntityType> derivedTypes = model.FindAllDerivedTypes(elementType).Cast<IEdmEntityType>();
bool derivedTypesDefineNavigationProperty = false;
// Add navigation link builders for all navigation properties of entity.
foreach (IEdmNavigationProperty navigationProperty in elementType.NavigationProperties())
{
Func<EntityInstanceContext, IEdmNavigationProperty, Uri> navigationLinkFactory =
(entityInstanceContext, navProperty) => entityInstanceContext.GenerateNavigationPropertyLink(navProperty, includeCast: false);
AddNavigationPropertyLinkBuilder(navigationProperty, new NavigationLinkBuilder(navigationLinkFactory, followsConventions: true));
}
// Add navigation link builders for all navigation properties in derived types.
foreach (IEdmEntityType derivedEntityType in derivedTypes)
{
foreach (IEdmNavigationProperty navigationProperty in derivedEntityType.DeclaredNavigationProperties())
{
derivedTypesDefineNavigationProperty = true;
Func<EntityInstanceContext, IEdmNavigationProperty, Uri> navigationLinkFactory =
(entityInstanceContext, navProperty) => entityInstanceContext.GenerateNavigationPropertyLink(navProperty, includeCast: true);
AddNavigationPropertyLinkBuilder(navigationProperty, new NavigationLinkBuilder(navigationLinkFactory, followsConventions: true));
}
}
_entitySetName = entitySet.Name;
_feedSelfLinkBuilder = (feedContext) => feedContext.GenerateFeedSelfLink();
Func<EntityInstanceContext, string> selfLinkFactory =
(entityInstanceContext) => entityInstanceContext.GenerateSelfLink(includeCast: derivedTypesDefineNavigationProperty);
_idLinkBuilder = new SelfLinkBuilder<string>(selfLinkFactory, followsConventions: true);
}
/// <summary>
/// Constructs an instance of an <see cref="EntitySetLinkBuilderAnnotation" />.
/// <remarks>Every parameter must be non-null.</remarks>
/// </summary>
public EntitySetLinkBuilderAnnotation(
IEdmEntitySet entitySet,
Func<FeedContext, Uri> feedSelfLinkBuilder,
SelfLinkBuilder<string> idLinkBuilder,
SelfLinkBuilder<Uri> editLinkBuilder,
SelfLinkBuilder<Uri> readLinkBuilder)
{
if (entitySet == null)
{
throw Error.ArgumentNull("entitySet");
}
_entitySetName = entitySet.Name;
_feedSelfLinkBuilder = feedSelfLinkBuilder;
_idLinkBuilder = idLinkBuilder;
_editLinkBuilder = editLinkBuilder;
_readLinkBuilder = readLinkBuilder;
}
/// <summary>
/// Constructs an instance of an <see cref="EntitySetLinkBuilderAnnotation" /> from an <see cref="EntitySetConfiguration" />.
/// </summary>
public EntitySetLinkBuilderAnnotation(EntitySetConfiguration entitySet)
{
if (entitySet == null)
{
throw Error.ArgumentNull("entitySet");
}
_entitySetName = entitySet.Name;
_feedSelfLinkBuilder = entitySet.GetFeedSelfLink();
_idLinkBuilder = entitySet.GetIdLink();
_editLinkBuilder = entitySet.GetEditLink();
_readLinkBuilder = entitySet.GetReadLink();
}
/// <summary>
/// Register a link builder for a <see cref="IEdmNavigationProperty" /> that navigates from Entities in this EntitySet.
/// </summary>
public void AddNavigationPropertyLinkBuilder(IEdmNavigationProperty navigationProperty, NavigationLinkBuilder linkBuilder)
{
_navigationPropertyLinkBuilderLookup[navigationProperty] = linkBuilder;
}
/// <summary>
/// Build a self-link URI given a <see cref="FeedContext" />.
/// </summary>
public virtual Uri BuildFeedSelfLink(FeedContext context)
{
if (context == null)
{
throw Error.ArgumentNull("context");
}
if (_feedSelfLinkBuilder == null)
{
return null;
}
return _feedSelfLinkBuilder(context);
}
/// <summary>
/// Constructs the <see cref="EntitySelfLinks" /> for a particular <see cref="EntityInstanceContext" /> and <see cref="ODataMetadataLevel" />.
/// </summary>
public virtual EntitySelfLinks BuildEntitySelfLinks(EntityInstanceContext instanceContext, ODataMetadataLevel metadataLevel)
{
EntitySelfLinks selfLinks = new EntitySelfLinks();
selfLinks.IdLink = BuildIdLink(instanceContext, metadataLevel);
selfLinks.EditLink = BuildEditLink(instanceContext, metadataLevel, selfLinks.IdLink);
selfLinks.ReadLink = BuildReadLink(instanceContext, metadataLevel, selfLinks.EditLink);
return selfLinks;
}
/// <summary>
/// Constructs the IdLink for a particular <see cref="EntityInstanceContext" /> and <see cref="ODataMetadataLevel" />.
/// </summary>
public virtual string BuildIdLink(EntityInstanceContext instanceContext, ODataMetadataLevel metadataLevel)
{
if (instanceContext == null)
{
throw Error.ArgumentNull("instanceContext");
}
if (_idLinkBuilder == null)
{
throw Error.InvalidOperation(SRResources.NoIdLinkFactoryFound, _entitySetName);
}
if (IsDefaultOrFull(metadataLevel) || (IsMinimal(metadataLevel) && !_idLinkBuilder.FollowsConventions))
{
return _idLinkBuilder.Factory(instanceContext);
}
else
{
// client can infer it and didn't ask for it.
return null;
}
}
/// <summary>
/// Constructs the EditLink URL for a particular <see cref="EntityInstanceContext" /> and <see cref="ODataMetadataLevel" />.
/// </summary>
public virtual Uri BuildEditLink(EntityInstanceContext instanceContext, ODataMetadataLevel metadataLevel, string idLink)
{
if (instanceContext == null)
{
throw Error.ArgumentNull("instanceContext");
}
if (_editLinkBuilder == null)
{
// edit link is the same as id link. emit only in default metadata mode.
if (metadataLevel == ODataMetadataLevel.Default)
{
return new Uri(idLink);
}
}
else if (IsDefaultOrFull(metadataLevel) ||
(IsMinimal(metadataLevel) && !_editLinkBuilder.FollowsConventions))
{
// edit link is the not the same as id link. Generate if the client asked for it (full metadata modes) or
// if the client cannot infer it (not follow conventions).
return _editLinkBuilder.Factory(instanceContext);
}
// client can infer it and didn't ask for it.
return null;
}
/// <summary>
/// Constructs a ReadLink URL for a particular <see cref="EntityInstanceContext" /> and <see cref="ODataMetadataLevel" />.
/// </summary>
public virtual Uri BuildReadLink(EntityInstanceContext instanceContext, ODataMetadataLevel metadataLevel, Uri editLink)
{
if (instanceContext == null)
{
throw Error.ArgumentNull("instanceContext");
}
if (_readLinkBuilder == null)
{
// read link is the same as edit link. emit only in default metadata mode.
if (metadataLevel == ODataMetadataLevel.Default)
{
return editLink;
}
}
else if (IsDefaultOrFull(metadataLevel) ||
(IsMinimal(metadataLevel) && !_readLinkBuilder.FollowsConventions))
{
// read link is not the same as edit link. Generate if the client asked for it (full metadata modes) or
// if the client cannot infer it (not follow conventions).
return _readLinkBuilder.Factory(instanceContext);
}
// client can infer it and didn't ask for it.
return null;
}
/// <summary>
/// Constructs a NavigationLink for a particular <see cref="EntityInstanceContext" />, <see cref="IEdmNavigationProperty" /> and <see cref="ODataMetadataLevel" />.
/// </summary>
public virtual Uri BuildNavigationLink(EntityInstanceContext instanceContext, IEdmNavigationProperty navigationProperty, ODataMetadataLevel metadataLevel)
{
if (instanceContext == null)
{
throw Error.ArgumentNull("instanceContext");
}
if (navigationProperty == null)
{
throw Error.ArgumentNull("navigationProperty");
}
NavigationLinkBuilder navigationLinkBuilder;
if (!_navigationPropertyLinkBuilderLookup.TryGetValue(navigationProperty, out navigationLinkBuilder))
{
throw Error.Argument("navigationProperty", SRResources.NoNavigationLinkFactoryFound, navigationProperty.Name, navigationProperty.DeclaringEntityType(), _entitySetName);
}
Contract.Assert(navigationLinkBuilder != null);
if (IsDefaultOrFull(metadataLevel) ||
(IsMinimal(metadataLevel) && !navigationLinkBuilder.FollowsConventions))
{
return navigationLinkBuilder.Factory(instanceContext, navigationProperty);
}
else
{
// client can infer it and didn't ask for it.
return null;
}
}
private static bool IsDefaultOrFull(ODataMetadataLevel level)
{
return level == ODataMetadataLevel.Default || level == ODataMetadataLevel.FullMetadata;
}
private static bool IsMinimal(ODataMetadataLevel level)
{
return level == ODataMetadataLevel.MinimalMetadata;
}
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder
{
internal class EntitySetUrlAnnotation
{
public string Url { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,322 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
// TODO: add support for FK properties
// CUT: support for bi-directional properties
/// <summary>
/// Represents an <see cref="IEdmEntityType"/> that can be built using <see cref="ODataModelBuilder"/>.
/// </summary>
public class EntityTypeConfiguration : StructuralTypeConfiguration
{
private List<PrimitivePropertyConfiguration> _keys = new List<PrimitivePropertyConfiguration>();
private EntityTypeConfiguration _baseType;
private bool _baseTypeConfigured;
/// <summary>
/// Initializes a new instance of the <see cref="EntityTypeConfiguration"/> class.
/// </summary>
/// <remarks>The default constructor is intended for use by unit testing only.</remarks>
public EntityTypeConfiguration()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EntityTypeConfiguration"/> class.
/// </summary>
/// <param name="modelBuilder">The <see cref="ODataModelBuilder"/> being used.</param>
/// <param name="clrType">The backing CLR type for this entity type.</param>
public EntityTypeConfiguration(ODataModelBuilder modelBuilder, Type clrType)
: base(modelBuilder, clrType)
{
}
/// <summary>
/// Gets the <see cref="EdmTypeKind"/> of this <see cref="IEdmTypeConfiguration"/>
/// </summary>
public override EdmTypeKind Kind
{
get { return EdmTypeKind.Entity; }
}
/// <summary>
/// Gets the collection of <see cref="NavigationPropertyConfiguration"/> of this entity type.
/// </summary>
public virtual IEnumerable<NavigationPropertyConfiguration> NavigationProperties
{
get
{
return ExplicitProperties.Values.OfType<NavigationPropertyConfiguration>();
}
}
/// <summary>
/// Gets the collection of keys for this entity type.
/// </summary>
public virtual IEnumerable<PrimitivePropertyConfiguration> Keys
{
get
{
return _keys;
}
}
/// <summary>
/// Gets or sets a value indicating whether this type is abstract.
/// </summary>
public virtual bool? IsAbstract { get; set; }
/// <summary>
/// Gets or sets the base type of this entity type.
/// </summary>
public virtual EntityTypeConfiguration BaseType
{
get
{
return _baseType;
}
set
{
DerivesFrom(value);
}
}
/// <summary>
/// Gets a value that represents whether the base type is explicitly configured or inferred.
/// </summary>
public virtual bool BaseTypeConfigured
{
get
{
return _baseTypeConfigured;
}
}
/// <summary>
/// Marks this entity type as abstract.
/// </summary>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public virtual EntityTypeConfiguration Abstract()
{
IsAbstract = true;
return this;
}
/// <summary>
/// Configures the key property(s) for this entity type.
/// </summary>
/// <param name="keyProperty">The property to be added to the key properties of this entity type.</param>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public virtual EntityTypeConfiguration HasKey(PropertyInfo keyProperty)
{
if (BaseType != null)
{
throw Error.InvalidOperation(SRResources.CannotDefineKeysOnDerivedTypes, FullName, BaseType.FullName);
}
PrimitivePropertyConfiguration propertyConfig = AddProperty(keyProperty);
// keys are always required
propertyConfig.IsRequired();
if (!_keys.Contains(propertyConfig))
{
_keys.Add(propertyConfig);
}
return this;
}
/// <summary>
/// Removes the property from the entity keys collection.
/// </summary>
/// <param name="keyProperty">The key to be removed.</param>
/// <remarks>This method just disable the property to be not a key anymore. It does not remove the property all together.
/// To remove the property completely, use the method <see cref="RemoveProperty"/></remarks>
public virtual void RemoveKey(PrimitivePropertyConfiguration keyProperty)
{
if (keyProperty == null)
{
throw Error.ArgumentNull("keyProperty");
}
_keys.Remove(keyProperty);
}
/// <summary>
/// Sets the base type of this entity type to <c>null</c> meaning that this entity type
/// does not derive from anything.
/// </summary>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public virtual EntityTypeConfiguration DerivesFromNothing()
{
_baseType = null;
_baseTypeConfigured = true;
return this;
}
/// <summary>
/// Sets the base type of this entity type.
/// </summary>
/// <param name="baseType">The base entity type.</param>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public virtual EntityTypeConfiguration DerivesFrom(EntityTypeConfiguration baseType)
{
if (baseType == null)
{
throw Error.ArgumentNull("baseType");
}
_baseType = baseType;
_baseTypeConfigured = true;
if (!baseType.ClrType.IsAssignableFrom(ClrType) || baseType.ClrType == ClrType)
{
throw Error.Argument("baseType", SRResources.TypeDoesNotInheritFromBaseType, ClrType.FullName, baseType.ClrType.FullName);
}
if (Keys.Any())
{
throw Error.InvalidOperation(SRResources.CannotDefineKeysOnDerivedTypes, FullName, baseType.FullName);
}
foreach (PropertyConfiguration property in Properties)
{
ValidatePropertyNotAlreadyDefinedInBaseTypes(property.PropertyInfo);
}
foreach (PropertyConfiguration property in this.DerivedProperties())
{
ValidatePropertyNotAlreadyDefinedInDerivedTypes(property.PropertyInfo);
}
return this;
}
/// <summary>
/// Adds a new EDM primitive property to this entity type.
/// </summary>
/// <param name="propertyInfo">The backing CLR property.</param>
/// <returns>Returns the <see cref="PrimitivePropertyConfiguration"/> of the added property.</returns>
public override PrimitivePropertyConfiguration AddProperty(PropertyInfo propertyInfo)
{
ValidatePropertyNotAlreadyDefinedInBaseTypes(propertyInfo);
ValidatePropertyNotAlreadyDefinedInDerivedTypes(propertyInfo);
return base.AddProperty(propertyInfo);
}
/// <summary>
/// Adds a new EDM complex property to this entity type.
/// </summary>
/// <param name="propertyInfo">The backing CLR property.</param>
/// <returns>Returns the <see cref="ComplexPropertyConfiguration"/> of the added property.</returns>
public override ComplexPropertyConfiguration AddComplexProperty(PropertyInfo propertyInfo)
{
ValidatePropertyNotAlreadyDefinedInBaseTypes(propertyInfo);
ValidatePropertyNotAlreadyDefinedInDerivedTypes(propertyInfo);
return base.AddComplexProperty(propertyInfo);
}
/// <summary>
/// Adds a new EDM collection property to this entity type.
/// </summary>
/// <param name="propertyInfo">The backing CLR property.</param>
/// <returns>Returns the <see cref="CollectionPropertyConfiguration"/> of the added property.</returns>
public override CollectionPropertyConfiguration AddCollectionProperty(PropertyInfo propertyInfo)
{
ValidatePropertyNotAlreadyDefinedInBaseTypes(propertyInfo);
ValidatePropertyNotAlreadyDefinedInDerivedTypes(propertyInfo);
return base.AddCollectionProperty(propertyInfo);
}
/// <summary>
/// Adds a new EDM navigation property to this entity type.
/// </summary>
/// <param name="navigationProperty">The backing CLR property.</param>
/// <param name="multiplicity">The <see cref="EdmMultiplicity"/> of the navigation property.</param>
/// <returns>Returns the <see cref="NavigationPropertyConfiguration"/> of the added property.</returns>
public virtual NavigationPropertyConfiguration AddNavigationProperty(PropertyInfo navigationProperty, EdmMultiplicity multiplicity)
{
if (navigationProperty == null)
{
throw Error.ArgumentNull("navigationProperty");
}
if (!navigationProperty.ReflectedType.IsAssignableFrom(ClrType))
{
throw Error.Argument("navigationProperty", SRResources.PropertyDoesNotBelongToType, navigationProperty.Name, ClrType.FullName);
}
ValidatePropertyNotAlreadyDefinedInBaseTypes(navigationProperty);
ValidatePropertyNotAlreadyDefinedInDerivedTypes(navigationProperty);
PropertyConfiguration propertyConfig;
NavigationPropertyConfiguration navigationPropertyConfig;
if (ExplicitProperties.ContainsKey(navigationProperty))
{
propertyConfig = ExplicitProperties[navigationProperty];
if (propertyConfig.Kind != PropertyKind.Navigation)
{
throw Error.Argument("navigationProperty", SRResources.MustBeNavigationProperty, navigationProperty.Name, ClrType.FullName);
}
navigationPropertyConfig = propertyConfig as NavigationPropertyConfiguration;
if (navigationPropertyConfig.Multiplicity != multiplicity)
{
throw Error.Argument("navigationProperty", SRResources.MustHaveMatchingMultiplicity, navigationProperty.Name, multiplicity);
}
}
else
{
navigationPropertyConfig = new NavigationPropertyConfiguration(navigationProperty, multiplicity, this);
ExplicitProperties[navigationProperty] = navigationPropertyConfig;
// make sure the related type is configured
ModelBuilder.AddEntity(navigationPropertyConfig.RelatedClrType);
}
return navigationPropertyConfig;
}
/// <summary>
/// Removes the property from the entity.
/// </summary>
/// <param name="propertyInfo">The <see cref="PropertyInfo"/> of the property to be removed.</param>
public override void RemoveProperty(PropertyInfo propertyInfo)
{
base.RemoveProperty(propertyInfo);
_keys.RemoveAll(p => p.PropertyInfo == propertyInfo);
}
private void ValidatePropertyNotAlreadyDefinedInBaseTypes(PropertyInfo propertyInfo)
{
PropertyConfiguration baseProperty = this.DerivedProperties().Where(p => p.Name == propertyInfo.Name).FirstOrDefault();
if (baseProperty != null)
{
throw Error.Argument("propertyInfo", SRResources.CannotRedefineBaseTypeProperty, propertyInfo.Name, baseProperty.PropertyInfo.ReflectedType.FullName);
}
}
private void ValidatePropertyNotAlreadyDefinedInDerivedTypes(PropertyInfo propertyInfo)
{
foreach (EntityTypeConfiguration derivedEntity in ModelBuilder.DerivedTypes(this))
{
PropertyConfiguration propertyInDerivedType = derivedEntity.Properties.Where(p => p.Name == propertyInfo.Name).FirstOrDefault();
if (propertyInDerivedType != null)
{
throw Error.Argument("propertyInfo", SRResources.PropertyAlreadyDefinedInDerivedType, propertyInfo.Name, FullName, derivedEntity.FullName);
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,223 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Represents an <see cref="IEdmEntityType"/> that can be built using <see cref="ODataModelBuilder"/>.
/// </summary>
/// <typeparam name="TEntityType">The backing CLR type for this <see cref="IEdmEntityType"/>.</typeparam>
public class EntityTypeConfiguration<TEntityType> : StructuralTypeConfiguration<TEntityType> where TEntityType : class
{
private EntityTypeConfiguration _configuration;
private EntityCollectionConfiguration<TEntityType> _collection;
private ODataModelBuilder _modelBuilder;
/// <summary>
/// Initializes a new instance of <see cref="EntityTypeConfiguration"/>.
/// </summary>
/// <param name="modelBuilder">The <see cref="ODataModelBuilder"/> being used.</param>
internal EntityTypeConfiguration(ODataModelBuilder modelBuilder)
: this(modelBuilder, new EntityTypeConfiguration(modelBuilder, typeof(TEntityType)))
{
}
internal EntityTypeConfiguration(ODataModelBuilder modelBuilder, EntityTypeConfiguration configuration)
: base(configuration)
{
Contract.Assert(modelBuilder != null);
Contract.Assert(configuration != null);
_modelBuilder = modelBuilder;
_configuration = configuration;
_collection = new EntityCollectionConfiguration<TEntityType>(configuration);
}
/// <summary>
/// Gets the base type of this entity type.
/// </summary>
public EntityTypeConfiguration BaseType
{
get
{
return _configuration.BaseType;
}
}
/// <summary>
/// Gets the collection of <see cref="NavigationPropertyConfiguration"/> of this entity type.
/// </summary>
public IEnumerable<NavigationPropertyConfiguration> NavigationProperties
{
get { return _configuration.NavigationProperties; }
}
/// <summary>
/// Used to access a Collection of Entities throw which you can configure
/// actions that are bindable to EntityCollections.
/// </summary>
public EntityCollectionConfiguration<TEntityType> Collection
{
get { return _collection; }
}
/// <summary>
/// Marks this entity type as abstract.
/// </summary>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public EntityTypeConfiguration<TEntityType> Abstract()
{
_configuration.IsAbstract = true;
return this;
}
/// <summary>
/// Sets the base type of this entity type to <c>null</c> meaning that this entity type
/// does not derive from anything.
/// </summary>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public EntityTypeConfiguration<TEntityType> DerivesFromNothing()
{
_configuration.DerivesFromNothing();
return this;
}
/// <summary>
/// Sets the base type of this entity type.
/// </summary>
/// <typeparam name="TBaseType">The base entity type.</typeparam>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "typeof(TBaseType) is used and getting it as a generic argument is cleaner")]
public EntityTypeConfiguration<TEntityType> DerivesFrom<TBaseType>() where TBaseType : class
{
EntityTypeConfiguration<TBaseType> baseEntityType = _modelBuilder.Entity<TBaseType>();
_configuration.DerivesFrom(baseEntityType._configuration);
return this;
}
/// <summary>
/// Configures the key property(s) for this entity type.
/// </summary>
/// <typeparam name="TKey">The type of key.</typeparam>
/// <param name="keyDefinitionExpression">A lambda expression representing the property to be used as the primary key. For example, in C# t => t.Id and in Visual Basic .Net Function(t) t.Id.</param>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Explicit Expression generic type is more clear")]
public EntityTypeConfiguration<TEntityType> HasKey<TKey>(Expression<Func<TEntityType, TKey>> keyDefinitionExpression)
{
ICollection<PropertyInfo> properties = PropertySelectorVisitor.GetSelectedProperties(keyDefinitionExpression);
foreach (PropertyInfo property in properties)
{
_configuration.HasKey(property);
}
return this;
}
/// <summary>
/// Configures a many relationship from this entity type.
/// </summary>
/// <typeparam name="TTargetEntity">The type of the entity at the other end of the relationship.</typeparam>
/// <param name="navigationPropertyExpression">A lambda expression representing the navigation property for the relationship. For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <returns>A configuration object that can be used to further configure the relationship.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Explicit Expression generic type is more clear")]
public NavigationPropertyConfiguration HasMany<TTargetEntity>(Expression<Func<TEntityType, IEnumerable<TTargetEntity>>> navigationPropertyExpression) where TTargetEntity : class
{
return GetOrCreateNavigationProperty(navigationPropertyExpression, EdmMultiplicity.Many);
}
/// <summary>
/// Configures an optional relationship from this entity type.
/// </summary>
/// <typeparam name="TTargetEntity">The type of the entity at the other end of the relationship.</typeparam>
/// <param name="navigationPropertyExpression">A lambda expression representing the navigation property for the relationship. For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <returns>A configuration object that can be used to further configure the relationship.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Explicit Expression generic type is more clear")]
public NavigationPropertyConfiguration HasOptional<TTargetEntity>(Expression<Func<TEntityType, TTargetEntity>> navigationPropertyExpression) where TTargetEntity : class
{
return GetOrCreateNavigationProperty(navigationPropertyExpression, EdmMultiplicity.ZeroOrOne);
}
/// <summary>
/// Configures a required relationship from this entity type.
/// </summary>
/// <typeparam name="TTargetEntity">The type of the entity at the other end of the relationship.</typeparam>
/// <param name="navigationPropertyExpression">A lambda expression representing the navigation property for the relationship. For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <returns>A configuration object that can be used to further configure the relationship.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Explicit Expression generic type is more clear")]
public NavigationPropertyConfiguration HasRequired<TTargetEntity>(Expression<Func<TEntityType, TTargetEntity>> navigationPropertyExpression) where TTargetEntity : class
{
return GetOrCreateNavigationProperty(navigationPropertyExpression, EdmMultiplicity.One);
}
/// <summary>
/// Create an Action that binds to this EntityType.
/// </summary>
/// <param name="name">The name of the action.</param>
/// <returns>The ActionConfiguration to allow further configuration of the new Action.</returns>
public ActionConfiguration Action(string name)
{
Contract.Assert(_configuration != null && _configuration.ModelBuilder != null);
ActionConfiguration action = _configuration.ModelBuilder.Action(name);
action.SetBindingParameter(BindingParameterConfiguration.DefaultBindingParameterName, _configuration, alwaysBindable: true);
return action;
}
/// <summary>
/// Create a Function that binds to this EntityType.
/// </summary>
/// <param name="name">The name of the function.</param>
/// <returns>The FunctionConfiguration to allow further configuration of the new Function.</returns>
public FunctionConfiguration Function(string name)
{
Contract.Assert(_configuration != null && _configuration.ModelBuilder != null);
FunctionConfiguration function = _configuration.ModelBuilder.Function(name);
function.SetBindingParameter(BindingParameterConfiguration.DefaultBindingParameterName, _configuration, alwaysBindable: true);
return function;
}
/// <summary>
/// Create an Action that sometimes binds to this EntityType
/// </summary>
/// <param name="name">The name of the action.</param>
/// <returns>The ActionConfiguration to allow further configuration of the new 'transient' Action.</returns>
public ActionConfiguration TransientAction(string name)
{
Contract.Assert(_configuration != null && _configuration.ModelBuilder != null);
ActionConfiguration action = _configuration.ModelBuilder.Action(name);
action.SetBindingParameter(BindingParameterConfiguration.DefaultBindingParameterName, _configuration, alwaysBindable: false);
return action;
}
/// <summary>
/// Create a Function that sometimes binds to this EntityType
/// </summary>
/// <param name="name">The name of the function.</param>
/// <returns>The FunctionConfiguration to allow further configuration of the new 'transient' Function.</returns>
public FunctionConfiguration TransientFunction(string name)
{
Contract.Assert(_configuration != null && _configuration.ModelBuilder != null);
FunctionConfiguration function = _configuration.ModelBuilder.Function(name);
function.SetBindingParameter(BindingParameterConfiguration.DefaultBindingParameterName, _configuration, alwaysBindable: false);
return function;
}
internal NavigationPropertyConfiguration GetOrCreateNavigationProperty(Expression navigationPropertyExpression, EdmMultiplicity multiplicity)
{
PropertyInfo navigationProperty = PropertySelectorVisitor.GetSelectedProperty(navigationPropertyExpression);
return _configuration.AddNavigationProperty(navigationProperty, multiplicity);
}
}
}

Просмотреть файл

@ -0,0 +1,196 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// FunctionConfiguration represents an OData function that you wish to expose via your service.
/// <remarks>
/// FunctionConfigurations are exposed via $metadata as a <FunctionImport/> element.
/// </remarks>
/// </summary>
public class FunctionConfiguration : ProcedureConfiguration
{
/// <summary>
/// Initializes a new instance of <see cref="FunctionConfiguration" /> class.
/// </summary>
/// <param name="builder">The ODataModelBuilder to which this FunctionConfiguration should be added.</param>
/// <param name="name">The name of this FunctionConfiguration.</param>
internal FunctionConfiguration(ODataModelBuilder builder, string name) : base(builder, name)
{
}
/// <inheritdoc />
public override ProcedureKind Kind
{
get { return ProcedureKind.Function; }
}
/// <inheritdoc />
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Copies existing spelling used in EdmLib.")]
public new bool IsComposable
{
get { return base.IsComposable; }
set { base.IsComposable = value; }
}
/// <inheritdoc />
public override bool IsSideEffecting
{
get { return false; }
}
/// <summary>
/// Gets/Sets a value indicating whether the function is supported in $filter.
/// </summary>
public bool SupportedInFilter { get; set; }
/// <summary>
/// Gets/Sets a value indicating whether the function is supported in $orderby.
/// </summary>
public bool SupportedInOrderBy { get; set; }
/// <summary>
/// Register a factory that creates functions links.
/// </summary>
public FunctionConfiguration HasFunctionLink(Func<EntityInstanceContext, Uri> functionLinkFactory, bool followsConventions)
{
if (functionLinkFactory == null)
{
throw new ArgumentNullException("functionLinkFactory");
}
if (!IsBindable || BindingParameter.TypeConfiguration.Kind != EdmTypeKind.Entity)
{
throw Error.InvalidOperation(SRResources.HasActionLinkRequiresBindToEntity, Name);
}
LinkFactory = functionLinkFactory;
FollowsConventions = followsConventions;
return this;
}
/// <summary>
/// Retrieves the currently registered function link factory.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Consistent with EF Has/Get pattern")]
public Func<EntityInstanceContext, Uri> GetFunctionLink()
{
return LinkFactory;
}
/// <summary>
/// Sets the return type to a single EntityType instance.
/// </summary>
/// <typeparam name="TEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetName">The entitySetName which contains the return EntityType instance</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public FunctionConfiguration ReturnsFromEntitySet<TEntityType>(string entitySetName) where TEntityType : class
{
ReturnsFromEntitySetImplementation<TEntityType>(entitySetName);
return this;
}
/// <summary>
/// Sets the return type to a collection of EntityType instances.
/// </summary>
/// <typeparam name="TElementEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetName">The entitySetName which contains the returned EntityType instances</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public FunctionConfiguration ReturnsCollectionFromEntitySet<TElementEntityType>(string entitySetName) where TElementEntityType : class
{
ReturnsCollectionFromEntitySetImplementation<TElementEntityType>(entitySetName);
return this;
}
/// <summary>
/// Established the return type of the Function.
/// <remarks>Used when the return type is a single Primitive or ComplexType.</remarks>
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public FunctionConfiguration Returns<TReturnType>()
{
ReturnsImplementation<TReturnType>();
return this;
}
/// <summary>
/// Establishes the return type of the Function
/// <remarks>Used when the return type is a collection of either Primitive or ComplexTypes.</remarks>
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public FunctionConfiguration ReturnsCollection<TReturnElementType>()
{
ReturnsCollectionImplementation<TReturnElementType>();
return this;
}
/// <summary>
/// Specifies the bindingParameter name, type and whether it is alwaysBindable, use only if the Function "isBindable".
/// </summary>
public FunctionConfiguration SetBindingParameter(string name, IEdmTypeConfiguration bindingParameterType, bool alwaysBindable)
{
SetBindingParameterImplementation(name, bindingParameterType, alwaysBindable);
return this;
}
/// <summary>
/// Sets the return type to a single EntityType instance.
/// </summary>
/// <typeparam name="TEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetPath">The entitySetPath which contains the return EntityType instance</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public FunctionConfiguration ReturnsEntityViaEntitySetPath<TEntityType>(string entitySetPath) where TEntityType : class
{
if (String.IsNullOrEmpty(entitySetPath))
{
throw new ArgumentNullException("entitySetPath");
}
ReturnsEntityViaEntitySetPathImplementation<TEntityType>(entitySetPath.Split('/'));
return this;
}
/// <summary>
/// Sets the return type to a single EntityType instance.
/// </summary>
/// <typeparam name="TEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetPath">The entitySetPath which contains the return EntityType instance</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public FunctionConfiguration ReturnsEntityViaEntitySetPath<TEntityType>(params string[] entitySetPath) where TEntityType : class
{
ReturnsEntityViaEntitySetPathImplementation<TEntityType>(entitySetPath);
return this;
}
/// <summary>
/// Sets the return type to a collection of EntityType instances.
/// </summary>
/// <typeparam name="TElementEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetPath">The entitySetPath which contains the returned EntityType instances</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public FunctionConfiguration ReturnsCollectionViaEntitySetPath<TElementEntityType>(string entitySetPath) where TElementEntityType : class
{
if (String.IsNullOrEmpty(entitySetPath))
{
throw new ArgumentNullException("entitySetPath");
}
ReturnsCollectionViaEntitySetPathImplementation<TElementEntityType>(entitySetPath.Split('/'));
return this;
}
/// <summary>
/// Sets the return type to a collection of EntityType instances.
/// </summary>
/// <typeparam name="TElementEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetPath">The entitySetPath which contains the returned EntityType instances</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public FunctionConfiguration ReturnsCollectionViaEntitySetPath<TElementEntityType>(params string[] entitySetPath) where TElementEntityType : class
{
ReturnsCollectionViaEntitySetPathImplementation<TElementEntityType>(entitySetPath);
return this;
}
}
}

Просмотреть файл

@ -0,0 +1,76 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// FunctionLinkBuilder can be used to annotate an Function.
/// This is how formatters create links to invoke bound functions.
/// </summary>
public class FunctionLinkBuilder
{
private Func<EntityInstanceContext, Uri> _functionLinkFactory;
/// <summary>
/// Create a new FunctionLinkBuilder based on an functionLinkFactory.
/// <remarks>
/// If the function is not available the functionLinkFactory delegate should return NULL.
/// </remarks>
/// </summary>
/// <param name="functionLinkFactory">The functionLinkFactory this FunctionLinkBuilder should use when building links.</param>
/// <param name="followsConventions">
/// A value indicating whether the function link factory generates links that follow OData conventions.
/// </param>
public FunctionLinkBuilder(Func<EntityInstanceContext, Uri> functionLinkFactory, bool followsConventions)
{
if (functionLinkFactory == null)
{
throw Error.ArgumentNull("functionLinkFactory");
}
_functionLinkFactory = functionLinkFactory;
FollowsConventions = followsConventions;
}
/// <summary>
/// Gets a boolean indicating whether the link factory follows OData conventions or not.
/// </summary>
public bool FollowsConventions { get; private set; }
/// <summary>
/// Builds the function link for the given entity.
/// </summary>
/// <param name="context">An instance context wrapping the entity instance.</param>
/// <returns>The generated function link.</returns>
public virtual Uri BuildFunctionLink(EntityInstanceContext context)
{
return _functionLinkFactory(context);
}
/// <summary>
/// Creates an function link factory that builds an function link, but only when appropriate based on the expensiveAvailabilityCheck, and whether expensive checks should be made,
/// which is deduced by looking at the EntityInstanceContext.SkipExpensiveFunctionAvailabilityChecks property.
/// </summary>
/// <param name="baseFactory">The function link factory that actually builds links if all checks pass.</param>
/// <param name="expensiveAvailabilityCheck">The availability check function that is expensive but when called returns whether the function is available.</param>
/// <returns>The new function link factory.</returns>
public static Func<EntityInstanceContext, Uri> CreateFunctionLinkFactory(Func<EntityInstanceContext, Uri> baseFactory, Func<EntityInstanceContext, bool> expensiveAvailabilityCheck)
{
return (EntityInstanceContext ctx) =>
{
if (ctx.SkipExpensiveAvailabilityChecks)
{
// OData says that if it is too expensive to check availability you should advertize functions
return baseFactory(ctx);
}
else if (expensiveAvailabilityCheck(ctx))
{
return baseFactory(ctx);
}
else
{
return null;
}
};
}
}
}

Просмотреть файл

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Represents an EdmType
/// </summary>
public interface IEdmTypeConfiguration
{
/// <summary>
/// The CLR type associated with the EdmType.
/// </summary>
Type ClrType { get; }
/// <summary>
/// The fullname (including namespace) of the EdmType.
/// </summary>
string FullName { get; }
/// <summary>
/// The namespace of the EdmType.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Namespace", Justification = "Namespace matches the EF naming scheme")]
string Namespace { get; }
/// <summary>
/// The name of the EdmType.
/// </summary>
string Name { get; }
/// <summary>
/// The kind of the EdmType.
/// Examples include EntityType, ComplexType, PrimitiveType, CollectionType.
/// </summary>
EdmTypeKind Kind { get; }
/// <summary>
/// The ODataModelBuilder used to create this IEdmType.
/// </summary>
ODataModelBuilder ModelBuilder { get; }
}
}

Просмотреть файл

@ -0,0 +1,205 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web.Http.OData.Builder.Conventions;
using System.Web.Http.OData.Properties;
using System.Web.Http.OData.Routing;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Contains helper methods for generating OData links that follow OData URL conventions.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class LinkGenerationHelpers
{
/// <summary>
/// Generates a self link following the OData URL conventions for the entity represented by <paramref name="entityContext"/>.
/// </summary>
/// <param name="entityContext">The <see cref="EntityInstanceContext"/> representing the entity for which the self link needs to be generated.</param>
/// <param name="includeCast">Represents whether the generated link should have a cast segment representing a type cast.</param>
/// <returns>The self link following the OData URL conventions.</returns>
public static string GenerateSelfLink(this EntityInstanceContext entityContext, bool includeCast)
{
if (entityContext == null)
{
throw Error.ArgumentNull("entityContext");
}
if (entityContext.Url == null)
{
throw Error.Argument("entityContext", SRResources.UrlHelperNull, typeof(EntityInstanceContext).Name);
}
List<ODataPathSegment> idLinkPathSegments = new List<ODataPathSegment>();
idLinkPathSegments.Add(new EntitySetPathSegment(entityContext.EntitySet));
idLinkPathSegments.Add(new KeyValuePathSegment(ConventionsHelpers.GetEntityKeyValue(entityContext)));
if (includeCast)
{
idLinkPathSegments.Add(new CastPathSegment(entityContext.EntityType));
}
string idLink = entityContext.Url.ODataLink(idLinkPathSegments);
if (idLink == null)
{
return null;
}
return idLink;
}
/// <summary>
/// Generates a navigation link following the OData URL conventions for the entity represented by <paramref name="entityContext"/> and the given
/// navigation property.
/// </summary>
/// <param name="entityContext">The <see cref="EntityInstanceContext"/> representing the entity for which the navigation link needs to be generated.</param>
/// <param name="navigationProperty">The EDM navigation property.</param>
/// <param name="includeCast">Represents whether the generated link should have a cast segment representing a type cast.</param>
/// <returns>The navigation link following the OData URL conventions.</returns>
public static Uri GenerateNavigationPropertyLink(this EntityInstanceContext entityContext, IEdmNavigationProperty navigationProperty, bool includeCast)
{
if (entityContext == null)
{
throw Error.ArgumentNull("entityContext");
}
if (entityContext.Url == null)
{
throw Error.Argument("entityContext", SRResources.UrlHelperNull, typeof(EntityInstanceContext).Name);
}
List<ODataPathSegment> navigationPathSegments = new List<ODataPathSegment>();
navigationPathSegments.Add(new EntitySetPathSegment(entityContext.EntitySet));
navigationPathSegments.Add(new KeyValuePathSegment(ConventionsHelpers.GetEntityKeyValue(entityContext)));
if (includeCast)
{
navigationPathSegments.Add(new CastPathSegment(entityContext.EntityType));
}
navigationPathSegments.Add(new NavigationPathSegment(navigationProperty));
string link = entityContext.Url.ODataLink(navigationPathSegments);
if (link == null)
{
return null;
}
return new Uri(link);
}
/// <summary>
/// Generates a feed self link following the OData URL conventions for the feed represented by <paramref name="feedContext"/>.
/// </summary>
/// <param name="feedContext">The <see cref="FeedContext"/> representing the feed for which the self link needs to be generated.</param>
/// <returns>The generated feed self link following the OData URL conventions.</returns>
public static Uri GenerateFeedSelfLink(this FeedContext feedContext)
{
if (feedContext == null)
{
throw Error.ArgumentNull("feedContext");
}
string selfLink = feedContext.Url.ODataLink(new EntitySetPathSegment(feedContext.EntitySet));
return selfLink == null ? null : new Uri(selfLink);
}
/// <summary>
/// Generates an action link following the OData URL conventions for the action <paramref name="action"/> and bound to the entity
/// represented by <paramref name="entityContext"/>.
/// </summary>
/// <param name="entityContext">The <see cref="EntityInstanceContext"/> representing the entity for which the action link needs to be generated.</param>
/// <param name="action">The action for which the action link needs to be generated.</param>
/// <returns>The generated action link following OData URL conventions.</returns>
public static Uri GenerateActionLink(this EntityInstanceContext entityContext, IEdmFunctionBase action)
{
if (entityContext == null)
{
throw Error.ArgumentNull("entityContext");
}
if (action == null)
{
throw Error.ArgumentNull("action");
}
IEdmFunctionParameter bindingParameter = action.Parameters.FirstOrDefault();
if (bindingParameter == null || !bindingParameter.Type.IsEntity())
{
throw Error.Argument("action", SRResources.ActionNotBoundToEntity, action.Name);
}
return GenerateActionLink(entityContext, bindingParameter.Type.FullName(), action.Name);
}
internal static Uri GenerateActionLink(this EntityInstanceContext entityContext, string bindingParameterType, string actionName)
{
List<ODataPathSegment> actionPathSegments = new List<ODataPathSegment>();
actionPathSegments.Add(new EntitySetPathSegment(entityContext.EntitySet));
actionPathSegments.Add(new KeyValuePathSegment(ConventionsHelpers.GetEntityKeyValue(entityContext)));
// generate link with cast if the entityset type doesn't match the entity type the action is bound to.
if (entityContext.EntitySet.ElementType.FullName() != bindingParameterType)
{
actionPathSegments.Add(new CastPathSegment(bindingParameterType));
}
actionPathSegments.Add(new ActionPathSegment(actionName));
string actionLink = entityContext.Url.ODataLink(actionPathSegments);
return actionLink == null ? null : new Uri(actionLink);
}
/// <summary>
/// Generates an function link following the OData URL conventions for the function <paramref name="function"/> and bound to the entity
/// represented by <paramref name="entityContext"/>.
/// </summary>
/// <param name="entityContext">The <see cref="EntityInstanceContext"/> representing the entity for which the function link needs to be generated.</param>
/// <param name="function">The function for which the function link needs to be generated.</param>
/// <returns>The generated function link following OData URL conventions.</returns>
public static Uri GenerateFunctionLink(this EntityInstanceContext entityContext, IEdmFunctionBase function)
{
if (entityContext == null)
{
throw Error.ArgumentNull("entityContext");
}
if (function == null)
{
throw Error.ArgumentNull("function");
}
IEdmFunctionParameter bindingParameter = function.Parameters.FirstOrDefault();
if (bindingParameter == null || !bindingParameter.Type.IsEntity())
{
throw Error.Argument("function", SRResources.FunctionNotBoundToEntity, function.Name);
}
return GenerateFunctionLink(entityContext, bindingParameter.Type.FullName(), function.Name, function.Parameters.Select(p => p.Name));
}
internal static Uri GenerateFunctionLink(this EntityInstanceContext entityContext, string bindingParameterType, string functionName, IEnumerable<string> parameterNames)
{
List<ODataPathSegment> functionPathSegments = new List<ODataPathSegment>();
functionPathSegments.Add(new EntitySetPathSegment(entityContext.EntitySet));
functionPathSegments.Add(new KeyValuePathSegment(ConventionsHelpers.GetEntityKeyValue(entityContext)));
// generate link with cast if the entityset type doesn't match the entity type the function is bound to.
if (entityContext.EntitySet.ElementType.FullName() != bindingParameterType)
{
functionPathSegments.Add(new CastPathSegment(bindingParameterType));
}
Dictionary<string, string> parametersDictionary = new Dictionary<string, string>();
foreach (string param in parameterNames)
{
parametersDictionary.Add(param, "@" + param);
}
functionPathSegments.Add(new FunctionPathSegment(functionName, parametersDictionary));
string functionLink = entityContext.Url.ODataLink(functionPathSegments);
return functionLink == null ? null : new Uri(functionLink);
}
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Encapsulates a navigation link factory and whether the link factory follows conventions or not.
/// </summary>
public class NavigationLinkBuilder
{
/// <summary>
/// Initializes a new instance of the <see cref="NavigationLinkBuilder"/> class.
/// </summary>
/// <param name="navigationLinkFactory">The navigation link factory for creating navigation links.</param>
/// <param name="followsConventions">Represents whether this factory follows OData conventions or not.</param>
public NavigationLinkBuilder(Func<EntityInstanceContext, IEdmNavigationProperty, Uri> navigationLinkFactory, bool followsConventions)
{
if (navigationLinkFactory == null)
{
throw Error.ArgumentNull("navigationLinkFactory");
}
Factory = navigationLinkFactory;
FollowsConventions = followsConventions;
}
/// <summary>
/// Gets the navigation link factory for creating navigation links.
/// </summary>
public Func<EntityInstanceContext, IEdmNavigationProperty, Uri> Factory { get; private set; }
/// <summary>
/// Gets a value representing whether this factory follows OData conventions or not.
/// </summary>
public bool FollowsConventions { get; private set; }
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Used to configure the binding for a navigation property for an entity set.
/// This configuration functionality is exposed by the model builder Fluent API, see <see
/// cref="ODataModelBuilder"/>.
/// </summary>
public class NavigationPropertyBindingConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="NavigationPropertyBindingConfiguration"/> class.
/// </summary>
/// <param name="navigationProperty">The navigation property for the binding.</param>
/// <param name="entitySet">The target entity set of the binding.</param>
public NavigationPropertyBindingConfiguration(NavigationPropertyConfiguration navigationProperty,
EntitySetConfiguration entitySet)
{
NavigationProperty = navigationProperty;
EntitySet = entitySet;
}
/// <summary>
/// Gets the navigation property of the binding.
/// </summary>
public NavigationPropertyConfiguration NavigationProperty { get; private set; }
/// <summary>
/// Gets the target entity set of the binding.
/// </summary>
public EntitySetConfiguration EntitySet { get; private set; }
}
}

Просмотреть файл

@ -0,0 +1,106 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Reflection;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Represents the configuration for a navigation property of an entity type.
/// </summary>
/// <remarks>This configuration functionality is exposed by the model builder Fluent API, see <see cref="ODataModelBuilder"/>.</remarks>
public class NavigationPropertyConfiguration : PropertyConfiguration
{
private readonly Type _relatedType = null;
/// <summary>
/// Initializes a new instance of the <see cref="NavigationPropertyConfiguration"/> class.
/// </summary>
/// <param name="property">The backing CLR property.</param>
/// <param name="multiplicity">The <see cref="EdmMultiplicity"/>.</param>
/// <param name="declaringType">The declaring entity type.</param>
public NavigationPropertyConfiguration(PropertyInfo property, EdmMultiplicity multiplicity, EntityTypeConfiguration declaringType)
: base(property, declaringType)
{
if (property == null)
{
throw Error.ArgumentNull("property");
}
Multiplicity = multiplicity;
_relatedType = property.PropertyType;
if (multiplicity == EdmMultiplicity.Many)
{
Type elementType;
if (!_relatedType.IsCollection(out elementType))
{
throw Error.Argument("property", SRResources.ManyToManyNavigationPropertyMustReturnCollection, property.Name, property.ReflectedType.Name);
}
_relatedType = elementType;
}
}
/// <summary>
/// Gets the declaring entity type.
/// </summary>
public EntityTypeConfiguration DeclaringEntityType
{
get
{
return DeclaringType as EntityTypeConfiguration;
}
}
/// <summary>
/// Gets the <see cref="EdmMultiplicity"/> of this navigation property.
/// </summary>
public EdmMultiplicity Multiplicity { get; private set; }
/// <summary>
/// Gets the backing CLR type of this property type.
/// </summary>
public override Type RelatedClrType
{
get { return _relatedType; }
}
/// <summary>
/// Gets the <see cref="PropertyKind"/> of this property.
/// </summary>
public override PropertyKind Kind
{
get { return PropertyKind.Navigation; }
}
/// <summary>
/// Marks the navigation property as optional.
/// </summary>
public NavigationPropertyConfiguration Optional()
{
if (Multiplicity == EdmMultiplicity.Many)
{
throw Error.InvalidOperation(SRResources.ManyNavigationPropertiesCannotBeChanged, Name);
}
Multiplicity = EdmMultiplicity.ZeroOrOne;
return this;
}
/// <summary>
/// Marks the navigation property as required.
/// </summary>
public NavigationPropertyConfiguration Required()
{
if (Multiplicity == EdmMultiplicity.Many)
{
throw Error.InvalidOperation(SRResources.ManyNavigationPropertiesCannotBeChanged, Name);
}
Multiplicity = EdmMultiplicity.One;
return this;
}
}
}

Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Represents a non-binding procedure parameter.
/// <remarks>
/// Non binding parameters are provided in the POST body for Actions
/// Non binding parameters are provided in 3 ways for Functions
/// - ~/.../Function(p1=value)
/// - ~/.../Function(p1=@x)?@x=value
/// - ~/.../Function?p1=value (only allowed if the Function is the last url path segment).
/// </remarks>
/// </summary>
public class NonbindingParameterConfiguration : ParameterConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="NonbindingParameterConfiguration"/> class.
/// </summary>
/// <param name="name">The name of the parameter.</param>
/// <param name="parameterType">The EDM type of the parameter.</param>
public NonbindingParameterConfiguration(string name, IEdmTypeConfiguration parameterType)
: base(name, parameterType)
{
EdmTypeKind kind = parameterType.Kind;
if (kind == EdmTypeKind.Collection)
{
kind = (parameterType as CollectionTypeConfiguration).ElementType.Kind;
}
if (kind == EdmTypeKind.Entity)
{
throw Error.Argument("parameterType", SRResources.InvalidParameterType, parameterType.FullName);
}
}
}
}

Просмотреть файл

@ -0,0 +1,729 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Web.Http.Dispatcher;
using System.Web.Http.OData.Builder.Conventions;
using System.Web.Http.OData.Builder.Conventions.Attributes;
using System.Web.Http.OData.Formatter;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// <see cref="ODataConventionModelBuilder"/> is used to automatically map CLR classes to an EDM model based on a set of <see cref="IConvention"/>.
/// </summary>
public class ODataConventionModelBuilder : ODataModelBuilder
{
private static readonly List<IConvention> _conventions = new List<IConvention>
{
// type and property conventions (ordering is important here).
new AbstractEntityTypeDiscoveryConvention(),
new DataContractAttributeEdmTypeConvention(),
new NotMappedAttributeConvention(), // NotMappedAttributeConvention has to run before EntityKeyConvention
new DataMemberAttributeEdmPropertyConvention(),
new RequiredAttributeEdmPropertyConvention(),
new ConcurrencyCheckAttributeEdmPropertyConvention(),
new EntityKeyConvention(),
new KeyAttributeEdmPropertyConvention(),
new IgnoreDataMemberAttributeEdmPropertyConvention(),
new NonFilterableAttributeEdmPropertyConvention(),
new UnsortableAttributeEdmPropertyConvention(),
new NotNavigableAttributeEdmPropertyConvention(),
new NotExpandableAttributeEdmPropertyConvention(),
// IEntitySetConvention's
new SelfLinksGenerationConvention(),
new NavigationLinksGenerationConvention(),
new AssociationSetDiscoveryConvention(),
// IEdmFunctionImportConventions's
new ActionLinkGenerationConvention(),
new FunctionLinkGenerationConvention(),
};
// These hashset's keep track of edmtypes/entitysets for which conventions
// have been applied or being applied so that we don't run a convention twice on the
// same type/set.
private HashSet<StructuralTypeConfiguration> _mappedTypes;
private HashSet<EntitySetConfiguration> _configuredEntitySets;
private HashSet<Type> _ignoredTypes;
private IEnumerable<StructuralTypeConfiguration> _explicitlyAddedTypes;
private bool _isModelBeingBuilt;
private bool _isQueryCompositionMode;
// build the mapping between type and its derived types to be used later.
private Lazy<IDictionary<Type, List<Type>>> _allTypesWithDerivedTypeMapping;
/// <summary>
/// Initializes a new instance of the <see cref="ODataConventionModelBuilder"/> class.
/// </summary>
public ODataConventionModelBuilder()
{
Initialize(new DefaultAssembliesResolver(), isQueryCompositionMode: false);
}
/// <summary>
/// Initializes a new <see cref="ODataConventionModelBuilder"/>.
/// </summary>
/// <param name="configuration">The <see cref="HttpConfiguration"/> to use.</param>
public ODataConventionModelBuilder(HttpConfiguration configuration)
: this(configuration, isQueryCompositionMode: false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ODataConventionModelBuilder"/> class.
/// </summary>
/// <param name="configuration">The <see cref="HttpConfiguration"/> to use.</param>
/// <param name="isQueryCompositionMode">If the model is being built for only querying.</param>
/// <remarks>The model built if <paramref name="isQueryCompositionMode"/> is <see langword="true"/> has more relaxed
/// inference rules and also treats all types as entity types. This constructor is intended for use by unit testing only.</remarks>
public ODataConventionModelBuilder(HttpConfiguration configuration, bool isQueryCompositionMode)
{
if (configuration == null)
{
throw Error.ArgumentNull("configuration");
}
Initialize(configuration.Services.GetAssembliesResolver(), isQueryCompositionMode);
}
/// <summary>
/// This action is invoked after the <see cref="ODataConventionModelBuilder"/> has run all the conventions, but before the configuration is locked
/// down and used to build the <see cref="IEdmModel"/>.
/// </summary>
/// <remarks>Use this action to modify the <see cref="ODataModelBuilder"/> configuration that has been inferred by convention.</remarks>
public Action<ODataConventionModelBuilder> OnModelCreating { get; set; }
internal void Initialize(IAssembliesResolver assembliesResolver, bool isQueryCompositionMode)
{
_isQueryCompositionMode = isQueryCompositionMode;
_configuredEntitySets = new HashSet<EntitySetConfiguration>();
_mappedTypes = new HashSet<StructuralTypeConfiguration>();
_ignoredTypes = new HashSet<Type>();
_allTypesWithDerivedTypeMapping = new Lazy<IDictionary<Type, List<Type>>>(
() => BuildDerivedTypesMapping(assembliesResolver),
isThreadSafe: false);
}
/// <summary>
/// Excludes a type from the model. This is used to remove types from the model that were added by convention during initial model discovery.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>The same <c ref="ODataConventionModelBuilder"/> so that multiple calls can be chained.</returns>
[SuppressMessage("Microsoft.Design", "CA1004: GenericMethodsShouldProvideTypeParameter", Justification = "easier to call the generic version than using typeof().")]
public ODataConventionModelBuilder Ignore<T>()
{
_ignoredTypes.Add(typeof(T));
return this;
}
/// <summary>
/// Excludes a type or types from the model. This is used to remove types from the model that were added by convention during initial model discovery.
/// </summary>
/// <param name="types">The types to be excluded from the model.</param>
/// <returns>The same <c ref="ODataConventionModelBuilder"/> so that multiple calls can be chained.</returns>
public ODataConventionModelBuilder Ignore(params Type[] types)
{
foreach (Type type in types)
{
_ignoredTypes.Add(type);
}
return this;
}
/// <inheritdoc />
public override EntityTypeConfiguration AddEntity(Type type)
{
EntityTypeConfiguration entityTypeConfiguration = base.AddEntity(type);
if (_isModelBeingBuilt)
{
MapType(entityTypeConfiguration);
}
return entityTypeConfiguration;
}
/// <inheritdoc />
public override ComplexTypeConfiguration AddComplexType(Type type)
{
ComplexTypeConfiguration complexTypeConfiguration = base.AddComplexType(type);
if (_isModelBeingBuilt)
{
MapType(complexTypeConfiguration);
}
return complexTypeConfiguration;
}
/// <inheritdoc />
public override EntitySetConfiguration AddEntitySet(string name, EntityTypeConfiguration entityType)
{
EntitySetConfiguration entitySetConfiguration = base.AddEntitySet(name, entityType);
if (_isModelBeingBuilt)
{
ApplyEntitySetConventions(entitySetConfiguration);
}
return entitySetConfiguration;
}
/// <inheritdoc />
public override IEdmModel GetEdmModel()
{
if (_isModelBeingBuilt)
{
throw Error.NotSupported(SRResources.GetEdmModelCalledMoreThanOnce);
}
// before we begin, get the set of types the user had added explicitly.
_explicitlyAddedTypes = new List<StructuralTypeConfiguration>(StructuralTypes);
_isModelBeingBuilt = true;
MapTypes();
DiscoverInheritanceRelationships();
// Don't RediscoverComplexTypes() and treat everything as an entity type if buidling a model for QueryableAttribute.
if (!_isQueryCompositionMode)
{
RediscoverComplexTypes();
}
// prune unreachable types
PruneUnreachableTypes();
// Apply entity set conventions.
IEnumerable<EntitySetConfiguration> explictlyConfiguredEntitySets = new List<EntitySetConfiguration>(EntitySets);
foreach (EntitySetConfiguration entitySet in explictlyConfiguredEntitySets)
{
ApplyEntitySetConventions(entitySet);
}
foreach (ProcedureConfiguration procedure in Procedures)
{
ApplyProcedureConventions(procedure);
}
if (OnModelCreating != null)
{
OnModelCreating(this);
}
return base.GetEdmModel();
}
internal bool IsIgnoredType(Type type)
{
Contract.Requires(type != null);
return _ignoredTypes.Contains(type);
}
// patch up the base type for all entities that don't have any yet.
internal void DiscoverInheritanceRelationships()
{
Dictionary<Type, EntityTypeConfiguration> entityMap = StructuralTypes.OfType<EntityTypeConfiguration>().ToDictionary(e => e.ClrType);
foreach (EntityTypeConfiguration entity in StructuralTypes.OfType<EntityTypeConfiguration>().Where(e => !e.BaseTypeConfigured).ToArray())
{
Type baseClrType = entity.ClrType.BaseType;
while (baseClrType != null)
{
// see if we there is an entity that we know mapping to this clr types base type.
EntityTypeConfiguration baseEntityType;
if (entityMap.TryGetValue(baseClrType, out baseEntityType))
{
RemoveBaseTypeProperties(entity, baseEntityType);
// disable derived type key check if we are building a model for query composition.
if (_isQueryCompositionMode)
{
// modifying the collection in the iterator, hence the ToArray().
foreach (PrimitivePropertyConfiguration keyProperty in entity.Keys.ToArray())
{
entity.RemoveKey(keyProperty);
}
}
entity.DerivesFrom(baseEntityType);
break;
}
baseClrType = baseClrType.BaseType;
}
}
}
internal void MapDerivedTypes(EntityTypeConfiguration entity)
{
HashSet<Type> visitedEntities = new HashSet<Type>();
Queue<EntityTypeConfiguration> entitiesToBeVisited = new Queue<EntityTypeConfiguration>();
entitiesToBeVisited.Enqueue(entity);
// populate all the derived types
while (entitiesToBeVisited.Count != 0)
{
EntityTypeConfiguration baseEntity = entitiesToBeVisited.Dequeue();
visitedEntities.Add(baseEntity.ClrType);
List<Type> derivedTypes;
if (_allTypesWithDerivedTypeMapping.Value.TryGetValue(baseEntity.ClrType, out derivedTypes))
{
foreach (Type derivedType in derivedTypes)
{
if (!visitedEntities.Contains(derivedType) && !IsIgnoredType(derivedType))
{
EntityTypeConfiguration derivedEntity = AddEntity(derivedType);
entitiesToBeVisited.Enqueue(derivedEntity);
}
}
}
}
}
// remove base type properties from the derived types.
internal void RemoveBaseTypeProperties(EntityTypeConfiguration derivedEntity, EntityTypeConfiguration baseEntity)
{
IEnumerable<EntityTypeConfiguration> typesToLift = new[] { derivedEntity }.Concat(this.DerivedTypes(derivedEntity));
foreach (PropertyConfiguration property in baseEntity.Properties.Concat(baseEntity.DerivedProperties()))
{
foreach (EntityTypeConfiguration entity in typesToLift)
{
PropertyConfiguration derivedPropertyToRemove = entity.Properties.Where(p => p.Name == property.Name).SingleOrDefault();
if (derivedPropertyToRemove != null)
{
entity.RemoveProperty(derivedPropertyToRemove.PropertyInfo);
}
}
}
foreach (PropertyInfo ignoredProperty in baseEntity.IgnoredProperties())
{
foreach (EntityTypeConfiguration entity in typesToLift)
{
PropertyConfiguration derivedPropertyToRemove = entity.Properties.Where(p => p.Name == ignoredProperty.Name).SingleOrDefault();
if (derivedPropertyToRemove != null)
{
entity.RemoveProperty(derivedPropertyToRemove.PropertyInfo);
}
}
}
}
private void RediscoverComplexTypes()
{
Contract.Assert(_explicitlyAddedTypes != null);
IEnumerable<EntityTypeConfiguration> misconfiguredEntityTypes = StructuralTypes
.Except(_explicitlyAddedTypes)
.OfType<EntityTypeConfiguration>()
.Where(entity => !entity.Keys().Any())
.ToArray();
ReconfigureEntityTypesAsComplexType(misconfiguredEntityTypes);
}
private void ReconfigureEntityTypesAsComplexType(IEnumerable<EntityTypeConfiguration> misconfiguredEntityTypes)
{
IEnumerable<EntityTypeConfiguration> actualEntityTypes = StructuralTypes
.Except(misconfiguredEntityTypes)
.OfType<EntityTypeConfiguration>()
.ToArray();
foreach (EntityTypeConfiguration misconfiguredEntityType in misconfiguredEntityTypes)
{
RemoveStructuralType(misconfiguredEntityType.ClrType);
// this is a wrongly inferred type. so just ignore any pending configuration from it.
AddComplexType(misconfiguredEntityType.ClrType);
foreach (EntityTypeConfiguration entityToBePatched in actualEntityTypes)
{
NavigationPropertyConfiguration[] propertiesToBeRemoved = entityToBePatched
.NavigationProperties
.Where(navigationProperty => navigationProperty.RelatedClrType == misconfiguredEntityType.ClrType)
.ToArray();
foreach (NavigationPropertyConfiguration propertyToBeRemoved in propertiesToBeRemoved)
{
string propertyNameAlias = propertyToBeRemoved.Name;
PropertyConfiguration propertyConfiguration;
entityToBePatched.RemoveProperty(propertyToBeRemoved.PropertyInfo);
if (propertyToBeRemoved.Multiplicity == EdmMultiplicity.Many)
{
propertyConfiguration = entityToBePatched.AddCollectionProperty(propertyToBeRemoved.PropertyInfo);
}
else
{
propertyConfiguration = entityToBePatched.AddComplexProperty(propertyToBeRemoved.PropertyInfo);
}
propertyConfiguration.Name = propertyNameAlias;
}
}
}
}
private void MapTypes()
{
foreach (StructuralTypeConfiguration edmType in _explicitlyAddedTypes)
{
MapType(edmType);
}
}
private void MapType(StructuralTypeConfiguration edmType)
{
if (!_mappedTypes.Contains(edmType))
{
_mappedTypes.Add(edmType);
EntityTypeConfiguration entity = edmType as EntityTypeConfiguration;
if (entity != null)
{
MapEntityType(entity);
}
else
{
MapComplexType(edmType as ComplexTypeConfiguration);
}
ApplyTypeAndPropertyConventions(edmType);
}
}
private void MapEntityType(EntityTypeConfiguration entity)
{
IEnumerable<PropertyInfo> properties = ConventionsHelpers.GetProperties(entity, includeReadOnly: _isQueryCompositionMode);
foreach (PropertyInfo property in properties)
{
bool isCollection;
StructuralTypeConfiguration mappedType;
PropertyKind propertyKind = GetPropertyType(property, out isCollection, out mappedType);
if (propertyKind == PropertyKind.Primitive || propertyKind == PropertyKind.Complex)
{
MapStructuralProperty(entity, property, propertyKind, isCollection);
}
else
{
// don't add this property if the user has already added it.
if (!entity.NavigationProperties.Where(p => p.Name == property.Name).Any())
{
NavigationPropertyConfiguration addedNavigationProperty;
if (!isCollection)
{
addedNavigationProperty = entity.AddNavigationProperty(property, EdmMultiplicity.ZeroOrOne);
}
else
{
addedNavigationProperty = entity.AddNavigationProperty(property, EdmMultiplicity.Many);
}
addedNavigationProperty.AddedExplicitly = false;
}
}
}
MapDerivedTypes(entity);
}
private void MapComplexType(ComplexTypeConfiguration complexType)
{
IEnumerable<PropertyInfo> properties = ConventionsHelpers.GetAllProperties(complexType, includeReadOnly: _isQueryCompositionMode);
foreach (PropertyInfo property in properties)
{
bool isCollection;
StructuralTypeConfiguration mappedType;
PropertyKind propertyKind = GetPropertyType(property, out isCollection, out mappedType);
if (propertyKind == PropertyKind.Primitive || propertyKind == PropertyKind.Complex)
{
MapStructuralProperty(complexType, property, propertyKind, isCollection);
}
else
{
// navigation property in a complex type ?
if (mappedType == null)
{
// the user told nothing about this type and this is the first time we are seeing this type.
// complex types cannot contain entities. So, treat it as complex property.
MapStructuralProperty(complexType, property, PropertyKind.Complex, isCollection);
}
else if (_explicitlyAddedTypes.Contains(mappedType))
{
// user told us that this is an entity type.
throw Error.InvalidOperation(SRResources.ComplexTypeRefersToEntityType, complexType.ClrType.FullName, mappedType.ClrType.FullName, property.Name);
}
else
{
// we tried to be over-smart earlier and made the bad choice. so patch up now.
EntityTypeConfiguration mappedTypeAsEntity = mappedType as EntityTypeConfiguration;
Contract.Assert(mappedTypeAsEntity != null);
ReconfigureEntityTypesAsComplexType(new EntityTypeConfiguration[] { mappedTypeAsEntity });
MapStructuralProperty(complexType, property, PropertyKind.Complex, isCollection);
}
}
}
}
private void MapStructuralProperty(StructuralTypeConfiguration type, PropertyInfo property, PropertyKind propertyKind, bool isCollection)
{
Contract.Assert(type != null);
Contract.Assert(property != null);
Contract.Assert(propertyKind == PropertyKind.Complex || propertyKind == PropertyKind.Primitive);
bool addedExplicitly = type.Properties.Any(p => p.PropertyInfo.Name == property.Name);
PropertyConfiguration addedEdmProperty;
if (!isCollection)
{
if (propertyKind == PropertyKind.Primitive)
{
addedEdmProperty = type.AddProperty(property);
}
else
{
addedEdmProperty = type.AddComplexProperty(property);
}
}
else
{
if (_isQueryCompositionMode)
{
Contract.Assert(propertyKind != PropertyKind.Complex, "we don't create complex types in query composition mode.");
}
addedEdmProperty = type.AddCollectionProperty(property);
}
addedEdmProperty.AddedExplicitly = addedExplicitly;
}
// figures out the type of the property (primitive, complex, navigation) and the corresponding edm type if we have seen this type
// earlier or the user told us about it.
private PropertyKind GetPropertyType(PropertyInfo property, out bool isCollection, out StructuralTypeConfiguration mappedType)
{
Contract.Assert(property != null);
if (EdmLibHelpers.GetEdmPrimitiveTypeOrNull(property.PropertyType) != null)
{
isCollection = false;
mappedType = null;
return PropertyKind.Primitive;
}
else
{
mappedType = GetStructuralTypeOrNull(property.PropertyType);
if (mappedType != null)
{
isCollection = false;
if (mappedType is ComplexTypeConfiguration)
{
return PropertyKind.Complex;
}
else
{
return PropertyKind.Navigation;
}
}
else
{
Type elementType;
if (property.PropertyType.IsCollection(out elementType))
{
isCollection = true;
// Collection properties - can be a collection of primitives, complex or entities.
if (EdmLibHelpers.GetEdmPrimitiveTypeOrNull(elementType) != null)
{
return PropertyKind.Primitive;
}
else
{
mappedType = GetStructuralTypeOrNull(elementType);
if (mappedType != null && mappedType is ComplexTypeConfiguration)
{
return PropertyKind.Complex;
}
else
{
// if we know nothing about this type we assume it to be an entity
// and patch up later
return PropertyKind.Navigation;
}
}
}
else
{
// if we know nothing about this type we assume it to be an entity
// and patch up later
isCollection = false;
return PropertyKind.Navigation;
}
}
}
}
// the convention model builder MapTypes() method might have went through deep object graphs and added a bunch of types
// only to realise after applying the conventions that the user has ignored some of the properties. So, prune the unreachable stuff.
private void PruneUnreachableTypes()
{
Contract.Assert(_explicitlyAddedTypes != null);
// Do a BFS starting with the types the user has explicitly added to find out the unreachable nodes.
Queue<StructuralTypeConfiguration> reachableTypes = new Queue<StructuralTypeConfiguration>(_explicitlyAddedTypes);
HashSet<StructuralTypeConfiguration> visitedTypes = new HashSet<StructuralTypeConfiguration>();
while (reachableTypes.Count != 0)
{
StructuralTypeConfiguration currentType = reachableTypes.Dequeue();
// go visit other end of each of this node's edges.
foreach (PropertyConfiguration property in currentType.Properties.Where(property => property.Kind != PropertyKind.Primitive))
{
if (property.Kind == PropertyKind.Collection)
{
// if the elementType is primitive we don't need to do anything.
CollectionPropertyConfiguration colProperty = property as CollectionPropertyConfiguration;
if (EdmLibHelpers.GetEdmPrimitiveTypeOrNull(colProperty.ElementType) != null)
{
continue;
}
}
StructuralTypeConfiguration propertyType = GetStructuralTypeOrNull(property.RelatedClrType);
Contract.Assert(propertyType != null, "we should already have seen this type");
if (!visitedTypes.Contains(propertyType))
{
reachableTypes.Enqueue(propertyType);
}
}
// all derived types and the base type are also reachable
EntityTypeConfiguration currentEntityType = currentType as EntityTypeConfiguration;
if (currentEntityType != null)
{
if (currentEntityType.BaseType != null && !visitedTypes.Contains(currentEntityType.BaseType))
{
reachableTypes.Enqueue(currentEntityType.BaseType);
}
foreach (EntityTypeConfiguration derivedType in this.DerivedTypes(currentEntityType))
{
if (!visitedTypes.Contains(derivedType))
{
reachableTypes.Enqueue(derivedType);
}
}
}
visitedTypes.Add(currentType);
}
StructuralTypeConfiguration[] allConfiguredTypes = StructuralTypes.ToArray();
foreach (StructuralTypeConfiguration type in allConfiguredTypes)
{
if (!visitedTypes.Contains(type))
{
// we don't have to fix up any properties because this type is unreachable and cannot be a property of any reachable type.
RemoveStructuralType(type.ClrType);
}
}
}
private void ApplyTypeAndPropertyConventions(StructuralTypeConfiguration edmTypeConfiguration)
{
foreach (IConvention convention in _conventions)
{
IEdmTypeConvention typeConvention = convention as IEdmTypeConvention;
if (typeConvention != null)
{
typeConvention.Apply(edmTypeConfiguration, this);
}
IEdmPropertyConvention propertyConvention = convention as IEdmPropertyConvention;
if (propertyConvention != null)
{
ApplyPropertyConvention(propertyConvention, edmTypeConfiguration);
}
}
}
private void ApplyEntitySetConventions(EntitySetConfiguration entitySetConfiguration)
{
if (!_configuredEntitySets.Contains(entitySetConfiguration))
{
_configuredEntitySets.Add(entitySetConfiguration);
foreach (IEntitySetConvention convention in _conventions.OfType<IEntitySetConvention>())
{
if (convention != null)
{
convention.Apply(entitySetConfiguration, this);
}
}
}
}
private void ApplyProcedureConventions(ProcedureConfiguration procedure)
{
foreach (IProcedureConvention convention in _conventions.OfType<IProcedureConvention>())
{
convention.Apply(procedure, this);
}
}
private StructuralTypeConfiguration GetStructuralTypeOrNull(Type clrType)
{
return StructuralTypes.Where(edmType => edmType.ClrType == clrType).SingleOrDefault();
}
private static void ApplyPropertyConvention(IEdmPropertyConvention propertyConvention, StructuralTypeConfiguration edmTypeConfiguration)
{
Contract.Assert(propertyConvention != null);
Contract.Assert(edmTypeConfiguration != null);
foreach (PropertyConfiguration property in edmTypeConfiguration.Properties.ToArray())
{
propertyConvention.Apply(property, edmTypeConfiguration);
}
}
private static Dictionary<Type, List<Type>> BuildDerivedTypesMapping(IAssembliesResolver assemblyResolver)
{
IEnumerable<Type> allTypes = TypeHelper.GetLoadedTypes(assemblyResolver).Where(t => t.IsVisible && t.IsClass && t != typeof(object));
Dictionary<Type, List<Type>> allTypeMapping = allTypes.ToDictionary(k => k, k => new List<Type>());
foreach (Type type in allTypes)
{
List<Type> derivedTypes;
if (type.BaseType != null && allTypeMapping.TryGetValue(type.BaseType, out derivedTypes))
{
derivedTypes.Add(type);
}
}
return allTypeMapping;
}
}
}

Просмотреть файл

@ -0,0 +1,405 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Web.Http.OData.Formatter;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// <see cref="ODataModelBuilder"/> is used to map CLR classes to an EDM model.
/// </summary>
// TODO: Feature 443884: add support for starting from an original model
public class ODataModelBuilder
{
private static readonly Version _defaultDataServiceVersion = new Version(3, 0);
private static readonly Version _defaultMaxDataServiceVersionn = new Version(3, 0);
private Dictionary<Type, StructuralTypeConfiguration> _structuralTypes = new Dictionary<Type, StructuralTypeConfiguration>();
private Dictionary<string, EntitySetConfiguration> _entitySets = new Dictionary<string, EntitySetConfiguration>();
private Dictionary<Type, PrimitiveTypeConfiguration> _primitiveTypes = new Dictionary<Type, PrimitiveTypeConfiguration>();
private List<ProcedureConfiguration> _procedures = new List<ProcedureConfiguration>();
private Version _dataServiceVersion;
private Version _maxDataServiceVersion;
/// <summary>
/// Initializes a new instance of the <see cref="ODataModelBuilder"/> class.
/// </summary>
public ODataModelBuilder()
{
Namespace = "Default";
ContainerName = "Container";
DataServiceVersion = _defaultDataServiceVersion;
MaxDataServiceVersion = _defaultMaxDataServiceVersionn;
// TODO: change ModelAliasingEnabled's default value to true in v4
}
/// <summary>
/// Gets or sets the namespace that will be used for the resulting model
/// </summary>
public string Namespace { get; set; }
/// <summary>
/// Gets or sets the name of the container that will hold all the EntitySets, Actions and Functions
/// </summary>
public string ContainerName { get; set; }
/// <summary>
/// Gets or sets the data service version of the model. The default value is 3.0.
/// </summary>
public Version DataServiceVersion
{
get
{
return _dataServiceVersion;
}
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_dataServiceVersion = value;
}
}
/// <summary>
/// Gets or sets the maximum data service version of the model. The default value is 3.0.
/// </summary>
public Version MaxDataServiceVersion
{
get
{
return _maxDataServiceVersion;
}
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_maxDataServiceVersion = value;
}
}
/// <summary>
/// Gets or sets model aliasing is enabled or not. The default value is false.
/// </summary>
public bool ModelAliasingEnabled { get; set; }
/// <summary>
/// Gets the collection of EDM entity sets in the model to be built.
/// </summary>
public virtual IEnumerable<EntitySetConfiguration> EntitySets
{
get { return _entitySets.Values; }
}
/// <summary>
/// Gets the collection of EDM types in the model to be built.
/// </summary>
public virtual IEnumerable<StructuralTypeConfiguration> StructuralTypes
{
get { return _structuralTypes.Values; }
}
/// <summary>
/// Gets the collection of Procedures (i.e. Actions, Functions and ServiceOperations) in the model to be built
/// </summary>
public virtual IEnumerable<ProcedureConfiguration> Procedures
{
get { return _procedures; }
}
/// <summary>
/// Registers an entity type as part of the model and returns an object that can be used to configure the entity.
/// This method can be called multiple times for the same entity to perform multiple lines of configuration.
/// </summary>
/// <typeparam name="TEntityType">The type to be registered or configured.</typeparam>
/// <returns>The configuration object for the specified entity type.</returns>
public EntityTypeConfiguration<TEntityType> Entity<TEntityType>() where TEntityType : class
{
return new EntityTypeConfiguration<TEntityType>(this, AddEntity(typeof(TEntityType)));
}
/// <summary>
/// Registers a type as a complex type in the model and returns an object that can be used to configure the complex type.
/// This method can be called multiple times for the same type to perform multiple lines of configuration.
/// </summary>
/// <typeparam name="TComplexType">The type to be registered or configured.</typeparam>
/// <returns>The configuration object for the specified complex type.</returns>
public ComplexTypeConfiguration<TComplexType> ComplexType<TComplexType>() where TComplexType : class
{
return new ComplexTypeConfiguration<TComplexType>(AddComplexType(typeof(TComplexType)));
}
/// <summary>
/// Registers an entity set as a part of the model and returns an object that can be used to configure the entity set.
/// This method can be called multiple times for the same type to perform multiple lines of configuration.
/// </summary>
/// <typeparam name="TEntityType">The entity type of the entity set.</typeparam>
/// <param name="name">The name of the entity set.</param>
/// <returns>The configuration object for the specified entity set.</returns>
public EntitySetConfiguration<TEntityType> EntitySet<TEntityType>(string name) where TEntityType : class
{
EntityTypeConfiguration entity = AddEntity(typeof(TEntityType));
return new EntitySetConfiguration<TEntityType>(this, AddEntitySet(name, entity));
}
/// <summary>
/// Adds a non-bindable action to the builder.
/// </summary>
/// <param name="name">The name of the action.</param>
/// <returns>The configuration object for the specified action.</returns>
public virtual ActionConfiguration Action(string name)
{
ActionConfiguration action = new ActionConfiguration(this, name);
_procedures.Add(action);
return action;
}
/// <summary>
/// Adds a non-bindable function to the builder.
/// </summary>
/// <param name="name">The name of the function.</param>
/// <returns>The configuration object for the specified function.</returns>
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", Justification = "Consistent with term in EdmLib.")]
public virtual FunctionConfiguration Function(string name)
{
FunctionConfiguration function = new FunctionConfiguration(this, name);
_procedures.Add(function);
return function;
}
/// <summary>
/// Registers an entity type as part of the model and returns an object that can be used to configure the entity.
/// This method can be called multiple times for the same entity to perform multiple lines of configuration.
/// </summary>
/// <param name="type">The type to be registered or configured.</param>
/// <returns>The configuration object for the specified entity type.</returns>
public virtual EntityTypeConfiguration AddEntity(Type type)
{
if (type == null)
{
throw Error.ArgumentNull("type");
}
if (!_structuralTypes.ContainsKey(type))
{
EntityTypeConfiguration entityTypeConfig = new EntityTypeConfiguration(this, type);
_structuralTypes.Add(type, entityTypeConfig);
return entityTypeConfig;
}
else
{
EntityTypeConfiguration config = _structuralTypes[type] as EntityTypeConfiguration;
if (config == null || config.ClrType != type)
{
throw Error.Argument("type", SRResources.TypeCannotBeEntityWasComplex, type.FullName);
}
return config;
}
}
/// <summary>
/// Registers an complex type as part of the model and returns an object that can be used to configure the entity.
/// This method can be called multiple times for the same entity to perform multiple lines of configuration.
/// </summary>
/// <param name="type">The type to be registered or configured.</param>
/// <returns>The configuration object for the specified complex type.</returns>
public virtual ComplexTypeConfiguration AddComplexType(Type type)
{
if (type == null)
{
throw Error.ArgumentNull("type");
}
if (!_structuralTypes.ContainsKey(type))
{
ComplexTypeConfiguration complexTypeConfig = new ComplexTypeConfiguration(this, type);
_structuralTypes.Add(type, complexTypeConfig);
return complexTypeConfig;
}
else
{
ComplexTypeConfiguration complexTypeConfig = _structuralTypes[type] as ComplexTypeConfiguration;
if (complexTypeConfig == null || complexTypeConfig.ClrType != type)
{
throw Error.Argument("type", SRResources.TypeCannotBeComplexWasEntity, type.FullName);
}
return complexTypeConfig;
}
}
/// <summary>
/// Adds a procedure to the model.
/// </summary>
public virtual void AddProcedure(ProcedureConfiguration procedure)
{
_procedures.Add(procedure);
}
/// <summary>
/// Registers an entity set as a part of the model and returns an object that can be used to configure the entity set.
/// This method can be called multiple times for the same type to perform multiple lines of configuration.
/// </summary>
/// <param name="name">The name of the entity set.</param>
/// <param name="entityType">The type to be registered or configured.</param>
/// <returns>The configuration object for the specified entity set.</returns>
public virtual EntitySetConfiguration AddEntitySet(string name, EntityTypeConfiguration entityType)
{
if (String.IsNullOrWhiteSpace(name))
{
throw Error.ArgumentNullOrEmpty("name");
}
if (entityType == null)
{
throw Error.ArgumentNull("entityType");
}
if (name.Contains("."))
{
throw Error.NotSupported(SRResources.InvalidEntitySetName, name);
}
EntitySetConfiguration entitySet = null;
if (_entitySets.ContainsKey(name))
{
entitySet = _entitySets[name] as EntitySetConfiguration;
if (entitySet.EntityType != entityType)
{
throw Error.Argument("entityType", SRResources.EntitySetAlreadyConfiguredDifferentEntityType, entitySet.Name, entitySet.EntityType.Name);
}
}
else
{
entitySet = new EntitySetConfiguration(this, entityType, name);
_entitySets[name] = entitySet;
}
return entitySet;
}
/// <summary>
/// Removes the type from the model.
/// </summary>
/// <param name="type">The type to be removed</param>
/// <returns><see>true</see> if the type is present in the model and <see>false</see> otherwise.</returns>
public virtual bool RemoveStructuralType(Type type)
{
if (type == null)
{
throw Error.ArgumentNull("type");
}
return _structuralTypes.Remove(type);
}
/// <summary>
/// Removes the entity set from the model.
/// </summary>
/// <param name="name">The name of the entity set to be removed</param>
/// <returns><see>true</see> if the entity set is present in the model and <see>false</see> otherwise.</returns>
public virtual bool RemoveEntitySet(string name)
{
if (name == null)
{
throw Error.ArgumentNull("name");
}
return _entitySets.Remove(name);
}
/// <summary>
/// Remove the procedure from the model
/// <remarks>
/// If there is more than one procedure with the name specified this method will not work.
/// You need to use the other RemoveProcedure(..) overload instead.
/// </remarks>
/// </summary>
/// <param name="name">The name of the procedure to be removed</param>
/// <returns><see>true</see> if the procedure is present in the model and <see>false</see> otherwise.</returns>
public virtual bool RemoveProcedure(string name)
{
if (name == null)
{
throw Error.ArgumentNull("name");
}
ProcedureConfiguration[] toRemove = _procedures.Where(p => p.Name == name).ToArray();
int count = toRemove.Count();
if (count == 1)
{
return RemoveProcedure(toRemove[0]);
}
else if (count == 0)
{
// For consistency with RemoveStructuralType().
// uses same semantics as Dictionary.Remove(key).
return false;
}
else
{
throw Error.InvalidOperation(SRResources.MoreThanOneProcedureFound, name);
}
}
/// <summary>
/// Remove the procedure from the model
/// </summary>
/// <param name="procedure">The procedure to be removed</param>
/// <returns><see>true</see> if the procedure is present in the model and <see>false</see> otherwise.</returns>
public virtual bool RemoveProcedure(ProcedureConfiguration procedure)
{
if (procedure == null)
{
throw Error.ArgumentNull("procedure");
}
return _procedures.Remove(procedure);
}
/// <summary>
/// Attempts to find either a pre-configured structural type or a primitive type that matches the T.
/// If no matches are found NULL is returned.
/// </summary>
public IEdmTypeConfiguration GetTypeConfigurationOrNull(Type type)
{
if (_primitiveTypes.ContainsKey(type))
{
return _primitiveTypes[type];
}
else
{
IEdmPrimitiveType edmType = EdmLibHelpers.GetEdmPrimitiveTypeOrNull(type);
PrimitiveTypeConfiguration primitiveType = null;
if (edmType != null)
{
primitiveType = new PrimitiveTypeConfiguration(this, edmType, type);
_primitiveTypes[type] = primitiveType;
return primitiveType;
}
else if (_structuralTypes.ContainsKey(type))
{
return _structuralTypes[type];
}
}
return null;
}
/// <summary>
/// Creates a <see cref="IEdmModel"/> based on the configuration performed using this builder.
/// </summary>
/// <returns>The model that was built.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Property is not appropriate, method does work")]
public virtual IEdmModel GetEdmModel()
{
return EdmModelHelperMethods.BuildEdmModel(this);
}
}
}

Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Represents a parameter to a Procedure
/// </summary>
public abstract class ParameterConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="ParameterConfiguration"/> class.
/// </summary>
/// <param name="name">The name of the parameter.</param>
/// <param name="parameterType">The EDM type of the paramter.</param>
protected ParameterConfiguration(string name, IEdmTypeConfiguration parameterType)
{
if (name == null)
{
throw Error.ArgumentNull("name");
}
if (parameterType == null)
{
throw Error.ArgumentNull("bindingParameterType");
}
Name = name;
TypeConfiguration = parameterType;
}
/// <summary>
/// The name of the parameter
/// </summary>
public string Name { get; protected set; }
/// <summary>
/// The type of the parameter
/// </summary>
public IEdmTypeConfiguration TypeConfiguration { get; protected set; }
}
}

Просмотреть файл

@ -0,0 +1,69 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Reflection;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Used to configure a primitive property of an entity type or complex type.
/// This configuration functionality is exposed by the model builder Fluent API, see <see cref="ODataModelBuilder"/>.
/// </summary>
public class PrimitivePropertyConfiguration : StructuralPropertyConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="PrimitivePropertyConfiguration"/> class.
/// </summary>
/// <param name="property">The name of the property.</param>
/// <param name="declaringType">The declaring EDM type of the property.</param>
public PrimitivePropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType)
: base(property, declaringType)
{
}
/// <summary>
/// Gets the type of this property.
/// </summary>
public override PropertyKind Kind
{
get { return PropertyKind.Primitive; }
}
/// <summary>
/// Gets the backing CLR type of this property type.
/// </summary>
public override Type RelatedClrType
{
get { return PropertyInfo.PropertyType; }
}
/// <summary>
/// Configures the property to be optional.
/// </summary>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public PrimitivePropertyConfiguration IsOptional()
{
OptionalProperty = true;
return this;
}
/// <summary>
/// Configures the property to be required.
/// </summary>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public PrimitivePropertyConfiguration IsRequired()
{
OptionalProperty = false;
return this;
}
/// <summary>
/// Configures the property to be used in concurrency checks. For OData this means to be part of the ETag.
/// </summary>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public PrimitivePropertyConfiguration IsConcurrencyToken()
{
ConcurrencyToken = true;
return this;
}
}
}

Просмотреть файл

@ -0,0 +1,95 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Represents a PrimitiveType
/// </summary>
public class PrimitiveTypeConfiguration : IEdmTypeConfiguration
{
private Type _clrType;
private IEdmPrimitiveType _edmType;
private ODataModelBuilder _builder;
/// <summary>
/// This constructor is public only for unit testing purposes.
/// To get a PrimitiveTypeConfiguration use ODataModelBuilder.GetTypeConfigurationOrNull(Type)
/// </summary>
public PrimitiveTypeConfiguration(ODataModelBuilder builder, IEdmPrimitiveType edmType, Type clrType)
{
if (builder == null)
{
throw Error.ArgumentNull("builder");
}
if (edmType == null)
{
throw Error.ArgumentNull("edmType");
}
if (clrType == null)
{
throw Error.ArgumentNull("clrType");
}
_builder = builder;
_clrType = clrType;
_edmType = edmType;
}
/// <summary>
/// Gets the backing CLR type of this EDM type.
/// </summary>
public Type ClrType
{
get { return _clrType; }
}
/// <summary>
/// Gets the full name of this EDM type.
/// </summary>
public string FullName
{
get { return _edmType.FullName(); }
}
/// <summary>
/// Gets the namespace of this EDM type.
/// </summary>
public string Namespace
{
get { return _edmType.Namespace; }
}
/// <summary>
/// Gets the name of this EDM type.
/// </summary>
public string Name
{
get { return _edmType.Name; }
}
/// <summary>
/// Gets the <see cref="EdmTypeKind"/> of this EDM type.
/// </summary>
public EdmTypeKind Kind
{
get { return EdmTypeKind.Primitive; }
}
/// <summary>
/// Gets the <see cref="ODataModelBuilder"/> used to create this configuration.
/// </summary>
public ODataModelBuilder ModelBuilder
{
get { return _builder; }
}
/// <summary>
/// Returns the IEdmPrimitiveType associated with this PrimitiveTypeConfiguration
/// </summary>
public IEdmPrimitiveType EdmPrimitiveType
{
get { return _edmType; }
}
}
}

Просмотреть файл

@ -0,0 +1,307 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Represents a Procedure that is exposed in the model
/// </summary>
public abstract class ProcedureConfiguration
{
private List<ParameterConfiguration> _parameters = new List<ParameterConfiguration>();
private BindingParameterConfiguration _bindingParameter;
/// <summary>
/// Initializes a new instance of <see cref="ProcedureConfiguration" /> class.
/// </summary>
/// <param name="builder">The ODataModelBuilder to which this ProcedureConfiguration should be added.</param>
/// <param name="name">The name of this ProcedureConfiguration.</param>
internal ProcedureConfiguration(ODataModelBuilder builder, string name)
{
Name = name;
ModelBuilder = builder;
}
/// <summary>
/// Gets or sets the currently registered procedure link factory.
/// </summary>
protected Func<EntityInstanceContext, Uri> LinkFactory { get; set; }
/// <summary>
/// Gets a value indicating whether procedure links follow OData conventions.
/// </summary>
public bool FollowsConventions { get; protected set; }
/// <summary>
/// The Name of the procedure
/// </summary>
public string Name { get; protected set; }
/// <summary>
/// The Kind of procedure, which can be either Action or Function
/// </summary>
public abstract ProcedureKind Kind { get; }
/// <summary>
/// Can the procedure be composed upon.
///
/// For example can a URL that invokes the procedure be used as the base url for
/// a request that invokes the procedure and does something else with the results
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Copies existing spelling used in EdmLib.")]
public virtual bool IsComposable { get; internal set; }
/// <summary>
/// Does the procedure have side-effects.
/// </summary>
public abstract bool IsSideEffecting { get; }
/// <summary>
/// The qualified name of the procedure when used in OData urls.
/// Qualification is required to distinguish the procedure from other possible single part identifiers.
/// </summary>
public string ContainerQualifiedName
{
get { return ModelBuilder.ContainerName + "." + Name; }
}
/// <summary>
/// The FullyQualifiedName is the ContainerQualifiedName further qualified using the Namespace.
/// Typically this is not required, because most services have at most one container with the same name.
/// </summary>
public string FullyQualifiedName
{
get { return ModelBuilder.Namespace + "." + ContainerQualifiedName; }
}
/// <summary>
/// The FullName is the ContainerQualifiedName.
/// </summary>
public string FullName
{
get { return ContainerQualifiedName; }
}
/// <summary>
/// The type returned when the procedure is invoked.
/// </summary>
public IEdmTypeConfiguration ReturnType { get; set; }
/// <summary>
/// The EntitySet that entities are returned from.
/// </summary>
public EntitySetConfiguration EntitySet { get; set; }
/// <summary>
/// The EntitySetPathExpression that entities are returned from.
/// </summary>
public IEnumerable<string> EntitySetPath { get; internal set; }
/// <summary>
/// Get the bindingParameter.
/// <remarks>Null means the procedure has no bindingParameter.</remarks>
/// </summary>
public virtual BindingParameterConfiguration BindingParameter
{
get { return _bindingParameter; }
}
/// <summary>
/// The parameters the procedure takes
/// </summary>
public virtual IEnumerable<ParameterConfiguration> Parameters
{
get
{
if (_bindingParameter != null)
{
yield return _bindingParameter;
}
foreach (ParameterConfiguration parameter in _parameters)
{
yield return parameter;
}
}
}
/// <summary>
/// Can the procedure be bound to a url representing the BindingParameter.
/// </summary>
public virtual bool IsBindable
{
get
{
return _bindingParameter != null;
}
}
/// <summary>
/// Whether this procedure can always be bound.
/// <example>
/// For example imagine an Watch action that can be bound to a Movie, it might not always be possible to Watch a movie,
/// in which case IsAlwaysBindable would return false.
/// </example>
/// </summary>
public virtual bool IsAlwaysBindable
{
get
{
if (IsBindable)
{
return _bindingParameter.AlwaysBindable;
}
return false;
}
}
/// <summary>
/// Sets the return type to a single EntityType instance.
/// </summary>
/// <typeparam name="TEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetName">The entitySetName which contains the return EntityType instance</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
internal void ReturnsFromEntitySetImplementation<TEntityType>(string entitySetName) where TEntityType : class
{
ModelBuilder.EntitySet<TEntityType>(entitySetName);
EntitySet = ModelBuilder.EntitySets.Single(s => s.Name == entitySetName);
ReturnType = ModelBuilder.GetTypeConfigurationOrNull(typeof(TEntityType));
}
/// <summary>
/// Sets the return type to a collection of EntityType instances.
/// </summary>
/// <typeparam name="TElementEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetName">The entitySetName which contains the returned EntityType instances</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
internal void ReturnsCollectionFromEntitySetImplementation<TElementEntityType>(string entitySetName) where TElementEntityType : class
{
Type clrCollectionType = typeof(IEnumerable<TElementEntityType>);
ModelBuilder.EntitySet<TElementEntityType>(entitySetName);
EntitySet = ModelBuilder.EntitySets.Single(s => s.Name == entitySetName);
IEdmTypeConfiguration elementType = ModelBuilder.GetTypeConfigurationOrNull(typeof(TElementEntityType));
ReturnType = new CollectionTypeConfiguration(elementType, clrCollectionType);
}
/// <summary>
/// Sets the return type to a single EntityType instance.
/// </summary>
/// <typeparam name="TEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetPath">The entitySetPath which contains the return EntityType instance</param>
internal void ReturnsEntityViaEntitySetPathImplementation<TEntityType>(IEnumerable<string> entitySetPath) where TEntityType : class
{
ReturnType = ModelBuilder.GetTypeConfigurationOrNull(typeof(TEntityType));
EntitySetPath = entitySetPath;
}
/// <summary>
/// Sets the return type to a collection of EntityType instances.
/// </summary>
/// <typeparam name="TElementEntityType">The type that is an EntityType</typeparam>
/// <param name="entitySetPath">The entitySetPath which contains the returned EntityType instances</param>
internal void ReturnsCollectionViaEntitySetPathImplementation<TElementEntityType>(IEnumerable<string> entitySetPath) where TElementEntityType : class
{
Type clrCollectionType = typeof(IEnumerable<TElementEntityType>);
IEdmTypeConfiguration elementType = ModelBuilder.GetTypeConfigurationOrNull(typeof(TElementEntityType));
ReturnType = new CollectionTypeConfiguration(elementType, clrCollectionType);
EntitySetPath = entitySetPath;
}
/// <summary>
/// Established the return type of the procedure.
/// <remarks>Used when the return type is a single Primitive or ComplexType.</remarks>
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
internal void ReturnsImplementation<TReturnType>()
{
Type returnType = typeof(TReturnType);
IEdmTypeConfiguration configuration = ModelBuilder.GetTypeConfigurationOrNull(returnType);
if (configuration == null)
{
ModelBuilder.AddComplexType(returnType);
configuration = ModelBuilder.GetTypeConfigurationOrNull(typeof(TReturnType));
}
ReturnType = configuration;
}
/// <summary>
/// Establishes the return type of the procedure
/// <remarks>Used when the return type is a collection of either Primitive or ComplexTypes.</remarks>
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
internal void ReturnsCollectionImplementation<TReturnElementType>()
{
// TODO: I don't like this temporary solution that says the CLR type of the collection is IEnumerable<T>.
// It basically has no meaning. That said the CLR type is meaningful for IEdmTypeConfiguration
// because I still think it is useful for IEdmPrimitiveTypes too.
// You can imagine the override of this that takes a delegate using the correct CLR type for the return type.
Type clrCollectionType = typeof(IEnumerable<TReturnElementType>);
Type clrElementType = typeof(TReturnElementType);
IEdmTypeConfiguration edmElementType = ModelBuilder.GetTypeConfigurationOrNull(clrElementType);
if (edmElementType == null)
{
ModelBuilder.AddComplexType(clrElementType);
edmElementType = ModelBuilder.GetTypeConfigurationOrNull(clrElementType);
}
ReturnType = new CollectionTypeConfiguration(edmElementType, clrCollectionType);
}
/// <summary>
/// Specifies the bindingParameter name, type and whether it is alwaysBindable, use only if the procedure "isBindable".
/// </summary>
internal void SetBindingParameterImplementation(string name, IEdmTypeConfiguration bindingParameterType, bool alwaysBindable)
{
_bindingParameter = new BindingParameterConfiguration(name, bindingParameterType, alwaysBindable);
}
/// <summary>
/// Adds a new non-binding parameter.
/// </summary>
public ParameterConfiguration AddParameter(string name, IEdmTypeConfiguration parameterType)
{
ParameterConfiguration parameter = new NonbindingParameterConfiguration(name, parameterType);
_parameters.Add(parameter);
return parameter;
}
/// <summary>
/// Adds a new non-binding parameter
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public ParameterConfiguration Parameter<TParameter>(string name)
{
Type type = typeof(TParameter);
IEdmTypeConfiguration parameterType = ModelBuilder.GetTypeConfigurationOrNull(type);
if (parameterType == null)
{
ModelBuilder.AddComplexType(type);
parameterType = ModelBuilder.GetTypeConfigurationOrNull(type);
}
return AddParameter(name, parameterType);
}
/// <summary>
/// Adds a new non-binding collection parameter
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")]
public ParameterConfiguration CollectionParameter<TElementType>(string name)
{
Type elementType = typeof(TElementType);
IEdmTypeConfiguration elementTypeConfiguration = ModelBuilder.GetTypeConfigurationOrNull(elementType);
if (elementTypeConfiguration == null)
{
ModelBuilder.AddComplexType(elementType);
elementTypeConfiguration = ModelBuilder.GetTypeConfigurationOrNull(elementType);
}
CollectionTypeConfiguration parameterType = new CollectionTypeConfiguration(elementTypeConfiguration, typeof(IEnumerable<>).MakeGenericType(elementType));
return AddParameter(name, parameterType);
}
/// <summary>
/// Gets or sets the <see cref="ODataModelBuilder"/> used to create this configuration.
/// </summary>
protected ODataModelBuilder ModelBuilder { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// The Kind of OData Procedure.
/// One of Action, Function or ServiceOperation.
/// </summary>
public enum ProcedureKind
{
/// <summary>
/// An action
/// </summary>
Action = 0,
/// <summary>
/// A function
/// </summary>
Function = 1,
/// <summary>
/// A service operation
/// </summary>
ServiceOperation = 2
}
}

Просмотреть файл

@ -0,0 +1,183 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Reflection;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Base class for all property configurations.
/// </summary>
public abstract class PropertyConfiguration
{
private string _name;
/// <summary>
/// Initializes a new instance of the <see cref="PropertyConfiguration"/> class.
/// </summary>
/// <param name="property">The name of the property.</param>
/// <param name="declaringType">The declaring EDM type of the property.</param>
protected PropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType)
{
if (property == null)
{
throw Error.ArgumentNull("property");
}
if (declaringType == null)
{
throw Error.ArgumentNull("declaringType");
}
PropertyInfo = property;
DeclaringType = declaringType;
AddedExplicitly = true;
_name = property.Name;
}
/// <summary>
/// Gets or sets the name of the property.
/// </summary>
public string Name
{
get
{
return _name;
}
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_name = value;
}
}
/// <summary>
/// Gets the declaring type.
/// </summary>
public StructuralTypeConfiguration DeclaringType { get; private set; }
/// <summary>
/// Gets the mapping CLR <see cref="PropertyInfo"/>.
/// </summary>
public PropertyInfo PropertyInfo { get; private set; }
/// <summary>
/// Gets the CLR <see cref="Type"/> of the property.
/// </summary>
public abstract Type RelatedClrType { get; }
/// <summary>
/// Gets the <see cref="PropertyKind"/> of the property.
/// </summary>
public abstract PropertyKind Kind { get; }
/// <summary>
/// Gets or sets a value that is <see langword="true"/> if the property was added by the user; <see langword="false"/> if it was inferred through conventions.
/// </summary>
/// <remarks>The default value is <see langword="true"/></remarks>
public bool AddedExplicitly { get; set; }
/// <summary>
/// Gets whether the property is restricted, i.e. nonfilterable, unsortable, not navigable, or not expandable.
/// </summary>
public bool IsRestricted
{
get { return NonFilterable || Unsortable || NotNavigable || NotExpandable; }
}
/// <summary>
/// Gets or sets whether the property is nonfilterable. default is false.
/// </summary>
public bool NonFilterable { get; set; }
/// <summary>
/// Gets or sets whether the property is unsortable. default is false.
/// </summary>
public bool Unsortable { get; set; }
/// <summary>
/// Gets or sets whether the property is not navigable. default is false.
/// </summary>
public bool NotNavigable { get; set; }
/// <summary>
/// Gets or sets whether the property is not expandable. default is false.
/// </summary>
public bool NotExpandable { get; set; }
/// <summary>
/// Sets the property as nonfilterable.
/// </summary>
public PropertyConfiguration IsNonFilterable()
{
NonFilterable = true;
return this;
}
/// <summary>
/// Sets the property as filterable.
/// </summary>
public PropertyConfiguration IsFilterable()
{
NonFilterable = false;
return this;
}
/// <summary>
/// Sets the property as unsortable.
/// </summary>
public PropertyConfiguration IsUnsortable()
{
Unsortable = true;
return this;
}
/// <summary>
/// Sets the property as sortable.
/// </summary>
public PropertyConfiguration IsSortable()
{
Unsortable = false;
return this;
}
/// <summary>
/// Sets the property as not navigable.
/// </summary>
public PropertyConfiguration IsNotNavigable()
{
NotNavigable = true;
return this;
}
/// <summary>
/// Sets the property as navigable.
/// </summary>
public PropertyConfiguration IsNavigable()
{
NotNavigable = false;
return this;
}
/// <summary>
/// Sets the property as not expandable.
/// </summary>
public PropertyConfiguration IsNotExpandable()
{
NotExpandable = true;
return this;
}
/// <summary>
/// Sets the property as expandable.
/// </summary>
public PropertyConfiguration IsExpandable()
{
NotExpandable = false;
return this;
}
}
}

Просмотреть файл

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// The kind of the EDM property.
/// </summary>
public enum PropertyKind
{
/// <summary>
/// Represents an EDM primitive property.
/// </summary>
Primitive = 0,
/// <summary>
/// Represents an EDM complex property.
/// </summary>
Complex = 1,
/// <summary>
/// Represents an EDM collection property.
/// </summary>
Collection = 2,
/// <summary>
/// Represents an EDM navigation property.
/// </summary>
Navigation = 3
}
}

Просмотреть файл

@ -0,0 +1,110 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Http.OData.Properties;
namespace System.Web.Http.OData.Builder
{
internal class PropertySelectorVisitor : ExpressionVisitor
{
private List<PropertyInfo> _properties = new List<PropertyInfo>();
[SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "Class is internal, virtual call okay")]
internal PropertySelectorVisitor(Expression exp)
{
Visit(exp);
}
public PropertyInfo Property
{
get
{
return _properties.SingleOrDefault();
}
}
public ICollection<PropertyInfo> Properties
{
get
{
return _properties;
}
}
protected override Expression VisitMember(MemberExpression node)
{
if (node == null)
{
throw Error.ArgumentNull("node");
}
PropertyInfo pinfo = node.Member as PropertyInfo;
if (pinfo == null)
{
throw Error.InvalidOperation(SRResources.MemberExpressionsMustBeProperties, node.Member.ReflectedType.FullName, node.Member.Name);
}
if (node.Expression.NodeType != ExpressionType.Parameter)
{
throw Error.InvalidOperation(SRResources.MemberExpressionsMustBeBoundToLambdaParameter);
}
_properties.Add(pinfo);
return node;
}
public static PropertyInfo GetSelectedProperty(Expression exp)
{
return new PropertySelectorVisitor(exp).Property;
}
public static ICollection<PropertyInfo> GetSelectedProperties(Expression exp)
{
return new PropertySelectorVisitor(exp).Properties;
}
public override Expression Visit(Expression exp)
{
if (exp == null)
{
return exp;
}
switch (exp.NodeType)
{
case ExpressionType.New:
case ExpressionType.MemberAccess:
case ExpressionType.Lambda:
return base.Visit(exp);
default:
throw Error.NotSupported(SRResources.UnsupportedExpressionNodeType);
}
}
protected override Expression VisitLambda<T>(Expression<T> lambda)
{
if (lambda == null)
{
throw Error.ArgumentNull("lambda");
}
if (lambda.Parameters.Count != 1)
{
throw Error.InvalidOperation(SRResources.LambdaExpressionMustHaveExactlyOneParameter);
}
Expression body = Visit(lambda.Body);
if (body != lambda.Body)
{
return Expression.Lambda(lambda.Type, body, lambda.Parameters);
}
return lambda;
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Encapsulates a self link factory and whether the link factory follows conventions or not.
/// </summary>
/// <typeparam name="T">The type of the self link generated. This should be <see cref="string"/> for ID links and <see cref="Uri"/> for read and edit links.</typeparam>
public class SelfLinkBuilder<T>
{
/// <summary>
/// Constructs a new instance of <see cref="SelfLinkBuilder{T}"/>.
/// </summary>
/// <param name="linkFactory">The link factory.</param>
/// <param name="followsConventions">Whether the factory follows odata conventions for link generation.</param>
public SelfLinkBuilder(Func<EntityInstanceContext, T> linkFactory, bool followsConventions)
{
if (linkFactory == null)
{
throw Error.ArgumentNull("linkFactory");
}
Factory = linkFactory;
FollowsConventions = followsConventions;
}
/// <summary>
/// Gets the factory for generating links.
/// </summary>
public Func<EntityInstanceContext, T> Factory { get; private set; }
/// <summary>
/// Gets a boolean indicating whether the link factory follows OData conventions or not.
/// </summary>
public bool FollowsConventions { get; private set; }
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Reflection;
using System.Web.Http.OData.Formatter;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Base class for all structural property configurations.
/// </summary>
public abstract class StructuralPropertyConfiguration : PropertyConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="StructuralPropertyConfiguration"/> class.
/// </summary>
/// <param name="property">The property of the configuration.</param>
/// <param name="declaringType">The declaring type of the property.</param>
protected StructuralPropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType)
: base(property, declaringType)
{
OptionalProperty = EdmLibHelpers.IsNullable(property.PropertyType);
}
/// <summary>
/// Gets or sets a value indicating whether this property is optional or not.
/// </summary>
public bool OptionalProperty { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this property is a concurrency token or not.
/// </summary>
public bool ConcurrencyToken { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,339 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Web.Http.OData.Formatter;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Represents an <see cref="IEdmStructuredType"/> that can be built using <see cref="ODataModelBuilder"/>.
/// </summary>
public abstract class StructuralTypeConfiguration : IEdmTypeConfiguration
{
private const string DefaultNamespace = "Default";
private string _namespace;
private string _name;
/// <summary>
/// Initializes a new instance of the <see cref="StructuralTypeConfiguration"/> class.
/// </summary>
/// <remarks>The default constructor is intended for use by unit testing only.</remarks>
protected StructuralTypeConfiguration()
{
ExplicitProperties = new Dictionary<PropertyInfo, PropertyConfiguration>();
RemovedProperties = new List<PropertyInfo>();
}
/// <summary>
/// Initializes a new instance of the <see cref="StructuralTypeConfiguration"/> class.
/// </summary>
/// <param name="clrType">The backing CLR type for this EDM structural type.</param>
/// <param name="modelBuilder">The associated <see cref="ODataModelBuilder"/>.</param>
[SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "The virtual property setters are only to support mocking frameworks, in which case this constructor shouldn't be called anyway.")]
protected StructuralTypeConfiguration(ODataModelBuilder modelBuilder, Type clrType)
: this()
{
if (modelBuilder == null)
{
throw Error.ArgumentNull("modelBuilder");
}
if (clrType == null)
{
throw Error.ArgumentNull("clrType");
}
ClrType = clrType;
ModelBuilder = modelBuilder;
_name = clrType.EdmName();
_namespace = clrType.Namespace ?? DefaultNamespace;
}
/// <summary>
/// Gets the <see cref="EdmTypeKind"/> of this edm type.
/// </summary>
public abstract EdmTypeKind Kind { get; }
/// <summary>
/// Gets the backing CLR <see cref="Type"/>.
/// </summary>
public virtual Type ClrType { get; private set; }
/// <summary>
/// Gets the full name of this edm type.
/// </summary>
public virtual string FullName
{
get
{
return Namespace + "." + Name;
}
}
/// <summary>
/// Gets or sets the namespace of this EDM type.
/// </summary>
public virtual string Namespace
{
get
{
return _namespace;
}
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_namespace = value;
AddedExplicitly = true;
}
}
/// <summary>
/// Gets or sets the name of this EDM type.
/// </summary>
public virtual string Name
{
get
{
return _name;
}
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_name = value;
AddedExplicitly = true;
}
}
/// <summary>
/// Gets the declared properties on this edm type.
/// </summary>
public IEnumerable<PropertyConfiguration> Properties
{
get
{
return ExplicitProperties.Values;
}
}
/// <summary>
/// Gets the properties from the backing CLR type that are to be ignored on this edm type.
/// </summary>
public ReadOnlyCollection<PropertyInfo> IgnoredProperties
{
get
{
return new ReadOnlyCollection<PropertyInfo>(RemovedProperties);
}
}
/// <summary>
/// Gets or sets a value that is <see langword="true"/> if the type's name or namespace was set by the user; <see langword="false"/> if it was inferred through conventions.
/// </summary>
/// <remarks>The default value is <see langword="false"/>.</remarks>
public bool AddedExplicitly { get; set; }
/// <summary>
/// The <see cref="ODataModelBuilder"/>.
/// </summary>
public virtual ODataModelBuilder ModelBuilder { get; private set; }
/// <summary>
/// Gets the collection of explicitly removed properties.
/// </summary>
protected internal IList<PropertyInfo> RemovedProperties { get; private set; }
/// <summary>
/// Gets the collection of explicitly added properties.
/// </summary>
protected internal IDictionary<PropertyInfo, PropertyConfiguration> ExplicitProperties { get; private set; }
/// <summary>
/// Adds a primitive property to this edm type.
/// </summary>
/// <param name="propertyInfo">The property being added.</param>
/// <returns>The <see cref="PrimitivePropertyConfiguration"/> so that the property can be configured further.</returns>
public virtual PrimitivePropertyConfiguration AddProperty(PropertyInfo propertyInfo)
{
if (propertyInfo == null)
{
throw Error.ArgumentNull("propertyInfo");
}
if (!propertyInfo.ReflectedType.IsAssignableFrom(ClrType))
{
throw Error.Argument("propertyInfo", SRResources.PropertyDoesNotBelongToType, propertyInfo.Name, ClrType.FullName);
}
// Remove from the ignored properties
if (RemovedProperties.Contains(propertyInfo))
{
RemovedProperties.Remove(propertyInfo);
}
PrimitivePropertyConfiguration propertyConfiguration = null;
if (ExplicitProperties.ContainsKey(propertyInfo))
{
propertyConfiguration = ExplicitProperties[propertyInfo] as PrimitivePropertyConfiguration;
if (propertyConfiguration == null)
{
throw Error.Argument("propertyInfo", SRResources.MustBePrimitiveProperty, propertyInfo.Name, ClrType.FullName);
}
}
else
{
propertyConfiguration = new PrimitivePropertyConfiguration(propertyInfo, this);
ExplicitProperties[propertyInfo] = propertyConfiguration;
}
return propertyConfiguration;
}
/// <summary>
/// Adds a complex property to this edm type.
/// </summary>
/// <param name="propertyInfo">The property being added.</param>
/// <returns>The <see cref="ComplexPropertyConfiguration"/> so that the property can be configured further.</returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Helper validates non null propertyInfo")]
public virtual ComplexPropertyConfiguration AddComplexProperty(PropertyInfo propertyInfo)
{
if (propertyInfo == null)
{
throw Error.ArgumentNull("propertyInfo");
}
if (!propertyInfo.ReflectedType.IsAssignableFrom(ClrType))
{
throw Error.Argument("propertyInfo", SRResources.PropertyDoesNotBelongToType, propertyInfo.Name, ClrType.FullName);
}
if (propertyInfo.PropertyType == ClrType)
{
throw Error.Argument("propertyInfo", SRResources.RecursiveComplexTypesNotAllowed, ClrType.FullName, propertyInfo.Name);
}
// Remove from the ignored properties
if (RemovedProperties.Contains(propertyInfo))
{
RemovedProperties.Remove(propertyInfo);
}
ComplexPropertyConfiguration propertyConfiguration = null;
if (ExplicitProperties.ContainsKey(propertyInfo))
{
propertyConfiguration = ExplicitProperties[propertyInfo] as ComplexPropertyConfiguration;
if (propertyConfiguration == null)
{
throw Error.Argument("propertyInfo", SRResources.MustBeComplexProperty, propertyInfo.Name, ClrType.FullName);
}
}
else
{
propertyConfiguration = new ComplexPropertyConfiguration(propertyInfo, this);
ExplicitProperties[propertyInfo] = propertyConfiguration;
// Make sure the complex type is in the model.
ModelBuilder.AddComplexType(propertyInfo.PropertyType);
}
return propertyConfiguration;
}
/// <summary>
/// Adds a collection property to this edm type.
/// </summary>
/// <param name="propertyInfo">The property being added.</param>
/// <returns>The <see cref="CollectionPropertyConfiguration"/> so that the property can be configured further.</returns>
public virtual CollectionPropertyConfiguration AddCollectionProperty(PropertyInfo propertyInfo)
{
if (propertyInfo == null)
{
throw Error.ArgumentNull("propertyInfo");
}
if (!propertyInfo.DeclaringType.IsAssignableFrom(ClrType))
{
throw Error.Argument("propertyInfo", SRResources.PropertyDoesNotBelongToType);
}
// Remove from the ignored properties
if (IgnoredProperties.Contains(propertyInfo))
{
RemovedProperties.Remove(propertyInfo);
}
CollectionPropertyConfiguration propertyConfiguration = null;
if (ExplicitProperties.ContainsKey(propertyInfo))
{
propertyConfiguration = ExplicitProperties[propertyInfo] as CollectionPropertyConfiguration;
if (propertyConfiguration == null)
{
throw Error.Argument("propertyInfo", SRResources.MustBeCollectionProperty, propertyInfo.Name, propertyInfo.DeclaringType.FullName);
}
}
else
{
propertyConfiguration = new CollectionPropertyConfiguration(propertyInfo, this);
ExplicitProperties[propertyInfo] = propertyConfiguration;
// If the ElementType is the same as this type this is recursive complex type nesting
if (propertyConfiguration.ElementType == ClrType)
{
throw Error.Argument("propertyInfo",
SRResources.RecursiveComplexTypesNotAllowed,
ClrType.Name,
propertyConfiguration.Name);
}
// If the ElementType is not primitive treat as a ComplexType and Add to the model.
IEdmPrimitiveTypeReference edmType = EdmLibHelpers.GetEdmPrimitiveTypeReferenceOrNull(propertyConfiguration.ElementType);
if (edmType == null)
{
ModelBuilder.AddComplexType(propertyConfiguration.ElementType);
}
}
return propertyConfiguration;
}
/// <summary>
/// Removes the given property.
/// </summary>
/// <param name="propertyInfo">The property being removed.</param>
public virtual void RemoveProperty(PropertyInfo propertyInfo)
{
if (propertyInfo == null)
{
throw Error.ArgumentNull("propertyInfo");
}
if (!propertyInfo.ReflectedType.IsAssignableFrom(ClrType))
{
throw Error.Argument("propertyInfo", SRResources.PropertyDoesNotBelongToType, propertyInfo.Name, ClrType.FullName);
}
if (ExplicitProperties.ContainsKey(propertyInfo))
{
ExplicitProperties.Remove(propertyInfo);
}
if (!RemovedProperties.Contains(propertyInfo))
{
RemovedProperties.Add(propertyInfo);
}
}
}
}

Просмотреть файл

@ -0,0 +1,235 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Builder
{
/// <summary>
/// Represents an <see cref="IEdmStructuredType"/> that can be built using <see cref="ODataModelBuilder"/>.
/// </summary>
public abstract class StructuralTypeConfiguration<TStructuralType> where TStructuralType : class
{
private StructuralTypeConfiguration _configuration;
/// <summary>
/// Initializes a new instance of the <see cref="StructuralTypeConfiguration{TStructuralType}"/> class.
/// </summary>
/// <param name="configuration">The inner configuration of the structural type.</param>
protected StructuralTypeConfiguration(StructuralTypeConfiguration configuration)
{
if (configuration == null)
{
throw Error.ArgumentNull("configuration");
}
_configuration = configuration;
}
/// <summary>
/// Gets the collection of EDM structural properties that belong to this type.
/// </summary>
public IEnumerable<PropertyConfiguration> Properties
{
get { return _configuration.Properties; }
}
/// <summary>
/// Gets the full name of this EDM type.
/// </summary>
public string FullName
{
get
{
return _configuration.FullName;
}
}
/// <summary>
/// Gets and sets the namespace of this EDM type.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Namespace", Justification = "Follow StructuralTypeConfiguration's naming")]
public string Namespace
{
get
{
return _configuration.Namespace;
}
set
{
_configuration.Namespace = value;
}
}
/// <summary>
/// Gets and sets the name of this EDM type.
/// </summary>
public string Name
{
get
{
return _configuration.Name;
}
set
{
_configuration.Name = value;
}
}
/// <summary>
/// Excludes a property from the type.
/// </summary>
/// <typeparam name="TProperty">The property type.</typeparam>
/// <param name="propertyExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <remarks>This method is used to exclude properties from the type that would have been added by convention during model discovery.</remarks>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")]
public virtual void Ignore<TProperty>(Expression<Func<TStructuralType, TProperty>> propertyExpression)
{
PropertyInfo ignoredProperty = PropertySelectorVisitor.GetSelectedProperty(propertyExpression);
_configuration.RemoveProperty(ignoredProperty);
}
/// <summary>
/// Adds a string property to the EDM type.
/// </summary>
/// <param name="propertyExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <returns>A configuration object that can be used to further configure the property.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")]
public PrimitivePropertyConfiguration Property(Expression<Func<TStructuralType, string>> propertyExpression)
{
return GetPrimitivePropertyConfiguration(propertyExpression, optional: true);
}
/// <summary>
/// Adds a binary property to the EDM type.
/// </summary>
/// <param name="propertyExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <returns>A configuration object that can be used to further configure the property.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")]
public PrimitivePropertyConfiguration Property(Expression<Func<TStructuralType, byte[]>> propertyExpression)
{
return GetPrimitivePropertyConfiguration(propertyExpression, optional: true);
}
/// <summary>
/// Adds a stream property the EDM type.
/// </summary>
/// <param name="propertyExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <returns>A configuration object that can be used to further configure the property.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")]
public PrimitivePropertyConfiguration Property(Expression<Func<TStructuralType, Stream>> propertyExpression)
{
return GetPrimitivePropertyConfiguration(propertyExpression, optional: true);
}
/// <summary>
/// Adds an optional primitive property to the EDM type.
/// </summary>
/// <typeparam name="T">The primitive property type.</typeparam>
/// <param name="propertyExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <returns>A configuration object that can be used to further configure the property.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")]
public PrimitivePropertyConfiguration Property<T>(Expression<Func<TStructuralType, T?>> propertyExpression) where T : struct
{
return GetPrimitivePropertyConfiguration(propertyExpression, optional: true);
}
/// <summary>
/// Adds a required primitive property to the EDM type.
/// </summary>
/// <typeparam name="T">The primitive property type.</typeparam>
/// <param name="propertyExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <returns>A configuration object that can be used to further configure the property.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")]
public PrimitivePropertyConfiguration Property<T>(Expression<Func<TStructuralType, T>> propertyExpression) where T : struct
{
return GetPrimitivePropertyConfiguration(propertyExpression);
}
/// <summary>
/// Adds a complex property to the EDM type.
/// </summary>
/// <typeparam name="TComplexType">The complex type.</typeparam>
/// <param name="propertyExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <returns>A configuration object that can be used to further configure the property.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")]
public ComplexPropertyConfiguration ComplexProperty<TComplexType>(Expression<Func<TStructuralType, TComplexType>> propertyExpression)
{
return GetComplexPropertyConfiguration(propertyExpression);
}
/// <summary>
/// Adds a collection property to the EDM type.
/// </summary>
/// <typeparam name="TElementType">The element type of the collection.</typeparam>
/// <param name="propertyExpression">A lambda expression representing the navigation property for the relationship.
/// For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET <c>Function(t) t.MyProperty</c>.</param>
/// <returns>A configuration object that can be used to further configure the property.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")]
public CollectionPropertyConfiguration CollectionProperty<TElementType>(Expression<Func<TStructuralType, IEnumerable<TElementType>>> propertyExpression)
{
return GetCollectionPropertyConfiguration(propertyExpression);
}
private PrimitivePropertyConfiguration GetPrimitivePropertyConfiguration(Expression propertyExpression, bool optional = false)
{
PropertyInfo propertyInfo = PropertySelectorVisitor.GetSelectedProperty(propertyExpression);
PrimitivePropertyConfiguration property = _configuration.AddProperty(propertyInfo);
if (optional)
{
property.IsOptional();
}
return property;
}
private ComplexPropertyConfiguration GetComplexPropertyConfiguration(Expression propertyExpression, bool optional = false)
{
PropertyInfo propertyInfo = PropertySelectorVisitor.GetSelectedProperty(propertyExpression);
ComplexPropertyConfiguration property = _configuration.AddComplexProperty(propertyInfo);
if (optional)
{
property.IsOptional();
}
else
{
property.IsRequired();
}
return property;
}
private CollectionPropertyConfiguration GetCollectionPropertyConfiguration(Expression propertyExpression, bool optional = false)
{
PropertyInfo propertyInfo = PropertySelectorVisitor.GetSelectedProperty(propertyExpression);
CollectionPropertyConfiguration property = _configuration.AddCollectionProperty(propertyInfo);
if (optional)
{
property.IsOptional();
}
else
{
property.IsRequired();
}
return property;
}
}
}

Просмотреть файл

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Reflection;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData
{
/// <summary>
/// Represents a mapping from an <see cref="IEdmProperty"/> to a CLR property info.
/// </summary>
public class ClrPropertyInfoAnnotation
{
private PropertyInfo _clrPropertyInfo;
/// <summary>
/// Initializes a new instance of <see cref="ClrPropertyInfoAnnotation"/> class.
/// </summary>
/// <param name="clrPropertyInfo">The backing CLR property info for the EDM property.</param>
public ClrPropertyInfoAnnotation(PropertyInfo clrPropertyInfo)
{
if (clrPropertyInfo == null)
{
throw Error.ArgumentNull("clrPropertyInfo");
}
ClrPropertyInfo = clrPropertyInfo;
}
/// <summary>
/// Gets or sets the backing CLR property info for the EDM property.
/// </summary>
public PropertyInfo ClrPropertyInfo
{
get
{
return _clrPropertyInfo;
}
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_clrPropertyInfo = value;
}
}
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.Data.Edm;
namespace System.Web.Http.OData
{
/// <summary>
/// Represents a mapping from an <see cref="IEdmType"/> to a CLR type.
/// </summary>
public class ClrTypeAnnotation
{
/// <summary>
/// Initializes a new instance of <see cref="ClrTypeAnnotation"/> class.
/// </summary>
/// <param name="clrType">The backing CLR type for the EDM type.</param>
public ClrTypeAnnotation(Type clrType)
{
ClrType = clrType;
}
/// <summary>
/// The backing CLR type for the EDM type.
/// </summary>
public Type ClrType { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,83 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Http.OData.Formatter.Deserialization;
namespace System.Web.Http.OData
{
/// <summary>
/// CompiledPropertyAccessor is a <see cref="PropertyAccessor{TEntityType}"/> that pre-compiles (using expression)
/// a Getter and Setter for the PropertyInfo of TEntityType provided via the constructor.
/// </summary>
/// <typeparam name="TEntityType">The type on which the PropertyInfo exists</typeparam>
internal class CompiledPropertyAccessor<TEntityType> : PropertyAccessor<TEntityType> where TEntityType : class
{
private bool _isCollection;
private PropertyInfo _property;
private Action<TEntityType, object> _setter;
private Func<TEntityType, object> _getter;
public CompiledPropertyAccessor(PropertyInfo property)
: base(property)
{
_property = property;
_isCollection = property.PropertyType.IsCollection();
_getter = MakeGetter(Property);
if (!_isCollection)
{
_setter = MakeSetter(property);
}
}
public override object GetValue(TEntityType entity)
{
if (entity == null)
{
throw Error.ArgumentNull("entity");
}
return _getter(entity);
}
public override void SetValue(TEntityType entity, object value)
{
if (entity == null)
{
throw Error.ArgumentNull("entity");
}
if (_isCollection)
{
DeserializationHelpers.SetCollectionProperty(entity, _property.Name, edmPropertyType: null,
value: value, clearCollection: true);
}
else
{
_setter(entity, value);
}
}
private static Action<TEntityType, object> MakeSetter(PropertyInfo property)
{
Type type = typeof(TEntityType);
ParameterExpression entityParameter = Expression.Parameter(type);
ParameterExpression objectParameter = Expression.Parameter(typeof(object));
MemberExpression toProperty = Expression.Property(Expression.TypeAs(entityParameter, property.DeclaringType), property);
UnaryExpression fromValue = Expression.Convert(objectParameter, property.PropertyType);
BinaryExpression assignment = Expression.Assign(toProperty, fromValue);
Expression<Action<TEntityType, object>> lambda = Expression.Lambda<Action<TEntityType, object>>(assignment, entityParameter, objectParameter);
return lambda.Compile();
}
private static Func<TEntityType, object> MakeGetter(PropertyInfo property)
{
Type type = typeof(TEntityType);
ParameterExpression entityParameter = Expression.Parameter(type);
MemberExpression fromProperty = Expression.Property(Expression.TypeAs(entityParameter, property.DeclaringType), property);
UnaryExpression convert = Expression.Convert(fromProperty, typeof(Object));
Expression<Func<TEntityType, object>> lambda = Expression.Lambda<Func<TEntityType, object>>(convert, entityParameter);
return lambda.Compile();
}
}
}

Просмотреть файл

@ -0,0 +1,60 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Net.Http;
namespace System.Web.Http.OData
{
internal static class ContentIdHelpers
{
private const string ContentId = "Content-ID";
public static string ResolveContentId(string url, IDictionary<string, string> contentIdToLocationMapping)
{
Contract.Assert(url != null);
Contract.Assert(contentIdToLocationMapping != null);
foreach (KeyValuePair<string, string> location in contentIdToLocationMapping)
{
int index = url.IndexOf("$" + location.Key, StringComparison.Ordinal);
if (index != -1)
{
// As location headers MUST be absolute URL's, we can ignore everything
// before the $content-id while resolving it.
return location.Value + url.Substring(index + 1 + location.Key.Length);
}
}
return url;
}
public static void CopyContentIdToResponse(HttpRequestMessage request, HttpResponseMessage response)
{
Contract.Assert(request != null);
Contract.Assert(response != null);
IEnumerable<string> values;
if (request.Headers.TryGetValues(ContentId, out values))
{
response.Headers.TryAddWithoutValidation(ContentId, values);
}
}
public static void AddLocationHeaderToMapping(HttpResponseMessage response, IDictionary<string, string> contentIdToLocationMapping)
{
Contract.Assert(response != null);
Contract.Assert(contentIdToLocationMapping != null);
IEnumerable<string> values;
if (response.Headers.TryGetValues(ContentId, out values))
{
if (response.Headers.Location != null)
{
contentIdToLocationMapping.Add(values.First(), response.Headers.Location.AbsoluteUri);
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,101 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Web.Http.OData.Formatter;
using System.Web.Http.OData.Properties;
namespace System.Web.Http.OData
{
/// <summary>
/// A class the tracks changes (i.e. the Delta) for an entity.
/// </summary>
[NonValidatingParameterBinding]
public abstract class Delta : DynamicObject, IDelta
{
/// <summary>
/// Clears the Delta and resets the underlying Entity.
/// </summary>
public abstract void Clear();
/// <summary>
/// Attempts to set the Property called <paramref name="name"/> to the <paramref name="value"/> specified.
/// <remarks>
/// Only properties that exist on Entity can be set.
/// If there is a type mismatch the request will fail.
/// </remarks>
/// </summary>
/// <param name="name">The name of the Property</param>
/// <param name="value">The new value of the Property</param>
/// <returns>True if successful</returns>
public abstract bool TrySetPropertyValue(string name, object value);
/// <summary>
/// Attempts to get the value of the Property called <paramref name="name"/> from the underlying Entity.
/// <remarks>
/// Only properties that exist on Entity can be retrieved.
/// Both modified and unmodified properties can be retrieved.
/// </remarks>
/// </summary>
/// <param name="name">The name of the Property</param>
/// <param name="value">The value of the Property</param>
/// <returns>True if the Property was found</returns>
public abstract bool TryGetPropertyValue(string name, out object value);
/// <summary>
/// Attempts to get the <see cref="Type"/> of the Property called <paramref name="name"/> from the underlying Entity.
/// <remarks>
/// Only properties that exist on Entity can be retrieved.
/// Both modified and unmodified properties can be retrieved.
/// </remarks>
/// </summary>
/// <param name="name">The name of the Property</param>
/// <param name="type">The type of the Property</param>
/// <returns>Returns <c>true</c> if the Property was found and <c>false</c> if not.</returns>
public abstract bool TryGetPropertyType(string name, out Type type);
/// <summary>
/// Overrides the DynamicObject TrySetMember method, so that only the properties
/// of Entity can be set.
/// </summary>
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (binder == null)
{
throw Error.ArgumentNull("binder");
}
return TrySetPropertyValue(binder.Name, value);
}
/// <summary>
/// Overrides the DynamicObject TryGetMember method, so that only the properties
/// of Entity can be got.
/// </summary>
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder == null)
{
throw Error.ArgumentNull("binder");
}
return TryGetPropertyValue(binder.Name, out result);
}
/// <summary>
/// Returns the Properties that have been modified through this Delta as an
/// enumeration of Property Names
/// </summary>
public abstract IEnumerable<string> GetChangedPropertyNames();
/// <summary>
/// Returns the Properties that have not been modified through this Delta as an
/// enumeration of Property Names
/// </summary>
public abstract IEnumerable<string> GetUnchangedPropertyNames();
}
}

Просмотреть файл

@ -0,0 +1,285 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Web.Http.OData.Formatter;
using System.Web.Http.OData.Properties;
namespace System.Web.Http.OData
{
/// <summary>
/// A class the tracks changes (i.e. the Delta) for a particular <typeparamref name="TEntityType"/>.
/// </summary>
/// <typeparam name="TEntityType">TEntityType is the base type of entity this delta tracks changes for.</typeparam>
[NonValidatingParameterBinding]
public class Delta<TEntityType> : TypedDelta, IDelta where TEntityType : class
{
// cache property accessors for this type and all its derived types.
private static ConcurrentDictionary<Type, Dictionary<string, PropertyAccessor<TEntityType>>> _propertyCache
= new ConcurrentDictionary<Type, Dictionary<string, PropertyAccessor<TEntityType>>>();
private Dictionary<string, PropertyAccessor<TEntityType>> _allProperties;
private HashSet<string> _updatableProperties;
private HashSet<string> _changedProperties;
private TEntityType _entity;
private Type _entityType;
/// <summary>
/// Initializes a new instance of <see cref="Delta{TEntityType}"/>.
/// </summary>
public Delta()
: this(typeof(TEntityType))
{
}
/// <summary>
/// Initializes a new instance of <see cref="Delta{TEntityType}"/>.
/// </summary>
/// <param name="entityType">The derived entity type for which the changes would be tracked.
/// <paramref name="entityType"/> should be assignable to instances of <typeparamref name="TEntityType"/>.</param>
public Delta(Type entityType)
: this(entityType, updatableProperties: null)
{
}
/// <summary>
/// Initializes a new instance of <see cref="Delta{TEntityType}"/>.
/// </summary>
/// <param name="entityType">The derived entity type for which the changes would be tracked.
/// <param name="updatableProperties">The set of properties that can be updated or reset.</param>
/// <paramref name="entityType"/> should be assignable to instances of <typeparamref name="TEntityType"/>.</param>
public Delta(Type entityType, IEnumerable<string> updatableProperties)
{
Reset(entityType);
InitializeProperties(updatableProperties);
}
/// <inheritdoc/>
public override Type EntityType
{
get
{
return _entityType;
}
}
/// <inheritdoc/>
public override Type ExpectedClrType
{
get { return typeof(TEntityType); }
}
/// <inheritdoc/>
public override void Clear()
{
Reset(_entityType);
}
/// <inheritdoc/>
public override bool TrySetPropertyValue(string name, object value)
{
if (name == null)
{
throw Error.ArgumentNull("name");
}
if (!_updatableProperties.Contains(name))
{
return false;
}
PropertyAccessor<TEntityType> cacheHit = _allProperties[name];
if (value == null && !EdmLibHelpers.IsNullable(cacheHit.Property.PropertyType))
{
return false;
}
Type propertyType = cacheHit.Property.PropertyType;
if (value != null && !propertyType.IsCollection() && !propertyType.IsAssignableFrom(value.GetType()))
{
return false;
}
//.Setter.Invoke(_entity, new object[] { value });
cacheHit.SetValue(_entity, value);
_changedProperties.Add(name);
return true;
}
/// <inheritdoc/>
public override bool TryGetPropertyValue(string name, out object value)
{
if (name == null)
{
throw Error.ArgumentNull("name");
}
PropertyAccessor<TEntityType> cacheHit;
if (_allProperties.TryGetValue(name, out cacheHit))
{
value = cacheHit.GetValue(_entity);
return true;
}
else
{
value = null;
return false;
}
}
/// <inheritdoc/>
public override bool TryGetPropertyType(string name, out Type type)
{
if (name == null)
{
throw Error.ArgumentNull("name");
}
PropertyAccessor<TEntityType> value;
if (_allProperties.TryGetValue(name, out value))
{
type = value.Property.PropertyType;
return true;
}
else
{
type = null;
return false;
}
}
/// <summary>
/// Returns the <see cref="EntityType"/> instance
/// that holds all the changes (and original values) being tracked by this Delta.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Not appropriate to be a property")]
public TEntityType GetEntity()
{
return _entity;
}
/// <inheritdoc/>
public override IEnumerable<string> GetChangedPropertyNames()
{
return _changedProperties;
}
/// <inheritdoc/>
public override IEnumerable<string> GetUnchangedPropertyNames()
{
return _updatableProperties.Except(GetChangedPropertyNames());
}
/// <summary>
/// Copies the changed property values from the underlying entity (accessible via <see cref="GetEntity()" />)
/// to the <paramref name="original"/> entity.
/// </summary>
/// <param name="original">The entity to be updated.</param>
public void CopyChangedValues(TEntityType original)
{
if (original == null)
{
throw Error.ArgumentNull("original");
}
if (!_entityType.IsAssignableFrom(original.GetType()))
{
throw Error.Argument("original", SRResources.DeltaTypeMismatch, _entityType, original.GetType());
}
PropertyAccessor<TEntityType>[] propertiesToCopy = GetChangedPropertyNames().Select(s => _allProperties[s]).ToArray();
foreach (PropertyAccessor<TEntityType> propertyToCopy in propertiesToCopy)
{
propertyToCopy.Copy(_entity, original);
}
}
/// <summary>
/// Copies the unchanged property values from the underlying entity (accessible via <see cref="GetEntity()" />)
/// to the <paramref name="original"/> entity.
/// </summary>
/// <param name="original">The entity to be updated.</param>
public void CopyUnchangedValues(TEntityType original)
{
if (original == null)
{
throw Error.ArgumentNull("original");
}
if (!_entityType.IsAssignableFrom(original.GetType()))
{
throw Error.Argument("original", SRResources.DeltaTypeMismatch, _entityType, original.GetType());
}
IEnumerable<PropertyAccessor<TEntityType>> propertiesToCopy = GetUnchangedPropertyNames().Select(s => _allProperties[s]);
foreach (PropertyAccessor<TEntityType> propertyToCopy in propertiesToCopy)
{
propertyToCopy.Copy(_entity, original);
}
}
/// <summary>
/// Overwrites the <paramref name="original"/> entity with the changes tracked by this Delta.
/// <remarks>The semantics of this operation are equivalent to a HTTP PATCH operation, hence the name.</remarks>
/// </summary>
/// <param name="original">The entity to be updated.</param>
public void Patch(TEntityType original)
{
CopyChangedValues(original);
}
/// <summary>
/// Overwrites the <paramref name="original"/> entity with the values stored in this Delta.
/// <remarks>The semantics of this operation are equivalent to a HTTP PUT operation, hence the name.</remarks>
/// </summary>
/// <param name="original">The entity to be updated.</param>
public void Put(TEntityType original)
{
CopyChangedValues(original);
CopyUnchangedValues(original);
}
private void Reset(Type entityType)
{
if (entityType == null)
{
throw Error.ArgumentNull("entityType");
}
if (!typeof(TEntityType).IsAssignableFrom(entityType))
{
throw Error.InvalidOperation(SRResources.DeltaEntityTypeNotAssignable, entityType, typeof(TEntityType));
}
_entity = Activator.CreateInstance(entityType) as TEntityType;
_changedProperties = new HashSet<string>();
_entityType = entityType;
}
private void InitializeProperties(IEnumerable<string> updatableProperties)
{
_allProperties = _propertyCache.GetOrAdd(
_entityType,
(backingType) => backingType
.GetProperties()
.Where(p => (p.GetSetMethod() != null || p.PropertyType.IsCollection()) && p.GetGetMethod() != null)
.Select<PropertyInfo, PropertyAccessor<TEntityType>>(p => new FastPropertyAccessor<TEntityType>(p))
.ToDictionary(p => p.Property.Name));
if (updatableProperties != null)
{
_updatableProperties = new HashSet<string>(updatableProperties);
_updatableProperties.IntersectWith(_allProperties.Keys);
}
else
{
_updatableProperties = new HashSet<string>(_allProperties.Keys);
}
}
}
}

Просмотреть файл

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData
{
/// <summary>
/// Represents an <see cref="IEdmObject"/> that is a collection of <see cref="IEdmComplexObject"/>s.
/// </summary>
public class EdmComplexObjectCollection : Collection<IEdmComplexObject>, IEdmObject
{
private IEdmCollectionTypeReference _edmType;
/// <summary>
/// Initialzes a new instance of the <see cref="EdmComplexObjectCollection"/> class.
/// </summary>
/// <param name="edmType">The edm type of the collection.</param>
public EdmComplexObjectCollection(IEdmCollectionTypeReference edmType)
{
Initialize(edmType);
}
/// <summary>
/// Initialzes a new instance of the <see cref="EdmComplexObjectCollection"/> class.
/// </summary>
/// <param name="edmType">The edm type of the collection.</param>
/// <param name="list">The list that is wrapped by the new collection.</param>
public EdmComplexObjectCollection(IEdmCollectionTypeReference edmType, IList<IEdmComplexObject> list)
: base(list)
{
Initialize(edmType);
}
/// <inheritdoc/>
public IEdmTypeReference GetEdmType()
{
return _edmType;
}
private void Initialize(IEdmCollectionTypeReference edmType)
{
if (edmType == null)
{
throw Error.ArgumentNull("edmType");
}
if (!edmType.ElementType().IsComplex())
{
throw Error.Argument("edmType",
SRResources.UnexpectedElementType, edmType.ElementType().ToTraceString(), edmType.ToTraceString(), typeof(IEdmComplexType).Name);
}
_edmType = edmType;
}
}
}

Просмотреть файл

@ -0,0 +1,40 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.Data.Edm;
namespace System.Web.Http.OData
{
/// <summary>
/// Represents an <see cref="IEdmComplexObject"/> with no backing CLR <see cref="Type"/>.
/// </summary>
public class EdmComplexObject : EdmStructuredObject, IEdmComplexObject
{
/// <summary>
/// Initializes a new instance of the <see cref="EdmStructuredObject"/> class.
/// </summary>
/// <param name="edmType">The <see cref="IEdmStructuredType"/> of this object.</param>
public EdmComplexObject(IEdmComplexType edmType)
: this(edmType, isNullable: false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EdmStructuredObject"/> class.
/// </summary>
/// <param name="edmType">The <see cref="IEdmComplexTypeReference"/> of this object.</param>
public EdmComplexObject(IEdmComplexTypeReference edmType)
: this(edmType.ComplexDefinition(), edmType.IsNullable)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EdmStructuredObject"/> class.
/// </summary>
/// <param name="edmType">The <see cref="IEdmComplexType"/> of this object.</param>
/// <param name="isNullable">true if this object can be nullable; otherwise, false.</param>
public EdmComplexObject(IEdmComplexType edmType, bool isNullable)
: base(edmType, isNullable)
{
}
}
}

Просмотреть файл

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData
{
/// <summary>
/// Represents an <see cref="IEdmObject"/> that is a collection of <see cref="IEdmEntityObject"/>s.
/// </summary>
public class EdmEntityObjectCollection : Collection<IEdmEntityObject>, IEdmObject
{
private IEdmCollectionTypeReference _edmType;
/// <summary>
/// Initialzes a new instance of the <see cref="EdmEntityObjectCollection"/> class.
/// </summary>
/// <param name="edmType">The edm type of the collection.</param>
public EdmEntityObjectCollection(IEdmCollectionTypeReference edmType)
{
Initialize(edmType);
}
/// <summary>
/// Initialzes a new instance of the <see cref="EdmEntityObjectCollection"/> class.
/// </summary>
/// <param name="edmType">The edm type of the collection.</param>
/// <param name="list">The list that is wrapped by the new collection.</param>
public EdmEntityObjectCollection(IEdmCollectionTypeReference edmType, IList<IEdmEntityObject> list)
: base(list)
{
Initialize(edmType);
}
/// <inheritdoc/>
public IEdmTypeReference GetEdmType()
{
return _edmType;
}
private void Initialize(IEdmCollectionTypeReference edmType)
{
if (edmType == null)
{
throw Error.ArgumentNull("edmType");
}
if (!edmType.ElementType().IsEntity())
{
throw Error.Argument("edmType",
SRResources.UnexpectedElementType, edmType.ElementType().ToTraceString(), edmType.ToTraceString(), typeof(IEdmEntityType).Name);
}
_edmType = edmType;
}
}
}

Просмотреть файл

@ -0,0 +1,40 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.Data.Edm;
namespace System.Web.Http.OData
{
/// <summary>
/// Represents an <see cref="IEdmEntityObject"/> with no backing CLR <see cref="Type"/>.
/// </summary>
public class EdmEntityObject : EdmStructuredObject, IEdmEntityObject
{
/// <summary>
/// Initializes a new instance of the <see cref="EdmStructuredObject"/> class.
/// </summary>
/// <param name="edmType">The <see cref="IEdmEntityType"/> of this object.</param>
public EdmEntityObject(IEdmEntityType edmType)
: this(edmType, isNullable: false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EdmStructuredObject"/> class.
/// </summary>
/// <param name="edmType">The <see cref="IEdmEntityTypeReference"/> of this object.</param>
public EdmEntityObject(IEdmEntityTypeReference edmType)
: this(edmType.EntityDefinition(), edmType.IsNullable)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EdmStructuredObject"/> class.
/// </summary>
/// <param name="edmType">The <see cref="IEdmEntityType"/> of this object.</param>
/// <param name="isNullable">true if this object can be nullable; otherwise, false.</param>
public EdmEntityObject(IEdmEntityType edmType, bool isNullable)
: base(edmType, isNullable)
{
}
}
}

Просмотреть файл

@ -0,0 +1,138 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Web.Http.OData.Builder;
using System.Web.Http.OData.Formatter;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData
{
/// <summary>
/// Provides extension methods for the <see cref="IEdmModel"/> interface.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class EdmModelExtensions
{
/// <summary>
/// Gets the <see cref="EntitySetLinkBuilderAnnotation"/> to be used while generating self and navigation links for the given entity set.
/// </summary>
/// <param name="model">The <see cref="IEdmModel"/> containing the entity set.</param>
/// <param name="entitySet">The entity set.</param>
/// <returns>The <see cref="EntitySetLinkBuilderAnnotation"/> if set for the given the entity set; otherwise, a new
/// <see cref="EntitySetLinkBuilderAnnotation"/> that generates URLs that follow OData URL conventions.</returns>
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "IEdmEntitySet is more relevant here.")]
public static EntitySetLinkBuilderAnnotation GetEntitySetLinkBuilder(this IEdmModel model, IEdmEntitySet entitySet)
{
if (model == null)
{
throw Error.ArgumentNull("model");
}
EntitySetLinkBuilderAnnotation annotation = model.GetAnnotationValue<EntitySetLinkBuilderAnnotation>(entitySet);
if (annotation == null)
{
// construct and set an entity set link builder that follows OData URL conventions.
annotation = new EntitySetLinkBuilderAnnotation(entitySet, model);
model.SetEntitySetLinkBuilder(entitySet, annotation);
}
return annotation;
}
/// <summary>
/// Sets the <see cref="EntitySetLinkBuilderAnnotation"/> to be used while generating self and navigation links for the given entity set.
/// </summary>
/// <param name="model">The <see cref="IEdmModel"/> containing the entity set.</param>
/// <param name="entitySet">The entity set.</param>
/// <param name="entitySetLinkBuilder">The <see cref="EntitySetLinkBuilderAnnotation"/> to set.</param>
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "IEdmEntitySet is more relevant here.")]
public static void SetEntitySetLinkBuilder(this IEdmModel model, IEdmEntitySet entitySet, EntitySetLinkBuilderAnnotation entitySetLinkBuilder)
{
if (model == null)
{
throw Error.ArgumentNull("model");
}
model.SetAnnotationValue(entitySet, entitySetLinkBuilder);
}
/// <summary>
/// Gets the <see cref="ActionLinkBuilder"/> to be used while generating action links for the given action.
/// </summary>
/// <param name="model">The <see cref="IEdmModel"/> containing the action.</param>
/// <param name="action">The action for which the link builder is needed.</param>
/// <returns>The <see cref="ActionLinkBuilder"/> for the given action if one is set; otherwise, a new <see cref="ActionLinkBuilder"/> that
/// generates action links following OData URL conventions.</returns>
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "IEdmFunctionImport is more relevant here.")]
public static ActionLinkBuilder GetActionLinkBuilder(this IEdmModel model, IEdmFunctionImport action)
{
if (model == null)
{
throw Error.ArgumentNull("model");
}
if (action == null)
{
throw Error.ArgumentNull("action");
}
ActionLinkBuilder actionLinkBuilder = model.GetAnnotationValue<ActionLinkBuilder>(action);
if (actionLinkBuilder == null)
{
actionLinkBuilder = new ActionLinkBuilder(entityInstanceContext => entityInstanceContext.GenerateActionLink(action), followsConventions: true);
model.SetActionLinkBuilder(action, actionLinkBuilder);
}
return actionLinkBuilder;
}
/// <summary>
/// Sets the <see cref="ActionLinkBuilder"/> to be used for generating the OData action link for the given action.
/// </summary>
/// <param name="model">The <see cref="IEdmModel"/> containing the entity set.</param>
/// <param name="action">The action for which the action link is to be generated.</param>
/// <param name="actionLinkBuilder">The <see cref="ActionLinkBuilder"/> to set.</param>
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "IEdmFunctionImport is more relevant here.")]
public static void SetActionLinkBuilder(this IEdmModel model, IEdmFunctionImport action, ActionLinkBuilder actionLinkBuilder)
{
if (model == null)
{
throw Error.ArgumentNull("model");
}
model.SetAnnotationValue(action, actionLinkBuilder);
}
/// <summary>
/// Sets the <see cref="FunctionLinkBuilder"/> to be used for generating the OData function link for the given function.
/// </summary>
/// <param name="model">The <see cref="IEdmModel"/> containing the entity set.</param>
/// <param name="function">The function for which the function link is to be generated.</param>
/// <param name="functionLinkBuilder">The <see cref="FunctionLinkBuilder"/> to set.</param>
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "IEdmFunctionImport is more relevant here.")]
public static void SetFunctionLinkBuilder(this IEdmModel model, IEdmFunctionImport function, FunctionLinkBuilder functionLinkBuilder)
{
if (model == null)
{
throw Error.ArgumentNull("model");
}
model.SetAnnotationValue(function, functionLinkBuilder);
}
internal static ClrTypeCache GetTypeMappingCache(this IEdmModel model)
{
Contract.Assert(model != null);
ClrTypeCache typeMappingCache = model.GetAnnotationValue<ClrTypeCache>(model);
if (typeMappingCache == null)
{
typeMappingCache = new ClrTypeCache();
model.SetAnnotationValue(model, typeMappingCache);
}
return typeMappingCache;
}
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше