diff --git a/src/System.Web.Compatibility/NotSupported.Exclude.txt b/src/System.Web.Compatibility/NotSupported.Exclude.txt index 04797e4eb..d8bd9fb39 100644 --- a/src/System.Web.Compatibility/NotSupported.Exclude.txt +++ b/src/System.Web.Compatibility/NotSupported.Exclude.txt @@ -5,3 +5,7 @@ T:System.Web.HttpUtility P:System.Web.HttpContext.Current P:System.Web.Hosting.HostingEnvironment.IsHosted P:System.Web.Hosting.HostingEnvironment.SiteName +T:System.Web.Routing.RequestContext +T:System.Web.Routing.RouteData +T:System.Web.Routing.RouteValueDictionary +T:System.Web.Routing.StopRoutingHandler diff --git a/src/System.Web.Compatibility/Strings.Designer.cs b/src/System.Web.Compatibility/Strings.Designer.cs index 124559871..7ceb5ec48 100644 --- a/src/System.Web.Compatibility/Strings.Designer.cs +++ b/src/System.Web.Compatibility/Strings.Designer.cs @@ -68,5 +68,14 @@ namespace System.Web { return ResourceManager.GetString("PlatformNotSupportedSystemWeb", resourceCulture); } } + + /// + /// Looks up a localized string similar to The RouteData must contain an item named '{0}' with a non-empty string value.. + /// + internal static string RouteData_RequiredValue { + get { + return ResourceManager.GetString("RouteData_RequiredValue", resourceCulture); + } + } } } diff --git a/src/System.Web.Compatibility/Strings.resx b/src/System.Web.Compatibility/Strings.resx index d3854a644..a8b3f4e3c 100644 --- a/src/System.Web.Compatibility/Strings.resx +++ b/src/System.Web.Compatibility/Strings.resx @@ -120,4 +120,7 @@ System.Web is not supported on this platform + + The RouteData must contain an item named '{0}' with a non-empty string value. + \ No newline at end of file diff --git a/src/System.Web.Compatibility/System/Web/Routing/RequestContext.cs b/src/System.Web.Compatibility/System/Web/Routing/RequestContext.cs new file mode 100644 index 000000000..5cd735f0b --- /dev/null +++ b/src/System.Web.Compatibility/System/Web/Routing/RequestContext.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +namespace System.Web.Routing +{ + [TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")] + public class RequestContext + { + public RequestContext() { } + + public RequestContext(HttpContextBase httpContext, RouteData routeData) + { + HttpContext = httpContext ?? throw new ArgumentNullException(nameof(httpContext)); + RouteData = routeData ?? throw new ArgumentNullException(nameof(routeData)); + } + + public virtual HttpContextBase HttpContext { get; set; } + + public virtual RouteData RouteData { get; set; } + } +} diff --git a/src/System.Web.Compatibility/System/Web/Routing/RouteData.cs b/src/System.Web.Compatibility/System/Web/Routing/RouteData.cs new file mode 100644 index 000000000..54217b0d3 --- /dev/null +++ b/src/System.Web.Compatibility/System/Web/Routing/RouteData.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using System.Runtime.CompilerServices; + +namespace System.Web.Routing { + + [TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")] + public class RouteData { + public RouteData() { } + + public RouteData(RouteBase route, IRouteHandler routeHandler) { + Route = route; + RouteHandler = routeHandler; + } + + public RouteValueDictionary DataTokens { get; } = new RouteValueDictionary(); + + public RouteBase Route { get; set; } + + public IRouteHandler RouteHandler { get; set; } + + public RouteValueDictionary Values { get; } = new RouteValueDictionary(); + + public string GetRequiredString(string valueName) + { + if (Values.TryGetValue(valueName, out object value)) + { + string valueString = value as string; + if (!string.IsNullOrEmpty(valueString)) + { + return valueString; + } + } + + throw new InvalidOperationException( + string.Format( + CultureInfo.CurrentUICulture, + Strings.RouteData_RequiredValue, + valueName)); + } + } +} diff --git a/src/System.Web.Compatibility/System/Web/Routing/RouteValueDictionary.cs b/src/System.Web.Compatibility/System/Web/Routing/RouteValueDictionary.cs new file mode 100644 index 000000000..a475eea6d --- /dev/null +++ b/src/System.Web.Compatibility/System/Web/Routing/RouteValueDictionary.cs @@ -0,0 +1,182 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace System.Web.Routing +{ + [TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")] + public class RouteValueDictionary : IDictionary + { + private Dictionary _dictionary; + + public RouteValueDictionary() + { + _dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + public RouteValueDictionary(object values) + { + _dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + + AddValues(values); + } + + public RouteValueDictionary(IDictionary dictionary) + { + _dictionary = new Dictionary(dictionary, StringComparer.OrdinalIgnoreCase); + } + + public int Count + { + get + { + return _dictionary.Count; + } + } + + public Dictionary.KeyCollection Keys + { + get + { + return _dictionary.Keys; + } + } + + public Dictionary.ValueCollection Values + { + get + { + return _dictionary.Values; + } + } + + public object this[string key] + { + get + { + object value; + TryGetValue(key, out value); + return value; + } + set + { + _dictionary[key] = value; + } + } + + public void Add(string key, object value) + { + _dictionary.Add(key, value); + } + + private void AddValues(object values) + { + if (values != null) + { + PropertyDescriptorCollection props = TypeDescriptor.GetProperties(values); + foreach (PropertyDescriptor prop in props) + { + object val = prop.GetValue(values); + Add(prop.Name, val); + } + } + } + + public void Clear() + { + _dictionary.Clear(); + } + + public bool ContainsKey(string key) + { + return _dictionary.ContainsKey(key); + } + + public bool ContainsValue(object value) + { + return _dictionary.ContainsValue(value); + } + + public Dictionary.Enumerator GetEnumerator() + { + return _dictionary.GetEnumerator(); + } + + public bool Remove(string key) + { + return _dictionary.Remove(key); + } + + public bool TryGetValue(string key, out object value) + { + return _dictionary.TryGetValue(key, out value); + } + + #region IDictionary Members + ICollection IDictionary.Keys + { + get + { + return _dictionary.Keys; + } + } + + ICollection IDictionary.Values + { + get + { + return _dictionary.Values; + } + } + #endregion + + #region ICollection> Members + void ICollection>.Add(KeyValuePair item) + { + ((ICollection>)_dictionary).Add(item); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return ((ICollection>)_dictionary).Contains(item); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((ICollection>)_dictionary).CopyTo(array, arrayIndex); + } + + bool ICollection>.IsReadOnly + { + get + { + return ((ICollection>)_dictionary).IsReadOnly; + } + } + + bool ICollection>.Remove(KeyValuePair item) + { + return ((ICollection>)_dictionary).Remove(item); + } + #endregion + + #region IEnumerable> Members + IEnumerator> IEnumerable>.GetEnumerator() + { + return GetEnumerator(); + } + #endregion + + #region IEnumerable Members + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + #endregion + } +} diff --git a/src/System.Web.Compatibility/System/Web/Routing/StopRoutingHandler.cs b/src/System.Web.Compatibility/System/Web/Routing/StopRoutingHandler.cs new file mode 100644 index 000000000..1c405a1ee --- /dev/null +++ b/src/System.Web.Compatibility/System/Web/Routing/StopRoutingHandler.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; + +namespace System.Web.Routing +{ + [TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")] + public class StopRoutingHandler : IRouteHandler + { + protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) + { + throw new NotSupportedException(); + } + + #region IRouteHandler Members + IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) + { + return GetHttpHandler(requestContext); + } + #endregion + + } +} diff --git a/tests/System.Web.Compatibility.Tests/RequestContextTests.cs b/tests/System.Web.Compatibility.Tests/RequestContextTests.cs new file mode 100644 index 000000000..a5fd655c9 --- /dev/null +++ b/tests/System.Web.Compatibility.Tests/RequestContextTests.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Web.Routing; +using Xunit; + +namespace System.Web.Compatibility.Tests +{ + public class RequestContextTests + { + [Fact] + public void RequestContextDefaultCtor() + { + RequestContext rc = new RequestContext(); + Assert.Null(rc.HttpContext); + Assert.Null(rc.RouteData); + } + + [Fact] + public void RequestContextCtor() + { + Assert.Throws("httpContext", () => new RequestContext(null, null)); + Assert.Throws("httpContext", () => new RequestContext(null, new RouteData())); + + // can't construct a HttpContextBase as it will throw PNSE + } + } +} diff --git a/tests/System.Web.Compatibility.Tests/RouteDataTests.cs b/tests/System.Web.Compatibility.Tests/RouteDataTests.cs new file mode 100644 index 000000000..af46f4390 --- /dev/null +++ b/tests/System.Web.Compatibility.Tests/RouteDataTests.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Web.Routing; +using Xunit; + +namespace System.Web.Compatibility.Tests +{ + public class RouteDataTests + { + [Fact] + public void RouteDataDefaultCtor() + { + RouteData rd = new RouteData(); + Assert.Null(rd.Route); + Assert.Null(rd.RouteHandler); + + Assert.NotNull(rd.Values); + Assert.NotNull(rd.DataTokens); + } + + class NoopHandler : IRouteHandler + { + public IHttpHandler GetHttpHandler(RequestContext requestContext) + { + throw new NotImplementedException(); + } + } + + [Fact] + public void RouteDataRouteHandlerCtor() + { + RouteData rd = new RouteData(null, null); + Assert.Null(rd.Route); + Assert.Null(rd.RouteHandler); + + Assert.NotNull(rd.Values); + Assert.NotNull(rd.DataTokens); + + // can't construct a RouteBase as it will throw PNSE + RouteBase rb = null; + IRouteHandler rh = new NoopHandler(); + rd = new RouteData(rb, rh); + Assert.Same(rh, rd.RouteHandler); + + Assert.NotNull(rd.Values); + Assert.NotNull(rd.DataTokens); + } + + [Fact] + public void RouteDataValuesString() + { + RouteData rd = new RouteData(); + string key = nameof(key); + string value = nameof(value); + rd.Values.Add(key, value); + + string actual = rd.GetRequiredString(key); + Assert.Equal(value, actual); + } + + [Theory] + [InlineData(typeof(RouteData))] + [InlineData(null)] + [InlineData(42)] + [InlineData(new [] { 1 })] + public void RouteDataValuesThrow(object value) + { + RouteData rd = new RouteData(); + string key = nameof(key); + rd.Values.Add(key, value); + + Assert.Throws(() => rd.GetRequiredString(key)); + } + + [Fact] + public void RouteDataValuesNoExist() + { + RouteData rd = new RouteData(); + string key = nameof(key); + + Assert.Throws(() => rd.GetRequiredString(key)); + } + } +} diff --git a/tests/System.Web.Compatibility.Tests/StopRoutingHandlerTests.cs b/tests/System.Web.Compatibility.Tests/StopRoutingHandlerTests.cs new file mode 100644 index 000000000..0ab911b74 --- /dev/null +++ b/tests/System.Web.Compatibility.Tests/StopRoutingHandlerTests.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Web.Routing; +using Xunit; + +namespace System.Web.Compatibility.Tests +{ + public class StopRoutingHandlerTests + { + [Fact] + public void StopRoutingHandlerDefault() + { + StopRoutingHandler srh = new StopRoutingHandler(); + + IRouteHandler rh = srh as IRouteHandler; + + Assert.NotNull(rh); + Assert.Throws(() => rh.GetHttpHandler(null)); + } + } +}