3

I have burned up google trying to construct some kind of class, that will determine universally if it is a AJAX call, or child action. So that my controllers can determine whether to return a partial view or a full view. So far I hadn't much luck. At the moment I am using the following code to achieve this:

 if (Request.IsAjaxRequest() || ControllerContext.IsChildAction)
            {
                return PartialView();
            }
 return View();

The problem is you have to do this in every action in a controller and every Condition you encounter but I am sure there is a way to achieve this via a helper but cant figure out how. Can you please point me to any links/example code to achieve this.

Edit:

@Aron I have posted a piece of code as whole controller would be too long. But you can see my predicament. The return contains a View and a Object/Model "k".

public ActionResult _Details_Message(int id = 0, int CId = 0)
        {
            ViewBag.MrnSortParm = CId;
            if (id != 0)
            {
                var k = mrn.MRNS.Where(u => u.Id == id).SingleOrDefault();
                if (k.To == User.Identity.Name)
                {
                    if (k.Type == 0) // message
                    {
                        k.Read = true;
                        mrn.Entry(k).State = EntityState.Modified;
                        mrn.SaveChanges();
                    }
                    return PartialView("_Details_Message", k);//replace the above code here
                }
                if (k.From == User.Identity.Name)
                {
                    return PartialView("_Sent", k); //replace the above code here
                }
            }
            var m = new message();
            m.CourierId = CId;
            return PartialView("_Create_Message", m); //replace the above code here
        }

Edit 2 I have found an answer it isn't an helper function but a modification in the view. The link is here . Can mark my own question as duplicate :(

peterh
  • 11,875
  • 18
  • 85
  • 108
Flood Gravemind
  • 3,773
  • 12
  • 47
  • 79

3 Answers3

8

A simple solution could be having similar code that you are using in the _ViewStart.cshtml file under Views folder:

@{
    Layout = Request.IsAjaxRequest() || ViewContext.IsChildAction
        ? null
        : "~/Views/Shared/_Layout.cshtml";
}

With that code you can just return View(); from all your actions.

Since all views are going through that step, that just might be your universal solution.

Dmitry Efimenko
  • 10,973
  • 7
  • 62
  • 79
  • Except if you have multiple Layout files which are set in the View file itself instead of in _ViewStart.cshtml. In that case you will need to do the above several times breaking DRY principles. In those cases you could create a custom WebViewPage class inheriting from `System.Web.Mvc.WebViewPage` – hofnarwillie Jan 08 '15 at 11:47
6

Well you're in luck because I wrote a TON of code to do something similar. This also takes into account if you want to return the model as a JSON object or a View. It also wraps all the Ajax calls into a wrapper response element

Basically if you have a UI guy doing stuff, you NEVER need to know what he wants. Let him write the views, or make AJAX calls. This completely decouples the UI person from the C# developer (as long as he understands how to write MVC Views, he doesn't need to know at all how the controller works, just the model being passed).

The ControllerBase class:

public abstract class MyControllerBase : Controller
{
    // could be moved to web.config
    private const _jsonDataType = "JsonDataType";

    public bool IsAjaxRequest
    {
        get
        {
            return this.HttpContext.Request.IsAjaxRequest();
        }
    }

    public bool IsAjaxHtmlRequest
    {
        get
        {
            return string.Equals(this.Request.Headers[MyControllerBase._jsonDataType], "html", StringComparison.CurrentCultureIgnoreCase);
        }
    }

    private JsonResponse GetAjaxResponse()
    {
        JsonResponse result = new JsonResponse();
        result.IsValid = true;
        return result;
    }

    private JsonResponse<T> GetAjaxResponse<T>(T model)
    {
        JsonResponse<T> result = new JsonResponse<T>();
        result.Data = model;
        result.IsValid = true;
        return result;
    }

    private JsonResponse<string> GetAjaxHtmlResponse()
    {
        JsonResponse<string> result = new JsonResponse<string>();
        result.Data = this.PartialViewToString(this.ControllerContext.RouteData.Values["Action"].ToString(), null);
        result.IsValid = true;
        return result;
    }

    private JsonResponse<string> GetAjaxHtmlResponse<T>(T model)
    {
        JsonResponse<string> result = new JsonResponse<string>();
        result.Data = this.PartialViewToString(this.ControllerContext.RouteData.Values["Action"].ToString(), model);
        result.IsValid = true;
        return result;
    }

    private JsonResponse<string> GetAjaxHtmlResponse<T>(T model, string viewName)
    {
        JsonResponse<string> result = new JsonResponse<string>();
        result.Data = this.PartialViewToString(viewName, model);
        result.IsValid = true;
        return result;
    }

    public ActionResult ViewOrAjax()
    {
        return this.ViewOrAjax(JsonRequestBehavior.DenyGet);
    }

    public ActionResult ViewOrAjax(JsonRequestBehavior jsonRequestBehavior)
    {
        if (this.ControllerContext.IsChildAction)
        {
            return this.PartialView(this.ControllerContext.RouteData.Values["Action"].ToString(), null);
        }

        if (this.IsAjaxRequest)
        {
            if (this.IsAjaxHtmlRequest)
            {
                return this.Json(this.GetAjaxHtmlResponse(), jsonRequestBehavior);
            }
            return this.Json(this.GetAjaxResponse(), jsonRequestBehavior);
        }

        return this.View(this.ControllerContext.RouteData.Values["Action"].ToString(), null);
    }

    public ActionResult ViewOrAjax<T>(T model)
    {
        return this.ViewOrAjax<T>(model, JsonRequestBehavior.DenyGet);
    }

    public ActionResult ViewOrAjax<T>(T model, JsonRequestBehavior jsonRequestBehavior)
    {
        if (this.ControllerContext.IsChildAction)
        {
            return this.PartialView(model);
        }

        if (this.IsAjaxRequest)
        {
            if (this.IsAjaxHtmlRequest)
            {
                return this.Json(this.GetAjaxHtmlResponse(model), jsonRequestBehavior);
            }
            return this.Json(this.GetAjaxResponse<T>(model), jsonRequestBehavior);
        }

        return this.View(model);
    }

    public ActionResult ViewOrAjax<T>(IView view, T model, JsonRequestBehavior jsonRequestBehavior)
    {
        if (this.ControllerContext.IsChildAction)
        {
            return this.PartialView(model);
        }

        if (this.IsAjaxRequest)
        {
            if (this.IsAjaxHtmlRequest)
            {
                return this.Json(this.GetAjaxHtmlResponse(model), jsonRequestBehavior);
            }
            return this.Json(this.GetAjaxResponse<T>(model), jsonRequestBehavior);
        }

        return this.View(view, model);
    }
    public ActionResult ViewOrAjax<T>(string viewName, T model)
    {
        return this.ViewOrAjax<T>(viewName, model, JsonRequestBehavior.DenyGet);
    }
    public ActionResult ViewOrAjax<T>(string viewName, T model, JsonRequestBehavior jsonRequestBehavior)
    {
        if (this.ControllerContext.IsChildAction)
        {
            return this.PartialView(model);
        }

        if (this.IsAjaxRequest)
        {
            if (this.IsAjaxHtmlRequest)
            {
                return this.Json(this.GetAjaxHtmlResponse(model, viewName), jsonRequestBehavior);
            }
            return this.Json(this.GetAjaxResponse<T>(model), jsonRequestBehavior);
        }

        return this.View(viewName, model);
    }
    public ActionResult ViewOrAjax<T>(string viewName, string masterName, T model)
    {
        return this.ViewOrAjax<T>(viewName, masterName, model, JsonRequestBehavior.DenyGet);
    }
    public ActionResult ViewOrAjax<T>(string viewName, string masterName, T model, JsonRequestBehavior jsonRequestBehavior)
    {
        if (this.ControllerContext.IsChildAction)
        {
            return this.PartialView(model);
        }

        if (this.IsAjaxRequest)
        {
            if (this.IsAjaxHtmlRequest)
            {
                return this.Json(this.GetAjaxHtmlResponse(model, viewName), jsonRequestBehavior);
            }
            return this.Json(this.GetAjaxResponse(model), jsonRequestBehavior);
        }

        return this.View(viewName, masterName, model);
    }

    protected internal new ViewResult View(string viewName, string masterName, object model)
    {
        if (model != null)
        {
            ViewData.Model = model;
        }

        ViewResult result = new ViewResult
        {
            ViewName = viewName,
            MasterName = masterName,
            ViewData = ViewData,
            TempData = TempData
        };

        return result;
    }
}

The JsonResponse<> global wrapper for Ajax Calls:

public class JsonResponse
{
    public JsonResponse()
    {
    }

    public bool IsValid { get; set; }
    public bool IsAjaxRequestUnsupported { get; set; }
    public string RedirectTo { get; set; }
    public string CanonicalUrl { get; set; }
}

public class JsonResponse<T> : JsonResponse
{
    public JsonResponse() : base()
    {
    }

    public T Data { get; set; }
}

The Javascript global_getJsonResponse code (require jQuery):

function global_getJsonResult(Controller, View, data, successCallback, completeCallback, methodType, returnType, jsonDataType) {
    if (IsString(Controller)
        && IsString(View)
        && !IsUndefinedOrNull(data)) {
        var ajaxData;
        var ajaxType;

        if (typeof (data) == "string") {
            ajaxData = data;
            ajaxType = "application/x-www-form-urlencoded"
        }
        else {
            ajaxData = JSON.stringify(data);
            ajaxType = "application/json; charset=utf-8";
        }

        var method = 'POST';

        if (methodType) {
            method = methodType;
        }

        var dataType = 'json';
        if (returnType) {
            dataType = returnType;
        }
        var jsonType = 'html';
        if (jsonDataType) {
            jsonType = jsonDataType;
        }

        var jqXHR = $.ajax({
            url: '/' + Controller + '/' + View,
            headers: { JsonDataType: jsonType },
            data: ajaxData,
            type: method,
            dataType: dataType,
            contentType: ajaxType,
            success: function (jsonResult) {
                if (!IsUndefinedOrNull(jsonResult)
                    && jsonResult.hasOwnProperty("RedirectTo")
                    && !IsUndefinedOrNull(jsonResult.RedirectTo)
                    && jsonResult.RedirectTo.length > 0) {
                    $.fn.notify('error', 'Login Expired', 'You have been inactive for a prolonged period of time, and have been logged out of the system.');
                    window.setTimeout(function () { window.location = jsonResult.RedirectTo }, 5000);
                }
                else if (IsFunction(successCallback)) {
                    successCallback(jsonResult, Controller + '/' + View);
                }
            },
            error: function (jqXHR, textStatus, errorThrown) {
                if (errorThrown != 'abort') {
                    $.fn.notify('error', 'Whoops! Something went wrong.', 'We have been notified of the error.'/* textStatus + ': ' + errorThrown*/);
                }

                log('ERROR IN global_getJsonResult() : ', textStatus, errorThrown, jqXHR);
            },
            complete: function (jqXHR, textStatus) {
                if (IsFunction(completeCallback)) {
                    completeCallback(jqXHR, textStatus, Controller + '/' + View);
                }
            }
        });

        return jqXHR;
    }
}

This code supports both server side and client side TimeOuts via Handling session timeout in ajax calls, with a change like:

protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
  if (filterContext.HttpContext.Request.IsAjaxRequest())
  {
    filterContext.Result = new JsonResult
    {
      Data = new JsonResponse<bool>
      {
        IsValid = false,
        RedirectTo = FormsAuthentication.LoginUrl
      },
      JsonRequestBehavior = JsonRequestBehavior.AllowGet
    };
  }
  else
  {
    base.HandleUnauthorizedRequest(filterContext);
  }
}

A couple of Extension Methods on controller to allow you to return rendered partial views as text in json (this code was from SO, I usually document such but I lost it):

internal static class ControllerExtensions
{
  public static string PartialViewToString(this Controller instance, object model)
  {
    string viewName = instance.ControllerContext.RouteData.GetRequiredString("action");

    return ControllerExtensions.PartialViewToString(instance, viewName, model);
  }

  public static string PartialViewToString(this Controller instance, string viewName, object model)
  {
    string result;

    ViewDataDictionary viewData = instance.ViewData;
    viewData.Model = model;

    using (var sw = new StringWriter())
    {
      var viewResult = ViewEngines.Engines.FindPartialView(instance.ControllerContext, viewName);

      var viewContext = new ViewContext(instance.ControllerContext, viewResult.View, viewData, instance.TempData, sw);
      viewResult.View.Render(viewContext, sw);

      viewResult.ViewEngine.ReleaseView(instance.ControllerContext, viewResult.View);
      result = sw.GetStringBuilder().ToString();
}

    return result;
  }
}

Now derive (sadly) all your controllers from this base controller:

public HomeController : MyBaseController
{
  public ActionResult Index()
  {
    var viewModel = new MyViewModel();

    return this.ViewOrAjax(viewModel);
  }
}

Now if the page is called by the browser as your standard get, you get the page rendered normally with a Layout (aka this.View(viewModel)).

If you call it using Ajax via the Javascript:

global_getJsonResult("Home",  // Controller or 'Area/Home' for areas
  "Index",                    // View
  $('#form').serialize(),     // Json object or a serialized Form
  jsCallBack,                 // call back function or null
  "Post",                     // Get or Post
  "Html");                    // "Html" to return a Partial View in "Data" 
                              // or "Json" to return a serialized view model in "Data"
Community
  • 1
  • 1
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
1

Improvement on Dmitry's answer:

Create a custom WebViewPage class so that you do not need to add the modification into multiple Views (relevant where there are multiple Layout files which are determined by the View itself instead of the _ViewStart file)

public abstract class CustomWebViewPage: WebViewPage
{
    public override string Layout
    {
        get
        {
            return Request.IsAjaxRequest() || ViewContext.IsChildAction ? null : base.Layout;
        }
        set
        {
            base.Layout = value;
        }
    }
}

public abstract class CustomWebViewPage<TModel>: CustomWebViewPage
{
}

And in the web.config (under the Views folder)

<pages pageBaseType="Fully.Qualified.Namespace.CustomWebViewPage">
hofnarwillie
  • 3,563
  • 10
  • 49
  • 73