Friday, August 8, 2014

ASP.NET MVC External REST Models

If you have a web application that needs to make use of third party services, you may find it useful to make a local model for that object as you process it on the server site. For the example, I’m going to integrate the LibraryThing getwork response for my example application.
First, we want to model the information we want:

public class LTWork {
    public int Id {get; set;}
    public string Author {get; set;}
    public string Title {get; set;}
    public string Url {get; set;}
    public IEnumerable<string> Characters {get; set}
}
As you can see, we’re wanting the basic book stats (title, author, URL, and a list of characters). When we cann the site, we get a REST response that looks like this.
Now comes the time to parse it. Since it’s coming in as XML, we’ll want to parse the XML and extract the appropriate data. We’ll probably want to leverage this in a LibraryThing-specific service. Here’s our basic definition (we’re going to be using IoC containers):

public ILibraryThingService {
    LTWork GetWork(
        int? id, int? isbn, int? lccn, int? oclc, string title
    );
}
public LibraryThingService : ILibraryThingService {
    private static string Url = 
        "http://www.librarything.com/services/rest";
    private static string Version = "1.1";
    private static string ApiKey = "..."; // yeah right…
    private static string BaseUrlBuilder() {
        return String.Join("/", Url, Version);
    }
    private static string CommandUrl(
        string commandName, 
        params KeyValuePair<string, string> arguments
    ) {
        return BaseUrlBuilder() + "/?" 
            + String.Join("&", 
                commandName, 
                arguments.Select(x => x.Key + "=" + x.Value), 
                "apikey=" + ApiKey
            );
    }
    private static string GetResponse(string url) {
        if (String.IsNullOrEmpty(url)) {
            throw new ArgumentNullException(url);
        }
        using (WebClient client = new WebClient()) {
             return client.DownloadString(url);
        }
    }
    private static LTWork ParseWork(string data) {
        if (String.IsNullOrEmpty(data)) {
            throw new ArgumentNullException("data");
        }
        XDocument xdoc = XDocument.Parse(data);
        LTWork ltWork = new LTWork();
        ltWork.Id = Int32.Parse(
            xdoc.Descendants("item")
                .Attributes("id")
                .FirstOrDefault()
                .Value
        );
        ltWork.Author = xdoc.Descendants("author")
            .FirstOrDefault().Value;
        ltWork.Title = xdoc.Descendants("title")
            .FirstOrDefault().Value;
        ltWork.Url = xdoc.Descendants("url").FirstOrDefault().Value;
        ltWork.Characters = xdoc.Descendants("field")
            .Where(x =>
                x.Attribute("type").Value == "3"
                && x.Attribute("name").Value == "characternames"
            )
            .SelectMany(x => x.Elelements("fact").Value);
        return ltWork;
   }
    public LTWork GetWork(
        int? id, int? isbn, int? lccn, int? oclc, string title
    ) {
        if (
            id == null 
            && isbn == null 
            && lccn == null 
            && oclc == null 
            && String.IsNullOrEmpty(title)
        ) {
            throw new ArgumentException(
                "Must specify at least one parameter"
            );
        }
        List<KeyValuePair<string, string>> arguments = 
            new List<KeyValuePair<string, string>>();
        if (id != null) {
            arguments
                .Add(new KeyValuePair("id", id.Value.ToString()));
        }
        if (isbn != null) {
            arguments
                .Add(new KeyValuePair("isbn", isbn.Value.ToString()));
        }
        if (lccn != null) {
            arguments
                .Add(new KeyValuePair("lccn", lccn.Value.ToString()));
        }
        if (oclc != null) {
            arguments
                .Add(new KeyValuePair("oclc", oclc.Value.ToString()));
        }
        if (!String.IsNullOrEmpty(title)) {
            arguments.Add(new KeyValuePair(
                "title", title.Value.Replace(' ', '+')
            ));
        }
        string url = CommandUrl("librarything.ck.getwork", arguments);
        string response = getResponse(url);
        return ParseWork(response);
    }
}
Whew. That looks right…
Okay, so what that’s going to do is send the request to the server, get back a chunk of XML, and then parse out work info and the list of characters from the XML body, then present it in the model. If you so desire, you could then store this to your own database, or just present it life, whatever works best for you and your application (and of course the LibraryThing API terms of service).
If you’re doing this with a JSON API, it’s much easier, but that’s for a different article.

No comments: