Primary Objectives:

  1. Be able to write a MVC web application that employs AJAX to build responsive views
  2. Learn how to use an existing external API to acquire data, serverside
  3. Demonstrate the use of JSON and AJAX
  4. Demonstrate custom routing URLs

Overall Requirements:

  • This application should have only one page (main view)
  • All functionality is created with Javascript and AJAX calls to action methods that don't return views (ok, you'll have to have one view for the main page itself)
  • Use JSON as the data exchange format
  • All Javascript must be placed into a separate file (in the Scripts folder) and included via a @section section
  • Use a different controller (not Home) for AJAX calls, that fits with your custom routing (below)
  • Write custom routing rule(s) in App_Start\RouteConfig.cs for retrieving data via AJAX with appropriate REST-like URLs
  • Use jQuery
  • Your Giphy API key must not be anywhere in your project source code or Git repository! It absolutely may not be sent to the client browser either.
  • You may not use any NuGet packages to implement AJAX for you (i.e. Microsoft.jQuery.Unobtrusive.Ajax ). You must do it yourself manually with regular action methods.

"To make sure I'm keeping up with the times I want to build a web page that uses Javascript and asynchronous calls to the web server to create a responsive design solution for a simple problem."

This homework is all about creating a responsive, single-page application. An extreme case of this kind of application is the GMail client. We don't have those kinds of aspirations, but we can definitely get the main process down.

So the page that first loads (from the server) doesn't have any data. It presents the interface only. But it does load your Javascript, which requests data from your server via asynchronous web requests and then builds the page content as desired as the requests come back from your server. Users then interact with the page and more asynchronous calls are placed to send data to and receive data back from your server. The benefit is that, perhaps surprisingly, the time to load HTML, CSS and associated resources, and build and render a DOM, is actually significant. Not doing those things, while using Javascript to retrieve data and modify the DOM, often creates a more responsive page for the user. We want to learn about this as a viable option for building parts of your project next year.

Here's some good documentation from the Mozilla Developer Network. But note, that's plain Javascript. As usual, we'll use jQuery to make it easier.

A second purpose of this homework is to learn how to use an existing API as a resource. Ideally we would use a real REST API like those offered by Lyft (Docs), Twitter (Docs) or Trello (Docs). It is certainly possible to use these. But, examples like these require you to apply for a developer api key or use more advanced authentication methods and additionally be really responsible in how you use it. Yes, of course we'll be responsible, but it would be better to choose something a bit less risky, and easier. The one we'll use isn't a comprehensive REST API, but that's OK. It's safe, kind of silly, easy and you can have your api key in seconds.

We will use an API from Giphy, the world's precious repository of cat and internet meme gifs.

Questions/Tasks:

  1. [Setup] Start by creating a simple MVC web application. You want to start with the minimal "Empty" example this time, but check the box to add folders and core references for MVC.

  2. [Content/Coding] Create just a single controller and view that creates one page with a text input. This will be the only view and the only page for the entire HW. Get rid of the nav bar.

    Wireframe of initial design
    Figure 1: The initial simple design.
  3. [Setup] Next up, go to Giphy and register as a developer. Create an application and they'll give you your API key which you will use to craft GET requests. This key is a secret; treat it like a password. Don't put it anywhere in your source code and certainly not in your repository anywhere. (I'll show you in class how to hide this secret from your repo and still use it in your code.)

    Read their documentation and try out their API Explorer so you understand how to use their API. We'll use their Sticker API and the Translate endpoint. Try out some searches with your key, with their API Explorer, Postman and a browser. I find that using Firefox Developers Edition is really nice for this because it parses the JSON and displays it in tree form with syntax highlighting.

    Get to know the correct format and terms for a search as well as the JSON data that is returned.

    For example, here is an object that is returned by searching for “lobster” with this request

    GET https://api.giphy.com/v1/stickers/translate?api_key=XXXYourKeyHereXXX&s=lobster

    {
    "data":
    {
        "type": "gif",
        "id": "7hqG8IJQRU1Qk",
        "slug": "weird-lobster-transparent-7hqG8IJQRU1Qk",
        "url": "https://giphy.com/stickers/weird-lobster-transparent-7hqG8IJQRU1Qk",
        "bitly_gif_url": "https://gph.is/1aSHDAX",
        "bitly_url": "https://gph.is/1aSHDAX",
        "embed_url": "https://giphy.com/embed/7hqG8IJQRU1Qk",
        "username": "scottgelber",
        "source": "https://time-cop.tumblr.com/post/49629120885/noah-spidermen-macbook-prawn-2013-animated-gif",
        "rating": "g",
        "content_url": "",
        "source_tld": "time-cop.tumblr.com",
        "source_post_url": "https://time-cop.tumblr.com/post/49629120885/noah-spidermen-macbook-prawn-2013-animated-gif",
        "is_sticker": 1,
        "import_datetime": "2013-11-03 00:18:48",
        "trending_datetime": "2017-06-26 17:34:10",
        "user":
        {
            "avatar_url": "https://media.giphy.com/avatars/scottgelber/3opUDVNqoB0y.gif",
            "banner_url": "",
            "profile_url": "https://giphy.com/scottgelber/",
            "username": "scottgelber",
            "display_name": "Scott Gelber",
            "guid": "Z2VsYmVyLnNjb3R0QGdtYWlsLmNvbQ",
            "is_verified": true
        },
        "images":
        {
            "fixed_height_still":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/200_s.gif",
                "width": "216",
                "height": "200",
                "size": "11676"
            },
            "original_still":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/giphy_s.gif",
                "width": "400",
                "height": "371",
                "size": "36390"
            },
            "fixed_width":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/200w.gif",
                "width": "200",
                "height": "186",
                "size": "269913",
                "mp4": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/200w.mp4",
                "mp4_size": "38405",
                "webp": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/200w.webp",
                "webp_size": "251982"
            },
            "fixed_height_small_still":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/100_s.gif",
                "width": "108",
                "height": "100",
                "size": "4633"
            },
            "fixed_height_downsampled":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/200_d.gif",
                "width": "216",
                "height": "200",
                "size": "50611",
                "webp": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/200_d.webp",
                "webp_size": "45910"
            },
            "preview":
            {
                "width": "228",
                "height": "210",
                "mp4": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/giphy-preview.mp4",
                "mp4_size": "43832"
            },
            "fixed_height_small":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/100.gif",
                "width": "108",
                "height": "100",
                "size": "99844",
                "mp4": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/100.mp4",
                "mp4_size": "15908",
                "webp": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/100.webp",
                "webp_size": "101130"
            },
            "downsized_still":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/giphy-downsized_s.gif",
                "width": "400",
                "height": "371",
                "size": "36390"
            },
            "downsized":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/giphy-downsized.gif",
                "width": "400",
                "height": "371",
                "size": "1021609"
            },
            "downsized_large":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/giphy.gif",
                "width": "400",
                "height": "371",
                "size": "1021609"
            },
            "fixed_width_small_still":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/100w_s.gif",
                "width": "100",
                "height": "93",
                "size": "4227"
            },
            "preview_webp":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/giphy-preview.webp",
                "width": "155",
                "height": "144",
                "size": "49166"
            },
            "fixed_width_still":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/200w_s.gif",
                "width": "200",
                "height": "186",
                "size": "10636"
            },
            "fixed_width_small":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/100w.gif",
                "width": "100",
                "height": "93",
                "size": "88287",
                "mp4": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/100w.mp4",
                "mp4_size": "14638",
                "webp": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/100w.webp",
                "webp_size": "90880"
            },
            "downsized_small":
            {
                "width": "400",
                "height": "370",
                "mp4": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/giphy-downsized-small.mp4",
                "mp4_size": "153039"
            },
            "fixed_width_downsampled":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/200w_d.gif",
                "width": "200",
                "height": "186",
                "size": "45280",
                "webp": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/200w_d.webp",
                "webp_size": "41440"
            },
            "downsized_medium":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/giphy.gif",
                "width": "400",
                "height": "371",
                "size": "1021609"
            },
            "original":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/giphy.gif",
                "width": "400",
                "height": "371",
                "size": "1021609",
                "frames": "41",
                "mp4": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/giphy.mp4",
                "mp4_size": "180633",
                "webp": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/giphy.webp",
                "webp_size": "895734",
                "hash": "847a68e3bb9b70a191b84d87700388fe"
            },
            "fixed_height":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/200.gif",
                "width": "216",
                "height": "200",
                "size": "301842",
                "mp4": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/200.mp4",
                "mp4_size": "42016",
                "webp": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/200.webp",
                "webp_size": "278668"
            },
            "looping":
            {
                "mp4": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/giphy-loop.mp4",
                "mp4_size": "1448551"
            },
            "original_mp4":
            {
                "width": "480",
                "height": "444",
                "mp4": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/giphy.mp4",
                "mp4_size": "180633"
            },
            "preview_gif":
            {
                "url": "https://media2.giphy.com/media/7hqG8IJQRU1Qk/giphy-preview.gif",
                "width": "143",
                "height": "133",
                "size": "48866"
            },
            "480w_still":
            {
                "url": "https://media1.giphy.com/media/7hqG8IJQRU1Qk/480w_s.jpg",
                "width": "480",
                "height": "445"
            }
        },
        "title": "weird computer STICKER by Scott Gelber",
        "_score": 1111013.2,
        "_topScoreQuotient": 0.9999963996957
    },
    "meta":
    {
        "status": 200,
        "msg": "OK",
        "response_id": "5bdc8b284b79706e59cad7cc"
    }
    }

    You get a JSON object which contains a bunch of information about a single sticker that "matches" the translation of your word. Here's that sticker as a small gif: Giphy sticker that translates from lobster. I used the static version because seeing all these animated gifs drives me nuts. Also inside is id, title, rating, source, and URL's for many versions of the image.

    To preview what we're doing, this JSON object is what you get at your server. You'll use C# to parse this, extract what's needed and build a JsonResult to send to your client.

  4. [Requirements] Now here's the plan.

    1. The user begins typing a phrase or sentence, for example "I'm going to walk my lobster".
    2. You have previously registered a Javascript callback function on the text input in order to run custom Javascript code.
    3. Your code waits for a space to be typed, which indicates a word has been typed. If the word is a "boring" word then just output it directly below. But if it's "interesting" we want to find a sticker translation for it. (I'll leave the defintion of boring and interesting up to you.) If a word needs a translation your Javascript makes an AJAX call to a controller action method, passing it the word that was just typed.
    4. Your action method assembles a GET request (using your secret API key and the Sticker API Translate endpoint) and sends it off to Giphy's servers. Your action method receives a JSON response to your server containing a match to the search word you're asking Giphy to "translate" for you. This is in the C# code.
    5. Using this, extract from the JSON object and assemble whatever data is needed in order for your client Javascript to display a sticker for the word. You send this data from the action method to the client as a JsonResult object, which is then processed by your Javascript code to modify the DOM and display the sticker image.
    6. Other than the initial page load of an empty search box, all UI triggered communication between the client and your server must be via JSON. We're never requesting and returning an entirely new page (view).
    7. The user then continues to type and common words are moved to the bottom, while interesting ones are translated and appear as images. You just keep doing this all this over again and the page never reloads -- it's dynamic. I suspect you'll want to reset the text box to empty at some point, or add a clear button to keep it tidy. That kind of thing is up to you. As is the annoy factor: use all animated gifs? or just one or two. That's up to you.
    8. For the AJAX call, use jQuery's ajax or getJSON method.
    9. Your controller action method for handling the AJAX request needs to use a custom route (in AppStart/RouteConfig.cs ) with an API feel. For example: ~/translate/lobster actually routes to the TranslateApi controller and the Sticker action method. We want to show that we can do custom routing rather than the default routing.
    Wireframe of initial design
    Figure 2: After typing some words, here's what it might look like. One AJAX call has been made from the client to the server, and the server has made one request to Giphy. This is just a wireframe image; in the original the cat is walking.
    Wireframe of initial design
    Figure 3: After typing more words, here's what it might look like. One more AJAX call has been made to the server.
  5. [Content/Coding] Add a database to your project to keep a log of search requests. Make a table that will hold information about requests made to your server. When a request comes in, add an entry to the table with information about the request. You should log things like date/time, what the request was, the IP address of the requestor and the client's browser agent type.

    Logging is a very common activity that is very useful when testing code. In production you'd probably only log requests that returned error codes, but we'd like to record everything.

  6. [Portfolio Content] And as usual, write it all up in your Portfolio. This one definitely needs a video of your application in use and a screenshot showing your log table accumulating data.