I am trying to create a method that will allow me to replace any occurrence of {....}
string with some values.
Let me give you an example:
public const string ProductsApi = "/api/companies/{companyId:int}/stores/{storeKey}/products";
This is a minimal API endpoint. In this particular endpoint we have 2 values to be replaced (this is optional, can be 1+ values to replace). So if companyId = 55
and storeKey = "se2"
, this will be the result:
public const string ProductPathForHttpClient = "/api/companies/55/stores/se2/products";
To minimize possibility for mistakes, I would like to have a string extension method, let's call it Interpolate(...)
, to do this:
var productsPathForHttpClient = roductsApi.Interpolate(55, "se2");
So, Interpolate
would find 1st occurrence of {..}
, and replace it with 1st value, then 2nd occurrence, and so on...
I can do this by searching for {
(start) and }
(end) and then replace this string with value, and then search for next occurrence, etc.
I am wondering if this is possible with regex? Basically I am trying to minimize mistakes to API calls, by defining the API, and then replace any {..}
with actual values for HttpClient
path.
Although Anton answer is correct, I will generalize it a little bit:
public static class TemplateHelper
{
public static string Replace(string template, IDictionary<string, object> replacements, bool throwOnNotFound = false)
{
if (replacements == null)
throw new ArgumentNullException(nameof(replacements));
if (template == null)
return null;
return TemplatePattern.Replace(template, x => OnMatch(x, replacements, throwOnNotFound));
}
private static string OnMatch(Match match, IDictionary<string, object> replacements, bool throwOnNotFound)
{
var name = match.Groups["name"].Value;
if (replacements.TryGetValue(name, out var value))
{
return value?.ToString() ?? string.Empty;
}
if (throwOnNotFound)
{
throw new KeyNotFoundException($"Variable name '{name}' not found in replacement list");
}
return match.Value;
}
private static readonly Regex TemplatePattern = new Regex(@"{(?<name>\w+)(?<type>:\w+)?}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
And some tests/examples of usage:
[TestFixture]
public class TemplateHelperTests
{
public static IEnumerable<TestCaseData> GetTestCases()
{
yield return T(
"/api/companies/{companyId:int}/stores/{storeKey}/products",
"/api/companies/55/stores/se2/products",
new Dictionary<string, object>()
{
{ "companyId", 55 },
{ "storeKey", "se2" }
}).SetName("simple");
yield return T(
"/api/companies/{companyId:int}/stores/{storeKey}/products",
"/api/companies/55/stores/{storeKey}/products",
new Dictionary<string, object>()
{
{ "companyId", 55 }
}).SetName("not_found");
yield return T(
"/api/companies/{companyId:int}/stores/{storeKey}/products",
"/api/companies/55/stores/se2/products",
new Dictionary<string, object>()
{
{ "companyId", 55 },
{ "storeKey", "se2" },
{ "foo", "bar" }
}).SetName("too_much");
}
private static TestCaseData T(string template, string expected, Dictionary<string, object> replacements)
{
return new TestCaseData(template, expected, replacements);
}
[Test]
[TestCaseSource(nameof(GetTestCases))]
public void Check(string template, string expected, Dictionary<string, object> replacements)
{
var actual = TemplateHelper.Replace(template, replacements);
actual.Should().Be(expected);
}
}