diff --git a/README.md b/README.md index 2ebe881..9aa8275 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,35 @@ # Random Data Web API -Provides the JSON/JSONP API to generate random data. +Provides the JSON Web API to generate random data. +This Web API supports CORS (Cross-Origin Resource Sharing). + +Using PaaS is the simplest way to host this Web API. +For example, if you fork this repository, you can deploy directly the Web API to an Azure Web App by the Microsoft Azure Portal. +In this case, the continuous deployment is configured. + +[日本語のドキュメント](docs) + +## Random Data +- alphabets +- alphanumerics +- byte sequence +- UUID (GUID) +- time-ordered ID + +## Web App +This project is actually the ASP.NET Web app that contains the following: +- Web API +- help page with specification +- test page using jQuery + +[randomdata.azurewebsites.net](https://randomdata.azurewebsites.net/) is a sample deployment. + +### Development Environment +- .NET Framework 4.5 +- ASP.NET Web API 5.2.3 +- ASP.NET Web API Help Page 5.2.3 +- ASP.NET Web API Cross-Origin Support 5.2.3 +- [Blaze 1.1.10](https://github.com/sakapon/Blaze) + +### Release Notes +- **v1.0.0** The first release, using ASP.NET MVC. +- **v2.0.6** Use ASP.NET Web API. diff --git a/RandomData2/RandomData2.sln b/RandomData2/RandomData2.sln new file mode 100644 index 0000000..6faca68 --- /dev/null +++ b/RandomData2/RandomData2.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27428.2015 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RandomDataWebApi", "RandomDataWebApi\RandomDataWebApi.csproj", "{A62DF1CA-11D0-427E-91CB-C08281D3DE7C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTest", "UnitTest\UnitTest.csproj", "{43896562-E647-4F9E-8498-8542FCA26834}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A62DF1CA-11D0-427E-91CB-C08281D3DE7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A62DF1CA-11D0-427E-91CB-C08281D3DE7C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A62DF1CA-11D0-427E-91CB-C08281D3DE7C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A62DF1CA-11D0-427E-91CB-C08281D3DE7C}.Release|Any CPU.Build.0 = Release|Any CPU + {43896562-E647-4F9E-8498-8542FCA26834}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43896562-E647-4F9E-8498-8542FCA26834}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43896562-E647-4F9E-8498-8542FCA26834}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43896562-E647-4F9E-8498-8542FCA26834}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {724388ED-9668-4465-8412-8C3C76730BC3} + EndGlobalSection +EndGlobal diff --git a/RandomGenerator/RandomWebApp/App_Start/WebApiConfig.cs b/RandomData2/RandomDataWebApi/App_Start/WebApiConfig.cs similarity index 55% rename from RandomGenerator/RandomWebApp/App_Start/WebApiConfig.cs rename to RandomData2/RandomDataWebApi/App_Start/WebApiConfig.cs index 8927264..de114de 100644 --- a/RandomGenerator/RandomWebApp/App_Start/WebApiConfig.cs +++ b/RandomData2/RandomDataWebApi/App_Start/WebApiConfig.cs @@ -2,13 +2,22 @@ using System.Collections.Generic; using System.Linq; using System.Web.Http; +using System.Web.Http.Cors; -namespace RandomWebApp +namespace RandomDataWebApi { public static class WebApiConfig { public static void Register(HttpConfiguration config) { + // Web API の設定およびサービス + + config.Formatters.Remove(config.Formatters.XmlFormatter); + config.EnableCors(new EnableCorsAttribute("*", "*", "*")); + + // Web API ルート + config.MapHttpAttributeRoutes(); + config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ApiDescriptionExtensions.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ApiDescriptionExtensions.cs new file mode 100644 index 0000000..0401850 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ApiDescriptionExtensions.cs @@ -0,0 +1,39 @@ +using System; +using System.Text; +using System.Web; +using System.Web.Http.Description; + +namespace RandomDataWebApi.Areas.HelpPage +{ + public static class ApiDescriptionExtensions + { + /// + /// Generates an URI-friendly ID for the . E.g. "Get-Values-id_name" instead of "GetValues/{id}?name={name}" + /// + /// The . + /// The ID as a string. + public static string GetFriendlyId(this ApiDescription description) + { + string path = description.RelativePath; + string[] urlParts = path.Split('?'); + string localPath = urlParts[0]; + string queryKeyString = null; + if (urlParts.Length > 1) + { + string query = urlParts[1]; + string[] queryKeys = HttpUtility.ParseQueryString(query).AllKeys; + queryKeyString = String.Join("_", queryKeys); + } + + StringBuilder friendlyPath = new StringBuilder(); + friendlyPath.AppendFormat("{0}-{1}", + description.HttpMethod.Method, + localPath.Replace("/", "-").Replace("{", String.Empty).Replace("}", String.Empty)); + if (queryKeyString != null) + { + friendlyPath.AppendFormat("_{0}", queryKeyString.Replace('.', '-')); + } + return friendlyPath.ToString(); + } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/App_Start/HelpPageConfig.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/App_Start/HelpPageConfig.cs new file mode 100644 index 0000000..0c44fa6 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/App_Start/HelpPageConfig.cs @@ -0,0 +1,114 @@ +// Uncomment the following to provide samples for PageResult. Must also add the Microsoft.AspNet.WebApi.OData +// package to your project. +////#define Handle_PageResultOfT + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Net.Http.Headers; +using System.Reflection; +using System.Web; +using System.Web.Http; +#if Handle_PageResultOfT +using System.Web.Http.OData; +#endif + +namespace RandomDataWebApi.Areas.HelpPage +{ + /// + /// Use this class to customize the Help Page. + /// For example you can set a custom to supply the documentation + /// or you can provide the samples for the requests/responses. + /// + public static class HelpPageConfig + { + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", + MessageId = "RandomDataWebApi.Areas.HelpPage.TextSample.#ctor(System.String)", + Justification = "End users may choose to merge this string with existing localized resources.")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", + MessageId = "bsonspec", + Justification = "Part of a URI.")] + public static void Register(HttpConfiguration config) + { + // Uncomment the following to use the documentation from XML documentation file. + config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/bin/RandomDataWebApi.xml"))); + + // Uncomment the following to use "sample string" as the sample for all actions that have string as the body parameter or return type. + // Also, the string arrays will be used for IEnumerable. The sample objects will be serialized into different media type + // formats by the available formatters. + config.SetSampleObjects(new Dictionary + { + { typeof(int), 8 }, + // {typeof(string), "sample string"}, + // {typeof(IEnumerable), new string[]{"sample 1", "sample 2"}} + }); + + // Extend the following to provide factories for types not handled automatically (those lacking parameterless + // constructors) or for which you prefer to use non-default property values. Line below provides a fallback + // since automatic handling will fail and GeneratePageResult handles only a single type. +#if Handle_PageResultOfT + config.GetHelpPageSampleGenerator().SampleObjectFactories.Add(GeneratePageResult); +#endif + + // Extend the following to use a preset object directly as the sample for all actions that support a media + // type, regardless of the body parameter or return type. The lines below avoid display of binary content. + // The BsonMediaTypeFormatter (if available) is not used to serialize the TextSample object. + config.SetSampleForMediaType( + new TextSample("Binary JSON content. See http://bsonspec.org for details."), + new MediaTypeHeaderValue("application/bson")); + + //// Uncomment the following to use "[0]=foo&[1]=bar" directly as the sample for all actions that support form URL encoded format + //// and have IEnumerable as the body parameter or return type. + //config.SetSampleForType("[0]=foo&[1]=bar", new MediaTypeHeaderValue("application/x-www-form-urlencoded"), typeof(IEnumerable)); + + //// Uncomment the following to use "1234" directly as the request sample for media type "text/plain" on the controller named "Values" + //// and action named "Put". + //config.SetSampleRequest("1234", new MediaTypeHeaderValue("text/plain"), "Values", "Put"); + + //// Uncomment the following to use the image on "../images/aspNetHome.png" directly as the response sample for media type "image/png" + //// on the controller named "Values" and action named "Get" with parameter "id". + //config.SetSampleResponse(new ImageSample("../images/aspNetHome.png"), new MediaTypeHeaderValue("image/png"), "Values", "Get", "id"); + + //// Uncomment the following to correct the sample request when the action expects an HttpRequestMessage with ObjectContent. + //// The sample will be generated as if the controller named "Values" and action named "Get" were having string as the body parameter. + //config.SetActualRequestType(typeof(string), "Values", "Get"); + + //// Uncomment the following to correct the sample response when the action returns an HttpResponseMessage with ObjectContent. + //// The sample will be generated as if the controller named "Values" and action named "Post" were returning a string. + //config.SetActualResponseType(typeof(string), "Values", "Post"); + } + +#if Handle_PageResultOfT + private static object GeneratePageResult(HelpPageSampleGenerator sampleGenerator, Type type) + { + if (type.IsGenericType) + { + Type openGenericType = type.GetGenericTypeDefinition(); + if (openGenericType == typeof(PageResult<>)) + { + // Get the T in PageResult + Type[] typeParameters = type.GetGenericArguments(); + Debug.Assert(typeParameters.Length == 1); + + // Create an enumeration to pass as the first parameter to the PageResult constuctor + Type itemsType = typeof(List<>).MakeGenericType(typeParameters); + object items = sampleGenerator.GetSampleObject(itemsType); + + // Fill in the other information needed to invoke the PageResult constuctor + Type[] parameterTypes = new Type[] { itemsType, typeof(Uri), typeof(long?), }; + object[] parameters = new object[] { items, null, (long)ObjectGenerator.DefaultCollectionSize, }; + + // Call PageResult(IEnumerable items, Uri nextPageLink, long? count) constructor + ConstructorInfo constructor = type.GetConstructor(parameterTypes); + return constructor.Invoke(parameters); + } + } + + return null; + } +#endif + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Controllers/HelpController.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/Controllers/HelpController.cs new file mode 100644 index 0000000..28d3cdd --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Controllers/HelpController.cs @@ -0,0 +1,68 @@ +using System; +using System.Web.Http; +using System.Web.Mvc; +using RandomDataWebApi.Areas.HelpPage.ModelDescriptions; +using RandomDataWebApi.Areas.HelpPage.Models; + +namespace RandomDataWebApi.Areas.HelpPage.Controllers +{ + /// + /// The controller that will handle requests for the help page. + /// + public class HelpController : Controller + { + private const string ErrorViewName = "Error"; + + public HelpController() + : this(GlobalConfiguration.Configuration) + { + } + + public HelpController(HttpConfiguration config) + { + Configuration = config; + } + + public HttpConfiguration Configuration { get; private set; } + + public ActionResult Index() + { + ViewBag.DocumentationProvider = Configuration.Services.GetDocumentationProvider(); + return View(Configuration.Services.GetApiExplorer().ApiDescriptions); + } + + public ActionResult Api(string apiId) + { + if (!String.IsNullOrEmpty(apiId)) + { + HelpPageApiModel apiModel = Configuration.GetHelpPageApiModel(apiId); + if (apiModel != null) + { + return View(apiModel); + } + } + + return View(ErrorViewName); + } + + public ActionResult ResourceModel(string modelName) + { + if (!String.IsNullOrEmpty(modelName)) + { + ModelDescriptionGenerator modelDescriptionGenerator = Configuration.GetModelDescriptionGenerator(); + ModelDescription modelDescription; + if (modelDescriptionGenerator.GeneratedModels.TryGetValue(modelName, out modelDescription)) + { + return View(modelDescription); + } + } + + return View(ErrorViewName); + } + + public ActionResult JsonTest() + { + return View(); + } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/HelpPage.css b/RandomData2/RandomDataWebApi/Areas/HelpPage/HelpPage.css new file mode 100644 index 0000000..a05b955 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/HelpPage.css @@ -0,0 +1,142 @@ +.help-page h1, +.help-page .h1, +.help-page h2, +.help-page .h2, +.help-page h3, +.help-page .h3, +#body.help-page, +footer.help-page, +.help-page-table th, +.help-page-table pre, +.help-page-table p { + font-family: "Segoe UI", Frutiger, "Frutiger Linotype", "Dejavu Sans", "Helvetica Neue", Arial, sans-serif; +} + +.help-page pre.wrapped { + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + white-space: pre-wrap; +} + +.help-page .warning-message-container { + margin-top: 20px; + padding: 0 10px; + color: #525252; + background: #EFDCA9; + border: 1px solid #CCCCCC; +} + +.help-page-table { + width: 100%; + border-collapse: collapse; + text-align: left; + margin: 0px 0px 20px 0px; + border-top: 1px solid #D4D4D4; +} + +.help-page-table th { + text-align: left; + font-weight: bold; + border-bottom: 1px solid #D4D4D4; + padding: 5px 6px 5px 6px; +} + +.help-page-table td { + border-bottom: 1px solid #D4D4D4; + padding: 10px 8px 10px 8px; + vertical-align: top; +} + +.help-page-table pre, +.help-page-table p { + margin: 0px; + padding: 0px; + font-family: inherit; + font-size: 100%; +} + +.help-page-table tbody tr:hover td { + background-color: #F3F3F3; +} + +.help-page a:hover { + background-color: transparent; +} + +.help-page .sample-header { + border: 2px solid #D4D4D4; + background: #00497E; + color: #FFFFFF; + padding: 8px 15px; + border-bottom: none; + display: inline-block; + margin: 10px 0px 0px 0px; +} + +.help-page .sample-content { + display: block; + border-width: 0; + padding: 15px 20px; + background: #FFFFFF; + border: 2px solid #D4D4D4; + margin: 0px 0px 10px 0px; +} + +.help-page .api-name { + width: 40%; +} + +.help-page .api-documentation { + width: 60%; +} + +.help-page .parameter-name { + width: 20%; +} + +.help-page .parameter-documentation { + width: 40%; +} + +.help-page .parameter-type { + width: 20%; +} + +.help-page .parameter-annotations { + width: 20%; +} + +.help-page h1, +.help-page .h1 { + font-size: 36px; + line-height: normal; +} + +.help-page h2, +.help-page .h2 { + font-size: 24px; +} + +.help-page h3, +.help-page .h3 { + font-size: 20px; +} + +#body.help-page, +footer.help-page { + font-size: 16px; + line-height: 143%; + color: #222; +} + +.help-page a { + color: #0000EE; + text-decoration: none; +} + +/* Set padding to keep content from hitting the edges */ +.body-content { + padding-left: 15px; + padding-right: 15px; +} diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/HelpPageAreaRegistration.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/HelpPageAreaRegistration.cs new file mode 100644 index 0000000..e093700 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/HelpPageAreaRegistration.cs @@ -0,0 +1,30 @@ +using System.Web.Http; +using System.Web.Mvc; + +namespace RandomDataWebApi.Areas.HelpPage +{ + public class HelpPageAreaRegistration : AreaRegistration + { + public override string AreaName + { + get + { + return "HelpPage"; + } + } + + public override void RegisterArea(AreaRegistrationContext context) + { + context.MapRoute( + "HelpPage_Root", + "", + new { controller = "Help", action = "Index" }); + context.MapRoute( + "HelpPage_Default", + "Help/{action}/{apiId}", + new { controller = "Help", action = "Index", apiId = UrlParameter.Optional }); + + HelpPageConfig.Register(GlobalConfiguration.Configuration); + } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/HelpPageConfigurationExtensions.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/HelpPageConfigurationExtensions.cs new file mode 100644 index 0000000..8452e6a --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/HelpPageConfigurationExtensions.cs @@ -0,0 +1,474 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Description; +using RandomDataWebApi.Areas.HelpPage.ModelDescriptions; +using RandomDataWebApi.Areas.HelpPage.Models; + +namespace RandomDataWebApi.Areas.HelpPage +{ + public static class HelpPageConfigurationExtensions + { + private const string ApiModelPrefix = "MS_HelpPageApiModel_"; + + /// + /// Sets the documentation provider for help page. + /// + /// The . + /// The documentation provider. + public static void SetDocumentationProvider(this HttpConfiguration config, IDocumentationProvider documentationProvider) + { + config.Services.Replace(typeof(IDocumentationProvider), documentationProvider); + } + + /// + /// Sets the objects that will be used by the formatters to produce sample requests/responses. + /// + /// The . + /// The sample objects. + public static void SetSampleObjects(this HttpConfiguration config, IDictionary sampleObjects) + { + config.GetHelpPageSampleGenerator().SampleObjects = sampleObjects; + } + + /// + /// Sets the sample request directly for the specified media type and action. + /// + /// The . + /// The sample request. + /// The media type. + /// Name of the controller. + /// Name of the action. + public static void SetSampleRequest(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName) + { + config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Request, controllerName, actionName, new[] { "*" }), sample); + } + + /// + /// Sets the sample request directly for the specified media type and action with parameters. + /// + /// The . + /// The sample request. + /// The media type. + /// Name of the controller. + /// Name of the action. + /// The parameter names. + public static void SetSampleRequest(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName, params string[] parameterNames) + { + config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Request, controllerName, actionName, parameterNames), sample); + } + + /// + /// Sets the sample request directly for the specified media type of the action. + /// + /// The . + /// The sample response. + /// The media type. + /// Name of the controller. + /// Name of the action. + public static void SetSampleResponse(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName) + { + config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Response, controllerName, actionName, new[] { "*" }), sample); + } + + /// + /// Sets the sample response directly for the specified media type of the action with specific parameters. + /// + /// The . + /// The sample response. + /// The media type. + /// Name of the controller. + /// Name of the action. + /// The parameter names. + public static void SetSampleResponse(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName, params string[] parameterNames) + { + config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Response, controllerName, actionName, parameterNames), sample); + } + + /// + /// Sets the sample directly for all actions with the specified media type. + /// + /// The . + /// The sample. + /// The media type. + public static void SetSampleForMediaType(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType) + { + config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType), sample); + } + + /// + /// Sets the sample directly for all actions with the specified type and media type. + /// + /// The . + /// The sample. + /// The media type. + /// The parameter type or return type of an action. + public static void SetSampleForType(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, Type type) + { + config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, type), sample); + } + + /// + /// Specifies the actual type of passed to the in an action. + /// The help page will use this information to produce more accurate request samples. + /// + /// The . + /// The type. + /// Name of the controller. + /// Name of the action. + public static void SetActualRequestType(this HttpConfiguration config, Type type, string controllerName, string actionName) + { + config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Request, controllerName, actionName, new[] { "*" }), type); + } + + /// + /// Specifies the actual type of passed to the in an action. + /// The help page will use this information to produce more accurate request samples. + /// + /// The . + /// The type. + /// Name of the controller. + /// Name of the action. + /// The parameter names. + public static void SetActualRequestType(this HttpConfiguration config, Type type, string controllerName, string actionName, params string[] parameterNames) + { + config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Request, controllerName, actionName, parameterNames), type); + } + + /// + /// Specifies the actual type of returned as part of the in an action. + /// The help page will use this information to produce more accurate response samples. + /// + /// The . + /// The type. + /// Name of the controller. + /// Name of the action. + public static void SetActualResponseType(this HttpConfiguration config, Type type, string controllerName, string actionName) + { + config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Response, controllerName, actionName, new[] { "*" }), type); + } + + /// + /// Specifies the actual type of returned as part of the in an action. + /// The help page will use this information to produce more accurate response samples. + /// + /// The . + /// The type. + /// Name of the controller. + /// Name of the action. + /// The parameter names. + public static void SetActualResponseType(this HttpConfiguration config, Type type, string controllerName, string actionName, params string[] parameterNames) + { + config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Response, controllerName, actionName, parameterNames), type); + } + + /// + /// Gets the help page sample generator. + /// + /// The . + /// The help page sample generator. + public static HelpPageSampleGenerator GetHelpPageSampleGenerator(this HttpConfiguration config) + { + return (HelpPageSampleGenerator)config.Properties.GetOrAdd( + typeof(HelpPageSampleGenerator), + k => new HelpPageSampleGenerator()); + } + + /// + /// Sets the help page sample generator. + /// + /// The . + /// The help page sample generator. + public static void SetHelpPageSampleGenerator(this HttpConfiguration config, HelpPageSampleGenerator sampleGenerator) + { + config.Properties.AddOrUpdate( + typeof(HelpPageSampleGenerator), + k => sampleGenerator, + (k, o) => sampleGenerator); + } + + /// + /// Gets the model description generator. + /// + /// The configuration. + /// The + public static ModelDescriptionGenerator GetModelDescriptionGenerator(this HttpConfiguration config) + { + return (ModelDescriptionGenerator)config.Properties.GetOrAdd( + typeof(ModelDescriptionGenerator), + k => InitializeModelDescriptionGenerator(config)); + } + + /// + /// Gets the model that represents an API displayed on the help page. The model is initialized on the first call and cached for subsequent calls. + /// + /// The . + /// The ID. + /// + /// An + /// + public static HelpPageApiModel GetHelpPageApiModel(this HttpConfiguration config, string apiDescriptionId) + { + object model; + string modelId = ApiModelPrefix + apiDescriptionId; + if (!config.Properties.TryGetValue(modelId, out model)) + { + Collection apiDescriptions = config.Services.GetApiExplorer().ApiDescriptions; + ApiDescription apiDescription = apiDescriptions.FirstOrDefault(api => String.Equals(api.GetFriendlyId(), apiDescriptionId, StringComparison.OrdinalIgnoreCase)); + if (apiDescription != null) + { + model = GenerateApiModel(apiDescription, config); + config.Properties.TryAdd(modelId, model); + } + } + + return (HelpPageApiModel)model; + } + + private static HelpPageApiModel GenerateApiModel(ApiDescription apiDescription, HttpConfiguration config) + { + HelpPageApiModel apiModel = new HelpPageApiModel() + { + ApiDescription = apiDescription, + }; + + ModelDescriptionGenerator modelGenerator = config.GetModelDescriptionGenerator(); + HelpPageSampleGenerator sampleGenerator = config.GetHelpPageSampleGenerator(); + GenerateUriParameters(apiModel, modelGenerator); + GenerateRequestModelDescription(apiModel, modelGenerator, sampleGenerator); + GenerateResourceDescription(apiModel, modelGenerator); + GenerateSampleUri(apiModel, sampleGenerator); + GenerateSamples(apiModel, sampleGenerator); + + return apiModel; + } + + private static void GenerateUriParameters(HelpPageApiModel apiModel, ModelDescriptionGenerator modelGenerator) + { + ApiDescription apiDescription = apiModel.ApiDescription; + foreach (ApiParameterDescription apiParameter in apiDescription.ParameterDescriptions) + { + if (apiParameter.Source == ApiParameterSource.FromUri) + { + HttpParameterDescriptor parameterDescriptor = apiParameter.ParameterDescriptor; + Type parameterType = null; + ModelDescription typeDescription = null; + ComplexTypeModelDescription complexTypeDescription = null; + if (parameterDescriptor != null) + { + parameterType = parameterDescriptor.ParameterType; + typeDescription = modelGenerator.GetOrCreateModelDescription(parameterType); + complexTypeDescription = typeDescription as ComplexTypeModelDescription; + } + + // Example: + // [TypeConverter(typeof(PointConverter))] + // public class Point + // { + // public Point(int x, int y) + // { + // X = x; + // Y = y; + // } + // public int X { get; set; } + // public int Y { get; set; } + // } + // Class Point is bindable with a TypeConverter, so Point will be added to UriParameters collection. + // + // public class Point + // { + // public int X { get; set; } + // public int Y { get; set; } + // } + // Regular complex class Point will have properties X and Y added to UriParameters collection. + if (complexTypeDescription != null + && !IsBindableWithTypeConverter(parameterType)) + { + foreach (ParameterDescription uriParameter in complexTypeDescription.Properties) + { + apiModel.UriParameters.Add(uriParameter); + } + } + else if (parameterDescriptor != null) + { + ParameterDescription uriParameter = + AddParameterDescription(apiModel, apiParameter, typeDescription); + + if (!parameterDescriptor.IsOptional) + { + uriParameter.Annotations.Add(new ParameterAnnotation() { Documentation = "Required" }); + } + + object defaultValue = parameterDescriptor.DefaultValue; + if (defaultValue != null) + { + uriParameter.Annotations.Add(new ParameterAnnotation() { Documentation = "Default value is " + Convert.ToString(defaultValue, CultureInfo.InvariantCulture) }); + } + } + else + { + Debug.Assert(parameterDescriptor == null); + + // If parameterDescriptor is null, this is an undeclared route parameter which only occurs + // when source is FromUri. Ignored in request model and among resource parameters but listed + // as a simple string here. + ModelDescription modelDescription = modelGenerator.GetOrCreateModelDescription(typeof(string)); + AddParameterDescription(apiModel, apiParameter, modelDescription); + } + } + } + } + + private static bool IsBindableWithTypeConverter(Type parameterType) + { + if (parameterType == null) + { + return false; + } + + return TypeDescriptor.GetConverter(parameterType).CanConvertFrom(typeof(string)); + } + + private static ParameterDescription AddParameterDescription(HelpPageApiModel apiModel, + ApiParameterDescription apiParameter, ModelDescription typeDescription) + { + ParameterDescription parameterDescription = new ParameterDescription + { + Name = apiParameter.Name, + Documentation = apiParameter.Documentation, + TypeDescription = typeDescription, + }; + + apiModel.UriParameters.Add(parameterDescription); + return parameterDescription; + } + + private static void GenerateRequestModelDescription(HelpPageApiModel apiModel, ModelDescriptionGenerator modelGenerator, HelpPageSampleGenerator sampleGenerator) + { + ApiDescription apiDescription = apiModel.ApiDescription; + foreach (ApiParameterDescription apiParameter in apiDescription.ParameterDescriptions) + { + if (apiParameter.Source == ApiParameterSource.FromBody) + { + Type parameterType = apiParameter.ParameterDescriptor.ParameterType; + apiModel.RequestModelDescription = modelGenerator.GetOrCreateModelDescription(parameterType); + apiModel.RequestDocumentation = apiParameter.Documentation; + } + else if (apiParameter.ParameterDescriptor != null && + apiParameter.ParameterDescriptor.ParameterType == typeof(HttpRequestMessage)) + { + Type parameterType = sampleGenerator.ResolveHttpRequestMessageType(apiDescription); + + if (parameterType != null) + { + apiModel.RequestModelDescription = modelGenerator.GetOrCreateModelDescription(parameterType); + } + } + } + } + + private static void GenerateResourceDescription(HelpPageApiModel apiModel, ModelDescriptionGenerator modelGenerator) + { + ResponseDescription response = apiModel.ApiDescription.ResponseDescription; + Type responseType = response.ResponseType ?? response.DeclaredType; + if (responseType != null && responseType != typeof(void)) + { + apiModel.ResourceDescription = modelGenerator.GetOrCreateModelDescription(responseType); + } + } + + private static void GenerateSampleUri(HelpPageApiModel apiModel, HelpPageSampleGenerator sampleGenerator) + { + apiModel.SampleUri = apiModel.ApiDescription.ParameterDescriptions + .Aggregate(apiModel.ApiDescription.RelativePath, (uri, p) => uri.Replace($"{{{p.Name}}}", sampleGenerator.GetSampleObject(p.ParameterDescriptor.ParameterType)?.ToString())); + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is recorded as ErrorMessages.")] + private static void GenerateSamples(HelpPageApiModel apiModel, HelpPageSampleGenerator sampleGenerator) + { + try + { + foreach (var item in sampleGenerator.GetSampleRequests(apiModel.ApiDescription)) + { + apiModel.SampleRequests.Add(item.Key, item.Value); + LogInvalidSampleAsError(apiModel, item.Value); + } + + foreach (var item in sampleGenerator.GetSampleResponses(apiModel.ApiDescription)) + { + apiModel.SampleResponses.Add(item.Key, item.Value); + LogInvalidSampleAsError(apiModel, item.Value); + } + } + catch (Exception e) + { + apiModel.ErrorMessages.Add(String.Format(CultureInfo.CurrentCulture, + "An exception has occurred while generating the sample. Exception message: {0}", + HelpPageSampleGenerator.UnwrapException(e).Message)); + } + } + + private static bool TryGetResourceParameter(ApiDescription apiDescription, HttpConfiguration config, out ApiParameterDescription parameterDescription, out Type resourceType) + { + parameterDescription = apiDescription.ParameterDescriptions.FirstOrDefault( + p => p.Source == ApiParameterSource.FromBody || + (p.ParameterDescriptor != null && p.ParameterDescriptor.ParameterType == typeof(HttpRequestMessage))); + + if (parameterDescription == null) + { + resourceType = null; + return false; + } + + resourceType = parameterDescription.ParameterDescriptor.ParameterType; + + if (resourceType == typeof(HttpRequestMessage)) + { + HelpPageSampleGenerator sampleGenerator = config.GetHelpPageSampleGenerator(); + resourceType = sampleGenerator.ResolveHttpRequestMessageType(apiDescription); + } + + if (resourceType == null) + { + parameterDescription = null; + return false; + } + + return true; + } + + private static ModelDescriptionGenerator InitializeModelDescriptionGenerator(HttpConfiguration config) + { + ModelDescriptionGenerator modelGenerator = new ModelDescriptionGenerator(config); + Collection apis = config.Services.GetApiExplorer().ApiDescriptions; + foreach (ApiDescription api in apis) + { + ApiParameterDescription parameterDescription; + Type parameterType; + if (TryGetResourceParameter(api, config, out parameterDescription, out parameterType)) + { + modelGenerator.GetOrCreateModelDescription(parameterType); + } + } + return modelGenerator; + } + + private static void LogInvalidSampleAsError(HelpPageApiModel apiModel, object sample) + { + InvalidSample invalidSample = sample as InvalidSample; + if (invalidSample != null) + { + apiModel.ErrorMessages.Add(invalidSample.ErrorMessage); + } + } + } +} diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/CollectionModelDescription.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/CollectionModelDescription.cs new file mode 100644 index 0000000..4f257f0 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/CollectionModelDescription.cs @@ -0,0 +1,7 @@ +namespace RandomDataWebApi.Areas.HelpPage.ModelDescriptions +{ + public class CollectionModelDescription : ModelDescription + { + public ModelDescription ElementDescription { get; set; } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ComplexTypeModelDescription.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ComplexTypeModelDescription.cs new file mode 100644 index 0000000..0e4466b --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ComplexTypeModelDescription.cs @@ -0,0 +1,14 @@ +using System.Collections.ObjectModel; + +namespace RandomDataWebApi.Areas.HelpPage.ModelDescriptions +{ + public class ComplexTypeModelDescription : ModelDescription + { + public ComplexTypeModelDescription() + { + Properties = new Collection(); + } + + public Collection Properties { get; private set; } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/DictionaryModelDescription.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/DictionaryModelDescription.cs new file mode 100644 index 0000000..e93668f --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/DictionaryModelDescription.cs @@ -0,0 +1,6 @@ +namespace RandomDataWebApi.Areas.HelpPage.ModelDescriptions +{ + public class DictionaryModelDescription : KeyValuePairModelDescription + { + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/EnumTypeModelDescription.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/EnumTypeModelDescription.cs new file mode 100644 index 0000000..7a8bdf3 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/EnumTypeModelDescription.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace RandomDataWebApi.Areas.HelpPage.ModelDescriptions +{ + public class EnumTypeModelDescription : ModelDescription + { + public EnumTypeModelDescription() + { + Values = new Collection(); + } + + public Collection Values { get; private set; } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/EnumValueDescription.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/EnumValueDescription.cs new file mode 100644 index 0000000..5fb47db --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/EnumValueDescription.cs @@ -0,0 +1,11 @@ +namespace RandomDataWebApi.Areas.HelpPage.ModelDescriptions +{ + public class EnumValueDescription + { + public string Documentation { get; set; } + + public string Name { get; set; } + + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/IModelDocumentationProvider.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/IModelDocumentationProvider.cs new file mode 100644 index 0000000..24ba7f0 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/IModelDocumentationProvider.cs @@ -0,0 +1,12 @@ +using System; +using System.Reflection; + +namespace RandomDataWebApi.Areas.HelpPage.ModelDescriptions +{ + public interface IModelDocumentationProvider + { + string GetDocumentation(MemberInfo member); + + string GetDocumentation(Type type); + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/KeyValuePairModelDescription.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/KeyValuePairModelDescription.cs new file mode 100644 index 0000000..58cd230 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/KeyValuePairModelDescription.cs @@ -0,0 +1,9 @@ +namespace RandomDataWebApi.Areas.HelpPage.ModelDescriptions +{ + public class KeyValuePairModelDescription : ModelDescription + { + public ModelDescription KeyModelDescription { get; set; } + + public ModelDescription ValueModelDescription { get; set; } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ModelDescription.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ModelDescription.cs new file mode 100644 index 0000000..982b6bb --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ModelDescription.cs @@ -0,0 +1,16 @@ +using System; + +namespace RandomDataWebApi.Areas.HelpPage.ModelDescriptions +{ + /// + /// Describes a type model. + /// + public abstract class ModelDescription + { + public string Documentation { get; set; } + + public Type ModelType { get; set; } + + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ModelDescriptionGenerator.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ModelDescriptionGenerator.cs new file mode 100644 index 0000000..a08af86 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ModelDescriptionGenerator.cs @@ -0,0 +1,451 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Reflection; +using System.Runtime.Serialization; +using System.Web.Http; +using System.Web.Http.Description; +using System.Xml.Serialization; +using Newtonsoft.Json; + +namespace RandomDataWebApi.Areas.HelpPage.ModelDescriptions +{ + /// + /// Generates model descriptions for given types. + /// + public class ModelDescriptionGenerator + { + // Modify this to support more data annotation attributes. + private readonly IDictionary> AnnotationTextGenerator = new Dictionary> + { + { typeof(RequiredAttribute), a => "Required" }, + { typeof(RangeAttribute), a => + { + RangeAttribute range = (RangeAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Range: inclusive between {0} and {1}", range.Minimum, range.Maximum); + } + }, + { typeof(MaxLengthAttribute), a => + { + MaxLengthAttribute maxLength = (MaxLengthAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Max length: {0}", maxLength.Length); + } + }, + { typeof(MinLengthAttribute), a => + { + MinLengthAttribute minLength = (MinLengthAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Min length: {0}", minLength.Length); + } + }, + { typeof(StringLengthAttribute), a => + { + StringLengthAttribute strLength = (StringLengthAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "String length: inclusive between {0} and {1}", strLength.MinimumLength, strLength.MaximumLength); + } + }, + { typeof(DataTypeAttribute), a => + { + DataTypeAttribute dataType = (DataTypeAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Data type: {0}", dataType.CustomDataType ?? dataType.DataType.ToString()); + } + }, + { typeof(RegularExpressionAttribute), a => + { + RegularExpressionAttribute regularExpression = (RegularExpressionAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Matching regular expression pattern: {0}", regularExpression.Pattern); + } + }, + }; + + // Modify this to add more default documentations. + private readonly IDictionary DefaultTypeDocumentation = new Dictionary + { + { typeof(Int16), "integer" }, + { typeof(Int32), "integer" }, + { typeof(Int64), "integer" }, + { typeof(UInt16), "unsigned integer" }, + { typeof(UInt32), "unsigned integer" }, + { typeof(UInt64), "unsigned integer" }, + { typeof(Byte), "byte" }, + { typeof(Char), "character" }, + { typeof(SByte), "signed byte" }, + { typeof(Uri), "URI" }, + { typeof(Single), "decimal number" }, + { typeof(Double), "decimal number" }, + { typeof(Decimal), "decimal number" }, + { typeof(String), "string" }, + { typeof(Guid), "globally unique identifier" }, + { typeof(TimeSpan), "time interval" }, + { typeof(DateTime), "date" }, + { typeof(DateTimeOffset), "date" }, + { typeof(Boolean), "boolean" }, + }; + + private Lazy _documentationProvider; + + public ModelDescriptionGenerator(HttpConfiguration config) + { + if (config == null) + { + throw new ArgumentNullException("config"); + } + + _documentationProvider = new Lazy(() => config.Services.GetDocumentationProvider() as IModelDocumentationProvider); + GeneratedModels = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + public Dictionary GeneratedModels { get; private set; } + + private IModelDocumentationProvider DocumentationProvider + { + get + { + return _documentationProvider.Value; + } + } + + public ModelDescription GetOrCreateModelDescription(Type modelType) + { + if (modelType == null) + { + throw new ArgumentNullException("modelType"); + } + + Type underlyingType = Nullable.GetUnderlyingType(modelType); + if (underlyingType != null) + { + modelType = underlyingType; + } + + ModelDescription modelDescription; + string modelName = ModelNameHelper.GetModelName(modelType); + if (GeneratedModels.TryGetValue(modelName, out modelDescription)) + { + if (modelType != modelDescription.ModelType) + { + throw new InvalidOperationException( + String.Format( + CultureInfo.CurrentCulture, + "A model description could not be created. Duplicate model name '{0}' was found for types '{1}' and '{2}'. " + + "Use the [ModelName] attribute to change the model name for at least one of the types so that it has a unique name.", + modelName, + modelDescription.ModelType.FullName, + modelType.FullName)); + } + + return modelDescription; + } + + if (DefaultTypeDocumentation.ContainsKey(modelType)) + { + return GenerateSimpleTypeModelDescription(modelType); + } + + if (modelType.IsEnum) + { + return GenerateEnumTypeModelDescription(modelType); + } + + if (modelType.IsGenericType) + { + Type[] genericArguments = modelType.GetGenericArguments(); + + if (genericArguments.Length == 1) + { + Type enumerableType = typeof(IEnumerable<>).MakeGenericType(genericArguments); + if (enumerableType.IsAssignableFrom(modelType)) + { + return GenerateCollectionModelDescription(modelType, genericArguments[0]); + } + } + if (genericArguments.Length == 2) + { + Type dictionaryType = typeof(IDictionary<,>).MakeGenericType(genericArguments); + if (dictionaryType.IsAssignableFrom(modelType)) + { + return GenerateDictionaryModelDescription(modelType, genericArguments[0], genericArguments[1]); + } + + Type keyValuePairType = typeof(KeyValuePair<,>).MakeGenericType(genericArguments); + if (keyValuePairType.IsAssignableFrom(modelType)) + { + return GenerateKeyValuePairModelDescription(modelType, genericArguments[0], genericArguments[1]); + } + } + } + + if (modelType.IsArray) + { + Type elementType = modelType.GetElementType(); + return GenerateCollectionModelDescription(modelType, elementType); + } + + if (modelType == typeof(NameValueCollection)) + { + return GenerateDictionaryModelDescription(modelType, typeof(string), typeof(string)); + } + + if (typeof(IDictionary).IsAssignableFrom(modelType)) + { + return GenerateDictionaryModelDescription(modelType, typeof(object), typeof(object)); + } + + if (typeof(IEnumerable).IsAssignableFrom(modelType)) + { + return GenerateCollectionModelDescription(modelType, typeof(object)); + } + + return GenerateComplexTypeModelDescription(modelType); + } + + // Change this to provide different name for the member. + private static string GetMemberName(MemberInfo member, bool hasDataContractAttribute) + { + JsonPropertyAttribute jsonProperty = member.GetCustomAttribute(); + if (jsonProperty != null && !String.IsNullOrEmpty(jsonProperty.PropertyName)) + { + return jsonProperty.PropertyName; + } + + if (hasDataContractAttribute) + { + DataMemberAttribute dataMember = member.GetCustomAttribute(); + if (dataMember != null && !String.IsNullOrEmpty(dataMember.Name)) + { + return dataMember.Name; + } + } + + return member.Name; + } + + private static bool ShouldDisplayMember(MemberInfo member, bool hasDataContractAttribute) + { + JsonIgnoreAttribute jsonIgnore = member.GetCustomAttribute(); + XmlIgnoreAttribute xmlIgnore = member.GetCustomAttribute(); + IgnoreDataMemberAttribute ignoreDataMember = member.GetCustomAttribute(); + NonSerializedAttribute nonSerialized = member.GetCustomAttribute(); + ApiExplorerSettingsAttribute apiExplorerSetting = member.GetCustomAttribute(); + + bool hasMemberAttribute = member.DeclaringType.IsEnum ? + member.GetCustomAttribute() != null : + member.GetCustomAttribute() != null; + + // Display member only if all the followings are true: + // no JsonIgnoreAttribute + // no XmlIgnoreAttribute + // no IgnoreDataMemberAttribute + // no NonSerializedAttribute + // no ApiExplorerSettingsAttribute with IgnoreApi set to true + // no DataContractAttribute without DataMemberAttribute or EnumMemberAttribute + return jsonIgnore == null && + xmlIgnore == null && + ignoreDataMember == null && + nonSerialized == null && + (apiExplorerSetting == null || !apiExplorerSetting.IgnoreApi) && + (!hasDataContractAttribute || hasMemberAttribute); + } + + private string CreateDefaultDocumentation(Type type) + { + string documentation; + if (DefaultTypeDocumentation.TryGetValue(type, out documentation)) + { + return documentation; + } + if (DocumentationProvider != null) + { + documentation = DocumentationProvider.GetDocumentation(type); + } + + return documentation; + } + + private void GenerateAnnotations(MemberInfo property, ParameterDescription propertyModel) + { + List annotations = new List(); + + IEnumerable attributes = property.GetCustomAttributes(); + foreach (Attribute attribute in attributes) + { + Func textGenerator; + if (AnnotationTextGenerator.TryGetValue(attribute.GetType(), out textGenerator)) + { + annotations.Add( + new ParameterAnnotation + { + AnnotationAttribute = attribute, + Documentation = textGenerator(attribute) + }); + } + } + + // Rearrange the annotations + annotations.Sort((x, y) => + { + // Special-case RequiredAttribute so that it shows up on top + if (x.AnnotationAttribute is RequiredAttribute) + { + return -1; + } + if (y.AnnotationAttribute is RequiredAttribute) + { + return 1; + } + + // Sort the rest based on alphabetic order of the documentation + return String.Compare(x.Documentation, y.Documentation, StringComparison.OrdinalIgnoreCase); + }); + + foreach (ParameterAnnotation annotation in annotations) + { + propertyModel.Annotations.Add(annotation); + } + } + + private CollectionModelDescription GenerateCollectionModelDescription(Type modelType, Type elementType) + { + ModelDescription collectionModelDescription = GetOrCreateModelDescription(elementType); + if (collectionModelDescription != null) + { + return new CollectionModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + ElementDescription = collectionModelDescription + }; + } + + return null; + } + + private ModelDescription GenerateComplexTypeModelDescription(Type modelType) + { + ComplexTypeModelDescription complexModelDescription = new ComplexTypeModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + Documentation = CreateDefaultDocumentation(modelType) + }; + + GeneratedModels.Add(complexModelDescription.Name, complexModelDescription); + bool hasDataContractAttribute = modelType.GetCustomAttribute() != null; + PropertyInfo[] properties = modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (PropertyInfo property in properties) + { + if (ShouldDisplayMember(property, hasDataContractAttribute)) + { + ParameterDescription propertyModel = new ParameterDescription + { + Name = GetMemberName(property, hasDataContractAttribute) + }; + + if (DocumentationProvider != null) + { + propertyModel.Documentation = DocumentationProvider.GetDocumentation(property); + } + + GenerateAnnotations(property, propertyModel); + complexModelDescription.Properties.Add(propertyModel); + propertyModel.TypeDescription = GetOrCreateModelDescription(property.PropertyType); + } + } + + FieldInfo[] fields = modelType.GetFields(BindingFlags.Public | BindingFlags.Instance); + foreach (FieldInfo field in fields) + { + if (ShouldDisplayMember(field, hasDataContractAttribute)) + { + ParameterDescription propertyModel = new ParameterDescription + { + Name = GetMemberName(field, hasDataContractAttribute) + }; + + if (DocumentationProvider != null) + { + propertyModel.Documentation = DocumentationProvider.GetDocumentation(field); + } + + complexModelDescription.Properties.Add(propertyModel); + propertyModel.TypeDescription = GetOrCreateModelDescription(field.FieldType); + } + } + + return complexModelDescription; + } + + private DictionaryModelDescription GenerateDictionaryModelDescription(Type modelType, Type keyType, Type valueType) + { + ModelDescription keyModelDescription = GetOrCreateModelDescription(keyType); + ModelDescription valueModelDescription = GetOrCreateModelDescription(valueType); + + return new DictionaryModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + KeyModelDescription = keyModelDescription, + ValueModelDescription = valueModelDescription + }; + } + + private EnumTypeModelDescription GenerateEnumTypeModelDescription(Type modelType) + { + EnumTypeModelDescription enumDescription = new EnumTypeModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + Documentation = CreateDefaultDocumentation(modelType) + }; + bool hasDataContractAttribute = modelType.GetCustomAttribute() != null; + foreach (FieldInfo field in modelType.GetFields(BindingFlags.Public | BindingFlags.Static)) + { + if (ShouldDisplayMember(field, hasDataContractAttribute)) + { + EnumValueDescription enumValue = new EnumValueDescription + { + Name = field.Name, + Value = field.GetRawConstantValue().ToString() + }; + if (DocumentationProvider != null) + { + enumValue.Documentation = DocumentationProvider.GetDocumentation(field); + } + enumDescription.Values.Add(enumValue); + } + } + GeneratedModels.Add(enumDescription.Name, enumDescription); + + return enumDescription; + } + + private KeyValuePairModelDescription GenerateKeyValuePairModelDescription(Type modelType, Type keyType, Type valueType) + { + ModelDescription keyModelDescription = GetOrCreateModelDescription(keyType); + ModelDescription valueModelDescription = GetOrCreateModelDescription(valueType); + + return new KeyValuePairModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + KeyModelDescription = keyModelDescription, + ValueModelDescription = valueModelDescription + }; + } + + private ModelDescription GenerateSimpleTypeModelDescription(Type modelType) + { + SimpleTypeModelDescription simpleModelDescription = new SimpleTypeModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + Documentation = CreateDefaultDocumentation(modelType) + }; + GeneratedModels.Add(simpleModelDescription.Name, simpleModelDescription); + + return simpleModelDescription; + } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ModelNameAttribute.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ModelNameAttribute.cs new file mode 100644 index 0000000..32db9a5 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ModelNameAttribute.cs @@ -0,0 +1,18 @@ +using System; + +namespace RandomDataWebApi.Areas.HelpPage.ModelDescriptions +{ + /// + /// Use this attribute to change the name of the generated for a type. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum, AllowMultiple = false, Inherited = false)] + public sealed class ModelNameAttribute : Attribute + { + public ModelNameAttribute(string name) + { + Name = name; + } + + public string Name { get; private set; } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ModelNameHelper.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ModelNameHelper.cs new file mode 100644 index 0000000..8534fd2 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ModelNameHelper.cs @@ -0,0 +1,36 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Reflection; + +namespace RandomDataWebApi.Areas.HelpPage.ModelDescriptions +{ + internal static class ModelNameHelper + { + // Modify this to provide custom model name mapping. + public static string GetModelName(Type type) + { + ModelNameAttribute modelNameAttribute = type.GetCustomAttribute(); + if (modelNameAttribute != null && !String.IsNullOrEmpty(modelNameAttribute.Name)) + { + return modelNameAttribute.Name; + } + + string modelName = type.Name; + if (type.IsGenericType) + { + // Format the generic type name to something like: GenericOfAgurment1AndArgument2 + Type genericType = type.GetGenericTypeDefinition(); + Type[] genericArguments = type.GetGenericArguments(); + string genericTypeName = genericType.Name; + + // Trim the generic parameter counts from the name + genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`')); + string[] argumentTypeNames = genericArguments.Select(t => GetModelName(t)).ToArray(); + modelName = String.Format(CultureInfo.InvariantCulture, "{0}Of{1}", genericTypeName, String.Join("And", argumentTypeNames)); + } + + return modelName; + } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ParameterAnnotation.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ParameterAnnotation.cs new file mode 100644 index 0000000..5942601 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ParameterAnnotation.cs @@ -0,0 +1,11 @@ +using System; + +namespace RandomDataWebApi.Areas.HelpPage.ModelDescriptions +{ + public class ParameterAnnotation + { + public Attribute AnnotationAttribute { get; set; } + + public string Documentation { get; set; } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ParameterDescription.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ParameterDescription.cs new file mode 100644 index 0000000..54999bd --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/ParameterDescription.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace RandomDataWebApi.Areas.HelpPage.ModelDescriptions +{ + public class ParameterDescription + { + public ParameterDescription() + { + Annotations = new Collection(); + } + + public Collection Annotations { get; private set; } + + public string Documentation { get; set; } + + public string Name { get; set; } + + public ModelDescription TypeDescription { get; set; } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/SimpleTypeModelDescription.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/SimpleTypeModelDescription.cs new file mode 100644 index 0000000..9aeafe6 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/ModelDescriptions/SimpleTypeModelDescription.cs @@ -0,0 +1,6 @@ +namespace RandomDataWebApi.Areas.HelpPage.ModelDescriptions +{ + public class SimpleTypeModelDescription : ModelDescription + { + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Models/HelpPageApiModel.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/Models/HelpPageApiModel.cs new file mode 100644 index 0000000..af854f4 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Models/HelpPageApiModel.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Net.Http.Headers; +using System.Web.Http.Description; +using RandomDataWebApi.Areas.HelpPage.ModelDescriptions; + +namespace RandomDataWebApi.Areas.HelpPage.Models +{ + /// + /// The model that represents an API displayed on the help page. + /// + public class HelpPageApiModel + { + /// + /// Initializes a new instance of the class. + /// + public HelpPageApiModel() + { + UriParameters = new Collection(); + SampleRequests = new Dictionary(); + SampleResponses = new Dictionary(); + ErrorMessages = new Collection(); + } + + /// + /// Gets or sets the that describes the API. + /// + public ApiDescription ApiDescription { get; set; } + + /// + /// Gets or sets the sample URI. + /// + public string SampleUri { get; set; } + + /// + /// Gets or sets the collection that describes the URI parameters for the API. + /// + public Collection UriParameters { get; private set; } + + /// + /// Gets or sets the documentation for the request. + /// + public string RequestDocumentation { get; set; } + + /// + /// Gets or sets the that describes the request body. + /// + public ModelDescription RequestModelDescription { get; set; } + + /// + /// Gets the request body parameter descriptions. + /// + public IList RequestBodyParameters + { + get + { + return GetParameterDescriptions(RequestModelDescription); + } + } + + /// + /// Gets or sets the that describes the resource. + /// + public ModelDescription ResourceDescription { get; set; } + + /// + /// Gets the resource property descriptions. + /// + public IList ResourceProperties + { + get + { + return GetParameterDescriptions(ResourceDescription); + } + } + + /// + /// Gets the sample requests associated with the API. + /// + public IDictionary SampleRequests { get; private set; } + + /// + /// Gets the sample responses associated with the API. + /// + public IDictionary SampleResponses { get; private set; } + + /// + /// Gets the error messages associated with this model. + /// + public Collection ErrorMessages { get; private set; } + + private static IList GetParameterDescriptions(ModelDescription modelDescription) + { + ComplexTypeModelDescription complexTypeModelDescription = modelDescription as ComplexTypeModelDescription; + if (complexTypeModelDescription != null) + { + return complexTypeModelDescription.Properties; + } + + CollectionModelDescription collectionModelDescription = modelDescription as CollectionModelDescription; + if (collectionModelDescription != null) + { + complexTypeModelDescription = collectionModelDescription.ElementDescription as ComplexTypeModelDescription; + if (complexTypeModelDescription != null) + { + return complexTypeModelDescription.Properties; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/HelpPageSampleGenerator.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/HelpPageSampleGenerator.cs new file mode 100644 index 0000000..cea0d1a --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/HelpPageSampleGenerator.cs @@ -0,0 +1,456 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Net.Http.Headers; +using System.Web.Http.Controllers; +using System.Web.Http.Description; +using System.Xml.Linq; +using Newtonsoft.Json; +using RandomDataWebApi.Controllers; + +namespace RandomDataWebApi.Areas.HelpPage +{ + /// + /// This class will generate the samples for the help page. + /// + public class HelpPageSampleGenerator + { + /// + /// Initializes a new instance of the class. + /// + public HelpPageSampleGenerator() + { + ActualHttpMessageTypes = new Dictionary(); + ActionSamples = new Dictionary(); + SampleObjects = new Dictionary(); + SampleObjectFactories = new List> + { + DefaultSampleObjectFactory, + }; + } + + /// + /// Gets CLR types that are used as the content of or . + /// + public IDictionary ActualHttpMessageTypes { get; internal set; } + + /// + /// Gets the objects that are used directly as samples for certain actions. + /// + public IDictionary ActionSamples { get; internal set; } + + /// + /// Gets the objects that are serialized as samples by the supported formatters. + /// + public IDictionary SampleObjects { get; internal set; } + + /// + /// Gets factories for the objects that the supported formatters will serialize as samples. Processed in order, + /// stopping when the factory successfully returns a non- object. + /// + /// + /// Collection includes just initially. Use + /// SampleObjectFactories.Insert(0, func) to provide an override and + /// SampleObjectFactories.Add(func) to provide a fallback. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "This is an appropriate nesting of generic types")] + public IList> SampleObjectFactories { get; private set; } + + /// + /// Gets the request body samples for a given . + /// + /// The . + /// The samples keyed by media type. + public IDictionary GetSampleRequests(ApiDescription api) + { + return GetSample(api, SampleDirection.Request); + } + + /// + /// Gets the response body samples for a given . + /// + /// The . + /// The samples keyed by media type. + public IDictionary GetSampleResponses(ApiDescription api) + { + return GetSample(api, SampleDirection.Response); + } + + /// + /// Gets the request or response body samples. + /// + /// The . + /// The value indicating whether the sample is for a request or for a response. + /// The samples keyed by media type. + public virtual IDictionary GetSample(ApiDescription api, SampleDirection sampleDirection) + { + if (api == null) + { + throw new ArgumentNullException("api"); + } + string controllerName = api.ActionDescriptor.ControllerDescriptor.ControllerName; + string actionName = api.ActionDescriptor.ActionName; + IEnumerable parameterNames = api.ParameterDescriptions.Select(p => p.Name); + Collection formatters; + Type type = ResolveType(api, controllerName, actionName, parameterNames, sampleDirection, out formatters); + var samples = new Dictionary(); + + // Use the samples provided directly for actions + var actionSamples = GetAllActionSamples(controllerName, actionName, parameterNames, sampleDirection); + foreach (var actionSample in actionSamples) + { + samples.Add(actionSample.Key.MediaType, WrapSampleIfString(actionSample.Value)); + } + + // Do the sample generation based on formatters only if an action doesn't return an HttpResponseMessage. + // Here we cannot rely on formatters because we don't know what's in the HttpResponseMessage, it might not even use formatters. + if (type != null && !typeof(HttpResponseMessage).IsAssignableFrom(type)) + { + object sampleObject = controllerName == "Random" ? GetSampleObjectForRandom(api) : GetSampleObject(type); + foreach (var formatter in formatters) + { + foreach (MediaTypeHeaderValue mediaType in formatter.SupportedMediaTypes) + { + if (!samples.ContainsKey(mediaType)) + { + object sample = GetActionSample(controllerName, actionName, parameterNames, type, formatter, mediaType, sampleDirection); + + // If no sample found, try generate sample using formatter and sample object + if (sample == null && sampleObject != null) + { + sample = WriteSampleObjectUsingFormatter(formatter, sampleObject, type, mediaType); + } + + samples.Add(mediaType, WrapSampleIfString(sample)); + } + } + } + } + + return samples; + } + + private static readonly RandomController RandomController = new RandomController(); + private object GetSampleObjectForRandom(ApiDescription api) + { + var method = ((ReflectedHttpActionDescriptor)api.ActionDescriptor).MethodInfo; + var parameters = api.ParameterDescriptions + .Select(p => GetSampleObject(p.ParameterDescriptor.ParameterType)) + .ToArray(); + return method.Invoke(RandomController, parameters); + } + + /// + /// Search for samples that are provided directly through . + /// + /// Name of the controller. + /// Name of the action. + /// The parameter names. + /// The CLR type. + /// The formatter. + /// The media type. + /// The value indicating whether the sample is for a request or for a response. + /// The sample that matches the parameters. + public virtual object GetActionSample(string controllerName, string actionName, IEnumerable parameterNames, Type type, MediaTypeFormatter formatter, MediaTypeHeaderValue mediaType, SampleDirection sampleDirection) + { + object sample; + + // First, try to get the sample provided for the specified mediaType, sampleDirection, controllerName, actionName and parameterNames. + // If not found, try to get the sample provided for the specified mediaType, sampleDirection, controllerName and actionName regardless of the parameterNames. + // If still not found, try to get the sample provided for the specified mediaType and type. + // Finally, try to get the sample provided for the specified mediaType. + if (ActionSamples.TryGetValue(new HelpPageSampleKey(mediaType, sampleDirection, controllerName, actionName, parameterNames), out sample) || + ActionSamples.TryGetValue(new HelpPageSampleKey(mediaType, sampleDirection, controllerName, actionName, new[] { "*" }), out sample) || + ActionSamples.TryGetValue(new HelpPageSampleKey(mediaType, type), out sample) || + ActionSamples.TryGetValue(new HelpPageSampleKey(mediaType), out sample)) + { + return sample; + } + + return null; + } + + /// + /// Gets the sample object that will be serialized by the formatters. + /// First, it will look at the . If no sample object is found, it will try to create + /// one using (which wraps an ) and other + /// factories in . + /// + /// The type. + /// The sample object. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", + Justification = "Even if all items in SampleObjectFactories throw, problem will be visible as missing sample.")] + public virtual object GetSampleObject(Type type) + { + object sampleObject; + + if (!SampleObjects.TryGetValue(type, out sampleObject)) + { + // No specific object available, try our factories. + foreach (Func factory in SampleObjectFactories) + { + if (factory == null) + { + continue; + } + + try + { + sampleObject = factory(this, type); + if (sampleObject != null) + { + break; + } + } + catch + { + // Ignore any problems encountered in the factory; go on to the next one (if any). + } + } + } + + return sampleObject; + } + + /// + /// Resolves the actual type of passed to the in an action. + /// + /// The . + /// The type. + public virtual Type ResolveHttpRequestMessageType(ApiDescription api) + { + string controllerName = api.ActionDescriptor.ControllerDescriptor.ControllerName; + string actionName = api.ActionDescriptor.ActionName; + IEnumerable parameterNames = api.ParameterDescriptions.Select(p => p.Name); + Collection formatters; + return ResolveType(api, controllerName, actionName, parameterNames, SampleDirection.Request, out formatters); + } + + /// + /// Resolves the type of the action parameter or return value when or is used. + /// + /// The . + /// Name of the controller. + /// Name of the action. + /// The parameter names. + /// The value indicating whether the sample is for a request or a response. + /// The formatters. + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", Justification = "This is only used in advanced scenarios.")] + public virtual Type ResolveType(ApiDescription api, string controllerName, string actionName, IEnumerable parameterNames, SampleDirection sampleDirection, out Collection formatters) + { + if (!Enum.IsDefined(typeof(SampleDirection), sampleDirection)) + { + throw new InvalidEnumArgumentException("sampleDirection", (int)sampleDirection, typeof(SampleDirection)); + } + if (api == null) + { + throw new ArgumentNullException("api"); + } + Type type; + if (ActualHttpMessageTypes.TryGetValue(new HelpPageSampleKey(sampleDirection, controllerName, actionName, parameterNames), out type) || + ActualHttpMessageTypes.TryGetValue(new HelpPageSampleKey(sampleDirection, controllerName, actionName, new[] { "*" }), out type)) + { + // Re-compute the supported formatters based on type + Collection newFormatters = new Collection(); + foreach (var formatter in api.ActionDescriptor.Configuration.Formatters) + { + if (IsFormatSupported(sampleDirection, formatter, type)) + { + newFormatters.Add(formatter); + } + } + formatters = newFormatters; + } + else + { + switch (sampleDirection) + { + case SampleDirection.Request: + ApiParameterDescription requestBodyParameter = api.ParameterDescriptions.FirstOrDefault(p => p.Source == ApiParameterSource.FromBody); + type = requestBodyParameter == null ? null : requestBodyParameter.ParameterDescriptor.ParameterType; + formatters = api.SupportedRequestBodyFormatters; + break; + case SampleDirection.Response: + default: + type = api.ResponseDescription.ResponseType ?? api.ResponseDescription.DeclaredType; + formatters = api.SupportedResponseFormatters; + break; + } + } + + return type; + } + + /// + /// Writes the sample object using formatter. + /// + /// The formatter. + /// The value. + /// The type. + /// Type of the media. + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is recorded as InvalidSample.")] + public virtual object WriteSampleObjectUsingFormatter(MediaTypeFormatter formatter, object value, Type type, MediaTypeHeaderValue mediaType) + { + if (formatter == null) + { + throw new ArgumentNullException("formatter"); + } + if (mediaType == null) + { + throw new ArgumentNullException("mediaType"); + } + + object sample = String.Empty; + MemoryStream ms = null; + HttpContent content = null; + try + { + if (formatter.CanWriteType(type)) + { + ms = new MemoryStream(); + content = new ObjectContent(type, value, formatter, mediaType); + formatter.WriteToStreamAsync(type, value, ms, content, null).Wait(); + ms.Position = 0; + StreamReader reader = new StreamReader(ms); + string serializedSampleString = reader.ReadToEnd(); + if (mediaType.MediaType.ToUpperInvariant().Contains("XML")) + { + serializedSampleString = TryFormatXml(serializedSampleString); + } + else if (mediaType.MediaType.ToUpperInvariant().Contains("JSON")) + { + serializedSampleString = TryFormatJson(serializedSampleString); + } + + sample = new TextSample(serializedSampleString); + } + else + { + sample = new InvalidSample(String.Format( + CultureInfo.CurrentCulture, + "Failed to generate the sample for media type '{0}'. Cannot use formatter '{1}' to write type '{2}'.", + mediaType, + formatter.GetType().Name, + type.Name)); + } + } + catch (Exception e) + { + sample = new InvalidSample(String.Format( + CultureInfo.CurrentCulture, + "An exception has occurred while using the formatter '{0}' to generate sample for media type '{1}'. Exception message: {2}", + formatter.GetType().Name, + mediaType.MediaType, + UnwrapException(e).Message)); + } + finally + { + if (ms != null) + { + ms.Dispose(); + } + if (content != null) + { + content.Dispose(); + } + } + + return sample; + } + + internal static Exception UnwrapException(Exception exception) + { + AggregateException aggregateException = exception as AggregateException; + if (aggregateException != null) + { + return aggregateException.Flatten().InnerException; + } + return exception; + } + + // Default factory for sample objects + private static object DefaultSampleObjectFactory(HelpPageSampleGenerator sampleGenerator, Type type) + { + // Try to create a default sample object + ObjectGenerator objectGenerator = new ObjectGenerator(); + return objectGenerator.GenerateObject(type); + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Handling the failure by returning the original string.")] + private static string TryFormatJson(string str) + { + try + { + object parsedJson = JsonConvert.DeserializeObject(str); + return JsonConvert.SerializeObject(parsedJson, Formatting.Indented); + } + catch + { + // can't parse JSON, return the original string + return str; + } + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Handling the failure by returning the original string.")] + private static string TryFormatXml(string str) + { + try + { + XDocument xml = XDocument.Parse(str); + return xml.ToString(); + } + catch + { + // can't parse XML, return the original string + return str; + } + } + + private static bool IsFormatSupported(SampleDirection sampleDirection, MediaTypeFormatter formatter, Type type) + { + switch (sampleDirection) + { + case SampleDirection.Request: + return formatter.CanReadType(type); + case SampleDirection.Response: + return formatter.CanWriteType(type); + } + return false; + } + + private IEnumerable> GetAllActionSamples(string controllerName, string actionName, IEnumerable parameterNames, SampleDirection sampleDirection) + { + HashSet parameterNamesSet = new HashSet(parameterNames, StringComparer.OrdinalIgnoreCase); + foreach (var sample in ActionSamples) + { + HelpPageSampleKey sampleKey = sample.Key; + if (String.Equals(controllerName, sampleKey.ControllerName, StringComparison.OrdinalIgnoreCase) && + String.Equals(actionName, sampleKey.ActionName, StringComparison.OrdinalIgnoreCase) && + (sampleKey.ParameterNames.SetEquals(new[] { "*" }) || parameterNamesSet.SetEquals(sampleKey.ParameterNames)) && + sampleDirection == sampleKey.SampleDirection) + { + yield return sample; + } + } + } + + private static object WrapSampleIfString(object sample) + { + string stringSample = sample as string; + if (stringSample != null) + { + return new TextSample(stringSample); + } + + return sample; + } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/HelpPageSampleKey.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/HelpPageSampleKey.cs new file mode 100644 index 0000000..9f5126c --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/HelpPageSampleKey.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Net.Http.Headers; + +namespace RandomDataWebApi.Areas.HelpPage +{ + /// + /// This is used to identify the place where the sample should be applied. + /// + public class HelpPageSampleKey + { + /// + /// Creates a new based on media type. + /// + /// The media type. + public HelpPageSampleKey(MediaTypeHeaderValue mediaType) + { + if (mediaType == null) + { + throw new ArgumentNullException("mediaType"); + } + + ActionName = String.Empty; + ControllerName = String.Empty; + MediaType = mediaType; + ParameterNames = new HashSet(StringComparer.OrdinalIgnoreCase); + } + + /// + /// Creates a new based on media type and CLR type. + /// + /// The media type. + /// The CLR type. + public HelpPageSampleKey(MediaTypeHeaderValue mediaType, Type type) + : this(mediaType) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + ParameterType = type; + } + + /// + /// Creates a new based on , controller name, action name and parameter names. + /// + /// The . + /// Name of the controller. + /// Name of the action. + /// The parameter names. + public HelpPageSampleKey(SampleDirection sampleDirection, string controllerName, string actionName, IEnumerable parameterNames) + { + if (!Enum.IsDefined(typeof(SampleDirection), sampleDirection)) + { + throw new InvalidEnumArgumentException("sampleDirection", (int)sampleDirection, typeof(SampleDirection)); + } + if (controllerName == null) + { + throw new ArgumentNullException("controllerName"); + } + if (actionName == null) + { + throw new ArgumentNullException("actionName"); + } + if (parameterNames == null) + { + throw new ArgumentNullException("parameterNames"); + } + + ControllerName = controllerName; + ActionName = actionName; + ParameterNames = new HashSet(parameterNames, StringComparer.OrdinalIgnoreCase); + SampleDirection = sampleDirection; + } + + /// + /// Creates a new based on media type, , controller name, action name and parameter names. + /// + /// The media type. + /// The . + /// Name of the controller. + /// Name of the action. + /// The parameter names. + public HelpPageSampleKey(MediaTypeHeaderValue mediaType, SampleDirection sampleDirection, string controllerName, string actionName, IEnumerable parameterNames) + : this(sampleDirection, controllerName, actionName, parameterNames) + { + if (mediaType == null) + { + throw new ArgumentNullException("mediaType"); + } + + MediaType = mediaType; + } + + /// + /// Gets the name of the controller. + /// + /// + /// The name of the controller. + /// + public string ControllerName { get; private set; } + + /// + /// Gets the name of the action. + /// + /// + /// The name of the action. + /// + public string ActionName { get; private set; } + + /// + /// Gets the media type. + /// + /// + /// The media type. + /// + public MediaTypeHeaderValue MediaType { get; private set; } + + /// + /// Gets the parameter names. + /// + public HashSet ParameterNames { get; private set; } + + public Type ParameterType { get; private set; } + + /// + /// Gets the . + /// + public SampleDirection? SampleDirection { get; private set; } + + public override bool Equals(object obj) + { + HelpPageSampleKey otherKey = obj as HelpPageSampleKey; + if (otherKey == null) + { + return false; + } + + return String.Equals(ControllerName, otherKey.ControllerName, StringComparison.OrdinalIgnoreCase) && + String.Equals(ActionName, otherKey.ActionName, StringComparison.OrdinalIgnoreCase) && + (MediaType == otherKey.MediaType || (MediaType != null && MediaType.Equals(otherKey.MediaType))) && + ParameterType == otherKey.ParameterType && + SampleDirection == otherKey.SampleDirection && + ParameterNames.SetEquals(otherKey.ParameterNames); + } + + public override int GetHashCode() + { + int hashCode = ControllerName.ToUpperInvariant().GetHashCode() ^ ActionName.ToUpperInvariant().GetHashCode(); + if (MediaType != null) + { + hashCode ^= MediaType.GetHashCode(); + } + if (SampleDirection != null) + { + hashCode ^= SampleDirection.GetHashCode(); + } + if (ParameterType != null) + { + hashCode ^= ParameterType.GetHashCode(); + } + foreach (string parameterName in ParameterNames) + { + hashCode ^= parameterName.ToUpperInvariant().GetHashCode(); + } + + return hashCode; + } + } +} diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/ImageSample.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/ImageSample.cs new file mode 100644 index 0000000..efce370 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/ImageSample.cs @@ -0,0 +1,41 @@ +using System; + +namespace RandomDataWebApi.Areas.HelpPage +{ + /// + /// This represents an image sample on the help page. There's a display template named ImageSample associated with this class. + /// + public class ImageSample + { + /// + /// Initializes a new instance of the class. + /// + /// The URL of an image. + public ImageSample(string src) + { + if (src == null) + { + throw new ArgumentNullException("src"); + } + Src = src; + } + + public string Src { get; private set; } + + public override bool Equals(object obj) + { + ImageSample other = obj as ImageSample; + return other != null && Src == other.Src; + } + + public override int GetHashCode() + { + return Src.GetHashCode(); + } + + public override string ToString() + { + return Src; + } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/InvalidSample.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/InvalidSample.cs new file mode 100644 index 0000000..27d23ff --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/InvalidSample.cs @@ -0,0 +1,37 @@ +using System; + +namespace RandomDataWebApi.Areas.HelpPage +{ + /// + /// This represents an invalid sample on the help page. There's a display template named InvalidSample associated with this class. + /// + public class InvalidSample + { + public InvalidSample(string errorMessage) + { + if (errorMessage == null) + { + throw new ArgumentNullException("errorMessage"); + } + ErrorMessage = errorMessage; + } + + public string ErrorMessage { get; private set; } + + public override bool Equals(object obj) + { + InvalidSample other = obj as InvalidSample; + return other != null && ErrorMessage == other.ErrorMessage; + } + + public override int GetHashCode() + { + return ErrorMessage.GetHashCode(); + } + + public override string ToString() + { + return ErrorMessage; + } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/ObjectGenerator.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/ObjectGenerator.cs new file mode 100644 index 0000000..aab6091 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/ObjectGenerator.cs @@ -0,0 +1,456 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Reflection; + +namespace RandomDataWebApi.Areas.HelpPage +{ + /// + /// This class will create an object of a given type and populate it with sample data. + /// + public class ObjectGenerator + { + internal const int DefaultCollectionSize = 2; + private readonly SimpleTypeObjectGenerator SimpleObjectGenerator = new SimpleTypeObjectGenerator(); + + /// + /// Generates an object for a given type. The type needs to be public, have a public default constructor and settable public properties/fields. Currently it supports the following types: + /// Simple types: , , , , , etc. + /// Complex types: POCO types. + /// Nullables: . + /// Arrays: arrays of simple types or complex types. + /// Key value pairs: + /// Tuples: , , etc + /// Dictionaries: or anything deriving from . + /// Collections: , , , , , or anything deriving from or . + /// Queryables: , . + /// + /// The type. + /// An object of the given type. + public object GenerateObject(Type type) + { + return GenerateObject(type, new Dictionary()); + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Here we just want to return null if anything goes wrong.")] + private object GenerateObject(Type type, Dictionary createdObjectReferences) + { + try + { + if (SimpleTypeObjectGenerator.CanGenerateObject(type)) + { + return SimpleObjectGenerator.GenerateObject(type); + } + + if (type.IsArray) + { + return GenerateArray(type, DefaultCollectionSize, createdObjectReferences); + } + + if (type.IsGenericType) + { + return GenerateGenericType(type, DefaultCollectionSize, createdObjectReferences); + } + + if (type == typeof(IDictionary)) + { + return GenerateDictionary(typeof(Hashtable), DefaultCollectionSize, createdObjectReferences); + } + + if (typeof(IDictionary).IsAssignableFrom(type)) + { + return GenerateDictionary(type, DefaultCollectionSize, createdObjectReferences); + } + + if (type == typeof(IList) || + type == typeof(IEnumerable) || + type == typeof(ICollection)) + { + return GenerateCollection(typeof(ArrayList), DefaultCollectionSize, createdObjectReferences); + } + + if (typeof(IList).IsAssignableFrom(type)) + { + return GenerateCollection(type, DefaultCollectionSize, createdObjectReferences); + } + + if (type == typeof(IQueryable)) + { + return GenerateQueryable(type, DefaultCollectionSize, createdObjectReferences); + } + + if (type.IsEnum) + { + return GenerateEnum(type); + } + + if (type.IsPublic || type.IsNestedPublic) + { + return GenerateComplexObject(type, createdObjectReferences); + } + } + catch + { + // Returns null if anything fails + return null; + } + + return null; + } + + private static object GenerateGenericType(Type type, int collectionSize, Dictionary createdObjectReferences) + { + Type genericTypeDefinition = type.GetGenericTypeDefinition(); + if (genericTypeDefinition == typeof(Nullable<>)) + { + return GenerateNullable(type, createdObjectReferences); + } + + if (genericTypeDefinition == typeof(KeyValuePair<,>)) + { + return GenerateKeyValuePair(type, createdObjectReferences); + } + + if (IsTuple(genericTypeDefinition)) + { + return GenerateTuple(type, createdObjectReferences); + } + + Type[] genericArguments = type.GetGenericArguments(); + if (genericArguments.Length == 1) + { + if (genericTypeDefinition == typeof(IList<>) || + genericTypeDefinition == typeof(IEnumerable<>) || + genericTypeDefinition == typeof(ICollection<>)) + { + Type collectionType = typeof(List<>).MakeGenericType(genericArguments); + return GenerateCollection(collectionType, collectionSize, createdObjectReferences); + } + + if (genericTypeDefinition == typeof(IQueryable<>)) + { + return GenerateQueryable(type, collectionSize, createdObjectReferences); + } + + Type closedCollectionType = typeof(ICollection<>).MakeGenericType(genericArguments[0]); + if (closedCollectionType.IsAssignableFrom(type)) + { + return GenerateCollection(type, collectionSize, createdObjectReferences); + } + } + + if (genericArguments.Length == 2) + { + if (genericTypeDefinition == typeof(IDictionary<,>)) + { + Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(genericArguments); + return GenerateDictionary(dictionaryType, collectionSize, createdObjectReferences); + } + + Type closedDictionaryType = typeof(IDictionary<,>).MakeGenericType(genericArguments[0], genericArguments[1]); + if (closedDictionaryType.IsAssignableFrom(type)) + { + return GenerateDictionary(type, collectionSize, createdObjectReferences); + } + } + + if (type.IsPublic || type.IsNestedPublic) + { + return GenerateComplexObject(type, createdObjectReferences); + } + + return null; + } + + private static object GenerateTuple(Type type, Dictionary createdObjectReferences) + { + Type[] genericArgs = type.GetGenericArguments(); + object[] parameterValues = new object[genericArgs.Length]; + bool failedToCreateTuple = true; + ObjectGenerator objectGenerator = new ObjectGenerator(); + for (int i = 0; i < genericArgs.Length; i++) + { + parameterValues[i] = objectGenerator.GenerateObject(genericArgs[i], createdObjectReferences); + failedToCreateTuple &= parameterValues[i] == null; + } + if (failedToCreateTuple) + { + return null; + } + object result = Activator.CreateInstance(type, parameterValues); + return result; + } + + private static bool IsTuple(Type genericTypeDefinition) + { + return genericTypeDefinition == typeof(Tuple<>) || + genericTypeDefinition == typeof(Tuple<,>) || + genericTypeDefinition == typeof(Tuple<,,>) || + genericTypeDefinition == typeof(Tuple<,,,>) || + genericTypeDefinition == typeof(Tuple<,,,,>) || + genericTypeDefinition == typeof(Tuple<,,,,,>) || + genericTypeDefinition == typeof(Tuple<,,,,,,>) || + genericTypeDefinition == typeof(Tuple<,,,,,,,>); + } + + private static object GenerateKeyValuePair(Type keyValuePairType, Dictionary createdObjectReferences) + { + Type[] genericArgs = keyValuePairType.GetGenericArguments(); + Type typeK = genericArgs[0]; + Type typeV = genericArgs[1]; + ObjectGenerator objectGenerator = new ObjectGenerator(); + object keyObject = objectGenerator.GenerateObject(typeK, createdObjectReferences); + object valueObject = objectGenerator.GenerateObject(typeV, createdObjectReferences); + if (keyObject == null && valueObject == null) + { + // Failed to create key and values + return null; + } + object result = Activator.CreateInstance(keyValuePairType, keyObject, valueObject); + return result; + } + + private static object GenerateArray(Type arrayType, int size, Dictionary createdObjectReferences) + { + Type type = arrayType.GetElementType(); + Array result = Array.CreateInstance(type, size); + bool areAllElementsNull = true; + ObjectGenerator objectGenerator = new ObjectGenerator(); + for (int i = 0; i < size; i++) + { + object element = objectGenerator.GenerateObject(type, createdObjectReferences); + result.SetValue(element, i); + areAllElementsNull &= element == null; + } + + if (areAllElementsNull) + { + return null; + } + + return result; + } + + private static object GenerateDictionary(Type dictionaryType, int size, Dictionary createdObjectReferences) + { + Type typeK = typeof(object); + Type typeV = typeof(object); + if (dictionaryType.IsGenericType) + { + Type[] genericArgs = dictionaryType.GetGenericArguments(); + typeK = genericArgs[0]; + typeV = genericArgs[1]; + } + + object result = Activator.CreateInstance(dictionaryType); + MethodInfo addMethod = dictionaryType.GetMethod("Add") ?? dictionaryType.GetMethod("TryAdd"); + MethodInfo containsMethod = dictionaryType.GetMethod("Contains") ?? dictionaryType.GetMethod("ContainsKey"); + ObjectGenerator objectGenerator = new ObjectGenerator(); + for (int i = 0; i < size; i++) + { + object newKey = objectGenerator.GenerateObject(typeK, createdObjectReferences); + if (newKey == null) + { + // Cannot generate a valid key + return null; + } + + bool containsKey = (bool)containsMethod.Invoke(result, new object[] { newKey }); + if (!containsKey) + { + object newValue = objectGenerator.GenerateObject(typeV, createdObjectReferences); + addMethod.Invoke(result, new object[] { newKey, newValue }); + } + } + + return result; + } + + private static object GenerateEnum(Type enumType) + { + Array possibleValues = Enum.GetValues(enumType); + if (possibleValues.Length > 0) + { + return possibleValues.GetValue(0); + } + return null; + } + + private static object GenerateQueryable(Type queryableType, int size, Dictionary createdObjectReferences) + { + bool isGeneric = queryableType.IsGenericType; + object list; + if (isGeneric) + { + Type listType = typeof(List<>).MakeGenericType(queryableType.GetGenericArguments()); + list = GenerateCollection(listType, size, createdObjectReferences); + } + else + { + list = GenerateArray(typeof(object[]), size, createdObjectReferences); + } + if (list == null) + { + return null; + } + if (isGeneric) + { + Type argumentType = typeof(IEnumerable<>).MakeGenericType(queryableType.GetGenericArguments()); + MethodInfo asQueryableMethod = typeof(Queryable).GetMethod("AsQueryable", new[] { argumentType }); + return asQueryableMethod.Invoke(null, new[] { list }); + } + + return Queryable.AsQueryable((IEnumerable)list); + } + + private static object GenerateCollection(Type collectionType, int size, Dictionary createdObjectReferences) + { + Type type = collectionType.IsGenericType ? + collectionType.GetGenericArguments()[0] : + typeof(object); + object result = Activator.CreateInstance(collectionType); + MethodInfo addMethod = collectionType.GetMethod("Add"); + bool areAllElementsNull = true; + ObjectGenerator objectGenerator = new ObjectGenerator(); + for (int i = 0; i < size; i++) + { + object element = objectGenerator.GenerateObject(type, createdObjectReferences); + addMethod.Invoke(result, new object[] { element }); + areAllElementsNull &= element == null; + } + + if (areAllElementsNull) + { + return null; + } + + return result; + } + + private static object GenerateNullable(Type nullableType, Dictionary createdObjectReferences) + { + Type type = nullableType.GetGenericArguments()[0]; + ObjectGenerator objectGenerator = new ObjectGenerator(); + return objectGenerator.GenerateObject(type, createdObjectReferences); + } + + private static object GenerateComplexObject(Type type, Dictionary createdObjectReferences) + { + object result = null; + + if (createdObjectReferences.TryGetValue(type, out result)) + { + // The object has been created already, just return it. This will handle the circular reference case. + return result; + } + + if (type.IsValueType) + { + result = Activator.CreateInstance(type); + } + else + { + ConstructorInfo defaultCtor = type.GetConstructor(Type.EmptyTypes); + if (defaultCtor == null) + { + // Cannot instantiate the type because it doesn't have a default constructor + return null; + } + + result = defaultCtor.Invoke(new object[0]); + } + createdObjectReferences.Add(type, result); + SetPublicProperties(type, result, createdObjectReferences); + SetPublicFields(type, result, createdObjectReferences); + return result; + } + + private static void SetPublicProperties(Type type, object obj, Dictionary createdObjectReferences) + { + PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + ObjectGenerator objectGenerator = new ObjectGenerator(); + foreach (PropertyInfo property in properties) + { + if (property.CanWrite) + { + object propertyValue = objectGenerator.GenerateObject(property.PropertyType, createdObjectReferences); + property.SetValue(obj, propertyValue, null); + } + } + } + + private static void SetPublicFields(Type type, object obj, Dictionary createdObjectReferences) + { + FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); + ObjectGenerator objectGenerator = new ObjectGenerator(); + foreach (FieldInfo field in fields) + { + object fieldValue = objectGenerator.GenerateObject(field.FieldType, createdObjectReferences); + field.SetValue(obj, fieldValue); + } + } + + private class SimpleTypeObjectGenerator + { + private long _index = 0; + private static readonly Dictionary> DefaultGenerators = InitializeGenerators(); + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These are simple type factories and cannot be split up.")] + private static Dictionary> InitializeGenerators() + { + return new Dictionary> + { + { typeof(Boolean), index => true }, + { typeof(Byte), index => (Byte)64 }, + { typeof(Char), index => (Char)65 }, + { typeof(DateTime), index => DateTime.Now }, + { typeof(DateTimeOffset), index => new DateTimeOffset(DateTime.Now) }, + { typeof(DBNull), index => DBNull.Value }, + { typeof(Decimal), index => (Decimal)index }, + { typeof(Double), index => (Double)(index + 0.1) }, + { typeof(Guid), index => Guid.NewGuid() }, + { typeof(Int16), index => (Int16)(index % Int16.MaxValue) }, + { typeof(Int32), index => (Int32)(index % Int32.MaxValue) }, + { typeof(Int64), index => (Int64)index }, + { typeof(Object), index => new object() }, + { typeof(SByte), index => (SByte)64 }, + { typeof(Single), index => (Single)(index + 0.1) }, + { + typeof(String), index => + { + return String.Format(CultureInfo.CurrentCulture, "sample string {0}", index); + } + }, + { + typeof(TimeSpan), index => + { + return TimeSpan.FromTicks(1234567); + } + }, + { typeof(UInt16), index => (UInt16)(index % UInt16.MaxValue) }, + { typeof(UInt32), index => (UInt32)(index % UInt32.MaxValue) }, + { typeof(UInt64), index => (UInt64)index }, + { + typeof(Uri), index => + { + return new Uri(String.Format(CultureInfo.CurrentCulture, "http://webapihelppage{0}.com", index)); + } + }, + }; + } + + public static bool CanGenerateObject(Type type) + { + return DefaultGenerators.ContainsKey(type); + } + + public object GenerateObject(Type type) + { + return DefaultGenerators[type](++_index); + } + } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/SampleDirection.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/SampleDirection.cs new file mode 100644 index 0000000..11e986e --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/SampleDirection.cs @@ -0,0 +1,11 @@ +namespace RandomDataWebApi.Areas.HelpPage +{ + /// + /// Indicates whether the sample is used for request or response + /// + public enum SampleDirection + { + Request = 0, + Response + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/TextSample.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/TextSample.cs new file mode 100644 index 0000000..2b9c6ed --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/SampleGeneration/TextSample.cs @@ -0,0 +1,37 @@ +using System; + +namespace RandomDataWebApi.Areas.HelpPage +{ + /// + /// This represents a preformatted text sample on the help page. There's a display template named TextSample associated with this class. + /// + public class TextSample + { + public TextSample(string text) + { + if (text == null) + { + throw new ArgumentNullException("text"); + } + Text = text; + } + + public string Text { get; private set; } + + public override bool Equals(object obj) + { + TextSample other = obj as TextSample; + return other != null && Text == other.Text; + } + + public override int GetHashCode() + { + return Text.GetHashCode(); + } + + public override string ToString() + { + return Text; + } + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/Api.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/Api.cshtml new file mode 100644 index 0000000..229b9c5 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/Api.cshtml @@ -0,0 +1,23 @@ +@using System.Web.Http +@using RandomDataWebApi +@using RandomDataWebApi.Areas.HelpPage.Models +@model HelpPageApiModel + +@{ + var description = Model.ApiDescription; + ViewBag.Title = description.HttpMethod.Method + " " + description.RelativePath; +} + + +
+ +
+ @Html.DisplayForModel() +
+
diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/ApiGroup.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/ApiGroup.cshtml new file mode 100644 index 0000000..5d0c618 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/ApiGroup.cshtml @@ -0,0 +1,41 @@ +@using System.Web.Http +@using System.Web.Http.Controllers +@using System.Web.Http.Description +@using RandomDataWebApi.Areas.HelpPage +@using RandomDataWebApi.Areas.HelpPage.Models +@model IGrouping + +@{ + var controllerDocumentation = ViewBag.DocumentationProvider != null ? + ViewBag.DocumentationProvider.GetDocumentation(Model.Key) : + null; +} + +

@Model.Key.ControllerName

+@if (!String.IsNullOrEmpty(controllerDocumentation)) +{ +

@controllerDocumentation

+} + + + + + + @foreach (var api in Model) + { + + + + + } + +
APIDescription
@api.HttpMethod.Method @api.RelativePath + @if (api.Documentation != null) + { +

@Html.Raw(api.Documentation)

+ } + else + { +

No documentation available.

+ } +
\ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/CollectionModelDescription.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/CollectionModelDescription.cshtml new file mode 100644 index 0000000..69d083a --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/CollectionModelDescription.cshtml @@ -0,0 +1,6 @@ +@using RandomDataWebApi.Areas.HelpPage.ModelDescriptions +@model CollectionModelDescription +@if (Model.ElementDescription is ComplexTypeModelDescription) +{ + @Html.DisplayFor(m => m.ElementDescription) +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/ComplexTypeModelDescription.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/ComplexTypeModelDescription.cshtml new file mode 100644 index 0000000..958fc99 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/ComplexTypeModelDescription.cshtml @@ -0,0 +1,3 @@ +@using RandomDataWebApi.Areas.HelpPage.ModelDescriptions +@model ComplexTypeModelDescription +@Html.DisplayFor(m => m.Properties, "Parameters") \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/DictionaryModelDescription.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/DictionaryModelDescription.cshtml new file mode 100644 index 0000000..5f4c641 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/DictionaryModelDescription.cshtml @@ -0,0 +1,4 @@ +@using RandomDataWebApi.Areas.HelpPage.ModelDescriptions +@model DictionaryModelDescription +Dictionary of @Html.DisplayFor(m => Model.KeyModelDescription.ModelType, "ModelDescriptionLink", new { modelDescription = Model.KeyModelDescription }) [key] +and @Html.DisplayFor(m => Model.ValueModelDescription.ModelType, "ModelDescriptionLink", new { modelDescription = Model.ValueModelDescription }) [value] \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/EnumTypeModelDescription.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/EnumTypeModelDescription.cshtml new file mode 100644 index 0000000..1222c02 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/EnumTypeModelDescription.cshtml @@ -0,0 +1,24 @@ +@using RandomDataWebApi.Areas.HelpPage.ModelDescriptions +@model EnumTypeModelDescription + +

Possible enumeration values:

+ + + + + + + @foreach (EnumValueDescription value in Model.Values) + { + + + + + + } + +
NameValueDescription
@value.Name +

@value.Value

+
+

@value.Documentation

+
\ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/HelpPageApiModel.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/HelpPageApiModel.cshtml new file mode 100644 index 0000000..92b4488 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/HelpPageApiModel.cshtml @@ -0,0 +1,72 @@ +@using System.Web.Http +@using System.Web.Http.Description +@using RandomDataWebApi +@using RandomDataWebApi.Areas.HelpPage.Models +@using RandomDataWebApi.Areas.HelpPage.ModelDescriptions +@model HelpPageApiModel + +@{ + ApiDescription description = Model.ApiDescription; +} +

@description.HttpMethod.Method @description.RelativePath

+
+ @if (description.ActionDescriptor.ControllerDescriptor.ControllerName == "Random") + { +

@Html.TextLink("/" + Model.SampleUri)

+ } +

@Html.Raw(description.Documentation)

+ +

Request Information

+ +

URI Parameters

+ @Html.DisplayFor(m => m.UriParameters, "Parameters") + +

Body Parameters

+ +

@Model.RequestDocumentation

+ + @if (Model.RequestModelDescription != null) + { + @Html.DisplayFor(m => m.RequestModelDescription.ModelType, "ModelDescriptionLink", new { modelDescription = Model.RequestModelDescription }) + if (Model.RequestBodyParameters != null) + { + @Html.DisplayFor(m => m.RequestBodyParameters, "Parameters") + } + } + else + { +

None.

+ } + + @if (Model.SampleRequests.Count > 0) + { +

Request Formats

+ @Html.DisplayFor(m => m.SampleRequests, "Samples") + } + +

Response Information

+ +

Resource Description

+ +

@description.ResponseDescription.Documentation

+ + @if (Model.ResourceDescription != null) + { + @Html.DisplayFor(m => m.ResourceDescription.ModelType, "ModelDescriptionLink", new { modelDescription = Model.ResourceDescription }) + if (Model.ResourceProperties != null) + { + @Html.DisplayFor(m => m.ResourceProperties, "Parameters") + } + } + else + { +

None.

+ } + + @if (Model.SampleResponses.Count > 0) + { +

Response Formats

+ @Html.DisplayFor(m => m.SampleResponses, "Samples") + } + +
\ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/ImageSample.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/ImageSample.cshtml new file mode 100644 index 0000000..fc993cb --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/ImageSample.cshtml @@ -0,0 +1,4 @@ +@using RandomDataWebApi.Areas.HelpPage +@model ImageSample + + \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/InvalidSample.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/InvalidSample.cshtml new file mode 100644 index 0000000..338714d --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/InvalidSample.cshtml @@ -0,0 +1,13 @@ +@using RandomDataWebApi.Areas.HelpPage +@model InvalidSample + +@if (HttpContext.Current.IsDebuggingEnabled) +{ +
+

@Model.ErrorMessage

+
+} +else +{ +

Sample not available.

+} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/KeyValuePairModelDescription.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/KeyValuePairModelDescription.cshtml new file mode 100644 index 0000000..3fc4c59 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/KeyValuePairModelDescription.cshtml @@ -0,0 +1,4 @@ +@using RandomDataWebApi.Areas.HelpPage.ModelDescriptions +@model KeyValuePairModelDescription +Pair of @Html.DisplayFor(m => Model.KeyModelDescription.ModelType, "ModelDescriptionLink", new { modelDescription = Model.KeyModelDescription }) [key] +and @Html.DisplayFor(m => Model.ValueModelDescription.ModelType, "ModelDescriptionLink", new { modelDescription = Model.ValueModelDescription }) [value] \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/ModelDescriptionLink.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/ModelDescriptionLink.cshtml new file mode 100644 index 0000000..94f7c2a --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/ModelDescriptionLink.cshtml @@ -0,0 +1,26 @@ +@using RandomDataWebApi.Areas.HelpPage.ModelDescriptions +@model Type +@{ + ModelDescription modelDescription = ViewBag.modelDescription; + if (modelDescription is ComplexTypeModelDescription || modelDescription is EnumTypeModelDescription) + { + if (Model == typeof(Object)) + { + @:Object + } + else + { + @Html.ActionLink(modelDescription.Name, "ResourceModel", "Help", new { modelName = modelDescription.Name }, null) + } + } + else if (modelDescription is CollectionModelDescription) + { + var collectionDescription = modelDescription as CollectionModelDescription; + var elementDescription = collectionDescription.ElementDescription; + @:Collection of @Html.DisplayFor(m => elementDescription.ModelType, "ModelDescriptionLink", new { modelDescription = elementDescription }) + } + else + { + @Html.DisplayFor(m => modelDescription) + } +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/Parameters.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/Parameters.cshtml new file mode 100644 index 0000000..cd4c47c --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/Parameters.cshtml @@ -0,0 +1,48 @@ +@using System.Collections.Generic +@using System.Collections.ObjectModel +@using System.Web.Http.Description +@using System.Threading +@using RandomDataWebApi.Areas.HelpPage.ModelDescriptions +@model IList + +@if (Model.Count > 0) +{ + + + + + + @foreach (ParameterDescription parameter in Model) + { + ModelDescription modelDescription = parameter.TypeDescription; + + + + + + + } + +
NameDescriptionTypeAdditional information
@parameter.Name +

@parameter.Documentation

+
+ @Html.DisplayFor(m => modelDescription.ModelType, "ModelDescriptionLink", new { modelDescription = modelDescription }) + + @if (parameter.Annotations.Count > 0) + { + foreach (var annotation in parameter.Annotations) + { +

@annotation.Documentation

+ } + } + else + { +

None.

+ } +
+} +else +{ +

None.

+} + diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/Samples.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/Samples.cshtml new file mode 100644 index 0000000..aa93885 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/Samples.cshtml @@ -0,0 +1,30 @@ +@using System.Net.Http.Headers +@model Dictionary + +@{ + // Group the samples into a single tab if they are the same. + Dictionary samples = Model.GroupBy(pair => pair.Value).ToDictionary( + pair => String.Join(", ", pair.Select(m => m.Key.ToString()).ToArray()), + pair => pair.Key); + var mediaTypes = samples.Keys; +} +
+ @foreach (var mediaType in mediaTypes) + { +

@mediaType

+
+ Sample: + @{ + var sample = samples[mediaType]; + if (sample == null) + { +

Sample not available.

+ } + else + { + @Html.DisplayFor(s => sample); + } + } +
+ } +
\ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/SimpleTypeModelDescription.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/SimpleTypeModelDescription.cshtml new file mode 100644 index 0000000..5241a49 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/SimpleTypeModelDescription.cshtml @@ -0,0 +1,3 @@ +@using RandomDataWebApi.Areas.HelpPage.ModelDescriptions +@model SimpleTypeModelDescription +@Model.Documentation \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/TextSample.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/TextSample.cshtml new file mode 100644 index 0000000..8bbf526 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/DisplayTemplates/TextSample.cshtml @@ -0,0 +1,6 @@ +@using RandomDataWebApi.Areas.HelpPage +@model TextSample + +
+@Model.Text
+
\ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/Index.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/Index.cshtml new file mode 100644 index 0000000..65bfed7 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/Index.cshtml @@ -0,0 +1,40 @@ +@using System.Web.Http +@using System.Web.Http.Controllers +@using System.Web.Http.Description +@using System.Collections.ObjectModel +@using RandomDataWebApi +@using RandomDataWebApi.Areas.HelpPage.Models +@model Collection + +@{ + ViewBag.Title = WebApiApplication.Title; + + // Group APIs by controller + ILookup apiGroups = Model.ToLookup(api => api.ActionDescriptor.ControllerDescriptor); +} + + +
+
+
+

@ViewBag.Title

+
+
+
+
+ +
+ @foreach (var group in apiGroups) + { + @Html.DisplayFor(m => group, "ApiGroup") + } +
+
diff --git a/RandomGenerator/RandomWebApp/Views/Home/JsonTest.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/JsonTest.cshtml similarity index 64% rename from RandomGenerator/RandomWebApp/Views/Home/JsonTest.cshtml rename to RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/JsonTest.cshtml index 7720440..17935e6 100644 --- a/RandomGenerator/RandomWebApp/Views/Home/JsonTest.cshtml +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/JsonTest.cshtml @@ -1,21 +1,26 @@ -@{ - Layout = null; - ViewBag.Title = "Random Data Web API - Test"; +@using RandomDataWebApi + +@{ + ViewBag.Title = $"{WebApiApplication.Title} - Test"; } - - - - - - @ViewBag.Title - - - -
+ + +
+ +

@ViewBag.Title

-

This Web API supports CORS.

-

Domain: @Html.TextBox("TargetDomain", string.Format("{0}://{1}", Request.Url.Scheme, Request.Url.Authority))

+

+ @WebApiApplication.Description
+ This Web API supports CORS (Cross-Origin Resource Sharing). +

+

API Domain: @Html.TextBox("TargetDomain", $"{Request.Url.Scheme}://{Request.Url.Authority}", new { style = "width:300px" })

NewAlphabets
@@ -93,63 +98,59 @@
 
-
-
-

© 2014 Keiho Sakapon.

-
+ - - +
diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/ResourceModel.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/ResourceModel.cshtml new file mode 100644 index 0000000..43a3cf3 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Help/ResourceModel.cshtml @@ -0,0 +1,24 @@ +@using System.Web.Http +@using RandomDataWebApi +@using RandomDataWebApi.Areas.HelpPage.ModelDescriptions +@model ModelDescription + +@{ + ViewBag.Title = Model.Name; +} + + +
+ +

@ViewBag.Title

+

@Model.Documentation

+
+ @Html.DisplayFor(m => Model) +
+
diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Shared/Error.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Shared/Error.cshtml new file mode 100644 index 0000000..0a52a52 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Shared/Error.cshtml @@ -0,0 +1,20 @@ +@using RandomDataWebApi + +@{ + ViewBag.Title = "Error"; +} + + +
+ +
+

@ViewBag.Title

+

The page was not found, or an unexpected error occurred.

+
+
diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Shared/_Layout.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000..b6f7423 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Shared/_Layout.cshtml @@ -0,0 +1,23 @@ +@using RandomDataWebApi; + + + + + + + @ViewBag.Title + @RenderSection("scripts", required: false) + + +
+ @RenderBody() +
+

+ @WebApiApplication.Title @($"v{WebApiApplication.FileVersion}") - + Project Site on GitHub
+ @WebApiApplication.Copyright +

+
+
+ + \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Web.config b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Web.config new file mode 100644 index 0000000..63a7a02 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/Web.config @@ -0,0 +1,41 @@ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/_ViewStart.cshtml b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/_ViewStart.cshtml new file mode 100644 index 0000000..0c50466 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/Views/_ViewStart.cshtml @@ -0,0 +1,4 @@ +@{ + // Change the Layout path below to blend the look and feel of the help page with your existing web pages. + Layout = "~/Areas/HelpPage/Views/Shared/_Layout.cshtml"; +} \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Areas/HelpPage/XmlDocumentationProvider.cs b/RandomData2/RandomDataWebApi/Areas/HelpPage/XmlDocumentationProvider.cs new file mode 100644 index 0000000..11f704e --- /dev/null +++ b/RandomData2/RandomDataWebApi/Areas/HelpPage/XmlDocumentationProvider.cs @@ -0,0 +1,161 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Web.Http.Controllers; +using System.Web.Http.Description; +using System.Xml.XPath; +using RandomDataWebApi.Areas.HelpPage.ModelDescriptions; + +namespace RandomDataWebApi.Areas.HelpPage +{ + /// + /// A custom that reads the API documentation from an XML documentation file. + /// + public class XmlDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider + { + private XPathNavigator _documentNavigator; + private const string TypeExpression = "/doc/members/member[@name='T:{0}']"; + private const string MethodExpression = "/doc/members/member[@name='M:{0}']"; + private const string PropertyExpression = "/doc/members/member[@name='P:{0}']"; + private const string FieldExpression = "/doc/members/member[@name='F:{0}']"; + private const string ParameterExpression = "param[@name='{0}']"; + + /// + /// Initializes a new instance of the class. + /// + /// The physical path to XML document. + public XmlDocumentationProvider(string documentPath) + { + if (documentPath == null) + { + throw new ArgumentNullException("documentPath"); + } + XPathDocument xpath = new XPathDocument(documentPath); + _documentNavigator = xpath.CreateNavigator(); + } + + public string GetDocumentation(HttpControllerDescriptor controllerDescriptor) + { + XPathNavigator typeNode = GetTypeNode(controllerDescriptor.ControllerType); + return GetTagValue(typeNode, "summary"); + } + + public virtual string GetDocumentation(HttpActionDescriptor actionDescriptor) + { + XPathNavigator methodNode = GetMethodNode(actionDescriptor); + return GetTagValue(methodNode, "summary"); + } + + public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor) + { + ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor; + if (reflectedParameterDescriptor != null) + { + XPathNavigator methodNode = GetMethodNode(reflectedParameterDescriptor.ActionDescriptor); + if (methodNode != null) + { + string parameterName = reflectedParameterDescriptor.ParameterInfo.Name; + XPathNavigator parameterNode = methodNode.SelectSingleNode(String.Format(CultureInfo.InvariantCulture, ParameterExpression, parameterName)); + if (parameterNode != null) + { + return parameterNode.Value.Trim(); + } + } + } + + return null; + } + + public string GetResponseDocumentation(HttpActionDescriptor actionDescriptor) + { + XPathNavigator methodNode = GetMethodNode(actionDescriptor); + return GetTagValue(methodNode, "returns"); + } + + public string GetDocumentation(MemberInfo member) + { + string memberName = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", GetTypeName(member.DeclaringType), member.Name); + string expression = member.MemberType == MemberTypes.Field ? FieldExpression : PropertyExpression; + string selectExpression = String.Format(CultureInfo.InvariantCulture, expression, memberName); + XPathNavigator propertyNode = _documentNavigator.SelectSingleNode(selectExpression); + return GetTagValue(propertyNode, "summary"); + } + + public string GetDocumentation(Type type) + { + XPathNavigator typeNode = GetTypeNode(type); + return GetTagValue(typeNode, "summary"); + } + + private XPathNavigator GetMethodNode(HttpActionDescriptor actionDescriptor) + { + ReflectedHttpActionDescriptor reflectedActionDescriptor = actionDescriptor as ReflectedHttpActionDescriptor; + if (reflectedActionDescriptor != null) + { + string selectExpression = String.Format(CultureInfo.InvariantCulture, MethodExpression, GetMemberName(reflectedActionDescriptor.MethodInfo)); + return _documentNavigator.SelectSingleNode(selectExpression); + } + + return null; + } + + private static string GetMemberName(MethodInfo method) + { + string name = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", GetTypeName(method.DeclaringType), method.Name); + ParameterInfo[] parameters = method.GetParameters(); + if (parameters.Length != 0) + { + string[] parameterTypeNames = parameters.Select(param => GetTypeName(param.ParameterType)).ToArray(); + name += String.Format(CultureInfo.InvariantCulture, "({0})", String.Join(",", parameterTypeNames)); + } + + return name; + } + + private static string GetTagValue(XPathNavigator parentNode, string tagName) + { + if (parentNode != null) + { + XPathNavigator node = parentNode.SelectSingleNode(tagName); + if (node != null) + { + return node.InnerXml.Trim(); + } + } + + return null; + } + + private XPathNavigator GetTypeNode(Type type) + { + string controllerTypeName = GetTypeName(type); + string selectExpression = String.Format(CultureInfo.InvariantCulture, TypeExpression, controllerTypeName); + return _documentNavigator.SelectSingleNode(selectExpression); + } + + private static string GetTypeName(Type type) + { + string name = type.FullName; + if (type.IsGenericType) + { + // Format the generic type name to something like: Generic{System.Int32,System.String} + Type genericType = type.GetGenericTypeDefinition(); + Type[] genericArguments = type.GetGenericArguments(); + string genericTypeName = genericType.FullName; + + // Trim the generic parameter counts from the name + genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`')); + string[] argumentTypeNames = genericArguments.Select(t => GetTypeName(t)).ToArray(); + name = String.Format(CultureInfo.InvariantCulture, "{0}{{{1}}}", genericTypeName, String.Join(",", argumentTypeNames)); + } + if (type.IsNested) + { + // Changing the nested type name from OuterType+InnerType to OuterType.InnerType to match the XML documentation syntax. + name = name.Replace("+", "."); + } + + return name; + } + } +} diff --git a/RandomData2/RandomDataWebApi/Controllers/RandomController.cs b/RandomData2/RandomDataWebApi/Controllers/RandomController.cs new file mode 100644 index 0000000..680b053 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Controllers/RandomController.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Web.Http; +using Blaze.Randomization; + +namespace RandomDataWebApi.Controllers +{ + /// + /// Generates random data. + /// + [RoutePrefix("api")] + public class RandomController : ApiController + { + /// + /// Creates new alphabets of the specified length. + /// + /// The length of the result. 0 ≦ x ≦ 4096. + /// Alphabets. + [HttpGet] + [Route("NewAlphabets/{length:int:range(0,4096)}")] + public string NewAlphabets(int length) + { + return RandomData.GenerateAlphabets(length); + } + + /// + /// Creates new alphanumerics of the specified length. + /// + /// The length of the result. 0 ≦ x ≦ 4096. + /// Alphanumerics. + [HttpGet] + [Route("NewAlphanumerics/{length:int:range(0,4096)}")] + public string NewAlphanumerics(int length) + { + return RandomData.GenerateAlphanumerics(length); + } + + /// + /// Creates a new byte sequence of the specified length, with the lowercase hexadecimal format. + /// + /// The length of the result. 0 ≦ x ≦ 4096. + /// The lowercase hexadecimal format. + [HttpGet] + [Route("NewBytes/hexlower/{length:int:range(0,4096)}")] + public string NewBytes_HexLower(int length) + { + var data = RandomData.GenerateBytes(length); + return data.ToHexString(false); + } + + /// + /// Creates a new byte sequence of the specified length, with the uppercase hexadecimal format. + /// + /// The length of the result. 0 ≦ x ≦ 4096. + /// The uppercase hexadecimal format. + [HttpGet] + [Route("NewBytes/hexupper/{length:int:range(0,4096)}")] + public string NewBytes_HexUpper(int length) + { + var data = RandomData.GenerateBytes(length); + return data.ToHexString(true); + } + + /// + /// Creates a new byte sequence of the specified length, with the Base64 format. + /// + /// The length of the result. 0 ≦ x ≦ 4096. + /// The Base64 format. + [HttpGet] + [Route("NewBytes/base64/{length:int:range(0,4096)}")] + public string NewBytes_Base64(int length) + { + var data = RandomData.GenerateBytes(length); + return Convert.ToBase64String(data); + } + + /// + /// Creates a new UUID (GUID), in particular a version 4 UUID. + /// + /// A UUID (GUID). + [HttpGet] + [Route("NewUuid")] + public Guid NewUuid() + { + return Guid.NewGuid(); + } + + /// + /// Creates a new time-ordered 16-byte ID with the UUID format.
+ /// The upper 8 bytes represent ticks of date/time (by 10-7 second) and the other 8 bytes are randomly generated. + ///
+ /// A pair of the ID and the created date/time. + [HttpGet] + [Route("NewOrderedId")] + public GuidWithDateTime NewOrderedId() + { + return RandomData.GenerateOrderedGuid(); + } + + /// + /// Creates a new time-ordered 16-byte ID with the UUID format, which is ordered for the uniqueidentifier data type of the SQL Server.
+ /// The lower 8 bytes represent ticks of date/time (by 10-7 second) and the other 8 bytes are randomly generated. + ///
+ /// A pair of the ID and the created date/time. + [HttpGet] + [Route("NewOrderedId/sqlserver")] + public GuidWithDateTime NewOrderedId_SqlServer() + { + return RandomData.GenerateOrderedSqlGuid(); + } + + /// + /// Creates a new time-ordered 16-byte ID with the UUID format.
+ /// The upper 6 bytes represent ticks of date/time (by about 10-3 second) and the other 10 bytes are randomly generated. + ///
+ /// A pair of the ID and the created date/time. + [HttpGet] + [Route("NewOrderedId2")] + public GuidWithDateTime NewOrderedId2() + { + return RandomData.GenerateOrderedGuid2(); + } + + /// + /// Creates a new time-ordered 16-byte ID with the UUID format, which is ordered for the uniqueidentifier data type of the SQL Server.
+ /// The lower 6 bytes represent ticks of date/time (by about 10-3 second) and the other 10 bytes are randomly generated. + ///
+ /// A pair of the ID and the created date/time. + [HttpGet] + [Route("NewOrderedId2/sqlserver")] + public GuidWithDateTime NewOrderedId2_SqlServer() + { + return RandomData.GenerateOrderedSqlGuid2(); + } + } +} diff --git a/RandomData2/RandomDataWebApi/Global.asax b/RandomData2/RandomDataWebApi/Global.asax new file mode 100644 index 0000000..0062804 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="RandomDataWebApi.WebApiApplication" Language="C#" %> diff --git a/RandomData2/RandomDataWebApi/Global.asax.cs b/RandomData2/RandomDataWebApi/Global.asax.cs new file mode 100644 index 0000000..266170d --- /dev/null +++ b/RandomData2/RandomDataWebApi/Global.asax.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Web; +using System.Web.Http; +using System.Web.Mvc; +using System.Web.Routing; + +namespace RandomDataWebApi +{ + public class WebApiApplication : System.Web.HttpApplication + { + public static string Title { get; } = typeof(WebApiApplication).Assembly.GetCustomAttribute().Title; + public static string Description { get; } = typeof(WebApiApplication).Assembly.GetCustomAttribute().Description; + public static string Copyright { get; } = typeof(WebApiApplication).Assembly.GetCustomAttribute().Copyright; + public static string FileVersion { get; } = typeof(WebApiApplication).Assembly.GetCustomAttribute().Version; + + protected void Application_Start() + { + AreaRegistration.RegisterAllAreas(); + GlobalConfiguration.Configure(WebApiConfig.Register); + } + + void Application_BeginRequest(object sender, EventArgs e) + { + ValidateUrlScheme(); + } + + void ValidateUrlScheme() + { + if (!Request.IsSecureConnection && + RequireHttps && + !string.Equals(Request.Url.Host, "localhost", StringComparison.InvariantCultureIgnoreCase)) + { + // If we use Uri.OriginalString property, ":80" may be appended. + var secureUrl = Regex.Replace(Request.Url.AbsoluteUri, @"^\w+(?=://)", Uri.UriSchemeHttps); + + if (PermanentHttps) + Response.RedirectPermanent(secureUrl, true); + else + Response.Redirect(secureUrl, true); + } + } + + static bool RequireHttps + { + get { return Convert.ToBoolean(ConfigurationManager.AppSettings["app:RequireHttps"]); } + } + + static bool PermanentHttps + { + get { return Convert.ToBoolean(ConfigurationManager.AppSettings["app:PermanentHttps"]); } + } + } +} diff --git a/RandomGenerator/RandomWebApp/HtmlEx.cs b/RandomData2/RandomDataWebApi/HtmlEx.cs similarity index 91% rename from RandomGenerator/RandomWebApp/HtmlEx.cs rename to RandomData2/RandomDataWebApi/HtmlEx.cs index 26a429a..35c8a17 100644 --- a/RandomGenerator/RandomWebApp/HtmlEx.cs +++ b/RandomData2/RandomDataWebApi/HtmlEx.cs @@ -4,7 +4,7 @@ using System.Web; using System.Web.Mvc; -namespace RandomWebApp +namespace RandomDataWebApi { public static class HtmlEx { diff --git a/RandomData2/RandomDataWebApi/KTools.VersionIncrement.ps1 b/RandomData2/RandomDataWebApi/KTools.VersionIncrement.ps1 new file mode 100644 index 0000000..ce39ba6 --- /dev/null +++ b/RandomData2/RandomDataWebApi/KTools.VersionIncrement.ps1 @@ -0,0 +1,52 @@ +$source = @" +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +public static class Program +{ + public static int Main(string[] args) + { + // args[0]: The target directory path (optional). + var dirPath = args.Length > 0 ? args[0] : "."; + + foreach (var filePath in GetAssemblyInfoPaths(dirPath)) + IncrementForFile(filePath); + + return 0; + } + + static IEnumerable GetAssemblyInfoPaths(string dirPath) + { + return Directory.EnumerateFiles(dirPath, "AssemblyInfo.cs", SearchOption.AllDirectories); + } + + internal static void IncrementForFile(string filePath) + { + var contents = File.ReadLines(filePath, Encoding.UTF8) + .Select(IncrementForLine) + .ToArray(); + File.WriteAllLines(filePath, contents, Encoding.UTF8); + } + + // (? IncrementNumber(m.Value)); + } + + static string IncrementNumber(string i) + { + return (int.Parse(i) + 1).ToString(); + } +} +"@ + +Add-Type -TypeDefinition $source -Language CSharp +exit [Program]::Main($Args) diff --git a/RandomGenerator/RandomWebApp/Properties/AssemblyInfo.cs b/RandomData2/RandomDataWebApi/Properties/AssemblyInfo.cs similarity index 73% rename from RandomGenerator/RandomWebApp/Properties/AssemblyInfo.cs rename to RandomData2/RandomDataWebApi/Properties/AssemblyInfo.cs index 9ae17fb..95bcc07 100644 --- a/RandomGenerator/RandomWebApp/Properties/AssemblyInfo.cs +++ b/RandomData2/RandomDataWebApi/Properties/AssemblyInfo.cs @@ -5,12 +5,12 @@ // アセンブリに関する一般情報は、以下の属性セットによって // 制御されます。アセンブリに関連付けられている情報を変更するには、 // これらの属性値を変更します。 -[assembly: AssemblyTitle("RandomWebApp")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyTitle("Random Data Web API")] +[assembly: AssemblyDescription("Provides the JSON Web API to generate random data.")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("RandomWebApp")] -[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyCompany("Keiho Sakapon")] +[assembly: AssemblyProduct("Random Data Web API")] +[assembly: AssemblyCopyright("© 2014 Keiho Sakapon")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -20,7 +20,7 @@ [assembly: ComVisible(false)] // このプロジェクトが COM に公開される場合、次の GUID がタイプ ライブラリの ID になります。 -[assembly: Guid("8e7e9f94-109f-48f5-b25a-12f080ab47b8")] +[assembly: Guid("a62df1ca-11d0-427e-91cb-c08281d3de7c")] // アセンブリのバージョン情報は、以下の 4 つの値で構成されています: // @@ -31,5 +31,5 @@ // // すべての値を指定するか、下のように "*" を使ってリビジョンおよびビルド番号を // 既定値にすることができます: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("2.0.6.0")] +[assembly: AssemblyFileVersion("2.0.6")] diff --git a/RandomData2/RandomDataWebApi/RandomDataWebApi.csproj b/RandomData2/RandomDataWebApi/RandomDataWebApi.csproj new file mode 100644 index 0000000..f04abfc --- /dev/null +++ b/RandomData2/RandomDataWebApi/RandomDataWebApi.csproj @@ -0,0 +1,233 @@ + + + + + + Debug + AnyCPU + + + 2.0 + {A62DF1CA-11D0-427E-91CB-C08281D3DE7C} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + RandomDataWebApi + RandomDataWebApi + v4.5 + true + + + + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + bin\RandomDataWebApi.xml + + + false + none + true + bin\ + TRACE + prompt + 4 + bin\RandomDataWebApi.xml + + + + ..\packages\Blaze.1.1.10\lib\net45\Blaze.dll + + + ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.8\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll + + + + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + + + + ..\packages\Microsoft.AspNet.Cors.5.2.3\lib\net45\System.Web.Cors.dll + + + + + + + + + + + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll + + + ..\packages\Microsoft.AspNet.WebApi.Cors.5.2.3\lib\net45\System.Web.Http.Cors.dll + + + ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll + + + ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll + + + + + + + + + + + + ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Global.asax + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Web.config + + + Web.config + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + True + True + 57844 + / + http://localhost:57844/ + False + False + + + False + + + + + + + このプロジェクトは、このコンピューター上にない NuGet パッケージを参照しています。それらのパッケージをダウンロードするには、[NuGet パッケージの復元] を使用します。詳細については、http://go.microsoft.com/fwlink/?LinkID=322105 を参照してください。見つからないファイルは {0} です。 + + + + + + \ No newline at end of file diff --git a/RandomGenerator/RandomWebApp/Web.Debug.config b/RandomData2/RandomDataWebApi/Web.Debug.config similarity index 88% rename from RandomGenerator/RandomWebApp/Web.Debug.config rename to RandomData2/RandomDataWebApi/Web.Debug.config index 99e774f..15874ba 100644 --- a/RandomGenerator/RandomWebApp/Web.Debug.config +++ b/RandomData2/RandomDataWebApi/Web.Debug.config @@ -1,6 +1,6 @@ - + - + \ No newline at end of file diff --git a/RandomGenerator/RandomWebApp/Web.Release.config b/RandomData2/RandomDataWebApi/Web.Release.config similarity index 84% rename from RandomGenerator/RandomWebApp/Web.Release.config rename to RandomData2/RandomDataWebApi/Web.Release.config index 88bfac3..da167f7 100644 --- a/RandomGenerator/RandomWebApp/Web.Release.config +++ b/RandomData2/RandomDataWebApi/Web.Release.config @@ -1,6 +1,6 @@ - + - + \ No newline at end of file diff --git a/RandomData2/RandomDataWebApi/Web.config b/RandomData2/RandomDataWebApi/Web.config new file mode 100644 index 0000000..12c6113 --- /dev/null +++ b/RandomData2/RandomDataWebApi/Web.config @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RandomData2/RandomDataWebApi/packages.config b/RandomData2/RandomDataWebApi/packages.config new file mode 100644 index 0000000..0a6dbb0 --- /dev/null +++ b/RandomData2/RandomDataWebApi/packages.config @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/RandomGenerator/RandomConsole/App.config b/RandomData2/UnitTest/App.config similarity index 87% rename from RandomGenerator/RandomConsole/App.config rename to RandomData2/UnitTest/App.config index 90d923a..8300f3b 100644 --- a/RandomGenerator/RandomConsole/App.config +++ b/RandomData2/UnitTest/App.config @@ -4,9 +4,6 @@
- - - diff --git a/RandomData2/UnitTest/Client/GlobalTest.cs b/RandomData2/UnitTest/Client/GlobalTest.cs new file mode 100644 index 0000000..4db25c4 --- /dev/null +++ b/RandomData2/UnitTest/Client/GlobalTest.cs @@ -0,0 +1,78 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTest.Client +{ + [TestClass] + public class GlobalTest + { + [TestMethod] + async public Task UriParameters() + { + Assert.AreEqual(HttpStatusCode.NotFound, await HttpHelper.GetAsync_StatusCode("api/NewAlphabets?length=8")); + Assert.AreEqual(HttpStatusCode.NotFound, await HttpHelper.GetAsync_StatusCode("api/NewAlphabets/?length=8")); + } + + [TestMethod] + async public Task Cors() + { + var uri = "api/NewUuid"; + + using (var http = new HttpClient { BaseAddress = HttpHelper.BaseUri }) + { + http.DefaultRequestHeaders.Add("Origin", "http://localhost:8080"); + var response = await http.GetAsync(uri); + response.EnsureSuccessStatusCode(); + + var allowOrigin = response.Headers.GetValues("Access-Control-Allow-Origin").ToArray(); + CollectionAssert.AreEqual(new[] { "*" }, allowOrigin); + } + } + + async static Task ContentType_Test(string[] acceptMediaTypes, string expectedMediaType) + { + var uri = "api/NewUuid"; + + using (var http = new HttpClient { BaseAddress = HttpHelper.BaseUri }) + { + foreach (var mediaType in acceptMediaTypes) + http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType)); + var response = await http.GetAsync(uri); + response.EnsureSuccessStatusCode(); + + Assert.AreEqual(expectedMediaType, response.Content.Headers.ContentType.MediaType); + var result = await response.Content.ReadAsAsync(); + Console.WriteLine(result); + } + } + + [TestMethod] + async public Task ContentType_Json() + { + await ContentType_Test(new[] { "application/json" }, "application/json"); + } + + [TestMethod] + async public Task ContentType_Xml() + { + await ContentType_Test(new[] { "application/xml" }, "application/json"); + } + + [TestMethod] + async public Task ContentType_Html() + { + await ContentType_Test(new[] { "text/html" }, "application/json"); + } + + [TestMethod] + async public Task ContentType_Chrome() + { + await ContentType_Test(new[] { "text/html", "application/xml" }, "application/json"); + } + } +} diff --git a/RandomData2/UnitTest/Client/HttpHelper.cs b/RandomData2/UnitTest/Client/HttpHelper.cs new file mode 100644 index 0000000..710d49f --- /dev/null +++ b/RandomData2/UnitTest/Client/HttpHelper.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace UnitTest.Client +{ + public static class HttpHelper + { + public static readonly Uri BaseUri = new Uri("http://localhost:57844/"); + + async public static Task GetAsync(string uri) + { + using (var http = new HttpClient { BaseAddress = BaseUri }) + { + var response = await http.GetAsync(uri); + response.EnsureSuccessStatusCode(); + return await response.Content.ReadAsAsync(); + } + } + + public static Task GetAsync(string uri, object query) => + GetAsync(AddQuery(uri, query)); + + async public static Task GetAsync_StatusCode(string uri) + { + using (var http = new HttpClient { BaseAddress = BaseUri }) + { + var response = await http.GetAsync(uri); + return response.StatusCode; + } + } + + public static string AddQuery(string uri, object query) => $"{uri}?{query.ToFormUrlEncoded()}"; + + public static string ToFormUrlEncoded(this object value) + { + using (var content = new FormUrlEncodedContent(value.EnumerateProperties())) + return content.ReadAsStringAsync().GetAwaiter().GetResult(); + } + + public static IEnumerable> EnumerateProperties(this object value) => + TypeDescriptor.GetProperties(value) + .Cast() + .Select(d => new KeyValuePair(d.Name, d.GetValue(value)?.ToString())); + } +} diff --git a/RandomData2/UnitTest/Client/RandomTest.cs b/RandomData2/UnitTest/Client/RandomTest.cs new file mode 100644 index 0000000..4218b7c --- /dev/null +++ b/RandomData2/UnitTest/Client/RandomTest.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Blaze.Randomization; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; + +namespace UnitTest.Client +{ + [TestClass] + public class RandomTest + { + static readonly Dictionary StatusCodes_4096 = new Dictionary + { + { -1, HttpStatusCode.NotFound }, + { 0, HttpStatusCode.OK }, + { 4096, HttpStatusCode.OK }, + { 4097, HttpStatusCode.NotFound }, + }; + + [TestMethod] + async public Task NewAlphabets_Bound() + { + foreach (var item in StatusCodes_4096) + Assert.AreEqual(item.Value, await HttpHelper.GetAsync_StatusCode($"api/NewAlphabets/{item.Key}")); + } + + [TestMethod] + async public Task NewAlphanumerics_Bound() + { + foreach (var item in StatusCodes_4096) + Assert.AreEqual(item.Value, await HttpHelper.GetAsync_StatusCode($"api/NewAlphanumerics/{item.Key}")); + } + + [TestMethod] + async public Task NewBytes_HexLower_Bound() + { + foreach (var item in StatusCodes_4096) + Assert.AreEqual(item.Value, await HttpHelper.GetAsync_StatusCode($"api/NewBytes/hexlower/{item.Key}")); + } + + [TestMethod] + async public Task NewBytes_HexUpper_Bound() + { + foreach (var item in StatusCodes_4096) + Assert.AreEqual(item.Value, await HttpHelper.GetAsync_StatusCode($"api/NewBytes/hexupper/{item.Key}")); + } + + [TestMethod] + async public Task NewBytes_Base64_Bound() + { + foreach (var item in StatusCodes_4096) + Assert.AreEqual(item.Value, await HttpHelper.GetAsync_StatusCode($"api/NewBytes/base64/{item.Key}")); + } + + static readonly int[] Lengths_4096 = Enumerable.Range(0, 50).Concat(Enumerable.Range(4097 - 10, 10)).ToArray(); + + [TestMethod] + async public Task NewAlphabets() + { + foreach (var length in Lengths_4096) + { + var result = await HttpHelper.GetAsync($"api/NewAlphabets/{length}"); + Assert.AreEqual(length, result.Length); + Console.WriteLine(result); + } + } + + [TestMethod] + async public Task NewAlphanumerics() + { + foreach (var length in Lengths_4096) + { + var result = await HttpHelper.GetAsync($"api/NewAlphanumerics/{length}"); + Assert.AreEqual(length, result.Length); + Console.WriteLine(result); + } + } + + [TestMethod] + async public Task NewBytes_HexLower() + { + foreach (var length in Lengths_4096) + { + var result = await HttpHelper.GetAsync($"api/NewBytes/hexlower/{length}"); + Assert.AreEqual(2 * length, result.Length); + Console.WriteLine(result); + } + } + + [TestMethod] + async public Task NewBytes_HexUpper() + { + foreach (var length in Lengths_4096) + { + var result = await HttpHelper.GetAsync($"api/NewBytes/hexupper/{length}"); + Assert.AreEqual(2 * length, result.Length); + Console.WriteLine(result); + } + } + + [TestMethod] + async public Task NewBytes_Base64() + { + foreach (var length in Lengths_4096) + { + var result = await HttpHelper.GetAsync($"api/NewBytes/base64/{length}"); + var bytes = Convert.FromBase64String(result); + Assert.AreEqual(length, bytes.Length); + Console.WriteLine(result); + } + } + + [TestMethod] + async public Task NewUuid() + { + for (int i = 0; i < 100; i++) + { + var result = await HttpHelper.GetAsync("api/NewUuid"); + Console.WriteLine(result); + } + } + + [TestMethod] + async public Task NewOrderedId() + { + for (int i = 0; i < 100; i++) + { + var result = await HttpHelper.GetAsync("api/NewOrderedId"); + Console.WriteLine(JsonConvert.SerializeObject(result)); + } + } + + [TestMethod] + async public Task NewOrderedId_SqlServer() + { + for (int i = 0; i < 100; i++) + { + var result = await HttpHelper.GetAsync("api/NewOrderedId/sqlserver"); + Console.WriteLine(JsonConvert.SerializeObject(result)); + } + } + + [TestMethod] + async public Task NewOrderedId2() + { + for (int i = 0; i < 100; i++) + { + var result = await HttpHelper.GetAsync("api/NewOrderedId2"); + Console.WriteLine(JsonConvert.SerializeObject(result)); + } + } + + [TestMethod] + async public Task NewOrderedId2_SqlServer() + { + for (int i = 0; i < 100; i++) + { + var result = await HttpHelper.GetAsync("api/NewOrderedId2/sqlserver"); + Console.WriteLine(JsonConvert.SerializeObject(result)); + } + } + } +} diff --git a/RandomData2/UnitTest/Database/RandomTestDb.cs b/RandomData2/UnitTest/Database/RandomTestDb.cs new file mode 100644 index 0000000..668c993 --- /dev/null +++ b/RandomData2/UnitTest/Database/RandomTestDb.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Data.Entity; +using System.Linq; + +namespace UnitTest.Database +{ + public class RandomTestDb : DbContext + { + public DbSet Categories { get; set; } + public DbSet Products { get; set; } + } + + public class Category + { + [MaxLength(36)] + public string Id { get; set; } + public DateTime Created { get; set; } + [Required] + [MaxLength(20)] + public string Name { get; set; } + } + + public class Product + { + public Guid Id { get; set; } + public DateTime Created { get; set; } + [Required] + [MaxLength(20)] + public string Name { get; set; } + } +} diff --git a/RandomData2/UnitTest/Database/SqlServerTest.cs b/RandomData2/UnitTest/Database/SqlServerTest.cs new file mode 100644 index 0000000..55b1fd6 --- /dev/null +++ b/RandomData2/UnitTest/Database/SqlServerTest.cs @@ -0,0 +1,119 @@ +using System; +using System.Linq; +using Blaze.Randomization; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTest.Database +{ + [TestClass] + public class SqlServerTest + { + [TestMethod] + public void Categories() + { + var expected = Enumerable.Range(0, 10) + .Select(i => + { + var id = RandomData.GenerateOrderedGuid2(); + var name = RandomData.GenerateAlphanumerics(20); + System.Threading.Thread.Sleep(1); + + return new Category + { + Id = id.Guid.ToString(), + Created = id.DateTime, + Name = name, + }; + }) + .ToArray(); + + using (var db = new RandomTestDb()) + { + db.Categories.AddRange(expected); + db.SaveChanges(); + } + + Category[] actual; + using (var db = new RandomTestDb()) + { + var lower = DateTime.UtcNow.AddSeconds(-30); + actual = db.Categories + .Where(p => p.Created.CompareTo(lower) > 0) + .OrderBy(p => p.Id) + .ToArray(); + } + + CollectionAssert.AreEqual(expected.Select(x => x.Name).ToArray(), actual.Select(x => x.Name).ToArray()); + } + + [TestMethod] + public void Products() + { + var expected = Enumerable.Range(0, 10) + .Select(i => + { + var idSql = RandomData.GenerateOrderedSqlGuid2(); + var name = RandomData.GenerateAlphanumerics(20); + System.Threading.Thread.Sleep(1); + + return new Product + { + Id = idSql.Guid, + Created = idSql.DateTime, + Name = name, + }; + }) + .ToArray(); + + using (var db = new RandomTestDb()) + { + db.Products.AddRange(expected); + db.SaveChanges(); + } + + Product[] actual; + using (var db = new RandomTestDb()) + { + var lower = DateTime.UtcNow.AddSeconds(-30); + actual = db.Products + .Where(p => p.Created.CompareTo(lower) > 0) + .OrderBy(p => p.Id) + .ToArray(); + } + + CollectionAssert.AreEqual(expected.Select(x => x.Name).ToArray(), actual.Select(x => x.Name).ToArray()); + } + + static void GetData() + { + using (var db = new RandomTestDb()) + { + var now = DateTime.UtcNow; + var lower = now.AddSeconds(-30).ToSqlGuid2(); + var upper = now.ToSqlGuid2(); + + var products = db.Products + .Where(p => p.Id.CompareTo(lower) > 0) + .Where(p => p.Id.CompareTo(upper) < 0) + .ToArray(); + + Assert.AreEqual(20, products.Length); + foreach (var product in products) + Console.WriteLine(product.Created.ToIso8601String()); + } + } + } + + public static class RandomDataHelper + { + public static Guid ToSqlGuid(this DateTime dateTime) + { + return new Guid(0, 0, 0, dateTime.ToBytesForSqlGuid()); + } + + public static Guid ToSqlGuid2(this DateTime dateTime) + { + return new Guid(new byte[10].Concat(dateTime.ToBytes2()).ToArray()); + } + } +} diff --git a/RandomData2/UnitTest/Properties/AssemblyInfo.cs b/RandomData2/UnitTest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6d6fc21 --- /dev/null +++ b/RandomData2/UnitTest/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("UnitTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("UnitTest")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("43896562-e647-4f9e-8498-8542fca26834")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/RandomData2/UnitTest/UnitTest.csproj b/RandomData2/UnitTest/UnitTest.csproj new file mode 100644 index 0000000..d486636 --- /dev/null +++ b/RandomData2/UnitTest/UnitTest.csproj @@ -0,0 +1,90 @@ + + + + + Debug + AnyCPU + {43896562-E647-4F9E-8498-8542FCA26834} + Library + Properties + UnitTest + UnitTest + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Blaze.1.1.10\lib\net45\Blaze.dll + + + ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + + + ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + + + ..\packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + True + + + + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + + + + + + + + + + + + + + + + + + + このプロジェクトは、このコンピューター上にない NuGet パッケージを参照しています。それらのパッケージをダウンロードするには、[NuGet パッケージの復元] を使用します。詳細については、http://go.microsoft.com/fwlink/?LinkID=322105 を参照してください。見つからないファイルは {0} です。 + + + + + + \ No newline at end of file diff --git a/RandomData2/UnitTest/packages.config b/RandomData2/UnitTest/packages.config new file mode 100644 index 0000000..f721f29 --- /dev/null +++ b/RandomData2/UnitTest/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/RandomGenerator/.nuget/NuGet.Config b/RandomGenerator/.nuget/NuGet.Config deleted file mode 100644 index 6a318ad..0000000 --- a/RandomGenerator/.nuget/NuGet.Config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/RandomGenerator/.nuget/NuGet.exe b/RandomGenerator/.nuget/NuGet.exe deleted file mode 100644 index 9cba6ed..0000000 Binary files a/RandomGenerator/.nuget/NuGet.exe and /dev/null differ diff --git a/RandomGenerator/.nuget/NuGet.targets b/RandomGenerator/.nuget/NuGet.targets deleted file mode 100644 index dec4c34..0000000 --- a/RandomGenerator/.nuget/NuGet.targets +++ /dev/null @@ -1,151 +0,0 @@ - - - - $(MSBuildProjectDirectory)\..\ - - - false - - - false - - - true - - - false - - - - - - - - - - - $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) - - - - - $(SolutionDir).nuget - - - - packages.$(MSBuildProjectName.Replace(' ', '_')).config - - - - - - $(PackagesProjectConfig) - - - - - packages.config - - - - - - - $(NuGetToolsPath)\NuGet.exe - @(PackageSource) - - "$(NuGetExePath)" - mono --runtime=v4.0.30319 $(NuGetExePath) - - $(TargetDir.Trim('\\')) - - -RequireConsent - -NonInteractive - - "$(SolutionDir) " - "$(SolutionDir)" - - - $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) - $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols - - - - RestorePackages; - $(BuildDependsOn); - - - - - $(BuildDependsOn); - BuildPackage; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RandomGenerator/RandomConsole/Program.cs b/RandomGenerator/RandomConsole/Program.cs deleted file mode 100644 index 2e1ccad..0000000 --- a/RandomGenerator/RandomConsole/Program.cs +++ /dev/null @@ -1,104 +0,0 @@ -using RandomLib; -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Data.Entity; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace RandomConsole -{ - static class Program - { - static void Main(string[] args) - { - InsertData(); - GetData(); - } - - static void InsertData() - { - using (var db = new RandomTestDb()) - { - for (int i = 0; i < 20; i++) - { - var id = RandomUtility.GenerateOrderedGuid2(); - var idSql = RandomUtility.GenerateOrderedSqlGuid2(); - var name = RandomUtility.GenerateAlphanumerics(20); - - db.Categories.Add(new Category - { - Id = id.Guid.ToString(), - Created = id.DateTime, - Name = name, - }); - db.Products.Add(new Product - { - Id = idSql.Guid, - Created = id.DateTime, - Name = name, - }); - - //System.Threading.Thread.Sleep(1); - } - - db.SaveChanges(); - } - } - - static void GetData() - { - using (var db = new RandomTestDb()) - { - var lower = new DateTime(2014, 4, 10, 14, 54, 59).ToSqlGuid2(); - var upper = new DateTime(2014, 4, 10, 14, 55, 1).ToSqlGuid2(); - - var products = db.Products - .Where(p => p.Id.CompareTo(lower) > 0) - .Where(p => p.Id.CompareTo(upper) < 0) - .ToArray(); - - foreach (var item in products) - { - Console.WriteLine(item.Created); - } - } - } - - public static Guid ToSqlGuid(this DateTime dateTime) - { - return new Guid(0, 0, 0, dateTime.ToBytesForSqlGuid()); - } - - public static Guid ToSqlGuid2(this DateTime dateTime) - { - return new Guid(new byte[10].Concat(dateTime.ToBytes2()).ToArray()); - } - } - - public class RandomTestDb : DbContext - { - public DbSet Categories { get; set; } - public DbSet Products { get; set; } - } - - public class Category - { - [MaxLength(36)] - public string Id { get; set; } - public DateTime Created { get; set; } - [Required] - [MaxLength(20)] - public string Name { get; set; } - } - - public class Product - { - public Guid Id { get; set; } - public DateTime Created { get; set; } - [Required] - [MaxLength(20)] - public string Name { get; set; } - } -} diff --git a/RandomGenerator/RandomConsole/Properties/AssemblyInfo.cs b/RandomGenerator/RandomConsole/Properties/AssemblyInfo.cs deleted file mode 100644 index b6fb2a4..0000000 --- a/RandomGenerator/RandomConsole/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 -// アセンブリに関連付けられている情報を変更するには、 -// これらの属性値を変更してください。 -[assembly: AssemblyTitle("RandomConsole")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("RandomConsole")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから -// 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、 -// その型の ComVisible 属性を true に設定してください。 -[assembly: ComVisible(false)] - -// 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です -[assembly: Guid("e6e528df-5df6-469b-9ccc-980c5228f25c")] - -// アセンブリのバージョン情報は、以下の 4 つの値で構成されています: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を -// 既定値にすることができます: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/RandomGenerator/RandomConsole/RandomConsole.csproj b/RandomGenerator/RandomConsole/RandomConsole.csproj deleted file mode 100644 index a5746da..0000000 --- a/RandomGenerator/RandomConsole/RandomConsole.csproj +++ /dev/null @@ -1,85 +0,0 @@ - - - - - Debug - AnyCPU - {5C9CE8D8-F9F0-4F5C-9B0B-85AB4B8E0EB6} - Exe - Properties - RandomConsole - RandomConsole - v4.5 - 512 - SAK - SAK - SAK - SAK - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.dll - - - ..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.SqlServer.dll - - - - - - - - - - - - - - - - - - - - - {c5ba44d2-4dd0-47d2-9d08-ce2c2783a95f} - RandomLib - - - - - - - このプロジェクトは、このコンピューターにはない NuGet パッケージを参照しています。これらをダウンロードするには、NuGet パッケージの復元を有効にしてください。詳細については、http://go.microsoft.com/fwlink/?LinkID=322105 を参照してください。不足しているファイルは {0} です。 - - - - - \ No newline at end of file diff --git a/RandomGenerator/RandomConsole/packages.config b/RandomGenerator/RandomConsole/packages.config deleted file mode 100644 index 0c5b0e9..0000000 --- a/RandomGenerator/RandomConsole/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/RandomGenerator/RandomGenerator.sln b/RandomGenerator/RandomGenerator.sln deleted file mode 100644 index 05d5c20..0000000 --- a/RandomGenerator/RandomGenerator.sln +++ /dev/null @@ -1,39 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{DA40575B-92CA-4DD2-A61C-FE1DFBC53CB0}" - ProjectSection(SolutionItems) = preProject - .nuget\NuGet.Config = .nuget\NuGet.Config - .nuget\NuGet.exe = .nuget\NuGet.exe - .nuget\NuGet.targets = .nuget\NuGet.targets - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RandomWebApp", "RandomWebApp\RandomWebApp.csproj", "{66B8665F-4A81-4DFB-AEAE-7E74B908A30E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RandomLib", "RandomLib\RandomLib.csproj", "{C5BA44D2-4DD0-47D2-9D08-CE2C2783A95F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RandomConsole", "RandomConsole\RandomConsole.csproj", "{5C9CE8D8-F9F0-4F5C-9B0B-85AB4B8E0EB6}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {66B8665F-4A81-4DFB-AEAE-7E74B908A30E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {66B8665F-4A81-4DFB-AEAE-7E74B908A30E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {66B8665F-4A81-4DFB-AEAE-7E74B908A30E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {66B8665F-4A81-4DFB-AEAE-7E74B908A30E}.Release|Any CPU.Build.0 = Release|Any CPU - {C5BA44D2-4DD0-47D2-9D08-CE2C2783A95F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C5BA44D2-4DD0-47D2-9D08-CE2C2783A95F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C5BA44D2-4DD0-47D2-9D08-CE2C2783A95F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C5BA44D2-4DD0-47D2-9D08-CE2C2783A95F}.Release|Any CPU.Build.0 = Release|Any CPU - {5C9CE8D8-F9F0-4F5C-9B0B-85AB4B8E0EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5C9CE8D8-F9F0-4F5C-9B0B-85AB4B8E0EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5C9CE8D8-F9F0-4F5C-9B0B-85AB4B8E0EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5C9CE8D8-F9F0-4F5C-9B0B-85AB4B8E0EB6}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/RandomGenerator/RandomLib/Properties/AssemblyInfo.cs b/RandomGenerator/RandomLib/Properties/AssemblyInfo.cs deleted file mode 100644 index 23631ae..0000000 --- a/RandomGenerator/RandomLib/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 -// アセンブリに関連付けられている情報を変更するには、 -// これらの属性値を変更してください。 -[assembly: AssemblyTitle("RandomLib")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("RandomLib")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから -// 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、 -// その型の ComVisible 属性を true に設定してください。 -[assembly: ComVisible(false)] - -// 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です -[assembly: Guid("deba08fb-d23a-4719-9985-ee6028b018c3")] - -// アセンブリのバージョン情報は、以下の 4 つの値で構成されています: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を -// 既定値にすることができます: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/RandomGenerator/RandomLib/RandomLib.csproj b/RandomGenerator/RandomLib/RandomLib.csproj deleted file mode 100644 index 8ec86f1..0000000 --- a/RandomGenerator/RandomLib/RandomLib.csproj +++ /dev/null @@ -1,57 +0,0 @@ - - - - - Debug - AnyCPU - {C5BA44D2-4DD0-47D2-9D08-CE2C2783A95F} - Library - Properties - RandomLib - RandomLib - v4.5 - 512 - SAK - SAK - SAK - SAK - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/RandomGenerator/RandomLib/RandomUtility.cs b/RandomGenerator/RandomLib/RandomUtility.cs deleted file mode 100644 index 9a7a516..0000000 --- a/RandomGenerator/RandomLib/RandomUtility.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Security.Cryptography; - -namespace RandomLib -{ - public static class RandomUtility - { - const string Alphabets = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - const string Alphanumerics = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - const string Symbols = @" !""#$%&'()*+,-./:;<=>?@[\]^_`{|}~"; - const string NonControlChars = @" !""#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; - - static readonly Random random = new Random(); - static readonly RandomNumberGenerator generator; - - static RandomUtility() - { - generator = RandomNumberGenerator.Create(); - AppDomain.CurrentDomain.ProcessExit += (o, e) => - { - generator.Dispose(); - }; - } - - public static string GenerateAlphabets(int length) - { - return new string( - Enumerable.Repeat(false, length) - .Select(_ => Alphabets[random.Next(Alphabets.Length)]) - .ToArray()); - } - - public static string GenerateAlphanumerics(int length) - { - return new string( - Enumerable.Repeat(false, length) - .Select(_ => Alphanumerics[random.Next(Alphanumerics.Length)]) - .ToArray()); - } - - public static byte[] GenerateBytes(int length) - { - if (length < 0) throw new ArgumentOutOfRangeException("length", length, "The value must be non-negative."); - - var data = new byte[length]; - generator.GetBytes(data); - return data; - } - - public static GuidWithDateTime GenerateOrderedGuid() - { - var dt = DateTime.UtcNow; - var ticks = dt.Ticks; - - var a = (int)(ticks >> 32); - var b = (short)(ticks >> 16); - var c = (short)ticks; - var d = GenerateBytes(8); - - return new GuidWithDateTime - { - Guid = new Guid(a, b, c, d), - DateTime = dt, - }; - } - - public static GuidWithDateTime GenerateOrderedGuid2() - { - var dt = DateTime.UtcNow; - var ticks = dt.Ticks; - - var a = (int)(ticks >> 28); - var b = (short)(ticks >> 12); - var r = GenerateBytes(2); - var c = (short)((r[0] << 8) | r[1]); - var d = GenerateBytes(8); - - return new GuidWithDateTime - { - Guid = new Guid(a, b, c, d), - DateTime = dt, - }; - } - - public static GuidWithDateTime GenerateOrderedSqlGuid() - { - var dt = DateTime.UtcNow; - - var a = GenerateBytes(8); - var b = dt.ToBytesForSqlGuid(); - - return new GuidWithDateTime - { - Guid = new Guid(a.Concat(b).ToArray()), - DateTime = dt, - }; - } - - public static GuidWithDateTime GenerateOrderedSqlGuid2() - { - var dt = DateTime.UtcNow; - - var a = GenerateBytes(10); - var b = dt.ToBytes2(); - - return new GuidWithDateTime - { - Guid = new Guid(a.Concat(b).ToArray()), - DateTime = dt, - }; - } - - public static string ToHexString(this byte[] data, bool uppercase) - { - if (data == null) throw new ArgumentNullException("data"); - - var format = uppercase ? "X2" : "x2"; - return data - .Select(b => b.ToString(format)) - .ConcatStrings(); - } - - static string ConcatStrings(this IEnumerable source) - { - return string.Concat(source); - } - - public static string ToIso8601String(this DateTime dateTime) - { - return dateTime.ToString("O", CultureInfo.InvariantCulture); - } - - public static byte[] ToBytes(this DateTime dateTime) - { - var ticks = dateTime.Ticks; - - var b = new byte[8]; - b[0] = (byte)(ticks >> 56); - b[1] = (byte)(ticks >> 48); - b[2] = (byte)(ticks >> 40); - b[3] = (byte)(ticks >> 32); - b[4] = (byte)(ticks >> 24); - b[5] = (byte)(ticks >> 16); - b[6] = (byte)(ticks >> 8); - b[7] = (byte)ticks; - return b; - } - - public static byte[] ToBytesForSqlGuid(this DateTime dateTime) - { - var ticks = dateTime.Ticks; - - var b = new byte[8]; - b[0] = (byte)(ticks >> 8); - b[1] = (byte)ticks; - b[2] = (byte)(ticks >> 56); - b[3] = (byte)(ticks >> 48); - b[4] = (byte)(ticks >> 40); - b[5] = (byte)(ticks >> 32); - b[6] = (byte)(ticks >> 24); - b[7] = (byte)(ticks >> 16); - return b; - } - - public static byte[] ToBytes2(this DateTime dateTime) - { - var ticks = dateTime.Ticks; - - var b = new byte[6]; - b[0] = (byte)(ticks >> 52); - b[1] = (byte)(ticks >> 44); - b[2] = (byte)(ticks >> 36); - b[3] = (byte)(ticks >> 28); - b[4] = (byte)(ticks >> 20); - b[5] = (byte)(ticks >> 12); - return b; - } - } - - [DebuggerDisplay("{Guid}")] - public struct GuidWithDateTime - { - public Guid Guid; - public DateTime DateTime; - } -} diff --git a/RandomGenerator/RandomWebApp/App_Start/FilterConfig.cs b/RandomGenerator/RandomWebApp/App_Start/FilterConfig.cs deleted file mode 100644 index 1e94a09..0000000 --- a/RandomGenerator/RandomWebApp/App_Start/FilterConfig.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Configuration; -using System.Text.RegularExpressions; -using System.Web; -using System.Web.Mvc; - -namespace RandomWebApp -{ - public class FilterConfig - { - public static void RegisterGlobalFilters(GlobalFilterCollection filters) - { - filters.Add(new HandleErrorAttribute()); - } - - public static void ValidateUrlScheme(HttpApplication app) - { - if (!app.Request.IsSecureConnection && - RequireHttps && - !string.Equals(app.Request.Url.Host, "localhost", StringComparison.InvariantCultureIgnoreCase)) - { - // Uri.OriginalString プロパティを使用すると、:80 が追加されてしまうことがあります。 - var secureUrl = Regex.Replace(app.Request.Url.AbsoluteUri, @"^\w+(?=://)", Uri.UriSchemeHttps); - - if (PermanentHttps) - { - app.Response.RedirectPermanent(secureUrl, true); - } - else - { - app.Response.Redirect(secureUrl, true); - } - } - } - - static bool RequireHttps - { - get { return Convert.ToBoolean(ConfigurationManager.AppSettings["app:RequireHttps"]); } - } - - static bool PermanentHttps - { - get { return Convert.ToBoolean(ConfigurationManager.AppSettings["app:PermanentHttps"]); } - } - } -} \ No newline at end of file diff --git a/RandomGenerator/RandomWebApp/App_Start/RouteConfig.cs b/RandomGenerator/RandomWebApp/App_Start/RouteConfig.cs deleted file mode 100644 index 5fe0202..0000000 --- a/RandomGenerator/RandomWebApp/App_Start/RouteConfig.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; - -namespace RandomWebApp -{ - public class RouteConfig - { - public static void RegisterRoutes(RouteCollection routes) - { - routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); - - routes.MapMvcAttributeRoutes(); - - routes.MapRoute( - name: "Default", - url: "{controller}/{action}/{id}", - defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } - ); - } - } -} \ No newline at end of file diff --git a/RandomGenerator/RandomWebApp/Controllers/HomeController.cs b/RandomGenerator/RandomWebApp/Controllers/HomeController.cs deleted file mode 100644 index 5de7f5f..0000000 --- a/RandomGenerator/RandomWebApp/Controllers/HomeController.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; - -namespace RandomWebApp.Controllers -{ - public class HomeController : Controller - { - public ActionResult Index() - { - return View(); - } - - public ActionResult JsonTest() - { - return View(); - } - - public ActionResult JsonpTest() - { - return View(); - } - } -} diff --git a/RandomGenerator/RandomWebApp/Controllers/RandomController.cs b/RandomGenerator/RandomWebApp/Controllers/RandomController.cs deleted file mode 100644 index 3726363..0000000 --- a/RandomGenerator/RandomWebApp/Controllers/RandomController.cs +++ /dev/null @@ -1,95 +0,0 @@ -using RandomLib; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; - -namespace RandomWebApp.Controllers -{ - // Attribute Routing in ASP.NET MVC 5 - // http://blogs.msdn.com/b/webdev/archive/2013/10/17/attribute-routing-in-asp-net-mvc-5.aspx - [RoutePrefix("json")] - [Route("{action=Index}")] - public class RandomController : JsonApiController - { - public ActionResult Index() - { - return RedirectToAction("", ""); - } - - // GET: /json/NewAlphabets/8 - [Route("NewAlphabets/{length:int:range(0,4096)}")] - public ActionResult NewAlphabets(int length) - { - return Json(RandomUtility.GenerateAlphabets(length)); - } - - // GET: /json/NewAlphanumerics/8 - [Route("NewAlphanumerics/{length:int:range(0,4096)}")] - public ActionResult NewAlphanumerics(int length) - { - return Json(RandomUtility.GenerateAlphanumerics(length)); - } - - // GET: /json/NewBytes/hexlower/16 - [Route("NewBytes/hexlower/{length:int:range(0,4096)}")] - public ActionResult NewBytes_HexLower(int length) - { - var data = RandomUtility.GenerateBytes(length); - return Json(data.ToHexString(false)); - } - - // GET: /json/NewBytes/hexupper/16 - [Route("NewBytes/hexupper/{length:int:range(0,4096)}")] - public ActionResult NewBytes_HexUpper(int length) - { - var data = RandomUtility.GenerateBytes(length); - return Json(data.ToHexString(true)); - } - - // GET: /json/NewBytes/base64/16 - [Route("NewBytes/base64/{length:int:range(0,4096)}")] - public ActionResult NewBytes_Base64(int length) - { - var data = RandomUtility.GenerateBytes(length); - return Json(Convert.ToBase64String(data)); - } - - // GET: /json/NewUuid - public ActionResult NewUuid() - { - return Json(Guid.NewGuid()); - } - - // GET: /json/NewOrderedId - public ActionResult NewOrderedId() - { - var id = RandomUtility.GenerateOrderedGuid(); - return Json(new { id = id.Guid, date = id.DateTime.ToIso8601String() }); - } - - // GET: /json/NewOrderedId/sqlserver - [Route("NewOrderedId/sqlserver")] - public ActionResult NewOrderedId_SqlServer() - { - var id = RandomUtility.GenerateOrderedSqlGuid(); - return Json(new { id = id.Guid, date = id.DateTime.ToIso8601String() }); - } - - // GET: /json/NewOrderedId2 - public ActionResult NewOrderedId2() - { - var id = RandomUtility.GenerateOrderedGuid2(); - return Json(new { id = id.Guid, date = id.DateTime.ToIso8601String() }); - } - - // GET: /json/NewOrderedId2/sqlserver - [Route("NewOrderedId2/sqlserver")] - public ActionResult NewOrderedId2_SqlServer() - { - var id = RandomUtility.GenerateOrderedSqlGuid2(); - return Json(new { id = id.Guid, date = id.DateTime.ToIso8601String() }); - } - } -} diff --git a/RandomGenerator/RandomWebApp/Global.asax b/RandomGenerator/RandomWebApp/Global.asax deleted file mode 100644 index 8bf2eb6..0000000 --- a/RandomGenerator/RandomWebApp/Global.asax +++ /dev/null @@ -1 +0,0 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="RandomWebApp.MvcApplication" Language="C#" %> diff --git a/RandomGenerator/RandomWebApp/Global.asax.cs b/RandomGenerator/RandomWebApp/Global.asax.cs deleted file mode 100644 index 33089e1..0000000 --- a/RandomGenerator/RandomWebApp/Global.asax.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Http; -using System.Web.Mvc; -using System.Web.Routing; - -namespace RandomWebApp -{ - // メモ: IIS6 または IIS7 のクラシック モードの詳細については、 - // http://go.microsoft.com/?LinkId=9394801 を参照してください - public class MvcApplication : System.Web.HttpApplication - { - protected void Application_Start() - { - AreaRegistration.RegisterAllAreas(); - - WebApiConfig.Register(GlobalConfiguration.Configuration); - FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); - RouteConfig.RegisterRoutes(RouteTable.Routes); - } - - void Application_BeginRequest(object sender, EventArgs e) - { - FilterConfig.ValidateUrlScheme(this); - } - } -} \ No newline at end of file diff --git a/RandomGenerator/RandomWebApp/JsonApiController.cs b/RandomGenerator/RandomWebApp/JsonApiController.cs deleted file mode 100644 index 2deffb2..0000000 --- a/RandomGenerator/RandomWebApp/JsonApiController.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; -using System.Web.Script.Serialization; - -namespace RandomWebApp -{ - public class JsonApiController : Controller - { - protected new ActionResult Json(object data) - { - Response.Headers["Expires"] = "-1"; - Response.Headers["Access-Control-Allow-Origin"] = "*"; - - var callback = Request.QueryString["callback"]; - - if (string.IsNullOrWhiteSpace(callback)) - { - return Json(data, JsonRequestBehavior.AllowGet); - } - else - { - var serializer = new JavaScriptSerializer(); - var json = serializer.Serialize(data); - return JavaScript(string.Format("{0}({1});", callback, json)); - } - } - } -} diff --git a/RandomGenerator/RandomWebApp/RandomWebApp.csproj b/RandomGenerator/RandomWebApp/RandomWebApp.csproj deleted file mode 100644 index dce39f5..0000000 --- a/RandomGenerator/RandomWebApp/RandomWebApp.csproj +++ /dev/null @@ -1,210 +0,0 @@ - - - - - Debug - AnyCPU - - - 2.0 - {66B8665F-4A81-4DFB-AEAE-7E74B908A30E} - {E3E379DF-F4C6-4180-9B81-6769533ABE47};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - RandomWebApp - RandomWebApp - v4.5 - false - true - - - - - ..\ - true - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - - False - ..\packages\Newtonsoft.Json.6.0.2\lib\net45\Newtonsoft.Json.dll - - - - - - ..\packages\Microsoft.Net.Http.2.2.19\lib\net45\System.Net.Http.Extensions.dll - - - False - ..\packages\Microsoft.AspNet.WebApi.Client.5.1.2\lib\net45\System.Net.Http.Formatting.dll - - - ..\packages\Microsoft.Net.Http.2.2.19\lib\net45\System.Net.Http.Primitives.dll - - - - - - - - - False - ..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.Helpers.dll - - - False - ..\packages\Microsoft.AspNet.WebApi.Core.5.1.2\lib\net45\System.Web.Http.dll - - - False - ..\packages\Microsoft.AspNet.WebApi.WebHost.5.1.2\lib\net45\System.Web.Http.WebHost.dll - - - False - ..\packages\Microsoft.AspNet.Mvc.5.1.2\lib\net45\System.Web.Mvc.dll - - - False - ..\packages\Microsoft.AspNet.Razor.3.1.2\lib\net45\System.Web.Razor.dll - - - False - ..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.dll - - - False - ..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.Deployment.dll - - - False - ..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.Razor.dll - - - - - - - - - - - - True - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - - - - - - - - - - Global.asax - - - - - - - - - - - - - Web.config - - - Web.config - - - - - - - - - - - - - - - - {c5ba44d2-4dd0-47d2-9d08-ce2c2783a95f} - RandomLib - - - - - - - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - - - - True - True - 0 - / - http://localhost:43906/ - False - False - - - False - - - - - - - - このプロジェクトは、このコンピューターにはない NuGet パッケージを参照しています。これらをダウンロードするには、NuGet パッケージの復元を有効にしてください。詳細については、http://go.microsoft.com/fwlink/?LinkID=322105 を参照してください。不足しているファイルは {0} です。 - - - - - - - - - - \ No newline at end of file diff --git a/RandomGenerator/RandomWebApp/Views/Home/Index.cshtml b/RandomGenerator/RandomWebApp/Views/Home/Index.cshtml deleted file mode 100644 index e8a5e94..0000000 --- a/RandomGenerator/RandomWebApp/Views/Home/Index.cshtml +++ /dev/null @@ -1,153 +0,0 @@ -@using RandomWebApp -@{ - Layout = null; - ViewBag.Title = "Random Data Web API"; -} - - - - - - - @ViewBag.Title - - -
-

@ViewBag.Title

-

Provides the JSON/JSONP API to generate random data.

-

Interfaces

-
-
NewAlphabets
-
-

Creates a new alphabets with the specified length.

-
    -
  • - @Html.TextLink("/json/NewAlphabets/8") -

    - e.g. -

    @("\"jVNXbgEA\"")
    -

    -
  • -
-
-
NewAlphanumerics
-
-

Creates a new alphanumerics with the specified length.

-
    -
  • - @Html.TextLink("/json/NewAlphanumerics/8") -

    - e.g. -

    @("\"RzZbOj7s\"")
    -

    -
  • -
-
-
NewBytes
-
-

Creates a new byte sequence with the specified length.

-
    -
  • - @Html.TextLink("/json/NewBytes/hexlower/16") -

    - The lowercase hexadecimal format.
    - e.g. -

    @("\"9a605681933a5d3c66cb65dd052331ef\"")
    -

    -
  • -
  • - @Html.TextLink("/json/NewBytes/hexupper/16") -

    - The uppercase hexadecimal format.
    - e.g. -

    @("\"9A605681933A5D3C66CB65DD052331EF\"")
    -

    -
  • -
  • - @Html.TextLink("/json/NewBytes/base64/16") -

    - The Base64 format.
    - e.g. -

    @("\"I9K7dQKQkUWx+aCavrFilQ==\"")
    -

    -
  • -
-
-
NewUuid
-
-

Creates a new UUID (GUID).

-
    -
  • - @Html.TextLink("/json/NewUuid") -

    - A version 4 UUID.
    - e.g. -

    @("\"b018469a-98a7-4d51-9283-bf9efdea27be\"")
    -

    -
  • -
-
-
NewOrderedId
-
-

- Creates a new time-ordered 16-byte ID with the UUID format.
- 8 bytes of them represent ticks of date/time (by 10-7 second) and the other 8 bytes are randomly generated.
- Returns a pair of the ID and the created date/time. -

-
    -
  • - @Html.TextLink("/json/NewOrderedId") -

    - The upper 8 bytes represent ticks of date/time.
    - e.g. -

    @("{\"id\":\"08d11c74-45cf-762f-e0f5-d5010081a7b1\",\"date\":\"2014-04-02T10:20:14.8958767Z\"}")
    -

    -
  • -
  • - @Html.TextLink("/json/NewOrderedId/sqlserver") -

    - Orders values in the uniqueidentifier data type in SQL Server.
    - The lower 8 bytes represent ticks of date/time.
    - e.g. -

    @("{\"id\":\"e0f5d501-0081-a7b1-762f-08d11c7445cf\",\"date\":\"2014-04-02T10:20:14.8958767Z\"}")
    -

    -
  • -
-
-
NewOrderedId2
-
-

- Creates a new time-ordered 16-byte ID with the UUID format.
- 6 bytes of them represent ticks of date/time (by about 10-3 second) and the other 10 bytes are randomly generated.
- Returns a pair of the ID and the created date/time. -

-
    -
  • - @Html.TextLink("/json/NewOrderedId2") -

    - The upper 6 bytes represent ticks of date/time.
    - e.g. -

    @("{\"id\":\"8d11c744-5cf7-29b5-e0f5-d5010081a7b1\",\"date\":\"2014-04-02T10:20:14.8958767Z\"}")
    -

    -
  • -
  • - @Html.TextLink("/json/NewOrderedId2/sqlserver") -

    - Orders values in the uniqueidentifier data type in SQL Server.
    - The lower 6 bytes represent ticks of date/time.
    - e.g. -

    @("{\"id\":\"e0f5d501-0081-a7b1-29b5-8d11c7445cf7\",\"date\":\"2014-04-02T10:20:14.8958767Z\"}")
    -

    -
  • -
-
-
-

Test

-

@Html.ActionLink("Test Page", "JsonTest")

-

@Html.ActionLink("Test Page for JSONP", "JsonpTest")

-
-
-

© 2014 Keiho Sakapon.

-
- - diff --git a/RandomGenerator/RandomWebApp/Views/Home/JsonpTest.cshtml b/RandomGenerator/RandomWebApp/Views/Home/JsonpTest.cshtml deleted file mode 100644 index 11ff5e4..0000000 --- a/RandomGenerator/RandomWebApp/Views/Home/JsonpTest.cshtml +++ /dev/null @@ -1,65 +0,0 @@ -@{ - Layout = null; - ViewBag.Title = "Random Data Web API - JSONP Test"; -} - - - - - - - @ViewBag.Title - - - -
-

@ViewBag.Title

-

Domain: @Html.TextBox("TargetDomain", string.Format("{0}://{1}", Request.Url.Scheme, Request.Url.Authority))

-
-
NewUuid
-
-

- -

-
 
-
-
NewOrderedId
-
-

- -

-
 
-
-
NewOrderedId2
-
-

- -

-
 
-
-
-
-
-

© 2014 Keiho Sakapon.

-
- - - diff --git a/RandomGenerator/RandomWebApp/Views/Web.config b/RandomGenerator/RandomWebApp/Views/Web.config deleted file mode 100644 index 5767aa7..0000000 --- a/RandomGenerator/RandomWebApp/Views/Web.config +++ /dev/null @@ -1,58 +0,0 @@ - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RandomGenerator/RandomWebApp/Web.config b/RandomGenerator/RandomWebApp/Web.config deleted file mode 100644 index 48e0cad..0000000 --- a/RandomGenerator/RandomWebApp/Web.config +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RandomGenerator/RandomWebApp/packages.config b/RandomGenerator/RandomWebApp/packages.config deleted file mode 100644 index f522a22..0000000 --- a/RandomGenerator/RandomWebApp/packages.config +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/AppSettings.md b/docs/AppSettings.md new file mode 100644 index 0000000..59284b3 --- /dev/null +++ b/docs/AppSettings.md @@ -0,0 +1,26 @@ +## アプリケーション設定 + +### Web.config +Web.config には、 次のようなアプリケーション設定があります。 + +```xml + + + + + + +``` + +それぞれの説明は次の通りです。 +- `app:RequireHttps` + - HTTPS を必須とする場合は `true` を指定します。 + - HTTP でアクセスすると、HTTPS の URL にリダイレクトされます。 +- `app:PermanentHttps` + - リダイレクト時に HTTPS を永続化する場合は `true` を指定します。 + - すなわち、HTTP ステータス コードは `true` のとき 301、`false` のとき 302 です。 + +### Azure Web App +Azure Web App では、[アプリケーション設定] で値を設定します。 + +![](images/AppSettings-Azure.png) diff --git a/docs/Deployment.md b/docs/Deployment.md new file mode 100644 index 0000000..5cd22e6 --- /dev/null +++ b/docs/Deployment.md @@ -0,0 +1,29 @@ +## デプロイ + +### Azure Web App にデプロイする手順 +GitHub にサインインして、このリポジトリを fork します。 +(Azure Web App と連携させるには、自身で所有しているリポジトリでなければならないためです。) + +![](images/Deployment-1.jpg) + +次に、Azure で Web App を作成します。 + +![](images/Deployment-2.jpg) + +Web App の作成が完了したら、[デプロイ オプション] を構成します。 +ソースとして GitHub を選択すると、アカウント承認の画面が現れます。さらにリポジトリとブランチを選択します。 + +![](images/Deployment-3.jpg) + +必要な設定はこれだけです。設定完了と同時に、ビルドおよびデプロイが開始されます。 +デプロイが完了すれば Web API が利用可能となります。 + +![](images/Deployment-4.png) + +継続的デプロイが構成されるため、これ以降も fork したリポジトリを更新すれば、自動的にビルドおよびデプロイが実行されます。 + +### アプリケーション設定 +- [アプリケーション設定](AppSettings.md) + +### 参照 +- [Azure と GitHub で継続的デプロイ (2017)](https://sakapon.wordpress.com/2017/12/30/azure-github-2017/) diff --git a/docs/Hosting.md b/docs/Hosting.md new file mode 100644 index 0000000..1c49e39 --- /dev/null +++ b/docs/Hosting.md @@ -0,0 +1,12 @@ +## ホスティング + +インターネットで公開されている一般的な Web API は、それを利用する開発者にとっては便利ですが、無償・有償を問わず永久に提供されるとは限りません。 +他者の提供するサービスは終了することがあるため、なるべく自身のアプリをそれに依存させず、自身でサービスを運用することが望ましいでしょう。 + +そこでこの Web API では、ソースコードをオープンソース ライセンスのもとで提供し、それを利用する開発者自身がサービスをホストすることを想定します。 +例えば Azure Web App などの PaaS を利用すれば GitHub から直接ビルドおよびデプロイができるため、簡単な手順でサービスの運用を開始させることができます。 + +- [Azure Web App にデプロイする手順](Deployment.md) + +なお、[randomdata.azurewebsites.net](https://randomdata.azurewebsites.net/) はカタログとして提供しているサイトです。 +保証・サポートはありません。 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..48d6583 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,31 @@ +# Random Data Web API +ランダムなデータを生成するための JSON Web API を提供します。 +この Web API は CORS (Cross-Origin Resource Sharing) をサポートしています。 + +PaaS を利用して、この Web API を簡単にホストすることができます。 +例えば、このリポジトリを fork すれば、Microsoft Azure Portal を利用して Web API を Azure Web App に直接デプロイできます。 +またこの場合は継続的デプロイが構成され、fork したリポジトリが更新されれば Azure Web App も自動的に更新されます。 + +- [ホスティングについて](Hosting.md) +- [Azure Web App にデプロイする手順](Deployment.md) +- [アプリケーション設定](AppSettings.md) + +## ランダム データの種類 +- アルファベット +- アルファベットと数字 +- バイト列 +- UUID (GUID) +- 時刻順の ID + +## Web アプリケーション +このプロジェクトは実際には ASP.NET Web アプリケーションであり、以下が含まれています。 +- Web API +- 仕様が記述されたヘルプページ +- jQuery を利用したテストページ + +[randomdata.azurewebsites.net](https://randomdata.azurewebsites.net/) は配置例です。 + +## ヘルプページの多言語対応 +ブラウザーの翻訳機能で何とかなるでしょう。 + +![](images/Help-Translation.gif) diff --git a/docs/images/AppSettings-Azure.png b/docs/images/AppSettings-Azure.png new file mode 100644 index 0000000..ae68381 Binary files /dev/null and b/docs/images/AppSettings-Azure.png differ diff --git a/docs/images/Deployment-1.jpg b/docs/images/Deployment-1.jpg new file mode 100644 index 0000000..7aebf29 Binary files /dev/null and b/docs/images/Deployment-1.jpg differ diff --git a/docs/images/Deployment-2.jpg b/docs/images/Deployment-2.jpg new file mode 100644 index 0000000..ebe532e Binary files /dev/null and b/docs/images/Deployment-2.jpg differ diff --git a/docs/images/Deployment-3.jpg b/docs/images/Deployment-3.jpg new file mode 100644 index 0000000..ea7a7e9 Binary files /dev/null and b/docs/images/Deployment-3.jpg differ diff --git a/docs/images/Deployment-4.png b/docs/images/Deployment-4.png new file mode 100644 index 0000000..74c6b52 Binary files /dev/null and b/docs/images/Deployment-4.png differ diff --git a/docs/images/Help-Translation.gif b/docs/images/Help-Translation.gif new file mode 100644 index 0000000..c517d62 Binary files /dev/null and b/docs/images/Help-Translation.gif differ