This question already has answers here:
asp.net mvc complex routing for tree path
(4 answers)
Closed 8 years ago.
I'd like to put together a forum/message board with ASP.NET MVC. Pretty common on these types of forums are hierarchical board categories, so for instance:
-General Discussion
-Technical Support
--Website Technical support
--Product Technical Support
---Product A Technical Support
---Product B Technical Support
Below each category are then topics and messages belong to those topics. What I'm primarily concerned with is 1.) getting to the correct place, given a URL, 2.) not including boatloads of unnecessary information in my URL, and 3.) being able to recreate a URL from code.
I'd like a URL to be something like this:
mysite.com/Forum/ - forum index
mysite.com/Forum/General-Discussion/ - board index of "general discussion"
mysite.com/Forum/Technical-Support/Product/Product-A/ - board index of "Product A Tech Support"
mysite.com/Forum/Technical-Support/Website/Topic1004/ - Topic index of topic with ID 1004 in the "Website Technical Support" board
mysite.com/Forum/Technical-Support/Website/Topic1004/3 - Page 3 of Topic with ID 1004
Now, I've excluded Action names from this because they can be inferred based on where I am. Each Board entity in my database has a "UrlPart" column, which is indexed, so I expect to be able to do relatively fast queries against that table to figure out where I am.
The question is: in order to figure out the correct place, should I use a custom route handler, a custom route binder, or should I just create obscure routing rules?
This suggestion looks pretty good but it also looks like a lot of work for little benefit:
ASP.NET MVC custom routing for search
This seems to indicate that creating a model binding would be easier:
MVC Dynamic Routes
To fulfill #3 I'm going to have to create my own custom URL generation logic, right?
If you need deep and/or non-conforming URLs, I would suggest that you employ attribute based routing, such as the solution discussed here.
I prefer an attribute based approach over putting every route in Application_Start, because you have better locality of reference, meaning the route specification and the controller which handles it is close together.
Here is how your controller actions would look for your example, using the UrlRoute framework I implemented (available on codeplex):
[UrlRoute(Path = "Forum")]
public ActionResult Index()
{
...
}
[UrlRoute(Path = "Forum/General-Discussion")]
public ActionResult GeneralDiscussion()
{
...
}
[UrlRoute(Path = "Forum/Technical-Support/Product/{productId}")]
public ActionResult ProductDetails(string productId)
{
...
}
[UrlRoute(Path = "Forum/Technical-Support/Website/{topicId}/{pageNum}")]
[UrlRouteParameterDefault(Name = "pageNum", Value = "1")]
public ActionResult SupportTopic(string topicId, int pageNum)
{
...
}
With this approach you can generate outbound URLs using the same helpers (Url.Route*, Url.Action*) that you would use if you manually added the routes using the default route handler, no extra work needed there.
You could have them all go to one controller action which handles the route handling for you by manually splitting the rest of the url out and calls what a method on your BLL which then delegates tasks to other methods finally returning a View() depending on your needs.
Related
I am developing one application in the ASP.NET MVC C# on the .NET 4 framework.
I confused in routing and I do the research and developed the one demo version It works as I want but I want to know is which method is best practice for developing application.
First I register the route like this:
routes.MapRoute(
name: "RoutesTesting",
url: "{controller}/{action}/{a}/{b}/{c}/{d}/{e}",
defaults: new { controller = "Home", action = "Test", e = UrlParameter.Optional }
);
I have one class that have the properties and it's name same as the route's parameter.
class MyClass{
public string a{get;set;}
public string b{get;set;}
public string c{get;set;}
public string d{get;set;}
public string e{get;set;}
}
Now I created the tow methods that works find and get the data from the URL successfully.
Method 1:
public ActionResult Test(MyClass objMyClass){
}
Method 2:
public ActionResult Test(string a,string b,string c,string d,string e=String.Empty){
}
My question is:
Is routing doing that conversation in my action method? Like it convert the parameter values in the `MyClass' object's properties?
Which method is best practice to use?
Is method 1 will throw any error or exception when the conversation is not possible ?
Thanks in advance...
The behavior you are seeing is a part of ASP.NET's Model Binding. It's the magic that lets you send across a JSON object of {"firstName":"Jonathon","lastName":"Chase"} and have to automagically be mapped to a model Person that looks like so:
public class Person {
public string FirstName {get;set;}
public string LastName {get;set;}
}
The fact that you can create a route like that is merely a consequence of this. Model Binding is a complex subject, but I can touch on some aspects of how you're forming your route, especially if the action you're creating is going to have a side-effect, such as writing to a database.
Typically if you're going to have a method that will effect state, you should use an Http verb other than Get, and send the model across in the body of the request, rather than in the query/url string. The Model Binding will take care of the mapping for you either way.
You should prefer to use a strong model rather than multiple primitives as parameters, especially in cases where the information will be sent in the body of a request over the query string.
These points are debatable, however, and shouldn't be considered hard or fast rules for the most part.
As to your last point, if the parameters are incorrect enough that the Route can't identifier the action or controller, you should get a 404. However, if you have a valuetype that isn't nullable as an expected routed property that isn't properly sent across, you should expect a 500 with an InvalidOperationException.
Take a look at How model binding works
Is routing doing that conversation in my action method? Like it
convert the parameter values in the `MyClass' object's properties?
The framework model binder is doing the conversion based on the actions parameter.
Which method is best practice to use?
That is an opinionated question. Depends on which one suits your needs. The framework handles both.
Is method 1 will throw any error or exception when the conversation is
not possible ?
Model binder will pass null to the action parameter for the properties that don't match.
I've hit an issue with webapi attribute routing. I am calling the following route: assessment/assessments/assessmenttypes as an HttpPost. I'm getting the error that multiple controller types match the url. The problem is that they definitely don't match. Initially when I first got this issue I was using different route prefixes, however I read that webapi can ignore route prefixes, so I changed all the routes to make them unique, so the only HttpPosts which are in the "matching controllers" are defined as follows:
[Route("assessments"), HttpPost]
public async Task<System.Int32> PostCreate([FromBody]Assessment assessment)
and
[Route("assessments/assessmenttypes"), HttpPost]
public async Task<System.Int32> PostCreate([FromBody]AssessmentType assessmentType)
Both controllers have the same RoutePrefix of: [RoutePrefix("assessment")]
Can anyone help please, this is very frustrating.
Thanks in advance!
What you need to understand first at all is how routing works, that's the key to solve your problem. I can update my answer later if you give me more information (at this moment it looks like you didn't post all the required information)
As you know, routing is the process of matching requests to routes.
The handler that does the magic is the HttpRoutingDispatcher with some help of extra libraries, but basically the dispatcher resolves the request. The final goal of processing the route is to identify two elements:
Route
Values
You have two different ways of resolving routes, convention-based (templates) or by attributes. It's a good idea to be consistent to avoid confusions specially when the application grows.
The routing has two different parts, fixed segments (they must match exactly) and variables (denoted as you know by {})
The request verbs are matched in two different ways, with the attribute
[HttpGet] [HttpPost] [HttpPut]
Or confusingly in my opinion using the first part of the method, if you have 3 different methods not marked with attributes the WebApi assumes the first letters try to indicate the verb:
GetList --> HttpGet of List
GetDetail --> HttpGet of Detail
PostDetail --> HttpPost of Detail
From the official documentation if you try the following template:
routeTemplate: "api/{controller}/{id}"
And you make a request from Ajax like the following:
$.ajax("/api/today/dayofweek", {
The following controller will fail (nearly always)
public class TodayController : ApiController {
[HttpGet]
public string DayOfWeek() {
return DateTime.Now.ToString("dddd");
}
[HttpGet]
public int DayNumber() {
return DateTime.Now.Day;
}
Because it's nearly impossible to establish which one is the correct one (a human being can of course, but unfortunately machines need more programming to work with fuzzy logic) :)
The problem in your case it's not both start with the same fixed route, the problem is that it's not possible to identify whether you're saying the second part belongs to a variable or it's a fixed part (it's ambiguous)
Giving you an example; how could we establish the meaning of this?
/segmenta/segmentb
/segmenta/segmentb/segmentc (is segment c a fixed route or a parameter?)
From an architectural point of view the reality is (based on Api design) that I don't think /assestmenttypes is a subordinated resource of /assestments. If you want to design a RESTful ROA Api assestmenttypes are correctly indicated as a resource if the type depends on the assestment (as a child, not as an attribute).
I would suggest you to review your paths too.
I want to create a REStful web service.
This service returns "Meetings" for a certain
day, week or months.
I would create 3 Get methods which takes date values determined by the API user.
GetMeetingsByDay(currentDate)
GetMeetingsByWeek(startDate,endDate)
GetMeetingsByMonth(startDate,endDate)
Having 3 Get methods I could not access the API anymore just via http-method type 'GET' like:
// GET: api/meetings/date
because there will be multiple GET...
I could merge the ByWeek and ByMonth methods and do this instead:
GetMeetings(startDate,endDate);
How would you make this service RESTful?
I wouldn't think of it in terms of the method names - I'd think of it in terms of the URLs to start with:
/api/meetings?date=...
/api/meetings?startDate=...&endDate=...
Think of it as a collection of meetings, and the query parameters are just that - query parameters. I would expect any value after the meetings element in the URL to be a meeting ID - for example, your GetMeetings method might return a meeting with an ID of foobar which I'd then expect to be able to fetch later with
/api/meetings/foobar
That suggests to me that you shouldn't have date as part of the URL path at all.
In terms of implementation, I don't know enough about WebAPI routing to know whether you could implement that with two methods of:
[HttpGet]
[Route("api/meetings")]
public ... GetMeetings([FromUri] DateTime startDate, [FromUri] DateTime endDate)
[HttpGet]
[Route("api/meetings")]
public ... GetMeetings([FromUri] DateTime date)
... or whether you need to a single method with optional parameters:
[HttpGet]
[Route("api/meetings")]
public ... GetMeetings(
[FromUri] DateTime? date = null,
[FromUri] DateTime? startDate = null,
[FromUri] DateTime? endDate = null)
In the latter case you'd need to then validate that the set of arguments provided was valid.
(As noted in comments, you may not need the Route here at all.)
Web API 2 supports a new type of routing, called attribute routing.
This is the scenario where you need to use attribute routing.
eg:
[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
One advantage of convention-based routing is that templates are
defined in a single place, and the routing rules are applied
consistently across all controllers. Unfortunately, convention-based
routing makes it hard to support certain URI patterns that are common
in RESTful APIs.
Read more here :
Attribute Routing in ASP.NET Web API 2
Create a REST API with Attribute Routing in ASP.NET Web API 2
From a previous good answer (source):
If you have a multiple Get actions that have a single primitive argument of the same type, ASP.NET Web API will look at the name of the argument to resolve which overloaded action to call.
For example, if you have two actions:
GetProductByName(string name)
GetProductByCategory(string category)
your http client can call as :
api/products?name=hammer
api/products?category=toys
and the routing engine will call the correct action.
The SO question, How does ASP.NET Web.api handle two methods with names starting with GET? can give you some more knowledge as it had been well answered.
EDIT :
I could merge the ByWeek and ByMonth methods and do this instead:
GetMeetings(startDate,endDate);
Definitely I would encourage this if you don't have any specific logic going into the GetMeetingsbyWeek and GetMeetingsbyMonth, like it merely fetches a list from database between the specified date range.
Look at Web API routing - http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Otherwise, you could use just a querystring - like api/meetings/date?currentDate=x&startDate=x&endDate=x
For creating a WebApi Controller with multiple GET requests, You need to specify the route as follows:
routeTemplate:api/{Controller}/{action}/{param}
This should solve your problem.
I am building a cms, on the edit screen for a section you can edit multiple types of pages, the urls need to remain nutral, like this:
foobar.com/edit/section/my-content-page-name
foobar.com/edit/section/my-gallery-page-name
foobar.com/edit/section/my-blog-page-name
In this scenario the Index action is used for both gets and posts.
At the moment I have one massive ViewModel, that encompasses all the data required across all page types.
I feel this is quite wrong and, makes an ugly solution for deciding what type of page update on the post.
How can I keep the Action the same but use it with different strongly type ViewModels?
Is this even possible?
public ActionResult Index(string page)
{
var model = _pageManager.GetSection(page, SelectedSite);
return View(model.PageType, model);
// renders appropriate View based on page type.
}
[Transaction]
[HttpPost]
[ValidateInput(false)]
public ActionResult Index(SectionIndexViewModel model)
{
// all page types post back to same action to update content etc.
// at this point SectionIndexViewModel is getting bloated with properties because it must cater for ALL page types data.
var action = Request["action"] ?? "";
// currently use this to determine what event has been triggered
switch (action.ToLower())
{
// then goes to update the appropriate page, blog or gallery
// etc.
all page types post back to same action to update content etc.
There is your problem. The same action should not handle all post backs. Create one controller per feature (content, gallery, blog). It's how MVC is intended to be used.
Single Responsibility Principle do also apply to controllers.
You can even move the controllers to class libraries to get plugin like architecture for your CMS. I've described how here: http://blog.gauffin.org/2012/05/griffin-mvccontrib-the-plugin-system/
I managed to achieve this with some MVC basics that I had forgotten about.
The routing remains as per the defaults.
For each ViewModel type I delivered an extra hidden field in the form, with the type of the page/content/ViewModel eg: Content Page, or Blog Page etc.
In the Post action, I check the type of the page from this hidden field.
Then use TryUpdateModel using the expected ViewModel type for that page type.
And the rest is straight forward.
Pretty basic stuff really.
I am looking at the following tutorial from Microsoft. As per this tutorial,
In the first example, "products" matches the controller named
ProductsController. The request is a GET request, so the framework
looks for a method on ProductsController whose name starts with
"Get...". Furthermore, the URI does not contain the optional {id}
segment, so the framework looks for a method with no parameters. The
ProductsController::GetAllProducts method meets all of these
requirements.
What happens if there are two methods like GetAllProducts() and GetSoldProducts()? Both have no parameters.
Your First Web API Tutorial
There are two possible solutions to this specific problem:
Alter MapHttpRoute calls to require specifying the name of the action. (I'm using Self-hosting syntax):
config.Routes.MapHttpRoute(
"API Route 1",
"api/{controller}/{action}");
config.Routes.MapHttpRoute(
"API Route 2",
"api/{action}",
new { controller = "products" });
So your http client would call:
api/products/GetAllProducts OR api/GetAllProducts
api/products/GetSoldProducts OR api/GetSoldProducts
See:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Place each method in a separate controller (ProductsController, SoldProductsController). So you would call api/products and api/soldproducts to get your results.
Related topic... in the situation where you have a multiple Get actions that have a single primitive argument of the same type, ASP.NET Web API will look at the name of the argument to resolve which overloaded action to call.
For example, if you have two actions:
GetProductByName(string name)
GetProductByCategory(string category)
your http client can call
api/products?name=hammer
api/products?category=toys
and the routing engine will call the correct action.
Assuming you're using the default routes the short answer is : the method defined first (at the top) of your class will be called. the other method is inaccessible.
NOTE : the beta behaved as above for 'matching multiple methods' - the RC & Release version is a bit more OCD. It throws an error if there are multiple potential matches. This change removes the confusion of multiple ambiguous matches. At the same time, it reduces our ability to mix REST and RPC style interfaces in the same controller, relying on the order & overlapping routes.
Stealing liberally from another post I wrote on the topic:
WebAPI Matching Semantic
The matching semantic used by WebAPI is fairly simple.
It matches the name of the action with the verb (verb = get? look for method starting with "get")
if a parameter is passed, the api seeks an action with a parameter
So in your code sample a GET request without a parameter matches the Get*( ) function without an parameters. A Get containing and ID looks for a Get***(int id).
Examples
While the matching semantic is simple, it creates some confusion for MVC developers (well at least this developer). Lets look at some examples :
Odd Names - Your get method can be named anything, so long as it starts with "get". So in the case of a widget controller you can name your functions GetStrawberry() and it will still be matched. Think of the matching as something like : methodname.StartsWith("Get")
Multiple Matching Methods - What happens if you have two Get methods with no parameters? GetStrawberry() and GetOrange(). As best I can tell, the function defined first (top of the file) in your code wins ...strange. This has the side effect of making some methods in your controller unreachable (at least with the default routes)....stranger.
UPDATE
#WinFXGuy - This was a bit long to put in a comment, but ...
Don't jump to conclusions! I tried to answer the question you posed, but that's only half the story. There is plenty you can do to change the default behavior.
First, WebAPI supports much of the oData spec. If you bubble an IQueryable up to your controller, oData paramaters are automatically integrated with the query object. It takes parameters like $filter, $top, and $skip. So you in your case you can write one method and pass something like $filter=sale_date neq null.
Additionally, you can apply the [ResultLimit] attribute to prevent people asking for 15 billion records.
Second you can modify the routes. The default routes aim towards a RESTful api, where you generally have 1 controller per entity. You can change the routes and make it RPC style.
If you look at my linked post I explain how I kept the default route binding, added 'sub folders' and also allowed additional method calls for scenarios where i needed GetAllProducts() and GetSoldProducts().
Adding an answer to reflect that the latest version of Web API supports [Route] attribute natively
[Route("api/products")]
public IEnumerable<Product> GetAllProducts(){}
[Route("api/products/sold")]
public IEnumerable<Product> GetSoldProducts(){}