I have a requirement to change a form's action depending on what selection a user makes on a dropdown box inside the form, I have an MVC4/C# project on .NET 4.5
The choice that the user makes will influence which controller or which action needs to be called.
I have done a bit of searching around and I have come down to a few solutions:
Implement JQuery client side to dynamically change the form's action
Have the form go to a specific action dedicated to routing the request to the proper controller/action
From what I have read, people have recommended against using JQuery since you can't be sure the client is running javascript, and similar issues.
I have been playing around with my second option here, I have been trying to use RedirectToAction to perform the routing inside a switch block, however it won't work with POST data since redirecting will cause a GET.
An example of what I've attempted inside the controller follows:
[HttpPost]
public ActionResult Index(TestObject object)
{
switch (object.Type) {
case("A"):
return RedirectToAction("ActionA");
case ("B"):
return RedirectToAction("ActionB");
case ("C"):
return RedirectToAction("ActionC");
default:
ModelState.AddModelError("", "Invalid Type");
return View();
}
}
[HttpPost]
public ActionResult ActionA(TestObject object)
{
// Do Stuff for ActionA
return View();
}
This is the first MVC project I've created so I'm wondering what is considered the proper way if everyone keeps saying not to use JQuery for a situation like this.
If the second option is the 'proper' solution, how do I go about sending the data to the alternate controller actions?
You could do something like the following, i.e. pass form values as routevalues:
RedirectToAction("ACTIONNAME", "CONTROLLERNAME", ROUTEVALUES);
e.g.
this.RedirectToAction("displayimport", "coilenquiry", new { Number = number });
then in your action, use the parameters...
public ActionResult DisplayImport(string Number)
{
}
There's not currently any way to pass a model to RedirectToAction directly. This is what TempData is for:
TempData["MyModel"] = model;
switch (object.Type) {
case("A"):
return RedirectToAction("ActionA");
case ("B"):
return RedirectToAction("ActionB");
case ("C"):
return RedirectToAction("ActionC");
default:
ModelState.AddModelError("", "Invalid Type");
return View();
}
In your next action (note that this will be a GET request so you'll need to take the HttpPost annotation off it):
var model = TempData["MyModel"] as TestObject;
However, I'm a big fan of avoiding session storage wherever possible so, in this scenario, I wouldn't necessarily say that using jQuery is without any merit. The argument that "you can switch JavaScript off" is pretty moot nowadays given that the vast majority of modern sites are dependent on it.
Related
I'm using default route configuration of domain/controller/action.
For instance, www.domain/Appointment/RequestAppointment calls the Appointment controller and RequestAppointment ActionResult.
I've been often asked by our Marketing folks to build redirects for shorter versions of our URLs for use in social media or on billboards.
So, they would like to see www.domain/Appointment/RequestAppointment as www.domain/appointment.
I don't have an issue doing that, in this case I am just:
//Appointment Controller
public ActionResult Index()
{
return RedirectToAction("RequestAppointment");
}
Now, our SEO person is telling me the new URL (www.domain/appointment) is returning a 302 redirect (which I understand) and that is bad for SEO. They would like a 301.
So, I understand I can return the (desired from SEO viewpoint) 301 redirect by using:
//Appointment Controller
public ActionResult Index()
{
return RedirectPermanent("http://www.domain.com/Appointment/RequestAppointment");
}
It works, but is there a better way or a best practice to handle this? I figure it can't be that uncommon, but I can't find anything.
I will also have to do the same thing with about 10 other unique URLs that fall into the www.domain/controller/action/id format that would be redirected to www.domain/promotion.
You don't need to set up redirects for these. You can set up the Routing engine to automatically map a given URL to a specific controller action, even if it doesn't match the given default structure. Keep in mind, these need to be BEFORE the default route. You can add as many of these as you want, and they will return a 200, not even a 301.
routes.MapRoute("Request Appointment",
"appointment", // <= What you want the URL after "www.domain" to be
new { controller = "Appointment", action = "RequestAppointment" });
Bear in mind, with this in place, you would need to have the URL be "appointment/index" if you want to get to the index action. However, the same holds true with any other sort of mapping. But, you can get to the same page with both of the following URLs with no server or client redirects occurring:
www.domain.com/appointment
www.domain.com/appointment/requestappointment
I have three types of roles for each of the menu links.
When the Billing guy is logging into the site
how can I determine dynamically the partial.html file that is shown in the content area?
I can not hardcode the content to the first actionlink in the menu, that means that always the Administration is loaded initially.
What can I do in such a case?
These types of decisions are best made in the Controller.
Example:
public HomeController: Controller
{
public ActionResult Administration()
{
// Determine the user's role.
// "GetRole()" does not really exist on the controller - use your own method.
string role = GetRole();
if (role == "Billing Guy")
return View("AdministrationBillingGuy")
else if (role == "SalesGuy")
return View("AdministrationSalesGuy")
else
return View();
// etc.
}
}
I can think of several ways to do this.
if you need all users to get the same url/action then you could do something like this
public ActionResult Custom(RoleEnum userRole)
{
switch(userRole)
{
case RoleEnum.Admin:
.....
return Partial("_adminPartial", viewModel);
// rest of you cases here
}
}
OR:
public ActionResult Custom(RoleEnum userRole)
{
var view = GetViewByRole(userRole);
// where GetViewByRole takes the enum and
// returns a string with the name of the partial
return Partial(view, viewModel);
}
Another way to do this is, and one that I'd recommend is to make an MVC Area for each user requiring a different layout and then at login you can redirect them to the proper Area, I recommend it because it allows for deeper differentiation between roles in the UI layer.
Another way to achieve the different layouts (am talking about MVC Layout Pages similar to ASP.Net Master pages) is to pass a string Layout to the view, using the ViewBag or any other method you like, then in the Razor code you could do something like this:
#model MyViewModel
#{
Layout = (string)ViewBag.Layout;
}
I leaved this last one for last as it appears a bit hacky to me. Hope this helps you
Well, you haven't provided enough information give any explicit direction, but generally, you should just alter your login post action to redirect to a different place depending on some identifying factor like a role (following is pseudocode)
// do login
if (user is "Billing")
{
// redirect to billing action
}
// etc.
The only reason you should be switching out partials or views is if you're doing a SPA (single page application) and utilizing JavaScript for routing. In that case, you would just need some endpoint you could hit with AJAX to get the user's "role".
However, I don't think that's what you're actually doing. If you're just using MVC directly, then you should be actually changing the URL, not just loading a different Razor view.
I have a view that executes some conditions and if one specific condition is X it must redirect to another page and show a pop-up with a message, so I was thinking in a variable with the resul of the condition that is executed id the controller (controller1.cs) and then use it in another controller (Controller2.cs) for the next actions.
Any suggests?
Don't use Sessions (you'll end up with using the Session as a kind of Singleton pattern with data you'll never use again). You want a RedirectToAction like this:
public ActionResult MyAction(string myResult)
{
if (condition)
{
return RedirectToAction("OtherAction", "Controller2", myResult);
}
else
{
return View();
}
}
You can choose to use other controllers / actions with objects you want to pass.
You can try using TempData -> http://www.devcurry.com/2012/05/what-is-aspnet-mvc-tempdata.html or Passing Information Between Controllers in ASP.Net-MVC -> Passing Information Between Controllers in ASP.Net-MVC.
I've got two controller actions at the moment: one that displays a strongly typed view, based on an unique identifier and another one that changes the model. Here's some code to visualize what I mean:
[HttpGet]
[ActionName("Edit")]
public ActionResult UpdateDevice(string code)
{
// Request the device based on the code.
var device = GetDeviceModel(code);
// Present the device in a view.
return View(device);
}
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
}
The code identifies the device, but it is also possible to change it. And that's my problem: Inside the post method I can access the new code using model.Code, but I also need to know the old code, to be able to change it.
I tried several alternatives, but none of them did satisfy my requirements:
ViewData does not get persisted until the post.
TempData is based on Sessions or Cookies – none of them I want to use at the moment.
Hidden fields and model bindings are not an option, because they can be manipulated on client side.
Finally I tried requesting data from the query string like this:
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
var oldCode = Request.QueryString["code"];
}
And this works! But I haven't found any resources on this around the web. So my question is: Is using the query string inside a post action safe against modifications? And what (if there are any) are the conditions for this to work?
If this is not a valid way to "remember" the code, are there any alternatives?
Based on the requirements you have mentioned in your question, it seems that you are looking for safety in the case. So I would say that QueryString is not safe. In my opinion using Session variables is the appropriate method that keeps your critical data in a safe location. You can use the method like this
[HttpGet]
[ActionName("Edit")]
public ActionResult UpdateDevice(string code)
{
Session["code"] = code;
....
}
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
if (Session["code"] == null)
throw new Exception("Error Message.");
var code = Session["code"].ToString();
....
Session["code"] = null;
}
I think you can make another property inside DeviceModel name OldCode.
and in your view you can save this value in hiddenfield like this
#Html.HiddenFor(m=>m.OldCode)
Now in controller post method you can get both the values like this.
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
var oldcode=model.OldCode;
var newcode=model.Code;
}
Hope this helps...
Nothing submitted via a GET or a POST request is safe from modifications. While a GET query string is obviously easy to modify, it doesn't take much effort to spoof POST variables either. It sounds to me like you need to re-think your approach.
Optimally, you would do permission checking server-side to determine if the user is allowed to update the device with the passed (old) code. If they do not have permission, return an error such as an HTTP 550 code.
If you truly can't support this approach, I would suggest adding an "OldCode" field to the DeviceModel class as suggested by others, but encrypting it before sending it to the client. Then you can safely write it to a hidden field, and decrypt back on the server without fear of the user changing the value.
I am learning MVC 3 after hours right now and last night I ran into an issue that seems like it would be very simple to solve but I can't seem to locate a solution for it.
I have a simple contact form. The end user fills out the form and submits it. When they submit the form I redirect the end user to a different action in the same controller which renders an "inquiry submitted" page/view which is basically a "Thank you page".
The controller is setup like so.
public ActionResult ContactUs()
{
return View();
}
[HttpPost]
public ActionResult ContactUs(ContactInfo contactInfo)
{
if (!ModelState.IsValid)
{
return View();
}
//perform some business logic
return RedirectToAction("InquirySubmitted",contactInfo);
}
public ActionResult InquirySubmitted(ContactInfo contactInfo)
{
return View(contactInfo);
}
The problem:
I do not want end users navigating directly to the InquirySubmitted action via the browser.
I only want the ContactUs action in the controller to be able to send users to the InquirySubmitted View.
I have attempted to make the InquirySubmitted action private so that only the controller can call it like so:
private ActionResult InquirySubmitted(ContactInfo contactInfo)
But this produces an error which I fully understand because I am forcing the browser to request InquirySubmitted by using RedirectToAction().
So my question is simply: What is the best "MVC 3 style" solution to this issue.
You will need to put logic in your InquirySubmitted ActionResult in order to prevent users from viewing the page if they are not supposed to.
You are already passing the InquirySubmitted method your model (ContactInfo). Could you simply inspect the data passed to the method and if it is absent then redirect the user to an error page (or some other page of your choice)?
An alternate solution would be to set a boolean in session that indicates that the user completed the "ContactUs" form. Then you could check for that session object within InquirySubmitted.
First, I would have to say.. Who cares if someone can navigate directly to the Inquiry submitted page? Is there any confidential information, or something sensitive there? If not, so what? What does it hurt?
However, if you're determined to do so. The answer to your question of "How to make an action not accessible directly from the browser" is that You can simply use Html.Action() to render the page, and then decorate the action method with a [ChildActionOnly] attribute.
This doesn't actually solve the problem though, since making the action indirectly accessible only answers your question, not solves your problem. Ultimately, you need to redirect the user to a url to load the page, so you will need some logic that determines if they can view the page or not. This is
Not sure if this still applies in MVC3, but in MVC2 it worked.
your global.asax file has your url structuring in it. You can add your InquirySubmitted to the list of urls that isn't accessible there.