如何通过操作过滤器和HttpResponseMessage在Web API中使用ETag [英] How to use ETag in Web API using action filter along with HttpResponseMessage

查看:39
本文介绍了如何通过操作过滤器和HttpResponseMessage在Web API中使用ETag的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个ASP.Net Web API控制器,它仅返回用户列表.

I have a ASP.Net Web API controller which simply returns the list of users.

public sealed class UserController : ApiController
{
    [EnableTag]
    public HttpResponseMessage Get()
    {
        var userList= this.RetrieveUserList(); // This will return list of users
        this.responseMessage = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new ObjectContent<List<UserViewModel>>(userList, new  JsonMediaTypeFormatter())
        };
        return this.responseMessage;
       }
}

和一个动作过滤器属性类 EnableTag ,该类负责管理ETag和缓存:

and an action filter attribute class EnableTag which is responsible to manage ETag and cache:

public class EnableTag : System.Web.Http.Filters.ActionFilterAttribute
{
    private static ConcurrentDictionary<string, EntityTagHeaderValue> etags = new ConcurrentDictionary<string, EntityTagHeaderValue>();

    public override void OnActionExecuting(HttpActionContext context)
    {
        if (context != null)
        {
            var request = context.Request;
            if (request.Method == HttpMethod.Get)
            {
                var key = GetKey(request);
                ICollection<EntityTagHeaderValue> etagsFromClient = request.Headers.IfNoneMatch;

                if (etagsFromClient.Count > 0)
                {
                    EntityTagHeaderValue etag = null;
                    if (etags.TryGetValue(key, out etag) && etagsFromClient.Any(t => t.Tag == etag.Tag))
                    {
                        context.Response = new HttpResponseMessage(HttpStatusCode.NotModified);
                        SetCacheControl(context.Response);
                    }
                }
            }
        }
    }

    public override void OnActionExecuted(HttpActionExecutedContext context)
    {
        var request = context.Request;
        var key = GetKey(request);

        EntityTagHeaderValue etag;
        if (!etags.TryGetValue(key, out etag) || request.Method == HttpMethod.Put || request.Method == HttpMethod.Post)
        {
            etag = new EntityTagHeaderValue("\"" + Guid.NewGuid().ToString() + "\"");
            etags.AddOrUpdate(key, etag, (k, val) => etag);
        }

        context.Response.Headers.ETag = etag;
        SetCacheControl(context.Response);
    }

    private static void SetCacheControl(HttpResponseMessage response)
    {
        response.Headers.CacheControl = new CacheControlHeaderValue()
        {
            MaxAge = TimeSpan.FromSeconds(60),
            MustRevalidate = true,
            Private = true
        };
    }

    private static string GetKey(HttpRequestMessage request)
    {
        return request.RequestUri.ToString();
    }
}

上面的代码创建一个属性类来管理ETag.因此,在第一个请求上,它将创建一个新的E-Tag,对于随后的请求,它将检查是否存在任何ETag.如果是这样,它将生成未修改 HTTP状态并返回到客户端.

The above code create an attribute class to manage ETag. So on the first request, it will create a new E-Tag and for the subsequent request it will check whether any ETag is existed. If so, it will generate Not Modified HTTP Status and return back to client.

我的问题是,如果我的用户列表(例如)中有更改,我想创建一个新的ETag.添加新用户,或删除现有用户.并附加响应.可以通过 userList 变量进行跟踪.

My problem is, I want to create a new ETag if there are changes in my user list, ex. a new user is added, or an existing user is deleted. and append it with the response. This can be tracked by the userList variable.

当前,从客户端和服务器接收到的ETag在每个第二个请求中都是相同的,因此在这种情况下,它始终会生成 Not Modified 状态,而我希望它实际上没有任何变化.

Currently, the ETag received from client and server are same from every second request, so in this case it will always generate Not Modified status, while I want it when actually nothing changed.

有人可以引导我朝这个方向前进吗?

Can anyone guide me in this direction?

推荐答案

我的要求是缓存我的Web api JSON响应...而且提供的所有解决方案都没有到生成数据位置的简单链接"-即在控制器中...

My requirement was to cache my web api JSON responses... And all the solutions provided don't have an easy "link" to where the data is generated - ie in the Controller...

所以我的解决方案是创建一个包装器"CacheableJsonResult",该包装器生成一个Response,然后将ETag添加到标头中.这样可以在生成controller方法并希望返回内容时传递etag.

So my solution was to create a wrapper "CacheableJsonResult" which generated a Response, and then added the ETag to the header. This allows a etag to be passed in when the controller method is generated and wants to return the content...

public class CacheableJsonResult<T> : JsonResult<T>
{
    private readonly string _eTag;
    private const int MaxAge = 10;  //10 seconds between requests so it doesn't even check the eTag!

    public CacheableJsonResult(T content, JsonSerializerSettings serializerSettings, Encoding encoding, HttpRequestMessage request, string eTag)
        :base(content, serializerSettings, encoding, request)
    {
        _eTag = eTag;
    }

    public override Task<HttpResponseMessage> ExecuteAsync(System.Threading.CancellationToken cancellationToken)
    {
        Task<HttpResponseMessage> response = base.ExecuteAsync(cancellationToken);

        return response.ContinueWith<HttpResponseMessage>((prior) =>
        {
            HttpResponseMessage message = prior.Result;

            message.Headers.ETag = new EntityTagHeaderValue(String.Format("\"{0}\"", _eTag));
            message.Headers.CacheControl = new CacheControlHeaderValue
            {
                Public = true,
                MaxAge = TimeSpan.FromSeconds(MaxAge)
            };

            return message;
        }, cancellationToken);
    }
}

然后,在您的控制器中-返回此对象:

And then, in your controller - return this object:

[HttpGet]
[Route("results/{runId}")]
public async Task<IHttpActionResult> GetRunResults(int runId)
{               
    //Is the current cache key in our cache?
    //Yes - return 304
    //No - get data - and update CacheKeys
    string tag = GetETag(Request);
    string cacheTag = GetCacheTag("GetRunResults");  //you need to implement this map - or use Redis if multiple web servers

    if (tag == cacheTag )
            return new StatusCodeResult(HttpStatusCode.NotModified, Request);

    //Build data, and update Cache...
    string newTag = "123";    //however you define this - I have a DB auto-inc ID on my messages

    //Call our new CacheableJsonResult - and assign the new cache tag
    return new CacheableJsonResult<WebsiteRunResults>(results, GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings, System.Text.UTF8Encoding.Default, Request, newTag);

    }
}

private static string GetETag(HttpRequestMessage request)
{
    IEnumerable<string> values = null;
    if (request.Headers.TryGetValues("If-None-Match", out values))
        return new EntityTagHeaderValue(values.FirstOrDefault()).Tag;

    return null;
}

您需要定义制作标签的粒度;我的数据是特定于用户的,因此我在CacheKey(etag)中包含了UserId

You need to define how granular to make your tags; my data is user-specific, so I include the UserId in the CacheKey (etag)

这篇关于如何通过操作过滤器和HttpResponseMessage在Web API中使用ETag的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆