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.
Related
I just started working with asp.net mvc and am working on a legacy codebase with controllers that all have the same validation code at the front of every controller. (ie. multiple lines of the exact same code returning if the user id/key are not correct)
Is there any way to separate this out into an external function that can return an HTTP response immediately if validation fails?
Right now I can do something like this:
if (!validate()) {
return forbid("Your un or Password is incorrect");
}
or for a more specific message:
var errorMsg = validate();
if (errorMsg) {
return forbid(errorMsg);
}
but if I'm going to put this at the front of most of my controllers, I'd rather have a one-liner that I takes up as little room as possible. Is there an option for bypassing the rest of the controller function and returning an HTTP response from inside my validate() function? Or is there possibly a more appropriate way to do this?
I'm getting a
No route matches the supplied values
while trying to return a RedirectToAction("Action", "Controller"). The method signature says "actionName" and "ControllerName". I'm assuming actionName is the method name in the Controller, Am I correct? For ControllerName I'm using the Controller File Name without the Controller Sufix. Ex.:
return RedirectToAction("Index", "WebApp")
where Index is a method of WebAppController and the command is being issued from a method of AnotherController
Both the caller controller and the called one are on the same Controllers directory on the same application.
I'm cofused because in this ASP.net MVC application there is also Route attributes and Action attributes where you can put names on methods, different than the real method name. In my case I have no Route["Name"] nor [httpXXX("route", Name="dasdasdas")] configured for the methods involved in my attempt.
I have been reading MS docs and some examples but It appears I'm doing the thing right but for strange reasons it's not working. I even tried using Redirect("Controller/Action") and with it the problem vanishes but the new problem is this way of redirect doesn't support passing data parameters to the target route.
At this point I'm not working with Action links in Views, different from Form related ones.
I would really appreciate if at least anyone can give me a hint about where can I find info.
The right way to call an action from a different controller is the one I was using:
return RedirectToAction("AnActionMethodName", "AControllerWithoutControllerSufix"[, object values]);
My problem, after several hour spent was that I added two useMvc calls in the Startup.Configure(...)method:
app.UseMvc();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=MyApp}/{action=Index}/{id?}");
});
This was due to copy + paste of code. It was obviously confusing the MVC component in charge of attending the redirection part, as the right way I supose is tho use only one. (I'm newbie on .Net Platform and its frameworks)
Anyway I leave this as a reminder about the risks and consequences of copying and pasting code. At some point something weird can happen to your app and the cause can be a simple copy + paste error.
I am new to symfony. As an exersice I`m trying to make some basic cms.And I was wondering is this aproach of routing wrong:
/**
* #Route("/back-office/", name="back-office")
*/
public function indexAction(Request $request,$page="")
{
switch($page){
case "":
return $this->render('CmsBundle:BackOffice:index.html.twig');
break;
default:
return $this->render('CmsBundle:BackOffice:site-map.html.twig');
break;
}
}
This is my yaml confing:
back_office_pages:
path: /{page}
defaults: { _controller: CmsBundle:BackOffice:index}
By using this aproach I wont have to configure each route in the yaml file. Since routes may vary. But I am not quite sure this is the symfony way of doing things so I decided to ask for advice..
What I`m trying to achive:
Lets say we have a user that have less back-end programing expirience or not at all and he stumbuled upon the CMS. The goal is to add front end pages using some user interface. Then we store the pages(slug) in the database. In the index action we retrive this data. From the database we can also assing template to a page (we need the user to have at least some html+css+twig).
So what we do is get the pages that user added :
ex : Gallery, Contacts
we check the request url
and if the page requested is in the array from the database we return the template related to the page.
NOTE:
If you disagree with this method please do not bash me but eplain why is this wrong. Because as I said I am still new with the framework.
Try setting your routing to:
back_office_pages:
resource: "#CmsBundle/Controller/"
type: annotation
to set up Routing Annotations inside your CmsBundle.
Then, your action should be working using the url "/back-office/{page}"
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.
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