Imported Upstream version 3.6.0

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

View File

@@ -0,0 +1,387 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using Newtonsoft.Json.Linq;
namespace Microsoft.Web.Http.Data.Helpers
{
internal static class DataControllerMetadataGenerator
{
private static readonly ConcurrentDictionary<DataControllerDescription, IEnumerable<TypeMetadata>> _metadataMap =
new ConcurrentDictionary<DataControllerDescription, IEnumerable<TypeMetadata>>();
private static readonly IEnumerable<JProperty> _emptyJsonPropertyEnumerable = Enumerable.Empty<JProperty>();
public static IEnumerable<TypeMetadata> GetMetadata(DataControllerDescription description)
{
return _metadataMap.GetOrAdd(description, desc =>
{
return GenerateMetadata(desc);
});
}
private static IEnumerable<TypeMetadata> GenerateMetadata(DataControllerDescription description)
{
List<TypeMetadata> metadata = new List<TypeMetadata>();
foreach (Type entityType in description.EntityTypes)
{
metadata.Add(new TypeMetadata(entityType));
}
// TODO: Complex types are NYI in DataControllerDescription
// foreach (Type complexType in description.ComplexTypes)
// {
// metadata.Add(new TypeMetadata(complexType));
// }
return metadata;
}
private static string EncodeTypeName(string typeName, string typeNamespace)
{
return String.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", typeName, MetadataStrings.NamespaceMarker, typeNamespace);
}
private static class MetadataStrings
{
public const string NamespaceMarker = ":#";
public const string TypeString = "type";
public const string ArrayString = "array";
public const string AssociationString = "association";
public const string FieldsString = "fields";
public const string ThisKeyString = "thisKey";
public const string IsForeignKey = "isForeignKey";
public const string OtherKeyString = "otherKey";
public const string NameString = "name";
public const string ReadOnlyString = "readonly";
public const string KeyString = "key";
public const string RulesString = "rules";
public const string MessagesString = "messages";
}
public class TypeMetadata
{
private List<string> _key = new List<string>();
private List<TypePropertyMetadata> _properties = new List<TypePropertyMetadata>();
public TypeMetadata(Type entityType)
{
Type type = TypeUtility.GetElementType(entityType);
TypeName = type.Name;
TypeNamespace = type.Namespace;
IEnumerable<PropertyDescriptor> properties =
TypeDescriptor.GetProperties(entityType).Cast<PropertyDescriptor>().OrderBy(p => p.Name)
.Where(p => TypeUtility.IsDataMember(p));
foreach (PropertyDescriptor pd in properties)
{
_properties.Add(new TypePropertyMetadata(pd));
if (TypeDescriptorExtensions.ExplicitAttributes(pd)[typeof(KeyAttribute)] != null)
{
_key.Add(pd.Name);
}
}
}
public string TypeName { get; private set; }
public string TypeNamespace { get; private set; }
public string EncodedTypeName
{
get { return EncodeTypeName(TypeName, TypeNamespace); }
}
public IEnumerable<string> Key
{
get { return _key; }
}
public IEnumerable<TypePropertyMetadata> Properties
{
get { return _properties; }
}
public JToken ToJToken()
{
JObject value = new JObject();
value[MetadataStrings.KeyString] = new JArray(Key.Select(k => (JToken)k));
value[MetadataStrings.FieldsString] = new JObject(Properties.Select(p => new JProperty(p.Name, p.ToJToken())));
// TODO: Only include these properties when they'll have non-empty values. Need to update SPA T4 templates to tolerate null in scaffolded SPA JavaScript.
//if (Properties.Any(p => p.ValidationRules.Count > 0))
//{
value[MetadataStrings.RulesString] = new JObject(
Properties.SelectMany(
p => p.ValidationRules.Count == 0
? _emptyJsonPropertyEnumerable
: new JProperty[]
{
new JProperty(
p.Name,
new JObject(p.ValidationRules.Select(
r => new JProperty(r.Name, r.ToJToken()))))
}));
//}
//if (Properties.Any(p => p.ValidationRules.Any(r => r.ErrorMessageString != null)))
//{
value[MetadataStrings.MessagesString] = new JObject(
Properties.SelectMany(
p => !p.ValidationRules.Any(r => r.ErrorMessageString != null)
? _emptyJsonPropertyEnumerable
: new JProperty[]
{
new JProperty(
p.Name,
new JObject(p.ValidationRules.SelectMany(r =>
r.ErrorMessageString == null
? _emptyJsonPropertyEnumerable
: new JProperty[]
{
new JProperty(r.Name, r.ErrorMessageString)
})))
}));
//}
return value;
}
}
public class TypePropertyAssociationMetadata
{
private List<string> _thisKeyMembers = new List<string>();
private List<string> _otherKeyMembers = new List<string>();
public TypePropertyAssociationMetadata(AssociationAttribute associationAttr)
{
Name = associationAttr.Name;
IsForeignKey = associationAttr.IsForeignKey;
_otherKeyMembers = associationAttr.OtherKeyMembers.ToList<string>();
_thisKeyMembers = associationAttr.ThisKeyMembers.ToList<string>();
}
public string Name { get; private set; }
public bool IsForeignKey { get; private set; }
public IEnumerable<string> ThisKeyMembers
{
get { return _thisKeyMembers; }
}
public IEnumerable<string> OtherKeyMembers
{
get { return _otherKeyMembers; }
}
public JToken ToJToken()
{
JObject value = new JObject();
value[MetadataStrings.NameString] = Name;
value[MetadataStrings.ThisKeyString] = new JArray(ThisKeyMembers.Select(k => (JToken)k));
value[MetadataStrings.OtherKeyString] = new JArray(OtherKeyMembers.Select(k => (JToken)k));
value[MetadataStrings.IsForeignKey] = IsForeignKey;
return value;
}
}
public class TypePropertyMetadata
{
private List<TypePropertyValidationRuleMetadata> _validationRules = new List<TypePropertyValidationRuleMetadata>();
public TypePropertyMetadata(PropertyDescriptor descriptor)
{
Name = descriptor.Name;
Type elementType = TypeUtility.GetElementType(descriptor.PropertyType);
IsArray = !elementType.Equals(descriptor.PropertyType);
// TODO: What should we do with nullable types here?
TypeName = elementType.Name;
TypeNamespace = elementType.Namespace;
AttributeCollection propertyAttributes = TypeDescriptorExtensions.ExplicitAttributes(descriptor);
// TODO, 336102, ReadOnlyAttribute for editability? RIA used EditableAttribute?
ReadOnlyAttribute readonlyAttr = (ReadOnlyAttribute)propertyAttributes[typeof(ReadOnlyAttribute)];
IsReadOnly = (readonlyAttr != null) ? readonlyAttr.IsReadOnly : false;
AssociationAttribute associationAttr = (AssociationAttribute)propertyAttributes[typeof(AssociationAttribute)];
if (associationAttr != null)
{
Association = new TypePropertyAssociationMetadata(associationAttr);
}
RequiredAttribute requiredAttribute = (RequiredAttribute)propertyAttributes[typeof(RequiredAttribute)];
if (requiredAttribute != null)
{
_validationRules.Add(new TypePropertyValidationRuleMetadata(requiredAttribute));
}
RangeAttribute rangeAttribute = (RangeAttribute)propertyAttributes[typeof(RangeAttribute)];
if (rangeAttribute != null)
{
Type operandType = rangeAttribute.OperandType;
operandType = Nullable.GetUnderlyingType(operandType) ?? operandType;
if (operandType.Equals(typeof(Double))
|| operandType.Equals(typeof(Int16))
|| operandType.Equals(typeof(Int32))
|| operandType.Equals(typeof(Int64))
|| operandType.Equals(typeof(Single)))
{
_validationRules.Add(new TypePropertyValidationRuleMetadata(rangeAttribute));
}
}
StringLengthAttribute stringLengthAttribute = (StringLengthAttribute)propertyAttributes[typeof(StringLengthAttribute)];
if (stringLengthAttribute != null)
{
_validationRules.Add(new TypePropertyValidationRuleMetadata(stringLengthAttribute));
}
DataTypeAttribute dataTypeAttribute = (DataTypeAttribute)propertyAttributes[typeof(DataTypeAttribute)];
if (dataTypeAttribute != null)
{
if (dataTypeAttribute.DataType.Equals(DataType.EmailAddress)
|| dataTypeAttribute.DataType.Equals(DataType.Url))
{
_validationRules.Add(new TypePropertyValidationRuleMetadata(dataTypeAttribute));
}
}
}
public string Name { get; private set; }
public string TypeName { get; private set; }
public string TypeNamespace { get; private set; }
public bool IsReadOnly { get; private set; }
public bool IsArray { get; private set; }
public TypePropertyAssociationMetadata Association { get; private set; }
public IList<TypePropertyValidationRuleMetadata> ValidationRules
{
get { return _validationRules; }
}
public JToken ToJToken()
{
JObject value = new JObject();
value[MetadataStrings.TypeString] = EncodeTypeName(TypeName, TypeNamespace);
if (IsReadOnly)
{
value[MetadataStrings.ReadOnlyString] = true;
}
if (IsArray)
{
value[MetadataStrings.ArrayString] = true;
}
if (Association != null)
{
value[MetadataStrings.AssociationString] = Association.ToJToken();
}
return value;
}
}
public class TypePropertyValidationRuleMetadata
{
private string _type;
public TypePropertyValidationRuleMetadata(RequiredAttribute attribute)
: this((ValidationAttribute)attribute)
{
Name = "required";
Value1 = true;
_type = "boolean";
}
public TypePropertyValidationRuleMetadata(RangeAttribute attribute)
: this((ValidationAttribute)attribute)
{
Name = "range";
Value1 = attribute.Minimum;
Value2 = attribute.Maximum;
_type = "array";
}
public TypePropertyValidationRuleMetadata(StringLengthAttribute attribute)
: this((ValidationAttribute)attribute)
{
if (attribute.MinimumLength != 0)
{
Name = "rangelength";
Value1 = attribute.MinimumLength;
Value2 = attribute.MaximumLength;
_type = "array";
}
else
{
Name = "maxlength";
Value1 = attribute.MaximumLength;
_type = "number";
}
}
public TypePropertyValidationRuleMetadata(DataTypeAttribute attribute)
: this((ValidationAttribute)attribute)
{
switch (attribute.DataType)
{
case DataType.EmailAddress:
Name = "email";
break;
case DataType.Url:
Name = "url";
break;
default:
break;
}
Value1 = true;
_type = "boolean";
}
public TypePropertyValidationRuleMetadata(ValidationAttribute attribute)
{
if (attribute.ErrorMessage != null)
{
ErrorMessageString = attribute.ErrorMessage;
}
}
public string Name { get; private set; }
public object Value1 { get; private set; }
public object Value2 { get; private set; }
public string ErrorMessageString { get; private set; }
public JToken ToJToken()
{
// The output json is determined by the number of values. The object constructor takes care the value assignment.
// When we have two values, we have two numbers that are written as an array.
// When we have only one value, it is written as it's type only.
if (_type == "array")
{
return new JArray() { new JValue(Value1), new JValue(Value2) };
}
else if (_type == "boolean")
{
return (bool)Value1;
}
else if (_type == "number")
{
return (int)Value1;
}
else
{
throw new InvalidOperationException("Unexpected validation rule type.");
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
//
// To add a suppression to this file, right-click the message in the
// Error List, point to "Suppress Message(s)", and click
// "In Project Suppression File".
// You do not need to add suppressions to this file manually.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Assembly is delay signed")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Web.Http.Data.Helpers", Justification = "There are just a few helpers for client generation.")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Web.Http.Data.TypeDescriptorExtensions.#ContainsAttributeType`1(System.ComponentModel.AttributeCollection)", Justification = "Used in Microsoft.Web.Http.Data assembly")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Web.Http.Data.TypeUtility.#GetKnownTypes(System.Type,System.Boolean)", Justification = "Used in Microsoft.Web.Http.Data assembly")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Web.Http.Data.TypeUtility.#UnwrapTaskInnerType(System.Type)", Justification = "Used in Microsoft.Web.Http.Data assembly")]

View File

@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Mvc;
using Newtonsoft.Json.Linq;
namespace Microsoft.Web.Http.Data.Helpers
{
[EditorBrowsable(EditorBrowsableState.Never)]
public static class MetadataExtensions
{
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Following established design pattern for HTML helpers.")]
public static IHtmlString Metadata<TDataController>(this HtmlHelper htmlHelper) where TDataController : DataController
{
HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor
{
Configuration = GlobalConfiguration.Configuration, // This helper can't be run until after global app init.
ControllerType = typeof(TDataController)
};
DataControllerDescription description = DataControllerDescription.GetDescription(controllerDescriptor);
IEnumerable<DataControllerMetadataGenerator.TypeMetadata> metadata =
DataControllerMetadataGenerator.GetMetadata(description);
JToken metadataValue = new JObject(metadata.Select(
m => new KeyValuePair<string, JToken>(m.EncodedTypeName, m.ToJToken())));
return htmlHelper.Raw(metadataValue);
}
}
}

View File

@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<CodeAnalysis Condition=" '$(CodeAnalysis)' == '' ">false</CodeAnalysis>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{B6895A1B-382F-4A69-99EC-E965E19B0AB3}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Microsoft.Web.Http.Data.Helpers</RootNamespace>
<AssemblyName>Microsoft.Web.Http.Data.Helpers</AssemblyName>
<FileAlignment>512</FileAlignment>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;ASPNETMVC</DefineConstants>
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\bin\Release\</OutputPath>
<DefineConstants>TRACE;ASPNETMVC</DefineConstants>
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
<RunCodeAnalysis>$(CodeAnalysis)</RunCodeAnalysis>
<DocumentationFile>$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'CodeCoverage|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\..\bin\CodeCoverage\</OutputPath>
<DefineConstants>TRACE;DEBUG;CODE_COVERAGE;ASPNETMVC</DefineConstants>
<DebugType>full</DebugType>
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\Newtonsoft.Json.4.5.1\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.configuration" />
<Reference Include="System.Net.Http, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Net.Http.2.0.20326.1\lib\net40\System.Net.Http.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.WebRequest, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Net.Http.2.0.20326.1\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Web" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\AptcaCommonAssemblyInfo.cs">
<Link>Properties\AptcaCommonAssemblyInfo.cs</Link>
</Compile>
<Compile Include="..\CommonAssemblyInfo.cs">
<Link>Properties\CommonAssemblyInfo.cs</Link>
</Compile>
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="DataControllerMetadataGenerator.cs" />
<Compile Include="MetadataExtensions.cs" />
<Compile Include="..\Microsoft.Web.Http.Data\TypeUtility.cs">
<Link>TypeUtility.cs</Link>
</Compile>
<Compile Include="..\Microsoft.Web.Http.Data\TypeDescriptorExtensions.cs">
<Link>TypeDescriptorExtensions.cs</Link>
</Compile>
<Compile Include="UpshotExtensions.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Web.Http.Data\Microsoft.Web.Http.Data.csproj">
<Project>{ACE91549-D86E-4EB6-8C2A-5FF51386BB68}</Project>
<Name>Microsoft.Web.Http.Data</Name>
</ProjectReference>
<ProjectReference Include="..\System.Web.Http.WebHost\System.Web.Http.WebHost.csproj">
<Project>{A0187BC2-8325-4BB2-8697-7F955CF4173E}</Project>
<Name>System.Web.Http.WebHost</Name>
</ProjectReference>
<ProjectReference Include="..\System.Web.Http\System.Web.Http.csproj">
<Project>{DDC1CE0C-486E-4E35-BB3B-EAB61F8F9440}</Project>
<Name>System.Web.Http</Name>
</ProjectReference>
<ProjectReference Include="..\System.Web.Mvc\System.Web.Mvc.csproj">
<Project>{3D3FFD8A-624D-4E9B-954B-E1C105507975}</Project>
<Name>System.Web.Mvc</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<CodeAnalysisDictionary Include="..\CodeAnalysisDictionary.xml" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("Microsoft.Web.Http.Data.Helpers")]
[assembly: AssemblyDescription("Microsoft.Web.Http.Data.Helpers")]
[assembly: InternalsVisibleTo("Microsoft.Web.Http.Data.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]

View File

@@ -0,0 +1,320 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Mvc;
namespace Microsoft.Web.Http.Data.Helpers
{
[EditorBrowsable(EditorBrowsableState.Never)]
public static class UpshotExtensions
{
public static UpshotConfigBuilder UpshotContext(this HtmlHelper htmlHelper)
{
return UpshotContext(htmlHelper, false);
}
public static UpshotConfigBuilder UpshotContext(this HtmlHelper htmlHelper, bool bufferChanges)
{
return new UpshotConfigBuilder(htmlHelper, bufferChanges);
}
}
public class UpshotConfigBuilder : IHtmlString
{
private readonly HtmlHelper htmlHelper;
private readonly bool bufferChanges;
private readonly IDictionary<string, IDataSourceConfig> dataSources = new Dictionary<string, IDataSourceConfig>();
private readonly IDictionary<Type, string> clientMappings = new Dictionary<Type, string>();
public UpshotConfigBuilder(HtmlHelper htmlHelper, bool bufferChanges)
{
this.htmlHelper = htmlHelper;
this.bufferChanges = bufferChanges;
}
private interface IDataSourceConfig
{
string ClientName { get; }
Type DataControllerType { get; }
string SharedDataContextExpression { get; }
string DataContextExpression { set; }
string ClientMappingsJson { set; }
string GetInitializationScript();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Following established design pattern for HTML helpers.")]
public UpshotConfigBuilder DataSource<TDataController>(Expression<Func<TDataController, object>> queryOperation) where TDataController : DataController
{
return this.DataSource<TDataController>(queryOperation, null, null);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "Following established design pattern for HTML helpers."),
System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Following established design pattern for HTML helpers.")]
public UpshotConfigBuilder DataSource<TDataController>(Expression<Func<TDataController, object>> queryOperation, string serviceUrl, string clientName) where TDataController : DataController
{
IDataSourceConfig dataSourceConfig = new DataSourceConfig<TDataController>(htmlHelper, bufferChanges, queryOperation, serviceUrl, clientName);
if (dataSources.ContainsKey(dataSourceConfig.ClientName))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Cannot have multiple data sources with the same clientName. Found multiple data sources with the name '{0}'", dataSourceConfig.ClientName));
}
dataSources.Add(dataSourceConfig.ClientName, dataSourceConfig);
return this;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Following established design pattern for HTML helpers.")]
public UpshotConfigBuilder ClientMapping<TEntity>(string clientConstructor)
{
if (string.IsNullOrEmpty(clientConstructor))
{
throw new ArgumentException("clientConstructor cannot be null or empty", "clientConstructor");
}
if (clientMappings.ContainsKey(typeof(TEntity)))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Cannot have multiple client mappings for the same entity type. Found multiple client mappings for '{0}'", typeof(TEntity).FullName));
}
clientMappings.Add(typeof(TEntity), clientConstructor);
return this;
}
public string ToHtmlString()
{
StringBuilder js = new StringBuilder("upshot.dataSources = upshot.dataSources || {};\n");
// First emit metadata for each referenced DataController
IEnumerable<Type> dataControllerTypes = dataSources.Select(x => x.Value.DataControllerType).Distinct();
foreach (Type dataControllerType in dataControllerTypes)
{
js.AppendFormat("upshot.metadata({0});\n", GetMetadata(dataControllerType));
}
// Let the first dataSource construct a dataContext, and all subsequent ones share it
IEnumerable<IDataSourceConfig> allDataSources = dataSources.Values;
IDataSourceConfig firstDataSource = allDataSources.FirstOrDefault();
if (firstDataSource != null)
{
// All but the first data source share the DataContext implicitly instantiated by the first.
foreach (IDataSourceConfig dataSource in allDataSources.Skip(1))
{
dataSource.DataContextExpression = firstDataSource.SharedDataContextExpression;
}
// Let the first dataSource define the client mappings
firstDataSource.ClientMappingsJson = GetClientMappingsObjectLiteral();
}
// Now emit initialization code for each dataSource
foreach (IDataSourceConfig dataSource in allDataSources)
{
js.AppendLine("\n" + dataSource.GetInitializationScript());
}
// Also record the mapping functions in use
foreach (var mapping in clientMappings)
{
js.AppendFormat("upshot.registerType(\"{0}\", function() {{ return {1} }});\n", EncodeServerTypeName(mapping.Key), mapping.Value);
}
return string.Format(CultureInfo.InvariantCulture, "<script type='text/javascript'>\n{0}</script>", js);
}
private string GetMetadata(Type dataControllerType)
{
var methodInfo = typeof(MetadataExtensions).GetMethod("Metadata");
var result = (IHtmlString)methodInfo.MakeGenericMethod(dataControllerType).Invoke(null, new[] { htmlHelper });
return result.ToHtmlString();
}
private string GetClientMappingsObjectLiteral()
{
IEnumerable<string> clientMappingStrings =
clientMappings.Select(
clientMapping => string.Format(CultureInfo.InvariantCulture, "\"{0}\": function(data) {{ return new {1}(data) }}", EncodeServerTypeName(clientMapping.Key), clientMapping.Value));
return string.Format(CultureInfo.InvariantCulture, "{{{0}}}", string.Join(",", clientMappingStrings));
}
// TODO: Duplicated from DataControllerMetadataGenerator.cs. Refactor when combining this into the main System.Web.Http.Data.Helper assembly.
private static string EncodeServerTypeName(Type type)
{
return String.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", type.Name, ":#", type.Namespace);
}
private class DataSourceConfig<TDataController> : IDataSourceConfig where TDataController : DataController
{
private readonly HtmlHelper htmlHelper;
private readonly bool bufferChanges;
private readonly Expression<Func<TDataController, object>> queryOperation;
private readonly string serviceUrlOverride;
private readonly string clientName;
public DataSourceConfig(HtmlHelper htmlHelper, bool bufferChanges, Expression<Func<TDataController, object>> queryOperation, string serviceUrlOverride, string clientName)
{
this.htmlHelper = htmlHelper;
this.bufferChanges = bufferChanges;
this.queryOperation = queryOperation;
this.serviceUrlOverride = serviceUrlOverride;
this.clientName = string.IsNullOrEmpty(clientName) ? DefaultClientName : clientName;
}
public string ClientName
{
get
{
return clientName;
}
}
public Type DataControllerType
{
get
{
return typeof(TDataController);
}
}
public string DataContextExpression { private get; set; }
public string ClientMappingsJson { private get; set; }
public string SharedDataContextExpression
{
get
{
return ClientExpression + ".getDataContext()";
}
}
private string ClientExpression
{
get
{
return "upshot.dataSources." + ClientName;
}
}
private Type EntityType
{
get
{
Type operationReturnType = OperationMethod.ReturnType;
Type genericTypeDefinition = operationReturnType.IsGenericType ? operationReturnType.GetGenericTypeDefinition() : null;
Type entityType;
if (genericTypeDefinition != null && (genericTypeDefinition == typeof(IQueryable<>) || genericTypeDefinition == typeof(IEnumerable<>)))
{
// Permits IQueryable<TEntity> and IEnumerable<TEntity>.
entityType = operationReturnType.GetGenericArguments().Single();
}
else
{
entityType = operationReturnType;
}
if (!Description.EntityTypes.Any(type => type == entityType))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "queryOperation '{0}' must return an entity type or an IEnumerable/IQueryable of an entity type", OperationMethod.Name));
}
return entityType;
}
}
private string ServiceUrl
{
get
{
if (!string.IsNullOrEmpty(serviceUrlOverride))
{
return serviceUrlOverride;
}
UrlHelper urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
string dataControllerName = typeof(TDataController).Name;
if (!dataControllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("DataController type name must end with 'Controller'");
}
string controllerRouteName = dataControllerName.Substring(0, dataControllerName.Length - "Controller".Length);
return urlHelper.RouteUrl(new { controller = controllerRouteName, action = UrlParameter.Optional, httproute = true });
}
}
private string DefaultClientName
{
get
{
string operationName = OperationMethod.Name;
// By convention, strip away any "Get" verb on the method. Clients can override by explictly specifying client name.
return operationName.StartsWith("Get", StringComparison.OrdinalIgnoreCase) && operationName.Length > 3 && char.IsLetter(operationName[3]) ? operationName.Substring(3) : operationName;
}
}
private MethodInfo OperationMethod
{
get
{
Expression body = queryOperation.Body;
// The VB compiler will inject a convert to object here.
if (body.NodeType == ExpressionType.Convert)
{
UnaryExpression convert = (UnaryExpression)body;
if (convert.Type == typeof(object))
{
body = convert.Operand;
}
}
MethodCallExpression methodCall = body as MethodCallExpression;
if (methodCall == null)
{
throw new ArgumentException("queryOperation must be a method call");
}
if (!methodCall.Method.DeclaringType.IsAssignableFrom(typeof(TDataController)))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "queryOperation must be a method on '{0}' or a base type", typeof(TDataController).Name));
}
return methodCall.Method;
}
}
private static DataControllerDescription Description
{
get
{
HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor
{
Configuration = GlobalConfiguration.Configuration, // This helper can't be run until after global app init.
ControllerType = typeof(TDataController)
};
DataControllerDescription description = DataControllerDescription.GetDescription(controllerDescriptor);
return description;
}
}
public string GetInitializationScript()
{
return string.Format(CultureInfo.InvariantCulture, @"{0} = upshot.RemoteDataSource({{
providerParameters: {{ url: ""{1}"", operationName: ""{2}"" }},
entityType: ""{3}"",
bufferChanges: {4},
dataContext: {5},
mapping: {6}
}});",
ClientExpression, ServiceUrl, OperationMethod.Name, EncodeServerTypeName(EntityType),
bufferChanges ? "true" : "false", DataContextExpression ?? "undefined", ClientMappingsJson ?? "undefined");
}
}
}
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Net.Http" version="2.0.20326.1" />
<package id="Newtonsoft.Json" version="4.5.1" />
</packages>