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

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.

Related

How can i do custom authorization in my ASP.NET MVC application

I am making a small SAAS application. I am using the same database for all users and only differentiating between the data using username and ids. This means that the user can type in a new url in the browser and see other users data. This, of course is not a desirable approach. I would like to do a check to see if the current user can actually access the resources: eg.
http://myapplication.com/images/15
And if the user changes the url to
http://myapplication.com/images/16
I should do a check in my database to see if the current user actually has access to see the images with user id 16. And if not redirect to a "not authorized" page.
How do I implement this?
The first step is to make sure that you never have any ID's for the user itself in the url. For instance, never have http://example.com/?user=10. You should always get the users id from their authentication rather than from the URL (or posted values either).
The second step, is to use that ID in your queries. So, for instance, let's say they seek http://example.com/images/100, then in your database you should have a mechanism that links the asset's ownership to the user, either a userid or a mapping table of id's to asset's, etc.. This way, if the user isn't allowed access, it will just return an empty result set. It's impossible for the data to be returned, and the empty result set should tell your page that the item doesn't exist (not necessarily an authorization failure, just that the object doesn't exist).
Third, any pages which are inherently about the user, such as a user profile, account page, or dashboard should never have any ID's at all in the URL, it should just automatically go to the authenticated users page.
Finally, if you need to prevent the user from accessing an entire page or set of pages, then you should do this in the OnAuthorization event or similar (custom attribute, base class, etc..) or using the built-in attribute authorization and use role based authorization. Never do authorization in the PageLoad or similar event (such as the controller action), because by the time you get to that step a lot of work has already happened in the pipeline. It's best to block access long before the page even starts to setup. Authorization events happen at the very beginning of the pipeline.
Make an Action that check userId and returns error page or file
public FileResult Image(string imageName)
{
string UserId = MethodWhereYouGetCurrentUserID();
if(imageName == null) return View("~/Views/Shared/Error.cshtml", (object)"Null image");
string imageShortName = imageName.Split(".")[0];
if(!UserId == imageShortName) return View(~/Views/Shared/Error.cshtml, (object)"You can't access to this");
string path = Server.MapPath("~/Contant/images/"+imageName);
return File(path, "image/jpg");
}
RouteConfig file
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute
(
name: "ImageRoute",
url: "/images/imageName",
default: new {controller = "Home", action = "GetImage"}
);
}

How to approach making a controller with (unlimited) db look-up routes

I'm digging into ASP.NET MVC from classic asp, and have completed some tutorials. I understand the concept now, but I have a main question about the controller. How are you able to control the url structure if you are getting your url's (with params) from a sql database?
Example: /custom-url-1 or /custom-url-23423411
(Returns params accordingly to feed the code)
I'm guessing it would have to do with ActionResult Index() , but not sure where to go after that. Any idea's where to look or is this even possible? Does MVC even allow this?
One Way you can approach this is to have everything go to one action in one controller and resolve the content in the view.
This is useful only if you have one type of view.
Second way is to have route constraint or a custom route constraint for each type of content you have
say : Galleries, Blog, Pages
and in each constraint check if the given url is of this type ( by db call), if the constraint returns true, it will point the request to the given controller and action.
The third way is to have a custom route handler that does the checking and routing (mind you this is probably the hardest task but works best if you have complex system, if your is simple try using method 1 or 2
P.S. if you want your urls separataed by "-" instead of "/" you can do just this
routes.MapRoute(
"Default", // Route name
"{controller}-{action}-{id}",// URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);

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

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.

Symfony2 output any HTML controller as JSON

I have a website completed that was created in Symfony2 and I now want a lot of the features of the site to now be made available in a mobile app.
My idea is by appending a simple URL variable then it will output all the variables of the relevant page request in JSON.
So if I connect to
www.domain.com/profile/john-smith
It returns the HTML page as now.
But if I go to
www.domain.com/profile/john-smith?app
Then it returns a JSON object of name, age and other profile info.
My app code then receives the JSON and processes.
I can't see any security issues as it's just really the variables presented in JSON and no HTML.
By doing the above I can create all the app code and simply make calls to the same URL as a web page, which would return the variables in JSON and save the need for any more server-side work.
The question is: How would I do this without modifying every controller?
I can't imagine an event listener would do it? Maybe I could intercept the Response object and strip out all the HTML?
Any ideas as to the best-practice way to do this? It should be pretty easy to code, but I'm trying to get my head around the design of it.
There is a correct way to configure the routes for this task
article_show:
path: /articles/{culture}/{year}/{title}.{_format}
defaults: { _controller: AcmeDemoBundle:Article:show, _format: html }
requirements:
culture: en|fr
_format: html|rss
year: \d+
However, this would still require you to edit every Controller with additional control structures to handle that output.
To solve that problem, you can do two things.
Create json templates for each template you have, then replace html in template.html.twig with template.'.$format.'.twig. (Be careful to ensure users can't pass a parameter without validation in the url, this would be a major security risk).
Create your own abstract controller class and override the render method to check the requested format and provide output based on that.
class MyAbstractController extends Symfony\Bundle\FrameworkBundle\Controller\Controller
{
public function render($view, array $parameters = array(), Response $response = null)
{
if($this->getRequest()->getRequestFormat() == 'json')
{
return new Response(json_encode($parameters));
}
else
{
parent::render($view, $parameters, $response);
}
}
}
NOTE The above code is a prototype, don't expect it to work out of the box.
I personally would deem the second method more correct, because there is no duplication of code, and less security concern.

Capturing an incoming webforms request in an MVC route

I'm converting a legacy webforms app to MVC, working through it a page at a time. To make the project easier to work with I've moved all the webforms pages, which were previously in the route of the project into a /webforms subdirectory. So I need to capture any incoming requests for /page.aspx?param=123 and redirect them to /webforms/page.aspx?param=123. I thought an easy way to do this would be to setup a route handler that passed any such requests to a controller that does that job. I set up a route like so:
routes.MapRoute("WebformsRedirect", "{*page}",
new { controller = "Webforms", action = "ForwardToPage" },
new { page = #"\S+.aspx\S*" }
);
This kind of works but it doesn't capture the query string, only the page part. I can get the query string for the Request object in the controller so it's not a huge deal but it would be nice to be able to do it through the route only. My routing unit tests (which I copied from Steve Sanderson's MVC book) actually pass correctly when I test them with querystrings so I'm confused why it isn't working. Is my regular expression wrong? They aren't my strong point.
QueryStrings are not part of the routing
if you requested for example "Home/Index?foo=bar" and you have a route that match "Foo/Bar" to Controller Foo , Action Bar without any more routing info (don't know anything about foo) you still can write
class HomeController: Controller {
ActionResult Index(string foo) {
}
}
now foo variable will equal bar , why ?
because its the model binder that gets the value of the parameters passed.
the model binder check 4 repositories by default QueryString , Routing Place Holders ,FormsCollections and Files
so what i am trying to say , the route and QueryStrings are two different things , it doesn't need to capture it

Resources