DNN Persona Bar module routing - dotnetnuke-9

I am trying to create dnn persona bar module for dnn9,
Front-end part sends request to the url like - http://dnndev.me/API/personaBar/ControllerName/ActionName
and I get error -
Unable to locate a controller for http://dnndev.me/API/personaBar/ControllerName/ActionName
Searched in namespaces: Dnn.PersonaBar.AdminLogs.Services, Dnn.PersonaBar.ConfigConsole.Services, Dnn.PersonaBar.CssEditor.Services, Dnn.PersonaBar.Extensions.Services, Dnn.PersonaBar.Licensing.Services, Dnn.PersonaBar.Pages.Services, Dnn.PersonaBar.Recyclebin.Services, Dnn.PersonaBar.Roles.Services, Dnn.PersonaBar.Security.Services, Dnn.PersonaBar.Seo.Services, Dnn.PersonaBar.Servers.Services, Dnn.PersonaBar.SiteImportExport.Services, Dnn.PersonaBar.Sites.Services, Dnn.PersonaBar.SiteSettings.Services, Dnn.PersonaBar.SqlConsole.Services, Dnn.PersonaBar.TaskScheduler.Services, Dnn.PersonaBar.Themes.Services, Dnn.PersonaBar.UI.Services, Dnn.PersonaBar.Users.Services, Dnn.PersonaBar.Vocabularies.Services.
the question is how to add my namespace to this list to
register custom api controller derived from DnnApiController ?
For SPA module it's simple with ServiceRouteMapper, is it posible for DNN persona bar module?

For a PersonaBar extension, your webapi service controllers should inherit from PersonaBarApiController.
[MenuPermission(MenuName = "Dnn.Users")]
public class UsersController : PersonaBarApiController
{
[HttpPost]
public HttpResponseMessage CreateUser(CreateUserContract contract)
{
...
}
}
Your dnn manifest should look something like this to install as PersonaBar menu extension:
<package name="Dnn.PersonaBar.Users" type="PersonaBar" version="01.06.00">
...
<component type="PersonaBarMenu">
<menu>
<identifier>Dnn.Users</identifier>
<moduleName>Users</moduleName>
<controller>Dnn.PersonaBar.Users.Components.UsersMenuController, Dnn.PersonaBar.Users</controller>
<resourceKey>nav_Users</resourceKey>
<path>Users</path>
<parent>Manage</parent>
<order>10</order>
<defaultPermissions>Administrators</defaultPermissions>
</menu>
</component>
...
</package>
This results in endpoint path: /API/PersonaBar/Users/CreateUser
Full example source code here

Related

Hybris + swagger integration swagger-ui.html UnknownResourceError

I'm trying to integrate swagger in MYcommercewebservices.
I read post and done all steps listed on it, but still having this error.
https://localhost:9002/mycommercewebservices/v2/v2/api-docs working fine. https://localhost:9002/mycommercewebservices/v2/swagger-ui.html - return UnknownResourceError.
Furthermore - if I navigate to https://localhost:9002/mycommercewebservices/swagger-ui.html (without 'v2') it'll show me this message (javascript alert):
Unable to infer base URL. This is common when using dynamic servlet
registration or when the API is behind an API Gateway. The base URL is
the root of where all the swagger resources are served. For e.g. if
the API is available at http://example.org/api/v2/api-docs then the
base URL is http://example.org/api/. Please enter the location
manually:
I found this controller, and probably part of the problem was in it because it was throwing an exception when I navigated to https://localhost:9002/mycommercewebservices/v2/swagger-ui.html
#Controller
public class DefaultController
{
#RequestMapping
public void defaultRequest(final HttpServletRequest request)
{
throw new UnknownResourceException("There is no resource for path " + YSanitizer.sanitize(request.getRequestURI()));
}
}
Now I disabled controller, but still having the same exception, but now it's in json format instead of .xml.
Thank you!
The main problem was in DefaultController (in MYcommercewebservices)
#Controller
public class DefaultController
{
#RequestMapping
public void defaultRequest(final HttpServletRequest request)
{
throw new UnknownResourceException("There is no resource for path " + YSanitizer.sanitize(request.getRequestURI()));
}
}
It was catching my request and throwing the exception.
When I disabled this controller, I continued to receive an exception, but now it was in json format(before it was in xml).
Than I added this to springmvc-v2-servlet.xml
<mvc:default-servlet-handler/>
<mvc:resources mapping="swagger-ui.html" location="classpath:/META-INF/resources/"/>
<mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/"/>
Now UI works fine!
Also there were another manipulation before all this, but you can find them in hybris experts(quite big post).

ApiController with CORS does not like Anonymous requests

I'm hosting an ASP.NET MVC project in Azure web apps.
In this project I'm using an ApiController to serve data to a client program.
This Api controller has a method defined as such:
[AllowAnonymous]
[RoutePrefix("api/v1/search")]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class CompanyDataController : APIController
{
[Route("companies")]
public string CompanySearch(string request)
{
return "well hello there beautiful";
}
}
When I try to make requests to this controller after its been published to Azure I get this error:
"The HTTP request was forbidden with client authentication scheme 'Anonymous'."
I can access the rest of the website without issue.
I have tried to allow anonymous access with a few variations of this in <system.web>:
<authentication mode="None" />
<authorization>
<allow users="?"/>
</authorization>
But that has made no difference.. any bright ideas?
From the link I posted in comments above... (wouldn't let me mark this as duplicate since the answer below wasn't marked as an answer!)
Since there is already an existing custom authorization filter on the class/controller level, therefore, to override a specific action handler (the method) and have it work without any authorization filters, we need to override the filter at the controller/class level. So adding the OverrideAuthorization filter did the trick. Now AllowAnonymous will be to do its magic.
[Route("api/accounts/{accountNumber}/GetX")]
[AllowAnonymous]
[OverrideAuthorization]
[HttpGet]
public HttpResponseMessage GetX(string accountNumber)
{
// Process
// ..
// ..
}

Web Api 2 global route prefix for route attributes?

I'd like to expose a company's api by two ways:
api.company.com (pure WebApi web site)
company.com/api (add WebApi to existing MVC5 company site)
So, I placed models/controllers in a separate assembly and reference it from both web sites.
Also, I use route attributes:
[RoutePrefix("products")]
public class ProductsController : ApiController
Now, the controller above can be accessed by:
api.company.com/products which is good
company.com/products which I'd like to change to company.com/api/products
Is there a way to keep using route attributes and setup MVC project so it adds "api" for all routes?
So this is probably not the only way you could do it, but this is how I would do it:
Create your own Attribute that inherits from RoutePrefixAttribute
Override the Prefix property and add some logic in there to prepend "api" to the prefix if running on the desired server.
Based on a setting in your web.config, prepend to the route or not.
public class CustomRoutePrefixAttribute : RoutePrefixAttribute
{
public CustomRoutePrefixAttribute(string prefix) : base(prefix)
{
}
public override string Prefix
{
get
{
if (Configuration.PrependApi)
{
return "api/" + base.Prefix;
}
return base.Prefix;
}
}
}
EDIT
(The below option is no longer supported as of Web API 2.2)
Alternatively you could also specify more than one route prefix:
[RoutePrefix("api/products")]
[RoutePrefix("products")]
public class ProductsController : ApiController
You can use Map on IAppBuilder
So Startup class will looks something like this
class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/api", map =>
{
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
map.UseWebApi(config);
});
}
}
Another option would be to forward traffic from one site to your Web API. Are you set on hosting two instances? If not, you could host only the one instance under say api.company.com/products. On your MVC company site implement an HttpHandler to redirect all traffic matching /api/* to api.company.com:
a. Create the handler in your MVC app:
public class WebApiHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string url = "api.company.com" + context.Request.RawUrl.Replace("/api","");
//Create new request with context.Request's headers and content
//Write the new response headers and content to context.Response
}
}
b. Register the handler in your web.config:
<configuration>
<system.web>
<httpHandlers>
<add verb="*" path="api/*" type="Name.Space.WebApiHandler" validate="false" />
</httpHandlers>
</system.web>
</configuration>
c. Enable CORS in your Web API if you haven't done so already.
You can just implement your api service as www.mycompany.com/api.
Then use UrlRewrite to map api.mycompany.com to www.mycompany.com/api
We even support this method of UrlRewrite in link generation, so if you generate links from the api.mycompany.com, your links will point to api.mycompany.com/controller/id.
Note that this is the only form of URL rewrite that works correctly for MVC link generation (api.xxx.yyy -> www.xxx.yyy/api)

WCF Session Service hosted in ASP.NET MVC 2.0 application

I have a must to host WCF Service using WCF Session mechanism.
I've read http://msdn.microsoft.com/en-us/library/ms733040.aspx but it is not enough...
My simple scenearion:
I have solution with 4 projects.
First - SessionWCF.Base, it is simple Class Library that contains base interface IServiceBase for my service.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace SessionWCF.Base
{
[ServiceContract(SessionMode = SessionMode.Required)]
public interface IServiceBase
{
[OperationContract(IsInitiating = true, IsTerminating = false)]
void BeginSession(string message);
[OperationContract(IsInitiating = false, IsTerminating = false)]
string GetMessage(int number);
[OperationContract(IsInitiating = false, IsTerminating = true)]
void EndSession();
}
}
Second - SessionWCF.Lib, it is WCF Class Library that contains service interface ISessionService and service class SessionService , it has project reference to SessionWCF.Base
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using SessionWCF.Base;
namespace SessionWCF.Lib
{
[ServiceContract(SessionMode = SessionMode.Required)]
public interface ISessionService : IServiceBase
{
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Activation;
namespace SessionWCF.Lib
{
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class SessionService : ISessionService
{
string message = "";
#region ISessionService Members
public void BeginSession(string message)
{
this.message = message;
}
public string GetMessage(int number)
{
return "message: " + message + " number: " + number;
}
public void EndSession()
{
message = "";
}
#endregion
}
}
Third - SessionWCF.Web it is ASP.NET MVC 2.0 application that has inside SessionService.svc file. I've deleted code behind and opened XML editor, this service is pointed to service from SessionWCF.Lib, and of course this project has reference to SessionWCF.Lib.
SessionService.svc:
<%# ServiceHost Language="C#" Debug="true" Service="SessionWCF.Lib.SessionService" CodeBehind="SessionWCF.Lib.SessionService.cs" %>
Web.config:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<services>
<service behaviorConfiguration="SessionServiceBehavior" name="SessionWCF.Web.SessionService">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="largeMessageHttpBinding" contract="SessionWCF.Lib.ISessionService">
<identity>
<dns value="**********"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
<host>
<baseAddresses>
<add baseAddress="http://**********/SessionWCF/SessionService.svc"/>
</baseAddresses>
</host>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="largeMessageHttpBinding" maxReceivedMessageSize="10485760">
<readerQuotas maxArrayLength="100000"/>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
<behavior name="SessionServiceBehavior">
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="True"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Fourth - SessionWCF.WPF it is standard WPF application that contanins SessionProxy class and in xaml form click event to call web service. This project has project reference to first one SessionWCF.Base.
SessionProxy class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SessionWCF.Base;
using System.ServiceModel;
namespace SessionWCF.WPF
{
public class SessionProxy
{
public IServiceBase Proxy { get; set; }
public SessionProxy(string url)
{
WSHttpBinding binding = new WSHttpBinding();
binding.ReceiveTimeout = new TimeSpan(0, 10, 0);
binding.OpenTimeout = new TimeSpan(0, 1, 0);
ChannelFactory<IServiceBase> factory = new ChannelFactory<IServiceBase>(binding,
new EndpointAddress(url));
Proxy = factory.CreateChannel();
}
}
}
Click event in xaml form:
private void Button_Click(object sender, RoutedEventArgs e)
{
string url = "http://**********/SessionWCF/SessionService.svc";
SessionProxy client = new SessionProxy(url);
client.Proxy.BeginSession("my message");
string msg = client.Proxy.GetMessage(666);
client.Proxy.EndSession();
txtMsg.Text = msg;
}
Now:
When I call web service in web browser I've get following error:
Error in '/SessionWCF' Application.
Contract requires Session, but Binding 'BasicHttpBinding' doesn't support it or isn't configured properly to support it.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: Contract requires Session, but Binding 'BasicHttpBinding' doesn't support it or isn't configured properly to support it.
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[InvalidOperationException: Contract requires Session, but Binding 'BasicHttpBinding' doesn't support it or isn't configured properly to support it.]
System.ServiceModel.Description.DispatcherBuilder.BuildChannelListener(StuffPerListenUriInfo stuff, ServiceHostBase serviceHost, Uri listenUri, ListenUriMode listenUriMode, Boolean supportContextSession, IChannelListener& result) +16376242
System.ServiceModel.Description.DispatcherBuilder.InitializeServiceHost(ServiceDescription description, ServiceHostBase serviceHost) +1940
System.ServiceModel.ServiceHostBase.InitializeRuntime() +82
System.ServiceModel.ServiceHostBase.OnOpen(TimeSpan timeout) +64
System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout) +789
System.ServiceModel.HostingManager.ActivateService(String normalizedVirtualPath) +287
System.ServiceModel.HostingManager.EnsureServiceAvailable(String normalizedVirtualPath) +1132
[ServiceActivationException: The service '/SessionWCF/SessionService.svc' cannot be activated due to an exception during compilation. The exception message is: Contract requires Session, but Binding 'BasicHttpBinding' doesn't support it or isn't configured properly to support it..]
System.Runtime.AsyncResult.End(IAsyncResult result) +890624
System.ServiceModel.Activation.HostedHttpRequestAsyncResult.End(IAsyncResult result) +180062
System.Web.CallHandlerExecutionStep.OnAsyncHandlerCompletion(IAsyncResult ar) +136
Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.1
When I call it in my xaml event I get ServiceActivationException:
The requested service, 'http://**********/SessionWCF/SessionService.svc' could not be activated. See the server's diagnostic trace logs for more information.
Is it wrong configuration in web.config?
Maybe I'm missing something in service attributes?
And the most important. Why it alerts me about BasicHttpBinding when I'm not using it ???
Any one could help me with this please? It is critical to my current project...
Regards,
Daniel Skowroński
UPDATE:
#marc_s
Firstly:
I think that server-side is wrong because when I simply paste url
'http://**********/SessionWCF/SessionService.svc' in any web browser I'll get error
"Contract requires Session, but Binding 'BasicHttpBinding' doesn't support it or isn't configured properly to support it. " instead metadata...
Secondly:
In my client WPF application I have always two options:
First - Create service reference and IDE will automatically generate proxy class and add all configuration to app.config.
Here I can't do that because I'm getting the same error as in web browser when I point to web service in Service Reference designer.
Second - Create poxy manually and app binding configuration from code, this gives me opportunity create proxy step by step, but it seems that ServiceActivationException it is the same problem "ACTIVATION", you can see in stack trace this lines:
[ServiceActivationException: The service '/SessionWCF/SessionService.svc' cannot be activated due to an exception during compilation. The exception message is: Contract requires Session, but Binding 'BasicHttpBinding' doesn't support it or isn't configured properly to support it..]
System.Runtime.AsyncResult.End(IAsyncResult result) +890624
System.ServiceModel.Activation.HostedHttpRequestAsyncResult.End(IAsyncResult result) +180062
System.Web.CallHandlerExecutionStep.OnAsyncHandlerCompletion(IAsyncResult ar) +136
Regards,
Daniel Skowroński
UPDATE:
#marc_s
I don't this it is the case because:
Firstly:
<services>
<service name="SessionWCF.Web.SessionService"
behaviorConfiguration="SessionServiceBehavior">
Service name it is a name of web service file inside asp.net application, so it points to SessionService.svc which belongs to SessionWCF.Web assembly (the same name as project).
Secondly:
<%# ServiceHost Language="C#" Debug="true"
Service="SessionWCF.Lib.SessionService"
CodeBehind="SessionWCF.Lib.SessionService.cs" %>
Service= is a factory method that gets "type" of the service to create, it also needs class description so CodeBehind= must be pointed to SessionService.cs file where factory method can find SessionService type inside SessionWCF.Lib assembly.
Mentioned two statements are not the issue because when NOT using State Service scenario this works like a charm...
I believe that for the State Service it must me configure something more in web.config are I'm missing something in interface/class description in WCF Class Library...
I'm still in critical situation...
Regards,
Daniel Skowroński
UPDATE
#marc_s you wrote
I think you're wrong here on the SVC
file - check out:
msdn.microsoft.com/en-us/library/aa751792.aspx
- the Service=".." attribute must be "The value of the Service attribute is
the common language runtime (CLR) type
name of the service implementation." -
you need to specify the .NET name of
the service implementation class here
! That's your
SessionWCF.Lib.SessionService class.
I agree with you becouse it is exacly what I've wrote :-)
You point this article: http://msdn.microsoft.com/en-us/library/aa751792.aspx
But a few lines below and you will see what it is under the hood:
new ServiceHost( typeof( MyNamespace.MyServiceImplementationTypeName) );
So when I typed:
<%# ServiceHost Language="C#" Debug="true"
Service="SessionWCF.Lib.SessionService"
CodeBehind="SessionWCF.Lib.SessionService.cs" %>
I pointed exacly: SessionWCF.Lib - namespace, SessionService - class where I have my service implemented.
I my example SessionWCF.Lib - it is both assembly name for .dll and namespace inside SessionWCF.Lib project what you can see at the top of this post when I describe second project in my solution, starting by "Second - SessionWCF.Lib, it is ..."
And again this solution WORKS perfectly without Session functionality of WCF, but it is NOT WORKING when I use WCF Session what I need...
Thanks for engagement but issue must be elsewhere...
UPDATE 2010-07-08
#marc_s was right about wrong configuration in web.config.
Proper configuration must have to be the same name as in Wcf Library:
<service behaviorConfiguration="SessionServiceBehavior" name="SessionWCF.Lib.SessionService">
Regards,
Daniel Skowroński
#marc_s was right about wrong configuration in web.config.
Proper configuration must have to be the same name as in Wcf Library:
<service behaviorConfiguration="SessionServiceBehavior" name="SessionWCF.Lib.SessionService">

Asp.Net MVC: How do I enable dashes in my urls?

I'd like to have dashes separate words in my URLs. So instead of:
/MyController/MyAction
I'd like:
/My-Controller/My-Action
Is this possible?
You can use the ActionName attribute like so:
[ActionName("My-Action")]
public ActionResult MyAction() {
return View();
}
Note that you will then need to call your View file "My-Action.cshtml" (or appropriate extension). You will also need to reference "my-action" in any Html.ActionLink methods.
There isn't such a simple solution for controllers.
Edit: Update for MVC5
Enable the routes globally:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapMvcAttributeRoutes();
// routes.MapRoute...
}
Now with MVC5, Attribute Routing has been absorbed into the project. You can now use:
[Route("My-Action")]
On Action Methods.
For controllers, you can apply a RoutePrefix attribute which will be applied to all action methods in that controller:
[RoutePrefix("my-controller")]
One of the benefits of using RoutePrefix is URL parameters will also be passed down to any action methods.
[RoutePrefix("clients/{clientId:int}")]
public class ClientsController : Controller .....
Snip..
[Route("edit-client")]
public ActionResult Edit(int clientId) // will match /clients/123/edit-client
You could create a custom route handler as shown in this blog:
http://blog.didsburydesign.com/2010/02/how-to-allow-hyphens-in-urls-using-asp-net-mvc-2/
public class HyphenatedRouteHandler : MvcRouteHandler{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.RouteData.Values["controller"] = requestContext.RouteData.Values["controller"].ToString().Replace("-", "_");
requestContext.RouteData.Values["action"] = requestContext.RouteData.Values["action"].ToString().Replace("-", "_");
return base.GetHttpHandler(requestContext);
}
}
...and the new route:
routes.Add(
new Route("{controller}/{action}/{id}",
new RouteValueDictionary(
new { controller = "Default", action = "Index", id = "" }),
new HyphenatedRouteHandler())
);
A very similar question was asked here: ASP.net MVC support for URL's with hyphens
I've developed an open source NuGet library for this problem which implicitly converts EveryMvc/Url to every-mvc/url.
Uppercase urls are problematic because cookie paths are case-sensitive, most of the internet is actually case-sensitive while Microsoft technologies treats urls as case-insensitive. (More on my blog post)
NuGet Package: https://www.nuget.org/packages/LowercaseDashedRoute/
To install it, simply open the NuGet window in the Visual Studio by right clicking the Project and selecting NuGet Package Manager, and on the "Online" tab type "Lowercase Dashed Route", and it should pop up.
Alternatively, you can run this code in the Package Manager Console:
Install-Package LowercaseDashedRoute
After that you should open App_Start/RouteConfig.cs and comment out existing route.MapRoute(...) call and add this instead:
routes.Add(new LowercaseDashedRoute("{controller}/{action}/{id}",
new RouteValueDictionary(
new { controller = "Home", action = "Index", id = UrlParameter.Optional }),
new DashedRouteHandler()
)
);
That's it. All the urls are lowercase, dashed, and converted implicitly without you doing anything more.
Open Source Project Url: https://github.com/AtaS/lowercase-dashed-route
Here's what I did using areas in ASP.NET MVC 5 and it worked liked a charm. I didn't have to rename my views, either.
In RouteConfig.cs, do this:
public static void RegisterRoutes(RouteCollection routes)
{
// add these to enable attribute routing and lowercase urls, if desired
routes.MapMvcAttributeRoutes();
routes.LowercaseUrls = true;
// routes.MapRoute...
}
In your controller, add this before your class definition:
[RouteArea("SampleArea", AreaPrefix = "sample-area")]
[Route("{action}")]
public class SampleAreaController: Controller
{
// ...
[Route("my-action")]
public ViewResult MyAction()
{
// do something useful
}
}
The URL that shows up in the browser if testing on local machine is: localhost/sample-area/my-action. You don't need to rename your view files or anything. I was quite happy with the end result.
After routing attributes are enabled you can delete any area registration files you have such as SampleAreaRegistration.cs.
This article helped me come to this conclusion. I hope it is useful to you.
Asp.Net MVC 5 will support attribute routing, allowing more explicit control over route names. Sample usage will look like:
[RoutePrefix("dogs-and-cats")]
public class DogsAndCatsController : Controller
{
[HttpGet("living-together")]
public ViewResult LivingTogether() { ... }
[HttpPost("mass-hysteria")]
public ViewResult MassHysteria() { }
}
To get this behavior for projects using Asp.Net MVC prior to v5, similar functionality can be found with the AttributeRouting project (also available as a nuget). In fact, Microsoft reached out to the author of AttributeRouting to help them with their implementation for MVC 5.
You could write a custom route that derives from the Route class GetRouteData to strip dashes, but when you call the APIs to generate a URL, you'll have to remember to include the dashes for action name and controller name.
That shouldn't be too hard.
You can define a specific route such as:
routes.MapRoute(
"TandC", // Route controllerName
"CommonPath/{controller}/Terms-and-Conditions", // URL with parameters
new {
controller = "Home",
action = "Terms_and_Conditions"
} // Parameter defaults
);
But this route has to be registered BEFORE your default route.
If you have access to the IIS URL Rewrite module ( http://blogs.iis.net/ruslany/archive/2009/04/08/10-url-rewriting-tips-and-tricks.aspx ), you can simply rewrite the URLs.
Requests to /my-controller/my-action can be rewritten to /mycontroller/myaction and then there is no need to write custom handlers or anything else. Visitors get pretty urls and you get ones MVC can understand.
Here's an example for one controller and action, but you could modify this to be a more generic solution:
<rewrite>
<rules>
<rule name="Dashes, damnit">
<match url="^my-controller(.*)" />
<action type="Rewrite" url="MyController/Index{R:1}" />
</rule>
</rules>
</rewrite>
The possible downside to this is you'll have to switch your project to use IIS Express or IIS for rewrites to work during development.
I'm still pretty new to MVC, so take it with a grain of salt. It's not an elegant, catch-all solution but did the trick for me in MVC4:
routes.MapRoute(
name: "ControllerName",
url: "Controller-Name/{action}/{id}",
defaults: new { controller = "ControllerName", action = "Index", id = UrlParameter.Optional }
);

Resources