WCF Rest 4.0、动态路由和 OutputCache

发布于 2024-11-11 05:46:49 字数 4634 浏览 4 评论 0原文

我在让 OutputCaching 与 WCF 4.0 WebHttp 服务的 HttpContext.RewritePath 配合使用时遇到问题。

我的服务是本地化的。这个想法是,您可以像这样调用 URL:

/languageCode/ServiceName/Method
e.g.
/en/MyService/GetItems

它将返回本地化为正确语言的结果。

我的方案基于这篇文章。这个想法是创建 RouteBase 的衍生产品,创建通往真实服务的独特的“私有”路由。当用户发出请求时,将从 URL 中解压缩语言代码并将其设置为当前线程的区域性,然后使用 HttpContext.RewritePath 加载实际的服务。

对于我的一生,我无法弄清楚如何将 OutputCaching 融入其中。我已经用 AspNetCacheProfile 装饰了我的服务方法,并且看到我自己的 VaryByCustom 重写被调用。然而,尽管从 VaryByCustom 收到重复的结果,.NET 仍然继续进入我的服务方法。

下面有很多代码,抱歉转储,但我怀疑它们都是相关的。


如何在 Global.asax.cs 中添加路由

RouteTable.Routes.Add(new CulturedServiceRoute(
    "devices", 
    new StructureMapServiceHostFactory(), 
    typeof(DeviceService)));

VaryByCustom 覆盖 Global.asax.cs 中的:

public override string GetVaryByCustomString(
    HttpContext context, string custom)
{

    // This method gets called twice: Once for the initial request, then a 
    // second time for the rewritten URL. I only want it to be called once!

    if (custom == "XmlDataFreshness")
    {
        var outputString = String.Format("{0}|{1}|{2}", 
            XmlDataLoader.LastUpdatedTicks, 
            context.Request.RawUrl, 
            context.Request.HttpMethod);
        return outputString;
    }

    return base.GetVaryByCustomString(context, custom);
}

这是动态服务路由类。

public class CulturedServiceRoute : RouteBase, IRouteHandler
{
    private readonly string _virtualPath = null;
    private readonly ServiceRoute _innerServiceRoute = null;
    private readonly Route _innerRoute = null;

    public CulturedServiceRoute(
        string pathPrefix, 
        ServiceHostFactoryBase serviceHostFactory, 
        Type serviceType)
    {
        if (pathPrefix.IndexOf("{") >= 0)
        {
            throw new ArgumentException(
                "Path prefix cannot include route parameters.", 
                "pathPrefix");
        }
        if (!pathPrefix.StartsWith("/")) pathPrefix = "/" + pathPrefix;
        pathPrefix = "{culture}" + pathPrefix;

        _virtualPath = String.Format("Cultured/{0}/", serviceType.FullName);
        _innerServiceRoute = new ServiceRoute(
            _virtualPath, serviceHostFactory, serviceType);
        _innerRoute = new Route(pathPrefix, this);
    }

    public override RouteData GetRouteData(
        HttpContextBase httpContext)
    {
        return _innerRoute.GetRouteData(httpContext);
    }

    public override VirtualPathData GetVirtualPath(
        RequestContext requestContext, RouteValueDictionary values)
    {
        return null;
    }

    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        // This method is called even if VaryByCustom 
        // returns a duplicate response!

        var culture = requestContext.RouteData.Values["culture"].ToString();
        var ci = new CultureInfo(culture);
        Thread.CurrentThread.CurrentUICulture = ci;
        Thread.CurrentThread.CurrentCulture = 
            CultureInfo.CreateSpecificCulture(ci.Name);

        requestContext.HttpContext.RewritePath("~/" + _virtualPath, true);
        return _innerServiceRoute.RouteHandler.GetHttpHandler(requestContext);
    }
}

最后,服务本身的相关部分:

[ServiceContract]
[AspNetCompatibilityRequirements(
    RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class DeviceService
{
    [AspNetCacheProfile("MyCacheProfile")]
    [WebGet(UriTemplate = "")]
    public IEnumerable<DeviceListItemModel> GetDevices()
    {
        // This is called AFTER the first VaryByCustom override is called.
        // I'd expect it not to be called unless VaryByCustom changes!

        var devices =
            from d in _deviceRepository.GetAll()
            where d.ReleaseDate < DateTime.Now
            orderby d.Id descending
            select new DeviceListItemModel(d);

        return devices;
    }

更新:我的缓存配置文件:

<caching>
  <outputCacheSettings>
    <outputCacheProfiles>
      <add name="MyCacheProfile" varyByCustom="XmlDataFreshness"
           varyByHeader="accept" varyByParam="*" location="Server"
           duration="3600" />
    </outputCacheProfiles>
  </outputCacheSettings>
</caching>

I'm having an issue getting OutputCaching to work with HttpContext.RewritePath for a WCF 4.0 WebHttp service.

My service is localized. The idea is that you call a URL like so:

/languageCode/ServiceName/Method
e.g.
/en/MyService/GetItems

And it'll return the results localized to the correct language.

My scheme is based on this article. The idea is to create a derivative of RouteBase that creates a unique, "private" route to the real service. When the user makes a request, the language code is unpacked from the URL and set as the culture for the current thread, and then HttpContext.RewritePath is used to load the actual service.

For the life of me I can't figure out how to work OutputCaching into the mix. I've decorated my service method with AspNetCacheProfile and am seeing my own VaryByCustom override called. However despite receiving a duplicate result from VaryByCustom, .NET continues into my service method anyway.

Lots of code below, sorry for the dump but I suspect it's all relevant.


How I add a route in Global.asax.cs

RouteTable.Routes.Add(new CulturedServiceRoute(
    "devices", 
    new StructureMapServiceHostFactory(), 
    typeof(DeviceService)));

VaryByCustom override in Global.asax.cs:

public override string GetVaryByCustomString(
    HttpContext context, string custom)
{

    // This method gets called twice: Once for the initial request, then a 
    // second time for the rewritten URL. I only want it to be called once!

    if (custom == "XmlDataFreshness")
    {
        var outputString = String.Format("{0}|{1}|{2}", 
            XmlDataLoader.LastUpdatedTicks, 
            context.Request.RawUrl, 
            context.Request.HttpMethod);
        return outputString;
    }

    return base.GetVaryByCustomString(context, custom);
}

This is the dynamic service route class.

public class CulturedServiceRoute : RouteBase, IRouteHandler
{
    private readonly string _virtualPath = null;
    private readonly ServiceRoute _innerServiceRoute = null;
    private readonly Route _innerRoute = null;

    public CulturedServiceRoute(
        string pathPrefix, 
        ServiceHostFactoryBase serviceHostFactory, 
        Type serviceType)
    {
        if (pathPrefix.IndexOf("{") >= 0)
        {
            throw new ArgumentException(
                "Path prefix cannot include route parameters.", 
                "pathPrefix");
        }
        if (!pathPrefix.StartsWith("/")) pathPrefix = "/" + pathPrefix;
        pathPrefix = "{culture}" + pathPrefix;

        _virtualPath = String.Format("Cultured/{0}/", serviceType.FullName);
        _innerServiceRoute = new ServiceRoute(
            _virtualPath, serviceHostFactory, serviceType);
        _innerRoute = new Route(pathPrefix, this);
    }

    public override RouteData GetRouteData(
        HttpContextBase httpContext)
    {
        return _innerRoute.GetRouteData(httpContext);
    }

    public override VirtualPathData GetVirtualPath(
        RequestContext requestContext, RouteValueDictionary values)
    {
        return null;
    }

    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        // This method is called even if VaryByCustom 
        // returns a duplicate response!

        var culture = requestContext.RouteData.Values["culture"].ToString();
        var ci = new CultureInfo(culture);
        Thread.CurrentThread.CurrentUICulture = ci;
        Thread.CurrentThread.CurrentCulture = 
            CultureInfo.CreateSpecificCulture(ci.Name);

        requestContext.HttpContext.RewritePath("~/" + _virtualPath, true);
        return _innerServiceRoute.RouteHandler.GetHttpHandler(requestContext);
    }
}

Finally, the relevant portions of the service itself:

[ServiceContract]
[AspNetCompatibilityRequirements(
    RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class DeviceService
{
    [AspNetCacheProfile("MyCacheProfile")]
    [WebGet(UriTemplate = "")]
    public IEnumerable<DeviceListItemModel> GetDevices()
    {
        // This is called AFTER the first VaryByCustom override is called.
        // I'd expect it not to be called unless VaryByCustom changes!

        var devices =
            from d in _deviceRepository.GetAll()
            where d.ReleaseDate < DateTime.Now
            orderby d.Id descending
            select new DeviceListItemModel(d);

        return devices;
    }

UPDATE: My cache profile:

<caching>
  <outputCacheSettings>
    <outputCacheProfiles>
      <add name="MyCacheProfile" varyByCustom="XmlDataFreshness"
           varyByHeader="accept" varyByParam="*" location="Server"
           duration="3600" />
    </outputCacheProfiles>
  </outputCacheSettings>
</caching>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

绝情姑娘 2024-11-18 05:46:49

嗯,对我来说似乎是一个有效的方法。缓存配置文件配置是否正确?当缓存不需要更新时,varyByCustom是否被多次调用并且一定会返回相同的结果?

Hmmm seems like a valid approach to me. Is the cache profile configured correctly? Is varyByCustom called multiple times and certain to return the same result when the cache does not need to be updated?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文