简体   繁体   English

将属性添加到 asp.net core 中的所有响应

[英]Add property to all responses in asp.net core

I have an API with multiple endpoints.我有一个具有多个端点的 API。 I'd like to add a property to all endpoint responses, without adding it to each endpoint response model individually.我想为所有端点响应添加一个属性,而不是单独将它添加到每个端点响应模型中。

Ex:前任:

public class MyClass
{
    public string MyProperty { get; set; } = "Hello";
}

public class MyOtherClass
{
    public string MyOtherProperty { get; set; } = "World";
}

public class MyController : ControllerBase
{
    [HttpPost]
    public async Task<ActionResult<MyClass>> EndpointOne(POSTData data)
    {
        // implementation omitted
    }

    [HttpPost]
    public async Task<ActionResult<MyOtherClass>> EndpointTwo(POSTOtherData otherData)
    {
        // implementation omitted
    }
}

Calling either endpoint returns a JSON representation of MyClass or MyOtherClass as appropriate - ie调用任一端点会根据需要返回MyClassMyOtherClass的 JSON 表示 - 即

{ "MyProperty":"Hello" } or { "MyOtherProperty":"World" }

I want to add a property, say a string ApiName , to all endpoints in the API, so that the result of the above code would be either (as appropriate)我想向 API 中的所有端点添加一个属性,比如字符串ApiName ,以便上述代码的结果是(视情况而定)

{ "MyProperty":"Hello", "ApiName":"My awesome API" } 

or或者

{ "MyOtherProperty":"World", "ApiName":"My awesome API" }

Is there a way to hook into the JSON-stringified result just before returning and add a top-level property like that?有没有办法在返回之前挂钩 JSON 字符串化的结果并添加这样的顶级属性? If so, I presume I'd have to wire it up in startup.cs , so I've been looking at app.UseEndpoints(...) methods, but haven't found anything that's worked so far.如果是这样,我想我必须在startup.cs中连接它,所以我一直在研究app.UseEndpoints(...)方法,但到目前为止还没有找到任何有效的方法。 Either it's not added the property, or it's replaced the original result with the new property.要么没有添加属性,要么将原始结果替换为新属性。

Thanks in advance!提前致谢!

Use Newtonsoft.Json in your net web api在您的网络 api 中使用 Newtonsoft.Json

Register a custom contract resolver in Startup.cs:在 Startup.cs 中注册一个自定义合约解析器:

builder.Services.AddControllers()
    .AddNewtonsoftJson(options => options.SerializerSettings.ContractResolver = CustomContractResolver.Instance);

The implementation:实施:

public class CustomContractResolver : DefaultContractResolver {
    public static CustomContractResolver Instance { get; } = new CustomContractResolver();

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(type, memberSerialization);
        // add new property
        ...
        properties.Add(newProp);
        return properties;
    }}

See more Json.net Add property to every class containing of a certain type查看更多Json.net 将属性添加到包含某种类型的每个类

You can add a base class with the shared property.您可以添加具有共享属性的基类。 Should work for both XML and JSON.应该适用于 XML 和 JSON。

public class MyApiClass
{
    public string ApiName => "MyAwesomeApi";
}

public class MyClass : MyApiClass
{
    public string MyProperty { get; set; } = "Hello";
}

public class MyOtherClass : MyApiClass
{
    public string MyOtherProperty { get; set; } = "World";
}

public class MyController : ControllerBase
{
    [HttpPost]
    public async Task<ActionResult<MyClass>> EndpointOne(POSTData data)
    {
        // implementation omitted
    }

    [HttpPost]
    public async Task<ActionResult<MyOtherClass>> EndpointTwo(POSTOtherData otherData)
    {
        // implementation omitted
    }
}

My 0.02 cents says to implement an abstract base class.我的 0.02 美分说要实现一个抽象基类。

Abstract class inheritance look similar to a standard inheritance.抽象类继承看起来类似于标准继承。

    public class MyClass:MyAbstractClass
    {
        [JsonPropertyName("Class Property")]
        public string MyProperty { get; set; } = "Hello";
        }
    public class MyOtherClass:MyAbstractClass
    {
        [JsonPropertyName("Class Property")]
        public string MyOtherProperty { get; set; } = "World";
        }

However the abstract class will allow you to implement additional features in the event you need them in the future.但是,抽象类将允许您在将来需要它们时实现附加功能。

    public abstract class MyAbstractClass{

        [JsonPropertyName("API Name")]
        public string ApiName{get;set;}="My Aweomse API";

        //Just a thought if you want to keep track of the end point names
        //while keeping your object names the same
        [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
        public string EndPointName{
            get{
                return get_endpoint_name();
                }}
        private string get_endpoint_name(){
            return this.GetType().Name;
            }

        //May as well make it easy to grab the JSON
        [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
        public string As_JSON{
            get {
                return to_json();
                }}

        private string to_json(){
            
            object _myObject = this;
            string _out;

            JsonSerializerOptions options = 
                new JsonSerializerOptions { 
                        WriteIndented = true };
            
            _out = 
                JsonSerializer.Serialize(_myObject, options);

            return _out;
        }
    }

Probably should have implemented a generic return object, then you could just loop through the task results.可能应该已经实现了一个通用的返回对象,然后你可以遍历任务结果。 I suppose you still can if you have the task return only the JSON string.我想如果你的任务只返回 JSON 字符串,你仍然可以。

    public static void run(){

        Task<MyClass> _t0 = task0();
        Task<MyOtherClass> _t1 = task1();
        Task[] _tasks = new Task[]{_t0,_t1};

        Task.WhenAll(_tasks).Wait();
        
        Console.WriteLine(""
        +$"{_t1.Result.ApiName}:\n"
        +$"End Point: {_t1.Result.EndPointName}:\n"
        +$"JSON:\n{_t1.Result.As_JSON}");
        
        Console.WriteLine(""
        +$"{_t0.Result.ApiName}:\n"
        +$"End Point: {_t0.Result.EndPointName}:\n"
        +$"JSON:\n{_t0.Result.As_JSON}");
        
    }

    private static Task<MyClass> task0(){
        return Task.Run(()=>{
            Console.WriteLine("Task 0 Doing Something");
            return new MyClass();
        });
    }
    private static Task<MyOtherClass> task1(){
        return Task.Run(()=>{
            Console.WriteLine("Task 1 Doing Something");
            return new MyOtherClass();
        });
    }

And of course the aweosome...awesome:-) results:当然还有令人敬畏的...令人敬畏的:-)结果:

结果

Another thought is that you could implement your two different tasks as abstract methods, but that's a different conversation all together.另一个想法是您可以将两个不同的任务实现为抽象方法,但这是一个不同的对话。

In addition to all of the great answers, I prefer to use Action Filter and ExpandoObject.除了所有出色的答案之外,我更喜欢使用 Action Filter 和 ExpandoObject。 In Program File you should add your custom action Filter.在程序文件中,您应该添加自定义操作过滤器。

builder.Services.AddControllers(opt =>
{
    opt.Filters.Add<ResponseHandler>();
});

and ResponseHandler acts like below:和 ResponseHandler 的行为如下:

public class ResponseHandler : IActionFilter
    {
        public void OnActionExecuted(ActionExecutedContext context)
        {
            IDictionary<string, object> expando = new ExpandoObject();

            foreach (var propertyInfo in (context.Result as ObjectResult).Value.GetType().GetProperties())
            {
                var currentValue = propertyInfo.GetValue((context.Result as ObjectResult).Value);
                expando.Add(propertyInfo.Name, currentValue);
            }
            dynamic result = expando as ExpandoObject;


            result.ApiName = context.ActionDescriptor.RouteValues["action"].ToString();
            context.Result = new ObjectResult(result);
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {

        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM