0

I'm coding a solution in asp.net core where I need to change the route names putting a slash in the name of controllers and their methods. I'm using this code.

public class SlugifyParameterTransformer : IOutboundParameterTransformer
    {
        public string TransformOutbound(object value)
        {          
            if (value == null) { return null; }

            // Slugify value

            return Regex.Replace(
                value.ToString(),
                "(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])",
                "-$1",
                RegexOptions.Compiled)
                .Trim()
                .ToLower();

        }
    }

at Startup.cs


  public IServiceProvider ConfigureServices(IServiceCollection services)
        {

             services.AddMvc(options =>
                                        {     
                                                                       
                                            options.Conventions.Add(new RouteTokenTransformerConvention(
                                                                        new SlugifyParameterTransformer()));
                                        }
                            );
}

I'll give an example.

The project has two controllers

namespace Selling.API.Controllers
{
    [ApiController]
    [Route("api/v2/[controller]")]
    public class SellsController : ControllerBase
    {
        [HttpPost("[action]")]
        public async Task<ActionResult<IResponse<Sells>>> GetAll(){
            // some code here
        }
            
    }
}
namespace Selling.API.Controllers
{
    [ApiController]
    [Route("api/v2/[controller]")]
    public class CustomersController : ControllerBase
    {
        [HttpPost("[action]")]
        public async Task<ActionResult<IResponse<Customers>>> GetAll(){
            // some code here
        }
            
    }
}

If I use the SlugifyParameterTransformer, it will convert routes using slashes, resulting in:

api/v2/sells/get-all
api/v2/customers/get-all

But, I need to ignore sells controller, because some applications use the original format:

api/v2/Sells/GetAll

The code runs perfectly and converts all routes names with slashes, but all controllers are converted and I need to exclude one controller of this convention. I've tried to code some solution in lugifyParameterTransformer class, but without success. How can I solve this?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459

3 Answers3

2

Here is a workaround for your example , you could do it by implementing IControllerModelConvention.

public class DashedRoutingConvention: IControllerModelConvention
{
    public void Apply(ControllerModel controller)
    {
        if (controller.ControllerName != "Home" && controller.ControllerName != "Sells")
        {
            if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null))
            {
                foreach (var controllerSelector in controller.Selectors.Where(x => x.AttributeRouteModel != null))
                {
                    var originalTemp = controllerSelector.AttributeRouteModel.Template;

                    var newTemplate = new StringBuilder();

                    newTemplate.Append(PascalToKebabCase(controller.ControllerName));
                    
                    controllerSelector.AttributeRouteModel = new AttributeRouteModel
                    {
                        Template = originalTemp.Replace("[controller]", newTemplate.ToString())
                    };

                    foreach (var controllerAction in controller.Actions)
                    {
                        foreach (var actionselector in controllerAction.Selectors.Where(x=>x.AttributeRouteModel!=null))
                        {
                            var origTemp = actionselector.AttributeRouteModel.Template;

                            var template = new StringBuilder();

                            template.Append(PascalToKebabCase(controllerAction.ActionName));
                            
                            controllerSelector.AttributeRouteModel = new AttributeRouteModel
                            {
                                  Template = originalTemp.Replace("[action]", newTemplate.ToString())
                            };
                        }
                    }
                }
            } 
        }
    }

    public static string PascalToKebabCase(string value)
    {
        if (string.IsNullOrEmpty(value))
            return value;
        var a= Regex.Replace(
            value,
            "(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])",
            "-$1",
            RegexOptions.Compiled)
            .Trim()
            .ToLower();

        return Regex.Replace(
            value,
            "(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])",
            "-$1",
            RegexOptions.Compiled)
            .Trim()
            .ToLower();
    }
}

Then registering it in Startup.cs

services.AddMvc(options =>
{  
    options.Conventions.Add(new DashedRoutingConvention());
});

It is just for the route template your provided in the example , you may have to unify routing templates on controllers and methods if you want to apply it to all routes.

For no-custom routes on controllers and actions , you could refer to here

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Xueli Chen
  • 11,987
  • 3
  • 25
  • 36
0

I did a little fix in your code

I change this:

 controllerSelector.AttributeRouteModel = new AttributeRouteModel
                            {
                                  Template = originalTemp.Replace("[action]", newTemplate.ToString())
                            };

for it:

  actionselector.AttributeRouteModel = new AttributeRouteModel
                                {
                                    Template = origTemp.Replace("[action]", template.ToString())
                                };

Thank you very much!

0

Thinking about this solution above, I did some improvements in the code where I pass the any Controller Class and the class will ignore or not the convention.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace Sequor.ProcessData.API
{
    public class DashedRoutingConvention : IControllerModelConvention
    {


        private IList<Type> IgnoredControllerList = new List<Type>();

        public DashedRoutingConvention AddControllerToIgnoreList(Type controller)
        {
            this.IgnoredControllerList.Add(controller);
            return this;
        }
        public void Apply(ControllerModel controller)
        {

            var result = IgnoredControllerList.Where( x => x.Name.Equals(string.Format("{0}Controller", controller.ControllerName))).SingleOrDefault();
            if (controller.ControllerName != "Home" && result == null)
            {
                if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null))
                {
                    foreach (var controllerSelector in controller.Selectors.Where(x => x.AttributeRouteModel != null))
                    {
                        var originalTemp = controllerSelector.AttributeRouteModel.Template;

                        var newTemplate = new StringBuilder();

                        newTemplate.Append(PascalToKebabCase(controller.ControllerName));

                        controllerSelector.AttributeRouteModel = new AttributeRouteModel
                        {
                            Template = originalTemp.Replace("[controller]", newTemplate.ToString())
                        };

                        foreach (var controllerAction in controller.Actions)
                        {
                            foreach (var actionselector in controllerAction.Selectors.Where(x => x.AttributeRouteModel != null))
                            {
                                var origTemp = actionselector.AttributeRouteModel.Template;

                                var template = new StringBuilder();

                                template.Append(PascalToKebabCase(controllerAction.ActionName));

                                actionselector.AttributeRouteModel = new AttributeRouteModel
                                {
                                    Template = origTemp.Replace("[action]", template.ToString())
                                };
                            }
                        }
                    }
                }
            }
        }

        public static string PascalToKebabCase(string value)
        {
            if (string.IsNullOrEmpty(value))
                return value;
             return Regex.Replace(
                     value.ToString(),
                    "(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])",
                    "-$1",
                    RegexOptions.Compiled)
                    .Trim()
                    .ToLower();
        }
    }
}

At Startup.cs

  services.AddMvc(options =>
            {
                options.Conventions.Add(new DashedRoutingConvention()
                                            .AddControllerToIgnoreList(typeof(ClassController))
                                            .AddControllerToIgnoreList(typeof(AnotherClassController))
                                        );
            });