开发者

Json response does not output in view, but prompts me to download the file instead

I am using WebRequest to read JSON data from the FCC so I can output it to a view. Here is my custom class to hold an FCC license:

     public class License
        {
            public string Name{ get; set; }
            public string Frn { get; set; }
            public string Callsign { get; set;}
            public string CategoryDesc { get; set; }
            public string ServiceDesc { get; set; }
            public string StatusDesc { get; set; }
            public DateTime ExpiredDate { get; set; }
            public string Id { get; set; }
            public string DetailUrl { get; set; }

        }

Here is the Controller action that I am using to read the json results.

I have Verizon Wireless hard-coded as the search value for now:

public ActionResult GetLicenses()
        {
            var result = string.Empty;
            var url = "http://data.fcc.gov/api/license-view/basicSearch/getLicenses?searchValue=Verizon+Wireless&format=jsonp&jsonCallback=?";

            var webRequest = WebRequest.Create(url);

            webRequest.Timeout = 2000;

            using (var response = webRequest.GetResponse() as HttpWebResponse)
            {
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    var receiveStream = response.GetResponseStream();
                    if (receiveStream != null)
                    {
                        var stream = new StreamReader(receiveStream);
                        result = stream.ReadToEnd();
                    }
                }
            }
            return new ContentResult { Content = result, ContentType = "application/json" };

        }

Here is the view. I am trying to enumerate through all the licenses and output them to a table, but when I go to /Home/GetLicenses, it prompts me to download the file:

@model IEnumerable<MvcApplication1.Models.License>

@{
    ViewBag.Title = "Licenses";
}

<h2>Licenses</h2>

<table>
    <tr>
        <th>
            Name
        </th>
        <th>
            Frn
        </th>
        <th>
            Callsign
        </开发者_Python百科th>
        <th>
            CategoryDesc
        </th>
        <th>
            ServiceDesc
        </th>
        <th>
            StatusDesc
        </th>
        <th>
            ExpiredDate
        </th>
        <th>
            DetailUrl
        </th>
        <th></th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Frn)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Callsign)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.CategoryDesc)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.ServiceDesc)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.StatusDesc)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.ExpiredDate)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.DetailUrl)
        </td>
    </tr>
}

</table>

I got the above working if I do it directly through jquery's getJSON method, but I wanted to see if I could get the results from a contoller to a view and then have it rendered in the view.

This is a sample of what is returned in the results variable:

?({
    "status": "OK",
    "Licenses": {
        "page": "1",
        "rowPerPage": "100",
        "totalRows": "1995",
        "lastUpdate": "Sep 21, 2011",
        "License": [
            {
                "licName": "CELLCO PARTNERSHIP (\"VERIZON WIRELESS\")",
                "frn": "",
                "callsign": "",
                "categoryDesc": "Satellite Earth Station",
                "serviceDesc": "",
                "statusDesc": "Active",
                "expiredDate": "",
                "licenseID": "2300007967",
                "licDetailURL": "http://licensing.fcc.gov/cgi-bin/ws.exe/prod/ib/forms/reports/swr031b.hts?prepare=&column=V_SITE_ANTENNA_FREQ.file_numberC/File+Number&q_set=V_SITE_ANTENNA_FREQ.file_numberC/File+Number/=/FCNNEW2000060800036"
            },
            {
                "licName": "CELLO PARTNERSHIP (\"VERIZON WIRELESS\")",
                "frn": "",
                "callsign": "",
                "categoryDesc": "Satellite Earth Station",
                "serviceDesc": "",
                "statusDesc": "Active",
                "expiredDate": "",
                "licenseID": "2300010661",
                "licDetailURL": "http://licensing.fcc.gov/cgi-bin/ws.exe/prod/ib/forms/reports/swr031b.hts?prepare=&column=V_SITE_ANTENNA_FREQ.file_numberC/File+Number&q_set=V_SITE_ANTENNA_FREQ.file_numberC/File+Number/=/FCNNEW2000083100048"
            },
            {
                "licName": "Cellco Partnership d/b/a Verizon Wireless",
                "frn": "0003290673",
                "callsign": "KE2XMC",
                "categoryDesc": "Experimental",
                "serviceDesc": "Experimental Developmental",
                "statusDesc": "Unknown",
                "expiredDate": "12/14/2000",
                "licenseID": "3000020853",
                "licDetailURL": "https://fjallfoss.fcc.gov/oetcf/els/reports/ELSSearchResult.cfm?callsign=KE2XMC"
            },
            {
                "licName": "Cellco Partnership d/b/a Verizon Wireless",
                "frn": "0003290673",
                "callsign": "WA2XPS",
                "categoryDesc": "Experimental",
                "serviceDesc": "Experimental Developmental",
                "statusDesc": "Unknown",
                "expiredDate": "12/14/2000",
                "licenseID": "3000020851",
                "licDetailURL": "https://fjallfoss.fcc.gov/oetcf/els/reports/ELSSearchResult.cfm?callsign=WA2XPS"
            },
            {
                "licName": "Cellco Partnership dba Verizon Wireless",
                "frn": "0003290673",
                "callsign": "KNKP866",
                "categoryDesc": "Mobile/Fixed Broadband",
                "serviceDesc": "Cellular",
                "statusDesc": "Cancelled",
                "expiredDate": "10/01/2005",
                "licenseID": "13328",
                "licDetailURL": "http://wireless2.fcc.gov/UlsApp/UlsSearch/license.jsp?__newWindow=false&licKey=13328"
            }
        ]
    }
})

I added this class:

 public class FCC
    {
        public string status { get; set; }
        public Licenses Licenses { get; set; }

    }

But I still get the Invalid JSON primitive.

 public ActionResult GetLicenses()
        {
            var result = string.Empty;
            var url =
                "http://data.fcc.gov/api/license-view/basicSearch/getLicenses?searchValue=Verizon+Wireless&format=jsonp&jsonCallback=?";

            var webRequest = WebRequest.Create(url);

            webRequest.Timeout = 2000;
            webRequest.ContentType = "application/json";

            using (var response = webRequest.GetResponse() as HttpWebResponse)
            {
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    var receiveStream = response.GetResponseStream();
                    if (receiveStream != null)
                    {
                        var stream = new StreamReader(receiveStream);
                        result = stream.ReadToEnd();
                    }
                }
            }

            FCC fcc = new FCC();

            if (result.StartsWith(@"?("))
            {
                result = result.Substring(2);
            }

            if (result.EndsWith(@")"))
            {
                result = result.Remove(result.Length - 1);
            }


            if (result != null)
            {
                JavaScriptSerializer serializer = new JavaScriptSerializer();
                fcc = serializer.Deserialize<FCC>(result);
            }
            return View(fcc.Licenses.License);


        }


By returning a ContentResult from your ActionMethod, your browser is going to respond with the appropriate action based upon the content. In this case a JSON string will be downloaded similar to a file since it is not a HTML doc.

If you want to render the results in a View and not through AJAX, then you will need to create a C# Model class representing your WebRequest response data, and then return a ViewResult and pass the model (or collection of models) to the View.

I would suggest changing your ActionMethod to do something like below, and also create a View called "Licenses"

Also, your example response is a bit of a trick. It is more complex than an array of your original License Object, AND it is wrapped with a ?(). The JavaScriptSerializer will only deserialize properties that it can match based on property name (it is case sensitive as well). And because of the ?() wrapping, we need to remove that so the deserialization won't break.

So you would need to modify your License object accordingly:

public class FCC
{
    public string status {get;set;}
    public Licenses Licenses {get; set;}
}
public class License
{
    public string licName{ get; set; }
    public string frn { get; set; }
    public string callsign { get; set;}
    public string categoryDesc { get; set; }
    public string serviceDesc { get; set; }
    public string statusDesc { get; set; }
    public string expiredDate { get; set; } //JSON dates and C# Dates are very finicky
    public string licenseID { get; set; }
    public string licDetailURL { get; set; }
}

public class Licenses
{
    public int page {get; set;}
    public int rowPerPage {get; set;}
    public int totalRows {get; set;}
    public string lastUpdate {get; set;}
    public List<License> License {get; set;}
}


public ActionResult Licenses()
{
        var result = string.Empty;
        var url = "http://data.fcc.gov/api/license-view/basicSearch/getLicenses?searchValue=Verizon+Wireless&format=jsonp&jsonCallback=?";

        var webRequest = WebRequest.Create(url);

        webRequest.Timeout = 2000;

        using (var response = webRequest.GetResponse() as HttpWebResponse)
        {
            if (response.StatusCode == HttpStatusCode.OK)
            {
                var receiveStream = response.GetResponseStream();
                if (receiveStream != null)
                {
                    var stream = new StreamReader(receiveStream);
                    result = stream.ReadToEnd();
                }
            }
        }

        FCC fcc = new FCC();

        if (result.StartsWith(@"?("))
        {
            result = result.Substring(2);
        }

        if (result.EndsWith(@")"))
        {
            result = result.Remove(result.Length - 1);
        }

        if(result != null)
        {
             JavaScriptSerializer serializer = new JavaScriptSerializer();
             fcc = serializer.Deserialize<FCC>(result);
        }
        return View(fcc.Licenses.License); //pass the data that your view needs

}

Lastly, you will need to change the property names in your CSHTML file, since the new License object doesn't have the same property names anymore.

AJAX is probably another question, but I'll post back here if I run across a good example


There are a couple of issues with your code which I will try to address:

  1. You have a controller action which returns a JSON string and you have defined some Razor view but this razor view is never invoked from the action.
  2. You are querying a remote service which returns JSONP instead of using the JSON possibility of the API which seems more adapted.
  3. You are jeopardizing a worker thread during the fetching of the remote resource.

So let's start by our view models:

public class License
{
    public string Name { get; set; }
    public string Frn { get; set; }
    public string Callsign { get; set; }
    public string CategoryDesc { get; set; }
    public string ServiceDesc { get; set; }
    public string StatusDesc { get; set; }
    public DateTime ExpiredDate { get; set; }
    public string Id { get; set; }
    public string DetailUrl { get; set; }
}

public class Licenses
{
    public License[] License { get; set; }
}

public class FCC
{
    public string status { get; set; }
    public Licenses Licenses { get; set; }
}

then we would have the following controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        using (var client = new WebClient())
        {
            var json = client.DownloadString("http://data.fcc.gov/api/license-view/basicSearch/getLicenses?searchValue=Verizon+Wireless&format=json");
            var serializer = new JavaScriptSerializer();
            var model = serializer.Deserialize<FCC>(json);
            return View(model.Licenses.License);
        }
    }
}

Notice how in the url I am no longer specifying the jsonCallback querystring parameter which is intended to be used with JSONP and I don't want JSONP, I want JSON. For that matter I have also set the format=json parameter.

And finally we could have the following ~/Views/Home/Index.cshtml view:

@model IEnumerable<License>

<table>
    <thead>
        <tr>
            <th>
                Name
            </th>
            <th>
                Frn
            </th>
            <th>
                Callsign
            </th>
            <th>
                CategoryDesc
            </th>
            <th>
                ServiceDesc
            </th>
            <th>
                StatusDesc
            </th>
            <th>
                ExpiredDate
            </th>
            <th>
                DetailUrl
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @Html.DisplayForModel()
    </tbody>
</table>

and the corresponding display template which will be rendered for each element of the Licenses collection (~/Views/Home/DisplayTemplates/License.cshtml):

@model License

<tr>
    <td>
        @Html.DisplayFor(x => x.Name)
    </td>
    <td>
        @Html.DisplayFor(x => x.Frn)
    </td>
    <td>
        @Html.DisplayFor(x => x.Callsign)
    </td>
    <td>
        @Html.DisplayFor(x => x.CategoryDesc)
    </td>
    <td>
        @Html.DisplayFor(x => x.ServiceDesc)
    </td>
    <td>
        @Html.DisplayFor(x => x.StatusDesc)
    </td>
    <td>
        @Html.DisplayFor(x => x.ExpiredDate)
    </td>
    <td>
        @Html.DisplayFor(x => x.DetailUrl)
    </td>
</tr>

OK, so far we have addresses point 1. and 2.

Now the third one. The problem with this synchronous call is the following line: client.DownloadString. This is a blocking call. Blocking calls on remote resources are very bad in ASP.NET applications. Here you are fetching some remote resource which could take time => you will be traversing network boundaries, internet firewalls, ... until you hit the remote web server which itself, in order to serve the request will query a database, ... You get the point: it is slow. And during all this time your web application is sitting and waiting and a thread was monopolized. Remember that you have a limited number of worker threads at your disposal so don't waste them.

The way to solve this very serious issue is to use asynchronous controllers and I/O Completion Ports. Those are built directly into windows kernel and allow you to perform IO intensive operations without blocking and monopolizing threads on your server.

Here's how your HomeController will become:

public class HomeController : AsyncController
{
    public void IndexAsync()
    {
        var client = new WebClient();
        AsyncManager.OutstandingOperations.Increment();
        client.DownloadStringCompleted += (s, e) => 
        {
            AsyncManager.OutstandingOperations.Decrement();
            if (e.Error != null)
            {
                AsyncManager.Parameters["error"] = e.Error.Message;
            }
            else
            {
                var serializer = new JavaScriptSerializer();
                var model = serializer.Deserialize<FCC>(e.Result);
                AsyncManager.Parameters["licenses"] = model.Licenses.License;
            }
        };
        client.DownloadStringAsync(new Uri("http://data.fcc.gov/api/license-view/basicSearch/getLicenses?searchValue=Verizon+Wireless&format=json"));
    }

    public ActionResult IndexCompleted(License[] licenses, string error)
    {
        if (!string.IsNullOrEmpty(error))
        {
            ModelState.AddModelError("licenses", error);
        }
        return View(licenses ?? Enumerable.Empty<License>());
    }
}


Here is what i did and its showing me Json data in DIV...

Your Controller...

public JsonResult GetLicenses()
        {
            var result = string.Empty;
            const string url = "http://data.fcc.gov/api/license-view/basicSearch/getLicenses?searchValue=Verizon+Wireless&format=jsonp&jsonCallback=?";

            var webRequest = WebRequest.Create(url);

            webRequest.Timeout = 2000;

            using (var response = webRequest.GetResponse() as HttpWebResponse)
            {
                if (response != null && response.StatusCode == HttpStatusCode.OK)
                {
                    var receiveStream = response.GetResponseStream();
                    if (receiveStream != null)
                    {
                        var stream = new StreamReader(receiveStream);
                        result = stream.ReadToEnd();

                    }
                }
            }
            return Json(result,JsonRequestBehavior.AllowGet);

make the return type Json and type JsonResult in Action

and if you want to call this through AJAX then here is the

$.ajax

<script type="text/javascript">
    $(document).ready(function () {
        $.ajax({
            type: 'GET',
            url: '@Url.Action("GetLicenses","Home")',
            success: function (data) {
                $('#content').html(data);
            },
            error: function (data) {
                $('#content').append(data);
            }
        });
    });

and make sure that you reference the Jquery files in _Layout.cshtml..

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜