Mvc OnException when Viewname and Action name is different - asp.net-mvc

I know that there are some types of handling exception on MVC. I choosed creating a base controller and overriding the OnException method. Everything is fine but in one of my controllers i have an post action name different from the view name like below
[HttpPost]
public ActionResult Kaydet(PersonelModel model)
{
var personel = new Personel();
SimpleMapper.PropertyMap(model,personel);
_personelService.Ekle(personel);
model.Id = personel.Id;
model.UyariBilgisi.BildirimTipi=BildirimTipi.Bilgi;
model.UyariBilgisi.UyariMetni = "Kayıt başarıyla eklendi.";
return View("PersonelDetay", model);
}
the view name is PersonelDetay.
Here is my OnException Method
protected override void OnException(ExceptionContext filterContext)
{
Exception exception = filterContext.Exception;
if (exception is NotificationException)
{
ViewModelBase viewModelBase = new ViewModelBase()
{
UyariBilgisi = new UyariBilgisi() { UyariMetni = exception.Message, BildirimTipi = BildirimTipi.Uyari }
};
filterContext.Result = View(viewModelBase);
filterContext.ExceptionHandled = true;
}
}
When the Kaydet method gets an notification exception OnException method works and tries to return "Kaydet" view but there is no view named "Kaydet"
To solve this problem i nedd the view name in OnException event. How can i get viewname? or do i thinking wrong? is there a better way a best practice ?

You could try this...
In your controller add the member variable Dictionary<string, string> _viewNames and then within your constructor initialize the collection using an action name as the key and the name of the view as the value:
_viewNames = new Dictionary<string, string>
{
{ "Kaydet", "PersonelDetay" }
}
Then create a method that will return a view name for the current action:
private string GetViewName()
{
return _viewNames[this.ControllerContext.RouteData.Values["action"].ToString()];
}
Change the return statement in the action to:
return View(GetViewName(), model)
and in OnException replace filterContext.Result with:
filterContext.Result = View(GetViewName(), viewModelBase);
Now this isn't ideal for a number of reasons, e.g. actions returning different views, action name changes have to be reflected when initializing the collection, etc, etc, but it may be useful for your current situation.
If there was a way to register different view names with MVC rather than pass them into the View method, then you could have OnException simply call View(viewModelBase) and have it use your custom named view, from any method, based on the action currently being executed.

Related

Initializing ViewData property inside ViewResult object

When I access ViewData inside a method in the controller, I am able to assign the value in the form of dictionary ie.
ViewData["message"]="this is a custom message";
but I got into a scenario where I was trying to handle the Exception in MVC, here is my code:
public void OnException(ExceptionContext filterContext)
{
if (!filterContext.ExceptionHandled && (filterContext.Exception is ArgumentOutOfRangeException))
{
filterContext.Result = new ViewResult { ViewName = "Error", ViewData = };
filterContext.ExceptionHandled = true;
}
}
Now when handling exception i would like to pass a message to the error page, so tried to access the Result property of the ExceptionContext.
Now
my question is why am I not able to assign a value to the ViewData in
a dictionary-like a format here
filterContext.Result = new ViewResult { ViewName = "Error", ViewData = };
This is also a property returning a ViewDataDictionary object, when am I able to assign a value in the Controller method like this ViewData["message"] = "argument exception error"; the why am I not able to do the same inside the ViewResult object.
I tried it myself and got an understanding on the inner workings of the MVC frameWork, please correct me if I am wrong and please provide an explanation for it, which would make to learn more about the framework.
When I access ViewData inside a method in the controller, I am able to
assign the value in the form of dictionary
This is because when we call the controller and the method, MVC takes responsibilty to assign objects to all the properties, thats the reason we could assign value for the ViewData inside the method.
filterContext.Result = new ViewResult { ViewName = "Error", ViewData =
};
When we are dealing with the ViewData property of the ViewResult class, we cannot assign the value, in this way ViewData["key"]="some value" because it requires ViewDataDictionary object. however we can do this to assign the value like this
var d = new ViewDataDictionary();
d["key"] = "some value";
filterContext.Result = new ViewResult { ViewName = "Error",ViewData=d };

How to get controller name in another class?

I am developing MVC application.
I want to pass controller to some other class for validation purpose.
After passing the controller, I am unable to get the controller name in that class.
[HttpPost]
public ActionResult Create(Location location)
{
if (ModelState.IsValid)
{
Validations v = new Validations();
boolean b;
//passing controller in another class's method
b = v.ValidProperty(location);
if (ValidProperties == true)
{
db.Locations.Add(location);
db.SaveChanges();
return RedirectToAction("Index");
}
}
}
Getting controller in below method
public void ValidProperty(object Controller)
{
//Gives an error in below line
string CtrName = (string)Controller.ToString;
}
How to get the controller Name ?
b = v.ValidProperty(ControllerContext);
you may be wondering where am I initializing ControllerContext variable.
well you don't have to
public void ValidProperty(ControllerContext ControllerContext)
{
// do your logic here.
}
You should call ControllerContext.RouteContext.GetRequiredString("controller")
To get the name of the controller, you can just use
RouteData.Values["controller"]

Changing the view in an ASP.NET MVC Filter

I want to redirect the user to a different view if they are using a mobile browser. I've decided I'd like to do this using MVC filters by applying it to actions which I want to have a mobile view.
I believe this redirect needs to happen in OnActionExecuted, however the filterContext does not contain information on the view - it does, however in OnResultExecuted, but by this time I believe it is too late to change the view.
How can I intercept the view name and change the ViewResult?
This is what I have in the result executed and what I'd like to have work in Action Executed.
public class MobilePageFilter : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if(filterContext.Result is ViewResult)
{
if (isMobileSite(filterContext.HttpContext.Session[SetMobile.SESSION_USE_MOBILE]))
{
ViewResult viewResult = (ViewResult)filterContext.Result;
string viewName = viewResult.ViewName;
filterContext.Result = new ViewResult
{
ViewName = "Mobile/" + viewName,
ViewData = viewResult.ViewData,
TempData = viewResult.TempData
};
}
}
base.OnResultExecuted(filterContext);
}
}
I would recommend you the following blog post which explains a better alternative to achieve what you are asking for rather than using action filters.
This is what I ended up doing, and wrapped up into a reusable attribute and the great thing is it retains the original URL while redirecting (or applying whatever result you wish) based on your requirements:
public class AuthoriseSiteAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Perform your condition, or straight result assignment here.
// For me I had to test the existance of a cookie.
if (yourConditionHere)
filterContext.Result = new SiteAccessDeniedResult();
}
}
public class SiteAccessDeniedResult : ViewResult
{
public SiteAccessDeniedResult()
{
ViewName = "~/Views/SiteAccess/Login.cshtml";
}
}
Then just add the attribute [SiteAccessAuthorise] to your controllers you wish to apply the authorisation access to (in my case) or add it to a BaseController. Make sure though the action you are redirecting to's underlying controller does not have the attribute though, or you'll be caught in an endless loop!

Cleanly handling normal errors

I use this pattern all over the place to grab data from the database and display a view:
public ActionResult Index(int? id)
{
RequestViewModel model;
model = this.ClientRepository.GetRequest(id);
return View("~/Views/Requests/Index.aspx", model);
}
If the repository returns null, which is the case if the record does not exist, then my page craps out and throws an error because the model is null.
I’d like to show a friendly “the requested record cannot be found” message instead of the yellow page of death or a generic “an error occurred” page.
What’s the recommended pattern to handle “normal” errors as opposed to unhandled exceptions?
Thanks,
Rick
You could write an action filter:
public class NullModelCheckerAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var viewResult = filterContext.Result as ViewResultBase;
if (viewResult != null && viewResult.ViewData.Model == null)
{
// If the action selected a view to render and passed a null model
// render the NotFound.aspx view
var result = new ViewResult();
result.ViewName = "~/Views/Errors/NotFound.aspx";
filterContext.HttpContext.Response.StatusCode = 404;
filterContext.Result = result;
}
}
}
And then decorate your base controller (that all your controllers derive from) with this attribute:
[NullModelChecker]
public class BaseController: Controller
{ }
This way your current code stays untouched.
--
UPDATE:
In ASP.NET MVC 3 you could register your action filter globally without even decorating your base controller with it. Simply add the following to your Application_Start in Global.asax:
GlobalFilters.Filters.Add(new NullModelCheckerAttribute());
I'm not familiar with ASP.NET MVC. I'm familiar though with Spring MVC.
Why can't you just put a simple if-else condition? Like this one:
public ActionResult Index(int? id)
{
RequestViewModel model;
model = this.ClientRepository.GetRequest(id);
if (model == null) {
return View("~/Views/Requests/FriendlyError.aspx");
}
return View("~/Views/Requests/Index.aspx", model);
}

ASP.NET MVC: Not executing actionfilters on redirect and throw HttpException

I've created an OnActionExecuted filter to populate some viewmodel attributes with data from db (I'm not using ViewData["somekey"], preferring many ViewModels descending from a common ancestor).
public class BaseController : Controller
{
protected DataClassesDataContext context = new DataClassesDataContext();
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
ViewModel model = (ViewModel) ViewData.Model;
model.IsUserAuthenticated = filterContext.HttpContext.User.Identity.IsAuthenticated;
if (model.IsUserAuthenticated)
{
model.UserName = filterContext.HttpContext.User.Identity.Name;
}
model.CommonAttribute = from c in context.Something select new SomethingElse() {...};
}
}
The problem is that when an action results in a redirect or a 404 error, OnActionExecuted tries to access ViewModel, which has not been initialized. Also, it's completely useless to fill those values, as they will not be used, since another action is going to be called.
How can I avoid filling viewodel on redirect?
A trivial solution would be to not fill in the model when it doesn't exist:
ViewModel model = ViewData.Model as ViewModel;
if (model != null)
{
model.IsUserAuthenticated = filterContext.HttpContext.User.Identity.IsAuthenticated;
if (model.IsUserAuthenticated)
{
model.UserName = filterContext.HttpContext.User.Identity.Name;
}
model.CommonAttribute = from c in context.Something select new SomethingElse() {...};
}

Resources