You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

熟悉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:

1. System.Text.Json 自定义序列化上下文(.NET 6+)

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));
    }
}
2. AutoMapper 条件投影

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);
}
3. Dynamic Filtering with Action Filters

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:06:39