mvc ActionResult is not return to anotherView - asp.net-mvc

Here I have two methods.
Login method
public ActionResult Login(LoginCrediential ObjCrendentials)
{
if (x == true)
return RedirectToAction("RoutingAction");
else
return View("Index");
}
RoutingAction method
public ActionResult RoutingAction()
{
if (User.IsInRole("Admin"))
return View("Admin/AdminHome/index");
else if (User.IsInRole("Hr"))
return View();
else
return View();
}
When my if condition Is true in the Login method, its navigating to RoutingAction() but throwing the following error
Admin/AdminHome/index or its master was not found or no view engine supports the searched locations
In url its showing as http://localhost:3013/Home/RoutingAction
Please help me how I can solve this issue.

Well, It's only going to search the Views folder of your controller (Home) and the Shared views. However, you should be able to specify the full path to the View:
return View("~/Areas/Admin/Views/AdminHome/Index.cshtml");
But, if you want the url to reflect this location, then you probably need to have an AdminHome Controller and redirect to its index action.

Related

Have same URL for two separate pages

I am working on testing different landing pages for users, and want 50% to land on a different page. The split is being done based on odd/even IP address. However, I want the URL of the landing page to be the same, but can't figure out how to do this. So far these are the two ActionResults:
[HttpGet]
public ActionResult VerifyVoucherCode()
{
var model = new VerifyVoucherModel();
model.ActivationCode = Request.GetFirstQueryValue("value", "voucher");
return View(model);
}
[HttpGet]
public ActionResult AlternateVerifyVoucherCode()
{
var model = new VerifyVoucherModel();
model.ActivationCode = Request.GetFirstQueryValue("value", "voucher");
return View(model);
}
As you can see the code is exactly the same, which I want to avoid regardless, but they correspond with different .cshtml files and partials and display different content. I want the URL for both to be 'SignUp/VerifyVoucher' but the second one is obviously at the moment 'SignUp/AlternateVerifyVoucher'. Is there any way I can get the AlternateVerifyVoucherCode to have the same URL but display and match up with the different razor pages?
Perhaps you need one entry point with mutliple results. Maybe something like this pseudo code?
[HttpGet]
public ActionResult VerifyVoucherCode()
{
var model = new SomeModel();
if (something == verifyvoucher)
{
return View("VerifyVoucherCode", model);
}
else if (something == alternateverifyvoucher)
{
return View("AlternateVerifyVoucherCode", model);
}
else
{
//TODO: error
return View("SomeError");
}
}

Return Response.Redirect or ActionResult from a single MVC method

I have a MVC method that currently returns an ActionResult - I have had to make some changes and based on the business logic I want to do a response.redirect instead.
So I want to do something like this:
public ActionResult Index(CountryHomePageType currentPage)
{
if (someVar = true)
{
return View();
}
else
{
Response.redirect("www.website.com")
}
}
but I can't becuase Resonse.Redirect is not a ActionResult....
How can I get round this?
If you are redirecting outside of your current mvc application you can use
return Redirect("<your external url>"); // like "https://www.google.com"
if you want to redirecto back you your homepage you can use
return RedirectToAction("Index", "Home");
assuming you are using the default mvc setup
You might want to also look at ActionFilters if you are making this check in multiple places.

What Views can a Controller Action return?

In ASP.NET MVC 5, does every Controller Action have to return a View with the same name as the Controller?
Here's my project. Have a webpage which contains a button to upload an image to a database. When the webpage is loaded, I want it to display a list of all the images that have already been uploaded. So, the Index (default) Action for this Controller loads the images from the database, and returns the Index View, which in turn displays the list of images:
public ActionResult Index()
{
// Load the images from the database
var images = GetImages();
return View(images);
}
On that same webpage, there is a button which allows the user to upload an image to the database. That button calls the Upload Action, which uploads the file based upon the "file" and "folder" arguments that are passed, and then finally returns the Index View again:
[HttpPost]
public ActionResult Upload(HttpPostedFileBase file, string folder)
{
// Upload the file from the specified folder
// ...
// ...
// ...
return Index();
}
However, when a user clicks on this upload button, the following error message is displayed:
The view 'Upload' or its master was not found or no view engine supports the searched locations
But I am not trying to render a View called "Upload" - I am trying to render the view called "Index", which is why I have the line return Index();.
Any help on where I'm going wrong?
Answer
Although Vitaliy and Nathan A provided adequate answers, I wanted to explain why your initial approach doesn't work because it's a great question and doesn't seem to make sense.
To get our answer we have to look at the ASP.NET MVC source code.
Before we get to that let's walk through your code.
The user visits (or POSTS to) /Controller/Upload
We do some logic and then return Index()
Index() is a method that returns its own view with its own model
MVC fails to find 'Upload' view and throws an exception
What went wrong?
Firstly know that Index() is being called and returned successfully. The model object is also being passed to the view (if one is found).
When you return Index(), it is returning View() which is an inherited method from the Controller class which returns a ViewResult.
A ViewResult inherits from ViewResultBase.
When a ViewResult is being returned it calls ExecuteResult().
Taking a look at the source code for ExecuteResult():
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (string.IsNullOrEmpty(this.ViewName))
{
this.ViewName = context.RouteData.GetRequiredString("action");
}
ViewEngineResult viewEngineResult = null;
if (this.View == null)
{
viewEngineResult = this.FindView(context);
this.View = viewEngineResult.View;
}
TextWriter output = context.HttpContext.Response.Output;
ViewContext viewContext = new ViewContext(context, this.View, this.ViewData, this.TempData, output);
this.View.Render(viewContext, output);
if (viewEngineResult != null)
{
viewEngineResult.ViewEngine.ReleaseView(context, this.View);
}
}
The key here is context.RouteData.GetRequiredString("action"). This code gets the action name so that a view can be found and rendered. Note that it is using a ControllerContext.
Because of this, your action is actually set when the Upload() method is first called. If you step through your Index() method you will see that calling context.RouteData.GetRequiredString("action") will return the string "Upload".
This is because, within the context of the user request, the action is in fact Upload (that's the page they requested).
Fun fact
If you return Index() and that method happens to alter the ViewBag (ViewData) then the ViewData will be altered regardless of what is rendered.
If your Upload() does this:
ViewBag.Test = "Upload method";
And you return Index() and your Index() does this:
ViewBag.Test = "Index method";
Then the value of Test will be "Index method".
Look up the documentation on the View method. It has several arguments you can provide, one of them being a string of the name of the view, but you always use the View() method if you want to return a view.
However, if you don't want to use the default View name (being the name of the action method), simply use a string to specify a new name like so:
public ActionResult Upload(HttpPostedFileBase file, string folder)
{
return View("Index");
}
you can do either:
return RedirectToAction("Index");
or:
return View("Index");
Just a side-note, you're not constrained to show views from the View folder for the controller name. You can do
public ActionResult something()
{
return View("../OtherView/somethingElse");
}

How can you use session variable to determine view?

I have my global.asax setup with the following Session_Start:
protected void Session_Start()
{
HttpContext.Current.Session.Add("sourceCode", "default");
}
On my controller I have the following:
public ActionResult Index(string sourceCode)
{
if (sourceCode != null && sourceCode != "default")
{
Session["sourceCode"] = sourceCode;
return View();
}
else
{
return View();
}
}
I want to be able to display different partial layouts based on this session variable. What is the proper way to do this? Can I load a partial view from the controller or do I need to handle that on the view?
This is a variable that I want to use site wide to determine special pricing and landing page creatives. Do I have to set this same structure up on every single controller or is there a more global way of doing this?
Thanks,
Brian
If you want to show the layout in all the pages, you might want to add the logic in the layout file. There, you will add something like that (assuming razor)
#if(HttpContext.Current.Session["someValue"]){
#*render some partial*#
}else{
#*render some other partial*#
}
By the convention of MVC, controller should decide which view it should open. For this in controller you have code like this:
public ActionResult Index(string sourceCode)
{
if (sourceCode != null && sourceCode != "default")
{
Session["sourceCode"] = sourceCode;
ViewData["PartialView"] = "partialviewname1";
}
else
{
ViewData["PartialView"] = "partialviewname2";
}
return View();
}
and in view you can write code something like this:
<div>
#Html.Partial(Convert.ToString(ViewData["PartialView"]))
</div>
and if you have decide which partial view you have to load on each and every request then you can write above logic in global action filter. Global action filter get executed before any requested action method. To know more about global action filter you can explore this link.
http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/understanding-action-filters-cs

ASP.NET MVC - Reusing Action Behaviors

This question pertains primarily to good design.
Suppose I have a controller action like DeletePage that can be invoked in two separate views of the same controller. Assuming the delete logic is not contained in the action itself, but rather some conditional checks and the like that call the correct business logic, it doesn't make sense to duplicate the structure of the delete action when I can instead have a private method that returns an ActionResult which I call in both actions which can cause a delete. My question is where is the best place to place a reusable action method like this? Right now I'm just marking them private and sticking them in a region of the controller class, but perhaps an sealed inner class would make more sense for such a method- or somewhere else entirely.
Thoughts?
public ActionResult EditPage(int id, FormCollection formCollection)
{
var page = _pagesRepository.GetPage(id);
if (page == null)
return View("NotFound");
if (page.IsProtected)
return View("IllegalOperation");
if (formCollection["btnSave"] != null)
{
//...
}
else if (formCollection["btnDelete"] != null)
{
return DeletePage(page);
}
return RedirectToAction("Index");
}
public ActionResult DeletePage(int id)
{
var page = _pagesRepository.GetPage(id);
if (page == null)
return View("NotFound");
return DeletePage(page);
}
// Reusable Action
private RedirectToRouteResult DeletePage(Page page)
{
if(page != null && !page.IsProtected)
{
_pagesRepository.Delete(page);
_pagesRepository.Save();
FlashMessage(string.Format(PageForms.PageDeleted, page.Name), MessageType.Success);
return RedirectToAction("Index");
}
return RedirectToAction("Index");
}
I don't see why you need to make your reusable method an action method. Why not just a private method that returns void/bool/etc indicating the result of the save, and let your public action method return the RedirectToAction()? Effectively it's the same result, but I think it's a clearer approach.
public ActionResult DeletePage(int id)
{
var page = _pagesRepository.GetPage(id);
if (page == null)
return View("NotFound");
DeletePage(page);
return RedirectToAction("Index");
}
//reusable method
private void DeletePage(Page page)
{
//your same validation/save logic here
}
In the future you might consider moving this private DeletePage method into a separate service class that performs the validation and/or saving. Returning an an ActionResult would definitely not make sense in that case, so I think this example would be a more appropriate approach for your scenario.
In my opinion, your reusable code is an Action because it is returning an ActionResult. So your use is fine. The DeletePage(Page page) could potentially remain public.
I look forward to other opinions.
Personally I agree with Kurt. The concept of Deleting an unprotected page should be decoupled from what action the controller should perform. Secondly it's confusing from the code what should happen when the page is protected. In one action it redirects to the index, in the second it redirects to the "IllegalOperation" view. Personally I'd do something a little like...
public ActionResult DeletePage(int id) {
var page = _pagesRepository.GetPage(id);
if (!PageIsValidForDeletion(page)) {
string invalidPageView = FindViewForInvalidPage(page);
return View(invalidPageView);
}
DeletePage(page);
return RedirectToAction("Index");
}

Resources