So in order accomplish what I asked in this post I did the following:
[iPhone]
[ActionName("Index")]
public ActionResult IndexIPhone()
{
return new Test.Areas.Mobile.Controllers.HomeController().Index();
}
[ActionName("Index")]
public ActionResult Index()
{
return View();
}
Which still serves the same view as the Index action method in this controller. Even though I can see it executing the Test.Areas.Mobile.Controllers.HomeController().Index() action method just fine. What's going on here? And how do I serve the Index view from Mobile area without changing the request URL (as asked in the original post referenced above)?
You have a few options:
Redirect to the Action you'd like to return: return RedirectToAction("Action-I-Want").
Return the View by name: return View("The-View-I-Want").
Note that with the 2nd approach you'd have to put your view in the "Shared" folder for all controllers to be able to find it and return it. This can get messy if you end up putting all your views there.
As a side note: The reason your work doesn't find the view is because default view engine looks for the view in the folder that "belongs" to the current executing controller context, regardless of what code you're calling.
Edit:
It is possible to group all "mobile" views in the same folder. On your Global.asax (or where ever you're setting up your ViewEngine, just add the path to your mobile View in the AreaViewLocationFormats. Mind you, you'll still have to name your views differently.
You can also write your own view engine. I'd do something like detecting the browser and then serving the right file. You could setup a convention like View.aspx, and View.m.aspx.
Anyhow, just take a look at WebFormViewEngine and you'll figure out what works best for you.
The easiest way to send a request to a view handled by another controller is RedirectToAction("View-Name", "Controller-Name").
There are overloads of View() that take route information that might work as well, but they'd require more effort to set up.
Well actually the easiest way is to make one version of your site programmed on standards instead of browser detection :D -- however in direct response to accomplish what it in a more of a ASP.NET mvc fashion, using:
RedirectToAction("ViewName", "ControllerName");
is a good method however I have found it is more practical if you feel you must program for different browser standards to create a primary view and an alternate "mobile" view under your controllers views. Then instead of writing special code on every controller, instead extend the controller like so.
public class ControllerExtended : Controller
{
private bool IsMobile = false;
private void DetectMobileDevices(){ .... }
}
Then modify your controller classes to instead say ControllerExtended classes and just add the one line to the top of each Action that you have alternate views of like so:
public class ApplicationsController : ControllerExtended
{
// GET: /Applications/Index
public ActionResult Index() {
this.DetectMobileDevices();
if(this.IsMobile){
return RedirectToAction("MobileIndex");
} else {
// actual action code goes here
return View();
}
}
}
Alternately you can use return View("ViewName"); but from my experience you want to actually perform different actions as opposed to just showing the result in a different view as in the case of presenting an HTML table as opposed to a Flex table to help iPhone users since there is no flash support in the iPhone, etc. (as of this writing)
Related
I remember reading & have been told you should only use [httppost] on controller actions that may change data (I can't find these sources though - and am unable to find anything on Google).
And if you are - say, just doing a lookup of data you should use [httpget].
I have 2 controller actions - used only for looking up data, never for changing it.
I have tried using two [httpget]s this in an ASP.NET MVC 5 controller:
public ActionResult MyAction(Guid Id)
{
// Id is used to populate the form
}
// I would previously have placed [httppost] here
public ActionResult MyAction(MyObject myobj)
{
// myobj is posted back from the form - but no data is changed in the database, it's just a lookup
}
However, I get this error:
The current request for action 'MyAction' on controller type
'MyController' is ambiguous between the following action methods:
The compiler is having a problem with ambiguous method names - so what is best practice for doing this?
Do we have to use a form specifying an explicitly different action? (this seems inelegant and messy - it makes [httppost] look simpler, more elegant and more terse if that is the case).
Is it indeed the case that we should use [httpget] in situations where no data is being persisted?
thx.
Your problem is that you have two methods with the same name in your controller. And action resolver can not decide which one to use.
Easiest way is to follow naming convention, in this case you won't have to put attributes:
public ActionResult GetMyAction(Guid Id)
{
// Id is used to populate the form
}
// I would previously have placed [httppost] here
public ActionResult PostMyAction(MyObject myobj)
{
// myobj is posted back from the form - but no data is changed in the database, it's just a lookup
}
However if you want to stay with your names, you will have to put appropriate attributes:
[HttpGet]
public ActionResult GetMyAction(Guid Id)
{
// Id is used to populate the form
}
// I would previously have placed [httppost] here
[HttpPost]
public ActionResult PostMyAction(MyObject myobj)
{
// myobj is posted back from the form - but no data is changed in the database, it's just a lookup
}
I think you're misunderstanding the rationale for get vs post. The important thing is not that post requests always change the state, the important thing is get requests never change state. One great example was a content management system that regularly experienced their content inexplicably disappearing before figuring out that a web crawler was indexing an admin page and following their deletion hyperlinks, all using http get, or prefetching browsers randomly adding or removing shopping cart items.
That said, I'd probably still make both of these gets using different actions, because the get verb better describes the service action, but maybe you have good reasons not to do that.
In MVC 5 I am attempting to use the controller to render a partial view only if the (Windows Authenticated) user belongs to one or more of a list of Active Directory groups. There are over 30 distinct groups I need to account for, so the "hello world" examples don't fit my needs. After playing scavenger hunt on the web, I managed to collect this much. No compile or runtime errors, but the content is showing for all users rather than the specific users. So the desired outcome is not yet achieved.
While I can achieve the desired outcome using if-then logic in the view, it creates a lot of unnecessary duplication and encourages spaghettification. So I'm trying to do this in the controller.
Summary of Desired Outcome:
When the user loads the viewpage, the partial view should only render if the Windows Authenticated user belongs to one or more of a list of groups defined in the controller action. If the user is not authorized, then the partial view is not included.
Controller Block:
[ChildActionOnly]
[Authorize(Roles="Domain\\GroupA,Domain\\GroupB")]
public ActionResult MonitorCSU()
{
return PartialView("MonitorCSU");
}
View Block:
<div class="rowWithCols3">
#Html.Partial("MonitorCSU")
Unsuccessful Iterations:
In the controller block I tried (unsuccessfully) to use an if-then block, the else case being another partial view with no content.
[ChildActionOnly]
public ActionResult MonitorCSU()
{
if (User.IsInRole("Domain\GroupA")) {
return PartialView("_MonitorCSU");
}
else
{
return PartialView("_Unauthorized");
}
}
In Razor, I tried using HTML.Action but when I tried run the page the browser hung in an infinite loop.
#Html.Partial() returns a partial view without calling a controller method. In order to call your controller method, you need to use
#Html.Action("MonitorCSU")
or
#{ Html.RenderAction("MonitorCSU") }
Note this assumes that the MonitorCSU() method is in the same controller as the method that generates the main view (other wise you also need to include a parameter for the controller name)
Refer documentation
While you've found a solution, you're going to have other problems with it. I would suggest a different approach, which is to use EditorTemplates and create a separate model for the html you want to render. Then, at runtime you would check whether the user is in the groups you specify, and if they are, you create an instance of the model, and if they are not you leave the model null. In this way, when the view is rendered with EditorFor(), it will ignore and not render the template for users who do not have access.
Thanks to #Stephen Muecke and and a commenter whose entry has mysteriously vanished, I have the missing pieces.
I was able to test this code with several real users and verified the desired behavior happens consistently.
Controller Block:
Main difference: take out authorization and use an if-then block send one of two partial views.
[ChildActionOnly]
public ActionResult MonitorCSU()
{
if (User.IsInRole("DOMAIN\\GroupA"))
{
return PartialView("MonitorCSU");
}
else
{
return PartialView("Unauthorized");
// this is an empty page
}
}
View Block:
The key difference is using HTML.Action
<div class="rowWithCols3">
#Html.Action("MonitorCSU")
I looked around and couldn't find an easy solution.
I've tried #GetUserName which doesn't work.
I've tried # { GetUserName which doesn't work.
There has to be an easy way to call a method from the razor view engine.
It is within a foreach loop.
I need GetUserName(item.userID)
The below code is in my controller:
[ChildActionOnly]
public string GetUserName(int userID)
{
ProPit_User user = db.ProPit_User.Find(userID);
return user.username;
}
Trying to call a controller action method directly from your view is usually a sign of bad design.
You have a few options, depending on what you are trying to do:
Avoid calling the method, instead put the data in your model, and render the model
Use Html.RenderAction
Put the method in another class and use normal function syntax.
(1) is usually my default approach, often incorporating DisplayTemplates and EditorTemplates
For (3), e.g.
public static class Util
{
public string MyUtilMethod(int blah)
}
And the view:
#Util.MyUtilMethod(1)
Although you can obtain the controller instance from your view, doing so is plain wrong as it violates the whole MVC (and MVVM) paradigm, as the view should not be aware of its controller.
(The only possible reason I can think of where this would be useful would perhaps be for testing the controller from a mocked view, although even here, it should be possible to test the exposed controller functionality directly from unit tests):
#{
var controller = ViewContext.Controller as MyController;
var userName = controller.GetUserName(123);
}
The better way to have arrived at this result is for the controller to pre-populate, and pass all the data needed by the View, such as the userName, to a custom ViewModel (as typed by the #model directive at the top of the Razor page), or to the ViewBag dynamic.
I am using ASP.NET MVC C#. I have my view with the following line of code:
#using (Html.BeginForm("TastingParty", "Contact"))
I am submitting to a different controller and action than the current page is on so I had to add a custom controller and action in that line. Now it gets to my controller and at the bottom of the method I call "return View();" so it will display the same page again. But it is looking for that view in the Contact controller because that is where I sent it.
How do I return the original View that it came from? Hopefully that all makes sense.
return RedirectToAction(Action,Controler)
You are searching for this?
You can either use return RedirectToAction(...
http://msdn.microsoft.com/en-us/library/system.web.mvc.controller.redirecttoaction(v=vs.118).aspx
Or, if you want to be able to reuse the view across different controllers, you can place it in the Views\Shared folder.
In the latter case, you then have the ability to return the posted model back out to that view.
public ActionResult YourAction(YourViewModel postedModel) {
// do something
return View(postedModel);
}
And again, if you want this to be a shared view, you could place it under Views\Shared\YourAction.cshtml.
if i start off on a Detail page:
http:\\www.mysite.com\App\Detail
i have a controller action called Update which normally will call redirectToAction back to the detail page. but i have an error that is caught in validation and i need to return before redirect (to avoid losing all of my ModelState). Here is my controller code:
public override ActionResult Update(Application entity)
{
base.Update(entity);
if (!ModelState.IsValid)
{
return View("Detail", GetAppViewModel(entity.Id));
}
return RedirectToAction("Detail", new { id = entity.Id })
but now I see the view with the validation error messages (as i am using HTML.ValidationSummary() ) but the url looks like this:
http:\\www.mysite.com\App\Update
is there anyway i can avoid the URL from changing without some hack of putting modelstate into some temp variables? Is there a best practice here as the only examples i have seen have been putting ModelState in some tempdata between calling redirectToAction.
As of ASP.NET MVC 2, there isn't any such API call that maintains the URL of the original action method when return View() is called from another action method.
Therefore as such, the recommended solution and a generally accepted convention in ASP.NET MVC is to have a corresponding, similarly named action method that only accepts a HTTP POST verb. So in your case, having another action method named Detail like so should solve your problem of having a different URL when validation fails.
[HttpPost]
public ActionResult Detail(Application entity)
{
base.Update(entity);
if (ModelState.IsValid)
{
//Save the entity here
}
return View("Detail", new { id = entity.Id });
}
This solution is in line with ASP.NET MVC best practices and also avoids having to fiddle around with modestate and tempdate.
In addition, if you haven't explored this option already then client side validation in asp.net mvc might also provide for some solution with regards to your URL problem. I emphasize some since this approach won't work when javascript is disabled on the browser.
So, the best solution would be have an action method named Detail but accepting only HTTP POST verb.
The problem here is actually caused by your implementation. This doesn't answer your question, but it describes where you've gone wrong in the first place.
If you want a page that is used to update or edit an item, the URL should reflect this. For example.
You visit http:\www.mysite.com\App\Detail and it displays some information about something. That is what the URL describes it is going to do. In your controller, the Detail() method would return the Detail.aspx view.
To edit an item, you would visit http:\www.mysite.com\App\Edit and change the information you wish to update, the form would post back to the same URL - you can handle this in the controller with these methods:
[HttpGet]
public ActionResult Edit() {
MyModel model = new MyModel();
...
return View(model);
}
[HttpPost]
public ActionResult Edit(MyModel model) {
...
if (ModelState.IsValid) {
// Save and redirect
...
return RedirectToAction("Detail");
}
return View(model);
}
If you ever find yourself doing this...
return View("SomeView", model);
You are making your own life harder (as well as breaking the principles behind URLs).
If you want to re-use part of a view, make it a partial view and render it inside of the view that is named after the method on the controller.
I apologise that this potentially isn't very helpful, but you are falling into an MVC anti-pattern trap by displaying the same view from a differently named method.
As #Malcolm sais, best practice is to put ModelState in TempData, but don't do it manually! If you'd do this manually in every controller action where it's relevant, you would introduce immense amounts of repeated code, and increase the maintenance cost vastly.
Instead, implement a couple of attributes that do the job for you. Kazi Manzur has an approach (scroll down to the end of the post) that has been widely spread, and Evan Nagle shows an implementation with tests that is essentially the same as Kazi's, but with different names. Since he also provides unit tests that make sure the attributes work, implementing them in your code will mean little or no maintenance cost. The only thing you'll have to keep track of is that the controller actions are decorated with the appropriate attributes, which can also be tested.
When you have the attributes in place, your controller might look something like this (I deliberately simplified, because I don't know the class you inherit from):
[HttpPost, PassState]
public ActionResult Update(EntityType entity)
{
// Only update if the model is valid
if (ModelState.IsValid) {
base.Update(entity);
}
// Always redirect to Detail view.
// Any errors will be passed along automagically, thanks to the attribute.
return RedirectToAction("Detail", new { id = entity.Id });
}
[HttpGet, GetState]
public ActionResult Detail(int id)
{
// Get stuff from the database and show the view
// The model state, if there is any, will be imported by the attribute.
}
You mention that you feel putting ModelState in TempData feels like a "hack" - why? I agree with you that doing it with repeated code in every single controller action seems hacky, but that's not what we're doing here. In fact, this is exactly what TempData is for. And I don't think the above code looks hacky... do you?
Although there are solutions to this problem that might appear simpler, such as just renaming the action method to preserve the URL, I would strongly advise against that approach. It does solve this problem, but introduces a couple of others - for example, you'll still have no protection against double form submission, and you'll have pretty confusing action names (where a call to Detail actually changes stuff on the server).
The best practice you ask for is actually what you explained not to do: putting modelstate into tempdata. Tempdata is meant for it, that's why I would not call it a hack.
If this is to much repetitive code you could use the attribute modeldatatotempdata of MVCContrib. But the store is still TempData.