We'll start with the client site. Just spin up a new MVC 4 project, call it Client. Open up the Index view for the HomeController and wipe everything out. This, will be the view:
<input id="txtBox1" type="text" /> <button id="getData">Get Data</button> <div id="modal"></div> @section scripts { }
Add a new JavaScript files to the Scripts folder, name it Index.js. We'll just be setting up the modal dialog and the AJAX call:
$(document).ready(function() { $('#modal').dialog({ title: "Bazinga", modal: true, autoOpen: false, height: 300, width: 500 }); $('#getData').click(function(e) { $.ajax({ type: 'GET', dataType: 'jsonp', url: 'fill this out later, success: function(data) { $('#modal').html(data.data); $('#modal').dialog("open"); }, error: function(jqXHR, textStatus, errorThrown) { alert(textStatus); alert(errorThrown); } }); }); });
Note the dataType on the AJAX call, jsonp. We will come back and fill out the URL once we set up the other site. If you want, you can use the AJAX $.getJSON method instead, as such:
$.getJSON('http://localhost:12345/Home/Index2?jsoncallback=?', function(data) { $('#modal').html(data.data); $('#modal').dialog("open"); });
To wrap up the Client site, open up BundleConfig in the App_Start folder. We will create a bundle for the scripts we need. Put this right at the top of the RegisterBundles method:
public static void RegisterBundles(BundleCollection bundles) { bundles.Add(new ScriptBundle("~/bundles/app").Include( "~/Scripts/jquery-{version}.js", "~/Scripts/jquery-ui-{version}.js", "~/Scripts/Index.js")); }
Jump over to _Layout.cshtml and just change the jQuery bundle to app...
@Scripts.Render("~/bundles/app")Time to fire up another web project, call this one Widget. To make this work, we will need a JsonpResult. Also, we will be rendering our content from a view as a string. This is a pretty slick technique, and we have Rick Strahl to thank. Take some time and go check out his post to get all the details on it: Rendering ASP.NET MVC Views to String
First, let's take care of the JsonpResult. I added a folder called Extensions, added a new C# file called JsonpResult:
using System; using System.Web; using System.Web.Mvc; public class JsonpResult : JsonResult { public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException(); } HttpRequestBase request = context.HttpContext.Request; HttpResponseBase response = context.HttpContext.Response; string jsoncallback = (context.RouteData.Values["jsoncallback"] as string) ?? request["jsoncallback"]; if (!String.IsNullOrEmpty(jsoncallback)) { if (String.IsNullOrEmpty(base.ContentType)) { base.ContentType = "application/x-javascript"; } response.Write(String.Format("{0}(", jsoncallback)); } base.ExecuteResult(context); if (!String.IsNullOrEmpty(jsoncallback)) { response.Write(")"); } } }
The important part here is the jsoncallback string. We look in Values of RouteData for a jsoncallback string, if we find one, we return, if not, we look in the request. Then we write the content type out. You can see this in Fiddler, let's have a looky look....
using System; using System.Web.Mvc; public class ViewRenderer { protected ControllerContext Context { get; set; } public ViewRenderer(ControllerContext controllerContext) { Context = controllerContext; } public static string RenderPartialView(string viewPath, ControllerContext controllerContext) { ViewRenderer renderer = new ViewRenderer(controllerContext); return renderer.RenderPartialView(viewPath); } public string RenderPartialView(string viewPath) { return RenderViewToStringInternal(viewPath, true); } protected string RenderViewToStringInternal(string viewPath, bool partial = false) { ViewEngineResult viewEngineResult = null; if (partial) { viewEngineResult = ViewEngines.Engines.FindPartialView(Context, viewPath); } else { viewEngineResult = ViewEngines.Engines.FindView(Context, viewPath, null); } if (viewEngineResult == null) { throw new FileNotFoundException("The view could not be found"); } IView view = viewEngineResult.View; string result = null; using (StringWriter sw = new StringWriter()) { ViewContext ctx = new ViewContext(Context, view, Context.Controller.ViewData, Context.Controller.TempData, sw); view.Render(ctx, sw); result = sw.ToString(); } return result; } }
Now to set up the content that will be returned to the client. In the Views folder, add a folder named Tmpl. In the Tmpl folder, add a view named Widget.cshtml. This is just going to be a simple example of sending down some text and JavaScript. Of course you have to be careful, scripting attacks, etc. Anywho, the Widget.cshtml file:
<p>Remote HTML loaded via JSONP.</p> <br /><br /> <input type="button" value="Press Me!" onclick="Message()" /> <script type="text/javascript"> function Message() { alert("Bazinga punk!"); } </script>
The final step is setting up an ActionResult in the HomeController. All we need to do is pass the path of our widget and the controller context to the ViewRenderer, which will return a string. We use this string as the data for our JsonpResult. The GetWidget ActionResult method in the HomeController:
using Widget.Extensions; [HttpGet] public ActionResult GetWidget() { string result = ViewRenderer.RenderPartialView("~/View/Tmpl/Widget.cshtml", ControllerContext); return new JsonpResult() { Data = new { data = result }, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; }
I almost forgot. Run the Widget project. We need to grab the URL and put it in the AJAX call in the Client project. All you will need to change is the port number for the localhost. The completed AJAX call should look as such:
$('#getData').click(function(e) { $.ajax({ type: 'GET', dataType: 'jsonp', url: 'http://localhost:61691/Home/GetWidget?jsoncallback=?', success: function(data) { $('#modal').html(data.data); $('#modal').dialog("open"); }, error: function(jqXHR, textStatus, errorThrown) { alert(textStatus); alert(errorThrown); } }); });
You might be wondering, what the heck is up with that URL? Yes, yes. In order for cross-domain to work, you are required to add the "?jsoncallback=?" bit, that is the key. If you omit that, the call will not work.
Now to test it out. Fire up the Widget project, it will just appear as the default view since we didn't change any of that. Then fire up the Client project upon running....