SonOfPirate
SonOfPirate

Reputation: 5494

Getting if-modified-since header to work with WCF

I am trying to get the "if-modified-since" header to work with my WCF web service.

When a user makes a request to my service, I add an ETag to the outgoing response that contains the timestamp of the request as follows:

var tag = String.Format("\"{0:o}\"", new DateTimeOffset(DateTime.Now));

This results in the following ETag header:

ETag: "2011-10-27T13:09:39.6242263-04:00"

I then take this value and echo it back as the if-modified-since header for subsequent requests like this:

If-Modified-Since:2011-10-27T13:09:39.6242263-04:00

When I examine WebOperationContext.Current.Headers.IfModifiedSince, I never get the value provided. The value is fixed at "12/31/1969 7:00:00 PM".

What am I doing wrong?

UPDATE

I should add that using Fiddler, I can set any value to the If-Modified-Since header and still get the same 1969 value in code.

Upvotes: 1

Views: 2010

Answers (1)

carlosfigueira
carlosfigueira

Reputation: 87228

First off, If-Modified-Since is about conditional GETs regarding the time of the last modification of the resource, while ETag is about conditional GETs regarding an identifier of the resources, so please be careful with mixing the two concepts.

The correct way of implementing support for If-Modified-Since in a WCF service is to use the CheckConditionalRetrieve passing a DateTime value in the WebOperationContext.Current.IncomingRequest object - see the code below for that. If the value of the IMS header is before the date you pass to CheckConditionalRetrieve, the method will exit at that point returning a 304 (Not Modified) response. Otherwise it will just continue. The code below shows that.

Another issue: even through the date format you're using (ISO 8601) works, but it's not correct based on the specification (section 14.25 in http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html, and section 3.3.1 in http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1), so you should consider using a valid format to prevent future problems.

You can find a good post about conditional GET support in WCF at http://blogs.msdn.com/b/endpoint/archive/2010/02/25/conditional-get-and-etag-support-in-wcf-webhttp-services.aspx.

public class StackOverflow_7919718
{
    [ServiceContract]
    public class Service
    {
        [WebGet(ResponseFormat = WebMessageFormat.Json)]
        public string GetData()
        {
            Console.WriteLine("If-Modified-Since header (1): {0}", WebOperationContext.Current.IncomingRequest.IfModifiedSince);
            WebOperationContext.Current.IncomingRequest.CheckConditionalRetrieve(DateTime.UtcNow);
            return "Data";
        }
    }

    public static void Test()
    {
        string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
        WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));
        host.Open();
        Console.WriteLine("Host opened");

        Console.WriteLine("Not sending If-Modified-Since header (should return 200):");
        Util.SendRequest(baseAddress + "/GetData", "GET", null, null);

        Console.WriteLine("Sending data in the past, ISO 8601 format (should return 200):");
        Util.SendRequest(baseAddress + "/GetData", "GET", null, null,
            new Dictionary<string, string> { { "If-Modified-Since", "2011-10-25T13:09:39.6242263-04:00" } });

        Console.WriteLine("Sending data in the future, ISO 8601 format (should return 304):");
        Util.SendRequest(baseAddress + "/GetData", "GET", null, null,
            new Dictionary<string, string> { { "If-Modified-Since", "2021-10-25T13:09:39.6242263-04:00" } });

        Console.WriteLine("Sending data in the past, RFC 1123 format (should return 200):");
        Util.SendRequest(baseAddress + "/GetData", "GET", null, null,
            new Dictionary<string, string> { { "If-Modified-Since", "Wed, 26 Oct 2011 01:00:00 GMT" } });

        Console.WriteLine("Sending data in the future, RFC 1123 format (should return 304):");
        Util.SendRequest(baseAddress + "/GetData", "GET", null, null,
            new Dictionary<string, string> { { "If-Modified-Since", "Mon, 27 Oct 2031 10:00:00 GMT" } });

        Console.Write("Press ENTER to close the host");
        Console.ReadLine();
        host.Close();
    }
}
public static class Util
{
    public static string SendRequest(string uri, string method, string contentType, string body)
    {
        return SendRequest(uri, method, contentType, body, null);
    }
    public static string SendRequest(string uri, string method, string contentType, string body, Dictionary<string, string> headers)
    {
        string responseBody = null;

        HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
        req.Method = method;
        if (headers != null)
        {
            foreach (string headerName in headers.Keys)
            {
                switch (headerName)
                {
                    case "If-Modified-Since":
                        req.IfModifiedSince = DateTime.Parse(headers[headerName]);
                        break;
                    default:
                        req.Headers[headerName] = headers[headerName];
                        break;
                }
            }
        }
        if (!String.IsNullOrEmpty(contentType))
        {
            req.ContentType = contentType;
        }

        if (body != null)
        {
            byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
            req.GetRequestStream().Write(bodyBytes, 0, bodyBytes.Length);
            req.GetRequestStream().Close();
        }

        HttpWebResponse resp;
        try
        {
            resp = (HttpWebResponse)req.GetResponse();
        }
        catch (WebException e)
        {
            resp = (HttpWebResponse)e.Response;
        }

        if (resp == null)
        {
            responseBody = null;
            Console.WriteLine("Response is null");
        }
        else
        {
            Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);
            foreach (string headerName in resp.Headers.AllKeys)
            {
                Console.WriteLine("{0}: {1}", headerName, resp.Headers[headerName]);
            }
            Console.WriteLine();
            Stream respStream = resp.GetResponseStream();
            if (respStream != null)
            {
                responseBody = new StreamReader(respStream).ReadToEnd();
                Console.WriteLine(responseBody);
            }
            else
            {
                Console.WriteLine("HttpWebResponse.GetResponseStream returned null");
            }
        }

        Console.WriteLine();
        Console.WriteLine("  *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*  ");
        Console.WriteLine();

        return responseBody;
    }
}

Upvotes: 2

Related Questions