17

I have a MVC app with quite a few Controller Actions that are called using Ajax (jQuery) and return partial views content which updates a part of the screen. But what I would rather do is return JSON something like this.

return Json(new { 
    Result = true, 
    Message = "Item has been saved", 
    Content = View("Partial") 
});

Where the HTML is just a property of the Json. What this means is I need to retrieve the HTML that is rendered by the View method. Is there any easy way to do this, a few examples I have seen are quite convoluted.

Edit: This question was originally for ASP.NET MVC 1, but if version 2 makes it easier I would like to hear the answer.

Craig
  • 36,306
  • 34
  • 114
  • 197
  • I changed the tag to asp.net-mvc so people see it in their prefs. – Jonathan Parker May 26 '09 at 01:06
  • Have you found an answer to this? It seems to be becoming quite a popular question. – Jon May 26 '09 at 12:51
  • Not yet. I don't think the NerdDinner answer does what I want. – Craig May 26 '09 at 14:00
  • I agree. There 2 other very similar questions on Stackoverflow both with no answer. http://stackoverflow.com/questions/756797/returning-a-partialview-with-both-html-and-javascript and http://stackoverflow.com/questions/910168/return-partialview-and-javascript-from-controller I'm sure there must be a way. It seems so sensible to want to do this. – Jon May 26 '09 at 14:09
  • A year has gone, did you find out anything? Im struggling with the same here.. – Martin at Mennt Apr 28 '10 at 17:04
  • I didn't find anything useful. I will start a bounty though as maybe ASP MVC 2 has something better. – Craig Apr 28 '10 at 23:13
  • @Craig what's the problem with Jon's answer? – eglasius Apr 30 '10 at 07:53
  • We end up with rewritting asp.net mvc, so RenderPartial returns string (it requires that your view is ascx). If this helps, I can email it to you. – st78 May 04 '10 at 16:23
  • Sergey, that would be good if you can send it to me. Can I repost the code here? – Craig May 05 '10 at 00:15
  • PLease see my answer below. Works like a charm. – Andy May 29 '10 at 10:13

7 Answers7

11

Here is the answer! It is slight change from Martin From's method and it seems to work. If there are things missing please can people contribute any code changes in the comments section. Thanks.

From you controller call it like this:

string HTMLOutput = Utils.RenderPartialToString("~/Views/Setting/IndexMain.ascx", "", items, this.ControllerContext.RequestContext);

Add this to a class

public static string RenderPartialToString(string controlName, object viewData, object model, System.Web.Routing.RequestContext viewContext)
{
     ViewDataDictionary vd = new ViewDataDictionary(viewData);
     ViewPage vp = new ViewPage { ViewData = vd };

     vp.ViewData = vd;
     vp.ViewData.Model = model;
     vp.ViewContext = new ViewContext();
     vp.Url = new UrlHelper(viewContext);

     Control control = vp.LoadControl(controlName);

     vp.Controls.Add(control);

     StringBuilder sb = new StringBuilder();

     using (StringWriter sw = new StringWriter(sb))
     using (HtmlTextWriter tw = new HtmlTextWriter(sw))
     {
         vp.RenderControl(tw);
     }

     return sb.ToString();
}
eglasius
  • 35,831
  • 5
  • 65
  • 110
Jon
  • 38,814
  • 81
  • 233
  • 382
4

NerdDinner has some pretty good examples of this. Here is the SearchController in NerdDinner, which has a method called SearchByLocation that returns a list of JsonDinners (source code for NerdDinner is Creative Commons):

namespace NerdDinner.Controllers {

    public class JsonDinner {
        public int      DinnerID    { get; set; }
        public string   Title       { get; set; }
        public double   Latitude    { get; set; }
        public double   Longitude   { get; set; }
        public string   Description { get; set; }
        public int      RSVPCount   { get; set; }
    }

    public class SearchController : Controller {

        IDinnerRepository dinnerRepository;

        //
        // Dependency Injection enabled constructors

        public SearchController()
            : this(new DinnerRepository()) {
        }

        public SearchController(IDinnerRepository repository) {
            dinnerRepository = repository;
        }

        //
        // AJAX: /Search/FindByLocation?longitude=45&latitude=-90

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult SearchByLocation(float latitude, float longitude) {

            var dinners = dinnerRepository.FindByLocation(latitude, longitude);

            var jsonDinners = from dinner in dinners
                              select new JsonDinner {
                                  DinnerID = dinner.DinnerID,
                                  Latitude = dinner.Latitude,
                                  Longitude = dinner.Longitude,
                                  Title = dinner.Title,
                                  Description = dinner.Description,
                                  RSVPCount = dinner.RSVPs.Count
                              };

            return Json(jsonDinners.ToList());
        }
    }
}
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
  • I am not sure this does what I want. It returns a Json list of items. BTW Robert, I never picked you as a developer while playing for St.Kilda. – Craig May 26 '09 at 01:33
  • I think you have me confused with someone else. :) – Robert Harvey May 26 '09 at 02:10
  • Do I understand correctly (from your original code) that you want to take a view along for the ride when you return the JsonResult? Because it's easy enough to return a single dinner rather than a list of dinners. – Robert Harvey May 26 '09 at 02:14
  • Yes, that is exactly what I want to do. – Craig May 26 '09 at 02:15
1

I've spent ages trying to do the same thing. I have a quick solution which will need to be extended on.

NOTE: I can see one issue already.. Any cookies and other variables are lost :(

What I did:

  1. Create new ActionResult

    public class JsonHtmlViewResult : ViewResult
    {
        public IJsonHtml Data { get; set; }
    
        public override void ExecuteResult(ControllerContext context)
        {
            if (Data == null)
            {
                Data = new DefaultJsonHtml();
            }
    
            using (StringWriter sw = new StringWriter())
            {
                HttpRequest request = HttpContext.Current.Request;
                HttpContext.Current = new HttpContext(request, new HttpResponse(sw));
    
                base.ExecuteResult(context);
    
                Data.HtmlContent = sw.ToString();
            }
    
            // Do the serialization stuff.
            HttpResponseBase response = context.HttpContext.Response;
            response.ClearContent();
            response.ContentType = "application/json";
    
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            response.Write(serializer.Serialize(Data));
        }
    }
    
  2. The Data class

    public interface IJsonHtml
    {
        String HtmlContent { get; set; }
    }
    
    public class DefaultJsonHtml : IJsonHtml
    {
        public String HtmlContent { get; set; }
    }
    
  3. The controller extensions

    public static ActionResult JsonHtmlViewResult(this Controller controller, string viewName, string masterName, object model, IJsonHtml data)
    {
        if (model != null)
        {
            controller.ViewData.Model = model;
        }
    
        return new JsonHtmlViewResult
        {
            Data = data,
            ViewName = viewName,
            MasterName = masterName,
            ViewData = controller.ViewData,
            TempData = controller.TempData
        };
    }
    
Jason Plank
  • 2,336
  • 5
  • 31
  • 40
Chris Kolenko
  • 1,020
  • 17
  • 32
1

I found a more recent answer using Razor that may be helpful http://codepaste.net/8xkoj2

public static string RenderViewToString(string viewPath, object model,ControllerContext context)
{            
    var viewEngineResult = ViewEngines.Engines.FindView(context, viewPath, null);
    var view = viewEngineResult.View;


    context.Controller.ViewData.Model = model;

    string result = String.Empty;
    using (var sw = new StringWriter())
    {

        var ctx = new ViewContext(context, view, 
                                  context.Controller.ViewData, 
                                  context.Controller.TempData, 
                                  sw);
        view.Render(ctx, sw);

        result = sw.ToString();
    }

    return result;
}
Craig
  • 36,306
  • 34
  • 114
  • 197
0

I don not know since which version number you can do this, but nowadays you can return JSON in a very simple way:

public ActionResult JSONaction()
{
    return Json(data, JsonRequestBehavior);
}

no need for elaborate helpers etc.

data is of course your data from your model JsonRequestBehavior specifies whether HTTP GET requests from the client are allowed. (source), is optional DenyGet is default behaviour, so if used mostly JsonRequestBehavior.AllowGet and here is why this is in there

Community
  • 1
  • 1
Daniël Tulp
  • 1,745
  • 2
  • 22
  • 51
0

Why not just have static html "partials" and grab all the dynamic content from the json? You should be able to load the html files with jquery when the page loads or when needed quite easily.

This link on JQuery Ajax gives this example:

//Alert out the results from requesting test.php (HTML or XML, depending on what was returned).
$.get("test.php", function(data){
  alert("Data Loaded: " + data);
});
Jonathan Parker
  • 6,705
  • 3
  • 43
  • 54
  • I could do this but the way ASP MVC works it would be a fair bit more work writing plumbing code. – Craig May 26 '09 at 01:29
0

Craig,

Have a look at this. Jeffery Palermo has written a SubController for ASP.NET MVC that should accomplish what you want:

MvcContrib - now with SubController support for ASP.NET MVC: http://jeffreypalermo.com/blog/mvccontrib-now-with-subcontroller-support/

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501