Can Spring MVC controller set the URL to be shown in browser? - spring-mvc

We have a Spring MVC Controller method which servers the url pattern /hello/{userName} .
#RequestMapping("/hello/{userName}")
public ModelAndView helloWorld(#PathVariable("userName") String productId) {
String message = "HELLO"+userName;
return new ModelAndView("hellopage", "message", message);
}
Here when we request /hello/Tom ,hellopage.html will be servered with URL in the browser http://localhost:8080/myApp/hello/Tom
We would need the URL to be http://localhost:8080/myApp/Tom .Is there any way I can set the URL to be shown in the browser when returning from the controller.

Sure you can do that using redirect. Write two controllers:
#RequestMapping("/hello/{userName}")
public string helloWorld(#PathVariable("userName") String userName, Model model) {
String message = "HELLO" + userName;
model.addAttribute("message", message);
return "redirect:" + userName;
}
#RequestMapping("/{userName}")
public ModelAndView userHello((#ModelAttribute("message") String message)) {
return new ModelAndView("hellopage", "message", message);
}

I think you could also use tuckey url-rewrite for this: http://tuckey.org/urlrewrite/
This allows Apache mod_rewrite functionality in Tomcat through the use of a filter, and you wouldn't have to write two controllers for everything.

Related

Spring MVC UI service form submission behind Spring Cloud Zuul

The problem is when a form POST happens and then the controller does a redirect it seems that the redirect does not know it is behind Zuul.
Form:
#RequestMapping(value = "/create-something", method = RequestMethod.GET)
public String getForm(Model model, #CookieValue(value = "XSRF-TOKEN", defaultValue = "none", required=true) String token) {
model.addAttribute("title", "Create New Something");
model.addAttribute("_csrf", token);
return "views/create-something";
}
POST:
#RequestMapping(value = "/create-something", method = RequestMethod.POST)
public String postForm(Model model, #ModelAttribute Something something) {
SomethingClient.createSomething(something);
return "redirect:" + something.getName() + "/";
}
This setup will result in the redirect trying to hit the internal IP of the UI service not the public Zuul URL.
After trying different approaches I finally settled on a direct URL. Not sure if there is a better solution but this does work and resolves the above problem.
New POST:
#RequestMapping(value = "/create-something", method = RequestMethod.POST)
public String postForm(Model model, #ModelAttribute Something something) {
SomethingClient.createSomething(something);
return "redirect:http://domain.com/ui/" + something.getName() + "/";
}
Another thought would be an interceptor. That felt like added complexity times a large number of UI services.

ASP MVC - How can I change the base url of the request for Url.Content?

Let's say I have a request with the following url:
foo.bar.com/do/something
The "something" action of the "do" controller returns a view with an image that has the following url: foo.bar.com/content/image.png (generated by the helper Url.Content) - this is just an example, my actual page has a lot of images
I want to know what can I do in the action to change the behaviour of the Url.Content so that it generates my image url with the url localhost/content/image.png.
This probably is not the best solution, but it may work for you:
You could write a extension such as the one below to achieve this:
// Determine if gen localhost or the normal hostname
public static bool IsUseLocalhost { get; set; }
public static string ContentFullPath(this UrlHelper url
, string virtualPath, string schema = "", string host = "")
{
var result = string.Empty;
Uri requestUrl = url.RequestContext.HttpContext.Request.Url;
if (string.IsNullOrEmpty(schema))
{
schema = requestUrl.Scheme;
}
if (string.IsNullOrEmpty(host))
{
if (IsUseLocalhost)
{
host = "localhost";
}
else
{
host = requestUrl.Authority;
}
}
result = string.Format("{0}://{1}{2}",
schema,
host,
VirtualPathUtility.ToAbsolute(virtualPath));
return result;
}
In the Action you can set the static IsUseLocalhost to true to turn all gen url with localhost.
Then in the view use it as:
#Url.ContentFullPath("~/content/image.png")
If you want to set explicity host, then in the view use it as:
#Url.ContentFullPath("~/content/image.png", host: "localhost")

HTTP Status 405 - Request method 'POST' not supported - jQuery.post()

Update: See here for solution
Using Spring MVC 4.
Here's my JavaScript code that makes the POST request:
$("input.toggleCourse").change(function(e){
var d = {
classID: classID,
courseID: courseID
};
$.post(
"<c:url value="/class/addCourse" />",
JSON.stringify(d))
.done(function(data){
alert("ok");
});
});
(Tried with and without JSON.stringify, tried full $.ajax instead of $.post)
Here's my controller
#RequestMapping(value = "/class/addCourse", method = RequestMethod.POST)
public #ResponseBody String addCourse(#RequestBody final CourseInClass cic) {
StringBuilder sb = new StringBuilder();
try{
Class c = classServ.findOne(cic.ClassID);
c.Courses.add(courseServ.findOne(cic.CourseID));
sb.append("{success:true}");
} catch (Exception e){
sb.append("{error:\"").append(e.getMessage()).append("\"}");
}
return sb.toString();
}
I checked the network log that it sends the correct headers to the correct url. Post requests work for normal forms, but not for this ajax call.
Thanks.
How do you think (String classID, String courseID) will be detected by Spring. i.e. how will the json object be mapped to java object.
If you want to use auto binding you can use jackson-mapper-asl. Take a look at this page
If you don't want to use it you can use #PathVariable,
change method signatures to public #ResponseBody String addCourse(#PathVariable String classID, #PathVariable String courseID) {..}
and then hit http://localhost:8080/<appname>/class/addCourse/<classID>/<courseID>

ASP.NET Web Api returns 200 OK when it should return 404

I have the following action methods on a controller in an ASP.NET Web API project:
[Route("api/v2/project/{projectId}/stuff"), HttpGet]
public IHttpActionResult Get(int projectId)
[Route("api/v2/project/{projectId}/stuff/{id:guid}"), HttpGet]
public IHttpActionResult Get(int projectId, [FromUri] Guid id)
[Route("api/v2/project/{projectId}/stuff"), HttpPost]
public IHttpActionResult Post(int projectId, [Required] Stuff stuff)
[Route("api/v2/project/{projectId}/stuff/{id:guid}"), HttpPut]
public IHttpActionResult Put(int projectId, [FromUri] Guid blastId, Stuff stuff)
[Route("api/v2/project/{projectId}/stuff/{id:guid}"), HttpDelete]
public IHttpActionResult Delete(int projectId, [FromUri] Guid id)
Due to a javascript error, I made a DELETE request to
api/v2/project/1234/stuff/undefined
i.e. instead of a GUID for the id, I got the string "undefined". As far as I can tell, this shouldn't match any of my routes, but instead of a 404 Not found (or even 405 Method not allowed), I got a 200 OK as response.
I set a breakpoint in each of these action methods and repeated the request using Fiddler, but none of the breakpoints was hit. I also tried installing the WebApiRouteDebugger package from nuget, but we're using a custom controller factory which hooks things up through our DI container, so I couldn't get it to work at all. I even tried throwing the following exception from one of my globally registered filters:
throw new Exception(actionContext.ControllerContext.ControllerDescriptor.ControllerName +
" " + actionContext.ActionDescriptor.ActionName);
but the DELETE request still goes through to 200 OK (no requests to valid urls seem to do that).
How else can I troubleshoot this? What could be the root cause?
In your Global.asax.cs file where your protected void Application_Start(object sender, EventArgs e) is, add the following:
protected void Application_BeginRequest(object sender, EventArgs e)
{
}
All server requests should come through here.
Add these using if not there.
using System.Web.Compilation;
using System.Reflection;
Then in the begin request call add this code to list out all of the active routes.
string Items = "";
IEnumerable<Assembly> Assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>();
foreach (Assembly FoundAssembly in Assemblies)
{
string AssemblyName = FoundAssembly.FullName;
IEnumerable<TypeInfo> Types = FoundAssembly.DefinedTypes.Where(type => type != null && type.IsPublic && type.IsClass && !type.IsAbstract && typeof(ApiController).IsAssignableFrom(type));
foreach (TypeInfo ControllerType in Types)
{
System.Web.Http.Controllers.ApiControllerActionSelector ApiControllerSelection = new System.Web.Http.Controllers.ApiControllerActionSelector();
System.Web.Http.Controllers.HttpControllerDescriptor ApiDescriptor = new System.Web.Http.Controllers.HttpControllerDescriptor(new System.Web.Http.HttpConfiguration(), ControllerType.Name, ControllerType);
ILookup<string, System.Web.Http.Controllers.HttpActionDescriptor> ApiMappings = ApiControllerSelection.GetActionMapping(ApiDescriptor);
foreach (var Maps in ApiMappings)
{
foreach (System.Web.Http.Controllers.HttpActionDescriptor Actions in Maps)
{
Items += "[ controller=" + ControllerType.Name + " action=" + ((System.Web.Http.Controllers.ReflectedHttpActionDescriptor)(Actions)).MethodInfo + "]";
}
}
}
}
This will list all controllers and their signatures. If your URL does not fit into any of these you may have to expand the list to include non controller routes.
I had the same issue. In my startup.cs I had the method
app.Run(async context =>
{
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("My Api");
});
This was causing all routes to receive a 200 response along with the string.
I only wanted to show this response on startup. This was the solution.
app.MapWhen(ctx => ctx.Request.Path.Value == "/", StartupPage);
Along with a method in the same Startup class
private void StartupPage(IAppBuilder app)
{
app.Run(async (context) =>
{
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("My Api");
});
}
I only return the 200 OK message when a request is made on the base url.
I'm am not sure of your routing but I think you have a routing that does not include actions in mapping:
api/v2/project/1234/stuff/undefined
Check web api config for this: api/{controller}[/{action}]/{id}. I think you might hit another route action with your action(like a GET).
P.S. post your route config to update answer.

Issue with mvc:annotation-driven and #PathVariable

Not sure what I am doing wrong here but when I DO NOT define the mvc:annotation-driven in my servlet, the value returned from the #PathVariable is not getting displayed in my jsp page and when I do define the annotation-driven, all other links e.g. home gets broken and I get the The specified HTTP method is not allowed for the requested resource (Request method GET not supported).
#Controller("HealthCheckController")
#RequestMapping("/healthCheckSummary")
public class HealthCheckController {
private Log log = LogFactory.getLog(GolSimpleMappingExceptionResolver.class);
private HealthCheckService healthCheckService = null;
private IEappDataAccessFacade dataAccessFacade;
#RequestMapping(value = "/{username}/{password}", method = RequestMethod.GET)
public String getEappStatus(#PathVariable String username, #PathVariable String password, Model model){
String dbConnectivityStatus = getDatabaseConnectivityStatus() ? "online" : "offline";
if (!username.equals("lola") || !password.equals("123")) {
// wrong credentials were provided
log.error("The login credentials in the header are not valid." + username + password);
throw new RuntimeException( "Unable to continue, the login credentials in the header are not valid." + username + password);
}
model.addAttribute("healthCheckSummary", dbConnectivityStatus);
return "healthCheckSummary";
}
public HealthCheckService getHealthCheckService()
{
return healthCheckService;
}
public boolean getDatabaseConnectivityStatus() {
String result = “OK”;
if (result != null) {
return true;
}
return false;
}
}
Oh and in the application context we have defined the
<tx:annotation-driven />
JSP page
<%# page language="java"%>
Welcome ${username} - ${password}
Eapp is currently ${healthCheckSummary}
Two things:
You never put your username and password #PathVariables in the model. Your jsp page has no way of knowing that they even existed as you lose any reference to them after the code leaves getEappStatus. Add the following to your handler method:
model.addAttribute("username", username);
model.addAttribute("password", password);
and see if that works.
You might want to add <mvc:annotation-driven/> just to avoid some surprises when you use some spring-mvc annotations. This isn't strictly necessary, but might save you some head scratching when some #RequestBody doesn't work.

Resources