熟悉Spring与Java,询问ASP.NET Web API中@JsonView的等效实现
Great question! I’ve been in exactly the same spot moving from Spring to ASP.NET Web API—losing that @JsonView functionality stings because no one wants to churn out a dozen almost-identical DTOs for every tiny business scenario. Let’s break down the best ways to replicate that behavior in ASP.NET:
This is the closest built-in alternative to Spring’s @JsonView. You can define different serialization "views" using JsonSerializerContext to pick which properties get included for each API endpoint.
Step 1: Create view marker classes
These act like the view interfaces you’d use in Spring:
// Marker for public-facing user data public class PublicUserView {} // Marker for internal/admin-only data (inherits public properties) public class InternalUserView : PublicUserView {}
Step 2: Configure serialization rules
Build a custom resolver to filter properties based on the active view:
[JsonSerializable(typeof(UserEntity))] public partial class UserJsonContext : JsonSerializerContext { public UserJsonContext() : base(GetDefaultOptions()) {} private static JsonSerializerOptions GetDefaultOptions() { var options = new JsonSerializerOptions(); options.TypeInfoResolverChain.Insert(0, new UserViewTypeInfoResolver()); return options; } } public class UserViewTypeInfoResolver : DefaultJsonTypeInfoResolver { public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) { var typeInfo = base.GetTypeInfo(type, options); if (type == typeof(UserEntity)) { // Check if we're using the internal view var isInternalView = options.Context.TryGetValue(typeof(InternalUserView), out _); // Hide sensitive properties for public views if (!isInternalView) { typeInfo.Properties.Remove(typeInfo.Properties.First(p => p.Name == "SocialSecurityNumber")); typeInfo.Properties.Remove(typeInfo.Properties.First(p => p.Name == "InternalNotes")); } } return typeInfo; } }
Step 3: Use the views in your controller
Apply the context to serialize the right properties for each endpoint:
[ApiController] [Route("api/users")] public class UsersController : ControllerBase { [HttpGet("public/{id}")] public IActionResult GetPublicUser(int id) { var user = FetchUserFromDatabase(id); var publicOptions = new JsonSerializerOptions { Context = new JsonSerializerContext(typeof(PublicUserView)) }; return Ok(JsonSerializer.Serialize(user, publicOptions)); } [HttpGet("internal/{id}")] [Authorize(Roles = "Admin")] public IActionResult GetInternalUser(int id) { var user = FetchUserFromDatabase(id); var internalOptions = new JsonSerializerOptions { Context = new JsonSerializerContext(typeof(InternalUserView)) }; return Ok(JsonSerializer.Serialize(user, internalOptions)); } }
If you’re already using AutoMapper (super common in ASP.NET projects), you can set up conditional mappings to reuse a single DTO instead of creating duplicates.
Example mapping configuration
public class UserProfile : Profile { public UserProfile() { // Base mapping for public views (hides sensitive fields) CreateMap<UserEntity, UserDto>() .ForMember(dest => dest.SocialSecurityNumber, opt => opt.Ignore()) .ForMember(dest => dest.InternalNotes, opt => opt.Ignore()); // Override for internal views (includes all fields) CreateMap<UserEntity, UserDto>() .IncludeBase<UserEntity, UserDto>() .ForMember(dest => dest.SocialSecurityNumber, opt => opt.MapFrom(src => src.SocialSecurityNumber)) .ForMember(dest => dest.InternalNotes, opt => opt.MapFrom(src => src.InternalNotes)) .Named("InternalUser"); } }
Usage in your controller
[HttpGet("public/{id}")] public IActionResult GetPublicUser(int id) { var user = FetchUserFromDatabase(id); var publicDto = _mapper.Map<UserDto>(user); return Ok(publicDto); } [HttpGet("internal/{id}")] [Authorize(Roles = "Admin")] public IActionResult GetInternalUser(int id) { var user = FetchUserFromDatabase(id); var internalDto = _mapper.Map<UserDto>(user, opt => opt.MapFromNamedProfile("InternalUser")); return Ok(internalDto); }
Keep your controller code clean by using a custom action filter that applies view logic automatically via an attribute.
Step 1: Create a view attribute
[AttributeUsage(AttributeTargets.Method)] public class JsonViewAttribute : Attribute { public Type ViewType { get; } public JsonViewAttribute(Type viewType) { ViewType = viewType; } }
Step 2: Build the action filter
public class JsonViewActionFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { } public void OnActionExecuted(ActionExecutedContext context) { if (context.Result is OkObjectResult okResult && context.ActionDescriptor.EndpointMetadata.FirstOrDefault(m => m is JsonViewAttribute) is JsonViewAttribute viewAttr) { var options = new JsonSerializerOptions(); options.TypeInfoResolverChain.Insert(0, new UserViewTypeInfoResolver()); options.Context = new JsonSerializerContext(viewAttr.ViewType); var serializedData = JsonSerializer.Serialize(okResult.Value, options); context.Result = new ContentResult { Content = serializedData, ContentType = "application/json", StatusCode = okResult.StatusCode }; } } }
Step 3: Register and use the filter
Add the filter to your startup configuration:
builder.Services.AddControllers(options => { options.Filters.Add<JsonViewActionFilter>(); });
Then apply the attribute to your controller actions:
[HttpGet("public/{id}")] [JsonView(typeof(PublicUserView))] public IActionResult GetPublicUser(int id) { var user = FetchUserFromDatabase(id); return Ok(user); } [HttpGet("internal/{id}")] [Authorize(Roles = "Admin")] [JsonView(typeof(InternalUserView))] public IActionResult GetInternalUser(int id) { var user = FetchUserFromDatabase(id); return Ok(user); }
Quick Recommendation
If you’re on .NET 6+, go with the System.Text.Json serialization context approach—it’s the most native and flexible option. If you’re already using AutoMapper, the conditional projection method integrates seamlessly with your existing setup.
内容的提问来源于stack exchange,提问作者Ivan Vasiljevic

