I'm trying to hide the parameters from the querystrings in my web application.
I have been able to do that by using the session to store temporary variables. So it would work like this:
1. Click the view profile button:
href="#Url.Action("RedirectWithId", "Redirect", new { act = "ProfileView", ctrl = "User", id = member.Id})"
2. Calls the redirection method and stores the temp data:
public class RedirectController : Controller
{
public ActionResult RedirectWithId(string act, string ctrl, int id)
{
Session["temp_data"] = id;
return RedirectToAction(act, ctrl);
}
}
3. Use it in the action method without the parameter:
public ActionResult ProfileView()
{
if (Session["temp_data"] == null)
{
return Redirect(Request.UrlReferrer.ToString());
}
int id = (int)Session["temp_data"];
var model = GetUserById(id);
return View(model);
}
So it works just fine, however, this way to hide parameters doesn't handle the case where let's say I go to a first profile(id 4), and then go to a second one(id 8). If from the second profile I press the back button on the navigator trying to go back to the first profile(id 4), I'm going to be redirected to the current profile(id 8), since 8 is the current value of the Session["temp_data"].
Is there a way to handle this perticular case? Or is the another totally different and better way to hide parameters in the URL?
Thank you!
You can try this instead of Session
TempData["temp_data"]
I came to the conclusion that since I am already using authorizations and roles within my application, I don't need to always hide the parameters. I can simply hide whenever I am passing a complex object as a parameter.
Related
I need feature that is something similar to Laravel's old input helper but in MVC 5.
https://laravel.com/docs/5.6/requests#old-input
If validation fails, I need to reload all my model data as it was in the previous request except those inputs where user entered something wrong.
The problem is that my form has many disabled inputs and fields that program is fetching within [HttpGet] method, and they're getting lost during submission. So I need to store them in session.
The code below seems to work but is there any more efficient and beautiful way to do so with a less amount of code within each controller?
[HttpGet]
[Route(#"TaskManagement/Edit/{guid}")]
public async Task<ActionResult> Edit(Guid guid)
{
var model = new EditTaskViewModel();
model.Guid = guid;
await model.GetTaskFromRemoteService(new UserInfo(User));
ControllerHelpers.DisplayAlerts(model, this);
TempData["OldModel"] = model;
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
[Route(#"TaskManagement/Edit/{guid}")]
public async Task<ActionResult> Edit(EditTaskViewModel model, Guid guid, string submit)
{
model.Guid = guid;
if (ModelState.IsValid) {
await model.UpdateTaskInRemoteService(new UserInfo(User), submit);
ControllerHelpers.DisplayAlerts(model, this, "Task successfully updated");
if (model.ErrorCode == null)
return RedirectToAction("Edit", new { guid = model.Guid });
return RedirectToAction("Index");
}
if (TempData["OldModel"] != null) {
model = (EditTaskViewModel)TempData["OldModel"];
}
return View(model);
}
Using session state (including TempData) like this may break when you have multiple copies of the page open. You can work around this by generating a unique ID for the session key and storing it in a hidden field.
However, I would try to avoid using session altogether.
A simple approach is to use hidden fields to store the values that aren't sent to the server because they are in disabled fields.
A more robust approach is a separate class (or at least a private method) that knows how to setup your model for the first time and in transition (e.g. failed server validation). I call these classes "composers" and I describe the approach here.
Pseudocode for how an action method with a composer might look:
if( ModelState.IsValid ){
return Redirect();
}
var rebuiltModel = _composer.ComposeEdit( incomingModel );
return View( rebuiltModel );
I think the answer was quite simple. The shortest and easiest way is to populate the object from the database\remote service once more.
The fields that user entered whether they're valid or not will stay as they were before. The rest of them will load once again.
The following two links access the same action method:
http://stage.bullydog.com/Products/accessories/podmount and http://stage.sctflash.com/Products/accessories/podmount
I use Request.Url.Host to determine the brand of products I want to return from a database. When I access http://stage.bullydog.com/Products/accessories/podmount first, Request.Url.Host contains the value stage.bullydog.com, but if I then go to http://stage.sctflash.com/Products/accessories/podmount, Request.Url.Host may contain stage.sctflash.com or it may contain stage.bullydog.com.
The action method that is called is:
public ActionResult GetAccessoriesByType(RenderModel model, string id)
{
Common _common = new Common();
string brand = Request.Url != null ? _common.GetProductBrand() : BrandType.SCT;
var productSearchResultsModel = new ProductSearchResultsModel
{
Accessories = _accessoryRepository.GetAccessoriesByType(id, brand)
};
return View("~/Views/accessories.cshtml", productSearchResultsModel);
}
The code that gets the brand is:
public class Common
{
public string GetProductBrand()
{
var host = HttpContext.Current.Request.Url.Host;
if (host.Contains("sctflash"))
return BrandType.SCT;
if (host.Contains("bigrig") || host.Contains("bigrigs"))
return BrandType.BigRig;
if (host.Contains("bullydog"))
return BrandType.BullyDog;
return BrandType.SCT;
}
}
How can I ensure the Request.Url.Host contains the proper host when the same action method is accessed from two different hosts?
You can see this in action if you go to http://stage.bullydog.com/Products/accessories/podmount and then go to http://stage.sctflash.com/Products/accessories/podmount and refresh either one, the logo should change which means that the value returned by Request.Url.Host was incorrect.
Also, can GetProductBrand be static and still be thread-safe in this case?
The reason why accessing another website was bringing back the one websites content and the Request.Url.Host had the other websites information was because I had declared OutPutCache[Duration=60] at the top of my controller. If I waited 60 seconds and then refreshed, the correct data for each website would return. Once I removed this from the controller, everything worked as expected.
I am working on a code project in Asp.net MVC. I have an issue with redirecting the user after they have completed an action. I have these controllers:
Index Search Page:
public ActionResult Index(){
//this method sets up viewmodel data for search preferences
Viewmodel obj = new Viewmodel();
//set values of dropdowns and searching capabilities
return View("Search", obj);
}
The user then fills out the search boxes in the view, chooses dropdowns. This will return a post search method that handles the data:
[HttpPost]
public ActionResult Index(Viewmodel obj, int? page)
{
data = from i in db.Database
select i;
if(!String.IsNullOrEmpty(obj.Example)
{
data = data.Where(x => x.poss == obj.poss);
}
//PAGING and other data formatting here
return View("Results", data);
}
Once the result list is displayed, I have a checkbox/button system in the result view that allows the user to select multiple results and mark them as "Good", "Bad" ETC. This is a method that changes the database very simply. My problem is that after the database alters the data, im not sure how to return the user back to the exact result set they were at. A method that returns void doesn't work, and the parameters are not separated, (one whole viewmodel), so i can't simply save the URL and return them back to the unique URL. I want to keep the viewmodel as the parameter. How can I save the viewmodel data that contains their search preferences for use later as well as the page number without changing my method to this:
[HttpPost]
public ActionResult Index(string dropdown1, string dropdown2, int num......){}
One thing I did was save view model to the session. Then I deserialize in the index method. Something like this in the index method:
[HttpGet]
public ActionResult AdvancedSearch()
{
HttpContext currentContext = System.Web.HttpContext.Current;
AdvancedSearchViewModel advancedSearchViewModel = (AdvancedSearchViewModel)Session["AdvancedSearchViewModel"];
if (advancedSearchViewModel == null)
{
advancedSearchViewModel = new AdvancedSearchViewModel();
AddAdvancedSearchLists(advancedSearchViewModel, currentContext);
}
return View(advancedSearchViewModel);
}
Here is some code to save to session in the post:
Session["AdvancedSearchViewModel"] = advancedSearchViewModel;
Note that if you have listboxes (drop down and multi select) you have to rebuild the listboxes in the post method and reselect the selections (for multi select). HTML is stateless. MVC does not send the listbox contents back to the server on the post, it only sends what was selected.
You can use TempData to achieve this.
Store ViewModel and Page in TempData within your POST Index action. The action method used to accept and store result status (i.e. good, bad, etc...) in the database will be able to access ViewModel and Page from the TempData given that it is the next immediate request. Once the database operation is done, just use RedirectToAction with the ViewModel and Page present in the TempData.
If the action which updates result status is not the next immediate request then you need to keep the data in session as answered by BGStack.
View
#using (Html.BeginForm("UpdateClient", "Client")) {
Controller
[HttpPost]
public ActionResult UpdateClient(Client client)
{
if (ModelState.IsValid) {
bool ret = _clientRepository.UpdateClient(client);
return RedirectToAction("Index", "Home");
}
return View(client);
}
Repository
public bool UpdateClient(Client client)
{
using (var context = new Entities()) {
context.AttachTo("Clients",client);
context.ObjectStateManager.ChangeObjectState(client, EntityState.Modified);
context.SaveChanges();
}
return true;
}
When I call the UpdateClient in the controller, the client ID is 0. How do I pass the ID I have updated?
When you call your UpdateClient method from the client-side code (i.e, the browser), you need to make sure that the fields you want set are posted back to the receiving page.
For example, I often use jQuery and a simple ajax call to post data back to the server. The post needs to contain matching values for the Client object:
$.post("/controller/UpdateClient", {
"clientId": 1,
"otherProp1": "some value",
"otherProp2": "some value 2"
}, function (data) {
}
};
In your case, you probably need to make sure you've got an input field that contains a field named after ClientId so that it gets posted to the server that way:
<input type="hidden" id="clientId" name="clientId" value="1" />
Additionally, you may find it useful to installer Fiddler2 and use it to watch the data actually being posted to the server. It's invaluable when dealing with this type of thing.
There is not much info u gave us but your id can be 0 in 3 cases.
1. If you generate or input id on client side u just didn't post it properly. Missed hidden input, wrong variable name.
2. If you generate ur I'd in model constructor you should take in mind that there LL be default constructor called. Then it ll overwrite its properties with new values. Look at your id property is there something that can block or overwrite incoming value.
3. If u generate Id on database level then you should know that data base don't return id when creating new object.
Its a common problem, but I'm not very experienced in it(we generate id in business logic of application).
I'm sure ull track problem if u will have another look at your code.
I have an ASP.NET MVC2 application that uses a parent controller to setup specific variables that are used around the app. I also implement validation to make sure that an ID in the URI exists in the database. If it does not, I redirect and stop the execution of the script.
My parent controller looks something like this:
// Inside class declaration
// Set instance of account object to blank account
protected Account account = new Account();
protected override void Initialize(System.Web.Routing.RequestContext requestContext) {
// Call parent init method
base.init(requestContext);
// Check to make sure account id exists
if (accountRepos.DoesExistById(requestContext.RouteData.Values["aid"].ToString()) {
account = accountRepos.GetById(requestContext.RouteData.Values["aid"].ToString());
} else {
requestContext.HttpContext.Response.Redirect("url");
requestContext.HttpContext.Response.End();
}
}
At first this worked, but now when an incorrect id is entered, it doesn't redirect and throws a NullPointerException when the Account class is used. I originally just declared the account variable rather instantiating it, but that also proved to throw exceptions and didn't redirect.
The reason I try to end the execution of the script is because I want to make sure that it stops even if the redirect doesn't work. Kinda like calling exit() after header() in PHP :p . If I am doing this wrong, I would appreciate any pointers.
I'm just wondering how I can fix this.
Any help is greatly appreciated =D
I don't think that's the proper way to do what you want. Instead you should use route constraints on your routes to make sure the id exists, and fall back from there in a "catch all" route.
Something like this:
Routes.MapRoute("Name", "Url", new { ... }, new {
Id = new IdConstraint() // <- the constraint returns true/false which tells the route if it should proceed to the action
});
The constraint would be something like this:
public class IdConstraint : IRouteConstraint {
public bool Match(
HttpContextBase Context,
Route Route,
string Parameter,
RouteValueDictionary Dictionary,
RouteDirection Direction) {
try {
int Param = Convert.ToInt32(Dictionary[Parameter]);
using (DataContext dc = new DataContext() {
ObjectTrackingEnabled = false
}) {
return (dc.Table.Any(
t =>
(t.Id == Param)));
};
} catch (Exception) {
return (false);
};
}
}
This is what I use with my routes to make sure that I'm getting an Id that really exists. If it doesn't exist, the constraint returns a false, and the route does not execute and the request continues down the route chain. At the very bottom of your routes you should have a generic catch all route that sends your user to a page that tells them what they want doesn't exist and to do X or X (something along those lines, I'm just coming up with scenarios).