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?

2
  • Tried the code ,but I didn't see the result you said .Could you share the expected result and the actual result ? Commented Jan 13, 2020 at 9:45
  • Hello @XueliChen, I improve my question, I'm sorry Commented Jan 13, 2020 at 12:21

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

Comments

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!

Comments

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))
                                        );
            });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.