How to pass multiple objects using RedirectToAction() in Asp.NET MVC? - asp.net

I would like to pass multiple objects using redirectToAction() method. Below is the actionresult i'm redirecting to.
public ActionResult GetEmployees(Models.Department department, Models.Category category, Models.Role role)
{
return View();
}
I'd like to do something like the below
public ActionResult test()
{
Models.Department dep = new Models.Department();
Models.Category cat.......etc
return RedirectToAction("GetEmployees", dep, cat, role); }
Any help would be greatly appreciated - thanks
Updated
Can I use something like
Models.Department dep = new Models.Department() { DepId = employee.DepartmentId };
Models.Category cat = new Models.Category() { CatId = employee.JobCategoryId };
Models.Role title = new Models.Role() { RoleId = employee.JobTitleId };
return RedirectToAction("GetEmployees", new { department = dep, category = cat, role = title });

You cannot pass objects to the RedirectToAction method. This method is designed to pass only parameters. So you will need to pass all the values you want to be emitted in the corresponding GET request:
return RedirectToAction("GetEmployees", new
{
DepId = dep.DepId,
DepName = dep.DepName,
CatId = cat.CatId,
RoleId = role.RoleId,
... so on for each property you need
});
But a better way is to only send the ids of those objects:
return RedirectToAction("GetEmployees", new
{
DepId = dep.DepId,
CatId = cat.CatId,
RoleId = role.RoleId
});
and then in the target controller action use those ids to retrieve the entities from your underlying datasore:
public ActionResult GetEmployees(int depId, int catId, int roleId)
{
var dep = repository.GetDep(depId);
var cat = repository.GetCat(catId);
var role = repository.GetRole(roleId);
...
}

This is not an answer, per se, but I see questions similar to this all the time that boil down to a fundamental misunderstanding or lack of understanding about how HTTP works. This is not a criticism, many web developers think that all they need to know is HTML, JavaScript and CSS to make a website, but they neglect to see the need to actually understand the platform their code is running on. Look at it this way: you wouldn't sit down and start writing an app without understanding your target platform (Windows, Mac, iOS, Android, etc.). You'd have to know how each handles memory, garbage collection, storage, etc., in order to write anything that would amount to anything.
The same applies to the web. It's a distributed platform, but a platform nonetheless, and it's important to understand how the underlying structure works. I obviously won't go into extreme detail on this here. There's entire volumes on HTTP and associated technologies. For more information, I highly recommend picking up something like O'Reilly's HTTP: The Definitive Guide.
As for your problem here, HTTP implements various "verbs", the most common of which are GET and POST. Simplistically, GET is a non-volatile request for a resource to be returned, while POST is volatile (changes will be made, resources deleted, etc.). With GET there is no request "body". A request can be composed of various parts, a URL, headers, and a body. In a POST, the posted data would constitute the request body, but GET does not have posted data and therefore no request body. Now, again, we're speaking simplistically here. You might wonder about the querystring. Would that not be "posted data"? Technically, yes, it can be, but again, technically, anything in the URL (or if we really want to be exact, the URI) is a piece of identifying data for an existing resource. When you make a search on Google, for example, your search query will be appended to the URI for the search results page. This is posted data (you posted the query), but it's not just data, the URI with the query string gives the location of the resource that corresponds to that exact query. Someone else who entered the same query would be sent to the same URL.
That was a bit of a tangent, but it's important to understand that a querystring is not a way to pass unrelated data. The querystring is part of the URI so the exact page loaded with two different querystrings is two entirely different resources.
Moving on, a redirect is not some special type of request (in the sense of being represented by a different HTTP verb); it's merely an instruction to the client's browser that it should issue another GET request to the specified URL. You don't get any control over what verb is used: it's always GET. So, you can't pass anything along for the ride. If you have objects that will be represented by the URI being redirected to, then obviously you would pass the identifying information required to retrieve them (id, slug, etc.) using either the URI path and/or querystring. If there's any data not directly related to the resource being represented, that data must go in some other type of storage system, such as the session.

Related

how can i hide ID in URL with asp.net MVC4

My URL
http://www.domain.com/Products/{Alias}-{ID}
and my route
routes.MapRoute(
name: "ProductDetail",
url: "Products/{Alias}-{detailId}",
defaults: new { controller = "Products", action = "ProductDetail", id = UrlParameter.Optional }
);
In controller
public ActionResult ProductDetail(int? detailId)
{
var pro = db.Products.Find(detailId);
if (pro == null)
{
return RedirectToAction("Index", "NotFound");
}
return View(pro);
}
Now, I want to hide ID in my URL like
http://www.domain.com/Products/{Alias}
How can i do that
Short Answer
It is not possible to do what you want. In that if you want to be able to access the detailId from your controller the you must pass the detailId as part of your URL - you cannot access something that does not exist.
Long Answer
There are other ways to get around 'hiding' the detailId from the user, and here are some suggestions:
1. Use Alias instead:
You can remove detailId all together and use the Alias value instead. This however will require the Alias value to be unique to the product you are trying to query, and the required changes might look like this:
routes.MapRoute(
//..
url: "Products/{Alias}",
//..
);
public ActionResult ProductDetail(string Alias)
{
var pro = db.Products.FindByAlias(Alias);
//...
}
2. Use a POST request:
Another solution that will effectively hide the detailId from the URL but still allow the value to be passed to the controller would be to use a POST request, where the parameter value would be specified in the POST request body.
The drawback of this however is that you cannot simply provide a URL for the user to click, and coding links within your site takes considerably more effort. Typically with MVC, POST request occur when a form is submitted, and you can also do POST request with javascript ajax calls.
This is a bit too much to go into in this answer so if you are interested then do some research, such as this question, or some generally info here.
3. Encrypt the detailId value:
Now this options doesn't hide the detailId from the URL, but if your concern is that the current ID is just too user friendly then you could 'encrypt' (used loosely) the value. For example you could convert to a base64 string, and then back to an int within your controller. This would give you a URL something like this:
http://www.domain.com/Products/{Alias}-MQ%3D%3D
This URL represents 1 as the detailId and you have to be ensure to URL encode/decode your values if using this method.
In this instance, Base64 conversion isn't really 'encrypting' it, any semi-savvy user will notice this and could get around it. But you could just as easily use a more secure 2-way encryption algorithm if you wanted to take this approach, one where only the server knows the encryption/decryption key. The drawback here is that only the server will be able to produce valid URLs for your users to 'click on'.
At this point it is worth considering that if your concern is that the URL is too user friendly by including a simple numeric ID, then the question is: why do you care?
If you are worried the user could simply change the detailId value and then have access to products they should have access to, then you have a bigger problem with security. If this is the case, then your controller should be responsibly for validating is the user has access to the product they are trying to access, and then act accordingly.
All security checking and validation should be handled server-side, never rely on your client code or user actions to do it for you.

Sails.js: How to properly handle request parameters/queries to backend?

I'm developing an application using Sails.js in the backend, and I'm having trouble validating requests as follows:
I want to block access to certain resources based on an attribute of the logged in user. I have the REST API blueprints enabled, and the shortcut routes disabled. I have the following piece of code:
User.findOne()
.where(query)
.exec(function(err, user) {
if (user.team !== req.user.team) {
return res.view('403');
}
return next();
});
where query is the criteria by which I'd like to do the database search. The idea is that the user can only access the requested user if they're in the same "team".
The problem is that the user can make at least the following kinds of requests to the backend, maybe more (I'm not too experienced with sails, please enlighten me if there's even more types):
localhost:1337/user?id=1
In this case, the req object will have a req.query attribute, which I can pass on as query as it is. Now, the following kind of request is also possible:
localhost:1337/user/1
Here req.query will be an empty object, while req.params is [ id: '1'].
Now this is troublesome; if I understand correctly, the the type of req.params isn't a JSON object, so I can't pass it as query as it is. In addition, I'd have to convert the id parameter into Int since it's originally a string for some reason (?).
What I'm asking is if there's a way I may have missed that handles both kinds of requests in the same way, or whether I'll have to take both cases into account in a way like
var query = isEmpty(req.query) ? req.params : req.query
in which case I'd have to convert req.params into something I could pass to the database search and would generally be troublesome and inconvenient. I'd like to know what the best way to handle this situation is.
Well, it's funny how right after posting a question you happen to find an answer. Apparently there's a function called req.allParams() which "Includes parameters parsed from the url path, the query string, and the request body." according to the official docs. I've no idea how I never bumped into this before, but now I did and it seems to work, so hooray!

Web API Complex Data in Get

I am using Web APi, as I am new to this, I dont know much about it.
I am trying to implement search, as of now I am starting with only text search, but later there may be huge search criteria. for one text that is easy, as web api works good with
primitive data types. Now I want to create a class of filter, say the pagenumber , the pagesize also all the search criteria, so I created a class. I have created a MVC application which is communicating with the web api, the web api returns Json data, then I de-serialize it to model. I am stuck with the complex object part, also as of now I am using a list to get the data, later that will be replaced by data base. Following is the code.
public IEnumerable<Document> Get(PaggingDetails request) //public async Task<IEnumerable<Note>> GetNotes() for Async (DB)
{
return _repository.GetAll(pagedetails.PageNumber, pagedetails.PageSize, pagedetails.PageFilter);
//return await db.Notes.ToListAsync<Note>(); for async
}
public string GetPage(int pagenumber,int pagesize,string pagefilter)
{
try
{
PaggingDetails PageDetails = new PaggingDetails();
PageDetails.PageFilter = pagefilter;
PageDetails.PageSize = pagesize;
PageDetails.PageNumber = pagenumber;
return new System.Net.WebClient().DownloadString
("http://.../api/Document/?pagedetails=" +
PageDetails);
//new HttpClient().GetStringAsync("http://localhost:18545/api/Emails"); for async
//also pass parameters
}
catch (Exception ex)
{
}
return "";
}
By deafult, you cannot use a class as the type of parameter of a GET Web API action. You need to use individual parameters of single types.
If you want to use a class as parameter nothing stops you to use a POST action, in which you can include the data without any problem.
However you can force a complex parameter of a GET action to be read from the URI by decorating the comples attribute with [FromUri].
You can read this document to better understand Web API parameter binding:
Parameter Binding in ASP.NET Web API
By default, Web API uses the following rules to bind parameters:
If the parameter is a “simple” type, Web API tries to get the value from the URI. Simple types include the .NET primitive types (int, bool, double, and so forth), plus TimeSpan, DateTime, Guid, decimal, and string, plus any type with a type converter that can convert from a string. (More about type converters later.)
For complex types, Web API tries to read the value from the message body, using a media-type formatter.
This is the standard way of working. If you use the [FromUri] attribute, the action selector won't be able to choose between different Get methods that receive different complex types. If you use a route with controller and action segments, you won't have that problem, becaus ethe actions selector will choose by action name, no matter what the aprameters are.
I don't like using the [FromUri] for this reason, and beacuse it's not the natural way to work with the GET action. But you can use it with the necessary precautions.

TempData implementation changes - Reasons for the change

In ASP.NET MVC 2, the lifespan of an entry in the TempDataDictionary was just one HTTP Request.
That translated to setting a value in one request, redirecting, and having access to the same item at the other end of the line. After this the entry would be no longer available, regardless of whether you read the value out of the dictionary at the latter end of the line or not.
Since ASP.NET MVC 3 (I believe), this implementation detail has changed quite significantly.
Entries in the TempDataDictionary are now only removed once they've been read.
MVC 4
public object this[string key]
{
get
{
object obj;
if (!this.TryGetValue(key, out obj))
return (object) null;
this._initialKeys.Remove(key);
return obj;
}
}
and
public bool TryGetValue(string key, out object value)
{
this._initialKeys.Remove(key);
return this._data.TryGetValue(key, out value);
}
MVC 2:
public object this[string key] {
get {
object value;
if (TryGetValue(key, out value)) {
return value;
}
return null;
}
and
public bool TryGetValue(string key, out object value) {
return _data.TryGetValue(key, out value);
}
Since most people seem to put items in the TempData collection in one request and immediately read them back out in the immediate next request, the functionality seems roughtly the same.
In scenarios where this is not the case such as wanting to read the TempData entry if redirected to one place, and expecting it to have been removed if requesting other resources and navigating back, this change has quite an impact.
No longer is the entry available for one http request but is available over many HTTP requests, be it only available to one single get on the dictionary.
I'd like to know more about this implimentation change, what were the reasons for the change, was this simply to cater for multiple redirects or are there deeper benefits?
Secondary to that, I'm intrigued to know if there's anything built in that now caters for single HTTP request sharing of data in the same way that TempData used to cater for?
You're correct that TempData keys are only cleared if they’ve been read (or after the user’s session expires) but this has been the case since MVC2, (http://forums.asp.net/post/3692286.aspx)
I'd like to know more about this implimentation change, what were the
reasons for the change, was this simply to cater for multiple
redirects or are there deeper benefits?
This change prevented problems that arose in MVC 1, such as TempData keys being deleted before they were read. So yes, the primary benefit is in avoiding these problems when you have multiple re-directs, or interleaved requests. In addition, the RedirectToRouteResult or RedirectResult methods now automatically call TempData.Keep() to prevent clearing of keys, even after they've been read so keep that in mind as well.
In scenarios where this is not the case such as wanting to read the
TempData entry if redirected to one place, and expecting it to have
been removed if requesting other resources and navigating back, this
change has quite an impact.
You’re correct, if you've been coding under the assumption that the TempData keys are cleared automatically you could run into unexpected problems. You can call TempData.Clear() to manually remove all keys from the TempDataDictionary, or TempData.Remove(key) to remove a specific key. You can also use TempData.Peek() to read the value of a TempData key without flagging it for removal from the TempDataDictionary.
Secondary to that, I'm intrigued to know if there's anything built in
that now caters for single HTTP request sharing of data in the same
way that TempData used to cater for?
I'm not aware of any new objects or functions that replicate the original implementation of TempData. Essentially we still use TempData but have to be mindful that the data persists until read and clear the dictionary manually if needed.

How to create a simple ASP.NET MVC action method that accepts HTTP-POST data?

i wish to have a simple Action in my controller that accepts a few optional values and some integer values.
this is my route i wish to have:
HTTP.POST
/review/create
and this is the Action method i would like...
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult Create(int userId,
int addressId,
byte baseScore,
byte reviewType,
string subject,
string description)
{ ... }
I'm under the uneducated impression that all of those arguments above will be populated by the forms collection values ... but it's not happening. Also, I have no idea how I would write a route, to handle those ... because those values are form post data....
here's my global.asax....
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Api - Search methods.
routes.MapRoute(
"Search Methods",
"{controller}/{action}"
);
In fact, the action method is never called because it doesn't seem to find it :(
But, if create and action without any of those arguments, then it finds it ?????????
How would you write a route and action method to accept some require and some optional arguments, for the route /review/create ?
As far as i can see you may rewrite your controller action like this:
public ActionResult Create(int foo, int bar, byte blah, string name, int? xxx) {
// code here
}
The ModelBinder will then ensure that foo,bar and blah are set. Name and xxx may be null. I can't test it a the moment, but i think return type of the action should be ActionResult.
If you are POST'ing a form, just make sure that the elements in your form (textboxes, checkboxes, textarea, etc) have id's that match the parameters in your method. As an alternative you can pass a FormCollection to the method, and do myFormCollection["foo"] to get a string representation of the value (which can then be parsed to an int).
From my experience, you are missing a number of key elements and concepts with this question.
First and foremost, I don't believe you can execute a POST without a form. The form has to contain the controls from which you pull the values that get passed to the controller method. If the goal is to simply unit test your POST controller method, then just call the method directly in your test, which it appears that you're doing, based on one of your comments. If you involve the view, then you're doing integration testing, not unit testing. Regardless of the test type, the test will always fail because you are choosing not to build the form. Even if you manage to force the POST using Fiddler, Firebug or any other mechanism, you're still not testing the view, you're testing the HTTP protocol.
I highly recommend that you employ a web application testing tool, such as WatiN or Selenium, to test your web pages, rather than throw together a quick-and-dirty test that really doesn't test anything useful.
In your post request set content-type="application/json; charset=UTF-8" and pass the values for the method parameter in JSON format. This should make Asp.MVC not to look in FormCollection for those values.

Resources