Complex routing in ASP.NET MVC5 - asp.net

I want to know the best approach to create my controllers structure.
Let's say I have several events and for each event I can have several devices.
My idea would be to have something like:
http://mydomain/event/1/device/4
So I can access the deviceId 4 (belonging to eventId 1).
Should I have two different controllers? One for Event and for Device or device info has to be in EventController?
How can I have this routing in my RouteConfig?

It's entirely up to you how you want to set this up. You can use separate controllers or the same controller. It doesn't matter.
As far as routing goes, if you're using standard MVC routing, you'll need to create a custom route for this:
routes.MapRoute(
"EventDevice",
"event/{eventId}/device/{deviceId}",
new { controller = "Event", action = "Device" }
);
Which would correspond with something like this:
public class EventController : Controller
{
public ActionResult Device(int eventId, int deviceId)
{
...
}
}
Just make sure you place that before the default route, so it will catch first. For more about custom routes see: http://www.asp.net/mvc/overview/older-versions-1/controllers-and-routing/creating-custom-routes-cs
Alternatively, in MVC5+ you can use attribute routing, which makes defining custom routes much easier if you're doing a lot of stuff like this. In RouteConfig.cs, uncomment the line:
// routes.MapMvcAttributeRoutes();
Then, on your action define the route like:
[Route("event/{eventId}/device/{deviceId}")]
public ActionResult Device(int eventId, int deviceId)
{
...
You can also use [RoutePrefix] on your controller class to move part of the route to apply to the whole controller. For example:
[RoutePrefix("event")]
public class EventController : Controller
{
[Route("{eventId}/device/{deviceId}")]
public ActionResult Device(int eventId, int deviceId)
{
...
}
}
For more about attribute routing see: http://blogs.msdn.com/b/webdev/archive/2013/10/17/attribute-routing-in-asp-net-mvc-5.aspx

Related

Url.Action returning incorrect URL for webapi action with Route attrubute

I have a problem with the behaviour of Url.Action();
I have a webapi where all controllers require explicit route prefix attribute and all actions require a route attribute.
I register my routes in the WebApiConfig.cs
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof( ApiVersionRouteConstraint )
}
};
config.MapHttpAttributeRoutes(constraintResolver);
I have currently commented out the line below, but (because) it did not change the incorrect behaviour:
//config.Routes.MapHttpRoute(name: "DefaultApi",
//routeTemplate: "api/v{version:apiVersion}/{controller}/{action}/{id}", defaults: new {id = RouteParameter.Optional});
My controllers look as follows:
[RoutePrefix("api/v{version:apiVersion}/programs")]
public class ProgramsController : ApiController
{
[HttpGet, Route("{telemetryKey}/versions/latest")]
public async Task<LatestVersionResponse> GetLatestVersionInfo(Guid telemetryKey)
{
// serious business logic
}
}
I expect that '#Url.Action("GetLatestVersionInfo", "Programs", new { telemetryKey = Guid.Parse("43808405-afca-4abb-a92a-519489d62290") })'
should return /api/v1/programs/43808405-afca-4abb-a92a-519489d62290/versions/latest
however, I get /Programs/GetLatestVersionInfo?telemetryKey=43808405-afca-4abb-a92a-519489d62290 instead. So, my routeprefix and route attributes are ignored.
Swagger correctly discovers my routes and I can validate that requests to the expected routes work OK - it's only the Url.Action() that is confused.
What can be wrong...?
Well, it seems there were a few things wrong.
Wrong helper:
I should be using the Url.HttpRouteUrl for generating API links from a razor view (Url.Link is for generating link from within API controllers)
Conflict with aspnet-api-versioning library
For some reason (perhaps a bug?) the prefix that I have on the controller (apiVersion variable) breaks the URL helper mechanism.
For now, I have ditched the aspnet-api-versioning library, but created an issue on their github repo, in case its a bug.
Since I really hate the idea of creating and maintaing magic strings, so I took the following approach - each controller has a public static class which contains const values for the route names:
[RoutePrefix("api/v1/developers")]
public class DevelopersController : ApiController
{
[HttpGet, Route("{developerId}/programs", Name = Routes.GetPrograms)]
public async Task<IEnumerable<Program>> GetPrograms(Guid developerId){}
public static class Routes
{
public const string GetPrograms = nameof(DevelopersController) +"."+ nameof(DevelopersController.GetPrograms);
}
}
Now that can be used from a razor controller in a simple and relatively safe manner:
#Url.HttpRouteUrl(DevelopersController.Routes.GetPrograms, new { developerId = /* uniquest of guids */})
A bit better than magic strings. I've also added a bunch of unit tests for controllers where I validate that each route is unique and proper and that the routes class only contains routes for the action it contains.
Try the following:
Name your route:
[HttpGet, Route("{telemetryKey}/versions/latest", Name="LatestVersionInfoRoute")]
public async Task<LatestVersionResponse> GetLatestVersionInfo(Guid telemetryKey)
{
// serious business logic
}
Use Url.Link method:
#Url.Link("LatestVersionInfoRoute", new { telemetryKey = Guid.Parse("43808405-afca-4abb-a92a-519489d62290") })

asp.net mvc 4 custom urls for action method

I am creating a asp.net mvc 4 application
public class AspNetController : Controller
{
//
// GET: /AspNet/
public ActionResult Index()
{
return View();
}
public ActionResult Introduction()
{
return View();
}
}
as Shown Above There is AspNet Controller and Introduction Action Method
Default Url for Introduction Action Method is
localhost:55134/aspnet/introduction
But I Want Url Like
localhost:55134/aspnet/introduction-To-AspNet
Same for
/localhost:55134/aspnet/NetFrameWork To
/localhost:55134/aspnet/What-is-.Net-Framework
How to do that
You should be able to use the ActionName attribute to decorate your routes.
[ActionName("Introduction-To-AspNet")]
public ActionResult Introduction()
{
return View();
}
You really want to use AttributeRouting, either via a 3rd party package or natively if you can.
Technically this concept comes under Routing in ASP.NET MVC.
For this you need to do an entry for route in App_Start->RouteConfig.cs file under RegisterRoutes(RouteCollection routes)
For Example:
routes.MapRoute(
"customRouteName",
"aspnet/introduction-To-AspNet",
new { controller = "AspNet", action = "Introduction" });
here aspnet/introduction-To-AspNet will append after your base url i.e. localhost:55134/
The quick and dirty answer is to add a route to your ~/AppStart/RouteConfig.cs file and it will be taken care of:
routes.MapRoute(
name: "CustomRoute",
url: "Aspnet/Introduction-To-AspNet",
defaults: new { controller = "Home", action = "AspNet", id = UrlParameter.Optional }
);
However, I'm assuming this is for some type of blog? I would reccomend that you have an action method called view, and then use your name as a parameter for the article. That way, you don't have to go in and edit the code every time you add a new article or other content:
public class ArticlesController : Controller
{
public ActionResult ViewArticle(string? title)
{
ViewBag.Article = title;
return View();
}
}
that way, your URL would be www.yoursite.com/Articles/ViewArticle/Introduction-To-AspNet. In general, you don't want to add tons of specific routes to your route config if you can avoid it. That being said, if this is a legacy system, the route table may be the only way.
EDIT
Ok, so what you can do is pass the string into the ViewBag and use a case statement to determine which partial view to show (I think this just might be your ideal solution):
<!--cshtml view-->
#switch(ViewBag.Article)
{
case 'Introduction-To-AspNet':
#Html.Partial('pathToPartialView.cshtml')
break;
case 'Some-Other-Article'
#Html.Partial('pathToAnotherPartialView.cshtml')
break;
...
...
default:
#Html.Partial('invalidArticleName.cshtml')
break;
}
The controller will pass the article name through the ViewBagand then you can use the case statement to figure out which article to render... and of course, the real secret sauce you've been looking for: #Html.Partial('URL') - this will take your partial and render it right were you put that in the page. You can also pass objects to that just as an FYI.
In addition, make sure that you have a default action on the switch statement that will show some sort of 404 page that indicates that the name in the URL was invalid. You ALWAYS want to have this anytime you're taking user input from the URL because people monkey with URLs all the time (and more innocently, copy+paste them wrong/incompletely all the time)

How to share a common object in each page request of a ASP.net MVC 4 webapp?

I come from "regular" asp.net so i'm a bit (totally) lost with MVC.
What I was doing with my own asp.net programmation pattern :
I have one custom class objet which represent the "page" and its properties (like mypage.loadJquery, mypage.isLogged, mypage.Title, custom cache logic, etc.)
This class is instanciate once on top of each ASHX page, I then manipulate a stringbuilder to produce HTML and spit it right at the browser at the end.
Having only one request on the ASHX page, i can use my page object instanciated at the top till the end when calling final response.write
Now i'm trying to go for MVC. I "kind of" understood the M/V/C model and the routing concept. I would like to keep my custom "page" object but I lost my page life cycle and I definitely don't know how to instanciate my page object ONCE in at the top of every call.
I need this instanciated ONCE shared object across every models, controllers, views, partial views, htmlhelper...
I realize MVC pattern might be confusing for me at this moment, bu how could I try to reproduce my need ?
(Very concrete exemple : On every request i need to check if the user is logged via his cookies. If it is I round trip the database to get user infos. Then I DO NEED THESE INFOS ON PRATICALLY EVERY model / controller / view of the app, but of course don't want to round back each time to security check and database querying, how can i have these info on the whole mvc cyle ?)
In my project I create interface IViewModel that contains all fields that I need in my layout/masterpage and set is as model of it so I can easily use them:
IViewModel.cs
public interface IViewModel
{
string Title { get; set; }
User User { get; set; }
}
Layout.cshtml
#model IViewModel
<html>
<head>
<title>#Model.Title</title>
</head>
<body>
#if (Model.User.IsAuthenticated) {
You are logged as #Model.User.Name
}
</body>
</html>
All my models implement that interface (or inherit from ViewModelBase that is default implementation of that class). Additionally I have custom action filter that check if returned ActionResult is (Partial)ViewResult and if Model of it implement my IViewModel interface and fill data in that interface.
public FillViewModelAttribute : ActionFilterAttribute
{
public override OnActionExecuted(ActionExecutedContext context)
{
var viewResult = context.Result as ViewResult;
if (viewResult != null && viewResult.Model is IViewModel)
{
var viewModel = (IViewModel)viewResult.Model;
// fill data
}
}
}
I created many projects like this. Basically, you can create a base controller class where all the other controllers inherit from it.
[Authorize]
public class BaseController : Controller
{
private Instructor _G_USER = null;
protected Instructor G_USER
{
get
{
if (_G_USER == null)
{
_G_USER = Your DB query here
ViewData["G_USER"] = _G_USER;
}
return _G_USER;
}
}
}
Then in your every child class, you can do
[Authorize]
public class YourController : BaseController
{
public ActionResult Index()
{
if(!G_USER.CAN_DO_THIS) throw new NoPermissionException();
return View();
}
}
To use the User in the view, create an extension method.
public static class ExtentionMethods
{
public static USER G_USER(this ViewPage page)
{
return (USER)page.ViewData["G_USER"];
}
}
Then use in the page like this
<%=this.G_USER().....%>

MVC 3 wildcard route ie www.mydomain.com/username

I would like to create a simple route which will allow me to have ONLY one item listed after the base URL (other than when it's a controller), and for that item to be passed into a controller as a parameter. for example:
www.mydomain.com/user1
www.mydomain.com/user2
www.mydomain.com/user3
www.mydomain.com/user3
where user1, user2 etc are usernames and are being passed dynamically, ie i don't want to have to create a controller for each username.
naturally i would want to make sure if something like this is possible that it wont cause conflicts with other genuine controller names, thus i guess the other controllers would have to have rules created specifically for them and listed above the wildcard route
i'm not sure how to do this as i guess the first item after the slash is usually a controller.
any ideas how to tackle this?
the examples i provided may seem ambiguous, when i put www.mydomain.com/user1 etc it represents that it could be anything (ie a username),for example, www.mydomain.com/jsmith, www.mydomain.com/djohnson, www.mydomain.com/sblake, www.mydomain.com/fheeley
the idea is that a users profile can be looked up simply by entering the domain name then a fwd slash and the username.
ASP.Net MVC routes are process from the top down, and as soon as a match is found it won't look any further for a match. So put your most specific routes first, and your wildcard route last. If none of the specific routes match, control will be passed to the wildcard route.
Use a route definition such as
routes.MapRoute("{username}", new { controller = "User", action = "View"});
in your global.asax.cs file but put it last in your list of route definitions.
MVC processes your route mappings from top to bottom, so put your most general route mappings at the top, and your more specific ones at the bottom.
After you do this you will need the corresponding controller/action so create a new Controller named "UsersController" with an action named "View", like as follows:
using System.Web.Mvc;
namespace Routes.Controllers
{
public class UsersController : Controller
{
//
// GET: /{username}
public ActionResult List(string username)
{
// Get User from database/repository and pass to view
var user = ...;
return View(user);
}
}
}
You have to do following thing.
Create one controller or Action for Handle above scenario. For example controller="Home" and Action="GetUser"
public ActionResult GetUser(string username){
// do your work
}
In Global.asax ( Top route)
// Look at point 3 for UserNameConstraint
route.MapRoute("UserRoute" , "{username}" , new { controller="Home" , action="GetUser" } , new { username = new UserNameConstraint() });
// Put your default route after above here
Now for Create Route constraint.
Hope this help you.
public class UserNameConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
List<string> userName = GetUserName from DB
userName.Contain(values[parameterName].ToString());
}
}
Create a route like as follows,
routes.MapRoute(
"DefaultWithUser", // Route name
"{user}/{controller}/{action}/{id}", // URL with parameters
new { user=UrlParameter.Optional, controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

Multiple controllers, one view, and one model ASP.NET MVC 3

I want to have one model & view that is served by multiple controllers in my ASP.NET MVC 3 app.
I'm implementing a system that interacts with the users' online calendar and I support Exchange, Google, Hotmail, Yahoo, Apple, ect... Each of these has wildly different implementations of calendar APIs, but I can abstract that away with my own model. I'm thinking that by implementing the polymorphism at the controller level I will be able to deal cleanly with the different APIs and authentication issues.
I have a nice clean model and view and I've implemented two controllers so far that prove I can read/query/write/update to both Exchange and Google: ExchangeController.cs and GoogleController.cs.
I have /Views/Calendar which contains my view code. I also have /Models/CalendarModel.cs that includes my model.
I want the test for which calendar system the user is using to happen in my ControllerFactory. I've implemented it like this:
public class CustomControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == typeof(CalendarController))
{
if(MvcApplication.IsExchange) // hack for now
return new ExchangeController();
else
return new GoogleController();
}
return base.GetControllerInstance(requestContext, controllerType);
}
}
and in my Application_Start:
ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());
This works. If I got to http://.../Calendar this factory code works and the correct controller is created!
This worked beautifully and I did it without really understanding what I was doing. Now i think I got it but I want to make sure I'm not missing something. I really spent time searching for something like this and didn't find anything.
One thing that concerns me is that I figured I'd be able to have an inheritance relationship between CalendarController and ExchangeController/GoogleController like this:
public class ExchangeController : CalendarController
{
But if I do that I get:
The current request for action 'Index' on controller type 'GoogleController' is ambiguous between the following action methods:
System.Web.Mvc.ViewResult Index(System.DateTime, System.DateTime) on type Controllers.GoogleController
System.Web.Mvc.ActionResult Index() on type Controllers.CalendarController
Which bums me out because I wanted to put some common functionality on the base and now I guess I'll have to use another way.
Is this the right way to do have multiple controllers for one view/model? What else am I going to have to consider?
EDIT: More details on my impl
Based on the responses below (thanks!) I think I need to show some more code to make sure you guys see what I'm trying to do. My model is really just a data model. It starts with this:
/// <summary>
/// Represents a user's calendar across a date range.
/// </summary>
public class Calendar
{
private List<Appointment> appointments = null;
/// <summary>
/// Date of the start of the calendar.
/// </summary>
public DateTime StartDate { get; set; }
/// <summary>
/// Date of the end of the calendar
/// </summary>
public DateTime EndDate { get; set; }
/// <summary>
/// List of all appointments on the calendar
/// </summary>
public List<Appointment> Appointments
{
get
{
if (appointments == null)
appointments = new List<Appointment>();
return appointments;
}
set { }
}
}
Then my controller has the following methods:
public class ExchangeController : Controller
{
//
// GET: /Exchange/
public ViewResult Index(DateTime startDate, DateTime endDate)
{
// Exchange specific gunk. The MvcApplication._service thing is a temporary hack
CalendarFolder calendar = (CalendarFolder)Folder.Bind(MvcApplication._service, WellKnownFolderName.Calendar);
Models.Calendar cal = new Models.Calendar();
cal.StartDate = startDate;
cal.EndDate = endDate;
// Copy the data from the exchange object to the model
foreach (Microsoft.Exchange.WebServices.Data.Appointment exAppt in findResults.Items)
{
Microsoft.Exchange.WebServices.Data.Appointment a = Microsoft.Exchange.WebServices.Data.Appointment.Bind(MvcApplication._service, exAppt.Id);
Models.Appointment appt = new Models.Appointment();
appt.End = a.End;
appt.Id = a.Id.ToString();
...
}
return View(cal);
}
//
// GET: /Exchange/Details/5
public ViewResult Details(string id)
{
...
Models.Appointment appt = new Models.Appointment();
...
return View(appt);
}
//
// GET: /Exchange/Edit/5
public ActionResult Edit(string id)
{
return Details(id);
}
//
// POST: /Exchange/Edit/5
[HttpPost]
public ActionResult Edit(MileLogr.Models.Appointment appointment)
{
if (ModelState.IsValid)
{
Microsoft.Exchange.WebServices.Data.Appointment a = Microsoft.Exchange.WebServices.Data.Appointment.Bind(MvcApplication._service, new ItemId(appointment.Id));
// copy stuff from the model (appointment)
// to the service (a)
a.Subject = appointment.Subject
...
a.Update(ConflictResolutionMode.AlwaysOverwrite, SendInvitationsOrCancellationsMode.SendToNone);
return RedirectToAction("Index");
}
return View(appointment);
}
//
// GET: /Exchange/Delete/5
public ActionResult Delete(string id)
{
return Details(id);
}
//
// POST: /Exchange/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(string id)
{
Microsoft.Exchange.WebServices.Data.Appointment a = Microsoft.Exchange.WebServices.Data.Appointment.Bind(MvcApplication._service, new ItemId(id));
a.Delete(DeleteMode.MoveToDeletedItems);
return RedirectToAction("Index");
}
So it's basically the typical CRUD stuff. I've provided the sample from the ExchangeCalendar.cs version. The GoogleCalendar.cs is obviously similar in implementation.
My model (Calendar) and the related classes (e.g. Appointment) are what get passed from controller to view. I don't want my view to see details of what underlying online service is being used. I do not understand how implementing the Calendar class with an interface (or abstract base class) will give me the polymorphism I am looking for.
SOMEWHERE I have to pick which implementation to use based on the user.
I can either do this:
In my model. I don't want to do this because then my model gets all crufty with service specific code.
In the controller. E.g. start each controller method with something that redirects to the right implementation
Below the controller. E.g. as I'm suggesting above with a new controller factory.
The responses below mention "service layer". I think this is, perhaps, where I'm off the rails. If you look at the way MVC is done normally with a database, the dbContext represents the "service layer", right? So maybe what you guys are suggesting is a 4th place where I can do the indirection? For example Edit above would go something like this:
private CalendarService svc = new CalendarService( e.g. Exchange or Google );
//
// POST: /Calendar/Edit/5
[HttpPost]
public ActionResult Edit(MileLogr.Models.Appointment appointment)
{
if (ModelState.IsValid)
{
svc.Update(appointment);
return RedirectToAction("Index");
}
return View(appointment);
}
Is this the right way to do it?
Sorry this has become so long-winded, but it's the only way I know how to get enough context across...
END EDIT
I wouldn't do it this way. As Jonas points out, controllers should be very simple and are intended to coordinate various "services" which are used to respond to the request. Are the flows of requests really all that different from calendar to calendar? Or is the data calls needed to grab that data different.
One way to do this would be to factor your calendars behind a common calendar interface (or abstract base class), and then accept the calendar into the controller via a constructor parameter.
public interface ICalendar {
// All your calendar methods
}
public abstract class Calendar {
}
public class GoogleCalendar : Calendar {}
public class ExchangeCalendar : Calendar {}
Then within your CalendarController,
public class CalendarController {
public CalendarController(ICalendar calendar) {}
}
This won't work by default, unless you register a dependency resolver. One quick way to do that is to use NuGet to install a package that sets one up. For example:
Install-Package Ninject.Mvc3
I think this would be a better architecture. But suppose you disagree, let me answer your original question.
The reason you get the ambiguous exception is you have two public Index methods that are not distinguished by an attribute that indicates one should respond to GETs and one to POSTs. All public methods of a controller are action methods.
If the CalendarController isn't meant to be instantiated directly (i.e. it'll always be inherited), then I would make the Index method on that class protected virtual and then override it in the derived class.
If the CalendarController is meant to be instantiated on its own, and the other derived classes are merely "flavors" of it, then you need to make the Index method public virtual and then have each of the derived classes override the Index method. If they don't override it, they're adding another Index method (C# rules, not ours) and you need to distinguish them for MVC's sake.
I think you're on a dangerous path here. A controller should generally be as simple as possible, and only contain the "glue" between e.g. your service layer and the models/views. By moving your general calendar abstractions and vendor specific implementations out of the controllers, you get rid of the coupling between your routes and the calendar implementation.
Edit: I would implement the polymorphism in the service layer instead, and have a factory class in the service layer check your user database for the current user's vendor and instantiate the corresponding implementation of a CalendarService class. This should eliminate the need for checking the calendar vendor in the controller, keeping it simple.
What I mean by coupling to the routes is that your custom URLs is what is currently causing you problems AFAICT. By going with a single controller and moving the complexity to the service layer, you can probably just use the default routes of MVC.
As the other answers suggest, you really should refactor your code so as to not require the multiple controllers in the first place.
However, you can still have your controllers inherit from a base class controller - you simply need to make sure that when you register the routes in the Global.asax.cs, you use the overload that specifies which namespace to find the controllers and action methods for a given route
e.g.
routes.MapRoute(null, "{controller}/{action}", new[] { "Namespace.Of.Controllers.To.USe" });

Resources