From b11f7d51c3959181622b95750dcf010ca415948b Mon Sep 17 00:00:00 2001 From: damianedwards Date: Wed, 6 May 2015 16:50:43 -0700 Subject: [PATCH] Bunch of changes: - Implemented pluggable strategies for determining request culture - Added NotNull on public APIs - Added support for a default request culture - Added options class for configuring the middleware - Improved the query string logic to support separate formatting & language cultures - Implemented the logic for accept-language header - Added more doc comments --- ...eptLanguageHeaderRequestCultureStrategy.cs | 67 ++++++++++++++++ .../CookieRequestCultureStrategy.cs | 18 +++++ .../CustomRequestCultureStrategy.cs | 24 ++++++ .../IApplicationBuilderExtensions.cs | 23 +++++- .../IRequestCultureFeature.cs | 7 ++ .../IRequestCultureStrategy.cs | 12 +++ .../Internal/CultureUtilities.cs | 29 +++++++ .../QueryStringRequestCultureStrategy.cs | 67 ++++++++++++++++ .../RequestCulture.cs | 5 +- .../RequestCultureFeature.cs | 8 +- .../RequestLocalizationMiddleware.cs | 55 +++++++------- .../RequestLocalizationMiddlewareOptions.cs | 76 +++++++++++++++++++ .../project.json | 2 + .../LocalizedString.cs | 6 +- .../StringLocalizerOfT.cs | 12 +-- .../project.json | 2 +- ...LocalizationServiceCollectionExtensions.cs | 3 +- .../ResourceManagerStringLocalizer.cs | 19 ++--- .../ResourceManagerStringLocalizerFactory.cs | 7 +- ...sourceManagerWithCultureStringLocalizer.cs | 17 +++-- .../project.json | 1 + 21 files changed, 396 insertions(+), 64 deletions(-) create mode 100644 src/Microsoft.AspNet.Localization/AcceptLanguageHeaderRequestCultureStrategy.cs create mode 100644 src/Microsoft.AspNet.Localization/CookieRequestCultureStrategy.cs create mode 100644 src/Microsoft.AspNet.Localization/CustomRequestCultureStrategy.cs create mode 100644 src/Microsoft.AspNet.Localization/IRequestCultureStrategy.cs create mode 100644 src/Microsoft.AspNet.Localization/Internal/CultureUtilities.cs create mode 100644 src/Microsoft.AspNet.Localization/QueryStringRequestCultureStrategy.cs create mode 100644 src/Microsoft.AspNet.Localization/RequestLocalizationMiddlewareOptions.cs diff --git a/src/Microsoft.AspNet.Localization/AcceptLanguageHeaderRequestCultureStrategy.cs b/src/Microsoft.AspNet.Localization/AcceptLanguageHeaderRequestCultureStrategy.cs new file mode 100644 index 0000000000..1a90e5332a --- /dev/null +++ b/src/Microsoft.AspNet.Localization/AcceptLanguageHeaderRequestCultureStrategy.cs @@ -0,0 +1,67 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Globalization; +using System.Linq; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Localization.Internal; +using Microsoft.Framework.Internal; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNet.Localization +{ + /// + /// Determines the culture information for a request via the value of the Accept-Language header. + /// + /// + /// + /// + public class AcceptLanguageHeaderRequestCultureStrategy : IRequestCultureStrategy + { + /// + /// The maximum number of values in the Accept-Language header to attempt to create a + /// from for the current request. + /// Defaults to 3. + /// + public int MaximumAcceptLanguageHeaderValuesToTry { get; set; } = 3; + + /// + public RequestCulture DetermineRequestCulture([NotNull] HttpContext httpContext) + { + var acceptLanguageHeader = httpContext.Request.GetTypedHeaders().AcceptLanguage; + + if (acceptLanguageHeader == null || acceptLanguageHeader.Count == 0) + { + return null; + } + + var languages = acceptLanguageHeader.AsEnumerable(); + + if (MaximumAcceptLanguageHeaderValuesToTry > 0) + { + // We take only the first configured number of languages from the header and then order those that we + // attempt to parse as a CultureInfo to mitigate potentially spinning CPU on lots of parse attempts. + languages = languages.Take(MaximumAcceptLanguageHeaderValuesToTry); + } + + var orderedLanguages = languages.OrderByDescending(h => h, StringWithQualityHeaderValueComparer.QualityComparer) + .ToList(); + + foreach (var language in orderedLanguages) + { + // Allow empty string values as they map to InvariantCulture, whereas null culture values will throw in + // the CultureInfo ctor + if (language.Value != null) + { + try + { + return new RequestCulture(CultureUtilities.GetCultureFromName(language.Value)); + } + catch (CultureNotFoundException) { } + } + } + + return null; + } + } +} diff --git a/src/Microsoft.AspNet.Localization/CookieRequestCultureStrategy.cs b/src/Microsoft.AspNet.Localization/CookieRequestCultureStrategy.cs new file mode 100644 index 0000000000..902d5819dd --- /dev/null +++ b/src/Microsoft.AspNet.Localization/CookieRequestCultureStrategy.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Http; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Localization +{ + public class CookieRequestCultureStrategy : IRequestCultureStrategy + { + public RequestCulture DetermineRequestCulture([NotNull] HttpContext httpContext) + { + // TODO + return null; + } + } +} diff --git a/src/Microsoft.AspNet.Localization/CustomRequestCultureStrategy.cs b/src/Microsoft.AspNet.Localization/CustomRequestCultureStrategy.cs new file mode 100644 index 0000000000..629862c1e5 --- /dev/null +++ b/src/Microsoft.AspNet.Localization/CustomRequestCultureStrategy.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Http; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Localization +{ + public class CustomRequestCultureStrategy : IRequestCultureStrategy + { + private readonly Func _strategy; + + public CustomRequestCultureStrategy([NotNull] Func strategy) + { + _strategy = strategy; + } + + public RequestCulture DetermineRequestCulture([NotNull] HttpContext httpContext) + { + return _strategy(httpContext); + } + } +} diff --git a/src/Microsoft.AspNet.Localization/IApplicationBuilderExtensions.cs b/src/Microsoft.AspNet.Localization/IApplicationBuilderExtensions.cs index 96330b7bd6..d3d7a65077 100644 --- a/src/Microsoft.AspNet.Localization/IApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNet.Localization/IApplicationBuilderExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Localization; +using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Builder { @@ -12,13 +13,29 @@ namespace Microsoft.AspNet.Builder { /// /// Adds the to automatically set culture information for - /// requests based on information provided by the client. + /// requests based on information provided by the client using the default options. /// /// The . /// The . - public static IApplicationBuilder UseRequestLocalization(this IApplicationBuilder builder) + public static IApplicationBuilder UseRequestLocalization([NotNull] this IApplicationBuilder builder) { - return builder.UseMiddleware(); + var options = new RequestLocalizationMiddlewareOptions(); + + return UseRequestLocalization(builder, options); + } + + /// + /// Adds the to automatically set culture information for + /// requests based on information provided by the client. + /// + /// The . + /// The options to configure the middleware with. + /// The . + public static IApplicationBuilder UseRequestLocalization( + [NotNull] this IApplicationBuilder builder, + [NotNull] RequestLocalizationMiddlewareOptions options) + { + return builder.UseMiddleware(options); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Localization/IRequestCultureFeature.cs b/src/Microsoft.AspNet.Localization/IRequestCultureFeature.cs index 65417e228d..73cd046f50 100644 --- a/src/Microsoft.AspNet.Localization/IRequestCultureFeature.cs +++ b/src/Microsoft.AspNet.Localization/IRequestCultureFeature.cs @@ -12,5 +12,12 @@ namespace Microsoft.AspNet.Localization /// The of the request. /// RequestCulture RequestCulture { get; } + + /// + /// The that determined the request's culture information. + /// If the value is null then no strategy was used and the request's culture was set to the value of + /// . + /// + IRequestCultureStrategy Strategy { get; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Localization/IRequestCultureStrategy.cs b/src/Microsoft.AspNet.Localization/IRequestCultureStrategy.cs new file mode 100644 index 0000000000..93f943488b --- /dev/null +++ b/src/Microsoft.AspNet.Localization/IRequestCultureStrategy.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Http; + +namespace Microsoft.AspNet.Localization +{ + public interface IRequestCultureStrategy + { + RequestCulture DetermineRequestCulture(HttpContext httpContext); + } +} diff --git a/src/Microsoft.AspNet.Localization/Internal/CultureUtilities.cs b/src/Microsoft.AspNet.Localization/Internal/CultureUtilities.cs new file mode 100644 index 0000000000..bea6d2145d --- /dev/null +++ b/src/Microsoft.AspNet.Localization/Internal/CultureUtilities.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Globalization; + +namespace Microsoft.AspNet.Localization.Internal +{ + public static class CultureUtilities + { + public static CultureInfo GetCultureFromName(string cultureName) + { + // Allow empty string values as they map to InvariantCulture, whereas null culture values will throw in + // the CultureInfo ctor + if (cultureName == null) + { + return null; + } + + try + { + return new CultureInfo(cultureName); + } + catch (CultureNotFoundException) + { + return null; + } + } + } +} diff --git a/src/Microsoft.AspNet.Localization/QueryStringRequestCultureStrategy.cs b/src/Microsoft.AspNet.Localization/QueryStringRequestCultureStrategy.cs new file mode 100644 index 0000000000..c7cfdb77e7 --- /dev/null +++ b/src/Microsoft.AspNet.Localization/QueryStringRequestCultureStrategy.cs @@ -0,0 +1,67 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Localization.Internal; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Localization +{ + /// + /// Determines the culture information for a request via values in the query string. + /// + public class QueryStringRequestCultureStrategy : IRequestCultureStrategy + { + /// + /// The key that contains the culture name. + /// Defaults to "culture". + /// + public string QueryStringKey { get; set; } = "culture"; + + /// + /// The key that contains the UI culture name. If not specified or no value is found, + /// will be used. + /// Defaults to "ui-culture". + /// + public string UIQueryStringKey { get; set; } = "ui-culture"; + + /// + public RequestCulture DetermineRequestCulture([NotNull] HttpContext httpContext) + { + var request = httpContext.Request; + if (!request.QueryString.HasValue) + { + return null; + } + + string queryCulture = null; + string queryUICulture = null; + + if (!string.IsNullOrWhiteSpace(QueryStringKey)) + { + queryCulture = request.Query[QueryStringKey]; + } + + if (!string.IsNullOrWhiteSpace(UIQueryStringKey)) + { + queryUICulture = request.Query[UIQueryStringKey]; + } + + if (queryCulture == null && queryUICulture == null) + { + // No values specified for either so no match + return null; + } + + if (queryCulture != null && queryUICulture == null) + { + // Value for culture but not for UI culture so default to culture value for both + queryUICulture = queryCulture; + } + + return new RequestCulture( + CultureUtilities.GetCultureFromName(queryCulture), + CultureUtilities.GetCultureFromName(queryUICulture)); + } + } +} diff --git a/src/Microsoft.AspNet.Localization/RequestCulture.cs b/src/Microsoft.AspNet.Localization/RequestCulture.cs index c0dc4a5680..ec109e6d8e 100644 --- a/src/Microsoft.AspNet.Localization/RequestCulture.cs +++ b/src/Microsoft.AspNet.Localization/RequestCulture.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Globalization; +using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Localization { @@ -15,7 +16,7 @@ namespace Microsoft.AspNet.Localization /// properties set to the same value. /// /// The for the request. - public RequestCulture(CultureInfo culture) + public RequestCulture([NotNull] CultureInfo culture) : this (culture, culture) { @@ -27,7 +28,7 @@ namespace Microsoft.AspNet.Localization /// /// The for the request to be used for formatting. /// The for the request to be used for text, i.e. language. - public RequestCulture(CultureInfo culture, CultureInfo uiCulture) + public RequestCulture([NotNull] CultureInfo culture, [NotNull] CultureInfo uiCulture) { Culture = culture; UICulture = uiCulture; diff --git a/src/Microsoft.AspNet.Localization/RequestCultureFeature.cs b/src/Microsoft.AspNet.Localization/RequestCultureFeature.cs index 730145bd30..76505b4759 100644 --- a/src/Microsoft.AspNet.Localization/RequestCultureFeature.cs +++ b/src/Microsoft.AspNet.Localization/RequestCultureFeature.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.Framework.Internal; + namespace Microsoft.AspNet.Localization { /// @@ -12,12 +14,16 @@ namespace Microsoft.AspNet.Localization /// Creates a new with the specified . /// /// The . - public RequestCultureFeature(RequestCulture requestCulture) + public RequestCultureFeature([NotNull] RequestCulture requestCulture, IRequestCultureStrategy strategy) { RequestCulture = requestCulture; + Strategy = strategy; } /// public RequestCulture RequestCulture { get; } + + /// + public IRequestCultureStrategy Strategy { get; } } } diff --git a/src/Microsoft.AspNet.Localization/RequestLocalizationMiddleware.cs b/src/Microsoft.AspNet.Localization/RequestLocalizationMiddleware.cs index 3be800652f..2064690662 100644 --- a/src/Microsoft.AspNet.Localization/RequestLocalizationMiddleware.cs +++ b/src/Microsoft.AspNet.Localization/RequestLocalizationMiddleware.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; +using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Localization { @@ -16,14 +17,17 @@ namespace Microsoft.AspNet.Localization public class RequestLocalizationMiddleware { private readonly RequestDelegate _next; - + private readonly RequestLocalizationMiddlewareOptions _options; + /// /// Creates a new . /// /// The representing the next middleware in the pipeline. - public RequestLocalizationMiddleware(RequestDelegate next) + /// + public RequestLocalizationMiddleware([NotNull] RequestDelegate next, [NotNull] RequestLocalizationMiddlewareOptions options) { _next = next; + _options = options; } /// @@ -31,42 +35,35 @@ namespace Microsoft.AspNet.Localization /// /// The . /// A that completes when the middleware has completed processing. - public async Task Invoke(HttpContext context) + public async Task Invoke([NotNull] HttpContext context) { - // TODO: Make this read from Accept-Language header, cookie, app-provided delegate, etc. - if (context.Request.QueryString.HasValue) + var requestCulture = _options.DefaultRequestCulture ?? + new RequestCulture(CultureInfo.DefaultThreadCurrentCulture, CultureInfo.DefaultThreadCurrentUICulture); + + IRequestCultureStrategy winningStrategy = null; + + if (_options.RequestCultureStrategies != null) { - var queryCulture = context.Request.Query["culture"]; - if (!string.IsNullOrEmpty(queryCulture)) + foreach (var strategy in _options.RequestCultureStrategies) { - var requestCulture = new RequestCulture(new CultureInfo(queryCulture)); - - context.SetFeature(new RequestCultureFeature(requestCulture)); - - var originalCulture = CultureInfo.CurrentCulture; - var originalUICulture = CultureInfo.CurrentUICulture; - - SetCurrentCulture(requestCulture); - - await _next(context); - - return; + var result = strategy.DetermineRequestCulture(context); + if (result != null) + { + requestCulture = result; + winningStrategy = strategy; + break; + } } } - else - { - // NOTE: The below doesn't seem to be needed anymore now that DNX is correctly managing culture across - // async calls but we'll need to verify properly. - // Forcibly set thread to en-US as sometimes previous threads have wrong culture across async calls, - // see note above. - //var defaultRequestCulture = new RequestCulture(new CultureInfo("en-US")); - //SetCurrentCulture(defaultRequestCulture); - } + + context.SetFeature(new RequestCultureFeature(requestCulture, winningStrategy)); + + SetCurrentThreadCulture(requestCulture); await _next(context); } - private void SetCurrentCulture(RequestCulture requestCulture) + private static void SetCurrentThreadCulture(RequestCulture requestCulture) { #if DNX451 Thread.CurrentThread.CurrentCulture = requestCulture.Culture; diff --git a/src/Microsoft.AspNet.Localization/RequestLocalizationMiddlewareOptions.cs b/src/Microsoft.AspNet.Localization/RequestLocalizationMiddlewareOptions.cs new file mode 100644 index 0000000000..cfa8e6e196 --- /dev/null +++ b/src/Microsoft.AspNet.Localization/RequestLocalizationMiddlewareOptions.cs @@ -0,0 +1,76 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +namespace Microsoft.AspNet.Localization +{ + /// + /// Specifies options for the . + /// + public class RequestLocalizationMiddlewareOptions + { + /// + /// Creates a new with default values. + /// + public RequestLocalizationMiddlewareOptions() + { + DefaultRequestCulture = new RequestCulture(CultureInfo.DefaultThreadCurrentCulture, CultureInfo.DefaultThreadCurrentUICulture); + + RequestCultureStrategies = new List + { + new QueryStringRequestCultureStrategy(), + new CookieRequestCultureStrategy(), + new AcceptLanguageHeaderRequestCultureStrategy { MaximumAcceptLanguageHeaderValuesToTry = MaximumAcceptLanguageHeaderValuesToTry } + }; + } + + /// + /// The maximum number of values in the Accept-Language header to attempt to create a + /// from for the current request. + /// Defaults to 3. + /// + public int MaximumAcceptLanguageHeaderValuesToTry { get; set; } = 3; + + /// + /// The default to use. This value will be used if none of the configured + /// options result in a non-null result. + /// Defaults to set to + /// and set to . + /// + public RequestCulture DefaultRequestCulture { get; set; } + + /// + /// The cultures supported by the application. If this value is non-null, the + /// will only set the current request culture to an entry in this + /// list. A value of null means all cultures are supported. + /// Defaults to null. + /// + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Improves usability")] + public IList SupportedCultures { get; set; } + + /// + /// The UI cultures supported by the application. If this value is non-null, the + /// will only set the current request culture to an entry in this + /// list. A value of null means all cultures are supported. + /// Defaults to null. + /// + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Improves usability")] + public IList SupportedUICultures { get; set; } + + /// + /// An ordered list of strategies used to determine a request's culture information. The first strategy that + /// returns a non-null result for a given request will be used. + /// Defaults to the following: + /// + /// + /// + /// + /// + /// + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Improves usability")] + public IList RequestCultureStrategies { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Localization/project.json b/src/Microsoft.AspNet.Localization/project.json index 7561196f6f..a6d3910968 100644 --- a/src/Microsoft.AspNet.Localization/project.json +++ b/src/Microsoft.AspNet.Localization/project.json @@ -4,6 +4,7 @@ "dependencies": { "Microsoft.Framework.Localization.Abstractions": "1.0.0-*", + "Microsoft.Framework.NotNullAttribute.Internal": { "type": "build", "version": "1.0.0-*" }, "Microsoft.AspNet.Http.Extensions": "1.0.0-*" }, @@ -13,6 +14,7 @@ "dependencies": { "System.Collections": "4.0.10-*", "System.Linq": "4.0.0-*", + "System.Globalization": "4.0.10-*", "System.Threading": "4.0.10-*", "Microsoft.CSharp": "4.0.0-*" } diff --git a/src/Microsoft.Framework.Localization.Abstractions/LocalizedString.cs b/src/Microsoft.Framework.Localization.Abstractions/LocalizedString.cs index 4faf864309..4549457ff1 100644 --- a/src/Microsoft.Framework.Localization.Abstractions/LocalizedString.cs +++ b/src/Microsoft.Framework.Localization.Abstractions/LocalizedString.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.Framework.Internal; + namespace Microsoft.Framework.Localization { /// @@ -13,7 +15,7 @@ namespace Microsoft.Framework.Localization /// /// The name of the string in the resource it was loaded from. /// The actual string. - public LocalizedString(string name, string value) + public LocalizedString([NotNull] string name, [NotNull] string value) : this(name, value, resourceNotFound: false) { @@ -25,7 +27,7 @@ namespace Microsoft.Framework.Localization /// The name of the string in the resource it was loaded from. /// The actual string. /// Whether the string was found in a resource. Set this to false to indicate an alternate string value was used. - public LocalizedString(string name, string value, bool resourceNotFound) + public LocalizedString([NotNull] string name, [NotNull] string value, bool resourceNotFound) { Name = name; Value = value; diff --git a/src/Microsoft.Framework.Localization.Abstractions/StringLocalizerOfT.cs b/src/Microsoft.Framework.Localization.Abstractions/StringLocalizerOfT.cs index f222bf93b3..8801cdf234 100644 --- a/src/Microsoft.Framework.Localization.Abstractions/StringLocalizerOfT.cs +++ b/src/Microsoft.Framework.Localization.Abstractions/StringLocalizerOfT.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; +using Microsoft.Framework.Internal; namespace Microsoft.Framework.Localization { @@ -19,7 +20,7 @@ namespace Microsoft.Framework.Localization /// Creates a new . /// /// The to use. - public StringLocalizer(IStringLocalizerFactory factory) + public StringLocalizer([NotNull] IStringLocalizerFactory factory) { _localizer = factory.Create(typeof(TResourceSource)); } @@ -28,16 +29,17 @@ namespace Microsoft.Framework.Localization public virtual IStringLocalizer WithCulture(CultureInfo culture) => _localizer.WithCulture(culture); /// - public virtual LocalizedString this[string key] => _localizer[key]; + public virtual LocalizedString this[[NotNull] string key] => _localizer[key]; /// - public virtual LocalizedString this[string key, params object[] arguments] => _localizer[key, arguments]; + public virtual LocalizedString this[[NotNull] string key, params object[] arguments] => + _localizer[key, arguments]; /// - public virtual LocalizedString GetString(string key) => _localizer.GetString(key); + public virtual LocalizedString GetString([NotNull] string key) => _localizer.GetString(key); /// - public virtual LocalizedString GetString(string key, params object[] arguments) => + public virtual LocalizedString GetString([NotNull] string key, params object[] arguments) => _localizer.GetString(key, arguments); /// diff --git a/src/Microsoft.Framework.Localization.Abstractions/project.json b/src/Microsoft.Framework.Localization.Abstractions/project.json index 87aa883bfd..63eb5f2524 100644 --- a/src/Microsoft.Framework.Localization.Abstractions/project.json +++ b/src/Microsoft.Framework.Localization.Abstractions/project.json @@ -3,7 +3,7 @@ "description": "Abstractions of application localization services.", "dependencies": { - + "Microsoft.Framework.NotNullAttribute.Internal": { "type": "build", "version": "1.0.0-*" } }, "frameworks": { diff --git a/src/Microsoft.Framework.Localization/LocalizationServiceCollectionExtensions.cs b/src/Microsoft.Framework.Localization/LocalizationServiceCollectionExtensions.cs index 9b4a0079fc..fe6f4694f2 100644 --- a/src/Microsoft.Framework.Localization/LocalizationServiceCollectionExtensions.cs +++ b/src/Microsoft.Framework.Localization/LocalizationServiceCollectionExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.Framework.Internal; using Microsoft.Framework.Localization; namespace Microsoft.Framework.DependencyInjection @@ -15,7 +16,7 @@ namespace Microsoft.Framework.DependencyInjection /// /// The to add the services to. /// The . - public static IServiceCollection AddLocalization(this IServiceCollection services) + public static IServiceCollection AddLocalization([NotNull] this IServiceCollection services) { services.TryAdd(new ServiceDescriptor( typeof(IStringLocalizerFactory), diff --git a/src/Microsoft.Framework.Localization/ResourceManagerStringLocalizer.cs b/src/Microsoft.Framework.Localization/ResourceManagerStringLocalizer.cs index b6db0b6d13..526b16ab99 100644 --- a/src/Microsoft.Framework.Localization/ResourceManagerStringLocalizer.cs +++ b/src/Microsoft.Framework.Localization/ResourceManagerStringLocalizer.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Resources; +using Microsoft.Framework.Internal; namespace Microsoft.Framework.Localization { @@ -27,9 +28,9 @@ namespace Microsoft.Framework.Localization /// The that contains the strings as embedded resources. /// The base name of the embedded resource in the that contains the strings. public ResourceManagerStringLocalizer( - ResourceManager resourceManager, - Assembly resourceAssembly, - string baseName) + [NotNull] ResourceManager resourceManager, + [NotNull] Assembly resourceAssembly, + [NotNull] string baseName) { ResourceManager = resourceManager; ResourceAssembly = resourceAssembly; @@ -52,20 +53,20 @@ namespace Microsoft.Framework.Localization protected string ResourceBaseName { get; } /// - public virtual LocalizedString this[string name] => GetString(name); + public virtual LocalizedString this[[NotNull] string name] => GetString(name); /// - public virtual LocalizedString this[string name, params object[] arguments] => GetString(name, arguments); + public virtual LocalizedString this[[NotNull] string name, params object[] arguments] => GetString(name, arguments); /// - public virtual LocalizedString GetString(string name) + public virtual LocalizedString GetString([NotNull] string name) { var value = GetStringSafely(name, null); return new LocalizedString(name, value ?? name, resourceNotFound: value == null); } /// - public virtual LocalizedString GetString(string name, params object[] arguments) + public virtual LocalizedString GetString([NotNull] string name, params object[] arguments) { var format = GetStringSafely(name, null); var value = string.Format(format ?? name, arguments); @@ -95,7 +96,7 @@ namespace Microsoft.Framework.Localization /// The name of the string resource. /// The to get the string for. /// The resource string, or null if none was found. - protected string GetStringSafely(string name, CultureInfo culture) + protected string GetStringSafely([NotNull] string name, [NotNull] CultureInfo culture) { var cacheKey = new MissingManifestCacheKey(name, culture); if (_missingManifestCache.ContainsKey(cacheKey)) @@ -133,7 +134,7 @@ namespace Microsoft.Framework.Localization /// /// The to get strings for. /// The . - protected IEnumerator GetEnumerator(CultureInfo culture) + protected IEnumerator GetEnumerator([NotNull] CultureInfo culture) { // TODO: I'm sure something here should be cached, probably the whole result var resourceNames = GetResourceNamesFromCultureHierarchy(culture); diff --git a/src/Microsoft.Framework.Localization/ResourceManagerStringLocalizerFactory.cs b/src/Microsoft.Framework.Localization/ResourceManagerStringLocalizerFactory.cs index 801f6acef5..3039a9d377 100644 --- a/src/Microsoft.Framework.Localization/ResourceManagerStringLocalizerFactory.cs +++ b/src/Microsoft.Framework.Localization/ResourceManagerStringLocalizerFactory.cs @@ -4,6 +4,7 @@ using System; using System.Reflection; using System.Resources; +using Microsoft.Framework.Internal; using Microsoft.Framework.Runtime; namespace Microsoft.Framework.Localization @@ -19,7 +20,7 @@ namespace Microsoft.Framework.Localization /// Creates a new . /// /// - public ResourceManagerStringLocalizerFactory(IApplicationEnvironment applicationEnvironment) + public ResourceManagerStringLocalizerFactory([NotNull] IApplicationEnvironment applicationEnvironment) { _applicationEnvironment = applicationEnvironment; } @@ -30,7 +31,7 @@ namespace Microsoft.Framework.Localization /// /// The . /// The . - public IStringLocalizer Create(Type resourceSource) + public IStringLocalizer Create([NotNull] Type resourceSource) { var typeInfo = resourceSource.GetTypeInfo(); var assembly = typeInfo.Assembly; @@ -44,7 +45,7 @@ namespace Microsoft.Framework.Localization /// The base name of the resource to load strings from. /// The location to load resources from. /// The . - public IStringLocalizer Create(string baseName, string location) + public IStringLocalizer Create([NotNull] string baseName, [NotNull] string location) { var assembly = Assembly.Load(new AssemblyName(location ?? _applicationEnvironment.ApplicationName)); diff --git a/src/Microsoft.Framework.Localization/ResourceManagerWithCultureStringLocalizer.cs b/src/Microsoft.Framework.Localization/ResourceManagerWithCultureStringLocalizer.cs index 7b988b04ba..8e9027af76 100644 --- a/src/Microsoft.Framework.Localization/ResourceManagerWithCultureStringLocalizer.cs +++ b/src/Microsoft.Framework.Localization/ResourceManagerWithCultureStringLocalizer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Resources; +using Microsoft.Framework.Internal; namespace Microsoft.Framework.Localization { @@ -25,30 +26,30 @@ namespace Microsoft.Framework.Localization /// The base name of the embedded resource in the that contains the strings. /// The specific to use. public ResourceManagerWithCultureStringLocalizer( - ResourceManager resourceManager, - Assembly assembly, - string baseName, - CultureInfo culture) + [NotNull] ResourceManager resourceManager, + [NotNull] Assembly assembly, + [NotNull] string baseName, + [NotNull] CultureInfo culture) : base(resourceManager, assembly, baseName) { _culture = culture; } /// - public override LocalizedString this[string name] => GetString(name); + public override LocalizedString this[[NotNull] string name] => GetString(name); /// - public override LocalizedString this[string name, params object[] arguments] => GetString(name, arguments); + public override LocalizedString this[[NotNull] string name, params object[] arguments] => GetString(name, arguments); /// - public override LocalizedString GetString(string name) + public override LocalizedString GetString([NotNull] string name) { var value = GetStringSafely(name, _culture); return new LocalizedString(name, value ?? name); } /// - public override LocalizedString GetString(string name, params object[] arguments) + public override LocalizedString GetString([NotNull] string name, params object[] arguments) { var format = GetStringSafely(name, _culture); var value = string.Format(_culture, format ?? name, arguments); diff --git a/src/Microsoft.Framework.Localization/project.json b/src/Microsoft.Framework.Localization/project.json index 39a74a1e5a..c0ad0046fd 100644 --- a/src/Microsoft.Framework.Localization/project.json +++ b/src/Microsoft.Framework.Localization/project.json @@ -5,6 +5,7 @@ "dependencies": { "Microsoft.Framework.DependencyInjection.Abstractions": "1.0.0-*", "Microsoft.Framework.Localization.Abstractions": "1.0.0-*", + "Microsoft.Framework.NotNullAttribute.Internal": { "type": "build", "version": "1.0.0-*" }, "Microsoft.Framework.Runtime.Abstractions": "1.0.0-*", "System.Resources.ReaderWriter": "4.0.0-*" },