Showing posts with label REST. Show all posts
Showing posts with label REST. Show all posts

Friday, August 01, 2008

ADO.NET Data Services: Creating a custom Data Context #3: Updating

This follows part 1 where I create a custom data context and part 2 where I create a client application to talk to it.

For your custom data context to allow for updates you have to implement IUpdatable.

ado_net_im_iupdatable

This interface has a number of cryptic methods and it wasn't at all clear at first how to write an implementation for it. I'm sure there must be some documentation somewhere but I couldn't find it. I resorted to writing trace writes in the empty methods and firing inserts and updates at my web service. You can then use Sysinternals' DebugView to watch what happens.

First of all lets try an insert:

public void AddANewTeacher()
{
  Console.WriteLine("\r\nAddANewTeacher");

  var frankyChicken = new Teacher
                        {
                            ID = 3,
                            Name = "Franky Chicken"
                        };
  service.AddObject("Teachers", frankyChicken);
  var response = service.SaveChanges();
}

We get this result:

ado_net_im_debug_insert

So you can see that first all the existing Teachers are returned, then a new Teacher instance is created, it's properties set, SaveChanges is called and then ResolveResource. For my simple in-memory implementation I just added the new Teacher to my static list of teachers:

public object CreateResource(string containerName, string fullTypeName)
{
  Trace.WriteLine(string.Format("CreateResource('{0}', '{1}')", containerName, fullTypeName));

  var type = Type.GetType(fullTypeName);
  var resource = Activator.CreateInstance(type);

  switch (containerName)
  {
      case "Teachers":
          Root.Teachers.Add((Teacher)resource);
          break;
      case "Courses":
          Root.Courses.Add((Course)resource);
          break;
      default:
          throw new ApplicationException("Unknown containerName");
  }

  return resource;
}

public void SetValue(object targetResource, string propertyName, object propertyValue)
{
  Trace.WriteLine(string.Format("SetValue('{0}', '{1}', '{2})", targetResource, propertyName, propertyValue));

  Type type = targetResource.GetType();
  var property = type.GetProperty(propertyName);
  property.SetValue(targetResource, propertyValue, null);
}

Next let's try an update:

public void UpdateATeacher()
{
  Console.WriteLine("\r\nUpdateATeacher");

  var fredJones = service.Teachers.Where(t => t.ID == 2).Single();

  fredJones.Name = "Fred B Jones";

  service.UpdateObject(fredJones);
  service.SaveChanges();
}

We get this result:

ado_net_im_debug_update

This time the Teacher to be updated is returned by GetResource then SetValue is called to update the Name property. Finally SaveChanges and ResolveResources are called again.

The GetResource implementation is straight from Shawn Wildermuth's LINQ to SQL implementation.

public object GetResource(IQueryable query, string fullTypeName)
{
  Trace.WriteLine(string.Format("GetResource('query', '{0}')", fullTypeName));

  // Get the first result
  var results = (IEnumerable)query;
  object returnValue = null;
  foreach (object result in results)
  {
      if (returnValue != null) break;
      returnValue = result;
  }

  // Check the Typename if needed
  if (fullTypeName != null)
  {
      if (fullTypeName != returnValue.GetType().FullName)
      {
          throw new DataServiceException("Incorrect Type Returned");
      }
  }

  // Return the resource
  return returnValue;
}

Now all I have to do is work on creating relationships between entities, possibly more on this next week.

Code is here:

http://static.mikehadlow.com/Mike.DataServices.InMemory.zip

Wednesday, February 06, 2008

RESTful file uploads with HttpWebRequest and IHttpHandler

I've currently got a requirement to transfer files over a web service. I would normally have used MTOM, but after reading Richardson & Ruby's excellent RESTful Web Services and especially their description of the Amazon S3 service, I decided to see how easy it would be to write a simple file upload service using a custom HttpHandler on the server and a raw HttpWebRequest on the client. It turned out to be extremely simple.

First implement your custom HttpHandler:

public class FileUploadHandler : IHttpHandler
{
    const string documentDirectory = @"C:\UploadedDocuments";

    public bool IsReusable
    {
        get { return false; }
    }

    public void ProcessRequest(HttpContext context)
    {
        string filePath = Path.Combine(documentDirectory, "UploadedFile.pdf");
        SaveRequestBodyAsFile(context.Request, filePath);
        context.Response.Write("Document uploaded!");
    }

    private static void SaveRequestBodyAsFile(HttpRequest request, string filePath)
    {
        using (FileStream fileStream = File.Open(filePath, FileMode.Create, FileAccess.Write))
        using (Stream requestStream = request.InputStream)
        {
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int byteCount = 0;
            while ((byteCount = requestStream.Read(buffer, 0, bufferSize)) > 0)
            {
                fileStream.Write(buffer, 0, byteCount);
            }
        }
    }
}

As you can see, it's simply a question of implementing IHttpHandler. The guts of the operation is in the ProcessRequest message, and all we do is save the input stream straight to disk. The SaveRequestBodyAsFile method just does a standard stream-to-stream read/write.

To get your handler to actual handle a request, you have to configure it in the handers section of the Web.config file, for examle:

<httpHandlers>
 <add verb="*" path="DocumentUploadService.upl" validate="false" type="TestUploadService.FileUploadHandler, TestUploadService"/>
</httpHandlers>

Here I've configured every request for 'DocumentUploadService.upl' to be handled by the FileUploadHandler. There are a couple of more things to do, first, if you don't want to configure IIS to ignore requests for non-existent resources you can put an empty file called 'DocumentUploadService.upl' in the root of your web site. You also need to configure IIS so that requests for .upl files (or whatever extension you choose) are routed to the ASP.NET engine. I usually just copy the settings for .aspx files.

On the client side you can execute a raw HTTP request by using the HttpWebRequest class. Here's my client code:

public void DocumentUploadSpike()
{
    string filePath = @"C:\Users\mike\Documents\somebig.pdf";

    string url = "http://localhost:51249/DocumentUploadService.upl";
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    request.Accept = "text/xml";
    request.Method = "PUT";

    using(FileStream fileStream = File.OpenRead(filePath))
    using (Stream requestStream = request.GetRequestStream())
    {
        int bufferSize = 1024;
        byte[] buffer = new byte[bufferSize];
        int byteCount = 0;
        while ((byteCount = fileStream.Read(buffer, 0, bufferSize)) > 0)
        {
            requestStream.Write(buffer, 0, byteCount);
        }
    }

    string result;

    using (WebResponse response = request.GetResponse())
    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
    {
        result = reader.ReadToEnd();
    }

    Console.WriteLine(result);
}

Here too we simply stream a file straight into the request stream and then call GetResponse on the HttpWebRequest. The last bit just writes the response text to the console. Note that I'm using the HTTP method PUT rather than POST, that's because we're effectively adding a resource. The resource location I'm adding should be part of the URL. For example, rather than:

http://localhost:51249/DocumentUploadService.upl

it should really look more like:

http://localhost:51249/mikehadlow/documents/2345

Indicating that user mikehadlow is saving a file to location 2345. To make it work would simply be a case of implementing some kind of routing (the MVC Framework would be ideal for this).