Proper way to route to controllers in Umbraco ASP.NET / IApplicationEventHander vs ApplicationEventHandler vs RouteConfig.cs, RenderMvcController etc - c#

I have a Solution structure like this:
MyApp.Core
--Properties
--References
--bin
--Events
|EventHandlers.cs
--Directory
--Controllers
|DirectoryController.cs
--Helpers
|ContextHelpers.cs
--Models
|DirectoryModel.cs
--AnotherSite
--Controllers
--Helpers
--Models
--Services
--Shared
--Controllers
|HomePageController.cs
--Helpers
|Extensions.cs
|app.config
|packages.config
MyApp.Umbraco
--Properties
--References
--bin
etc........
--Views
--Directory
--Partials
|DirectoryFilters.cshtml
|DirectoryBase.cshtml
|DirectoryHome.cshtml
|FDirectory.cshtml
|SDirectory.cshtml
--Partials
--Shared
|Base.cshtml
|Web.config
etc........
My Umbraco instance uses the models and controllers from my "Core" project. There is nested directory structure, because of multiple websites in one installation, in the "Core", and also in the "Views" directory in the Umbraco instance.
I am still fairly noob to .NET MVC, and I understand route hijacking, but the documentation for Umbraco's routing is slim. I have the following:
EventHandlers.cs
namespace MyApp.Core.Events
{
/// <summary>
/// Registers site specific Umbraco application event handlers
/// </summary>
public class MyAppStartupHandler : IApplicationEventHandler
{
public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
}
public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
RegisterCustomRoutes();
}
public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
}
private static void RegisterCustomRoutes()
{
// Custom Routes
RouteTable.Routes.MapUmbracoRoute(
"FDirectory",
"fdirectory/{id}",
new
{
controller = "Directory",
action = "FDirectory",
id = UrlParameter.Optional
},
new PublishedPageRouteHandler(1000));
RouteTable.Routes.MapUmbracoRoute(
"SDirectory",
"sdirectory/{id}",
new
{
controller = "Directory",
action = "SDirectory",
id = UrlParameter.Optional
},
new PublishedPageRouteHandler(1001));
RouteTable.Routes.MapUmbracoRoute(
"HomePage",
"",
new
{
controller = "HomePage",
action = "Index",
id = UrlParameter.Optional
},
new PublishedPageRouteHandler(1002));
}
}
public class PublishedPageRouteHandler : UmbracoVirtualNodeRouteHandler
{
private readonly int _pageId;
public PublishedPageRouteHandler(int pageId)
{
_pageId = pageId;
}
protected override IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext)
{
if (umbracoContext != null)
{
umbracoContext = ContextHelpers.EnsureUmbracoContext();
}
var helper = new UmbracoHelper(UmbracoContext.Current);
return helper.TypedContent(_pageId);
}
}
}
DirectoryController.cs
namespace MyApp.Core.Directory.Controllers
{
public class DirectoryController : RenderMvcController
{
public DirectoryController() : this(UmbracoContext.Current) { }
public DirectoryController(UmbracoContext umbracoContext) : base(umbracoContext) { }
public ActionResult FDirectory(RenderModel model)
{
return CurrentTemplate(new DirectoryModel(model.Content));
}
public ActionResult SDirectory(RenderModel model)
{
return CurrentTemplate(new DirectoryModel(model.Content));
}
}
}
So Umbraco does not install with an App_Start folder. I would like to know what the best approach is for a multi-site installation of Umbraco for registering the routes to the controllers. My implementation works, but it seems like I shouldn't have to create actions for every single page I am going to have in a site, in every controller. I know Umbraco has its own routing, so using Umbraco concepts, ASP.NET MVC concepts, and whatever else is available, what is the best way to implement this type of solution structure? Should I even worry about using a RouteConfig.cs and create a App_Start directory? Or is what I am doing the best approach? Should I use IApplicationEventHandler or ApplicationEventHandler?
Also, I have to hard code the node ID's. I've read that there is a way to Dynamically? And example of this would be great.
Examples of the best way to implement a structured multi-site Umbraco MVC solution is what I am asking for I guess, in regards to routing the controllers, with some detail, or links to strong examples. I have searched and researched, and there are bits and pieces out there, but not really a good example like what I am working with. I am going to have to create a RouteMap for every single page I create at this point, and I don't know if this is the most efficient way of doing this. I even tried implementing a DefaultController, but didn't see the point of that when your solution is going to have multiple controllers.

I'm not entirely sure what you are trying to achieve with this, but I'll try to explain how it works and maybe you can clarify afterwards.
I assume you have the basics of Umbraco figured out (creating document types + documents based on the document types). This is how Umbraco is normally used and it will automatically do routing for you for each of these "content nodes" (documents) you create in a site.
So create a document named document1 and it will be automatically routed in your site at URL: http://localhost/document1. By default this document will be served through a default MVC controller and it will all take place behind the scenes without you having to do anything.
Route hijacking allows you to override this default behavior and "shove in" a controller that lets you interfere with how the request is handled. To use hijacking you create a RenderMvcController with the alias of your document type. That could be HomePageController : RenderMvcController.
This controller should have an action with the following signature:
public override ActionResult Index(RenderModel model)
In this action you are able to modify the model being sent to the view in any way you like. That could be - getting some external data to add on to the model or triggering some logic or whatever you need to do.
This is all automatically hooked up by naming convention and you will not have to register any routes manually for this to work.
The other type of Umbraco MVC controller you can create is a SurfaceController. This one is usually used for handling rendering of child actions and form submissions (HttpPost). The SurfaceController is also automatically routed by Umbraco and will be located on a "not so pretty" URL. However since it is usually really not used for anything but rendering child actions and taking form submits, it doesn't really matter what URL it is located at.
Besides these auto-routed controllers you are of course able to register your own MVC controllers like in any standard MVC website. The one difference though is that unlike a normal ASP.NET MVC website, an Umbraco site does not have the automagical default registration of controllers allowing the routing to "just work" when creating a new controller.
So if you want to have a plain old MVC controller render in an Umbraco site without it being related to a document/node in Umbraco, you would have to register a route for it like you would do in any other MVC site. The best way of doing that is to hook in and add it to the Routes using an ApplicationEventHandler class. That will automatically be triggered during application startup - essentially allowing you to do what you would normally do in App_Start.
Just to be clear though - if you plan on using data from Umbraco, you should not be using normal MVC controllers and should not require any manual route registration to be done. You usually want to render a template/view in context of a document/node created in Umbraco (where you can modify data/properties of the document) and then the route hijacking is the way to go.
From what it looks like, it could seem that the correct way to do what you are trying to do is to simply create two document types:
FDirectory and SDirectory
You click to allow both of these to be created in root and then you create documents called FDirectory and SDirectory and they will be automatically routed on these URLs. Creating a RenderMvcController's called FDirectoryController : RenderMvcController will then make sure it is used to hijack the routing whenever that page is requested.
If you're simply trying to set up a multi-site solution I would suggest you create a Website document type and create a node for each site you want, in the root of your Umbraco content tree. Right click each of these nodes and edit the hostname to be whatever you need it to be. This can also be some "child url" like /fdirectory or /sdirectory in case you need to test this on localhost without using multiple hostnames.
Hope this gives you the pointers needed, otherwise try to explain what you are trying to do and I'll see if I can refine my answer a bit!

Related

Front Controller in MVC c#

Could someone tell me which is the front controller in MVC 4 c# visual studio please?
I mean, i have to do a big application and i want add security to restrict the access to the controllers and actions. I used to do this in the Logistic of the Front Controller in CodeIgniter, adding a token to the session, so if someone wanted to write the route manually on the browser he couldnt access.
I've been reading about [Authorize(Roles="Admin")] and i have to admit that is a solution, but that means i have to write in every method of the all controllers, and i want to have that centralized in the front-controller with IF/ELSE.
PD: If you don't know how to do this, at least try to tell me where can i find the front controller in MVC c# visual studio please.
Thanks for all.
There is no front controller in MVC. You need to create a base controller , And your every controller will inherit Base controller.
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var getControllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var getActionName = filterContext.ActionDescriptor.ActionName;
//Write your code here
}
}
Now Inherit your controller with Base controller.
public class AccountController : BaseController
{
//Your action goes here.
}
There is no such thing as a front controller in ASP MVC. I think the thing you're looking for is some sort of base controller where all of the other controllers inherit from.
You can add this Authorize attribute to methods or classes (whole controllers). If every action needs this attribute I suggest to create a master controller and let every controller inherit from this controller.
Consider using action filters.
http://www.asp.net/mvc/overview/older-versions-1/controllers-and-routing/understanding-action-filters-cs

ASP.Net MVC 4 w/ AttributeRouting and multiple RoutePrefix attributes

TL;DR
I need a way to programtically choose which RoutePrefix is chosen when generating URLs based on the properties of a user in my MVC app
Not TL;DR
I have an MVC 4 app (with the AttributeRouting NuGet package)
Due to the requirements of the hosting environment I have to have two routes for a lot of my actions so that the hosting environment can have different permissions for access.
I am solving this by decorating my controller with with [RoutePrefix("full")] [RoutePrefix("lite)]. which allows each action method to be accessed via /full/{action} and /lite/{action}.
This works perfectly.
[RoutePrefix("full")]
[RoutePrefix("lite")]
public class ResultsController : BaseController
{
// Can be accessed via /full/results/your-results and /lite/results/your-results and
[Route("results/your-results")]
public async Task<ActionResult> All()
{
}
}
However, each user should only use either full or lite in their urls, which is determined by some properties of that user.
Obviously when I use RedirectToAction() or #Html.ActionLink() it will just choose the first available route and won't keep the "correct" prefix.
I figured I can override the RedirectToAction() method as well as add my own version of #Html.ActionLink() methods.
This will work, but it will involve some nasty code for me to generate the URLs because all I get is a string representing the action and controllers, but not the reflected types. Also there might be route attributes such as in my example, so I am going to have to replicated a lot of MVCs built in code.
Is there a better solution to the problem I am trying to solve?
How about something like:
[RoutePrefix("{version:regex(^full|lite$)}")]
Then, when you create your links:
#Url.RouteUrl("SomeRoute", new { version = "full" })
Or
#Url.RouteUrl("SomeRoute", new { version = "lite" })
You could even do the following to just keep whatever was already set:
#Url.RouteUrl("SomeRoute", new { version = Request["version"] })
I ended up finding a solution to this
I just overrided the default routes to include this. ASP.Net automatically keeps the usertype value and puts it back in when it regenerates the routes
const string userTypeRegex = "^(full|lite)$";
routes.Add("Default", new Route("{usertype}/{controller}/{action}/{id}",
new { controller = "Sessions", action = "Login", id = UrlParameter.Optional }, new { usertype = userTypeRegex }));
I found that this didn't work with the Route or RoutePrefix attributes, and so I had to remove all of them. Forcing me to add specific routes in these cases
routes.Add("Profile-Simple", new Route("{usertype}/profile/simple",
new { controller = "ProfileSimple", action = "Index" }, new { usertype = userTypeRegex }));
I thought that a half-dozen hard coded routes in my RouteConfig file was a better solution than having to manually add values to each place I generated a URL (as in Chris's solution).

How can I use asp.net MVC Areas to setup an application to serve my different clients using same code base

I am seeking help in setup an application so that I can give my clients their own url for browsing.
I am thinking about creating asp.net MVC application and by using AREAS feature ( I will consider each area as my client) I will develop individual application for my client and provide them the url which will serve a their own application running.
Basically, I want to keep my all clients in one application but give them different url.
As areas works as follows:
localhost:5699 -- it will land to default home controller and index page
ocalhost:5699/area1/home/index - it lands to Home controller or Area and renders index view of this area
and so on for another area.
So. I want to ask, can i use this approach to give my clients unique url which I can map to particular Area of application and client can browse simple typing their url and that land to index page of that area?
for example:
www.area1.com -- I want to map this url to localhost/5699/area1/home/index.aspx
www.area2.com -- I want to map this url to localhost/5699/area2/home/index.aspx
Please help, how can i will setup all above in production and development environment
Basically, i want to setup my application such that if my client want different UI and additional functionality I can easily alter respective controller.
If I understand the question correctly, I think you can accomplish your goal by way of the Route and RoutePrefix attributes. These attributes will decorate controllers and methods and give you the ability to tweak the URL's exactly how you want it.
localhost/5699/area1/home/index
[RoutePrefix("area1")]
public class Area1Controller: ApiController
{
[Route("home/index")]
public ActionResult Index()
{
// controller logic here
}
}
localhost/5699/area2/home/index
[RoutePrefix("area2")]
public class Area2Controller: ApiController
{
[Route("home/index")]
public ActionResult Index()
{
// controller logic here
}
}

skip all Umbraco magic for single method

I was just assigned to implement one functionality in project that uses Umbraco. My job is to basically generate specific XML and return it to user. However i cannot get it to work, because when i create new controller (i've tried creating
Controller, RenderMvcController and SurfaceController
) and method in it (also if i just create new method in existing controller), i get error 404 after typing url into browser. Example: I create TestController and method Index in it. I've tried combinations where TestController was derived from RenderMvcController or SurfaceController or just Controller. After compiling, etc. when i run
http://my_address/Test
or
http://my_address/Test/Index
i get 404 error from umbraco. I looked at another pages in umbraco that were already in project and they all are also configured somehow in umbraco web panel:
http://my_address/umbraco
I aslo tried adding new methods to existings controllers, but no luck (again 404 errors). I've never worked with umbraco and i don't know how to configure it. I just want to know if there is any way to create method which will be accessible at:
http://my_address/MyMethod
or
http://my_address/MyController/MyMethod
and would return just exactly what i will program it to (without any Views, Partial Views, etc. - i can set Headers and ContentType manually and my content is pure text) in an existing Umbraco project without having to deal with umbraco admin panel?
Thanks for any help :)
//Edit
My mind is officially blown... My response is culture dependent (i mean i pull different data from db depending on country), but it's not as simple as
CurrentCulture.CultureInfo
Umbraco is configured to return different culture based on domain extension (Germany for .de, Great Britain for .co.uk, and Dennmark for .dk - it's just a manual configuration in umbraco admin panel assigning different culture info and views to different hostnames). Regular controllers get this modified culture from
RenderModel.CurrentCulture
passed as argument to controller's method. Is there a way to create umbraco controller/method/anthing that will not have layout/model assigned to it (so i can display pure XML data i receive from external service) and still have access to umbraco's RenderModel's culture? What i am trying to create is if user types url:
http://my_address.de/myController/myMethod
my controller will get current culture, call external service passing culture as parameter and display received data without wrapping it in any views. Example:
public class myController : SomeBaseUmbracoControllerOrsomething
{
public string/XmlDocument/ActionResult myMethod(RenderModel model)
{
int countryId = myFunctionToTranslateCultureToCountryId(model.CurrentCulture);
return MethodThatCallsExternalServiceAndReturnsXml(countryId);
}
}
Sorry for confusion, but i've learned about this whole mess with countries just now...
You don't want to use
controller, because this is not picked up by umbraco routing process
you don't want to use RenderMvcController, because this is overkill
you don't want to use Surfacecontroller because you are not using a Child action or form.
What you need is a UmbracoApiController (http://our.umbraco.org/documentation/Reference/WebApi/) or is your umbraco version is PRE 6.1 then use /Base extention (http://our.umbraco.org/documentation/Reference/Api/Base/Index)
Or if you really want to skip ALL umbraco magic for a certain route, add the path to the web.config/AppSettings/umbracoReservedUrls.

Change ASP.NET MVC Routes dynamically

usually, when I look at a ASP.Net MVC application, the Route table gets configured at startup and is not touched ever after.
I have a couple of questions on that but they are closely related to each other:
Is it possible to change the route table at runtime?
How would/should I avoid threading issues?
Is there maybe a better way to provide a dynamic URL? I know that IDs etc. can appear in the URL but can't see how this could be applicable in what I want to achieve.
How can I avoid that, even though I have the default controller/action route defined, that default route doesn't work for a specific combination, e.g. the "Post" action on the "Comments" controller is not available through the default route?
Background: Comment Spammers usually grab the posting URL from the website and then don't bother to go through the website anymore to do their automated spamming. If I regularly modify my post URL to some random one, spammers would have to go back to the site and find the correct post URL to try spamming. If that URL changes constantly I'd think that that could make the spammers' work more tedious, which should usually mean that they give up on the affected URL.
I would consider to implement my own IRouteHandler and put some custom logic in my custom ControllerActionInvoker. How it would work ? The route table wouldn't dynamically change but you could check in your custom ControllerActionInvoker for a random parameter in the route path and invoke or not the corresponding action.
My route :
routes.Add
(
new Route
(
"blog/comment/{*data}",
new RouteValueDictionary(new {controller = "blog", action = "comment", data = ""}),
new MyRouteHandler()
)
);
My I route handler :
class MyRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MyHttpHandler(requestContext);
}
}`
My handler :
class MyHttpHandler : MvcHandler
{
public MyHttpHandler(RequestContext requestContext) : base(requestContext)
{
}
protected override void ProcessRequest(HttpContextBase httpContext)
{
IController controller = new BlogController();
(controller as Controller).ActionInvoker = new MyActionInvoker();
controller.Execute(RequestContext);
} }`
and my action ivoker where the custom logic for handling an action or not should be coded :
class MyActionInvoker : ControllerActionInvoker
{
protected override ActionResult InvokeActionMethod(MethodInfo methodInfo, IDictionary<string, object> parameters)
{
var data = ControllerContext.RouteData.GetRequiredString("data");
// put my custom logic to check whetever I'll handle the action or not. The data could be a parameter in the database for that purpose.
return base.InvokeActionMethod(methodInfo, parameters);
}
}
I don't know it it's the best solution but for now it's the one that comes to my mind.
Considering the actual problem background, the usual approach is to include a dynamically created transaction number. It should be stored in a hidden form field as well as in the server side session dictionary and only be valid for exactly one request.
I think today a lot of frameworks provide such a security mechanism; whereas this attack type is known as Cross-Site-Request-Forgery (csrf).

Categories

Resources