You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

348 lines
15 KiB

namespace WTA.Shared.Extensions;
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using WTA.Shared.Application;
using WTA.Shared.Attributes;
using WTA.Shared.Domain;
public static class JsonSchemaExtensions
{
public static object GetViewModel(this Type modelType)
{
var model = Activator.CreateInstance(modelType);
if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(PaginationModel<,>))
{
if (model != null && model.GetPropertyValue<object, object>("Query") is BaseEntity entity)
{
entity.GetType().GetProperties().ForEach(o =>
{
if (o.PropertyType.IsNullableType())
{
o.SetValue(entity, null);
}
});
}
}
return new
{
Schema = modelType.GetMetadataForType(),
Model = model
};
}
public static object GetMetadataForType(this Type modelType)
{
using var scope = WebApp.Current.Services.CreateScope();
var meta = scope.ServiceProvider.GetRequiredService<IModelMetadataProvider>().GetMetadataForType(modelType);
return meta.GetSchema(scope.ServiceProvider);
}
public static object GetSchema(this ModelMetadata meta, IServiceProvider serviceProvider, ModelMetadata? parent = null)
{
var schema = new Dictionary<string, object>();
var modelType = meta.UnderlyingOrModelType;
var title = meta.ContainerType == null ? modelType.GetDisplayName() : meta.ContainerType?.GetProperty(meta.PropertyName!)?.GetDisplayName() ?? meta.GetDisplayName();
if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(PaginationModel<,>))
{
var genericType = modelType.GetGenericArguments()[0];
title = genericType.GetDisplayName();
}
schema.Add("title", title);
// array
if (meta.IsEnumerableType)
{
if (modelType != meta.ElementMetadata!.ModelType.UnderlyingSystemType)
{
schema.Add("type", "array");
2 years ago
schema.TryAdd("multiple", true);
schema.TryAdd("url", "url");
schema.Add("items", meta.ElementMetadata.GetSchema(serviceProvider, meta));
}
}
else
{
if (!modelType.IsValueType && modelType != typeof(string))
{
schema.Add("type", "object");
var properties = new Dictionary<string, object>();
foreach (var propertyMetadata in meta.Properties)
{
if (meta.ContainerType != propertyMetadata.ContainerType)
{
if (propertyMetadata.IsEnumerableType)
{
//array
if (propertyMetadata.ElementType == propertyMetadata.ContainerType)
{
continue;
}
}
else if (!propertyMetadata.ModelType.IsValueType && propertyMetadata.ModelType != typeof(string))
{
//object
if (propertyMetadata.ModelType == propertyMetadata.ContainerType)
{
continue;
}
if (parent != null)
{
continue;
}
}
else
{
//property
}
properties.Add(propertyMetadata.Name!, propertyMetadata.GetSchema(serviceProvider, meta));
}
}
schema.Add(nameof(properties), properties);
}
else
{
if (modelType.IsEnum)
{
schema.TryAdd("options", Enum.GetNames(modelType).Select(o => new { Value = o, Label = ((Enum)Enum.Parse(modelType, o)).GetDisplayName() }).ToArray());
schema.TryAdd("input", "select");
if (modelType.HasAttribute<FlagsAttribute>())
{
schema.TryAdd("multiple", true);
}
}
AddType(schema, modelType);
if (meta.ModelType.IsNullableType())
{
schema.Add("nullable", true);
}
}
}
schema.AddNotNull("description", meta.Description);
schema.AddNotNull("format", meta.DataTypeName?.ToLowerCamelCase());
schema.AddNotNull("input", meta.TemplateHint?.ToLowerCamelCase());
if (meta is DefaultModelMetadata defaultModelMetadata)
{
var propertyName = defaultModelMetadata.Name;
if (propertyName != null)
{
if (defaultModelMetadata.Attributes.Attributes.FirstOrDefault(o => o.GetType() == typeof(DefaultValueAttribute)) is DefaultValueAttribute defaultValue)
{
schema.AddNotNull("default", defaultValue.Value);
}
if (defaultModelMetadata.Attributes.Attributes.FirstOrDefault(o => o.GetType() == typeof(NavigationAttribute)) is NavigationAttribute navigationAttribute)
{
var path = navigationAttribute.Path ?? $"{propertyName[..^2]}.Name";
path = string.Join('.', path.Split('.').Select(o => o.ToLowerCamelCase()));
schema.Add("navigation", path);
}
if (defaultModelMetadata.Attributes.Attributes.FirstOrDefault(o => o.GetType() == typeof(ScaffoldColumnAttribute)) is ScaffoldColumnAttribute scaffoldColumnAttribute
&& !scaffoldColumnAttribute.Scaffold)
{
//列表、详情、新建、更新、查询都不显示
schema.Add("hidden", true);
}
if (defaultModelMetadata.Attributes.Attributes.FirstOrDefault(o => o.GetType() == typeof(ReadOnlyAttribute)) is ReadOnlyAttribute readOnlyAttribute
&& readOnlyAttribute.IsReadOnly)
{
//列表、详情显示,编辑时不显示,查询时显示
schema.Add("readOnly", true);
}
if (defaultModelMetadata.Attributes.Attributes.Any(o => o.GetType() == typeof(DisplayOnlyAttribute)))
{
//列表、详情、编辑时都只显示,查询时显示
schema.Add("displayOnly", true);
}
}
}
var roles = meta.GetRules(serviceProvider, title);
if (roles.Any())
{
schema.Add("rules", roles);
}
return schema;
}
public static List<Dictionary<string, object>> GetRules(this ModelMetadata meta, IServiceProvider serviceProvider, string title)
{
var pm = (meta as DefaultModelMetadata)!;
var rules = new List<Dictionary<string, object>>();
var validationProvider = serviceProvider.GetRequiredService<IValidationAttributeAdapterProvider>();
var localizer = serviceProvider.GetRequiredService<IStringLocalizer>();
var actionContext = new ActionContext { HttpContext = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext! };
var provider = new EmptyModelMetadataProvider();
var modelValidationContextBase = new ModelValidationContextBase(actionContext, meta, new EmptyModelMetadataProvider());
2 years ago
if (pm.IsRequired &&
!pm.IsNullableValueType &&
!pm.UnderlyingOrModelType.IsValueType &&
!pm.IsEnumerableType &&
!pm.Attributes.Attributes.Any(o => o.GetType() == typeof(RequiredAttribute))
)
{
var message = string.Format(CultureInfo.InvariantCulture, localizer.GetString(nameof(RequiredAttribute)).Value, title);
rules.Add(new Dictionary<string, object> { { "required", true }, { "message", message } });
}
if (pm.IsRequired)
{
if (!pm.ModelType.IsValueType && pm.Attributes.Attributes.Any(o => o.GetType() == typeof(RequiredAttribute)))
{
var message = string.Format(CultureInfo.InvariantCulture, localizer.GetString(nameof(RequiredAttribute)).Value, title);
rules.Add(new Dictionary<string, object> { { "required", true }, { "message", message } });
}
}
foreach (var item in pm.Attributes.Attributes)
{
if (item is ValidationAttribute attribute && !string.IsNullOrEmpty(attribute.ErrorMessage))
{
var errorMessage = localizer.GetString(attribute.ErrorMessage).Value;
string? message;
if (attribute is RemoteAttribute)
{
message = string.Format(CultureInfo.InvariantCulture, errorMessage, title);
}
else if (attribute is DataTypeAttribute)
{
if (attribute is FileExtensionsAttribute extensionsAttribute)
{
message = string.Format(CultureInfo.InvariantCulture, errorMessage, title, extensionsAttribute.Extensions);
}
else
{
message = string.Format(CultureInfo.InvariantCulture, errorMessage, title);
}
}
else
{
message = validationProvider.GetAttributeAdapter(attribute!, localizer)?.GetErrorMessage(modelValidationContextBase);
}
var rule = new Dictionary<string, object>();
if (attribute is RegularExpressionAttribute regularExpression)
{
rule.Add("pattern", regularExpression.Pattern);
}
else if (attribute is MaxLengthAttribute maxLength)
{
rule.Add("max", maxLength.Length);
}
else if (attribute is RequiredAttribute)
{
rule.Add("required", true);
}
else if (attribute is CompareAttribute compare)//??
{
rule.Add("validator", "compare");
rule.Add("compare", compare.OtherProperty.ToLowerCamelCase());
}
else if (attribute is MinLengthAttribute minLength)
{
rule.Add("min", minLength.Length);
}
else if (attribute is CreditCardAttribute)
{
rule.Add("validator", "creditcard");
}
else if (attribute is StringLengthAttribute stringLength)
{
rule.Add("min", stringLength.MinimumLength);
rule.Add("max", stringLength.MaximumLength);
}
else if (attribute is RangeAttribute range)
{
rule.Add("type", "number");
rule.Add("min", range.Minimum is int minInt ? minInt : (double)range.Minimum);
rule.Add("max", range.Maximum is int maxInt ? maxInt : (double)range.Maximum);
}
else if (attribute is EmailAddressAttribute)
{
rule.Add("type", "email");
}
else if (attribute is PhoneAttribute)
{
rule.Add("validator", "phone");
}
else if (attribute is UrlAttribute)
{
rule.Add("type", "url");
}
else if (attribute is FileExtensionsAttribute fileExtensions)
{
rule.Add("validator", "accept");
rule.Add("extensions", fileExtensions.Extensions);
}
else if (attribute is RemoteAttribute remote)
{
rule.Add("validator", "remote");
var attributes = new Dictionary<string, string>();
remote.AddValidation(new ClientModelValidationContext(actionContext, pm, provider, attributes));
rule.Add("remote", attributes["data-val-remote-url"]);
//rule.Add("fields", remote.AdditionalFields.Split(',').Where(o => !string.IsNullOrEmpty(o)).Select(o => o.ToLowerCamelCase()).ToList());
}
else if (attribute is DataTypeAttribute dataType)
{
var name = dataType.GetDataTypeName();
if (name == DataType.Date.ToString())
{
rule.TryAdd("format", "date");
}
else if (name == DataType.DateTime.ToString())
{
rule.TryAdd("format", "datetime");
}
}
else
{
//Console.WriteLine($"{attribute.GetType().Name}");
}
rule.Add("message", message!);
//rule.Add("trigger", "change");
rules.Add(rule);
}
else
{
//Console.WriteLine($"{item.GetType().Name}");
}
}
return rules;
}
private static void AddType(Dictionary<string, object> schema, Type modelType)
{
var type = "string";
if (modelType == typeof(bool))
{
type = "boolean";
}
else if (modelType == typeof(short) ||
modelType == typeof(int) ||
modelType == typeof(long))
{
type = "integer";
}
else if (
modelType == typeof(float) ||
modelType == typeof(double) ||
modelType == typeof(decimal))
{
type = "number";
}
schema.Add("type", type);
if (modelType.GetUnderlyingType() == typeof(DateTime))
{
schema.TryAdd("format", "datetime");
}
}
}