Hiding URL Parameters from Controller to View: MVC 5 - asp.net-mvc

Say I have a website with a wide variety of different pages, each tailored for an individual user and accessible only by that user. When a user accesses a page, the URL for that page will be UserStuff/Pages/11, where 11 is the page's ID. Right now, because users can have multiple pages, I am using an ActionLink element to direct the user to their selected page, like so:
// In Index.cshtml
// ...
#Html.ActionLink("View Page", "Pages", new { id = page.ID }, new { #class=...})
The controller intercepts the given id:
public ActionResult Pages(int id)
{
// ...
Page page = db.Pages.Find(id);
return View(page);
}
This works well and delivers exactly how I want the application to behave. However, I wish for the ID to be hidden in the URL. How do I pass information from the View to the controller via a parameter without having it exposed in the URL? Say that I don't want the Page's ID visible in the URL, how can I link to a different Action that takes the given parameter?
I believe that keeping the request a GET request is important due to other actions redirecting to the resulting page. Wrapping it in a form/POST request would make said actions unable to access the information without having to go through the same process as the original user, which is not something that I want in this implementation.

Related

MVC Redirect to a different view in another controller

After a user has completed a form in MVC and the post action is underway, I am trying to redirect them back to another view within another model.
Eg. This form is a sub form of a main form and once the user has complete this sub form I want them to go back to the main form.
I thought the following might have done it, but it doesn't recognize the model...
//send back to edit page of the referral
return RedirectToAction("Edit", clientViewRecord.client);
Any suggestions are more that welcome...
You can't do it the way you are doing it. You are trying to pass a complex object in the url, and that just doesn't work. The best way to do this is using route values, but that requires you to build the route values specifically. Because of all this work, and the fact that the route values will be shown on the URL, you probably want this to be as simple a concise as possible. I suggest only passing the ID to the object, which you would then use to look up the object in the target action method.
For instance:
return RedirectToAction("Edit", new {id = clientViewRecord.client.ClientId});
The above assumes you at using standard MVC routing that takes an id parameter. and that client is a complex object and not just the id, in which case you'd just use id = clientViewRecord.client
A redirect is actually just a simple response. It has a status code (302 or 307 typically) and a Location response header that includes the URL you want to redirect to. Once the client receives this response, they will typically, then, request that URL via GET. Importantly, that's a brand new request, and the client will not include any data with it other than things that typically go along for the ride by default, like cookies.
Long and short, you cannot redirect with a "payload". That's just not how HTTP works. If you need the data after the redirect, you must persist it in some way, whether that be in a database or in the user's session.
If your intending to redirect to an action with the model. I could suggest using the tempdata to pass the model to the action method.
TempData["client"] = clientViewRecord.client;
return RedirectToAction("Edit");
public ActionResult Edit ()
{
if (TempData["client"] != null)
{
var client= TempData["client"] as Client ;
//to do...
}
}

Enforcing a choice prior to viewing MVC and Web Forms pages

I'm working on a system that needs to know a user's choice before they enter a site. Up till now the choice has been stored in a cookie and checked by JavaScript on the page load - if the cookie doesn't exist then a dialog is shown and the user makes the choice.
Although we'd normally expect the user to arrive at the homepage of the application, they can legally follow a URL to any page within the application, so the JavaScript to check the choice exists on every page.
This has caused problems (almost always fixed by clearing cookies) so we're switching to store the choice in the database. What we need is a neat way of making sure that all pages (MVC and Web Forms) check that the choice has been made and, if it hasn't, either display a dialog or redirect to a page where the choice can be made.
The main thing bothering me is that to cause a redirect using MVC, I need to return a RedirectResult, and this can only be done from an Action. I don't want every action to have code regarding this check - it seems like the kind of thing that should be possible from a base controller (in the same way a base page could cause a Response.Redirect.
Can anyone suggest a good way for all pages to perform a check on the database and then either cause a redirect or show a dialog?
The main thing bothering me is that to cause a redirect using MVC, I
need to return a RedirectResult, and this can only be done from an
Action.
Oh not at all. You could also redirect from a custom action filters.
For example you could write a custom IAuthorizationFilter that will check whether the user made the necessary choice and if not redirect to some given page. The check could be done against a cookie, database or wherever you decide to persist this information:
public class EnsureChoiceHasBeenMadeAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
// get the current user
var user = filterContext.HttpContext.User;
if (user.Identity.IsAuthenticated && !UserMadeAChoice(user.Identity.Name))
{
// if the current user is authenticated and he didn't made a choice
// redirect him to some page without even attempting to execute
// the controller action that he requested
var values = new RouteValueDictionary(new
{
controller = "home",
action = "index"
});
filterContext.Result = new RedirectToRouteResult(values);
}
}
private bool UserMadeAChoice(string username)
{
throw new NotImplementedException();
}
}
Now you have different possibilities:
You decorate the controllers/actions that you want to perform this check with the [EnsureChoiceHasBeenMade] attribute
You register the action filter as a global action filter so that it applies to absolutely all actions
You write a custom filter provider in order to dynamically apply the action filter to some actions based on some dynamic values (you have access to the HttpContext).

Passing and Storing objects between Pages

I'd like some advice on this issue. Basically, I have 2 pages (for simplicity sake), and the second page is completely dependent on the first page.
So lets say the main page is a search page, and the second page is a view page.
The site works off XML requests, and the ultimate aim is to keep the XML requests to a minimum as each request is a little slow. I considered using Sessions, but I've had some problems in the past with sessions being mixed between users randomly which i only manage to curb by changing the recycle process in IIS to a very small time frame, so I'm wondering if there is any other way. I've tried TempData, but that expires after one request, so a page refresh on the view page doesn't seem possible. (or is it?)
Anyways, we have the Search page that has, say, 5 attributes that are required by the View page, but only 2 are needed to make the XML request on the view page.
e.g.
Search Page Contains:
ID,
Name,
City,
Email,
Height
View Page needs the following from the Search page to complete the xml request:
ID,
Name,
Email
View Page displays all the information from the search page, plus everything in the XML response.
The link i have in the search page only has the ID in the url, so name and email are required for the XML request on the second page some how. Not sure if it's possible without sessions?
What i've tried is:
Store the search results in TempData. That way, when someone clicks the 'View' link (View), the View page loads the search results like so:
var viewPage = SearchResults.Where(w => w.ID == id).FirstOrDefault();
The ViewModel then renders the page by grabbing the Name and Email from viewPage, making an XML request, and displaying the response, along with other required details from viewPage.
It works as expected with tempdata. Data only persists on the first request, dies on a page refresh. Sessions is the alternative, but is there any other?
(sorry for the wall of text :)
Why not using more standard techniques like a <form> tag in the search page pointing to the controller action that will perform the search:
#using (Html.BeginForm("search", "somecontroller", FormMethod.Get))
{
... some input fields for your search criteria
<button type="submit">Search</button>
}
and then you will have the Search controller action:
public ActionResult Search(SearchModel model)
{
var results = ....
return View(results);
}
I've used GET method on the form which allows the user to bookmark the results page and come back to it later.

Make ActionLink to specific user

How do I make the links on the site lead to the current user's page(s)? I dont want them to get lists of every user but only themselves. I'm thinking something like this for my menu links:
#Html.ActionLink("My Details", "Details", "Customers", Membership.GetUser().ProviderUserKey)
I think it's better if you send user to "My Details" page without any parameter or route values.
The page is going to show current users data. So why to pass it like this?
Just Redirect user to "My Detail" page, and after that, when user is in that page, you can get current user using: HttpContext.CurrentUser.
Don't put user data in route values. Instead you can get it in that page's controller and pass it to the page.
If you want the "Links" to display to show when the page is viewed by an authenticated user, create a ChildAction that returns the data you want like:
[Authorize]
[ChildActionOnly]
public ActionResult UserLinks() {
var items = someRepository.GetLinks(User.Identity.Name);
return PartialView(items,"SomePartialView");
}
Then, use the RenderAction in the view like so:
#{ Html.RenderAction("UserLinks");}
Secondly, I agree with the comment by "Afshin Gh". Rather than passing data in your "ActionLink(s)", you could code it in your Controller to filter the data as required - like I'm showing in the "UserLinks" method above. This way, someone can't just manipulate the Url to display data for another customer.

How to create a view that is not accessible directly but can only be redirected to?

I'm currently working on the user registration in my project. After the registration is done I wish to show some confirmation to the user. I decided to create another view. That's fine.
Now, if after the registration I just return the view like:
public class MyController : Controller
{
[AcceptVerbs (HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult Registration (FormCollection form)
{
/* Some logic goes here */
return View ("ConfirmationView");
}
}
Everything is working as desired. No changed url in the title bar. But... If I click the refresh button, the browser will submit the data from the form again which I do not want.
Then I decided to create a separate action, but that means it will produce a new url in the address bar. I do not want the user to click refresh now because this view will not be able to sensibly display the confirmation information again. Is there any way to make an action not accessible directly? Or at least any way to determine whether it was called directly or by redirection? In the latter case I would just take the user away from that page to maybe the home page.
Any way to accomplish this?
So I found the solution myself.
One can use TempData to detect the repeated or external action calls.
public class MyController : Controller
{
[AcceptVerbs (HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult Registration (FormCollection form)
{
/* Some logic goes here */
TempData["RedirectCall"] = true;
return RedirectToAction ("Confirmation");
}
[AcceptVerbs (HttpVerbs.Get)]
public ActionResult Confirmation ()
{
if (TempData["RedirectCall"] == null)
return RedirectToAction ("StartPage", "Home");
return View ();
}
}
Nice and simple. :)
One way to solve your problem is to attach a guid or similar type of "random" data to a user session, and check for a valid session when the page is requested. If there is none, you redirect to a page saying that this url is not available at the moment, and that the user will soon be redirected (and then redirect to home after 5 seconds or so using js).
Roughly it would work like this:
When the user is registered, a session cookie is created with for example a GUID. The GUID is also stored in a database table, in which you have one column for the UserID primary key and one for the GUID. You also create an authentication cookie, thus logging the user on to your site.
When all datacalls etc are done, the user has been successfully registered and so on, you redirect to the confirmation page.
When the confirmation page is loaded, the user is automatically logged on (because you created the authentication cookie in step 1). You can then check for a row in the UserID-GUID table corresponding to the logged on user.
a) If there is such a row, you delete the row, and display the confirmation page with all the information.
b) If there is no such row, you display the error message and redirect. As you deleted the row when you showed the message the first time, the user will not be able to access the confirmation page again.
Note: If you use this approach (or some other that makes the confirmation page available only once) you should make sure that it is clearly stated on the confirmation page that the user won't be able to access that page again.
if(Request.UrlReferrer == null)
{
return RedirectToAction("Index");
}

Resources