Is there a clean way to manage my Asp.Net Mvc Web site to both work correctly if javascript is enabled/disabled. Because, for now, I have to do hack like that to make both work. I think that doesn't make code that is easy maintainable and reusable...
if (Request.IsAjaxRequest())
{
return PartialView("SignUpForm", user);
}
else
{
return View("SignUp", user);
}
In this answer I've outlined a modal window technique that works cleanly without javascript; no code changes if you wanted to disable all the modal and javascript functionality.
Simple ASP.NET MVC CRUD views opening/closing in JavaScript UI dialog
The bits that I think are most important for you is custom ViewEngine:
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
//you might have to customize this bit
if (controllerContext.HttpContext.Request.IsAjaxRequest())
return base.FindView(controllerContext, viewName, "Modal", useCache);
return base.FindView(controllerContext, viewName, "Site", useCache);
}
This code turns off the javascript and the surrounding template by loading a separate MasterPage if a request is from ajax or not. By switching the masterpage inside your own custom ViewEngine you avoid the if( Ajax ) code in all of your controllers and keeps thing clean.
Related
My current situation is as follows.
I have a form that when it gets submitted, passes the model into the controller and it does what it needs to do. At the end it redirects to a confirmation page that also gets passed the same model. All of that works fine, except when I am on the confirmation page, whenever I reload the page it resubmits the form.
I've tried using TempData but when I use that it requires for my model to be serializable and my model uses other models inside it that were made by other people that all need to be serializable which would result in like 15-20 different classes all needing to become serializable which just doesnt seem reasonable.
Here is some of what I am working with:
[HttpPost]
public async Task<ActionResult> SubmitClaim(WarrantyClaim model)
{
... code ...
return BeddingWarrantyConfirmation(model);
}
public ActionResult BeddingWarrantyConfirmation(WarrantyClaim model)
{
return View("BeddingWarrantyConfirmation",model);
}
You could make use of the Post Redirect Get Pattern. You could return the following in SubmitClaim:
return RedirectToAction("BeddingWarrantyConfirmation", "CONTROLLER", model);
For more information, please see https://en.wikipedia.org/wiki/Post/Redirect/Get
This is a common problem in MVC development, you can follow the Post/Redirect/Get strategy to avoid the request.
https://en.wikipedia.org/wiki/Post/Redirect/Get
I'm going to try an explain this as best as possible. So, here is my issue.
I have a controller that has two actions (we will call this Controller1 with an Index action) one uses the HttpGet attribute, the other has the HttpPost attribute and both use the Authorize attribute to make sure someone is authenticated.
The Controller1.Index view uses a Partial View which calls a View called _Commands (also on the Controller1 controller). This partial view also has the Authorize attribute associated with it in the controller.
If the user isn't authorized, they are forced to the Login controller (LoginController) which has a Get and Post attributes (Index action) associated with it.
The Controller1.Index calls the partial view through an action called GetRecord. If the record exists, it renders the Partial (returns a PartialView), if it doesn't it pretty much just renders a blank DIV. This is all done through Jquery .ajax calls using POST ajax calls.
However, the issue is when a user sits on the page for a large amount of time and their session expires, and he does an jquery event (say change a drop down that fires the ajax call to update the partial), the partial is rendered with the Login.Index view (and calls the LoginController.Index GET action), where the _Command partial should be.
Now my question is, how can I force the login screen to "kick itself" out of any partial call and then reload as the top page?
I've tried a couple things that DID NOT work. For instance, I tried Request.IsAjaxRequest in the Login GET and it had no effect. I also tried IsChildAction, with no effect. Both returned FALSE.
Anyone else have any ideas how I can work around this issue?
Here's what has worked for me on recent projects.
First, in a global js file I setup the following:
$.ajaxSetup({
error: function (xhr, textStatus, errorThrown) {
//make sure this isn't caused by navigating away from the page by checking the xhr readyState
if (xhr.readyState == 4) {
switch (xhr.status) {
case 401:
case 403:
// take them to the login but hang on to their current url
//calling reload does this by leveraging the rest of our framework automagically!
window.location.reload(true);
break;
default:
bootbox.alert('<div class="text-center"><h2>An error was encountered</h2><h3>Sorry, an error has occurred. The system administrators have been notified.</h3></div>');
break;
}
}
}
});
Obviously a full page reload already handles booting the user back to login, so I just detect a 403 and force the page to reload. There's also some other ajax error handling there, not necessary for what you're requesting.
Now, that 403 isn't the default unauth status, so to make that happen I have a custom auth attribute:
public class AuthorizationRequiredAttribute : AuthorizeAttribute
{
#region Overrides of AuthorizeAttribute
public override void OnAuthorization(AuthorizationContext filterContext)
{
var skipAuthorization =
filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) ||
filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true);
if (skipAuthorization) return;
base.OnAuthorization(filterContext);
//now look to see if this is an ajax request, and if so, we'll return a custom status code
if (filterContext.Result == null) return;
if (filterContext.Result.GetType() == typeof (HttpUnauthorizedResult) &&
filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new ContentResult();
filterContext.HttpContext.Response.StatusCode = 403;
}
}
#endregion
}
There are reasons (that I can't recall now, but pretty sure I got the info from somewhere else on SO) that you can't rely on the standard unauth status code and thus must override it with that 403. Hope this helps!
I'm looking for a few pointers in updating partial areas in MVC3 with razor engine.
Right now, I'm using jquery for the ajax request.
A little context: I have a list of "Collections" which I wrapped in a partial view, just performing a for-each on a list in the model object of the view in question.
I then have a "add collection" button, with just shows a modal dialog box with the forms for adding a new collection. Upon clicking "add" an ajax request creates the collection in the database, and currently returns a JSON object indicating a success along with the string "Collection Created". In my jquery ajax handler, I examine this json object, to check if the status is "success" and then use jquery to display a growl-like notification containt the "Collection Created" string.
Now my question is: Is there anyway I can update my list of collection in this ajax request? Is there anyway I can throw the partial view, iterating the collections, back with the success json object somehow?
Put very simply: I'd like to update HTML somehow, but still maintain the JSON object, so I can display my notification.
Yes, you can render your partial as a string and wrap it up as JSON. I use this method quite a lot but I've heard people saying it's bad practice. So far I haven't experienced any problems and I've been using it for a couple of years.
I actually use a custom ActionResult which returns JSON. It wraps up multiple views and sends them back to the client as an Array of strings. The "not so straight forward" bit is rendering views as strings from within a controller action. Here's some code to do that:
public static string RenderViewToString(ControllerContext controllerContext, string viewPath, ViewDataDictionary viewData, TempDataDictionary tempData)
{
ViewEngineResult result = ViewEngines.Engines.FindPartialView(controllerContext, viewPath);
if (result == null || result.View == null)
throw new Exception("No view found for the following path: " + viewPath);
ViewContext viewContext = new ViewContext(controllerContext, result.View, viewData, tempData, new StringWriter());
HtmlHelper helper = new HtmlHelper(viewContext, new ViewPage());
return helper.Partial(viewPath, viewData).ToHtmlString();
}
You can always make two ajax requests inside a submit handler. Each request would call a different controller action (Create, List).
If that doesn't work for you, have you considered displaying success message inside a partial?
I'm writing a page in MVC3, and there are a few places where I want to request a page in ajax and from the url bar too.
If the request is a full pagerequest I want the Action to render "Example.cshtml" which is a full view.
But is the request is an ajax request I would only want to render the "_Example.cshtml" which is a partial view.
My code is
if (Request.IsAjaxRequest())
{
return PartialView("_Example");
}
return View();
but since the MVC3 is all about conventions and that everything is reconfigurable, I would like to able to write
if (Request.IsAjaxRequest())
{
return PartialView();
}
return View();
and still load the "_Example.cshtml" if it's a partial view.
I name "_Something.cshtml" all my partial views anyway so wouldn't it be cleaner if I could just call PartialView(); ?
Please tell me that this is possible. And tell me how.
EDIT:
I still want to be able to make the partialviews different from views, so switching the masterpage would be enough for only a few cases.
I would like to do something like overloading the default path to look for partialviews, like:
PartialViewLocationFormats = new[] {
"~/Views/{1}/_{0}.ascx",
};
but this affect PartialView("Example") to use _Example.cshtml too, which is undesirable.
EDIT:
An other thing I tried is to overload the controller's PartialView() and PartialView(object model) methods, but they cannot be overridden neither can I find a proper way to find which action were they called from.
Have you seen this? It may help with what you are looking for.
I wrote a pretty substantial answer on this one:
Simple ASP.NET MVC CRUD views opening/closing in JavaScript UI dialog
Basically you adjust the view engine to open full "master themed" views for regular requests and then return the view, but with an empty master, when the request is done via ajax.
You could create an ActionFilterAttribute:
public class AjaxResultActionFilter: ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var result = filterContext.Result as ViewResult;
result.ViewName = "_" + result.ViewName;
filterContext.Result = result;
}
else
{
base.OnResultExecuting(filterContext);
}
}
}
This will append a "_" infront of the ViewName that MVC tries to resolve if the IsAjaxRequest returns true.
All you need to do now is to decorate either your actions or the controller with this attribute:
[AjaxResultActionFilter]
public ActionResult Index()
{
return View("Index");
}
Now, I wrote this on the fly, so in this version it only works when you actually pass the ViewName into the return View() statement.
With some debugging and more time though, I'm sure this could be fixed :)
Hope this helps!
I'm trying to get the master page changed for all my aspx pages. For some reason I'm unable to detect when this function is called for a ascx page instead. Any help in correting this would be appreciated.
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
var action = filterContext.Result as ViewResult;
if (action != null && action.MasterName != "" && Request.IsAjaxRequest())
{
action.MasterName = "Ajax";
}
base.OnActionExecuted(filterContext);
}
If you're still keen on changing the master page based on the fact whether your request is ajax or not - I just accidentally stumbled on exactly the thing you were looking for:
http://devlicio.us/blogs/sergio_pereira/archive/2008/12/05/reusing-views-in-asp-net-mvc-for-ajax-updates.aspx
Basically, instead of overriging the OnActionExecuting method in the BaseController - override the View method! You get exactly the thing you want, with a method that seems specifically designed for it :)
protected override ViewResult View(string viewName, string masterName, object model)
{
return base.View(viewName, Request.IsAjaxRequest() ? "Empty" : masterName, model);
}
So you're saying that MasterPage is empty when you're executing actions to .ascx "pages"?
.ascx's aren't pages, they're UserControls / PartialViews. And as such they don't have master pages. They can be dropped in a mage, or a master page.. But if your request is returning an .ascx, it won't have a master page.. )
UPD:
This is most likely because of the way MVC works - all the 3 parts (M-V-C) are completely independant. Which means that when your code executes inside the controller, we know nothing at all about the view. And the View is the one that selects the master page, not the controller.
Tbh if you're trying to change the way the app looks (change the master page) inside the controller - you're most likely doing something wrong. It was designed with separation of context in the first place, and you're trying to go around it :)
UPD2:
So you're saying that you want to return the full page + master page for regular requests, and just the page without the master (well, clean at least) for ajax requests? You're still trying the wrong approach.
Here's what I've been doing instead:
if (!Request.IsAjaxRequest())
return View(model);
else
return PartialView("PartialName", model);
Exactly the same situation. If I'm loading the url in a browser - it returns the full page, master and all.. If I'm loading it later on, using an ajax call - just load the partial view. Simple and easy. And still adheres to the MVC methodology :)
Also, if you're absolutely keen on preselecting the master name.. just do it like this:
return View("ViewName", "MasterName", model);