I've my rest uri's as below
/leagues - Lists all the leagues organized so far
/leagues/{leagueName} - Details of the give leagueName
/leagues/{leagueName}/levels/ - Lists all the levels of a given league (For e.g. Playoffs, QF, SF etc)
Controllers
1.
#RequestMapping (value = "/leagues", method = RequestMethod.GET)
public String list(Model model)
{
return "/leagues/list";
}
2.
#RequestMapping (value = "/leagues/{leagueName}", method = RequestMethod.GET)
public String leagues(#PathVariable String leagueName, Model model)
{
return "/leagues/league";
}
3.
#RequestMapping (value = "/leagues/{leagueName}/levels", method = RequestMethod.GET)
public String levels(#PathVariable ("leagueName") String leagueName, Model model)
{
return "/leagues/levels/list";
}
The problem I am having is that the controller method for the (2) /leagues/{leagueName} is being invoked when I try (2) /leagues/levels/. Need help on how to fix this.
You forget to add the leguname to the url: use /leagues/SuperHero/levels/ instead (/leagues/levels/)
If you want to filter out /leagues/levels/`, then you can try to add an additional handler method:
#RequestMapping(value="/leagues/levels/")
for example, to return all levels of any league
The behavior is perfectly fine... The URL leagues/levels matches the second method's RequestMapping...
If you want to invoke the third method u need the URL as leagues/[some string]/levels
Related
I have the following code in my receiving controller:
[Route("api/StudentUserId/{string}"), HttpGet]
public StudentDto StudentUserId(string userId)
{
StudentModel sm = new StudentModel(userId);
StudentDto dto = sm.ConvertToDto();
return dto;
}
After running this project, I have another project that I use to test the WebAPI controller calls. I use the following code to read a student record form the database using their userId:
protected T SendRequestToReadItemUsingString<T>(string resource, string userId) where T : new()
{
string resourceString = string.Format("{0}/{{userId}}", resource);
RestRequest request = new RestRequest(resourceString, Method.GET);
request.AddUrlSegment("userId", userId);
RestClient restClient = new RestClient(Service.Location);
var response = restClient.Execute<T>(request);
T retVal = response.Data;
return retVal;
}
Comparible code seems to work if I change the userId to an int Id in both the controller and calling code. I can't seem to get it to work with string. If I place a breakpoint in the controller code it never hits it and the calling code just returns a null.
Thanks for your help
Please note that WebApi works based on reflection this means that your curly braces {vars} must match the same name in your methods.
Therefore to match this api/StudentUserId/{string} your method needs to be declare like this:
[Route("api/StudentUserId/{userId}"), HttpGet]
public StudentDto StudentUserId(string userId)
return userId;
}
Where the parameter {string} was replaced by userId.
If you want to read more about this Routing Rules here is similar post on this;
WebApi Routing Configuration
What I have is the following extension method:
public MyCustomAttribute[] GetActionAttributes(
this Controller #this,
string action,
string controller,
string area,
string method)
{
}
How does ASP.NET MVC 3 find the action method, given the area, controller, action names and the method (GET, POST)?
To this moment I have nothing... no clues on how to do this.
I am currently looking for the stack trace inside a controller action, to find out how MVC dicovered it.
Why I need these attributes
My attributes contain information about whether a given user can or not access it... but depending on whether they can or not access it, I wan't to show or hide some html fields, links, and other things that could call that action.
Other uses
I have thought of using this to place an attribute over an action, that tells the css class of the link that will be rendered to call it... and some other UI hints... and then build an HtmlHelper that will render that link, looking at these attributes.
Not a duplicate
Yes, some will say this is possibly a duplicate of this question...
that does not have the answer I want:
How can i get the MethodInfo of the controller action that will get called given a request?
That's why I have specified the circumstances of my question.
I have looked inside MVC 3 source code, and tested with MVC 4, and discovered how to do it.
I have tagged the question wrong... it is not for MVC 3, I am using MVC 4. Though, as I could find a solution looking at MVC 3 code, then it may work with MVC 3 too.
At the end... I hope this is worth 5 hours of exploration, with a lot trials and errors.
Works with
MVC 3 (I think)
MVC 4 (tested)
Drawbacks of my solution
Unfortunately, this solution is quite complex, and dependent on things that I don't like very much:
static object ControllerBuilder.Current (very bad for unit testing)
a lot of classes from MVC (high coupling is always bad)
not universal (it works with MVC 3 default objects, but may not work with other implementations derived from MVC... e.g. derived MvcHandler, custom IControllerFactory, and so on ...)
internals dependency (depends on specific aspects of MVC 3, (MVC 4 behaves like this too) may be MVC 5 is different... e.g. I know that RouteData object is not used to find the controller type, so I simply use stub RouteData objects)
mocks of complex objects to pass data (I needed to mock HttpContextWrapper and HttpRequestWrapper in order to set the http method to be POST or GET... these pretty simple values comes from complex objects (oh god! =\ ))
The code
public static Attribute[] GetAttributes(
this Controller #this,
string action = null,
string controller = null,
string method = "GET")
{
var actionName = action
?? #this.RouteData.GetRequiredString("action");
var controllerName = controller
?? #this.RouteData.GetRequiredString("controller");
var controllerFactory = ControllerBuilder.Current
.GetControllerFactory();
var controllerContext = #this.ControllerContext;
var otherController = (ControllerBase)controllerFactory
.CreateController(
new RequestContext(controllerContext.HttpContext, new RouteData()),
controllerName);
var controllerDescriptor = new ReflectedControllerDescriptor(
otherController.GetType());
var controllerContext2 = new ControllerContext(
new MockHttpContextWrapper(
controllerContext.HttpContext.ApplicationInstance.Context,
method),
new RouteData(),
otherController);
var actionDescriptor = controllerDescriptor
.FindAction(controllerContext2, actionName);
var attributes = actionDescriptor.GetCustomAttributes(true)
.Cast<Attribute>()
.ToArray();
return attributes;
}
EDIT
Forgot the mocked classes
class MockHttpContextWrapper : HttpContextWrapper
{
public MockHttpContextWrapper(HttpContext httpContext, string method)
: base(httpContext)
{
this.request = new MockHttpRequestWrapper(httpContext.Request, method);
}
private readonly HttpRequestBase request;
public override HttpRequestBase Request
{
get { return request; }
}
class MockHttpRequestWrapper : HttpRequestWrapper
{
public MockHttpRequestWrapper(HttpRequest httpRequest, string httpMethod)
: base(httpRequest)
{
this.httpMethod = httpMethod;
}
private readonly string httpMethod;
public override string HttpMethod
{
get { return httpMethod; }
}
}
}
Hope all of this helps someone...
Happy coding for everybody!
You can achieve this functionality by using the AuthorizeAttribute. You can get the Controller and Action name in OnAuthorization method. PLease find sample code below.
public sealed class AuthorizationFilterAttribute : AuthorizeAttribute
{
/// <summary>
/// Use for validate user permission and when it also validate user session is active.
/// </summary>
/// <param name="filterContext">Filter Context.</param>
public override void OnAuthorization(AuthorizationContext filterContext)
{
string actionName = filterContext.ActionDescriptor.ActionName;
string controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
if (!IsUserHasPermission(controller, actionName))
{
// Do your required opeation
}
}
}
if you have a default route configured like
routes.MapRoute(
"Area",
"",
new { area = "MyArea", controller = "Home", action = "MyAction" }
);
you can get the route information inside the controller action like
ht tp://localhost/Admin
will give you
public ActionResult MyAction(string area, string controller, string action)
{
//area=Admin
//controller=Home
//action=MyAction
//also you can use RouteValues to get the route information
}
here is a great blog post and a utility by Phil Haack RouteDebugger 2.0
This is a short notice! Be sure to use filterContext.RouteData.DataTokens["area"]; instead of filterContext.RouteData.Values["area"];
Good Luck.
Is it possible for a Controller method to handle all Posted items which derive from a particular base class? The idea is to be able to dispatch Commands by posting them to an endpoint. When I try the following, the "cmd" parameter in the Post method is always null.
Example
//the model:
public abstract class Command{
public int CommandId{get; set;}
}
public class CommandA:Command{
public string StringParam{get; set;}
}
public class CommandB:Command{
public DateTime DateParam{get; set;}
}
//and in the controller:
public HttpResponseMessage Post([FromBody]Command cmd)
{
//cmd parameter is always null when I Post a CommandA or CommandB
//it works if I have separate Post methods for each Command type
if (ModelState.IsValid)
{
if (cmd is CommandA)
{
var cmdA = (CommandA)cmd;
// do whatever
}
if (cmd is CommandB)
{
var cmdB = (CommandB)cmd;
//do whatever
}
//placeholder return stuff
var response = Request.CreateResponse(HttpStatusCode.Created);
var relativePath = "/api/ToDo/" + cmd.TestId.ToString();
response.Headers.Location = new Uri(Request.RequestUri, relativePath);
return response;
}
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
Again, when I try this approach the Post method gets called, but the parameter is always null from the framework. However if I replace it with a Post method with a specific CommandA parameter type, it works.
Is what I'm attempting possible? Or does every message type need a separate handler method in the controller?
If you are sending data in Json format, then following blog gives more details about how hierarchies deserialization can be achieved in json.net:
http://dotnetbyexample.blogspot.com/2012/02/json-deserialization-with-jsonnet-class.html
I am using sping3 mvc in my application.
I have some entities which need the CRUD operation.
When query the record,the pagination may be needed,so I create the class Pagination:
class Pagination{
int page,
int pageSize,
String sort;
}
This is my Dao:
public List<Post> list(Pagination pagination) {
/////
return xxx;
}
In controller:
#RequestMapping(value = "/", method = RequestMethod.GET)
public String list(#RequestParam int page,#RequestParam int pageSize,#RequestParam String sort) {
List<Post> posts=postDao.list(new Pagination(page,pageSize,sort));
return "";
}
Now,if there no page or pageSize parameter in the httprequest,it will throw the typeconvert exception,in fact,I know I can redirect to the exception page but I do not want this. I want to use some default configuation in this case,for example:
http://xx/post/list ==>return all records without pagination
http://xx/post/list?page=1&pageSize=10&sort=id ==>pagination
http://xx/post/list?page=1 ==>pagination with the default pageSize(maybe 10) and sort(id)
http://xx/post/list?page=notnumber&pageSize=10 ==>pagination with page=1
Then I want to know where and how to validate and reset the Pagination parameter?
You can change your parameter datatype of int page and pageSize to String page and String pageSize, and after doing this you can just check whether the passed parameter values is not null. If parameter value is not null use passed value otherwise use default value.
like this
#RequestMapping(value = "/", method = RequestMethod.GET)
public String list(#RequestParam String page,#RequestParam String pageSize,#RequestParam String sort) {
if(page ==null)
page=1; // assign page default value 1 because user didn't pass page value.
if(pageSize==null)
pageSize=10; // assign pageSize default value 10 because user didn't pass pageSize value.
List posts=postDao.list(new Pagination(page,pageSize,sort));
return "";
}
You must understand that the parameters you send for request would be casted from String to int, for it better if you directly receive in String and call your constructor of Pagination.
You can use conditional instruction inline or other logic, up to u.
In this method u would call the constructor Pagination passing the values of request only when will be informed, another way will send default values that u must put in constants.
#RequestMapping(value = "/", method = RequestMethod.GET)
public String list(#RequestParam String page, #RequestParam String pageSize, #RequestParam String sort) {
List posts = postDao.list(new Pagination(
StringUtils.isEmpty(page) ? page : "1",
StringUtils.isEmpty(pageSize) ? pageSize : "15",
StringUtils.isEmpty(sort) ? sort : "asc"));
return "";
}
I have a problem with the my Controller code. GET works fine (both empty form + form populated from db), POST works fine only for creating new object, but doesn't work for editing. Part of my #Controller class:
#RequestMapping(value = "/vehicle_save.html", method = RequestMethod.GET)
public String setUpForm(#RequestParam(value="id", required = false) Long id, ModelMap model) {
Vehicle v;
if (id == null) {
v = new Vehicle();
} else {
v = vehicleManager.findVehicle(id);
}
model.addAttribute("vehicle", v);
return "vehicle_save";
}
#RequestMapping(value = "/vehicle_save.html", method = RequestMethod.POST)
public String save(#ModelAttribute("vehicle") Vehicle vehicle, BindingResult result, SessionStatus status) {
vehicleValidator.validate(vehicle, result);
if (result.hasErrors()) {
return "vehicle_save";
}
if(vehicle.getId() == null) {
vehicleManager.createVehicle(vehicle);
} else {
vehicleManager.updateVehicle(vehicle);
}
status.setComplete();
return "redirect:vehicle_list.html";
}
The first method creates a vehicle object (including its ID). But the second method gets the same object without the ID field (set to null).
What could I do: manually set vehicle.setID(id from parameters) and then save it to database. This causes JPAOptimisticLockException + I don't like that solution.
Is there a way to pass my Vehicle object with ID to the second method? BTW, I would like to avoid adding hidden ID field to the JSP.
the example you suggested is using session to store the value. the #SessionAttribute is to bind an existing model object to the session. Look at the source code the class is annotated with #SessionAttributes("pet").Which means your model attribute named "pet" is getting stored in session.Also look at the code in processSubmit method of EditPetForm class
#RequestMapping(method = { RequestMethod.PUT, RequestMethod.POST })
public String processSubmit(#ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "pets/form";
}
else {
this.clinic.storePet(pet);
status.setComplete(); //look at its documentation
return "redirect:/owners/" + pet.getOwner().getId();
}
}
I havnt used something like this before.But i guess putting ur id in session is the way
BTW, I would like to avoid adding hidden ID field to the JSP.
This is common solution. What's wrong with it ? You should create hidden input with id.
May be you can try using session, cause you cant store info between two request. But that will be uglier i guess.
Btw, Can you please explain a little why you want to avoid adding hidden fields? I'm little curious