using System.Collections; using System.Net.Mime; using System.Reflection; using System.Xml.Linq; using Masuit.Tools.Systems; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Newtonsoft.Json.Linq; namespace Masuit.Tools.AspNetCore.ModelBinder; public class FromBodyOrDefaultModelBinder(ILogger logger) : IModelBinder { private static readonly List BindTypes = [ BindType.Query, BindType.Body, BindType.Header, BindType.Form, BindType.Cookie, BindType.Route ]; public Task BindModelAsync(ModelBindingContext bindingContext) { var context = bindingContext.HttpContext; var attr = bindingContext.GetAttribute(); var field = attr?.FieldName ?? bindingContext.FieldName; var modelType = bindingContext.ModelType; object value = null; if (attr != null) { if (modelType.IsSimpleType() || modelType.IsSimpleArrayType() || modelType.IsSimpleListType()) { if (attr.Type == BindType.Default) { foreach (var type in BindTypes) { value = GetBindingValue(bindingContext, type, field, modelType); if (value != null) { break; } } } else { foreach (var type in attr.Type.Split()) { value = GetBindingValue(bindingContext, type, field, modelType); if (value != null) { break; } } } } else { if (bindingContext.HttpContext.Items.TryGetValue("BodyOrDefaultModelBinder@JsonBody", out var obj) && obj is JObject json) { if (modelType.IsArray || modelType.IsGenericType && modelType.GenericTypeArguments.Length == 1) { if (json.TryGetValue(field, StringComparison.OrdinalIgnoreCase, out var jtoken)) { value = jtoken.ToObject(modelType); } else { logger.LogWarning($"TraceIdentifier:{context.TraceIdentifier},BodyOrDefaultModelBinder从{json}中获取{field}失败!"); } } else { // 可能是 字典或者实体 类型,尝试将modeltype 当初整个请求参数对象 try { value = json.ToObject(modelType); } catch (Exception e) { logger.LogError(e, e.Message, json.ToString()); } } } if (value == null) { var (requestData, keys) = GetRequestData(bindingContext, modelType); if (keys.Any()) { var instance = Activator.CreateInstance(modelType); switch (requestData) { case IEnumerable> stringValues: { foreach (var item in stringValues) { var property = modelType.GetProperty(item.Key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); if (property != null) { property.SetValue(instance, item.Value.ConvertObject(property.PropertyType)); } } break; } case IEnumerable> strs: { //处理Cookie foreach (var item in strs) { var property = modelType.GetProperty(item.Key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); if (property != null) { property.SetValue(instance, item.Value.ConvertObject(property.PropertyType)); } } break; } case IEnumerable> objects: { //处理路由 foreach (var item in objects) { var property = modelType.GetProperty(item.Key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); if (property != null) { property.SetValue(instance, item.Value.ConvertObject(property.PropertyType)); } } break; } } value = instance; } } } if (value == null && attr.DefaultValue != null) { value = attr.DefaultValue.ChangeType(modelType); } } if (value != null) { bindingContext.Result = ModelBindingResult.Success(value); } return Task.CompletedTask; } private static (IEnumerable data, List keys) GetRequestData(ModelBindingContext bindingContext, Type type) { var request = bindingContext.HttpContext.Request; var props = type.GetProperties().Select(t => t.Name).ToList(); var query = props.Except(request.Query.Keys, StringComparer.OrdinalIgnoreCase).ToList(); var headers = props.Except(request.Headers.Keys, StringComparer.OrdinalIgnoreCase).ToList(); var cookies = props.Except(request.Cookies.Keys, StringComparer.OrdinalIgnoreCase).ToList(); var routes = props.Except(bindingContext.ActionContext.RouteData.Values.Keys, StringComparer.OrdinalIgnoreCase).ToList(); var list = new List, IEnumerable>>() { new(query, request.Query), new(headers, request.Headers), new(cookies, request.Cookies), new(routes, bindingContext.ActionContext.RouteData.Values), }; if (request.HasFormContentType && request.Form.Count > 0) { var forms = props.Except(request.Form.Keys, StringComparer.OrdinalIgnoreCase).ToList(); list.Add(new KeyValuePair, IEnumerable>(forms, request.Form)); } var kv = list.OrderBy(t => t.Key.Count).FirstOrDefault(); return (kv.Value, props.Except(kv.Key).ToList()); } /// /// 获取要绑定的值 /// /// /// /// /// private object GetBindingValue(ModelBindingContext bindingContext, BindType bindType, string fieldName, Type modelType) { var context = bindingContext.HttpContext; var mediaType = string.Empty; if (!string.IsNullOrWhiteSpace(context.Request.ContentType)) { try { var contentType = new ContentType(context.Request.ContentType); mediaType = contentType.MediaType.ToLower(); } catch (Exception ex) { logger.LogError(ex, ex.Message, context.Request.ContentType); } } object targetValue = null; switch (bindType) { case BindType.Body: switch (mediaType) { case "application/json": { if (bindingContext.HttpContext.Items.TryGetValue("BodyOrDefaultModelBinder@JsonBody", out var obj) && obj is JObject json && json.TryGetValue(fieldName, StringComparison.OrdinalIgnoreCase, out var values)) { targetValue = values.ConvertObject(modelType); } } break; case "application/xml": { if (bindingContext.HttpContext.Items.TryGetValue("BodyOrDefaultModelBinder@XmlBody", out var obj) && obj is XDocument xml) { var xmlElt = xml.Element(fieldName); if (xmlElt != null) { targetValue = xmlElt.Value.ConvertObject(modelType); } } break; } } break; case BindType.Query: { if (context.Request.Query is { Count: > 0 } && context.Request.Query.TryGetValue(fieldName, out var values)) { targetValue = values.ConvertObject(modelType); } } break; case BindType.Form: { if (context.Request is { HasFormContentType: true, Form.Count: > 0 } && context.Request.Form.TryGetValue(fieldName, out var values)) { targetValue = values.ConvertObject(modelType); } } break; case BindType.Header: { if (context.Request.Headers is { Count: > 0 } && context.Request.Headers.TryGetValue(fieldName, out var values)) { targetValue = values.ConvertObject(modelType); } } break; case BindType.Cookie: { if (context.Request.Cookies is { Count: > 0 } && context.Request.Cookies.TryGetValue(fieldName, out var values)) { targetValue = values.ConvertObject(modelType); } } break; case BindType.Route: { if (bindingContext.ActionContext.RouteData.Values is { Count: > 0 } && bindingContext.ActionContext.RouteData.Values.TryGetValue(fieldName, out var values)) { targetValue = values.ConvertObject(modelType); } } break; case BindType.Services: targetValue = bindingContext.ActionContext.HttpContext.RequestServices.GetRequiredService(modelType); break; } return targetValue; } }