String Route Constraint
Asked Answered
B

2

8

I have an ASP .Net Core 1.1 MVC Web API. How can I have a string route constraint in a controller action?

I have the following two actions:

/ GET: api/Users/5
[HttpGet("{id:int}")]
[Authorize]
public async Task<IActionResult> GetUser([FromRoute] int id)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    User user = await _context.User.SingleOrDefaultAsync(m => m.UserId == id);

    if (user == null)
        return NotFound();

    return Ok(user);
}

// GET: api/Users/abcde12345
[HttpGet("{nameIdentifier:string}")]
[Authorize]
public async Task<IActionResult> GetUserByNameIdentifier([FromRoute] string nameIdentifier)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    User user = await _context.User.SingleOrDefaultAsync(m => m.NameIdentifier == nameIdentifier);

    if (user == null)
        return NotFound();

    return Ok(user);
}

The first one works but the second doesn't - .Net doesn't like the "string" constraint. So basically, id I execute an HTTPGET request again:

http://mywebsite.com/api/users/5

it must execute the first action and if I request

http://mywebsite.com/api/users/abcde

it must execute the second one... Any ideas? Thanks...

Birnbaum answered 22/6, 2017 at 8:53 Comment(0)
S
15

As mentioned by @Aistis, the route data by default is string. That is the reason there is no route constraint to enforce the data type as string. The string constraint you are using is useful if you want to restrict the value to a specific string value, it can not be used to enforce the type as string. There are couple of options to achieve what you are trying to do.

1.Use regex route constraint. Note that you need to change the regular expression based on your needs.

// GET: api/Users/abcde12345
[HttpGet("{nameIdentifier:regex(^[[a-zA-Z]])}")]
[Authorize]
public async Task<IActionResult> GetUserByNameIdentifier([FromRoute] string nameIdentifier)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    User user = await _context.User.SingleOrDefaultAsync(m => m.NameIdentifier == nameIdentifier);

    if (user == null)
        return NotFound();

    return Ok(user);
}

2.You can implement your own route constraint as below, again change the regular expression to suit your need.

public class NameIdRouteConstraint : RegexRouteConstraint
{
    public NameIdRouteConstraint() : base(@"([A-Za-z]{3})([0-9]{3})$")
    {
    }
}

Now, you can map this custom route constraint in Startup.cs as below

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();
    services.Configure<RouteOptions>(options =>
         options.ConstraintMap.Add("nameId", typeof(NameIdRouteConstraint )));
}

The custom route constraint "nameId" is ready to use as below

 // GET: api/Users/abcde12345
[HttpGet("{nameIdentifier:nameId}")]
[Authorize]
public async Task<IActionResult> GetUserByNameIdentifier([FromRoute] string nameIdentifier)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    User user = await _context.User.SingleOrDefaultAsync(m => m.NameIdentifier == nameIdentifier);

    if (user == null)
        return NotFound();

    return Ok(user);
}
Shive answered 24/6, 2017 at 4:16 Comment(1)
Thanks Pradeep. Very detailed answer!Birnbaum
M
1

Not sure if I understand question correctly, but everything that comes via http is a string, asp.net can convert string into int like you have in first method, but you don't have to enforce that string comes from your request, all data by defaul will be in a string format.

abc412 is a string, so is 12352 in a string format, if you want to ensure that only alphabetical characters are allowed, you have to perform that check inside your controller once you receive data. Your method already receives data as a string via your controller header

    public async Task<IActionResult> GetUserByNameIdentifier([FromRoute] string nameIdentifier)

What you need to do to check for allowed data inside the controller method and proceed accordingly.

You could also just have two separate apicalls, search by id, search by name

Maladjusted answered 22/6, 2017 at 10:20 Comment(1)
Thank you so much AistisBirnbaum

© 2022 - 2024 — McMap. All rights reserved.